Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions bt-daemon/src/agents/antigravity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{Agent, Identity, Register, Registrar};
use std::sync::Arc;

pub(crate) struct Antigravity;

pub(crate) const IDENTITY: Identity = Identity {
id: "antigravity",
source: "antigravity",
aliases: &["agy"],
display_name: "Google Antigravity",
};

impl Agent for Antigravity {
fn identity(&self) -> &'static Identity {
&IDENTITY
}
}

impl Register for Antigravity {
fn register(self: Arc<Self>, registrar: &mut Registrar) {
registrar.setup.insert(self);
}
}
23 changes: 23 additions & 0 deletions bt-daemon/src/agents/claude.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{Agent, Identity, Register, Registrar};
use std::sync::Arc;

pub(crate) struct Claude;

pub(crate) const IDENTITY: Identity = Identity {
id: "claude",
source: "claude-code",
aliases: &[],
display_name: "Claude Code",
};

impl Agent for Claude {
fn identity(&self) -> &'static Identity {
&IDENTITY
}
}

impl Register for Claude {
fn register(self: Arc<Self>, registrar: &mut Registrar) {
registrar.setup.insert(self);
}
}
23 changes: 23 additions & 0 deletions bt-daemon/src/agents/codex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{Agent, Identity, Register, Registrar};
use std::sync::Arc;

pub(crate) struct Codex;

pub(crate) const IDENTITY: Identity = Identity {
id: "codex",
source: "codex",
aliases: &[],
display_name: "Codex",
};

impl Agent for Codex {
fn identity(&self) -> &'static Identity {
&IDENTITY
}
}

impl Register for Codex {
fn register(self: Arc<Self>, registrar: &mut Registrar) {
registrar.setup.insert(self);
}
}
23 changes: 23 additions & 0 deletions bt-daemon/src/agents/cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{Agent, Identity, Register, Registrar};
use std::sync::Arc;

pub(crate) struct Cursor;

pub(crate) const IDENTITY: Identity = Identity {
id: "cursor",
source: "cursor",
aliases: &[],
display_name: "Cursor",
};

impl Agent for Cursor {
fn identity(&self) -> &'static Identity {
&IDENTITY
}
}

impl Register for Cursor {
fn register(self: Arc<Self>, registrar: &mut Registrar) {
registrar.setup.insert(self);
}
}
23 changes: 23 additions & 0 deletions bt-daemon/src/agents/grok.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{Agent, Identity, Register, Registrar};
use std::sync::Arc;

pub(crate) struct Grok;

pub(crate) const IDENTITY: Identity = Identity {
id: "grok",
source: "grok",
aliases: &[],
display_name: "Grok",
};

impl Agent for Grok {
fn identity(&self) -> &'static Identity {
&IDENTITY
}
}

impl Register for Grok {
fn register(self: Arc<Self>, registrar: &mut Registrar) {
registrar.setup.insert(self);
}
}
100 changes: 100 additions & 0 deletions bt-daemon/src/agents/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! One type per supported coding agent.
//!
//! Each agent implements the capability traits it supports, such as
//! [`Setup`], and registers itself with the matching registries in
//! [`Register::register`]. A command looks up agents in its own registry, so
//! an agent without a capability simply does not appear there.

mod antigravity;
mod claude;
mod codex;
mod cursor;
mod grok;
mod opencode;
mod pi;

pub(crate) use antigravity::Antigravity;
pub(crate) use claude::Claude;
pub(crate) use codex::Codex;
pub(crate) use cursor::Cursor;
pub(crate) use grok::Grok;
pub(crate) use opencode::OpenCode;
pub(crate) use pi::Pi;

use crate::setup::Setup;
use std::sync::{Arc, OnceLock};

/// The names an agent is known by.
#[derive(Debug)]
pub(crate) struct Identity {
/// The CLI name, which also names the agent's settings file.
pub id: &'static str,
/// The `source` its hooks report and its translator is registered under.
pub source: &'static str,
/// Other accepted spellings.
pub aliases: &'static [&'static str],
pub display_name: &'static str,
}

impl Identity {
pub fn matches(&self, name: &str) -> bool {
self.id == name || self.source == name || self.aliases.contains(&name)
}
}

/// Implemented by every agent type and required by every capability trait,
/// so any registry can find an agent by name.
pub(crate) trait Agent: Send + Sync + 'static {
fn identity(&self) -> &'static Identity;
}

/// The agents that support one capability.
pub(crate) struct Agents<T: ?Sized> {
agents: Vec<Arc<T>>,
}

impl<T: ?Sized> Default for Agents<T> {
fn default() -> Self {
Self { agents: Vec::new() }
}
}

impl<T: ?Sized + Agent> Agents<T> {
pub fn insert(&mut self, agent: Arc<T>) {
self.agents.push(agent);
}

/// Find an agent by its id, source, or an alias.
pub fn get(&self, name: &str) -> Option<&T> {
self.agents
.iter()
.find(|agent| agent.identity().matches(name))
.map(|agent| &**agent)
}
}

/// Every capability registry. Agents add themselves in [`Register::register`].
#[derive(Default)]
pub(crate) struct Registrar {
pub setup: Agents<dyn Setup>,
}

pub(crate) trait Register {
fn register(self: Arc<Self>, registrar: &mut Registrar);
}

/// The registries for all supported agents.
pub(crate) fn registrar() -> &'static Registrar {
static REGISTRAR: OnceLock<Registrar> = OnceLock::new();
REGISTRAR.get_or_init(|| {
let mut registrar = Registrar::default();
Arc::new(Antigravity).register(&mut registrar);
Arc::new(Claude).register(&mut registrar);
Arc::new(Codex).register(&mut registrar);
Arc::new(Cursor).register(&mut registrar);
Arc::new(Grok).register(&mut registrar);
Arc::new(OpenCode).register(&mut registrar);
Arc::new(Pi).register(&mut registrar);
registrar
})
}
23 changes: 23 additions & 0 deletions bt-daemon/src/agents/opencode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{Agent, Identity, Register, Registrar};
use std::sync::Arc;

pub(crate) struct OpenCode;

pub(crate) const IDENTITY: Identity = Identity {
id: "opencode",
source: "opencode",
aliases: &["open-code"],
display_name: "OpenCode",
};

impl Agent for OpenCode {
fn identity(&self) -> &'static Identity {
&IDENTITY
}
}

impl Register for OpenCode {
fn register(self: Arc<Self>, registrar: &mut Registrar) {
registrar.setup.insert(self);
}
}
23 changes: 23 additions & 0 deletions bt-daemon/src/agents/pi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{Agent, Identity, Register, Registrar};
use std::sync::Arc;

pub(crate) struct Pi;

pub(crate) const IDENTITY: Identity = Identity {
id: "pi",
source: "pi",
aliases: &[],
display_name: "Pi",
};

impl Agent for Pi {
fn identity(&self) -> &'static Identity {
&IDENTITY
}
}

impl Register for Pi {
fn register(self: Arc<Self>, registrar: &mut Registrar) {
registrar.setup.insert(self);
}
}
12 changes: 12 additions & 0 deletions bt-daemon/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use clap::{Args, ValueEnum};
use std::ffi::OsString;
use std::path::PathBuf;

use crate::agents::{self, Agent, Identity};
use crate::wire;

/// Arguments for `serve`.
Expand Down Expand Up @@ -194,6 +195,17 @@ pub enum ParentObjectType {
PlaygroundLogs,
}

impl ImportSource {
pub(crate) fn identity(self) -> &'static Identity {
match self {
Self::Codex => agents::Codex.identity(),
Self::Cursor => agents::Cursor.identity(),
Self::Claude => agents::Claude.identity(),
Self::Antigravity => agents::Antigravity.identity(),
}
}
}

impl From<ParentObjectType> for SpanObjectType {
fn from(value: ParentObjectType) -> Self {
match value {
Expand Down
1 change: 1 addition & 0 deletions bt-daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

pub mod paths;

mod agents;
mod args;
mod client;
mod command_output;
Expand Down
26 changes: 15 additions & 11 deletions bt-daemon/src/setup/antigravity.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Google Antigravity: the tracing plugin installed with the `agy` CLI.

use super::common::{edit_object, CommandRunner, FileAccess};
use crate::agents::Antigravity;
use crate::paths;
use crate::setup::Setup;
use anyhow::bail;
use serde_json::Value;
use std::path::Path;
Expand Down Expand Up @@ -51,20 +53,20 @@ fn remove_legacy_registration(config_dir: &Path) -> anyhow::Result<()> {
)
}

fn enable_at(runner: &mut impl CommandRunner, config_dir: &Path) -> anyhow::Result<()> {
fn enable_at(runner: &mut dyn CommandRunner, config_dir: &Path) -> anyhow::Result<()> {
let home = home(config_dir)?;
runner.run_in_home("agy", &["plugin", "install", PLUGIN_SOURCE], home)?;
runner.run_in_home("agy", &["plugin", "enable", PLUGIN], home)?;
remove_legacy_registration(config_dir)
}

fn disable_at(runner: &mut impl CommandRunner, config_dir: &Path) -> anyhow::Result<()> {
fn disable_at(runner: &mut dyn CommandRunner, config_dir: &Path) -> anyhow::Result<()> {
// Antigravity's uninstall command is idempotent when the plugin is absent.
runner.run_in_home("agy", &["plugin", "uninstall", PLUGIN], home(config_dir)?)?;
remove_legacy_registration(config_dir)
}

fn update_at(runner: &mut impl CommandRunner, config_dir: &Path) -> anyhow::Result<()> {
fn update_at(runner: &mut dyn CommandRunner, config_dir: &Path) -> anyhow::Result<()> {
let home = home(config_dir)?;
let plugins = runner.json_in_home("agy", &["plugin", "list"], home)?;
let installed = plugins
Expand All @@ -83,16 +85,18 @@ fn update_at(runner: &mut impl CommandRunner, config_dir: &Path) -> anyhow::Resu
runner.run_in_home("agy", &["plugin", "install", PLUGIN_SOURCE], home)
}

pub(super) fn enable(runner: &mut impl CommandRunner) -> anyhow::Result<()> {
enable_at(runner, &paths::antigravity_config_dir())
}
impl Setup for Antigravity {
fn enable(&self, runner: &mut dyn CommandRunner) -> anyhow::Result<()> {
enable_at(runner, &paths::antigravity_config_dir())
}

pub(super) fn disable(runner: &mut impl CommandRunner) -> anyhow::Result<()> {
disable_at(runner, &paths::antigravity_config_dir())
}
fn disable(&self, runner: &mut dyn CommandRunner) -> anyhow::Result<()> {
disable_at(runner, &paths::antigravity_config_dir())
}

pub(super) fn update(runner: &mut impl CommandRunner) -> anyhow::Result<()> {
update_at(runner, &paths::antigravity_config_dir())
fn update(&self, runner: &mut dyn CommandRunner) -> anyhow::Result<()> {
update_at(runner, &paths::antigravity_config_dir())
}
}

#[cfg(test)]
Expand Down
Loading
Loading