feat: acp methods for config extensions (#9581)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use agent_client_protocol::schema::McpServer;
|
||||
use agent_client_protocol::{JsonRpcRequest, JsonRpcResponse};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -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,
|
||||
#[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>,
|
||||
#[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)]
|
||||
pub enabled: bool,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -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"
|
||||
},
|
||||
{
|
||||
|
||||
+417
-26
@@ -224,44 +224,419 @@
|
||||
"x-side": "agent",
|
||||
"x-method": "session/delete"
|
||||
},
|
||||
"GetExtensionsRequest_unstable": {
|
||||
"GetConfigExtensionsRequest_unstable": {
|
||||
"type": "object",
|
||||
"description": "List configured extensions and any warnings.",
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/config/extensions/list"
|
||||
},
|
||||
"GetExtensionsResponse_unstable": {
|
||||
"GetConfigExtensionsResponse_unstable": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extensions": {
|
||||
"type": "array",
|
||||
"items": {},
|
||||
"description": "Array of ExtensionEntry objects with `enabled` flag, `configKey`, and flattened config details."
|
||||
"items": {
|
||||
"$ref": "#/$defs/GooseExtensionEntry"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"default": []
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"extensions",
|
||||
"warnings"
|
||||
"extensions"
|
||||
],
|
||||
"description": "List configured extensions and any warnings.",
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/config/extensions/list"
|
||||
},
|
||||
"AddConfigExtensionRequest_unstable": {
|
||||
"GooseExtensionEntry": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extension": {
|
||||
"$ref": "#/$defs/GooseExtension"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"configKey": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"extension",
|
||||
"enabled"
|
||||
]
|
||||
},
|
||||
"GooseExtension": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"display_name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"timeout": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 0
|
||||
},
|
||||
"bundled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "builtin"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"display_name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"bundled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "platform"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server": {
|
||||
"$ref": "#/$defs/McpServer"
|
||||
},
|
||||
"envKeys": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"timeout": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 0
|
||||
},
|
||||
"socket": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"bundled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "mcp"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"server"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpServer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/McpServerHttp",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "http"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"description": "HTTP transport configuration\n\nOnly available when the Agent capabilities indicate `mcp_capabilities.http` is `true`."
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/McpServerSse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "sse"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"description": "SSE transport configuration\n\nOnly available when the Agent capabilities indicate `mcp_capabilities.sse` is `true`."
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/McpServerStdio",
|
||||
"description": "Stdio transport configuration\n\nAll Agents MUST support this transport."
|
||||
}
|
||||
],
|
||||
"description": "Configuration for connecting to an MCP (Model Context Protocol) server.\n\nMCP servers provide tools and context that the agent can use when\nprocessing prompts.\n\nSee protocol docs: [MCP Servers](https://agentclientprotocol.com/protocol/session-setup#mcp-servers)"
|
||||
},
|
||||
"HttpHeader": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "The name of the HTTP header."
|
||||
},
|
||||
"extensionConfig": {
|
||||
"description": "Extension configuration. Must be a JSON object matching one of the\n`ExtensionConfig` variants (e.g. `stdio`, `streamable_http`, `builtin`).\n`name` and `enabled` are injected server-side.",
|
||||
"default": null
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "The value to set for the HTTP header."
|
||||
},
|
||||
"_meta": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {},
|
||||
"description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"value"
|
||||
],
|
||||
"description": "An HTTP header to set when making requests to the MCP server."
|
||||
},
|
||||
"McpServerHttp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Human-readable name identifying this MCP server."
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "URL to the MCP server."
|
||||
},
|
||||
"headers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/HttpHeader"
|
||||
},
|
||||
"description": "HTTP headers to set when making requests to the MCP server."
|
||||
},
|
||||
"_meta": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {},
|
||||
"description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "http"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"name",
|
||||
"url",
|
||||
"headers"
|
||||
],
|
||||
"description": "HTTP transport configuration for MCP."
|
||||
},
|
||||
"McpServerSse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Human-readable name identifying this MCP server."
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "URL to the MCP server."
|
||||
},
|
||||
"headers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/HttpHeader"
|
||||
},
|
||||
"description": "HTTP headers to set when making requests to the MCP server."
|
||||
},
|
||||
"_meta": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {},
|
||||
"description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "sse"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"name",
|
||||
"url",
|
||||
"headers"
|
||||
],
|
||||
"description": "SSE transport configuration for MCP."
|
||||
},
|
||||
"McpServerStdio": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Human-readable name identifying this MCP server."
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "Path to the MCP server executable."
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Command-line arguments to pass to the MCP server."
|
||||
},
|
||||
"env": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/EnvVariable"
|
||||
},
|
||||
"description": "Environment variables to set when launching the MCP server."
|
||||
},
|
||||
"_meta": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {},
|
||||
"description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"command",
|
||||
"args",
|
||||
"env"
|
||||
],
|
||||
"description": "Stdio transport configuration for MCP."
|
||||
},
|
||||
"EnvVariable": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the environment variable."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "The value to set for the environment variable."
|
||||
},
|
||||
"_meta": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {},
|
||||
"description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"value"
|
||||
],
|
||||
"description": "An environment variable to set when launching an MCP server."
|
||||
},
|
||||
"GetAvailableExtensionsRequest_unstable": {
|
||||
"type": "object",
|
||||
"description": "List Goose-owned extension definitions available to configure or enable.",
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/extensions/available"
|
||||
},
|
||||
"GetAvailableExtensionsResponse_unstable": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/GooseExtension"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"extensions"
|
||||
],
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/extensions/available"
|
||||
},
|
||||
"AddConfigExtensionRequest_unstable": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extension": {
|
||||
"$ref": "#/$defs/GooseExtension"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
@@ -269,7 +644,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
"extension"
|
||||
],
|
||||
"description": "Persist a new extension to the user's global goose config.",
|
||||
"x-side": "agent",
|
||||
@@ -289,7 +664,7 @@
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/config/extensions/remove"
|
||||
},
|
||||
"ToggleConfigExtensionRequest_unstable": {
|
||||
"SetConfigExtensionEnabledRequest_unstable": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"configKey": {
|
||||
@@ -303,9 +678,9 @@
|
||||
"configKey",
|
||||
"enabled"
|
||||
],
|
||||
"description": "Toggle the `enabled` flag for a persisted extension in the user's global goose config.",
|
||||
"description": "Set the `enabled` flag for a persisted extension in the user's global goose config.",
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/config/extensions/toggle"
|
||||
"x-method": "_goose/unstable/config/extensions/set-enabled"
|
||||
},
|
||||
"GetSessionExtensionsRequest_unstable": {
|
||||
"type": "object",
|
||||
@@ -545,9 +920,8 @@
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint",
|
||||
"minimum": 0,
|
||||
"description": "Context window size in tokens."
|
||||
"description": "Context window size in tokens.",
|
||||
"minimum": 0
|
||||
},
|
||||
"reasoning": {
|
||||
"type": [
|
||||
@@ -2962,11 +3336,20 @@
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/GetExtensionsRequest_unstable"
|
||||
"$ref": "#/$defs/GetConfigExtensionsRequest_unstable"
|
||||
}
|
||||
],
|
||||
"description": "Params for _goose/unstable/config/extensions/list",
|
||||
"title": "GetExtensionsRequest_unstable"
|
||||
"title": "GetConfigExtensionsRequest_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/GetAvailableExtensionsRequest_unstable"
|
||||
}
|
||||
],
|
||||
"description": "Params for _goose/unstable/extensions/available",
|
||||
"title": "GetAvailableExtensionsRequest_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
@@ -2989,11 +3372,11 @@
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/ToggleConfigExtensionRequest_unstable"
|
||||
"$ref": "#/$defs/SetConfigExtensionEnabledRequest_unstable"
|
||||
}
|
||||
],
|
||||
"description": "Params for _goose/unstable/config/extensions/toggle",
|
||||
"title": "ToggleConfigExtensionRequest_unstable"
|
||||
"description": "Params for _goose/unstable/config/extensions/set-enabled",
|
||||
"title": "SetConfigExtensionEnabledRequest_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
@@ -3474,10 +3857,18 @@
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/GetExtensionsResponse_unstable"
|
||||
"$ref": "#/$defs/GetConfigExtensionsResponse_unstable"
|
||||
}
|
||||
],
|
||||
"title": "GetExtensionsResponse_unstable"
|
||||
"title": "GetConfigExtensionsResponse_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/GetAvailableExtensionsResponse_unstable"
|
||||
}
|
||||
],
|
||||
"title": "GetAvailableExtensionsResponse_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
|
||||
@@ -75,11 +75,18 @@ impl GooseAcpAgent {
|
||||
self.on_delete_session(req).await
|
||||
}
|
||||
|
||||
#[custom_method(GetExtensionsRequest)]
|
||||
async fn dispatch_get_extensions(
|
||||
#[custom_method(GetConfigExtensionsRequest)]
|
||||
async fn dispatch_get_config_extensions(
|
||||
&self,
|
||||
) -> Result<GetExtensionsResponse, agent_client_protocol::Error> {
|
||||
self.on_get_extensions().await
|
||||
) -> Result<GetConfigExtensionsResponse, agent_client_protocol::Error> {
|
||||
self.on_get_config_extensions().await
|
||||
}
|
||||
|
||||
#[custom_method(GetAvailableExtensionsRequest)]
|
||||
async fn dispatch_get_available_extensions(
|
||||
&self,
|
||||
) -> Result<GetAvailableExtensionsResponse, agent_client_protocol::Error> {
|
||||
self.on_get_available_extensions().await
|
||||
}
|
||||
|
||||
#[custom_method(AddConfigExtensionRequest)]
|
||||
@@ -98,12 +105,12 @@ impl GooseAcpAgent {
|
||||
self.on_remove_config_extension(req).await
|
||||
}
|
||||
|
||||
#[custom_method(ToggleConfigExtensionRequest)]
|
||||
async fn dispatch_toggle_config_extension(
|
||||
#[custom_method(SetConfigExtensionEnabledRequest)]
|
||||
async fn dispatch_set_config_extension_enabled(
|
||||
&self,
|
||||
req: ToggleConfigExtensionRequest,
|
||||
req: SetConfigExtensionEnabledRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
self.on_toggle_config_extension(req).await
|
||||
self.on_set_config_extension_enabled(req).await
|
||||
}
|
||||
|
||||
#[custom_method(GetSessionExtensionsRequest)]
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use crate::agents::extension::Envs;
|
||||
use crate::config::extensions::ExtensionEntry;
|
||||
use agent_client_protocol::schema::{HttpHeader, McpServer, McpServerHttp, McpServerStdio};
|
||||
|
||||
impl GooseAcpAgent {
|
||||
pub(super) async fn on_add_extension(
|
||||
@@ -30,9 +33,9 @@ impl GooseAcpAgent {
|
||||
Ok(EmptyResponse {})
|
||||
}
|
||||
|
||||
pub(super) async fn on_get_extensions(
|
||||
pub(super) async fn on_get_config_extensions(
|
||||
&self,
|
||||
) -> Result<GetExtensionsResponse, agent_client_protocol::Error> {
|
||||
) -> Result<GetConfigExtensionsResponse, agent_client_protocol::Error> {
|
||||
let extensions = crate::config::extensions::get_all_extensions()
|
||||
.into_iter()
|
||||
.filter(|ext| {
|
||||
@@ -40,51 +43,46 @@ impl GooseAcpAgent {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let warnings = crate::config::extensions::get_warnings();
|
||||
let extensions_json = extensions
|
||||
let extensions = extensions
|
||||
.into_iter()
|
||||
.map(|e| {
|
||||
let config_key = e.config.key();
|
||||
let mut value = serde_json::to_value(&e)?;
|
||||
if let Some(obj) = value.as_object_mut() {
|
||||
obj.insert(
|
||||
"config_key".to_string(),
|
||||
serde_json::Value::String(config_key),
|
||||
);
|
||||
}
|
||||
Ok::<_, serde_json::Error>(value)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.internal_err()?;
|
||||
Ok(GetExtensionsResponse {
|
||||
extensions: extensions_json,
|
||||
.map(config_entry_to_goose_entry)
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
Ok(GetConfigExtensionsResponse {
|
||||
extensions,
|
||||
warnings,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) async fn on_get_available_extensions(
|
||||
&self,
|
||||
) -> Result<GetAvailableExtensionsResponse, agent_client_protocol::Error> {
|
||||
let extensions = crate::config::get_available_extensions()
|
||||
.into_iter()
|
||||
.map(|config| config_to_goose_extension(&config))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(GetAvailableExtensionsResponse { extensions })
|
||||
}
|
||||
|
||||
pub(super) async fn on_add_config_extension(
|
||||
&self,
|
||||
req: AddConfigExtensionRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
let mut obj = match req.extension_config {
|
||||
serde_json::Value::Object(obj) => obj,
|
||||
_ => {
|
||||
return Err(agent_client_protocol::Error::invalid_params()
|
||||
.data("extensionConfig must be a JSON object"));
|
||||
}
|
||||
};
|
||||
obj.insert(
|
||||
"name".to_string(),
|
||||
serde_json::Value::String(req.name.clone()),
|
||||
);
|
||||
let conversion = goose_extension_to_config(req.extension)?;
|
||||
|
||||
let config: crate::agents::ExtensionConfig =
|
||||
serde_json::from_value(serde_json::Value::Object(obj)).map_err(|e| {
|
||||
agent_client_protocol::Error::invalid_params().data(format!("bad config: {e}"))
|
||||
})?;
|
||||
Config::global()
|
||||
.set_secret_values(&conversion.secret_updates)
|
||||
.internal_err_ctx("Failed to save extension env secrets")?;
|
||||
|
||||
crate::config::extensions::set_extension(crate::config::extensions::ExtensionEntry {
|
||||
crate::config::extensions::set_extension(ExtensionEntry {
|
||||
enabled: req.enabled,
|
||||
config,
|
||||
config: conversion.config,
|
||||
});
|
||||
Ok(EmptyResponse {})
|
||||
}
|
||||
@@ -93,25 +91,21 @@ impl GooseAcpAgent {
|
||||
&self,
|
||||
req: RemoveConfigExtensionRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
let keys = crate::config::extensions::get_all_extension_names();
|
||||
if !keys.iter().any(|k| k == &req.config_key) {
|
||||
return Err(agent_client_protocol::Error::invalid_params()
|
||||
.data(format!("Extension '{}' not found", req.config_key)));
|
||||
}
|
||||
crate::config::extensions::remove_extension(&req.config_key);
|
||||
Ok(EmptyResponse {})
|
||||
}
|
||||
|
||||
pub(super) async fn on_toggle_config_extension(
|
||||
pub(super) async fn on_set_config_extension_enabled(
|
||||
&self,
|
||||
req: ToggleConfigExtensionRequest,
|
||||
req: SetConfigExtensionEnabledRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
let keys = crate::config::extensions::get_all_extension_names();
|
||||
if !keys.iter().any(|k| k == &req.config_key) {
|
||||
let updated =
|
||||
crate::config::extensions::set_extension_enabled(&req.config_key, req.enabled);
|
||||
if !updated {
|
||||
return Err(agent_client_protocol::Error::invalid_params()
|
||||
.data(format!("Extension '{}' not found", req.config_key)));
|
||||
}
|
||||
crate::config::extensions::set_extension_enabled(&req.config_key, req.enabled);
|
||||
|
||||
Ok(EmptyResponse {})
|
||||
}
|
||||
|
||||
@@ -142,3 +136,655 @@ impl GooseAcpAgent {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn config_to_goose_extension(
|
||||
config: &ExtensionConfig,
|
||||
) -> Result<Option<GooseExtension>, agent_client_protocol::Error> {
|
||||
let extension = match config {
|
||||
ExtensionConfig::Builtin {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
..
|
||||
} => GooseExtension::Builtin {
|
||||
name: name.clone(),
|
||||
description: empty_string_to_none(description),
|
||||
display_name: display_name.clone(),
|
||||
timeout: *timeout,
|
||||
bundled: *bundled,
|
||||
},
|
||||
ExtensionConfig::Platform {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
..
|
||||
} => GooseExtension::Platform {
|
||||
name: name.clone(),
|
||||
description: empty_string_to_none(description),
|
||||
display_name: display_name.clone(),
|
||||
bundled: *bundled,
|
||||
},
|
||||
ExtensionConfig::Stdio {
|
||||
name,
|
||||
description,
|
||||
cmd,
|
||||
args,
|
||||
env_keys,
|
||||
timeout,
|
||||
bundled,
|
||||
..
|
||||
} => GooseExtension::Mcp {
|
||||
server: McpServer::Stdio(McpServerStdio::new(name, cmd).args(args.clone())),
|
||||
env_keys: env_keys.clone(),
|
||||
description: empty_string_to_none(description),
|
||||
timeout: *timeout,
|
||||
socket: None,
|
||||
bundled: *bundled,
|
||||
},
|
||||
ExtensionConfig::StreamableHttp {
|
||||
name,
|
||||
description,
|
||||
uri,
|
||||
env_keys,
|
||||
headers,
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
..
|
||||
} => {
|
||||
let headers = headers
|
||||
.iter()
|
||||
.map(|(key, value)| HttpHeader::new(key, value))
|
||||
.collect();
|
||||
GooseExtension::Mcp {
|
||||
server: McpServer::Http(McpServerHttp::new(name, uri).headers(headers)),
|
||||
env_keys: env_keys.clone(),
|
||||
description: empty_string_to_none(description),
|
||||
timeout: *timeout,
|
||||
socket: socket.clone(),
|
||||
bundled: *bundled,
|
||||
}
|
||||
}
|
||||
ExtensionConfig::Frontend { .. }
|
||||
| ExtensionConfig::InlinePython { .. }
|
||||
| ExtensionConfig::Sse { .. } => return Ok(None),
|
||||
};
|
||||
Ok(Some(extension))
|
||||
}
|
||||
|
||||
struct ConfigExtensionConversion {
|
||||
config: ExtensionConfig,
|
||||
secret_updates: Vec<(String, serde_json::Value)>,
|
||||
}
|
||||
|
||||
fn goose_extension_to_config(
|
||||
extension: GooseExtension,
|
||||
) -> Result<ConfigExtensionConversion, agent_client_protocol::Error> {
|
||||
let mut secret_updates = Vec::new();
|
||||
let config = match extension {
|
||||
GooseExtension::Builtin {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
} => ExtensionConfig::Builtin {
|
||||
name,
|
||||
description: description.unwrap_or_default(),
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
},
|
||||
GooseExtension::Platform {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
} => ExtensionConfig::Platform {
|
||||
name,
|
||||
description: description.unwrap_or_default(),
|
||||
display_name,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
},
|
||||
GooseExtension::Mcp {
|
||||
server,
|
||||
env_keys,
|
||||
description,
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
} => match server {
|
||||
McpServer::Stdio(stdio) => {
|
||||
if socket.is_some() {
|
||||
return Err(agent_client_protocol::Error::invalid_params()
|
||||
.data("socket is only supported for streamable_http MCP extensions"));
|
||||
}
|
||||
let mut env_keys = env_keys;
|
||||
for env in stdio.env {
|
||||
if !env_keys.contains(&env.name) {
|
||||
env_keys.push(env.name.clone());
|
||||
}
|
||||
secret_updates.push((env.name, serde_json::Value::String(env.value)));
|
||||
}
|
||||
ExtensionConfig::Stdio {
|
||||
name: stdio.name,
|
||||
description: description.unwrap_or_default(),
|
||||
cmd: stdio.command.to_string_lossy().to_string(),
|
||||
args: stdio.args,
|
||||
envs: Envs::default(),
|
||||
env_keys,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
}
|
||||
}
|
||||
McpServer::Http(http) => ExtensionConfig::StreamableHttp {
|
||||
name: http.name,
|
||||
description: description.unwrap_or_default(),
|
||||
uri: http.url,
|
||||
envs: Envs::default(),
|
||||
env_keys,
|
||||
headers: http
|
||||
.headers
|
||||
.into_iter()
|
||||
.map(|header| (header.name, header.value))
|
||||
.collect(),
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
},
|
||||
McpServer::Sse(_) => {
|
||||
return Err(agent_client_protocol::Error::invalid_params()
|
||||
.data("SSE is unsupported, migrate to streamable_http"));
|
||||
}
|
||||
_ => {
|
||||
return Err(
|
||||
agent_client_protocol::Error::invalid_params().data("unsupported MCP server")
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(ConfigExtensionConversion {
|
||||
config,
|
||||
secret_updates,
|
||||
})
|
||||
}
|
||||
|
||||
fn config_entry_to_goose_entry(
|
||||
entry: ExtensionEntry,
|
||||
) -> Result<Option<GooseExtensionEntry>, agent_client_protocol::Error> {
|
||||
let config_key = entry.config.key();
|
||||
let Some(extension) = config_to_goose_extension(&entry.config)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(GooseExtensionEntry {
|
||||
extension,
|
||||
enabled: entry.enabled,
|
||||
config_key: Some(config_key),
|
||||
}))
|
||||
}
|
||||
|
||||
fn empty_string_to_none(value: &str) -> Option<String> {
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::agents::extension::Envs;
|
||||
use agent_client_protocol::schema::{McpServer, McpServerSse};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn builtin_config_converts_to_goose_builtin_extension() {
|
||||
let config = ExtensionConfig::Builtin {
|
||||
name: "developer".to_string(),
|
||||
description: "Developer tools".to_string(),
|
||||
display_name: Some("Developer".to_string()),
|
||||
timeout: Some(30),
|
||||
bundled: Some(true),
|
||||
available_tools: vec!["shell".to_string()],
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config)
|
||||
.expect("conversion should succeed")
|
||||
.expect("builtin should be supported");
|
||||
|
||||
let GooseExtension::Builtin {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
} = extension
|
||||
else {
|
||||
panic!("expected builtin extension");
|
||||
};
|
||||
|
||||
assert_eq!(name, "developer");
|
||||
assert_eq!(description.as_deref(), Some("Developer tools"));
|
||||
assert_eq!(display_name.as_deref(), Some("Developer"));
|
||||
assert_eq!(timeout, Some(30));
|
||||
assert_eq!(bundled, Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_config_converts_to_goose_platform_extension() {
|
||||
let config = ExtensionConfig::Platform {
|
||||
name: "todo".to_string(),
|
||||
description: "Todo tools".to_string(),
|
||||
display_name: Some("Todo".to_string()),
|
||||
bundled: Some(true),
|
||||
available_tools: vec!["write_todos".to_string()],
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config)
|
||||
.expect("conversion should succeed")
|
||||
.expect("platform should be supported");
|
||||
|
||||
let GooseExtension::Platform {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
} = extension
|
||||
else {
|
||||
panic!("expected platform extension");
|
||||
};
|
||||
|
||||
assert_eq!(name, "todo");
|
||||
assert_eq!(description.as_deref(), Some("Todo tools"));
|
||||
assert_eq!(display_name.as_deref(), Some("Todo"));
|
||||
assert_eq!(bundled, Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdio_config_converts_to_goose_mcp_extension_without_literal_envs() {
|
||||
let config = ExtensionConfig::Stdio {
|
||||
name: "test-stdio".to_string(),
|
||||
description: "Test stdio".to_string(),
|
||||
cmd: "test-command".to_string(),
|
||||
args: vec!["--flag".to_string(), "value".to_string()],
|
||||
envs: Envs::new(HashMap::from([(
|
||||
"SECRET_TOKEN".to_string(),
|
||||
"literal-secret".to_string(),
|
||||
)])),
|
||||
env_keys: vec!["SECRET_TOKEN".to_string()],
|
||||
timeout: Some(42),
|
||||
bundled: None,
|
||||
available_tools: vec![],
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config)
|
||||
.expect("conversion should succeed")
|
||||
.expect("stdio should be supported");
|
||||
|
||||
let GooseExtension::Mcp {
|
||||
server,
|
||||
env_keys,
|
||||
description,
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
} = extension
|
||||
else {
|
||||
panic!("expected mcp extension");
|
||||
};
|
||||
|
||||
assert_eq!(env_keys, vec!["SECRET_TOKEN"]);
|
||||
assert_eq!(description.as_deref(), Some("Test stdio"));
|
||||
assert_eq!(timeout, Some(42));
|
||||
assert_eq!(socket, None);
|
||||
assert_eq!(bundled, None);
|
||||
|
||||
let McpServer::Stdio(stdio) = server else {
|
||||
panic!("expected stdio server");
|
||||
};
|
||||
|
||||
assert_eq!(stdio.name, "test-stdio");
|
||||
assert_eq!(stdio.command.to_string_lossy(), "test-command");
|
||||
assert_eq!(stdio.args, vec!["--flag", "value"]);
|
||||
assert!(stdio.env.is_empty(), "literal envs should not be exposed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn streamable_http_config_converts_to_goose_mcp_extension_without_literal_envs() {
|
||||
let config = ExtensionConfig::StreamableHttp {
|
||||
name: "test-http".to_string(),
|
||||
description: "Test HTTP".to_string(),
|
||||
uri: "https://example.com/mcp".to_string(),
|
||||
envs: Envs::new(HashMap::from([(
|
||||
"API_TOKEN".to_string(),
|
||||
"literal-secret".to_string(),
|
||||
)])),
|
||||
env_keys: vec!["API_TOKEN".to_string()],
|
||||
headers: HashMap::from([(
|
||||
"Authorization".to_string(),
|
||||
"Bearer ${API_TOKEN}".to_string(),
|
||||
)]),
|
||||
timeout: Some(99),
|
||||
socket: Some("@egress.sock".to_string()),
|
||||
bundled: None,
|
||||
available_tools: vec![],
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config)
|
||||
.expect("conversion should succeed")
|
||||
.expect("streamable http should be supported");
|
||||
|
||||
let GooseExtension::Mcp {
|
||||
server,
|
||||
env_keys,
|
||||
description,
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
} = extension
|
||||
else {
|
||||
panic!("expected mcp extension");
|
||||
};
|
||||
|
||||
assert_eq!(env_keys, vec!["API_TOKEN"]);
|
||||
assert_eq!(description.as_deref(), Some("Test HTTP"));
|
||||
assert_eq!(timeout, Some(99));
|
||||
assert_eq!(socket.as_deref(), Some("@egress.sock"));
|
||||
assert_eq!(bundled, None);
|
||||
|
||||
let McpServer::Http(http) = server else {
|
||||
panic!("expected http server");
|
||||
};
|
||||
|
||||
assert_eq!(http.name, "test-http");
|
||||
assert_eq!(http.url, "https://example.com/mcp");
|
||||
assert_eq!(http.headers.len(), 1);
|
||||
assert_eq!(http.headers[0].name, "Authorization");
|
||||
assert_eq!(http.headers[0].value, "Bearer ${API_TOKEN}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_python_config_is_skipped() {
|
||||
let config = ExtensionConfig::InlinePython {
|
||||
name: "python-tools".to_string(),
|
||||
description: "Python tools".to_string(),
|
||||
code: "print('hello')".to_string(),
|
||||
timeout: Some(12),
|
||||
dependencies: Some(vec!["requests".to_string()]),
|
||||
available_tools: vec!["fetch".to_string()],
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config).expect("conversion should succeed");
|
||||
|
||||
assert!(extension.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frontend_config_is_skipped() {
|
||||
let tool = rmcp::model::Tool::new(
|
||||
"pick_color",
|
||||
"Pick a color",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hex": { "type": "string" }
|
||||
}
|
||||
})
|
||||
.as_object()
|
||||
.expect("schema should be object")
|
||||
.clone(),
|
||||
);
|
||||
let config = ExtensionConfig::Frontend {
|
||||
name: "frontend-tools".to_string(),
|
||||
description: "Frontend tools".to_string(),
|
||||
tools: vec![tool],
|
||||
instructions: Some("Use frontend tools carefully".to_string()),
|
||||
bundled: None,
|
||||
available_tools: vec!["pick_color".to_string()],
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config).expect("conversion should succeed");
|
||||
|
||||
assert!(extension.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sse_config_is_skipped() {
|
||||
let config = ExtensionConfig::Sse {
|
||||
name: "legacy-sse".to_string(),
|
||||
description: "Legacy SSE".to_string(),
|
||||
uri: Some("https://example.com/sse".to_string()),
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config).expect("conversion should succeed");
|
||||
|
||||
assert!(extension.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goose_mcp_stdio_extension_converts_to_config_without_literal_envs() {
|
||||
let extension = GooseExtension::Mcp {
|
||||
server: McpServer::Stdio(
|
||||
McpServerStdio::new("test-stdio", "test-command")
|
||||
.args(vec!["--flag".to_string(), "value".to_string()]),
|
||||
),
|
||||
env_keys: vec!["SECRET_TOKEN".to_string()],
|
||||
description: Some("Test stdio".to_string()),
|
||||
timeout: Some(42),
|
||||
socket: None,
|
||||
bundled: Some(true),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(extension).expect("conversion should succeed");
|
||||
assert!(conversion.secret_updates.is_empty());
|
||||
|
||||
let ExtensionConfig::Stdio {
|
||||
name,
|
||||
description,
|
||||
cmd,
|
||||
args,
|
||||
envs,
|
||||
env_keys,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools,
|
||||
} = conversion.config
|
||||
else {
|
||||
panic!("expected stdio config");
|
||||
};
|
||||
|
||||
assert_eq!(name, "test-stdio");
|
||||
assert_eq!(description, "Test stdio");
|
||||
assert_eq!(cmd, "test-command");
|
||||
assert_eq!(args, vec!["--flag", "value"]);
|
||||
assert!(
|
||||
envs.get_env().is_empty(),
|
||||
"literal envs should not be persisted"
|
||||
);
|
||||
assert_eq!(env_keys, vec!["SECRET_TOKEN"]);
|
||||
assert_eq!(timeout, Some(42));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert!(available_tools.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goose_mcp_stdio_extension_extracts_literal_envs_for_config_add() {
|
||||
let extension = GooseExtension::Mcp {
|
||||
server: McpServer::Stdio(McpServerStdio::new("test-stdio", "test-command").env(vec![
|
||||
agent_client_protocol::schema::EnvVariable::new("SECRET_TOKEN", "literal-secret"),
|
||||
agent_client_protocol::schema::EnvVariable::new("OTHER_TOKEN", "other-secret"),
|
||||
])),
|
||||
env_keys: vec!["SECRET_TOKEN".to_string()],
|
||||
description: Some("Test stdio".to_string()),
|
||||
timeout: Some(42),
|
||||
socket: None,
|
||||
bundled: Some(true),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(extension).expect("conversion should succeed");
|
||||
|
||||
assert_eq!(
|
||||
conversion.secret_updates,
|
||||
vec![
|
||||
(
|
||||
"SECRET_TOKEN".to_string(),
|
||||
serde_json::Value::String("literal-secret".to_string())
|
||||
),
|
||||
(
|
||||
"OTHER_TOKEN".to_string(),
|
||||
serde_json::Value::String("other-secret".to_string())
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
let ExtensionConfig::Stdio { envs, env_keys, .. } = conversion.config else {
|
||||
panic!("expected stdio config");
|
||||
};
|
||||
|
||||
assert!(
|
||||
envs.get_env().is_empty(),
|
||||
"literal envs should not be persisted"
|
||||
);
|
||||
assert_eq!(env_keys, vec!["SECRET_TOKEN", "OTHER_TOKEN"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goose_mcp_streamable_http_extension_converts_to_config_without_literal_envs() {
|
||||
let extension = GooseExtension::Mcp {
|
||||
server: McpServer::Http(
|
||||
McpServerHttp::new("test-http", "https://example.com/mcp").headers(vec![
|
||||
HttpHeader::new("Authorization", "Bearer ${API_TOKEN}"),
|
||||
]),
|
||||
),
|
||||
env_keys: vec!["API_TOKEN".to_string()],
|
||||
description: Some("Test HTTP".to_string()),
|
||||
timeout: Some(99),
|
||||
socket: Some("@egress.sock".to_string()),
|
||||
bundled: Some(true),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(extension).expect("conversion should succeed");
|
||||
assert!(conversion.secret_updates.is_empty());
|
||||
|
||||
let ExtensionConfig::StreamableHttp {
|
||||
name,
|
||||
description,
|
||||
uri,
|
||||
envs,
|
||||
env_keys,
|
||||
headers,
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools,
|
||||
} = conversion.config
|
||||
else {
|
||||
panic!("expected streamable http config");
|
||||
};
|
||||
|
||||
assert_eq!(name, "test-http");
|
||||
assert_eq!(description, "Test HTTP");
|
||||
assert_eq!(uri, "https://example.com/mcp");
|
||||
assert!(
|
||||
envs.get_env().is_empty(),
|
||||
"literal envs should not be persisted"
|
||||
);
|
||||
assert_eq!(env_keys, vec!["API_TOKEN"]);
|
||||
assert_eq!(
|
||||
headers,
|
||||
HashMap::from([(
|
||||
"Authorization".to_string(),
|
||||
"Bearer ${API_TOKEN}".to_string()
|
||||
)])
|
||||
);
|
||||
assert_eq!(timeout, Some(99));
|
||||
assert_eq!(socket.as_deref(), Some("@egress.sock"));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert!(available_tools.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goose_builtin_extension_converts_to_config() {
|
||||
let builtin = GooseExtension::Builtin {
|
||||
name: "developer".to_string(),
|
||||
description: Some("Developer tools".to_string()),
|
||||
display_name: Some("Developer".to_string()),
|
||||
timeout: Some(30),
|
||||
bundled: Some(true),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(builtin).expect("conversion should succeed");
|
||||
assert!(conversion.secret_updates.is_empty());
|
||||
|
||||
let ExtensionConfig::Builtin {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools,
|
||||
} = conversion.config
|
||||
else {
|
||||
panic!("expected builtin config");
|
||||
};
|
||||
|
||||
assert_eq!(name, "developer");
|
||||
assert_eq!(description, "Developer tools");
|
||||
assert_eq!(display_name.as_deref(), Some("Developer"));
|
||||
assert_eq!(timeout, Some(30));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert!(available_tools.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goose_platform_extension_converts_to_config() {
|
||||
let platform = GooseExtension::Platform {
|
||||
name: "todo".to_string(),
|
||||
description: Some("Todo tools".to_string()),
|
||||
display_name: Some("Todo".to_string()),
|
||||
bundled: Some(true),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(platform).expect("conversion should succeed");
|
||||
assert!(conversion.secret_updates.is_empty());
|
||||
|
||||
let ExtensionConfig::Platform {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
available_tools,
|
||||
} = conversion.config
|
||||
else {
|
||||
panic!("expected platform config");
|
||||
};
|
||||
|
||||
assert_eq!(name, "todo");
|
||||
assert_eq!(description, "Todo tools");
|
||||
assert_eq!(display_name.as_deref(), Some("Todo"));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert!(available_tools.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goose_mcp_sse_extension_is_rejected_for_config_add() {
|
||||
let extension = GooseExtension::Mcp {
|
||||
server: McpServer::Sse(McpServerSse::new("legacy-sse", "https://example.com/sse")),
|
||||
env_keys: Vec::new(),
|
||||
description: None,
|
||||
timeout: None,
|
||||
socket: None,
|
||||
bundled: None,
|
||||
};
|
||||
|
||||
assert!(goose_extension_to_config(extension).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ fn main() {
|
||||
strip_integer_formats(def);
|
||||
}
|
||||
|
||||
add_mcp_server_transport_discriminants(&mut defs);
|
||||
|
||||
// Annotate $defs entries with x-method/x-side. Only set x-method for types
|
||||
// used by exactly one method (shared types like EmptyResponse skip x-method).
|
||||
for (name, methods_list) in &type_methods {
|
||||
@@ -316,6 +318,42 @@ fn rewrite_unstable_schema_refs(value: &mut Value, unstable_type_names: &BTreeSe
|
||||
}
|
||||
}
|
||||
|
||||
fn add_mcp_server_transport_discriminants(defs: &mut Map<String, Value>) {
|
||||
add_object_discriminant(defs, "McpServerHttp", "http");
|
||||
add_object_discriminant(defs, "McpServerSse", "sse");
|
||||
}
|
||||
|
||||
fn add_object_discriminant(defs: &mut Map<String, Value>, def_name: &str, tag: &str) {
|
||||
let def = defs
|
||||
.get_mut(def_name)
|
||||
.unwrap_or_else(|| panic!("missing {def_name} schema definition"));
|
||||
let obj = def
|
||||
.as_object_mut()
|
||||
.unwrap_or_else(|| panic!("{def_name} schema definition must be an object"));
|
||||
|
||||
let properties = obj
|
||||
.entry("properties")
|
||||
.or_insert_with(|| json!({}))
|
||||
.as_object_mut()
|
||||
.unwrap_or_else(|| panic!("{def_name}.properties must be an object"));
|
||||
properties.insert(
|
||||
"type".into(),
|
||||
json!({
|
||||
"type": "string",
|
||||
"const": tag,
|
||||
}),
|
||||
);
|
||||
|
||||
let required = obj
|
||||
.entry("required")
|
||||
.or_insert_with(|| json!([]))
|
||||
.as_array_mut()
|
||||
.unwrap_or_else(|| panic!("{def_name}.required must be an array"));
|
||||
if !required.iter().any(|item| item.as_str() == Some("type")) {
|
||||
required.insert(0, json!("type"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively strip `"format"` from integer-typed schemas.
|
||||
///
|
||||
/// schemars emits `"format": "uint64"` / `"int64"` etc. for Rust integer types.
|
||||
@@ -323,7 +361,15 @@ fn rewrite_unstable_schema_refs(value: &mut Value, unstable_type_names: &BTreeSe
|
||||
fn strip_integer_formats(value: &mut Value) {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let is_integer = map.get("type").and_then(|v| v.as_str()) == Some("integer");
|
||||
let is_integer = match map.get("type") {
|
||||
Some(Value::String(schema_type)) => schema_type == "integer",
|
||||
Some(Value::Array(schema_types)) => schema_types.iter().any(|schema_type| {
|
||||
schema_type
|
||||
.as_str()
|
||||
.is_some_and(|schema_type| schema_type == "integer")
|
||||
}),
|
||||
_ => false,
|
||||
};
|
||||
if is_integer {
|
||||
map.remove("format");
|
||||
}
|
||||
@@ -367,3 +413,105 @@ fn replace_true_schemas(value: &mut Value) {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn adds_http_and_sse_discriminants_without_tagging_stdio() {
|
||||
let mut defs = Map::from_iter([
|
||||
(
|
||||
"McpServerHttp".into(),
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}),
|
||||
),
|
||||
(
|
||||
"McpServerSse".into(),
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}),
|
||||
),
|
||||
(
|
||||
"McpServerStdio".into(),
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}),
|
||||
),
|
||||
]);
|
||||
|
||||
add_mcp_server_transport_discriminants(&mut defs);
|
||||
|
||||
assert_eq!(
|
||||
defs["McpServerHttp"]["properties"]["type"],
|
||||
json!({ "type": "string", "const": "http" })
|
||||
);
|
||||
assert_eq!(
|
||||
defs["McpServerSse"]["properties"]["type"],
|
||||
json!({ "type": "string", "const": "sse" })
|
||||
);
|
||||
assert_eq!(defs["McpServerStdio"]["properties"].get("type"), None);
|
||||
assert!(defs["McpServerHttp"]["required"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.contains(&json!("type")));
|
||||
assert!(defs["McpServerSse"]["required"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.contains(&json!("type")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_integer_formats_from_nullable_integer_schemas() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"timeout": {
|
||||
"type": ["integer", "null"],
|
||||
"format": "uint64",
|
||||
"minimum": 0
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"format": "custom"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
strip_integer_formats(&mut schema);
|
||||
|
||||
assert_eq!(
|
||||
schema["properties"]["timeout"].get("format"),
|
||||
None,
|
||||
"nullable integer formats should be stripped"
|
||||
);
|
||||
assert_eq!(
|
||||
schema["properties"]["count"].get("format"),
|
||||
None,
|
||||
"integer formats should be stripped"
|
||||
);
|
||||
assert_eq!(
|
||||
schema["properties"]["name"]["format"],
|
||||
json!("custom"),
|
||||
"non-integer formats should be preserved"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,3 +22,7 @@ pub fn register_builtin_extensions(extensions: HashMap<&'static str, SpawnServer
|
||||
pub fn get_builtin_extension(name: &str) -> Option<SpawnServerFn> {
|
||||
BUILTIN_REGISTRY.read().unwrap().get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn get_builtin_extension_names() -> Vec<&'static str> {
|
||||
BUILTIN_REGISTRY.read().unwrap().keys().copied().collect()
|
||||
}
|
||||
|
||||
@@ -106,12 +106,16 @@ pub fn remove_extension(key: &str) {
|
||||
save_extensions_map(extensions);
|
||||
}
|
||||
|
||||
pub fn set_extension_enabled(key: &str, enabled: bool) {
|
||||
/// Returns true when an existing extension was updated, false when the key was missing.
|
||||
pub fn set_extension_enabled(key: &str, enabled: bool) -> bool {
|
||||
let mut extensions = get_extensions_map();
|
||||
if let Some(entry) = extensions.get_mut(key) {
|
||||
entry.enabled = enabled;
|
||||
save_extensions_map(extensions);
|
||||
}
|
||||
let Some(entry) = extensions.get_mut(key) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
entry.enabled = enabled;
|
||||
save_extensions_map(extensions);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn get_all_extensions() -> Vec<ExtensionEntry> {
|
||||
@@ -145,6 +149,40 @@ pub fn get_enabled_extensions_with_config(config: &Config) -> Vec<ExtensionConfi
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_available_extensions() -> Vec<ExtensionConfig> {
|
||||
let mut builtin_names = crate::builtin_extension::get_builtin_extension_names();
|
||||
builtin_names.sort_unstable();
|
||||
|
||||
let mut platform_definitions = PLATFORM_EXTENSIONS
|
||||
.values()
|
||||
.filter(|definition| !definition.hidden)
|
||||
.collect::<Vec<_>>();
|
||||
platform_definitions.sort_unstable_by_key(|definition| definition.name);
|
||||
|
||||
builtin_names
|
||||
.into_iter()
|
||||
.map(|name| ExtensionConfig::Builtin {
|
||||
name: name.to_string(),
|
||||
description: String::new(),
|
||||
display_name: Some(name.to_string()),
|
||||
timeout: None,
|
||||
bundled: Some(true),
|
||||
available_tools: Vec::new(),
|
||||
})
|
||||
.chain(
|
||||
platform_definitions
|
||||
.into_iter()
|
||||
.map(|definition| ExtensionConfig::Platform {
|
||||
name: definition.name.to_string(),
|
||||
description: definition.description.to_string(),
|
||||
display_name: Some(definition.display_name.to_string()),
|
||||
bundled: Some(true),
|
||||
available_tools: Vec::new(),
|
||||
}),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_warnings() -> Vec<String> {
|
||||
let raw: Mapping = Config::global()
|
||||
.get_param(EXTENSIONS_CONFIG_KEY)
|
||||
|
||||
@@ -17,9 +17,9 @@ pub use base::{merge_config_values, Config, ConfigError};
|
||||
pub use declarative_providers::DeclarativeProviderConfig;
|
||||
pub use experiments::ExperimentManager;
|
||||
pub use extensions::{
|
||||
get_all_extension_names, get_all_extensions, get_enabled_extensions, get_extension_by_name,
|
||||
get_warnings, is_extension_enabled, remove_extension, resolve_extensions_for_new_session,
|
||||
set_extension, set_extension_enabled, ExtensionEntry,
|
||||
get_all_extension_names, get_all_extensions, get_available_extensions, get_enabled_extensions,
|
||||
get_extension_by_name, get_warnings, is_extension_enabled, remove_extension,
|
||||
resolve_extensions_for_new_session, set_extension, set_extension_enabled, ExtensionEntry,
|
||||
};
|
||||
pub use goose_mode::GooseMode;
|
||||
pub use permission::PermissionManager;
|
||||
|
||||
@@ -18,15 +18,21 @@ use std::sync::{Arc, LazyLock, Mutex};
|
||||
|
||||
use common_tests::fixtures::OpenAiFixture;
|
||||
|
||||
const DEFAULT_ACP_TEST_CONFIG: &str = "GOOSE_MODEL: gpt-4o\nGOOSE_PROVIDER: openai\n";
|
||||
const DEFAULT_ACP_TEST_CONFIG: &str =
|
||||
"GOOSE_MODEL: gpt-4o\nGOOSE_PROVIDER: openai\nGOOSE_DISABLE_KEYRING: true\n";
|
||||
|
||||
static ACP_CONFIG_ROOT: LazyLock<tempfile::TempDir> =
|
||||
LazyLock::new(|| tempfile::tempdir().unwrap());
|
||||
|
||||
fn write_acp_global_config(contents: &str) -> PathBuf {
|
||||
std::env::set_var("GOOSE_PATH_ROOT", ACP_CONFIG_ROOT.path());
|
||||
std::env::set_var("GOOSE_DISABLE_KEYRING", "1");
|
||||
let config_dir = goose::config::paths::Paths::config_dir();
|
||||
std::fs::create_dir_all(&config_dir).unwrap();
|
||||
let mut contents = contents.to_string();
|
||||
if !contents.contains("GOOSE_DISABLE_KEYRING") {
|
||||
contents.push_str("GOOSE_DISABLE_KEYRING: true\n");
|
||||
}
|
||||
std::fs::write(
|
||||
config_dir.join(goose::config::base::CONFIG_YAML_NAME),
|
||||
contents,
|
||||
@@ -120,6 +126,132 @@ fn test_custom_get_tools() {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_custom_get_extensions() {
|
||||
let config_key = "test-stdio-acp-mutation-flow";
|
||||
let _guard = env_lock::lock_env([("EXTENSIONS", None::<&str>)]);
|
||||
write_acp_global_config(DEFAULT_ACP_TEST_CONFIG);
|
||||
|
||||
run_test(async move {
|
||||
let openai = OpenAiFixture::new(vec![], Arc::new(EnforceSessionId::default())).await;
|
||||
let conn = AcpServerConnection::new(TestConnectionConfig::default(), openai).await;
|
||||
|
||||
let add_result = send_custom(
|
||||
conn.cx(),
|
||||
"_goose/unstable/config/extensions/add",
|
||||
serde_json::json!({
|
||||
"enabled": true,
|
||||
"extension": {
|
||||
"type": "mcp",
|
||||
"description": "Test stdio",
|
||||
"envKeys": ["SECRET_TOKEN"],
|
||||
"timeout": 42,
|
||||
"server": {
|
||||
"type": "stdio",
|
||||
"name": config_key,
|
||||
"command": "test-command",
|
||||
"args": ["--flag", "value"],
|
||||
"env": [
|
||||
{ "name": "INLINE_TOKEN", "value": "inline-secret" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
assert!(add_result.is_ok(), "expected ok, got: {:?}", add_result);
|
||||
let stored_inline_token = goose::config::Config::global()
|
||||
.get_secret::<String>("INLINE_TOKEN")
|
||||
.expect("inline env should be saved as a secret");
|
||||
assert!(
|
||||
stored_inline_token == "inline-secret",
|
||||
"inline env secret was not saved correctly"
|
||||
);
|
||||
|
||||
let list_extension = || async {
|
||||
let result = send_custom(
|
||||
conn.cx(),
|
||||
"_goose/unstable/config/extensions/list",
|
||||
serde_json::json!({}),
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_ok(), "expected ok, got: {:?}", result);
|
||||
|
||||
let response = result.unwrap();
|
||||
let extensions = response
|
||||
.get("extensions")
|
||||
.and_then(|extensions| extensions.as_array())
|
||||
.expect("extensions should be an array");
|
||||
extensions
|
||||
.iter()
|
||||
.find(|entry| entry["configKey"] == config_key)
|
||||
.cloned()
|
||||
};
|
||||
|
||||
let entry = list_extension()
|
||||
.await
|
||||
.unwrap_or_else(|| panic!("missing added extension entry"));
|
||||
assert_eq!(entry["enabled"], true);
|
||||
assert_eq!(entry["configKey"], config_key);
|
||||
|
||||
let extension = &entry["extension"];
|
||||
assert_eq!(extension["type"], "mcp");
|
||||
assert_eq!(
|
||||
extension["envKeys"],
|
||||
serde_json::json!(["SECRET_TOKEN", "INLINE_TOKEN"])
|
||||
);
|
||||
assert_eq!(extension["description"], "Test stdio");
|
||||
assert_eq!(extension["timeout"], 42);
|
||||
assert!(extension.get("socket").is_none());
|
||||
|
||||
let server = &extension["server"];
|
||||
assert_eq!(server["name"], config_key);
|
||||
assert_eq!(server["command"], "test-command");
|
||||
assert_eq!(server["args"], serde_json::json!(["--flag", "value"]));
|
||||
assert_eq!(server["env"], serde_json::json!([]));
|
||||
|
||||
let set_enabled_result = send_custom(
|
||||
conn.cx(),
|
||||
"_goose/unstable/config/extensions/set-enabled",
|
||||
serde_json::json!({
|
||||
"configKey": config_key,
|
||||
"enabled": false,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
set_enabled_result.is_ok(),
|
||||
"expected ok, got: {:?}",
|
||||
set_enabled_result
|
||||
);
|
||||
|
||||
let entry = list_extension()
|
||||
.await
|
||||
.unwrap_or_else(|| panic!("missing disabled extension entry"));
|
||||
assert_eq!(entry["enabled"], false);
|
||||
|
||||
let remove_result = send_custom(
|
||||
conn.cx(),
|
||||
"_goose/unstable/config/extensions/remove",
|
||||
serde_json::json!({
|
||||
"configKey": config_key,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
remove_result.is_ok(),
|
||||
"expected ok, got: {:?}",
|
||||
remove_result
|
||||
);
|
||||
|
||||
assert!(
|
||||
list_extension().await.is_none(),
|
||||
"removed extension should not be listed"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_custom_get_available_extensions() {
|
||||
write_acp_global_config(DEFAULT_ACP_TEST_CONFIG);
|
||||
run_test(async move {
|
||||
let openai = OpenAiFixture::new(vec![], Arc::new(EnforceSessionId::default())).await;
|
||||
@@ -127,20 +259,36 @@ fn test_custom_get_extensions() {
|
||||
|
||||
let result = send_custom(
|
||||
conn.cx(),
|
||||
"_goose/unstable/config/extensions/list",
|
||||
"_goose/unstable/extensions/available",
|
||||
serde_json::json!({}),
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_ok(), "expected ok, got: {:?}", result);
|
||||
|
||||
let response = result.unwrap();
|
||||
let extensions = response
|
||||
.get("extensions")
|
||||
.and_then(|extensions| extensions.as_array())
|
||||
.expect("extensions should be an array");
|
||||
assert!(!extensions.is_empty(), "extensions should not be empty");
|
||||
assert!(
|
||||
response.get("extensions").is_some(),
|
||||
"missing 'extensions' field"
|
||||
extensions.iter().all(|extension| matches!(
|
||||
extension["type"].as_str(),
|
||||
Some("builtin" | "platform")
|
||||
)),
|
||||
"available extensions should only include builtin and platform entries"
|
||||
);
|
||||
assert!(
|
||||
response.get("warnings").is_some(),
|
||||
"missing 'warnings' field"
|
||||
extensions.iter().any(|extension| {
|
||||
extension["type"] == "platform" && extension["name"] == "developer"
|
||||
}),
|
||||
"developer platform extension should be available"
|
||||
);
|
||||
assert!(
|
||||
!extensions.iter().any(|extension| {
|
||||
extension["type"] == "platform" && extension["name"] == "orchestrator"
|
||||
}),
|
||||
"hidden orchestrator platform extension should not be available"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,77 @@
|
||||
import type { ExtensionResponse, ExtensionEntry } from '../api';
|
||||
import type { GooseExtensionEntry, McpServer } from '@aaif/goose-sdk';
|
||||
import { getAcpClient } from './acpConnection';
|
||||
|
||||
function headersToRecord(headers: { name: string; value: string }[] = []) {
|
||||
return Object.fromEntries(headers.map(({ name, value }) => [name, value]));
|
||||
}
|
||||
|
||||
function mcpServerToExtension(
|
||||
server: McpServer,
|
||||
entry: GooseExtensionEntry
|
||||
): ExtensionEntry | null {
|
||||
const extension = entry.extension;
|
||||
if (extension.type !== 'mcp') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('command' in server) {
|
||||
return {
|
||||
type: 'stdio',
|
||||
enabled: entry.enabled,
|
||||
name: server.name,
|
||||
description: extension.description ?? '',
|
||||
cmd: server.command,
|
||||
args: server.args,
|
||||
env_keys: extension.envKeys ?? [],
|
||||
timeout: extension.timeout,
|
||||
bundled: extension.bundled,
|
||||
};
|
||||
}
|
||||
|
||||
if ('url' in server) {
|
||||
return {
|
||||
type: 'streamable_http',
|
||||
enabled: entry.enabled,
|
||||
name: server.name,
|
||||
description: extension.description ?? '',
|
||||
uri: server.url,
|
||||
headers: headersToRecord(server.headers),
|
||||
env_keys: extension.envKeys ?? [],
|
||||
timeout: extension.timeout,
|
||||
socket: extension.socket,
|
||||
bundled: extension.bundled,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function gooseExtensionEntryToExtensionEntry(entry: GooseExtensionEntry): ExtensionEntry | null {
|
||||
const extension = entry.extension;
|
||||
|
||||
switch (extension.type) {
|
||||
case 'builtin':
|
||||
case 'platform':
|
||||
return {
|
||||
...extension,
|
||||
description: extension.description ?? '',
|
||||
enabled: entry.enabled,
|
||||
};
|
||||
case 'mcp':
|
||||
return mcpServerToExtension(extension.server, entry);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getConfiguredExtensions(): Promise<ExtensionResponse> {
|
||||
const client = await getAcpClient();
|
||||
const response = await client.goose.configExtensionsList_unstable({});
|
||||
return {
|
||||
extensions: response.extensions as ExtensionEntry[],
|
||||
warnings: response.warnings,
|
||||
extensions: response.extensions
|
||||
.map(gooseExtensionEntryToExtensionEntry)
|
||||
.filter((entry): entry is ExtensionEntry => entry !== null),
|
||||
warnings: response.warnings ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,8 +46,10 @@ import type {
|
||||
ExportSessionResponse_unstable,
|
||||
ExportSourceRequest_unstable,
|
||||
ExportSourceResponse_unstable,
|
||||
GetExtensionsRequest_unstable,
|
||||
GetExtensionsResponse_unstable,
|
||||
GetAvailableExtensionsRequest_unstable,
|
||||
GetAvailableExtensionsResponse_unstable,
|
||||
GetConfigExtensionsRequest_unstable,
|
||||
GetConfigExtensionsResponse_unstable,
|
||||
GetSessionExtensionsRequest_unstable,
|
||||
GetSessionExtensionsResponse_unstable,
|
||||
GetToolsRequest_unstable,
|
||||
@@ -94,8 +96,8 @@ import type {
|
||||
RemoveConfigExtensionRequest_unstable,
|
||||
RemoveExtensionRequest_unstable,
|
||||
RenameSessionRequest_unstable,
|
||||
SetConfigExtensionEnabledRequest_unstable,
|
||||
SetSessionSystemPromptRequest_unstable,
|
||||
ToggleConfigExtensionRequest_unstable,
|
||||
UnarchiveSessionRequest_unstable,
|
||||
UpdateSessionProjectRequest_unstable,
|
||||
UpdateSourceRequest_unstable,
|
||||
@@ -115,7 +117,8 @@ import {
|
||||
zDictationTranscribeResponse_unstable,
|
||||
zExportSessionResponse_unstable,
|
||||
zExportSourceResponse_unstable,
|
||||
zGetExtensionsResponse_unstable,
|
||||
zGetAvailableExtensionsResponse_unstable,
|
||||
zGetConfigExtensionsResponse_unstable,
|
||||
zGetSessionExtensionsResponse_unstable,
|
||||
zGetToolsResponse_unstable,
|
||||
zGooseSessionNotification_unstable,
|
||||
@@ -208,15 +211,27 @@ export class GooseExtClient {
|
||||
}
|
||||
|
||||
async configExtensionsList_unstable(
|
||||
params: GetExtensionsRequest_unstable,
|
||||
): Promise<GetExtensionsResponse_unstable> {
|
||||
params: GetConfigExtensionsRequest_unstable,
|
||||
): Promise<GetConfigExtensionsResponse_unstable> {
|
||||
const raw = await this.conn.extMethod(
|
||||
"_goose/unstable/config/extensions/list",
|
||||
params,
|
||||
);
|
||||
return zGetExtensionsResponse_unstable.parse(
|
||||
return zGetConfigExtensionsResponse_unstable.parse(
|
||||
raw,
|
||||
) as GetExtensionsResponse_unstable;
|
||||
) as GetConfigExtensionsResponse_unstable;
|
||||
}
|
||||
|
||||
async extensionsAvailable_unstable(
|
||||
params: GetAvailableExtensionsRequest_unstable,
|
||||
): Promise<GetAvailableExtensionsResponse_unstable> {
|
||||
const raw = await this.conn.extMethod(
|
||||
"_goose/unstable/extensions/available",
|
||||
params,
|
||||
);
|
||||
return zGetAvailableExtensionsResponse_unstable.parse(
|
||||
raw,
|
||||
) as GetAvailableExtensionsResponse_unstable;
|
||||
}
|
||||
|
||||
async configExtensionsAdd_unstable(
|
||||
@@ -234,11 +249,11 @@ export class GooseExtClient {
|
||||
);
|
||||
}
|
||||
|
||||
async configExtensionsToggle_unstable(
|
||||
params: ToggleConfigExtensionRequest_unstable,
|
||||
async configExtensionsSetEnabled_unstable(
|
||||
params: SetConfigExtensionEnabledRequest_unstable,
|
||||
): Promise<void> {
|
||||
await this.conn.extMethod(
|
||||
"_goose/unstable/config/extensions/toggle",
|
||||
"_goose/unstable/config/extensions/set-enabled",
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export type { AddConfigExtensionRequest_unstable, AddExtensionRequest_unstable, ArchiveSessionRequest_unstable, CreateSourceRequest_unstable, CreateSourceResponse_unstable, CustomProviderConfigDto, CustomProviderCreateRequest_unstable, CustomProviderCreateResponse_unstable, CustomProviderDeleteRequest_unstable, CustomProviderDeleteResponse_unstable, CustomProviderReadRequest_unstable, CustomProviderReadResponse_unstable, CustomProviderUpdateRequest_unstable, CustomProviderUpdateResponse_unstable, DefaultsReadRequest_unstable, DefaultsReadResponse_unstable, DefaultsSaveRequest_unstable, DeleteSessionRequest, DeleteSourceRequest_unstable, DictationConfigRequest_unstable, DictationConfigResponse_unstable, DictationDownloadProgress, DictationLocalModelStatus, DictationModelCancelRequest_unstable, DictationModelDeleteRequest_unstable, DictationModelDownloadProgressRequest_unstable, DictationModelDownloadProgressResponse_unstable, DictationModelDownloadRequest_unstable, DictationModelOption, DictationModelSelectRequest_unstable, DictationModelsListRequest_unstable, DictationModelsListResponse_unstable, DictationProviderStatusEntry, DictationSecretDeleteRequest_unstable, DictationSecretSaveRequest_unstable, DictationTranscribeRequest_unstable, DictationTranscribeResponse_unstable, ElicitationRespondRequest_unstable, EmptyResponse, ExportSessionRequest_unstable, ExportSessionResponse_unstable, ExportSourceRequest_unstable, ExportSourceResponse_unstable, ExtNotification, ExtRequest, ExtResponse, GetExtensionsRequest_unstable, GetExtensionsResponse_unstable, GetSessionExtensionsRequest_unstable, GetSessionExtensionsResponse_unstable, GetToolsRequest_unstable, GetToolsResponse_unstable, GooseSessionNotification_unstable, GooseSessionUpdate, GooseToolCallRequest_unstable, GooseToolCallResponse_unstable, ImportSessionRequest_unstable, ImportSessionResponse_unstable, ImportSourcesRequest_unstable, ImportSourcesResponse_unstable, Interaction, InteractionState, InteractionUpdate, ListProvidersRequest_unstable, ListProvidersResponse_unstable, ListSourcesRequest_unstable, ListSourcesResponse_unstable, OnboardingImportApplyRequest_unstable, OnboardingImportApplyResponse_unstable, OnboardingImportCandidate, OnboardingImportCounts, OnboardingImportScanRequest_unstable, OnboardingImportScanResponse_unstable, OnboardingImportSourceKind, PreferenceKey, PreferencesReadRequest_unstable, PreferencesReadResponse_unstable, PreferencesRemoveRequest_unstable, PreferencesSaveRequest_unstable, PreferenceValue, ProviderCatalogListRequest_unstable, ProviderCatalogListResponse_unstable, ProviderCatalogTemplateRequest_unstable, ProviderCatalogTemplateResponse_unstable, ProviderConfigAuthenticateRequest_unstable, ProviderConfigChangeResponse_unstable, ProviderConfigDeleteRequest_unstable, ProviderConfigFieldUpdate, ProviderConfigFieldValueDto, ProviderConfigKey, ProviderConfigReadRequest_unstable, ProviderConfigReadResponse_unstable, ProviderConfigSaveRequest_unstable, ProviderConfigStatusDto, ProviderConfigStatusRequest_unstable, ProviderConfigStatusResponse_unstable, ProviderInventoryEntryDto, ProviderInventoryModelDto, ProviderSetupCatalogEntryDto, ProviderSetupCatalogListRequest_unstable, ProviderSetupCatalogListResponse_unstable, ProviderSetupCategoryDto, ProviderSetupFieldDto, ProviderSetupGroupDto, ProviderSetupMethodDto, ProviderSupportedModelsListRequest_unstable, ProviderSupportedModelsListResponse_unstable, ProviderTemplateCapabilitiesDto, ProviderTemplateCatalogEntryDto, ProviderTemplateDto, ProviderTemplateModelDto, ReadResourceRequest_unstable, ReadResourceResponse_unstable, RefreshProviderInventoryRequest_unstable, RefreshProviderInventoryResponse_unstable, RefreshProviderInventorySkipDto, RefreshProviderInventorySkipReasonDto, RemoveConfigExtensionRequest_unstable, RemoveExtensionRequest_unstable, RenameSessionRequest_unstable, SessionSystemPromptMode, SessionUsageUpdate, SetSessionSystemPromptRequest_unstable, SourceEntry, SourceScope, SourceType, StatusMessage, StatusMessageUpdate, ToggleConfigExtensionRequest_unstable, UnarchiveSessionRequest_unstable, UpdateSessionProjectRequest_unstable, UpdateSourceRequest_unstable, UpdateSourceResponse_unstable, UpdateWorkingDirRequest_unstable } from './types.gen.js';
|
||||
export type { AddConfigExtensionRequest_unstable, AddExtensionRequest_unstable, ArchiveSessionRequest_unstable, CreateSourceRequest_unstable, CreateSourceResponse_unstable, CustomProviderConfigDto, CustomProviderCreateRequest_unstable, CustomProviderCreateResponse_unstable, CustomProviderDeleteRequest_unstable, CustomProviderDeleteResponse_unstable, CustomProviderReadRequest_unstable, CustomProviderReadResponse_unstable, CustomProviderUpdateRequest_unstable, CustomProviderUpdateResponse_unstable, DefaultsReadRequest_unstable, DefaultsReadResponse_unstable, DefaultsSaveRequest_unstable, DeleteSessionRequest, DeleteSourceRequest_unstable, DictationConfigRequest_unstable, DictationConfigResponse_unstable, DictationDownloadProgress, DictationLocalModelStatus, DictationModelCancelRequest_unstable, DictationModelDeleteRequest_unstable, DictationModelDownloadProgressRequest_unstable, DictationModelDownloadProgressResponse_unstable, DictationModelDownloadRequest_unstable, DictationModelOption, DictationModelSelectRequest_unstable, DictationModelsListRequest_unstable, DictationModelsListResponse_unstable, DictationProviderStatusEntry, DictationSecretDeleteRequest_unstable, DictationSecretSaveRequest_unstable, DictationTranscribeRequest_unstable, DictationTranscribeResponse_unstable, ElicitationRespondRequest_unstable, EmptyResponse, EnvVariable, ExportSessionRequest_unstable, ExportSessionResponse_unstable, ExportSourceRequest_unstable, ExportSourceResponse_unstable, ExtNotification, ExtRequest, ExtResponse, GetAvailableExtensionsRequest_unstable, GetAvailableExtensionsResponse_unstable, GetConfigExtensionsRequest_unstable, GetConfigExtensionsResponse_unstable, GetSessionExtensionsRequest_unstable, GetSessionExtensionsResponse_unstable, GetToolsRequest_unstable, GetToolsResponse_unstable, GooseExtension, GooseExtensionEntry, GooseSessionNotification_unstable, GooseSessionUpdate, GooseToolCallRequest_unstable, GooseToolCallResponse_unstable, HttpHeader, ImportSessionRequest_unstable, ImportSessionResponse_unstable, ImportSourcesRequest_unstable, ImportSourcesResponse_unstable, Interaction, InteractionState, InteractionUpdate, ListProvidersRequest_unstable, ListProvidersResponse_unstable, ListSourcesRequest_unstable, ListSourcesResponse_unstable, McpServer, McpServerHttp, McpServerSse, McpServerStdio, OnboardingImportApplyRequest_unstable, OnboardingImportApplyResponse_unstable, OnboardingImportCandidate, OnboardingImportCounts, OnboardingImportScanRequest_unstable, OnboardingImportScanResponse_unstable, OnboardingImportSourceKind, PreferenceKey, PreferencesReadRequest_unstable, PreferencesReadResponse_unstable, PreferencesRemoveRequest_unstable, PreferencesSaveRequest_unstable, PreferenceValue, ProviderCatalogListRequest_unstable, ProviderCatalogListResponse_unstable, ProviderCatalogTemplateRequest_unstable, ProviderCatalogTemplateResponse_unstable, ProviderConfigAuthenticateRequest_unstable, ProviderConfigChangeResponse_unstable, ProviderConfigDeleteRequest_unstable, ProviderConfigFieldUpdate, ProviderConfigFieldValueDto, ProviderConfigKey, ProviderConfigReadRequest_unstable, ProviderConfigReadResponse_unstable, ProviderConfigSaveRequest_unstable, ProviderConfigStatusDto, ProviderConfigStatusRequest_unstable, ProviderConfigStatusResponse_unstable, ProviderInventoryEntryDto, ProviderInventoryModelDto, ProviderSetupCatalogEntryDto, ProviderSetupCatalogListRequest_unstable, ProviderSetupCatalogListResponse_unstable, ProviderSetupCategoryDto, ProviderSetupFieldDto, ProviderSetupGroupDto, ProviderSetupMethodDto, ProviderSupportedModelsListRequest_unstable, ProviderSupportedModelsListResponse_unstable, ProviderTemplateCapabilitiesDto, ProviderTemplateCatalogEntryDto, ProviderTemplateDto, ProviderTemplateModelDto, ReadResourceRequest_unstable, ReadResourceResponse_unstable, RefreshProviderInventoryRequest_unstable, RefreshProviderInventoryResponse_unstable, RefreshProviderInventorySkipDto, RefreshProviderInventorySkipReasonDto, RemoveConfigExtensionRequest_unstable, RemoveExtensionRequest_unstable, RenameSessionRequest_unstable, SessionSystemPromptMode, SessionUsageUpdate, SetConfigExtensionEnabledRequest_unstable, SetSessionSystemPromptRequest_unstable, SourceEntry, SourceScope, SourceType, StatusMessage, StatusMessageUpdate, UnarchiveSessionRequest_unstable, UpdateSessionProjectRequest_unstable, UpdateSourceRequest_unstable, UpdateSourceResponse_unstable, UpdateWorkingDirRequest_unstable } from './types.gen.js';
|
||||
|
||||
export const GOOSE_EXT_METHODS = [
|
||||
{
|
||||
@@ -45,8 +45,13 @@ export const GOOSE_EXT_METHODS = [
|
||||
},
|
||||
{
|
||||
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",
|
||||
@@ -59,8 +64,8 @@ export const GOOSE_EXT_METHODS = [
|
||||
responseType: "EmptyResponse",
|
||||
},
|
||||
{
|
||||
method: "_goose/unstable/config/extensions/toggle",
|
||||
requestType: "ToggleConfigExtensionRequest_unstable",
|
||||
method: "_goose/unstable/config/extensions/set-enabled",
|
||||
requestType: "SetConfigExtensionEnabledRequest_unstable",
|
||||
responseType: "EmptyResponse",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -119,32 +119,211 @@ export type DeleteSessionRequest = {
|
||||
/**
|
||||
* List configured extensions and any warnings.
|
||||
*/
|
||||
export type GetExtensionsRequest_unstable = {
|
||||
export type GetConfigExtensionsRequest_unstable = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
* List configured extensions and any warnings.
|
||||
*/
|
||||
export type GetExtensionsResponse_unstable = {
|
||||
export type GetConfigExtensionsResponse_unstable = {
|
||||
extensions: Array<GooseExtensionEntry>;
|
||||
warnings?: Array<string>;
|
||||
};
|
||||
|
||||
export type GooseExtensionEntry = {
|
||||
extension: GooseExtension;
|
||||
enabled: boolean;
|
||||
configKey?: string | null;
|
||||
};
|
||||
|
||||
export type GooseExtension = {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
display_name?: string | null;
|
||||
timeout?: number | null;
|
||||
bundled?: boolean | null;
|
||||
type: 'builtin';
|
||||
} | {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
display_name?: string | null;
|
||||
bundled?: boolean | null;
|
||||
type: 'platform';
|
||||
} | {
|
||||
server: McpServer;
|
||||
envKeys?: Array<string>;
|
||||
description?: string | null;
|
||||
timeout?: number | null;
|
||||
socket?: string | null;
|
||||
bundled?: boolean | null;
|
||||
type: 'mcp';
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration for connecting to an MCP (Model Context Protocol) server.
|
||||
*
|
||||
* MCP servers provide tools and context that the agent can use when
|
||||
* processing prompts.
|
||||
*
|
||||
* See protocol docs: [MCP Servers](https://agentclientprotocol.com/protocol/session-setup#mcp-servers)
|
||||
*/
|
||||
export type McpServer = McpServerHttp | McpServerSse | McpServerStdio;
|
||||
|
||||
/**
|
||||
* An HTTP header to set when making requests to the MCP server.
|
||||
*/
|
||||
export type HttpHeader = {
|
||||
/**
|
||||
* Array of ExtensionEntry objects with `enabled` flag, `configKey`, and flattened config details.
|
||||
* The name of the HTTP header.
|
||||
*/
|
||||
extensions: Array<unknown>;
|
||||
warnings: Array<string>;
|
||||
name: string;
|
||||
/**
|
||||
* The value to set for the HTTP header.
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* The _meta property is reserved by ACP to allow clients and agents to attach additional
|
||||
* metadata to their interactions. Implementations MUST NOT make assumptions about values at
|
||||
* these keys.
|
||||
*
|
||||
* See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
|
||||
*/
|
||||
_meta?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* HTTP transport configuration for MCP.
|
||||
*/
|
||||
export type McpServerHttp = {
|
||||
/**
|
||||
* Human-readable name identifying this MCP server.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* URL to the MCP server.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* HTTP headers to set when making requests to the MCP server.
|
||||
*/
|
||||
headers: Array<HttpHeader>;
|
||||
/**
|
||||
* The _meta property is reserved by ACP to allow clients and agents to attach additional
|
||||
* metadata to their interactions. Implementations MUST NOT make assumptions about values at
|
||||
* these keys.
|
||||
*
|
||||
* See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
|
||||
*/
|
||||
_meta?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
type: 'http';
|
||||
};
|
||||
|
||||
/**
|
||||
* SSE transport configuration for MCP.
|
||||
*/
|
||||
export type McpServerSse = {
|
||||
/**
|
||||
* Human-readable name identifying this MCP server.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* URL to the MCP server.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* HTTP headers to set when making requests to the MCP server.
|
||||
*/
|
||||
headers: Array<HttpHeader>;
|
||||
/**
|
||||
* The _meta property is reserved by ACP to allow clients and agents to attach additional
|
||||
* metadata to their interactions. Implementations MUST NOT make assumptions about values at
|
||||
* these keys.
|
||||
*
|
||||
* See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
|
||||
*/
|
||||
_meta?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
type: 'sse';
|
||||
};
|
||||
|
||||
/**
|
||||
* Stdio transport configuration for MCP.
|
||||
*/
|
||||
export type McpServerStdio = {
|
||||
/**
|
||||
* Human-readable name identifying this MCP server.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Path to the MCP server executable.
|
||||
*/
|
||||
command: string;
|
||||
/**
|
||||
* Command-line arguments to pass to the MCP server.
|
||||
*/
|
||||
args: Array<string>;
|
||||
/**
|
||||
* Environment variables to set when launching the MCP server.
|
||||
*/
|
||||
env: Array<EnvVariable>;
|
||||
/**
|
||||
* The _meta property is reserved by ACP to allow clients and agents to attach additional
|
||||
* metadata to their interactions. Implementations MUST NOT make assumptions about values at
|
||||
* these keys.
|
||||
*
|
||||
* See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
|
||||
*/
|
||||
_meta?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* An environment variable to set when launching an MCP server.
|
||||
*/
|
||||
export type EnvVariable = {
|
||||
/**
|
||||
* The name of the environment variable.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The value to set for the environment variable.
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* The _meta property is reserved by ACP to allow clients and agents to attach additional
|
||||
* metadata to their interactions. Implementations MUST NOT make assumptions about values at
|
||||
* these keys.
|
||||
*
|
||||
* See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
|
||||
*/
|
||||
_meta?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* List Goose-owned extension definitions available to configure or enable.
|
||||
*/
|
||||
export type GetAvailableExtensionsRequest_unstable = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type GetAvailableExtensionsResponse_unstable = {
|
||||
extensions: Array<GooseExtension>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Persist a new extension to the user's global goose config.
|
||||
*/
|
||||
export type AddConfigExtensionRequest_unstable = {
|
||||
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.
|
||||
*/
|
||||
extensionConfig?: unknown;
|
||||
extension: GooseExtension;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
@@ -156,9 +335,9 @@ export type RemoveConfigExtensionRequest_unstable = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export type ToggleConfigExtensionRequest_unstable = {
|
||||
export type SetConfigExtensionEnabledRequest_unstable = {
|
||||
configKey: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
@@ -1166,14 +1345,14 @@ export type InteractionUpdate = {
|
||||
export type ExtRequest = {
|
||||
id: string;
|
||||
method: string;
|
||||
params?: AddExtensionRequest_unstable | RemoveExtensionRequest_unstable | GetToolsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | DeleteSessionRequest | GetExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | ToggleConfigExtensionRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | ElicitationRespondRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | {
|
||||
params?: AddExtensionRequest_unstable | RemoveExtensionRequest_unstable | GetToolsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | ElicitationRespondRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type ExtResponse = {
|
||||
id: string;
|
||||
result?: EmptyResponse | GetToolsResponse_unstable | GooseToolCallResponse_unstable | ReadResourceResponse_unstable | GetExtensionsResponse_unstable | GetSessionExtensionsResponse_unstable | ListProvidersResponse_unstable | ProviderSupportedModelsListResponse_unstable | ProviderCatalogListResponse_unstable | ProviderSetupCatalogListResponse_unstable | ProviderCatalogTemplateResponse_unstable | CustomProviderCreateResponse_unstable | CustomProviderReadResponse_unstable | CustomProviderUpdateResponse_unstable | CustomProviderDeleteResponse_unstable | RefreshProviderInventoryResponse_unstable | ProviderConfigReadResponse_unstable | ProviderConfigStatusResponse_unstable | ProviderConfigChangeResponse_unstable | PreferencesReadResponse_unstable | DefaultsReadResponse_unstable | OnboardingImportScanResponse_unstable | OnboardingImportApplyResponse_unstable | ExportSessionResponse_unstable | ImportSessionResponse_unstable | CreateSourceResponse_unstable | ListSourcesResponse_unstable | UpdateSourceResponse_unstable | ExportSourceResponse_unstable | ImportSourcesResponse_unstable | DictationTranscribeResponse_unstable | DictationConfigResponse_unstable | DictationModelsListResponse_unstable | DictationModelDownloadProgressResponse_unstable | unknown;
|
||||
result?: EmptyResponse | GetToolsResponse_unstable | GooseToolCallResponse_unstable | ReadResourceResponse_unstable | GetConfigExtensionsResponse_unstable | GetAvailableExtensionsResponse_unstable | GetSessionExtensionsResponse_unstable | ListProvidersResponse_unstable | ProviderSupportedModelsListResponse_unstable | ProviderCatalogListResponse_unstable | ProviderSetupCatalogListResponse_unstable | ProviderCatalogTemplateResponse_unstable | CustomProviderCreateResponse_unstable | CustomProviderReadResponse_unstable | CustomProviderUpdateResponse_unstable | CustomProviderDeleteResponse_unstable | RefreshProviderInventoryResponse_unstable | ProviderConfigReadResponse_unstable | ProviderConfigStatusResponse_unstable | ProviderConfigChangeResponse_unstable | PreferencesReadResponse_unstable | DefaultsReadResponse_unstable | OnboardingImportScanResponse_unstable | OnboardingImportApplyResponse_unstable | ExportSessionResponse_unstable | ImportSessionResponse_unstable | CreateSourceResponse_unstable | ListSourcesResponse_unstable | UpdateSourceResponse_unstable | ExportSourceResponse_unstable | ImportSourcesResponse_unstable | DictationTranscribeResponse_unstable | DictationConfigResponse_unstable | DictationModelsListResponse_unstable | DictationModelDownloadProgressResponse_unstable | unknown;
|
||||
} | {
|
||||
error: {
|
||||
code: number;
|
||||
|
||||
+170
-11
@@ -115,22 +115,179 @@ export const zDeleteSessionRequest = z.object({
|
||||
/**
|
||||
* List configured extensions and any warnings.
|
||||
*/
|
||||
export const zGetExtensionsRequest_unstable = z.record(z.unknown());
|
||||
export const zGetConfigExtensionsRequest_unstable = z.record(z.unknown());
|
||||
|
||||
/**
|
||||
* An HTTP header to set when making requests to the MCP server.
|
||||
*/
|
||||
export const zHttpHeader = z.object({
|
||||
name: z.string(),
|
||||
value: z.string(),
|
||||
_meta: z.union([
|
||||
z.record(z.unknown()),
|
||||
z.null()
|
||||
]).optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP transport configuration for MCP.
|
||||
*/
|
||||
export const zMcpServerHttp = z.object({
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
headers: z.array(zHttpHeader),
|
||||
_meta: z.union([
|
||||
z.record(z.unknown()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('http')
|
||||
});
|
||||
|
||||
/**
|
||||
* SSE transport configuration for MCP.
|
||||
*/
|
||||
export const zMcpServerSse = z.object({
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
headers: z.array(zHttpHeader),
|
||||
_meta: z.union([
|
||||
z.record(z.unknown()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('sse')
|
||||
});
|
||||
|
||||
/**
|
||||
* An environment variable to set when launching an MCP server.
|
||||
*/
|
||||
export const zEnvVariable = z.object({
|
||||
name: z.string(),
|
||||
value: z.string(),
|
||||
_meta: z.union([
|
||||
z.record(z.unknown()),
|
||||
z.null()
|
||||
]).optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* Stdio transport configuration for MCP.
|
||||
*/
|
||||
export const zMcpServerStdio = z.object({
|
||||
name: z.string(),
|
||||
command: z.string(),
|
||||
args: z.array(z.string()),
|
||||
env: z.array(zEnvVariable),
|
||||
_meta: z.union([
|
||||
z.record(z.unknown()),
|
||||
z.null()
|
||||
]).optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* Configuration for connecting to an MCP (Model Context Protocol) server.
|
||||
*
|
||||
* MCP servers provide tools and context that the agent can use when
|
||||
* processing prompts.
|
||||
*
|
||||
* See protocol docs: [MCP Servers](https://agentclientprotocol.com/protocol/session-setup#mcp-servers)
|
||||
*/
|
||||
export const zMcpServer = z.union([
|
||||
zMcpServerHttp,
|
||||
zMcpServerSse,
|
||||
zMcpServerStdio
|
||||
]);
|
||||
|
||||
export const zGooseExtension = z.union([
|
||||
z.object({
|
||||
name: z.string(),
|
||||
description: z.union([
|
||||
z.string(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
display_name: z.union([
|
||||
z.string(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
timeout: z.union([
|
||||
z.number().int().gte(0),
|
||||
z.null()
|
||||
]).optional(),
|
||||
bundled: z.union([
|
||||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('builtin')
|
||||
}),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
description: z.union([
|
||||
z.string(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
display_name: z.union([
|
||||
z.string(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
bundled: z.union([
|
||||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('platform')
|
||||
}),
|
||||
z.object({
|
||||
server: zMcpServer,
|
||||
envKeys: z.array(z.string()).optional(),
|
||||
description: z.union([
|
||||
z.string(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
timeout: z.union([
|
||||
z.number().int().gte(0),
|
||||
z.null()
|
||||
]).optional(),
|
||||
socket: z.union([
|
||||
z.string(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
bundled: z.union([
|
||||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('mcp')
|
||||
})
|
||||
]);
|
||||
|
||||
export const zGooseExtensionEntry = z.object({
|
||||
extension: zGooseExtension,
|
||||
enabled: z.boolean(),
|
||||
configKey: z.union([
|
||||
z.string(),
|
||||
z.null()
|
||||
]).optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* List configured extensions and any warnings.
|
||||
*/
|
||||
export const zGetExtensionsResponse_unstable = z.object({
|
||||
extensions: z.array(z.unknown()),
|
||||
warnings: z.array(z.string())
|
||||
export const zGetConfigExtensionsResponse_unstable = z.object({
|
||||
extensions: z.array(zGooseExtensionEntry),
|
||||
warnings: z.array(z.string()).optional().default([])
|
||||
});
|
||||
|
||||
/**
|
||||
* List Goose-owned extension definitions available to configure or enable.
|
||||
*/
|
||||
export const zGetAvailableExtensionsRequest_unstable = z.record(z.unknown());
|
||||
|
||||
export const zGetAvailableExtensionsResponse_unstable = z.object({
|
||||
extensions: z.array(zGooseExtension)
|
||||
});
|
||||
|
||||
/**
|
||||
* Persist a new extension to the user's global goose config.
|
||||
*/
|
||||
export const zAddConfigExtensionRequest_unstable = z.object({
|
||||
name: z.string(),
|
||||
extensionConfig: z.unknown().optional().default(null),
|
||||
extension: zGooseExtension,
|
||||
enabled: z.boolean().optional().default(false)
|
||||
});
|
||||
|
||||
@@ -142,9 +299,9 @@ export const zRemoveConfigExtensionRequest_unstable = z.object({
|
||||
});
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export const zToggleConfigExtensionRequest_unstable = z.object({
|
||||
export const zSetConfigExtensionEnabledRequest_unstable = z.object({
|
||||
configKey: z.string(),
|
||||
enabled: z.boolean()
|
||||
});
|
||||
@@ -1190,10 +1347,11 @@ export const zExtRequest = z.object({
|
||||
zUpdateWorkingDirRequest_unstable,
|
||||
zSetSessionSystemPromptRequest_unstable,
|
||||
zDeleteSessionRequest,
|
||||
zGetExtensionsRequest_unstable,
|
||||
zGetConfigExtensionsRequest_unstable,
|
||||
zGetAvailableExtensionsRequest_unstable,
|
||||
zAddConfigExtensionRequest_unstable,
|
||||
zRemoveConfigExtensionRequest_unstable,
|
||||
zToggleConfigExtensionRequest_unstable,
|
||||
zSetConfigExtensionEnabledRequest_unstable,
|
||||
zGetSessionExtensionsRequest_unstable,
|
||||
zListProvidersRequest_unstable,
|
||||
zProviderSupportedModelsListRequest_unstable,
|
||||
@@ -1257,7 +1415,8 @@ export const zExtResponse = z.union([
|
||||
zGetToolsResponse_unstable,
|
||||
zGooseToolCallResponse_unstable,
|
||||
zReadResourceResponse_unstable,
|
||||
zGetExtensionsResponse_unstable,
|
||||
zGetConfigExtensionsResponse_unstable,
|
||||
zGetAvailableExtensionsResponse_unstable,
|
||||
zGetSessionExtensionsResponse_unstable,
|
||||
zListProvidersResponse_unstable,
|
||||
zProviderSupportedModelsListResponse_unstable,
|
||||
|
||||
Reference in New Issue
Block a user