Move platform extensions into their own folder (#7210)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -15,8 +15,8 @@ use super::tool_execution::{ToolCallResult, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DEC
|
|||||||
use crate::action_required_manager::ActionRequiredManager;
|
use crate::action_required_manager::ActionRequiredManager;
|
||||||
use crate::agents::extension::{ExtensionConfig, ExtensionResult, ToolInfo};
|
use crate::agents::extension::{ExtensionConfig, ExtensionResult, ToolInfo};
|
||||||
use crate::agents::extension_manager::{get_parameter_names, ExtensionManager};
|
use crate::agents::extension_manager::{get_parameter_names, ExtensionManager};
|
||||||
use crate::agents::extension_manager_extension::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
|
|
||||||
use crate::agents::final_output_tool::{FINAL_OUTPUT_CONTINUATION_MESSAGE, FINAL_OUTPUT_TOOL_NAME};
|
use crate::agents::final_output_tool::{FINAL_OUTPUT_CONTINUATION_MESSAGE, FINAL_OUTPUT_TOOL_NAME};
|
||||||
|
use crate::agents::platform_extensions::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
|
||||||
use crate::agents::platform_tools::PLATFORM_MANAGE_SCHEDULE_TOOL_NAME;
|
use crate::agents::platform_tools::PLATFORM_MANAGE_SCHEDULE_TOOL_NAME;
|
||||||
use crate::agents::prompt_manager::PromptManager;
|
use crate::agents::prompt_manager::PromptManager;
|
||||||
use crate::agents::retry::{RetryManager, RetryResult};
|
use crate::agents::retry::{RetryManager, RetryResult};
|
||||||
|
|||||||
@@ -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 std::collections::HashMap;
|
||||||
|
|
||||||
use crate::agents::mcp_client::McpClientTrait;
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::config::extensions::name_to_key;
|
use crate::config::extensions::name_to_key;
|
||||||
use crate::config::permission::PermissionLevel;
|
use crate::config::permission::PermissionLevel;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use rmcp::model::Tool;
|
use rmcp::model::Tool;
|
||||||
use rmcp::service::ClientInitializeError;
|
use rmcp::service::ClientInitializeError;
|
||||||
use rmcp::ServiceError as ClientError;
|
use rmcp::ServiceError as ClientError;
|
||||||
@@ -22,6 +13,10 @@ use thiserror::Error;
|
|||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
pub use crate::agents::platform_extensions::{
|
||||||
|
PlatformExtensionContext, PlatformExtensionDef, PLATFORM_EXTENSIONS,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("process quit before initialization: stderr = {stderr}")]
|
#[error("process quit before initialization: stderr = {stderr}")]
|
||||||
pub struct ProcessExit {
|
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
|
/// Errors from Extension operation
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ExtensionError {
|
pub enum ExtensionError {
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
mod agent;
|
mod agent;
|
||||||
pub(crate) mod apps_extension;
|
|
||||||
pub(crate) mod builtin_skills;
|
pub(crate) mod builtin_skills;
|
||||||
pub(crate) mod chatrecall_extension;
|
|
||||||
pub(crate) mod code_execution_extension;
|
|
||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod execute_commands;
|
pub mod execute_commands;
|
||||||
pub mod extension;
|
pub mod extension;
|
||||||
pub mod extension_malware_check;
|
pub mod extension_malware_check;
|
||||||
pub mod extension_manager;
|
pub mod extension_manager;
|
||||||
pub mod extension_manager_extension;
|
|
||||||
pub mod final_output_tool;
|
pub mod final_output_tool;
|
||||||
mod large_response_handler;
|
mod large_response_handler;
|
||||||
pub mod mcp_client;
|
pub mod mcp_client;
|
||||||
pub mod moim;
|
pub mod moim;
|
||||||
|
pub mod platform_extensions;
|
||||||
pub mod platform_tools;
|
pub mod platform_tools;
|
||||||
pub mod prompt_manager;
|
pub mod prompt_manager;
|
||||||
mod reply_parts;
|
mod reply_parts;
|
||||||
@@ -21,9 +18,6 @@ mod schedule_tool;
|
|||||||
pub mod subagent_execution_tool;
|
pub mod subagent_execution_tool;
|
||||||
pub(crate) mod subagent_handler;
|
pub(crate) mod subagent_handler;
|
||||||
pub(crate) mod subagent_task_config;
|
pub(crate) mod subagent_task_config;
|
||||||
pub(crate) mod summon_extension;
|
|
||||||
pub(crate) mod todo_extension;
|
|
||||||
pub(crate) mod tom_extension;
|
|
||||||
mod tool_execution;
|
mod tool_execution;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -150,7 +150,7 @@ impl AppsManagerClient {
|
|||||||
|
|
||||||
fn ensure_default_apps(&self) -> Result<(), String> {
|
fn ensure_default_apps(&self) -> Result<(), String> {
|
||||||
// TODO(Douwe): we have the same check in cache, consider unifying that
|
// TODO(Douwe): we have the same check in cache, consider unifying that
|
||||||
const CLOCK_HTML: &str = include_str!("../goose_apps/clock.html");
|
const CLOCK_HTML: &str = include_str!("../../goose_apps/clock.html");
|
||||||
|
|
||||||
// Check if clock app exists
|
// Check if clock app exists
|
||||||
let clock_path = self.apps_dir.join("clock.html");
|
let clock_path = self.apps_dir.join("clock.html");
|
||||||
-5
@@ -13,7 +13,6 @@ use tokio_util::sync::CancellationToken;
|
|||||||
|
|
||||||
pub static EXTENSION_NAME: &str = "chatrecall";
|
pub static EXTENSION_NAME: &str = "chatrecall";
|
||||||
|
|
||||||
/// Parameters for the chatrecall tool
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
struct ChatRecallParams {
|
struct ChatRecallParams {
|
||||||
/// Search keywords. Use multiple related terms/synonyms (e.g., 'database postgres sql'). Mutually exclusive with session_id.
|
/// Search keywords. Use multiple related terms/synonyms (e.g., 'database postgres sql'). Mutually exclusive with session_id.
|
||||||
@@ -120,7 +119,6 @@ impl ChatRecallClient {
|
|||||||
total
|
total
|
||||||
);
|
);
|
||||||
|
|
||||||
// Show first 3 messages
|
|
||||||
let first_count = std::cmp::min(3, total);
|
let first_count = std::cmp::min(3, total);
|
||||||
output.push_str("--- First Few Messages ---\n\n");
|
output.push_str("--- First Few Messages ---\n\n");
|
||||||
for (idx, msg) in msgs.iter().take(first_count).enumerate() {
|
for (idx, msg) in msgs.iter().take(first_count).enumerate() {
|
||||||
@@ -134,7 +132,6 @@ impl ChatRecallClient {
|
|||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show last 3 messages (if different from first)
|
|
||||||
if total > first_count {
|
if total > first_count {
|
||||||
output.push_str("--- Last Few Messages ---\n\n");
|
output.push_str("--- Last Few Messages ---\n\n");
|
||||||
let last_count = std::cmp::min(3, total);
|
let last_count = std::cmp::min(3, total);
|
||||||
@@ -186,7 +183,6 @@ impl ChatRecallClient {
|
|||||||
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
|
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
|
||||||
.map(|dt| dt.with_timezone(&chrono::Utc));
|
.map(|dt| dt.with_timezone(&chrono::Utc));
|
||||||
|
|
||||||
// Exclude current session from results to avoid self-referential loops
|
|
||||||
let exclude_session_id = Some(current_session_id.to_string());
|
let exclude_session_id = Some(current_session_id.to_string());
|
||||||
|
|
||||||
match self
|
match self
|
||||||
@@ -248,7 +244,6 @@ impl ChatRecallClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_tools() -> Vec<Tool> {
|
fn get_tools() -> Vec<Tool> {
|
||||||
// Generate JSON schema from the ChatRecallParams struct
|
|
||||||
let schema = schema_for!(ChatRecallParams);
|
let schema = schema_for!(ChatRecallParams);
|
||||||
let schema_value =
|
let schema_value =
|
||||||
serde_json::to_value(schema).expect("Failed to serialize ChatRecallParams schema");
|
serde_json::to_value(schema).expect("Failed to serialize ChatRecallParams schema");
|
||||||
+6
-18
@@ -16,10 +16,8 @@ use serde_json::Value;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
pub static EXTENSION_NAME: &str = "Extension Manager";
|
pub static EXTENSION_NAME: &str = "Extension Manager";
|
||||||
// pub static DISPLAY_NAME: &str = "Extension Manager";
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ExtensionManagerToolError {
|
pub enum ExtensionManagerToolError {
|
||||||
@@ -32,9 +30,6 @@ pub enum ExtensionManagerToolError {
|
|||||||
#[error("Missing required parameter: {param_name}")]
|
#[error("Missing required parameter: {param_name}")]
|
||||||
MissingParameter { param_name: String },
|
MissingParameter { param_name: String },
|
||||||
|
|
||||||
#[error("Invalid action: {action}. Must be 'enable' or 'disable'")]
|
|
||||||
InvalidAction { action: String },
|
|
||||||
|
|
||||||
#[error("Extension operation failed: {message}")]
|
#[error("Extension operation failed: {message}")]
|
||||||
OperationFailed { message: String },
|
OperationFailed { message: String },
|
||||||
|
|
||||||
@@ -336,7 +331,6 @@ impl ExtensionManagerClient {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Only add resource tools if extension manager supports resources
|
|
||||||
if let Some(weak_ref) = &self.context.extension_manager {
|
if let Some(weak_ref) = &self.context.extension_manager {
|
||||||
if let Some(extension_manager) = weak_ref.upgrade() {
|
if let Some(extension_manager) = weak_ref.upgrade() {
|
||||||
if extension_manager.supports_resources().await {
|
if extension_manager.supports_resources().await {
|
||||||
@@ -454,18 +448,12 @@ impl McpClientTrait for ExtensionManagerClient {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(content) => Ok(CallToolResult::success(content)),
|
Ok(content) => Ok(CallToolResult::success(content)),
|
||||||
Err(error) => {
|
Err(error) => Ok(CallToolResult {
|
||||||
// Log the error for debugging
|
content: vec![Content::text(error.to_string())],
|
||||||
error!("Extension manager tool '{}' failed: {}", name, error);
|
is_error: Some(true),
|
||||||
|
structured_content: None,
|
||||||
// Return proper error result with is_error flag set
|
meta: None,
|
||||||
Ok(CallToolResult {
|
}),
|
||||||
content: vec![Content::text(error.to_string())],
|
|
||||||
is_error: Some(true), // ✅ Properly mark as error
|
|
||||||
structured_content: None,
|
|
||||||
meta: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
pub mod apps;
|
||||||
|
pub mod chatrecall;
|
||||||
|
pub mod code_execution;
|
||||||
|
pub mod ext_manager;
|
||||||
|
pub mod summon;
|
||||||
|
pub mod todo;
|
||||||
|
pub mod tom;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::agents::mcp_client::McpClientTrait;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
pub use ext_manager::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
|
||||||
|
|
||||||
|
// These are used by integration tests in crates/goose/tests/
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use ext_manager::MANAGE_EXTENSIONS_TOOL_NAME;
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use ext_manager::SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME;
|
||||||
|
|
||||||
|
pub static PLATFORM_EXTENSIONS: Lazy<HashMap<&'static str, PlatformExtensionDef>> = Lazy::new(
|
||||||
|
|| {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
todo::EXTENSION_NAME,
|
||||||
|
PlatformExtensionDef {
|
||||||
|
name: todo::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::TodoClient::new(ctx).unwrap()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
apps::EXTENSION_NAME,
|
||||||
|
PlatformExtensionDef {
|
||||||
|
name: apps::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::AppsManagerClient::new(ctx).unwrap()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
chatrecall::EXTENSION_NAME,
|
||||||
|
PlatformExtensionDef {
|
||||||
|
name: chatrecall::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::ChatRecallClient::new(ctx).unwrap()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"extensionmanager",
|
||||||
|
PlatformExtensionDef {
|
||||||
|
name: ext_manager::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(ext_manager::ExtensionManagerClient::new(ctx).unwrap()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
summon::EXTENSION_NAME,
|
||||||
|
PlatformExtensionDef {
|
||||||
|
name: summon::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::SummonClient::new(ctx).unwrap()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
code_execution::EXTENSION_NAME,
|
||||||
|
PlatformExtensionDef {
|
||||||
|
name: code_execution::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::CodeExecutionClient::new(ctx).unwrap())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
tom::EXTENSION_NAME,
|
||||||
|
PlatformExtensionDef {
|
||||||
|
name: tom::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::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>,
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ use serde_json::{json, Value};
|
|||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use super::super::agents::Agent;
|
use super::super::agents::Agent;
|
||||||
use crate::agents::code_execution_extension::EXTENSION_NAME as CODE_EXECUTION_EXTENSION;
|
use crate::agents::platform_extensions::code_execution;
|
||||||
use crate::conversation::message::{Message, MessageContent, ToolRequest};
|
use crate::conversation::message::{Message, MessageContent, ToolRequest};
|
||||||
use crate::conversation::Conversation;
|
use crate::conversation::Conversation;
|
||||||
use crate::providers::base::{stream_from_single_message, MessageStream, Provider, ProviderUsage};
|
use crate::providers::base::{stream_from_single_message, MessageStream, Provider, ProviderUsage};
|
||||||
@@ -146,7 +146,7 @@ impl Agent {
|
|||||||
|
|
||||||
let code_execution_active = self
|
let code_execution_active = self
|
||||||
.extension_manager
|
.extension_manager
|
||||||
.is_extension_enabled(CODE_EXECUTION_EXTENSION)
|
.is_extension_enabled(code_execution::EXTENSION_NAME)
|
||||||
.await;
|
.await;
|
||||||
if code_execution_active {
|
if code_execution_active {
|
||||||
tools.retain(|tool| {
|
tools.retain(|tool| {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::agents::extension_manager_extension::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
|
use crate::agents::platform_extensions::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
|
||||||
use crate::config::permission::PermissionLevel;
|
use crate::config::permission::PermissionLevel;
|
||||||
use crate::config::{GooseMode, PermissionManager};
|
use crate::config::{GooseMode, PermissionManager};
|
||||||
use crate::conversation::message::{Message, ToolRequest};
|
use crate::conversation::message::{Message, ToolRequest};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::agents::extension_manager_extension::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
|
use crate::agents::platform_extensions::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
|
||||||
use crate::config::permission::PermissionLevel;
|
use crate::config::permission::PermissionLevel;
|
||||||
use crate::config::PermissionManager;
|
use crate::config::PermissionManager;
|
||||||
use crate::conversation::message::{Message, MessageContent, ToolRequest};
|
use crate::conversation::message::{Message, MessageContent, ToolRequest};
|
||||||
|
|||||||
@@ -510,7 +510,7 @@ mod tests {
|
|||||||
mod extension_manager_tests {
|
mod extension_manager_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use goose::agents::extension::ExtensionConfig;
|
use goose::agents::extension::ExtensionConfig;
|
||||||
use goose::agents::extension_manager_extension::{
|
use goose::agents::platform_extensions::{
|
||||||
MANAGE_EXTENSIONS_TOOL_NAME, SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
MANAGE_EXTENSIONS_TOOL_NAME, SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
||||||
};
|
};
|
||||||
use goose::agents::AgentConfig;
|
use goose::agents::AgentConfig;
|
||||||
|
|||||||
Reference in New Issue
Block a user