From 050a8f2f42677324bd39a8538ac17883aaa0f23c Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Mon, 7 Apr 2025 13:42:38 -0700 Subject: [PATCH] Add -with-remote-extension (#2062) --- .../core/computercontroller/script.rs | 1 + .../core/computercontroller/web_scrape.rs | 1 + .../eval_suites/core/developer/create_file.rs | 1 + .../eval_suites/core/developer/list_files.rs | 1 + .../core/developer/simple_repo_clone_test.rs | 1 + .../eval_suites/core/developer_image/image.rs | 1 + .../search_replace.rs | 1 + .../src/eval_suites/core/memory/save_fact.rs | 1 + .../goose-bench/src/eval_suites/evaluation.rs | 2 ++ .../src/eval_suites/vibes/blog_summary.rs | 1 + .../src/eval_suites/vibes/flappy_bird.rs | 1 + .../src/eval_suites/vibes/goose_wiki.rs | 1 + .../eval_suites/vibes/restaurant_research.rs | 1 + .../src/eval_suites/vibes/squirrel_census.rs | 1 + crates/goose-cli/src/cli.rs | 26 +++++++++++++++- crates/goose-cli/src/commands/bench.rs | 1 + crates/goose-cli/src/session/builder.rs | 9 ++++++ crates/goose-cli/src/session/mod.rs | 31 +++++++++++++++++++ .../docs/getting-started/using-extensions.md | 17 ++++++++-- .../docs/guides/goose-cli-commands.md | 18 +++++++++++ documentation/docs/guides/running-tasks.md | 8 ++++- 21 files changed, 121 insertions(+), 4 deletions(-) diff --git a/crates/goose-bench/src/eval_suites/core/computercontroller/script.rs b/crates/goose-bench/src/eval_suites/core/computercontroller/script.rs index 83367eca..56b6c1de 100644 --- a/crates/goose-bench/src/eval_suites/core/computercontroller/script.rs +++ b/crates/goose-bench/src/eval_suites/core/computercontroller/script.rs @@ -77,6 +77,7 @@ impl Evaluation for ComputerControllerScript { ExtensionRequirements { builtin: vec!["computercontroller".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/core/computercontroller/web_scrape.rs b/crates/goose-bench/src/eval_suites/core/computercontroller/web_scrape.rs index 09710e3b..2858a370 100644 --- a/crates/goose-bench/src/eval_suites/core/computercontroller/web_scrape.rs +++ b/crates/goose-bench/src/eval_suites/core/computercontroller/web_scrape.rs @@ -80,6 +80,7 @@ impl Evaluation for ComputerControllerWebScrape { ExtensionRequirements { builtin: vec!["computercontroller".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/core/developer/create_file.rs b/crates/goose-bench/src/eval_suites/core/developer/create_file.rs index efbf88b7..114c89fd 100644 --- a/crates/goose-bench/src/eval_suites/core/developer/create_file.rs +++ b/crates/goose-bench/src/eval_suites/core/developer/create_file.rs @@ -120,6 +120,7 @@ impl Evaluation for DeveloperCreateFile { ExtensionRequirements { builtin: vec!["developer".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/core/developer/list_files.rs b/crates/goose-bench/src/eval_suites/core/developer/list_files.rs index 051a66e5..539c3d0a 100644 --- a/crates/goose-bench/src/eval_suites/core/developer/list_files.rs +++ b/crates/goose-bench/src/eval_suites/core/developer/list_files.rs @@ -81,6 +81,7 @@ impl Evaluation for DeveloperListFiles { ExtensionRequirements { builtin: vec!["developer".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/core/developer/simple_repo_clone_test.rs b/crates/goose-bench/src/eval_suites/core/developer/simple_repo_clone_test.rs index b1af460d..bb45050a 100644 --- a/crates/goose-bench/src/eval_suites/core/developer/simple_repo_clone_test.rs +++ b/crates/goose-bench/src/eval_suites/core/developer/simple_repo_clone_test.rs @@ -207,6 +207,7 @@ impl Evaluation for SimpleRepoCloneTest { ExtensionRequirements { builtin: vec!["developer".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/core/developer_image/image.rs b/crates/goose-bench/src/eval_suites/core/developer_image/image.rs index 8b77e5cc..6387bef7 100644 --- a/crates/goose-bench/src/eval_suites/core/developer_image/image.rs +++ b/crates/goose-bench/src/eval_suites/core/developer_image/image.rs @@ -98,6 +98,7 @@ impl Evaluation for DeveloperImage { ExtensionRequirements { builtin: vec!["developer".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/core/developer_search_replace/search_replace.rs b/crates/goose-bench/src/eval_suites/core/developer_search_replace/search_replace.rs index 9807e5bc..225b15d4 100644 --- a/crates/goose-bench/src/eval_suites/core/developer_search_replace/search_replace.rs +++ b/crates/goose-bench/src/eval_suites/core/developer_search_replace/search_replace.rs @@ -102,6 +102,7 @@ impl Evaluation for DeveloperSearchReplace { ExtensionRequirements { builtin: vec!["developer".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/core/memory/save_fact.rs b/crates/goose-bench/src/eval_suites/core/memory/save_fact.rs index bb683981..5572b854 100644 --- a/crates/goose-bench/src/eval_suites/core/memory/save_fact.rs +++ b/crates/goose-bench/src/eval_suites/core/memory/save_fact.rs @@ -82,6 +82,7 @@ impl Evaluation for MemoryRememberMemory { ExtensionRequirements { builtin: vec!["memory".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/evaluation.rs b/crates/goose-bench/src/eval_suites/evaluation.rs index ec4f5059..21de042a 100644 --- a/crates/goose-bench/src/eval_suites/evaluation.rs +++ b/crates/goose-bench/src/eval_suites/evaluation.rs @@ -27,6 +27,7 @@ pub enum EvaluationMetric { pub struct ExtensionRequirements { pub builtin: Vec, pub external: Vec, + pub remote: Vec, } #[async_trait] @@ -54,6 +55,7 @@ pub trait Evaluation: Send + Sync { ExtensionRequirements { builtin: Vec::new(), external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/vibes/blog_summary.rs b/crates/goose-bench/src/eval_suites/vibes/blog_summary.rs index 7ccec8ae..29f0ffed 100644 --- a/crates/goose-bench/src/eval_suites/vibes/blog_summary.rs +++ b/crates/goose-bench/src/eval_suites/vibes/blog_summary.rs @@ -82,6 +82,7 @@ impl Evaluation for BlogSummary { ExtensionRequirements { builtin: vec!["developer".to_string()], external: vec!["uvx mcp-server-fetch".to_string()], + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/vibes/flappy_bird.rs b/crates/goose-bench/src/eval_suites/vibes/flappy_bird.rs index 057b7e73..d634da88 100644 --- a/crates/goose-bench/src/eval_suites/vibes/flappy_bird.rs +++ b/crates/goose-bench/src/eval_suites/vibes/flappy_bird.rs @@ -114,6 +114,7 @@ impl Evaluation for FlappyBird { ExtensionRequirements { builtin: vec!["developer".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/vibes/goose_wiki.rs b/crates/goose-bench/src/eval_suites/vibes/goose_wiki.rs index 6c9c8bce..74be7644 100644 --- a/crates/goose-bench/src/eval_suites/vibes/goose_wiki.rs +++ b/crates/goose-bench/src/eval_suites/vibes/goose_wiki.rs @@ -92,6 +92,7 @@ impl Evaluation for GooseWiki { ExtensionRequirements { builtin: vec!["developer".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/vibes/restaurant_research.rs b/crates/goose-bench/src/eval_suites/vibes/restaurant_research.rs index 98f695b8..f0368fb2 100644 --- a/crates/goose-bench/src/eval_suites/vibes/restaurant_research.rs +++ b/crates/goose-bench/src/eval_suites/vibes/restaurant_research.rs @@ -102,6 +102,7 @@ Present the information in order of significance or quality. Focus specifically ExtensionRequirements { builtin: vec!["developer".to_string()], external: vec!["uvx mcp-server-fetch".to_string()], + remote: Vec::new(), } } } diff --git a/crates/goose-bench/src/eval_suites/vibes/squirrel_census.rs b/crates/goose-bench/src/eval_suites/vibes/squirrel_census.rs index 30d288f8..361d945b 100644 --- a/crates/goose-bench/src/eval_suites/vibes/squirrel_census.rs +++ b/crates/goose-bench/src/eval_suites/vibes/squirrel_census.rs @@ -170,6 +170,7 @@ After writing the script, run it using python3 and show the results. Do not ask ExtensionRequirements { builtin: vec!["developer".to_string()], external: Vec::new(), + remote: Vec::new(), } } } diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index 35895050..ae596609 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -128,6 +128,16 @@ enum Command { )] extension: Vec, + /// Add remote extensions with a URL + #[arg( + long = "with-remote-extension", + value_name = "URL", + help = "Add remote extensions (can be specified multiple times)", + long_help = "Add remote extensions from a URL. Can be specified multiple times. Format: 'url...'", + action = clap::ArgAction::Append + )] + remote_extension: Vec, + /// Add builtin extensions by name #[arg( long = "with-builtin", @@ -203,6 +213,16 @@ enum Command { )] extension: Vec, + /// Add remote extensions + #[arg( + long = "with-remote-extension", + value_name = "URL", + help = "Add remote extensions (can be specified multiple times)", + long_help = "Add remote extensions. Can be specified multiple times. Format: 'url...'", + action = clap::ArgAction::Append + )] + remote_extension: Vec, + /// Add builtin extensions by name #[arg( long = "with-builtin", @@ -323,6 +343,7 @@ pub async fn cli() -> Result<()> { resume, debug, extension, + remote_extension, builtin, }) => { match command { @@ -336,6 +357,7 @@ pub async fn cli() -> Result<()> { identifier.map(extract_identifier), resume, extension, + remote_extension, builtin, debug, ) @@ -357,6 +379,7 @@ pub async fn cli() -> Result<()> { resume, debug, extension, + remote_extension, builtin, }) => { let contents = match (instructions, input_text) { @@ -385,6 +408,7 @@ pub async fn cli() -> Result<()> { identifier.map(extract_identifier), resume, extension, + remote_extension, builtin, debug, ) @@ -468,7 +492,7 @@ pub async fn cli() -> Result<()> { return Ok(()); } else { // Run session command by default - let mut session = build_session(None, false, vec![], vec![], false).await; + let mut session = build_session(None, false, vec![], vec![], vec![], false).await; setup_logging( session.session_file().file_stem().and_then(|s| s.to_str()), None, diff --git a/crates/goose-cli/src/commands/bench.rs b/crates/goose-cli/src/commands/bench.rs index d9a204e8..8d812af2 100644 --- a/crates/goose-cli/src/commands/bench.rs +++ b/crates/goose-cli/src/commands/bench.rs @@ -85,6 +85,7 @@ async fn run_eval( None, false, requirements.external, + requirements.remote, requirements.builtin, false, ) diff --git a/crates/goose-cli/src/session/builder.rs b/crates/goose-cli/src/session/builder.rs index c89c7dbc..59a390b6 100644 --- a/crates/goose-cli/src/session/builder.rs +++ b/crates/goose-cli/src/session/builder.rs @@ -14,6 +14,7 @@ pub async fn build_session( identifier: Option, resume: bool, extensions: Vec, + remote_extensions: Vec, builtins: Vec, debug: bool, ) -> Session { @@ -126,6 +127,14 @@ pub async fn build_session( } } + // Add remote extensions if provided + for extension_str in remote_extensions { + if let Err(e) = session.add_remote_extension(extension_str).await { + eprintln!("Failed to start extension: {}", e); + process::exit(1); + } + } + // Add builtin extensions for builtin in builtins { if let Err(e) = session.add_builtin(builtin).await { diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 5fa5aeb9..1b0a88bb 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -172,6 +172,37 @@ impl Session { Ok(()) } + /// Add a remote extension to the session + /// + /// # Arguments + /// * `extension_url` - URL of the server + pub async fn add_remote_extension(&mut self, extension_url: String) -> Result<()> { + let name: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(8) + .map(char::from) + .collect(); + + let config = ExtensionConfig::Sse { + name, + uri: extension_url, + envs: Envs::new(HashMap::new()), + description: Some(goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string()), + // TODO: should set timeout + timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT), + }; + + self.agent + .add_extension(config) + .await + .map_err(|e| anyhow::anyhow!("Failed to start extension: {}", e))?; + + // Invalidate the completion cache when a new extension is added + self.invalidate_completion_cache().await; + + Ok(()) + } + /// Add a builtin extension to the session /// /// # Arguments diff --git a/documentation/docs/getting-started/using-extensions.md b/documentation/docs/getting-started/using-extensions.md index 32bd7763..4b8e47ea 100644 --- a/documentation/docs/getting-started/using-extensions.md +++ b/documentation/docs/getting-started/using-extensions.md @@ -376,7 +376,7 @@ goose session --with-builtin developer --with-builtin computercontroller To enable an extension while starting a session, run the following command: ```bash -goose session --with-extension "{extension command}" --with-extension "{antoher extension command}" +goose session --with-extension "{extension command}" --with-extension "{another extension command}" ``` For example, to start a session with the [Fetch extension](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch), you'd run: @@ -404,9 +404,22 @@ goose session --with-extension "GITHUB_PERSONAL_ACCESS_TOKEN= npx -y Note that you'll need [Node.js](https://nodejs.org/) installed on your system to run this command, as it uses `npx`. ::: +### Remote Extensions over SSE + +To enable a remote extension over SSE while starting a session, run the following command: + +```bash +goose session --with-remote-extension "{extension URL}" --with-remote-extension "{another extension URL}" +``` + +For example, to start a session with a remote extension running on localhost on port 8080, you'd run: + +```bash +goose session --with-remote-extension "http://localhost:8080/sse" +``` ## Developing Extensions + Goose extensions are implemented with MCP, a standard protocol that allows AI models and agents to securely connect with local or remote resources. Learn how to build your own [extension as an MCP server](https://modelcontextprotocol.io/quickstart/server). - [extensions-directory]: https://block.github.io/goose/v1/extensions diff --git a/documentation/docs/guides/goose-cli-commands.md b/documentation/docs/guides/goose-cli-commands.md index 0dd76229..52e41c14 100644 --- a/documentation/docs/guides/goose-cli-commands.md +++ b/documentation/docs/guides/goose-cli-commands.md @@ -79,6 +79,24 @@ goose configure goose session --with-extension "GITHUB_PERSONAL_ACCESS_TOKEN= npx -y @modelcontextprotocol/server-github" ``` +- Start a session with the specified remote extension over SSE + + **Options:** + + **`--with-remote-extension `** + + **Usage:** + + ```bash + goose session --with-remote-extension + ``` + + **Examples:** + + ```bash + goose session --with-remote-extension "http://localhost:8080/sse" + ``` + - Start a session with the specified [built-in extension](/docs/getting-started/using-extensions#built-in-extensions) enabled (e.g. 'developer') **Options:** diff --git a/documentation/docs/guides/running-tasks.md b/documentation/docs/guides/running-tasks.md index 4259e2ba..ae7c18d3 100644 --- a/documentation/docs/guides/running-tasks.md +++ b/documentation/docs/guides/running-tasks.md @@ -92,7 +92,7 @@ goose run -n my-project -r ### Working with Extensions -If you want to ensure specific extensions are available when running your task, you can indicate this with arguments. This can be done using the `--with-extension` or `--with-builtin` flags: +If you want to ensure specific extensions are available when running your task, you can indicate this with arguments. This can be done using the `--with-extension`, `--with-remote-extension`, or `--with-builtin` flags: - Using built-in extensions e.g developer and computercontroller extensions @@ -106,6 +106,12 @@ goose run --with-builtin "developer,computercontroller" -t "your instructions" goose run --with-extension "ENV1=value1 custom-extension-args" -t "your instructions" ``` +- Using remote extensions + +```bash +goose run --with-remote-extension "url" -t "your instructions" +``` + ## Common Use Cases ### Running Script Files