feat: goose serve (#8209)

This commit is contained in:
Alex Hancock
2026-03-31 16:52:20 -04:00
committed by GitHub
parent c4d4ee8e4e
commit 01f879fc1b
5 changed files with 57 additions and 65 deletions

3
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"] }

View File

@@ -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<String>,
}
#[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(())
}

View File

@@ -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 }

View File

@@ -732,6 +732,26 @@ enum Command {
builtins: Vec<String>,
},
/// 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<String>,
},
/// Start or resume interactive chat sessions
#[command(
about = "Start or resume interactive chat sessions",
@@ -1009,6 +1029,7 @@ fn get_command_name(command: &Option<Command>) -> &'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<String>) -> 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,