From 01f879fc1b8b36dbaa0eefeab193bfc3bf99b426 Mon Sep 17 00:00:00 2001 From: Alex Hancock Date: Tue, 31 Mar 2026 16:52:20 -0400 Subject: [PATCH] feat: goose serve (#8209) --- Cargo.lock | 3 +- crates/goose-acp/Cargo.toml | 6 ---- crates/goose-acp/src/bin/server.rs | 57 ------------------------------ crates/goose-cli/Cargo.toml | 1 + crates/goose-cli/src/cli.rs | 55 ++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 65 deletions(-) delete mode 100644 crates/goose-acp/src/bin/server.rs diff --git a/Cargo.lock b/Cargo.lock index b04364d6..1f12205c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4453,7 +4453,6 @@ dependencies = [ "async-stream", "async-trait", "axum", - "clap", "fs-err", "futures", "goose", @@ -4475,7 +4474,6 @@ dependencies = [ "tokio-util", "tower-http", "tracing", - "tracing-subscriber", "url", "uuid", "wiremock", @@ -4496,6 +4494,7 @@ dependencies = [ "anstream 0.6.21", "anyhow", "async-trait", + "axum", "base64 0.22.1", "bat", "bzip2", diff --git a/crates/goose-acp/Cargo.toml b/crates/goose-acp/Cargo.toml index 32f2148c..a271d741 100644 --- a/crates/goose-acp/Cargo.toml +++ b/crates/goose-acp/Cargo.toml @@ -7,10 +7,6 @@ license.workspace = true repository.workspace = true description.workspace = true -[[bin]] -name = "goose-acp-server" -path = "src/bin/server.rs" - [[bin]] name = "generate-acp-schema" path = "src/bin/generate_acp_schema.rs" @@ -44,10 +40,8 @@ url = { workspace = true } # HTTP server dependencies axum = { workspace = true, features = ["ws"] } -clap = { workspace = true } serde = { workspace = true, features = ["derive"] } tower-http = { workspace = true, features = ["cors"] } -tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } async-stream = { workspace = true } http-body-util = "0.1.3" uuid = { workspace = true, features = ["v7"] } diff --git a/crates/goose-acp/src/bin/server.rs b/crates/goose-acp/src/bin/server.rs deleted file mode 100644 index f59f634a..00000000 --- a/crates/goose-acp/src/bin/server.rs +++ /dev/null @@ -1,57 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use goose::builtin_extension::register_builtin_extensions; -use goose::config::paths::Paths; -use goose_acp::server_factory::{AcpServer, AcpServerFactoryConfig}; -use std::net::SocketAddr; -use std::sync::Arc; -use tracing::info; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; - -#[derive(Parser)] -#[command(name = "goose-acp-server")] -#[command(about = "ACP server for goose over HTTP and WebSocket")] -struct Cli { - #[arg(long, default_value = "127.0.0.1")] - host: String, - - #[arg(long, default_value = "3284")] - port: u16, - - #[arg(long = "builtin", action = clap::ArgAction::Append)] - builtins: Vec, -} - -#[tokio::main] -async fn main() -> Result<()> { - let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); - tracing_subscriber::registry() - .with(filter) - .with(tracing_subscriber::fmt::layer().with_target(true)) - .init(); - - register_builtin_extensions(goose_mcp::BUILTIN_EXTENSIONS.clone()); - - let cli = Cli::parse(); - - let builtins = if cli.builtins.is_empty() { - vec!["developer".to_string()] - } else { - cli.builtins - }; - - let server = Arc::new(AcpServer::new(AcpServerFactoryConfig { - builtins, - data_dir: Paths::data_dir(), - config_dir: Paths::config_dir(), - })); - let router = goose_acp::transport::create_router(server); - - let addr: SocketAddr = format!("{}:{}", cli.host, cli.port).parse()?; - info!("Starting goose-acp-server on {}", addr); - - let listener = tokio::net::TcpListener::bind(addr).await?; - axum::serve(listener, router).await?; - - Ok(()) -} diff --git a/crates/goose-cli/Cargo.toml b/crates/goose-cli/Cargo.toml index 42d894fd..6c20a644 100644 --- a/crates/goose-cli/Cargo.toml +++ b/crates/goose-cli/Cargo.toml @@ -63,6 +63,7 @@ clap_complete = "4.5.62" comfy-table = "7.2.2" sha2 = { workspace = true } sigstore-verify = { version = "0.6", default-features = false } +axum.workspace = true [target.'cfg(target_os = "windows")'.dependencies] winapi = { workspace = true } diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index 58735d42..0f9f9898 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -732,6 +732,26 @@ enum Command { builtins: Vec, }, + /// Start ACP server over HTTP and WebSocket + #[command(about = "Start ACP server over HTTP and WebSocket")] + Serve { + #[arg(long, default_value = "127.0.0.1")] + host: String, + + #[arg(long, default_value = "3284")] + port: u16, + + #[arg( + long = "with-builtin", + value_name = "NAME", + help = "Add builtin extensions by name (e.g., 'developer' or multiple: 'developer,github')", + long_help = "Add one or more builtin extensions that are bundled with goose by specifying their names, comma-separated", + value_delimiter = ',', + action = clap::ArgAction::Append + )] + builtins: Vec, + }, + /// Start or resume interactive chat sessions #[command( about = "Start or resume interactive chat sessions", @@ -1009,6 +1029,7 @@ fn get_command_name(command: &Option) -> &'static str { Some(Command::Info { .. }) => "info", Some(Command::Mcp { .. }) => "mcp", Some(Command::Acp { .. }) => "acp", + Some(Command::Serve { .. }) => "serve", Some(Command::Session { .. }) => "session", Some(Command::Project {}) => "project", Some(Command::Projects) => "projects", @@ -1038,6 +1059,35 @@ async fn handle_mcp_command(server: McpCommand) -> Result<()> { Ok(()) } +async fn handle_serve_command(host: String, port: u16, builtins: Vec) -> Result<()> { + use goose::config::paths::Paths; + use goose_acp::server_factory::{AcpServer, AcpServerFactoryConfig}; + use std::net::SocketAddr; + use std::sync::Arc; + use tracing::info; + + let builtins = if builtins.is_empty() { + vec!["developer".to_string()] + } else { + builtins + }; + + let server = Arc::new(AcpServer::new(AcpServerFactoryConfig { + builtins, + data_dir: Paths::data_dir(), + config_dir: Paths::config_dir(), + })); + let router = goose_acp::transport::create_router(server); + + let addr: SocketAddr = format!("{}:{}", host, port).parse()?; + info!("Starting ACP server on {}", addr); + + let listener = tokio::net::TcpListener::bind(addr).await?; + axum::serve(listener, router).await?; + + Ok(()) +} + async fn handle_session_subcommand(command: SessionCommand) -> Result<()> { match command { SessionCommand::List { @@ -1708,6 +1758,11 @@ pub async fn cli() -> anyhow::Result<()> { Some(Command::Info { verbose }) => handle_info(verbose), Some(Command::Mcp { server }) => handle_mcp_command(server).await, Some(Command::Acp { builtins }) => goose_acp::server::run(builtins).await, + Some(Command::Serve { + host, + port, + builtins, + }) => handle_serve_command(host, port, builtins).await, Some(Command::Session { command: Some(cmd), .. }) => handle_session_subcommand(cmd).await,