Revert "Rewrite extension management tools" (#5243)
This commit is contained in:
@@ -10,9 +10,12 @@ use uuid::Uuid;
|
||||
|
||||
use crate::agents::extension::{ExtensionConfig, ExtensionError, ExtensionResult, ToolInfo};
|
||||
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::platform_tools::PLATFORM_MANAGE_SCHEDULE_TOOL_NAME;
|
||||
use crate::agents::platform_tools::{
|
||||
PLATFORM_LIST_RESOURCES_TOOL_NAME, PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME,
|
||||
PLATFORM_MANAGE_SCHEDULE_TOOL_NAME, PLATFORM_READ_RESOURCE_TOOL_NAME,
|
||||
PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
||||
};
|
||||
use crate::agents::prompt_manager::PromptManager;
|
||||
use crate::agents::recipe_tools::dynamic_task_tools::{
|
||||
create_dynamic_task, create_dynamic_task_tool, DYNAMIC_TASK_TOOL_NAME_PREFIX,
|
||||
@@ -29,7 +32,7 @@ use crate::agents::tool_route_manager::ToolRouteManager;
|
||||
use crate::agents::tool_router_index_manager::ToolRouterIndexManager;
|
||||
use crate::agents::types::SessionConfig;
|
||||
use crate::agents::types::{FrontendTool, ToolResultReceiver};
|
||||
use crate::config::{get_enabled_extensions, Config};
|
||||
use crate::config::{get_enabled_extensions, get_extension_by_name, Config};
|
||||
use crate::context_mgmt::DEFAULT_COMPACTION_THRESHOLD;
|
||||
use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation};
|
||||
use crate::mcp_utils::ToolResult;
|
||||
@@ -85,7 +88,7 @@ pub struct ToolCategorizeResult {
|
||||
/// The main goose Agent
|
||||
pub struct Agent {
|
||||
pub(super) provider: Mutex<Option<Arc<dyn Provider>>>,
|
||||
pub extension_manager: Arc<ExtensionManager>,
|
||||
pub extension_manager: ExtensionManager,
|
||||
pub(super) sub_recipe_manager: Mutex<SubRecipeManager>,
|
||||
pub(super) tasks_manager: TasksManager,
|
||||
pub(super) final_output_tool: Arc<Mutex<Option<FinalOutputTool>>>,
|
||||
@@ -97,7 +100,7 @@ pub struct Agent {
|
||||
pub(super) tool_result_tx: mpsc::Sender<(String, ToolResult<Vec<Content>>)>,
|
||||
pub(super) tool_result_rx: ToolResultReceiver,
|
||||
|
||||
pub tool_route_manager: Arc<ToolRouteManager>,
|
||||
pub(super) tool_route_manager: ToolRouteManager,
|
||||
pub(super) scheduler_service: Mutex<Option<Arc<dyn SchedulerTrait>>>,
|
||||
pub(super) retry_manager: RetryManager,
|
||||
pub(super) tool_inspection_manager: ToolInspectionManager,
|
||||
@@ -160,7 +163,7 @@ impl Agent {
|
||||
|
||||
Self {
|
||||
provider: Mutex::new(None),
|
||||
extension_manager: Arc::new(ExtensionManager::new()),
|
||||
extension_manager: ExtensionManager::new(),
|
||||
sub_recipe_manager: Mutex::new(SubRecipeManager::new()),
|
||||
tasks_manager: TasksManager::new(),
|
||||
final_output_tool: Arc::new(Mutex::new(None)),
|
||||
@@ -171,7 +174,7 @@ impl Agent {
|
||||
confirmation_rx: Mutex::new(confirm_rx),
|
||||
tool_result_tx: tool_tx,
|
||||
tool_result_rx: Arc::new(Mutex::new(tool_rx)),
|
||||
tool_route_manager: Arc::new(ToolRouteManager::new()),
|
||||
tool_route_manager: ToolRouteManager::new(),
|
||||
scheduler_service: Mutex::new(None),
|
||||
retry_manager: RetryManager::new(),
|
||||
tool_inspection_manager: Self::create_default_tool_inspection_manager(),
|
||||
@@ -401,6 +404,28 @@ impl Agent {
|
||||
return (request_id, Ok(ToolCallResult::from(result)));
|
||||
}
|
||||
|
||||
if tool_call.name == PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME {
|
||||
let extension_name = tool_call
|
||||
.arguments
|
||||
.as_ref()
|
||||
.and_then(|args| args.get("extension_name"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let action = tool_call
|
||||
.arguments
|
||||
.as_ref()
|
||||
.and_then(|args| args.get("action"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let (request_id, result) = self
|
||||
.manage_extensions(action, extension_name, request_id)
|
||||
.await;
|
||||
|
||||
return (request_id, Ok(ToolCallResult::from(result)));
|
||||
}
|
||||
|
||||
if tool_call.name == FINAL_OUTPUT_TOOL_NAME {
|
||||
return if let Some(final_output_tool) = self.final_output_tool.lock().await.as_mut() {
|
||||
let result = final_output_tool.execute_tool_call(tool_call.clone()).await;
|
||||
@@ -463,12 +488,12 @@ impl Agent {
|
||||
let parent_session_id = session.id.to_string();
|
||||
let parent_working_dir = session.working_dir.clone();
|
||||
|
||||
// Get extensions from the agent's runtime state rather than global config
|
||||
// This ensures subagents inherit extensions that were dynamically enabled by the parent
|
||||
let extensions = self.get_extension_configs().await;
|
||||
|
||||
let task_config =
|
||||
TaskConfig::new(provider, parent_session_id, parent_working_dir, extensions);
|
||||
let task_config = TaskConfig::new(
|
||||
provider,
|
||||
parent_session_id,
|
||||
parent_working_dir,
|
||||
get_enabled_extensions(),
|
||||
);
|
||||
|
||||
let arguments = match tool_call.arguments.clone() {
|
||||
Some(args) => Value::Object(args),
|
||||
@@ -535,6 +560,31 @@ impl Agent {
|
||||
.map(Value::Object)
|
||||
.unwrap_or(Value::Object(serde_json::Map::new()));
|
||||
create_dynamic_task(arguments, &self.tasks_manager, loaded_extensions).await
|
||||
} else if tool_call.name == PLATFORM_READ_RESOURCE_TOOL_NAME {
|
||||
// Check if the tool is read_resource and handle it separately
|
||||
let arguments = tool_call
|
||||
.arguments
|
||||
.clone()
|
||||
.map(Value::Object)
|
||||
.unwrap_or(Value::Object(serde_json::Map::new()));
|
||||
ToolCallResult::from(
|
||||
self.extension_manager
|
||||
.read_resource(arguments, cancellation_token.unwrap_or_default())
|
||||
.await,
|
||||
)
|
||||
} else if tool_call.name == PLATFORM_LIST_RESOURCES_TOOL_NAME {
|
||||
let arguments = tool_call
|
||||
.arguments
|
||||
.clone()
|
||||
.map(Value::Object)
|
||||
.unwrap_or(Value::Object(serde_json::Map::new()));
|
||||
ToolCallResult::from(
|
||||
self.extension_manager
|
||||
.list_resources(arguments, cancellation_token.unwrap_or_default())
|
||||
.await,
|
||||
)
|
||||
} else if tool_call.name == PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME {
|
||||
ToolCallResult::from(self.extension_manager.search_available_extensions().await)
|
||||
} else if self.is_frontend_tool(&tool_call.name).await {
|
||||
// For frontend tools, return an error indicating we need frontend execution
|
||||
ToolCallResult::from(Err(ErrorData::new(
|
||||
@@ -603,6 +653,109 @@ impl Agent {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(super) async fn manage_extensions(
|
||||
&self,
|
||||
action: String,
|
||||
extension_name: String,
|
||||
request_id: String,
|
||||
) -> (String, Result<Vec<Content>, ErrorData>) {
|
||||
if self.tool_route_manager.is_router_functional().await {
|
||||
let selector = self.tool_route_manager.get_router_tool_selector().await;
|
||||
if let Some(selector) = selector {
|
||||
let selector_action = if action == "disable" { "remove" } else { "add" };
|
||||
let selector = Arc::new(selector);
|
||||
if let Err(e) = ToolRouterIndexManager::update_extension_tools(
|
||||
&selector,
|
||||
&self.extension_manager,
|
||||
&extension_name,
|
||||
selector_action,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return (
|
||||
request_id,
|
||||
Err(ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!("Failed to update LLM index: {}", e),
|
||||
None,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if action == "disable" {
|
||||
let result = self
|
||||
.extension_manager
|
||||
.remove_extension(&extension_name)
|
||||
.await
|
||||
.map(|_| {
|
||||
vec![Content::text(format!(
|
||||
"The extension '{}' has been disabled successfully",
|
||||
extension_name
|
||||
))]
|
||||
})
|
||||
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None));
|
||||
return (request_id, result);
|
||||
}
|
||||
|
||||
let config = match get_extension_by_name(&extension_name) {
|
||||
Some(config) => config,
|
||||
None => {
|
||||
return (
|
||||
request_id,
|
||||
Err(ErrorData::new(
|
||||
ErrorCode::RESOURCE_NOT_FOUND,
|
||||
format!(
|
||||
"Extension '{}' not found. Please check the extension name and try again.",
|
||||
extension_name
|
||||
),
|
||||
None,
|
||||
)),
|
||||
)
|
||||
}
|
||||
};
|
||||
let result = self
|
||||
.extension_manager
|
||||
.add_extension(config)
|
||||
.await
|
||||
.map(|_| {
|
||||
vec![Content::text(format!(
|
||||
"The extension '{}' has been installed successfully",
|
||||
extension_name
|
||||
))]
|
||||
})
|
||||
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None));
|
||||
|
||||
// Update LLM index if operation was successful and LLM routing is functional
|
||||
if result.is_ok() && self.tool_route_manager.is_router_functional().await {
|
||||
let selector = self.tool_route_manager.get_router_tool_selector().await;
|
||||
if let Some(selector) = selector {
|
||||
let llm_action = if action == "disable" { "remove" } else { "add" };
|
||||
let selector = Arc::new(selector);
|
||||
if let Err(e) = ToolRouterIndexManager::update_extension_tools(
|
||||
&selector,
|
||||
&self.extension_manager,
|
||||
&extension_name,
|
||||
llm_action,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return (
|
||||
request_id,
|
||||
Err(ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!("Failed to update LLM index: {}", e),
|
||||
None,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(request_id, result)
|
||||
}
|
||||
|
||||
pub async fn add_extension(&self, extension: ExtensionConfig) -> ExtensionResult<()> {
|
||||
match &extension {
|
||||
ExtensionConfig::Frontend {
|
||||
@@ -671,10 +824,21 @@ impl Agent {
|
||||
|
||||
if extension_name.is_none() || extension_name.as_deref() == Some("platform") {
|
||||
// Add platform tools
|
||||
// TODO: migrate the manage schedule tool as well
|
||||
prefixed_tools.extend([platform_tools::manage_schedule_tool()]);
|
||||
prefixed_tools.extend([
|
||||
platform_tools::search_available_extensions_tool(),
|
||||
platform_tools::manage_extensions_tool(),
|
||||
platform_tools::manage_schedule_tool(),
|
||||
]);
|
||||
// Dynamic task tool
|
||||
prefixed_tools.push(create_dynamic_task_tool());
|
||||
|
||||
// Add resource tools if supported
|
||||
if self.extension_manager.supports_resources().await {
|
||||
prefixed_tools.extend([
|
||||
platform_tools::read_resource_tool(),
|
||||
platform_tools::list_resources_tool(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if extension_name.is_none() {
|
||||
@@ -1051,7 +1215,7 @@ impl Agent {
|
||||
let mut enable_extension_request_ids = vec![];
|
||||
for request in &remaining_requests {
|
||||
if let Ok(tool_call) = &request.tool_call {
|
||||
if tool_call.name == MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE {
|
||||
if tool_call.name == PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME {
|
||||
enable_extension_request_ids.push(request.id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::agents::extension_manager_extension;
|
||||
use crate::agents::todo_extension;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -35,8 +34,8 @@ impl ProcessExit {
|
||||
}
|
||||
}
|
||||
|
||||
pub static PLATFORM_EXTENSIONS: Lazy<HashMap<&'static str, PlatformExtensionDef>> = Lazy::new(
|
||||
|| {
|
||||
pub static PLATFORM_EXTENSIONS: Lazy<HashMap<&'static str, PlatformExtensionDef>> =
|
||||
Lazy::new(|| {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
map.insert(
|
||||
@@ -50,28 +49,12 @@ pub static PLATFORM_EXTENSIONS: Lazy<HashMap<&'static str, PlatformExtensionDef>
|
||||
},
|
||||
);
|
||||
|
||||
map.insert(
|
||||
"extensionmanager",
|
||||
PlatformExtensionDef {
|
||||
name: extension_manager_extension::EXTENSION_NAME,
|
||||
description:
|
||||
"Enable extension management tools for discovering, enabling, and disabling extensions",
|
||||
default_enabled: true,
|
||||
client_factory: |ctx| Box::new(extension_manager_extension::ExtensionManagerClient::new(ctx).unwrap()),
|
||||
},
|
||||
);
|
||||
|
||||
map
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlatformExtensionContext {
|
||||
pub session_id: Option<String>,
|
||||
pub extension_manager:
|
||||
Option<std::sync::Weak<crate::agents::extension_manager::ExtensionManager>>,
|
||||
pub tool_route_manager:
|
||||
Option<std::sync::Weak<crate::agents::tool_route_manager::ToolRouteManager>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -238,11 +238,7 @@ impl ExtensionManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
extensions: Mutex::new(HashMap::new()),
|
||||
context: Mutex::new(PlatformExtensionContext {
|
||||
session_id: None,
|
||||
extension_manager: None,
|
||||
tool_route_manager: None,
|
||||
}),
|
||||
context: Mutex::new(PlatformExtensionContext { session_id: None }),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,546 +0,0 @@
|
||||
use crate::agents::extension::PlatformExtensionContext;
|
||||
use crate::agents::mcp_client::{Error, McpClientTrait};
|
||||
use crate::agents::tool_router_index_manager::ToolRouterIndexManager;
|
||||
use crate::config::get_extension_by_name;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use indoc::indoc;
|
||||
use rmcp::model::{
|
||||
CallToolResult, Content, ErrorCode, ErrorData, GetPromptResult, Implementation,
|
||||
InitializeResult, JsonObject, ListPromptsResult, ListResourcesResult, ListToolsResult,
|
||||
ProtocolVersion, ReadResourceResult, ServerCapabilities, ServerNotification, Tool,
|
||||
ToolAnnotations, ToolsCapability,
|
||||
};
|
||||
use schemars::{schema_for, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::error;
|
||||
|
||||
pub static EXTENSION_NAME: &str = "Extension Manager";
|
||||
// pub static DISPLAY_NAME: &str = "Extension Manager";
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ExtensionManagerToolError {
|
||||
#[error("Unknown tool: {tool_name}")]
|
||||
UnknownTool { tool_name: String },
|
||||
|
||||
#[error("Extension manager not available")]
|
||||
ManagerUnavailable,
|
||||
|
||||
#[error("Missing required parameter: {param_name}")]
|
||||
MissingParameter { param_name: String },
|
||||
|
||||
#[error("Invalid action: {action}. Must be 'enable' or 'disable'")]
|
||||
InvalidAction { action: String },
|
||||
|
||||
#[error("Extension operation failed: {message}")]
|
||||
OperationFailed { message: String },
|
||||
|
||||
#[error("Failed to deserialize parameters: {0}")]
|
||||
DeserializationError(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ManageExtensionAction {
|
||||
Enable,
|
||||
Disable,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ManageExtensionsParams {
|
||||
pub action: ManageExtensionAction,
|
||||
pub extension_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ReadResourceParams {
|
||||
pub uri: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extension_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ListResourcesParams {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extension_name: Option<String>,
|
||||
}
|
||||
|
||||
pub const READ_RESOURCE_TOOL_NAME: &str = "read_resource";
|
||||
pub const LIST_RESOURCES_TOOL_NAME: &str = "list_resources";
|
||||
pub const SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME: &str = "search_available_extensions";
|
||||
pub const MANAGE_EXTENSIONS_TOOL_NAME: &str = "manage_extensions";
|
||||
pub const MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE: &str = "extensionmanager__manage_extensions";
|
||||
|
||||
pub struct ExtensionManagerClient {
|
||||
info: InitializeResult,
|
||||
#[allow(dead_code)]
|
||||
context: PlatformExtensionContext,
|
||||
}
|
||||
|
||||
impl ExtensionManagerClient {
|
||||
pub fn new(context: PlatformExtensionContext) -> Result<Self> {
|
||||
let info = InitializeResult {
|
||||
protocol_version: ProtocolVersion::V_2025_03_26,
|
||||
capabilities: ServerCapabilities {
|
||||
tools: Some(ToolsCapability {
|
||||
list_changed: Some(false),
|
||||
}),
|
||||
resources: None,
|
||||
prompts: None,
|
||||
completions: None,
|
||||
experimental: None,
|
||||
logging: None,
|
||||
},
|
||||
server_info: Implementation {
|
||||
name: EXTENSION_NAME.to_string(),
|
||||
title: Some(EXTENSION_NAME.to_string()),
|
||||
version: "1.0.0".to_string(),
|
||||
icons: None,
|
||||
website_url: None,
|
||||
},
|
||||
instructions: Some(indoc! {r#"
|
||||
Extension Management
|
||||
|
||||
Use these tools to discover, enable, and disable extensions, as well as review resources.
|
||||
|
||||
Available tools:
|
||||
- search_available_extensions: Find extensions available to enable/disable
|
||||
- manage_extensions: Enable or disable extensions
|
||||
- list_resources: List resources from extensions
|
||||
- read_resource: Read specific resources from extensions
|
||||
|
||||
Use search_available_extensions when you need to find what extensions are available.
|
||||
Use manage_extensions to enable or disable specific extensions by name.
|
||||
Use list_resources and read_resource to work with extension data and resources.
|
||||
"#}.to_string()),
|
||||
};
|
||||
|
||||
Ok(Self { info, context })
|
||||
}
|
||||
|
||||
async fn handle_search_available_extensions(
|
||||
&self,
|
||||
) -> Result<Vec<Content>, ExtensionManagerToolError> {
|
||||
if let Some(weak_ref) = &self.context.extension_manager {
|
||||
if let Some(extension_manager) = weak_ref.upgrade() {
|
||||
match extension_manager.search_available_extensions().await {
|
||||
Ok(content) => Ok(content),
|
||||
Err(e) => Err(ExtensionManagerToolError::OperationFailed {
|
||||
message: format!("Failed to search available extensions: {}", e.message),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ExtensionManagerToolError::ManagerUnavailable)
|
||||
}
|
||||
} else {
|
||||
Err(ExtensionManagerToolError::ManagerUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_manage_extensions(
|
||||
&self,
|
||||
arguments: Option<JsonObject>,
|
||||
) -> Result<Vec<Content>, ExtensionManagerToolError> {
|
||||
let arguments = arguments.ok_or(ExtensionManagerToolError::MissingParameter {
|
||||
param_name: "arguments".to_string(),
|
||||
})?;
|
||||
|
||||
let params: ManageExtensionsParams =
|
||||
serde_json::from_value(serde_json::Value::Object(arguments))?;
|
||||
|
||||
match self
|
||||
.manage_extensions_impl(params.action, params.extension_name)
|
||||
.await
|
||||
{
|
||||
Ok(content) => Ok(content),
|
||||
Err(error_data) => Err(ExtensionManagerToolError::OperationFailed {
|
||||
message: error_data.message.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn manage_extensions_impl(
|
||||
&self,
|
||||
action: ManageExtensionAction,
|
||||
extension_name: String,
|
||||
) -> Result<Vec<Content>, ErrorData> {
|
||||
let extension_manager = self
|
||||
.context
|
||||
.extension_manager
|
||||
.as_ref()
|
||||
.and_then(|weak| weak.upgrade())
|
||||
.ok_or_else(|| {
|
||||
ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
"Extension manager is no longer available".to_string(),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
|
||||
let tool_route_manager = self
|
||||
.context
|
||||
.tool_route_manager
|
||||
.as_ref()
|
||||
.and_then(|weak| weak.upgrade());
|
||||
|
||||
// Update tool router index if router is functional
|
||||
if let Some(tool_route_manager) = &tool_route_manager {
|
||||
if tool_route_manager.is_router_functional().await {
|
||||
let selector = tool_route_manager.get_router_tool_selector().await;
|
||||
if let Some(selector) = selector {
|
||||
let selector_action = if action == ManageExtensionAction::Disable {
|
||||
"remove"
|
||||
} else {
|
||||
"add"
|
||||
};
|
||||
let selector = Arc::new(selector);
|
||||
if let Err(e) = ToolRouterIndexManager::update_extension_tools(
|
||||
&selector,
|
||||
&extension_manager,
|
||||
&extension_name,
|
||||
selector_action,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!("Failed to update LLM index: {}", e),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if action == ManageExtensionAction::Disable {
|
||||
let result = extension_manager
|
||||
.remove_extension(&extension_name)
|
||||
.await
|
||||
.map(|_| {
|
||||
vec![Content::text(format!(
|
||||
"The extension '{}' has been disabled successfully",
|
||||
extension_name
|
||||
))]
|
||||
})
|
||||
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None));
|
||||
return result;
|
||||
}
|
||||
|
||||
let config = match get_extension_by_name(&extension_name) {
|
||||
Some(config) => config,
|
||||
None => {
|
||||
return Err(ErrorData::new(
|
||||
ErrorCode::RESOURCE_NOT_FOUND,
|
||||
format!(
|
||||
"Extension '{}' not found. Please check the extension name and try again.",
|
||||
extension_name
|
||||
),
|
||||
None,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let result = extension_manager
|
||||
.add_extension(config)
|
||||
.await
|
||||
.map(|_| {
|
||||
vec![Content::text(format!(
|
||||
"The extension '{}' has been installed successfully",
|
||||
extension_name
|
||||
))]
|
||||
})
|
||||
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None));
|
||||
|
||||
// Update LLM index if operation was successful and LLM routing is functional
|
||||
if result.is_ok() {
|
||||
if let Some(tool_route_manager) = &tool_route_manager {
|
||||
if tool_route_manager.is_router_functional().await {
|
||||
let selector = tool_route_manager.get_router_tool_selector().await;
|
||||
if let Some(selector) = selector {
|
||||
let llm_action = if action == ManageExtensionAction::Disable {
|
||||
"remove"
|
||||
} else {
|
||||
"add"
|
||||
};
|
||||
let selector = Arc::new(selector);
|
||||
if let Err(e) = ToolRouterIndexManager::update_extension_tools(
|
||||
&selector,
|
||||
&extension_manager,
|
||||
&extension_name,
|
||||
llm_action,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!("Failed to update LLM index: {}", e),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn handle_list_resources(
|
||||
&self,
|
||||
arguments: Option<JsonObject>,
|
||||
) -> Result<Vec<Content>, ExtensionManagerToolError> {
|
||||
if let Some(weak_ref) = &self.context.extension_manager {
|
||||
if let Some(extension_manager) = weak_ref.upgrade() {
|
||||
let params = arguments
|
||||
.map(serde_json::Value::Object)
|
||||
.unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
|
||||
|
||||
match extension_manager
|
||||
.list_resources(params, tokio_util::sync::CancellationToken::default())
|
||||
.await
|
||||
{
|
||||
Ok(content) => Ok(content),
|
||||
Err(e) => Err(ExtensionManagerToolError::OperationFailed {
|
||||
message: format!("Failed to list resources: {}", e.message),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ExtensionManagerToolError::ManagerUnavailable)
|
||||
}
|
||||
} else {
|
||||
Err(ExtensionManagerToolError::ManagerUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_read_resource(
|
||||
&self,
|
||||
arguments: Option<JsonObject>,
|
||||
) -> Result<Vec<Content>, ExtensionManagerToolError> {
|
||||
if let Some(weak_ref) = &self.context.extension_manager {
|
||||
if let Some(extension_manager) = weak_ref.upgrade() {
|
||||
let params = arguments
|
||||
.map(serde_json::Value::Object)
|
||||
.unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
|
||||
|
||||
match extension_manager
|
||||
.read_resource(params, tokio_util::sync::CancellationToken::default())
|
||||
.await
|
||||
{
|
||||
Ok(content) => Ok(content),
|
||||
Err(e) => Err(ExtensionManagerToolError::OperationFailed {
|
||||
message: format!("Failed to read resource: {}", e.message),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ExtensionManagerToolError::ManagerUnavailable)
|
||||
}
|
||||
} else {
|
||||
Err(ExtensionManagerToolError::ManagerUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn get_tools(&self) -> Vec<Tool> {
|
||||
let mut tools = vec![
|
||||
Tool::new(
|
||||
SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME.to_string(),
|
||||
"Searches for additional extensions available to help complete tasks.
|
||||
Use this tool when you're unable to find a specific feature or functionality you need to complete your task, or when standard approaches aren't working.
|
||||
These extensions might provide the exact tools needed to solve your problem.
|
||||
If you find a relevant one, consider using your tools to enable it.".to_string(),
|
||||
Arc::new(
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"properties": {}
|
||||
})
|
||||
.as_object()
|
||||
.expect("Schema must be an object")
|
||||
.clone()
|
||||
),
|
||||
).annotate(ToolAnnotations {
|
||||
title: Some("Discover extensions".to_string()),
|
||||
read_only_hint: Some(true),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(false),
|
||||
open_world_hint: Some(false),
|
||||
}),
|
||||
Tool::new(
|
||||
MANAGE_EXTENSIONS_TOOL_NAME.to_string(),
|
||||
"Tool to manage extensions and tools in goose context.
|
||||
Enable or disable extensions to help complete tasks.
|
||||
Enable or disable an extension by providing the extension name.
|
||||
".to_string(),
|
||||
Arc::new(
|
||||
serde_json::to_value(schema_for!(ManageExtensionsParams))
|
||||
.expect("Failed to serialize schema")
|
||||
.as_object()
|
||||
.expect("Schema must be an object")
|
||||
.clone()
|
||||
),
|
||||
).annotate(ToolAnnotations {
|
||||
title: Some("Enable or disable an extension".to_string()),
|
||||
read_only_hint: Some(false),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(false),
|
||||
open_world_hint: Some(false),
|
||||
}),
|
||||
];
|
||||
|
||||
// Only add resource tools if extension manager supports resources
|
||||
if let Some(weak_ref) = &self.context.extension_manager {
|
||||
if let Some(extension_manager) = weak_ref.upgrade() {
|
||||
if extension_manager.supports_resources().await {
|
||||
tools.extend([
|
||||
Tool::new(
|
||||
LIST_RESOURCES_TOOL_NAME.to_string(),
|
||||
indoc! {r#"
|
||||
List resources from an extension(s).
|
||||
|
||||
Resources allow extensions to share data that provide context to LLMs, such as
|
||||
files, database schemas, or application-specific information. This tool lists resources
|
||||
in the provided extension, and returns a list for the user to browse. If no extension
|
||||
is provided, the tool will search all extensions for the resource.
|
||||
"#}.to_string(),
|
||||
Arc::new(
|
||||
serde_json::to_value(schema_for!(ListResourcesParams))
|
||||
.expect("Failed to serialize schema")
|
||||
.as_object()
|
||||
.expect("Schema must be an object")
|
||||
.clone()
|
||||
),
|
||||
).annotate(ToolAnnotations {
|
||||
title: Some("List resources".to_string()),
|
||||
read_only_hint: Some(true),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(false),
|
||||
open_world_hint: Some(false),
|
||||
}),
|
||||
Tool::new(
|
||||
READ_RESOURCE_TOOL_NAME.to_string(),
|
||||
indoc! {r#"
|
||||
Read a resource from an extension.
|
||||
|
||||
Resources allow extensions to share data that provide context to LLMs, such as
|
||||
files, database schemas, or application-specific information. This tool searches for the
|
||||
resource URI in the provided extension, and reads in the resource content. If no extension
|
||||
is provided, the tool will search all extensions for the resource.
|
||||
"#}.to_string(),
|
||||
Arc::new(
|
||||
serde_json::to_value(schema_for!(ReadResourceParams))
|
||||
.expect("Failed to serialize schema")
|
||||
.as_object()
|
||||
.expect("Schema must be an object")
|
||||
.clone()
|
||||
),
|
||||
).annotate(ToolAnnotations {
|
||||
title: Some("Read a resource".to_string()),
|
||||
read_only_hint: Some(true),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(false),
|
||||
open_world_hint: Some(false),
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tools
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl McpClientTrait for ExtensionManagerClient {
|
||||
async fn list_resources(
|
||||
&self,
|
||||
_next_cursor: Option<String>,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<ListResourcesResult, Error> {
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
async fn read_resource(
|
||||
&self,
|
||||
_uri: &str,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<ReadResourceResult, Error> {
|
||||
// Extension manager doesn't expose resources directly
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
async fn list_tools(
|
||||
&self,
|
||||
_next_cursor: Option<String>,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<ListToolsResult, Error> {
|
||||
Ok(ListToolsResult {
|
||||
tools: self.get_tools().await,
|
||||
next_cursor: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn call_tool(
|
||||
&self,
|
||||
name: &str,
|
||||
arguments: Option<JsonObject>,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<CallToolResult, Error> {
|
||||
let result = match name {
|
||||
SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME => {
|
||||
self.handle_search_available_extensions().await
|
||||
}
|
||||
MANAGE_EXTENSIONS_TOOL_NAME => self.handle_manage_extensions(arguments).await,
|
||||
LIST_RESOURCES_TOOL_NAME => self.handle_list_resources(arguments).await,
|
||||
READ_RESOURCE_TOOL_NAME => self.handle_read_resource(arguments).await,
|
||||
_ => Err(ExtensionManagerToolError::UnknownTool {
|
||||
tool_name: name.to_string(),
|
||||
}),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(content) => Ok(CallToolResult::success(content)),
|
||||
Err(error) => {
|
||||
// Log the error for debugging
|
||||
error!("Extension manager tool '{}' failed: {}", name, error);
|
||||
|
||||
// Return proper error result with is_error flag set
|
||||
Ok(CallToolResult {
|
||||
content: vec![Content::text(error.to_string())],
|
||||
is_error: Some(true), // ✅ Properly mark as error
|
||||
structured_content: None,
|
||||
meta: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_prompts(
|
||||
&self,
|
||||
_next_cursor: Option<String>,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<ListPromptsResult, Error> {
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
async fn get_prompt(
|
||||
&self,
|
||||
_name: &str,
|
||||
_arguments: Value,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<GetPromptResult, Error> {
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
async fn subscribe(&self) -> mpsc::Receiver<ServerNotification> {
|
||||
mpsc::channel(1).1
|
||||
}
|
||||
|
||||
fn get_info(&self) -> Option<&InitializeResult> {
|
||||
Some(&self.info)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ mod agent;
|
||||
pub mod extension;
|
||||
pub mod extension_malware_check;
|
||||
pub mod extension_manager;
|
||||
pub mod extension_manager_extension;
|
||||
pub mod final_output_tool;
|
||||
mod large_response_handler;
|
||||
pub mod mcp_client;
|
||||
|
||||
@@ -1,8 +1,116 @@
|
||||
use indoc::indoc;
|
||||
use rmcp::model::{Tool, ToolAnnotations};
|
||||
use rmcp::object;
|
||||
|
||||
pub const PLATFORM_READ_RESOURCE_TOOL_NAME: &str = "platform__read_resource";
|
||||
pub const PLATFORM_LIST_RESOURCES_TOOL_NAME: &str = "platform__list_resources";
|
||||
pub const PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME: &str =
|
||||
"platform__search_available_extensions";
|
||||
pub const PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME: &str = "platform__manage_extensions";
|
||||
pub const PLATFORM_MANAGE_SCHEDULE_TOOL_NAME: &str = "platform__manage_schedule";
|
||||
|
||||
pub fn read_resource_tool() -> Tool {
|
||||
Tool::new(
|
||||
PLATFORM_READ_RESOURCE_TOOL_NAME.to_string(),
|
||||
indoc! {r#"
|
||||
Read a resource from an extension.
|
||||
|
||||
Resources allow extensions to share data that provide context to LLMs, such as
|
||||
files, database schemas, or application-specific information. This tool searches for the
|
||||
resource URI in the provided extension, and reads in the resource content. If no extension
|
||||
is provided, the tool will search all extensions for the resource.
|
||||
"#}.to_string(),
|
||||
object!({
|
||||
"type": "object",
|
||||
"required": ["uri"],
|
||||
"properties": {
|
||||
"uri": {"type": "string", "description": "Resource URI"},
|
||||
"extension_name": {"type": "string", "description": "Optional extension name"}
|
||||
}
|
||||
})
|
||||
).annotate(ToolAnnotations {
|
||||
title: Some("Read a resource".to_string()),
|
||||
read_only_hint: Some(true),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(false),
|
||||
open_world_hint: Some(false),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_resources_tool() -> Tool {
|
||||
Tool::new(
|
||||
PLATFORM_LIST_RESOURCES_TOOL_NAME.to_string(),
|
||||
indoc! {r#"
|
||||
List resources from an extension(s).
|
||||
|
||||
Resources allow extensions to share data that provide context to LLMs, such as
|
||||
files, database schemas, or application-specific information. This tool lists resources
|
||||
in the provided extension, and returns a list for the user to browse. If no extension
|
||||
is provided, the tool will search all extensions for the resource.
|
||||
"#}
|
||||
.to_string(),
|
||||
object!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extension_name": {"type": "string", "description": "Optional extension name"}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.annotate(ToolAnnotations {
|
||||
title: Some("List resources".to_string()),
|
||||
read_only_hint: Some(true),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(false),
|
||||
open_world_hint: Some(false),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn search_available_extensions_tool() -> Tool {
|
||||
Tool::new(
|
||||
PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME.to_string(),
|
||||
"Searches for additional extensions available to help complete tasks.
|
||||
Use this tool when you're unable to find a specific feature or functionality you need to complete your task, or when standard approaches aren't working.
|
||||
These extensions might provide the exact tools needed to solve your problem.
|
||||
If you find a relevant one, consider using your tools to enable it.".to_string(),
|
||||
object!({
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"properties": {}
|
||||
})
|
||||
).annotate(ToolAnnotations {
|
||||
title: Some("Discover extensions".to_string()),
|
||||
read_only_hint: Some(true),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(false),
|
||||
open_world_hint: Some(false),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn manage_extensions_tool() -> Tool {
|
||||
Tool::new(
|
||||
PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME.to_string(),
|
||||
"Tool to manage extensions and tools in goose context.
|
||||
Enable or disable extensions to help complete tasks.
|
||||
Enable or disable an extension by providing the extension name.
|
||||
"
|
||||
.to_string(),
|
||||
object!({
|
||||
"type": "object",
|
||||
"required": ["action", "extension_name"],
|
||||
"properties": {
|
||||
"action": {"type": "string", "description": "The action to perform", "enum": ["enable", "disable"]},
|
||||
"extension_name": {"type": "string", "description": "The name of the extension to enable"}
|
||||
}
|
||||
}),
|
||||
).annotate(ToolAnnotations {
|
||||
title: Some("Enable or disable an extension".to_string()),
|
||||
read_only_hint: Some(false),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(false),
|
||||
open_world_hint: Some(false),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn manage_schedule_tool() -> Tool {
|
||||
Tool::new(
|
||||
PLATFORM_MANAGE_SCHEDULE_TOOL_NAME.to_string(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::agents::extension_manager_extension::{
|
||||
LIST_RESOURCES_TOOL_NAME, MANAGE_EXTENSIONS_TOOL_NAME, READ_RESOURCE_TOOL_NAME,
|
||||
SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
||||
use super::platform_tools::{
|
||||
PLATFORM_LIST_RESOURCES_TOOL_NAME, PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME,
|
||||
PLATFORM_READ_RESOURCE_TOOL_NAME, PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use rmcp::model::{Tool, ToolAnnotations};
|
||||
@@ -57,9 +57,9 @@ pub fn llm_search_tool_prompt() -> String {
|
||||
- {}
|
||||
- {}
|
||||
"#,
|
||||
SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
||||
MANAGE_EXTENSIONS_TOOL_NAME,
|
||||
READ_RESOURCE_TOOL_NAME,
|
||||
LIST_RESOURCES_TOOL_NAME
|
||||
PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
||||
PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME,
|
||||
PLATFORM_READ_RESOURCE_TOOL_NAME,
|
||||
PLATFORM_LIST_RESOURCES_TOOL_NAME
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,12 +17,6 @@ pub struct ToolRouteManager {
|
||||
router_disabled_override: Mutex<bool>,
|
||||
}
|
||||
|
||||
impl Default for ToolRouteManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolRouteManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -103,6 +97,9 @@ impl ToolRouteManager {
|
||||
// Wrap selector in Arc for the index manager methods
|
||||
let selector_arc = Arc::new(selector);
|
||||
|
||||
// First index platform tools
|
||||
ToolRouterIndexManager::index_platform_tools(&selector_arc, extension_manager).await?;
|
||||
|
||||
if reindex_all.unwrap_or(false) {
|
||||
let enabled_extensions = extension_manager.list_extensions().await?;
|
||||
for extension_name in enabled_extensions {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use tracing;
|
||||
|
||||
use crate::agents::extension_manager::ExtensionManager;
|
||||
use crate::agents::platform_tools;
|
||||
use crate::agents::router_tool_selector::RouterToolSelector;
|
||||
|
||||
/// Manages tool indexing operations for the router when LLM routing is enabled
|
||||
@@ -73,4 +74,31 @@ impl ToolRouterIndexManager {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Indexes platform tools (search_available_extensions, manage_extensions, etc.)
|
||||
pub async fn index_platform_tools(
|
||||
selector: &Arc<Box<dyn RouterToolSelector>>,
|
||||
extension_manager: &ExtensionManager,
|
||||
) -> Result<()> {
|
||||
let mut tools = Vec::new();
|
||||
|
||||
// Add the standard platform tools
|
||||
tools.push(platform_tools::search_available_extensions_tool());
|
||||
tools.push(platform_tools::manage_extensions_tool());
|
||||
|
||||
// Add resource tools if supported
|
||||
if extension_manager.supports_resources().await {
|
||||
tools.push(platform_tools::read_resource_tool());
|
||||
tools.push(platform_tools::list_resources_tool());
|
||||
}
|
||||
|
||||
// Index all platform tools at once
|
||||
selector
|
||||
.index_tools(&tools, "platform")
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to index platform tools: {}", e))?;
|
||||
|
||||
tracing::info!("Indexed platform tools for LLM search");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user