diff --git a/crates/agent_servers/src/agent_servers.rs b/crates/agent_servers/src/agent_servers.rs index b9751d7f63053b..ac6d36c64cab08 100644 --- a/crates/agent_servers/src/agent_servers.rs +++ b/crates/agent_servers/src/agent_servers.rs @@ -2,6 +2,7 @@ mod acp; mod claude; mod custom; mod gemini; +mod goose; #[cfg(any(test, feature = "test-support"))] pub mod e2e_tests; @@ -12,6 +13,7 @@ use collections::HashMap; pub use custom::*; use fs::Fs; pub use gemini::*; +pub use goose::*; use http_client::read_no_proxy_from_env; use project::agent_server_store::AgentServerStore; diff --git a/crates/agent_servers/src/goose.rs b/crates/agent_servers/src/goose.rs new file mode 100644 index 00000000000000..6417525b5c67a2 --- /dev/null +++ b/crates/agent_servers/src/goose.rs @@ -0,0 +1,71 @@ +use std::rc::Rc; +use std::{any::Any, path::Path}; + +use crate::{AgentServer, AgentServerDelegate}; +use acp_thread::AgentConnection; +use anyhow::Result; +use gpui::{App, SharedString, Task}; + +#[derive(Clone)] +pub struct GooseAcp; + +impl AgentServer for GooseAcp { + fn telemetry_id(&self) -> &'static str { + "goose-acp" + } + + fn name(&self) -> SharedString { + "Goose".into() + } + + fn logo(&self) -> ui::IconName { + ui::IconName::Terminal + } + + fn connect( + &self, + root_dir: Option<&Path>, + delegate: AgentServerDelegate, + cx: &mut App, + ) -> Task, Option)>> { + let name = self.name(); + let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string()); + let is_remote = delegate.project.read(cx).is_via_remote_server(); + let default_mode = self.default_mode(cx); + + cx.spawn(async move |cx| { + // Create a command to run "goose acp" + let command = project::agent_server_store::AgentServerCommand { + path: "goose".into(), + args: vec!["acp".to_string()], + env: None, + }; + + let root_dir = std::path::PathBuf::from(root_dir.as_deref().unwrap_or(".")); + + let connection = + crate::acp::connect(name, command, &root_dir, default_mode, is_remote, cx).await?; + Ok((connection, None)) + }) + } + + fn into_any(self: Rc) -> Rc { + self + } +} + +#[cfg(test)] +pub(crate) mod tests { + use project::agent_server_store::AgentServerCommand; + + use super::*; + use std::path::Path; + + pub fn local_command() -> AgentServerCommand { + AgentServerCommand { + path: "goose".into(), + args: vec!["acp".to_string()], + env: None, + } + } +} diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 7334f1cf1283ad..81c7e463b9f404 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -213,6 +213,7 @@ pub enum AgentType { TextThread, Gemini, ClaudeCode, + GooseAcp, NativeAgent, Custom { name: SharedString, @@ -227,6 +228,7 @@ impl AgentType { Self::NativeAgent => "Agent 2".into(), Self::Gemini => "Gemini CLI".into(), Self::ClaudeCode => "Claude Code".into(), + Self::GooseAcp => "Goose".into(), Self::Custom { name, .. } => name.into(), } } @@ -236,6 +238,7 @@ impl AgentType { Self::Zed | Self::NativeAgent | Self::TextThread => None, Self::Gemini => Some(IconName::AiGemini), Self::ClaudeCode => Some(IconName::AiClaude), + Self::GooseAcp => Some(IconName::Terminal), Self::Custom { .. } => Some(IconName::Terminal), } } @@ -246,6 +249,7 @@ impl From for AgentType { match value { ExternalAgent::Gemini => Self::Gemini, ExternalAgent::ClaudeCode => Self::ClaudeCode, + ExternalAgent::GooseAcp => Self::GooseAcp, ExternalAgent::Custom { name, command } => Self::Custom { name, command }, ExternalAgent::NativeAgent => Self::NativeAgent, } @@ -1380,6 +1384,11 @@ impl AgentPanel { cx, ) } + AgentType::GooseAcp => { + self.selected_agent = AgentType::GooseAcp; + self.serialize(cx); + self.external_thread(Some(crate::ExternalAgent::GooseAcp), None, None, window, cx) + } AgentType::Custom { name, command } => self.external_thread( Some(crate::ExternalAgent::Custom { name, command }), None, @@ -1950,6 +1959,32 @@ impl AgentPanel { } }), ) + .item( + ContextMenuEntry::new("New Goose Thread") + .icon(IconName::Terminal) + .disabled(is_via_collab) + .icon_color(Color::Muted) + .handler({ + let workspace = workspace.clone(); + move |window, cx| { + if let Some(workspace) = workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + if let Some(panel) = + workspace.panel::(cx) + { + panel.update(cx, |panel, cx| { + panel.new_agent_thread( + AgentType::GooseAcp, + window, + cx, + ); + }); + } + }); + } + } + }), + ) .map(|mut menu| { let agent_names = agent_server_store .read(cx) diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 94efa767c5f1cd..567aac05898c00 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -169,6 +169,7 @@ enum ExternalAgent { #[default] Gemini, ClaudeCode, + GooseAcp, NativeAgent, Custom { name: SharedString, @@ -190,6 +191,7 @@ impl ExternalAgent { Self::NativeAgent => "zed", Self::Gemini => "gemini-cli", Self::ClaudeCode => "claude-code", + Self::GooseAcp => "goose-acp", Self::Custom { .. } => "custom", } } @@ -202,6 +204,7 @@ impl ExternalAgent { match self { Self::Gemini => Rc::new(agent_servers::Gemini), Self::ClaudeCode => Rc::new(agent_servers::ClaudeCode), + Self::GooseAcp => Rc::new(agent_servers::GooseAcp), Self::NativeAgent => Rc::new(agent2::NativeAgentServer::new(fs, history)), Self::Custom { name, command: _ } => { Rc::new(agent_servers::CustomAgentServer::new(name.clone()))