commands to acp+ migration: extensions management (#8733)

This commit is contained in:
Alex Hancock
2026-04-22 15:23:02 -04:00
committed by GitHub
parent ee46794b18
commit e7e50320a1
13 changed files with 303 additions and 204 deletions
+33 -1
View File
@@ -99,11 +99,43 @@ pub struct GetExtensionsRequest {}
/// List configured extensions and any warnings.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
pub struct GetExtensionsResponse {
/// Array of ExtensionEntry objects with `enabled` flag and config details.
/// Array of ExtensionEntry objects with `enabled` flag, `configKey`, and flattened config details.
pub extensions: Vec<serde_json::Value>,
pub warnings: Vec<String>,
}
/// Persist a new extension to the user's global goose config.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/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,
#[serde(default)]
pub enabled: bool,
}
/// Remove a persisted extension from the user's global goose config.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/config/extensions/remove", response = EmptyResponse)]
#[serde(rename_all = "camelCase")]
pub struct RemoveConfigExtensionRequest {
pub config_key: String,
}
/// Toggle 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/config/extensions/toggle", response = EmptyResponse)]
#[serde(rename_all = "camelCase")]
pub struct ToggleConfigExtensionRequest {
pub config_key: String,
pub enabled: bool,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/session/extensions", response = GetSessionExtensionsResponse)]
#[serde(rename_all = "camelCase")]
+15
View File
@@ -35,6 +35,21 @@
"requestType": "GetExtensionsRequest",
"responseType": "GetExtensionsResponse"
},
{
"method": "_goose/config/extensions/add",
"requestType": "AddConfigExtensionRequest",
"responseType": "EmptyResponse"
},
{
"method": "_goose/config/extensions/remove",
"requestType": "RemoveConfigExtensionRequest",
"responseType": "EmptyResponse"
},
{
"method": "_goose/config/extensions/toggle",
"requestType": "ToggleConfigExtensionRequest",
"responseType": "EmptyResponse"
},
{
"method": "_goose/session/extensions",
"requestType": "GetSessionExtensionsRequest",
+82 -1
View File
@@ -151,7 +151,7 @@
"extensions": {
"type": "array",
"items": {},
"description": "Array of ExtensionEntry objects with `enabled` flag and config details."
"description": "Array of ExtensionEntry objects with `enabled` flag, `configKey`, and flattened config details."
},
"warnings": {
"type": "array",
@@ -168,6 +168,60 @@
"x-side": "agent",
"x-method": "_goose/config/extensions"
},
"AddConfigExtensionRequest": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"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
},
"enabled": {
"type": "boolean",
"default": false
}
},
"required": [
"name"
],
"description": "Persist a new extension to the user's global goose config.",
"x-side": "agent",
"x-method": "_goose/config/extensions/add"
},
"RemoveConfigExtensionRequest": {
"type": "object",
"properties": {
"configKey": {
"type": "string"
}
},
"required": [
"configKey"
],
"description": "Remove a persisted extension from the user's global goose config.",
"x-side": "agent",
"x-method": "_goose/config/extensions/remove"
},
"ToggleConfigExtensionRequest": {
"type": "object",
"properties": {
"configKey": {
"type": "string"
},
"enabled": {
"type": "boolean"
}
},
"required": [
"configKey",
"enabled"
],
"description": "Toggle the `enabled` flag for a persisted extension in the user's global goose config.",
"x-side": "agent",
"x-method": "_goose/config/extensions/toggle"
},
"GetSessionExtensionsRequest": {
"type": "object",
"properties": {
@@ -1408,6 +1462,33 @@
"description": "Params for _goose/config/extensions",
"title": "GetExtensionsRequest"
},
{
"allOf": [
{
"$ref": "#/$defs/AddConfigExtensionRequest"
}
],
"description": "Params for _goose/config/extensions/add",
"title": "AddConfigExtensionRequest"
},
{
"allOf": [
{
"$ref": "#/$defs/RemoveConfigExtensionRequest"
}
],
"description": "Params for _goose/config/extensions/remove",
"title": "RemoveConfigExtensionRequest"
},
{
"allOf": [
{
"$ref": "#/$defs/ToggleConfigExtensionRequest"
}
],
"description": "Params for _goose/config/extensions/toggle",
"title": "ToggleConfigExtensionRequest"
},
{
"allOf": [
{
+68 -1
View File
@@ -2870,7 +2870,17 @@ impl GooseAcpAgent {
let warnings = crate::config::extensions::get_warnings();
let extensions_json = extensions
.into_iter()
.map(|e| serde_json::to_value(&e))
.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<_>, _>>()
.map_err(|e| sacp::Error::internal_error().data(e.to_string()))?;
Ok(GetExtensionsResponse {
@@ -2879,6 +2889,63 @@ impl GooseAcpAgent {
})
}
#[custom_method(AddConfigExtensionRequest)]
async fn on_add_config_extension(
&self,
req: AddConfigExtensionRequest,
) -> Result<EmptyResponse, sacp::Error> {
let mut obj = match req.extension_config {
serde_json::Value::Object(obj) => obj,
_ => {
return Err(
sacp::Error::invalid_params().data("extensionConfig must be a JSON object")
);
}
};
obj.insert(
"name".to_string(),
serde_json::Value::String(req.name.clone()),
);
let config: crate::agents::ExtensionConfig =
serde_json::from_value(serde_json::Value::Object(obj))
.map_err(|e| sacp::Error::invalid_params().data(format!("bad config: {e}")))?;
crate::config::extensions::set_extension(crate::config::extensions::ExtensionEntry {
enabled: req.enabled,
config,
});
Ok(EmptyResponse {})
}
#[custom_method(RemoveConfigExtensionRequest)]
async fn on_remove_config_extension(
&self,
req: RemoveConfigExtensionRequest,
) -> Result<EmptyResponse, sacp::Error> {
let keys = crate::config::extensions::get_all_extension_names();
if !keys.iter().any(|k| k == &req.config_key) {
return Err(sacp::Error::invalid_params()
.data(format!("Extension '{}' not found", req.config_key)));
}
crate::config::extensions::remove_extension(&req.config_key);
Ok(EmptyResponse {})
}
#[custom_method(ToggleConfigExtensionRequest)]
async fn on_toggle_config_extension(
&self,
req: ToggleConfigExtensionRequest,
) -> Result<EmptyResponse, sacp::Error> {
let keys = crate::config::extensions::get_all_extension_names();
if !keys.iter().any(|k| k == &req.config_key) {
return Err(sacp::Error::invalid_params()
.data(format!("Extension '{}' not found", req.config_key)));
}
crate::config::extensions::set_extension_enabled(&req.config_key, req.enabled);
Ok(EmptyResponse {})
}
#[custom_method(GetSessionExtensionsRequest)]
async fn on_get_session_extensions(
&self,