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
106 changes: 92 additions & 14 deletions crates/goose-sdk-types/src/custom_requests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use agent_client_protocol::schema::McpServer;
use agent_client_protocol::{JsonRpcRequest, JsonRpcResponse};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -147,30 +148,104 @@ pub struct DeleteSessionRequest {
pub session_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum GooseExtension {
Builtin {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
display_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
timeout: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bundled: Option<bool>,
},
Platform {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
display_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bundled: Option<bool>,
},
Mcp {
server: McpServer,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude unsupported SSE servers from the add schema

When an ACP/SDK client follows the generated AddConfigExtensionRequest_unstable type, server: McpServer admits McpServerSse, but the config-add conversion rejects that exact variant with invalid_params in goose_extension_to_config (crates/goose/src/acp/server/extensions.rs:290-292). In that scenario the typed request passes the published schema/Zod validation but can never be persisted, so clients can build a seemingly valid _goose/unstable/config/extensions/add payload that always fails; use a Goose-specific MCP server union that excludes SSE or make the schema reflect the rejection.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because we reuse the standard ACP McpServer, which includes SSE. Goose config persistence intentionally no longer supports SSE and rejects it at conversion time.

#[serde(default, rename = "envKeys", skip_serializing_if = "Vec::is_empty")]
env_keys: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
timeout: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
socket: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bundled: Option<bool>,
},
}

impl Default for GooseExtension {
fn default() -> Self {
Self::Builtin {
name: String::new(),
description: None,
display_name: None,
timeout: None,
bundled: None,
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct GooseExtensionEntry {
pub extension: GooseExtension,
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub config_key: Option<String>,
}

/// List Goose-owned extension definitions available to configure or enable.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(
method = "_goose/unstable/extensions/available",
response = GetAvailableExtensionsResponse
)]
pub struct GetAvailableExtensionsRequest {}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
#[serde(rename_all = "camelCase")]
pub struct GetAvailableExtensionsResponse {
pub extensions: Vec<GooseExtension>,
}

/// List configured extensions and any warnings.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/unstable/config/extensions/list", response = GetExtensionsResponse)]
pub struct GetExtensionsRequest {}
#[request(
method = "_goose/unstable/config/extensions/list",
response = GetConfigExtensionsResponse
)]
pub struct GetConfigExtensionsRequest {}

/// List configured extensions and any warnings.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
pub struct GetExtensionsResponse {
/// Array of ExtensionEntry objects with `enabled` flag, `configKey`, and flattened config details.
pub extensions: Vec<serde_json::Value>,
pub struct GetConfigExtensionsResponse {
pub extensions: Vec<GooseExtensionEntry>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the flattened list shape or update TUI consumers

When the text UI opens the extensions manager, it still filters configResp.extensions with isExtEntry, which requires top-level enabled, type, and name fields (ui/text/src/extensions.tsx:18-30,129-131). This response now serializes each item as { extension: { type, name, ... }, enabled, configKey }, so every configured extension is filtered out and the manager shows an empty list for existing configs until the TUI/generated SDK are updated or a compatibility shape is kept.

Useful? React with 👍 / 👎.

#[serde(default)]
pub warnings: Vec<String>,
}

pub type GetExtensionsRequest = GetConfigExtensionsRequest;
pub type GetExtensionsResponse = GetConfigExtensionsResponse;

/// Persist a new extension to the user's global goose config.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/unstable/config/extensions/add", response = EmptyResponse)]
#[serde(rename_all = "camelCase")]
pub struct AddConfigExtensionRequest {
pub name: String,
/// Extension configuration. Must be a JSON object matching one of the
/// `ExtensionConfig` variants (e.g. `stdio`, `streamable_http`, `builtin`).
/// `name` and `enabled` are injected server-side.
#[serde(default)]
pub extension_config: serde_json::Value,
pub extension: GooseExtension,
#[serde(default)]
Comment on lines 247 to 249

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update config add callers for the new request shape

Changing _goose/unstable/config/extensions/add to require extension makes current TS callers send invalid params: the text UI still calls configExtensionsAdd_unstable({ name, extensionConfig, enabled }) (ui/text/src/extensions.tsx:190-194), and the generated SDK type/client still exposes that old payload. In that add-extension flow, serde will fail because extension is missing, so users can no longer persist a new stdio/HTTP extension from the TUI until those callers are regenerated/updated or backward compatibility is handled.

Useful? React with 👍 / 👎.

pub enabled: bool,
}
Expand All @@ -183,11 +258,14 @@ pub struct RemoveConfigExtensionRequest {
pub config_key: String,
}

/// Toggle the `enabled` flag for a persisted extension in the user's global goose config.
/// Set the `enabled` flag for a persisted extension in the user's global goose config.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/unstable/config/extensions/toggle", response = EmptyResponse)]
#[request(
method = "_goose/unstable/config/extensions/set-enabled",
response = EmptyResponse
)]
#[serde(rename_all = "camelCase")]
pub struct ToggleConfigExtensionRequest {
pub struct SetConfigExtensionEnabledRequest {
pub config_key: String,
pub enabled: bool,
}
Expand Down
13 changes: 9 additions & 4 deletions crates/goose/acp-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@
},
{
"method": "_goose/unstable/config/extensions/list",
"requestType": "GetExtensionsRequest_unstable",
"responseType": "GetExtensionsResponse_unstable"
"requestType": "GetConfigExtensionsRequest_unstable",
"responseType": "GetConfigExtensionsResponse_unstable"
},
{
"method": "_goose/unstable/extensions/available",
"requestType": "GetAvailableExtensionsRequest_unstable",
"responseType": "GetAvailableExtensionsResponse_unstable"
},
{
"method": "_goose/unstable/config/extensions/add",
Expand All @@ -56,8 +61,8 @@
"responseType": "EmptyResponse"
},
{
"method": "_goose/unstable/config/extensions/toggle",
"requestType": "ToggleConfigExtensionRequest_unstable",
"method": "_goose/unstable/config/extensions/set-enabled",
"requestType": "SetConfigExtensionEnabledRequest_unstable",
"responseType": "EmptyResponse"
},
{
Expand Down
Loading
Loading