Move platform extensions into their own folder (#7210)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2026-02-13 21:32:00 +01:00
committed by GitHub
parent 58d3431bf5
commit a800ca7d7f
15 changed files with 189 additions and 199 deletions
+4 -162
View File
@@ -1,18 +1,9 @@
use crate::agents::apps_extension;
use crate::agents::chatrecall_extension;
use crate::agents::code_execution_extension;
use crate::agents::extension_manager_extension;
use crate::agents::summon_extension;
use crate::agents::todo_extension;
use crate::agents::tom_extension;
use std::collections::HashMap;
use crate::agents::mcp_client::McpClientTrait;
use crate::config;
use crate::config::extensions::name_to_key;
use crate::config::permission::PermissionLevel;
use crate::config::Config;
use once_cell::sync::Lazy;
use rmcp::model::Tool;
use rmcp::service::ClientInitializeError;
use rmcp::ServiceError as ClientError;
@@ -22,6 +13,10 @@ use thiserror::Error;
use tracing::warn;
use utoipa::ToSchema;
pub use crate::agents::platform_extensions::{
PlatformExtensionContext, PlatformExtensionDef, PLATFORM_EXTENSIONS,
};
#[derive(Error, Debug)]
#[error("process quit before initialization: stderr = {stderr}")]
pub struct ProcessExit {
@@ -42,159 +37,6 @@ impl ProcessExit {
}
}
pub static PLATFORM_EXTENSIONS: Lazy<HashMap<&'static str, PlatformExtensionDef>> = Lazy::new(
|| {
let mut map = HashMap::new();
map.insert(
todo_extension::EXTENSION_NAME,
PlatformExtensionDef {
name: todo_extension::EXTENSION_NAME,
display_name: "Todo",
description:
"Enable a todo list for goose so it can keep track of what it is doing",
default_enabled: true,
unprefixed_tools: false,
client_factory: |ctx| Box::new(todo_extension::TodoClient::new(ctx).unwrap()),
},
);
map.insert(
apps_extension::EXTENSION_NAME,
PlatformExtensionDef {
name: apps_extension::EXTENSION_NAME,
display_name: "Apps",
description:
"Create and manage custom Goose apps through chat. Apps are HTML/CSS/JavaScript and run in sandboxed windows.",
default_enabled: true,
unprefixed_tools: false,
client_factory: |ctx| Box::new(apps_extension::AppsManagerClient::new(ctx).unwrap()),
},
);
map.insert(
chatrecall_extension::EXTENSION_NAME,
PlatformExtensionDef {
name: chatrecall_extension::EXTENSION_NAME,
display_name: "Chat Recall",
description:
"Search past conversations and load session summaries for contextual memory",
default_enabled: false,
unprefixed_tools: false,
client_factory: |ctx| {
Box::new(chatrecall_extension::ChatRecallClient::new(ctx).unwrap())
},
},
);
map.insert(
"extensionmanager",
PlatformExtensionDef {
name: extension_manager_extension::EXTENSION_NAME,
display_name: "Extension Manager",
description:
"Enable extension management tools for discovering, enabling, and disabling extensions",
default_enabled: true,
unprefixed_tools: false,
client_factory: |ctx| Box::new(extension_manager_extension::ExtensionManagerClient::new(ctx).unwrap()),
},
);
map.insert(
summon_extension::EXTENSION_NAME,
PlatformExtensionDef {
name: summon_extension::EXTENSION_NAME,
display_name: "Summon",
description: "Load knowledge and delegate tasks to subagents",
default_enabled: true,
unprefixed_tools: true,
client_factory: |ctx| Box::new(summon_extension::SummonClient::new(ctx).unwrap()),
},
);
map.insert(
code_execution_extension::EXTENSION_NAME,
PlatformExtensionDef {
name: code_execution_extension::EXTENSION_NAME,
display_name: "Code Mode",
description:
"Goose will make extension calls through code execution, saving tokens",
default_enabled: false,
unprefixed_tools: true,
client_factory: |ctx| {
Box::new(code_execution_extension::CodeExecutionClient::new(ctx).unwrap())
},
},
);
map.insert(
tom_extension::EXTENSION_NAME,
PlatformExtensionDef {
name: tom_extension::EXTENSION_NAME,
display_name: "Top Of Mind",
description:
"Inject custom context into every turn via GOOSE_MOIM_MESSAGE_TEXT and GOOSE_MOIM_MESSAGE_FILE environment variables",
default_enabled: true,
unprefixed_tools: false,
client_factory: |ctx| Box::new(tom_extension::TomClient::new(ctx).unwrap()),
},
);
map
},
);
#[derive(Clone)]
pub struct PlatformExtensionContext {
pub extension_manager:
Option<std::sync::Weak<crate::agents::extension_manager::ExtensionManager>>,
pub session_manager: std::sync::Arc<crate::session::SessionManager>,
}
impl PlatformExtensionContext {
pub fn result_with_platform_notification(
&self,
mut result: rmcp::model::CallToolResult,
extension_name: impl Into<String>,
event_type: impl Into<String>,
mut additional_params: serde_json::Map<String, serde_json::Value>,
) -> rmcp::model::CallToolResult {
additional_params.insert("extension".to_string(), extension_name.into().into());
additional_params.insert("event_type".to_string(), event_type.into().into());
let meta_value = serde_json::json!({
"platform_notification": {
"method": "platform_event",
"params": additional_params
}
});
if let Some(ref mut meta) = result.meta {
if let Some(obj) = meta_value.as_object() {
for (k, v) in obj {
meta.0.insert(k.clone(), v.clone());
}
}
} else {
result.meta = Some(rmcp::model::Meta(meta_value.as_object().unwrap().clone()));
}
result
}
}
/// Definition for a platform extension that runs in-process with direct agent access.
#[derive(Debug, Clone)]
pub struct PlatformExtensionDef {
pub name: &'static str,
pub display_name: &'static str,
pub description: &'static str,
pub default_enabled: bool,
/// If true, tools are exposed without extension prefix for intuitive first-class use.
pub unprefixed_tools: bool,
pub client_factory: fn(PlatformExtensionContext) -> Box<dyn McpClientTrait>,
}
/// Errors from Extension operation
#[derive(Error, Debug)]
pub enum ExtensionError {