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
25 changes: 25 additions & 0 deletions crates/goose-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,16 @@ enum Command {
)]
remote_extensions: Vec<String>,

/// Add streamable HTTP extensions with a URL
#[arg(
long = "with-streamable-http-extension",
value_name = "URL",
help = "Add streamable HTTP extensions (can be specified multiple times)",
long_help = "Add streamable HTTP extensions from a URL. Can be specified multiple times. Format: 'url...'",
action = clap::ArgAction::Append
)]
streamable_http_extensions: Vec<String>,

/// Add builtin extensions by name
#[arg(
long = "with-builtin",
Expand Down Expand Up @@ -509,6 +519,16 @@ enum Command {
)]
remote_extensions: Vec<String>,

/// Add streamable HTTP extensions
#[arg(
long = "with-streamable-http-extension",
value_name = "URL",
help = "Add streamable HTTP extensions (can be specified multiple times)",
long_help = "Add streamable HTTP extensions. Can be specified multiple times. Format: 'url...'",
action = clap::ArgAction::Append
)]
streamable_http_extensions: Vec<String>,

/// Add builtin extensions by name
#[arg(
long = "with-builtin",
Expand Down Expand Up @@ -674,6 +694,7 @@ pub async fn cli() -> Result<()> {
max_turns,
extensions,
remote_extensions,
streamable_http_extensions,
builtins,
}) => {
return match command {
Expand Down Expand Up @@ -714,6 +735,7 @@ pub async fn cli() -> Result<()> {
no_session: false,
extensions,
remote_extensions,
streamable_http_extensions,
builtins,
extensions_override: None,
additional_system_prompt: None,
Expand Down Expand Up @@ -774,6 +796,7 @@ pub async fn cli() -> Result<()> {
max_turns,
extensions,
remote_extensions,
streamable_http_extensions,
builtins,
params,
explain,
Expand Down Expand Up @@ -863,6 +886,7 @@ pub async fn cli() -> Result<()> {
no_session,
extensions,
remote_extensions,
streamable_http_extensions,
builtins,
extensions_override: input_config.extensions_override,
additional_system_prompt: input_config.additional_system_prompt,
Expand Down Expand Up @@ -990,6 +1014,7 @@ pub async fn cli() -> Result<()> {
no_session: false,
extensions: Vec::new(),
remote_extensions: Vec::new(),
streamable_http_extensions: Vec::new(),
builtins: Vec::new(),
extensions_override: None,
additional_system_prompt: None,
Expand Down
1 change: 1 addition & 0 deletions crates/goose-cli/src/commands/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub async fn agent_generator(
no_session: false,
extensions: requirements.external,
remote_extensions: requirements.remote,
streamable_http_extensions: Vec::new(),
builtins: requirements.builtin,
extensions_override: None,
additional_system_prompt: None,
Expand Down
42 changes: 42 additions & 0 deletions crates/goose-cli/src/session/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub struct SessionBuilderConfig {
pub extensions: Vec<String>,
/// List of remote extension commands to add
pub remote_extensions: Vec<String>,
/// List of streamable HTTP extension commands to add
pub streamable_http_extensions: Vec<String>,
/// List of builtin extension commands to add
pub builtins: Vec<String>,
/// List of extensions to enable, enable only this set and ignore configured ones
Expand Down Expand Up @@ -454,6 +456,43 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
}
}

// Add streamable HTTP extensions if provided
for extension_str in session_config.streamable_http_extensions {
if let Err(e) = session
.add_streamable_http_extension(extension_str.clone())
.await
{
eprintln!(
"{}",
style(format!(
"Warning: Failed to start streamable HTTP extension '{}': {}",
extension_str, e
))
.yellow()
);
eprintln!(
"{}",
style(format!(
"Continuing without streamable HTTP extension '{}'",
extension_str
))
.yellow()
);

// Offer debugging help
if let Err(debug_err) = offer_extension_debugging_help(
&extension_str,
&e.to_string(),
Arc::clone(&provider_for_display),
session_config.interactive,
)
.await
{
eprintln!("Note: Could not start debugging session: {}", debug_err);
}
}
}

// Add builtin extensions
for builtin in session_config.builtins {
if let Err(e) = session.add_builtin(builtin.clone()).await {
Expand Down Expand Up @@ -531,6 +570,7 @@ mod tests {
no_session: false,
extensions: vec!["echo test".to_string()],
remote_extensions: vec!["http://example.com".to_string()],
streamable_http_extensions: vec!["http://example.com/streamable".to_string()],
builtins: vec!["developer".to_string()],
extensions_override: None,
additional_system_prompt: Some("Test prompt".to_string()),
Expand All @@ -549,6 +589,7 @@ mod tests {

assert_eq!(config.extensions.len(), 1);
assert_eq!(config.remote_extensions.len(), 1);
assert_eq!(config.streamable_http_extensions.len(), 1);
assert_eq!(config.builtins.len(), 1);
assert!(config.debug);
assert_eq!(config.max_tool_repetitions, Some(5));
Expand All @@ -567,6 +608,7 @@ mod tests {
assert!(!config.no_session);
assert!(config.extensions.is_empty());
assert!(config.remote_extensions.is_empty());
assert!(config.streamable_http_extensions.is_empty());
assert!(config.builtins.is_empty());
assert!(config.extensions_override.is_none());
assert!(config.additional_system_prompt.is_none());
Expand Down
34 changes: 34 additions & 0 deletions crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,40 @@ impl Session {
Ok(())
}

/// Add a streamable HTTP extension to the session
///
/// # Arguments
/// * `extension_url` - URL of the server
pub async fn add_streamable_http_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::StreamableHttp {
name,
uri: extension_url,
envs: Envs::new(HashMap::new()),
env_keys: Vec::new(),
headers: HashMap::new(),
description: Some(goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string()),
// TODO: should set timeout
timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),
bundled: None,
};

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
Expand Down
36 changes: 35 additions & 1 deletion documentation/docs/getting-started/using-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,26 @@ For example, a deeplink for a URL like `http://localhost:8080/sse` would look li

```
goose://extension?url=http%3A%2F%2Flocalhost%3A8080%2Fsse&timeout=<timeout>&id=<id>&name=<name>&description=<description>>
```

</TabItem>
<TabItem value="streamable_http" label="Streaming HTTP">
```
goose://extension?url=<remote-streamable-http-url>&type=streamable_http&id=<id>&name=<n>&description=<description>
```

Parameters:
- `url`: The URL of the remote Streaming HTTP server
- `type`: Must be set to `streamable_http` to specify the protocol type
- `timeout`: Maximum time (in seconds) to wait for extension responses
- `id`: Unique identifier for the extension
- `name`: Display name for the extension
- `description`: Brief description of the extension's functionality

For example, a deeplink for a URL like `https://example.com/streamable` would look like this when URL-encoded:

```
goose://extension?url=https%3A%2F%2Fexample.com%2Fstreamable&type=streamable_http&timeout=<timeout>&id=<id>&name=<n>&description=<description>
```

</TabItem>
Expand Down Expand Up @@ -596,8 +616,22 @@ For example, to start a session with a remote extension running on localhost on
goose session --with-remote-extension "http://localhost:8080/sse"
```

### Remote Extensions over Streaming HTTP

To enable a remote extension over Streaming HTTP while starting a session, run the following command:

```bash
goose session --with-streamable-http-extension "{extension URL}" --with-streamable-http-extension "{another extension URL}"
```

For example, to start a session with a Streaming HTTP extension, you'd run:

```bash
goose session --with-streamable-http-extension "https://example.com/streamable"
```

## 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
[extensions-directory]: https://block.github.io/goose/v1/extensions
67 changes: 59 additions & 8 deletions documentation/docs/guides/goose-cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ goose configure

### session [options]

- Start a session and give it a name
#### Start a session and give it a name

**Options:**

Expand All @@ -43,8 +43,9 @@ goose configure
```bash
goose session --name <n>
```
---

- Resume a previous session
#### Resume a previous session

**Options:**

Expand All @@ -59,8 +60,9 @@ goose configure
```bash
goose session --resume --id <id>
```
---

- Start a session with the specified extension
#### Start a session with the specified extension

**Options:**

Expand All @@ -83,8 +85,9 @@ goose configure
```bash
goose session --with-extension "GITHUB_PERSONAL_ACCESS_TOKEN=<YOUR_TOKEN> npx -y @modelcontextprotocol/server-github"
```
---

- Start a session with the specified remote extension over SSE
#### Start a session with the specified remote extension over SSE

**Options:**

Expand All @@ -102,7 +105,48 @@ goose configure
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')
---

#### Start a session with the specified remote extension over Streaming HTTP

**Options:**

**`--with-streamable-http-extension <url>`**

**Usage:**

```bash
goose session --with-streamable-http-extension <url>
```

**Examples:**

```bash
goose session --with-streamable-http-extension "https://example.com/streamable"
```

**Advanced Examples:**

```bash
# Start a session with a streamable HTTP extension
goose session --with-streamable-http-extension "http://api.example.com"

# Use multiple streamable HTTP extensions
goose session \
--with-streamable-http-extension "http://api1.example.com" \
--with-streamable-http-extension "http://api2.example.com"

# Mix different extension types
goose session \
--with-extension "echo hello" \
--with-remote-extension "http://sse.example.com/sse" \
--with-streamable-http-extension "http://http.example.com" \
--with-builtin "developer"
```

---

#### Start a session with the specified [built-in extension](/docs/getting-started/using-extensions#built-in-extensions) enabled (e.g. 'developer')

**Options:**

Expand All @@ -119,8 +163,9 @@ goose configure
```bash
goose session --with-builtin computercontroller
```
---

- Enable debug mode to output complete tool responses, detailed parameter values, and full file paths
#### Enable debug mode to output complete tool responses, detailed parameter values, and full file paths

**Options:**

Expand All @@ -131,8 +176,9 @@ goose configure
```bash
goose session --name my-session --debug
```
---

- Limit the maximum number of turns the agent can take before asking for user input to continue
#### Limit the maximum number of turns the agent can take before asking for user input to continue

**Options:**

Expand All @@ -144,7 +190,9 @@ goose configure
goose session --max-turns 50
```

- Set the [maximum number of turns](/docs/guides/smart-context-management#maximum-turns) allowed without user input (default: 1000)
---

#### Set the [maximum number of turns](/docs/guides/smart-context-management#maximum-turns) allowed without user input (default: 1000)

**Options:**

Expand All @@ -170,6 +218,7 @@ goose configure
```

---

### session list [options]

List all saved sessions.
Expand Down Expand Up @@ -326,6 +375,8 @@ Execute commands from an instruction file or stdin. Check out the [full guide](/
- **`--recipe <RECIPE_FILE_NAME> <OPTIONS>`**: Load a custom recipe in current session
- **`-p, --path <PATH>`**: Path for this run session (e.g. `./playground.jsonl`)
- **`--with-extension <COMMAND>`**: Add stdio extensions (can be used multiple times in the same command)
- **`--with-remote-extension <URL>`**: Add remote extensions over SSE (can be used multiple times in the same command)
- **`--with-streamable-http-extension <URL>`**: Add remote extensions over Streaming HTTP (can be used multiple times in the same command)
- **`--with-builtin <n>`**: Add builtin extensions by name (e.g., 'developer' or multiple: 'developer,github')
- **`--debug`**: Output complete tool responses, detailed parameter values, and full file paths
- **`--max-turns <NUMBER>`**: [Maximum number of turns](/docs/guides/smart-context-management#maximum-turns) allowed without user input (default: 1000)
Expand Down
Loading
Loading