feat: ToolError migration to ErrorData (#4051)

This commit is contained in:
Alex Hancock
2025-08-12 16:18:41 -04:00
committed by GitHub
parent 88b013194c
commit bd1eff52a4
35 changed files with 2459 additions and 1336 deletions
+47 -25
View File
@@ -44,9 +44,11 @@ use crate::scheduler_trait::SchedulerTrait;
use crate::session;
use crate::tool_monitor::{ToolCall, ToolMonitor};
use crate::utils::is_token_cancelled;
use mcp_core::{ToolError, ToolResult};
use mcp_core::ToolResult;
use regex::Regex;
use rmcp::model::{Content, GetPromptResult, Prompt, ServerNotification, Tool};
use rmcp::model::{
Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ServerNotification, Tool,
};
use serde_json::Value;
use tokio::sync::{mpsc, Mutex, RwLock};
use tokio_util::sync::CancellationToken;
@@ -376,7 +378,7 @@ impl Agent {
tool_call: mcp_core::tool::ToolCall,
request_id: String,
cancellation_token: Option<CancellationToken>,
) -> (String, Result<ToolCallResult, ToolError>) {
) -> (String, Result<ToolCallResult, ErrorData>) {
// Check if this tool call should be allowed based on repetition monitoring
if let Some(monitor) = self.tool_monitor.lock().await.as_mut() {
let tool_call_info = ToolCall::new(tool_call.name.clone(), tool_call.arguments.clone());
@@ -384,8 +386,10 @@ impl Agent {
if !monitor.check_tool_call(tool_call_info) {
return (
request_id,
Err(ToolError::ExecutionError(
Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Tool call rejected: exceeded maximum allowed repetitions".to_string(),
None,
)),
);
}
@@ -425,8 +429,10 @@ impl Agent {
} else {
(
request_id,
Err(ToolError::ExecutionError(
Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Final output tool not defined".to_string(),
None,
)),
)
};
@@ -478,8 +484,10 @@ impl Agent {
ToolCallResult::from(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(ToolError::ExecutionError(
ToolCallResult::from(Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Frontend tool execution required".to_string(),
None,
)))
} else if tool_call.name == TODO_READ_TOOL_NAME {
// Handle task planner read tool
@@ -505,11 +513,13 @@ impl Agent {
if max_chars > 0 && char_count > max_chars {
return (
request_id,
Ok(ToolCallResult::from(Err(ToolError::ExecutionError(
Ok(ToolCallResult::from(Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!(
"Todo list too large: {} chars (max: {})",
char_count, max_chars
),
None,
)))),
);
}
@@ -537,7 +547,11 @@ impl Agent {
.dispatch_tool_call(tool_call.clone(), cancellation_token.unwrap_or_default())
.await;
result.unwrap_or_else(|e| {
ToolCallResult::from(Err(ToolError::ExecutionError(e.to_string())))
ToolCallResult::from(Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
e.to_string(),
None,
)))
})
};
@@ -554,12 +568,13 @@ impl Agent {
)
}
#[allow(clippy::too_many_lines)]
pub(super) async fn manage_extensions(
&self,
action: String,
extension_name: String,
request_id: String,
) -> (String, Result<Vec<Content>, ToolError>) {
) -> (String, Result<Vec<Content>, ErrorData>) {
let selector = self.tool_route_manager.get_router_tool_selector().await;
if ToolRouterIndexManager::is_tool_router_enabled(&selector) {
if let Some(selector) = selector {
@@ -576,10 +591,11 @@ impl Agent {
{
return (
request_id,
Err(ToolError::ExecutionError(format!(
"Failed to update vector index: {}",
e
))),
Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to update vector index: {}", e),
None,
)),
);
}
}
@@ -595,7 +611,7 @@ impl Agent {
extension_name
))]
})
.map_err(|e| ToolError::ExecutionError(e.to_string()));
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None));
return (request_id, result);
}
@@ -604,19 +620,24 @@ impl Agent {
Ok(None) => {
return (
request_id,
Err(ToolError::ExecutionError(format!(
Err(ErrorData::new(
ErrorCode::RESOURCE_NOT_FOUND,
format!(
"Extension '{}' not found. Please check the extension name and try again.",
extension_name
))),
),
None,
)),
)
}
Err(e) => {
return (
request_id,
Err(ToolError::ExecutionError(format!(
"Failed to get extension config: {}",
e
))),
Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to get extension config: {}", e),
None,
)),
)
}
};
@@ -629,7 +650,7 @@ impl Agent {
extension_name
))]
})
.map_err(|e| ToolError::ExecutionError(e.to_string()));
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None));
drop(extension_manager);
// Update vector index if operation was successful and vector routing is enabled
@@ -650,10 +671,11 @@ impl Agent {
{
return (
request_id,
Err(ToolError::ExecutionError(format!(
"Failed to update vector index: {}",
e
))),
Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to update vector index: {}", e),
None,
)),
);
}
}
+59 -35
View File
@@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
use futures::stream::{FuturesUnordered, StreamExt};
use futures::{future, FutureExt};
use mcp_core::handler::require_str_parameter;
use mcp_core::{ToolCall, ToolError};
use mcp_core::ToolCall;
use rmcp::service::ClientInitializeError;
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
use rmcp::transport::{
@@ -30,7 +30,7 @@ use crate::config::{Config, ExtensionConfigManager};
use crate::oauth::oauth_flow;
use crate::prompt_template;
use mcp_client::client::{McpClient, McpClientTrait};
use rmcp::model::{Content, GetPromptResult, Prompt, ResourceContents, Tool};
use rmcp::model::{Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ResourceContents, Tool};
use rmcp::transport::auth::AuthClient;
use serde_json::Value;
@@ -546,7 +546,7 @@ impl ExtensionManager {
&self,
params: Value,
cancellation_token: CancellationToken,
) -> Result<Vec<Content>, ToolError> {
) -> Result<Vec<Content>, ErrorData> {
let uri = require_str_parameter(&params, "uri")?;
let extension_name = params.get("extension_name").and_then(|v| v.as_str());
@@ -588,7 +588,11 @@ impl ExtensionManager {
uri, available_extensions
);
Err(ToolError::InvalidParameters(error_msg))
Err(ErrorData::new(
ErrorCode::RESOURCE_NOT_FOUND,
error_msg,
None,
))
}
async fn read_resource_from_extension(
@@ -596,7 +600,7 @@ impl ExtensionManager {
uri: &str,
extension_name: &str,
cancellation_token: CancellationToken,
) -> Result<Vec<Content>, ToolError> {
) -> Result<Vec<Content>, ErrorData> {
let available_extensions = self
.clients
.keys()
@@ -608,17 +612,22 @@ impl ExtensionManager {
extension_name, available_extensions
);
let client = self
.clients
.get(extension_name)
.ok_or(ToolError::InvalidParameters(error_msg))?;
let client = self.clients.get(extension_name).ok_or(ErrorData::new(
ErrorCode::INVALID_PARAMS,
error_msg,
None,
))?;
let client_guard = client.lock().await;
let read_result = client_guard
.read_resource(uri, cancellation_token)
.await
.map_err(|_| {
ToolError::ExecutionError(format!("Could not read resource with uri: {}", uri))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Could not read resource with uri: {}", uri),
None,
)
})?;
let mut result = Vec::new();
@@ -637,9 +646,13 @@ impl ExtensionManager {
&self,
extension_name: &str,
cancellation_token: CancellationToken,
) -> Result<Vec<Content>, ToolError> {
) -> Result<Vec<Content>, ErrorData> {
let client = self.clients.get(extension_name).ok_or_else(|| {
ToolError::InvalidParameters(format!("Extension {} is not valid", extension_name))
ErrorData::new(
ErrorCode::INVALID_PARAMS,
format!("Extension {} is not valid", extension_name),
None,
)
})?;
let client_guard = client.lock().await;
@@ -647,10 +660,11 @@ impl ExtensionManager {
.list_resources(None, cancellation_token)
.await
.map_err(|e| {
ToolError::ExecutionError(format!(
"Unable to list resources for {}, {:?}",
extension_name, e
))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Unable to list resources for {}, {:?}", extension_name, e),
None,
)
})
.map(|lr| {
let resource_list = lr
@@ -668,7 +682,7 @@ impl ExtensionManager {
&self,
params: Value,
cancellation_token: CancellationToken,
) -> Result<Vec<Content>, ToolError> {
) -> Result<Vec<Content>, ErrorData> {
let extension = params.get("extension").and_then(|v| v.as_str());
match extension {
@@ -727,16 +741,18 @@ impl ExtensionManager {
cancellation_token: CancellationToken,
) -> Result<ToolCallResult> {
// Dispatch tool call based on the prefix naming convention
let (client_name, client) = self
.get_client_for_tool(&tool_call.name)
.ok_or_else(|| ToolError::NotFound(tool_call.name.clone()))?;
let (client_name, client) = self.get_client_for_tool(&tool_call.name).ok_or_else(|| {
ErrorData::new(ErrorCode::RESOURCE_NOT_FOUND, tool_call.name.clone(), None)
})?;
// rsplit returns the iterator in reverse, tool_name is then at 0
let tool_name = tool_call
.name
.strip_prefix(client_name)
.and_then(|s| s.strip_prefix("__"))
.ok_or_else(|| ToolError::NotFound(tool_call.name.clone()))?
.ok_or_else(|| {
ErrorData::new(ErrorCode::RESOURCE_NOT_FOUND, tool_call.name.clone(), None)
})?
.to_string();
let arguments = tool_call.arguments.clone();
@@ -749,7 +765,7 @@ impl ExtensionManager {
.call_tool(&tool_name, arguments, cancellation_token)
.await
.map(|call| call.content.unwrap_or_default())
.map_err(|e| ToolError::ExecutionError(e.to_string()))
.map_err(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None))
};
Ok(ToolCallResult {
@@ -762,9 +778,13 @@ impl ExtensionManager {
&self,
extension_name: &str,
cancellation_token: CancellationToken,
) -> Result<Vec<Prompt>, ToolError> {
) -> Result<Vec<Prompt>, ErrorData> {
let client = self.clients.get(extension_name).ok_or_else(|| {
ToolError::InvalidParameters(format!("Extension {} is not valid", extension_name))
ErrorData::new(
ErrorCode::INVALID_PARAMS,
format!("Extension {} is not valid", extension_name),
None,
)
})?;
let client_guard = client.lock().await;
@@ -772,10 +792,11 @@ impl ExtensionManager {
.list_prompts(None, cancellation_token)
.await
.map_err(|e| {
ToolError::ExecutionError(format!(
"Unable to list prompts for {}, {:?}",
extension_name, e
))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Unable to list prompts for {}, {:?}", extension_name, e),
None,
)
})
.map(|lp| lp.prompts)
}
@@ -783,7 +804,7 @@ impl ExtensionManager {
pub async fn list_prompts(
&self,
cancellation_token: CancellationToken,
) -> Result<HashMap<String, Vec<Prompt>>, ToolError> {
) -> Result<HashMap<String, Vec<Prompt>>, ErrorData> {
let mut futures = FuturesUnordered::new();
for extension_name in self.clients.keys() {
@@ -846,7 +867,7 @@ impl ExtensionManager {
.map_err(|e| anyhow::anyhow!("Failed to get prompt: {}", e))
}
pub async fn search_available_extensions(&self) -> Result<Vec<Content>, ToolError> {
pub async fn search_available_extensions(&self) -> Result<Vec<Content>, ErrorData> {
let mut output_parts = vec![];
// First get disabled extensions from current config
@@ -1140,8 +1161,11 @@ mod tests {
.result
.await;
assert!(matches!(
result.err().unwrap(),
ToolError::ExecutionError(_)
result,
Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
..
})
));
// this should error out, specifically with an ToolError::NotFound
@@ -1155,10 +1179,10 @@ mod tests {
.dispatch_tool_call(invalid_tool_call, CancellationToken::default())
.await;
if let Err(err) = result {
let tool_err = err.downcast_ref::<ToolError>().expect("Expected ToolError");
assert!(matches!(tool_err, ToolError::NotFound(_)));
let tool_err = err.downcast_ref::<ErrorData>().expect("Expected ErrorData");
assert_eq!(tool_err.code, ErrorCode::RESOURCE_NOT_FOUND);
} else {
panic!("Expected ToolError::NotFound");
panic!("Expected ErrorData with ErrorCode::RESOURCE_NOT_FOUND");
}
}
}
+13 -7
View File
@@ -1,9 +1,10 @@
use crate::agents::tool_execution::ToolCallResult;
use crate::recipe::Response;
use indoc::formatdoc;
use mcp_core::{ToolCall, ToolError};
use rmcp::model::{Content, Tool, ToolAnnotations};
use mcp_core::ToolCall;
use rmcp::model::{Content, ErrorCode, ErrorData, Tool, ToolAnnotations};
use serde_json::Value;
use std::borrow::Cow;
pub const FINAL_OUTPUT_TOOL_NAME: &str = "recipe__final_output";
pub const FINAL_OUTPUT_CONTINUATION_MESSAGE: &str =
@@ -127,13 +128,18 @@ impl FinalOutputTool {
"Final output successfully collected.".to_string(),
)]))
}
Err(error) => ToolCallResult::from(Err(ToolError::InvalidParameters(error))),
Err(error) => ToolCallResult::from(Err(ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: Cow::from(error),
data: None,
})),
}
}
_ => ToolCallResult::from(Err(ToolError::NotFound(format!(
"Unknown tool: {}",
tool_call.name
)))),
_ => ToolCallResult::from(Err(ErrorData {
code: ErrorCode::INVALID_REQUEST,
message: Cow::from(format!("Unknown tool: {}", tool_call.name)),
data: None,
})),
}
}
@@ -1,6 +1,5 @@
use chrono::Utc;
use mcp_core::ToolError;
use rmcp::model::Content;
use rmcp::model::{Content, ErrorData};
use std::fs::File;
use std::io::Write;
@@ -8,8 +7,8 @@ const LARGE_TEXT_THRESHOLD: usize = 200_000;
/// Process tool response and handle large text content
pub fn process_tool_response(
response: Result<Vec<Content>, ToolError>,
) -> Result<Vec<Content>, ToolError> {
response: Result<Vec<Content>, ErrorData>,
) -> Result<Vec<Content>, ErrorData> {
match response {
Ok(contents) => {
let mut processed_contents = Vec::new();
@@ -79,8 +78,8 @@ fn write_large_text_to_file(content: &str) -> Result<String, std::io::Error> {
#[cfg(test)]
mod tests {
use super::*;
use mcp_core::ToolError;
use rmcp::model::Content;
use rmcp::model::{Content, ErrorCode, ErrorData};
use std::borrow::Cow;
use std::fs;
use std::path::Path;
@@ -213,8 +212,12 @@ mod tests {
#[test]
fn test_error_response_passes_through() {
// Create an error response
let error = ToolError::ExecutionError("Test error".to_string());
let response: Result<Vec<Content>, ToolError> = Err(error);
let error = ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from("Test error"),
data: None,
};
let response: Result<Vec<Content>, ErrorData> = Err(error);
// Process the response
let processed = process_tool_response(response);
@@ -222,8 +225,9 @@ mod tests {
// Verify the error is passed through unchanged
assert!(processed.is_err());
match processed {
Err(ToolError::ExecutionError(msg)) => {
assert_eq!(msg, "Test error");
Err(err) => {
assert_eq!(err.code, ErrorCode::INTERNAL_ERROR);
assert_eq!(err.message, "Test error");
}
_ => panic!("Expected execution error"),
}
@@ -5,10 +5,10 @@
use crate::agents::subagent_execution_tool::tasks_manager::TasksManager;
use crate::agents::subagent_execution_tool::{lib::ExecutionMode, task_types::Task};
use crate::agents::tool_execution::ToolCallResult;
use mcp_core::ToolError;
use rmcp::model::{Content, Tool, ToolAnnotations};
use rmcp::model::{Content, ErrorCode, ErrorData, Tool, ToolAnnotations};
use rmcp::object;
use serde_json::{json, Value};
use std::borrow::Cow;
pub const DYNAMIC_TASK_TOOL_NAME_PREFIX: &str = "dynamic_task__create_task";
@@ -110,9 +110,11 @@ pub async fn create_dynamic_task(params: Value, tasks_manager: &TasksManager) ->
let task_params_array = extract_task_parameters(&params);
if task_params_array.is_empty() {
return ToolCallResult::from(Err(ToolError::ExecutionError(
"No task parameters provided".to_string(),
)));
return ToolCallResult::from(Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from("No task parameters provided"),
data: None,
}));
}
let tasks = create_text_instruction_tasks_from_params(&task_params_array);
@@ -129,10 +131,11 @@ pub async fn create_dynamic_task(params: Value, tasks_manager: &TasksManager) ->
let tasks_json = match serde_json::to_string(&task_execution_payload) {
Ok(json) => json,
Err(e) => {
return ToolCallResult::from(Err(ToolError::ExecutionError(format!(
"Failed to serialize task list: {}",
e
))))
return ToolCallResult::from(Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to serialize task list: {}", e)),
data: None,
}))
}
};
tasks_manager.save_tasks(tasks.clone()).await;
+81 -43
View File
@@ -1,11 +1,11 @@
use mcp_core::ToolError;
use rmcp::model::Content;
use rmcp::model::Tool;
use rmcp::model::{Content, ErrorCode, ErrorData};
use anyhow::{Context, Result};
use async_trait::async_trait;
use serde::Serialize;
use serde_json::Value;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::env;
@@ -32,11 +32,11 @@ pub enum RouterToolSelectionStrategy {
#[async_trait]
pub trait RouterToolSelector: Send + Sync {
async fn select_tools(&self, params: Value) -> Result<Vec<Content>, ToolError>;
async fn index_tools(&self, tools: &[Tool], extension_name: &str) -> Result<(), ToolError>;
async fn remove_tool(&self, tool_name: &str) -> Result<(), ToolError>;
async fn record_tool_call(&self, tool_name: &str) -> Result<(), ToolError>;
async fn get_recent_tool_calls(&self, limit: usize) -> Result<Vec<String>, ToolError>;
async fn select_tools(&self, params: Value) -> Result<Vec<Content>, ErrorData>;
async fn index_tools(&self, tools: &[Tool], extension_name: &str) -> Result<(), ErrorData>;
async fn remove_tool(&self, tool_name: &str) -> Result<(), ErrorData>;
async fn record_tool_call(&self, tool_name: &str) -> Result<(), ErrorData>;
async fn get_recent_tool_calls(&self, limit: usize) -> Result<Vec<String>, ErrorData>;
fn selector_type(&self) -> RouterToolSelectionStrategy;
}
@@ -80,11 +80,15 @@ impl VectorToolSelector {
#[async_trait]
impl RouterToolSelector for VectorToolSelector {
async fn select_tools(&self, params: Value) -> Result<Vec<Content>, ToolError> {
async fn select_tools(&self, params: Value) -> Result<Vec<Content>, ErrorData> {
let query = params
.get("query")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidParameters("Missing 'query' parameter".to_string()))?;
.ok_or_else(|| ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: Cow::from("Missing 'query' parameter"),
data: None,
})?;
let k = params.get("k").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
@@ -93,29 +97,38 @@ impl RouterToolSelector for VectorToolSelector {
// Check if provider supports embeddings
if !self.embedding_provider.supports_embeddings() {
return Err(ToolError::ExecutionError(
"Embedding provider does not support embeddings".to_string(),
));
return Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from("Embedding provider does not support embeddings"),
data: None,
});
}
let embeddings = self
.embedding_provider
.create_embeddings(vec![query.to_string()])
.await
.map_err(|e| {
ToolError::ExecutionError(format!("Failed to generate query embedding: {}", e))
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to generate query embedding: {}", e)),
data: None,
})?;
let query_embedding = embeddings
.into_iter()
.next()
.ok_or_else(|| ToolError::ExecutionError("No embedding returned".to_string()))?;
let query_embedding = embeddings.into_iter().next().ok_or_else(|| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from("No embedding returned"),
data: None,
})?;
let vector_db = self.vector_db.read().await;
let tools = vector_db
.search_tools(query_embedding, k, extension_name)
.await
.map_err(|e| ToolError::ExecutionError(format!("Failed to search tools: {}", e)))?;
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to search tools: {}", e)),
data: None,
})?;
let selected_tools: Vec<Content> = tools
.into_iter()
@@ -131,7 +144,7 @@ impl RouterToolSelector for VectorToolSelector {
Ok(selected_tools)
}
async fn index_tools(&self, tools: &[Tool], extension_name: &str) -> Result<(), ToolError> {
async fn index_tools(&self, tools: &[Tool], extension_name: &str) -> Result<(), ErrorData> {
let texts_to_embed: Vec<String> = tools
.iter()
.map(|tool| {
@@ -150,17 +163,21 @@ impl RouterToolSelector for VectorToolSelector {
.collect();
if !self.embedding_provider.supports_embeddings() {
return Err(ToolError::ExecutionError(
"Embedding provider does not support embeddings".to_string(),
));
return Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from("Embedding provider does not support embeddings"),
data: None,
});
}
let embeddings = self
.embedding_provider
.create_embeddings(texts_to_embed)
.await
.map_err(|e| {
ToolError::ExecutionError(format!("Failed to generate tool embeddings: {}", e))
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to generate tool embeddings: {}", e)),
data: None,
})?;
// Create tool records
@@ -194,8 +211,10 @@ impl RouterToolSelector for VectorToolSelector {
let existing_tools = vector_db
.search_tools(record.vector.clone(), 1, Some(&record.extension_name))
.await
.map_err(|e| {
ToolError::ExecutionError(format!("Failed to search for existing tools: {}", e))
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to search for existing tools: {}", e)),
data: None,
})?;
// Only add if no exact match found
@@ -212,21 +231,30 @@ impl RouterToolSelector for VectorToolSelector {
vector_db
.index_tools(new_tool_records)
.await
.map_err(|e| ToolError::ExecutionError(format!("Failed to index tools: {}", e)))?;
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to index tools: {}", e)),
data: None,
})?;
}
Ok(())
}
async fn remove_tool(&self, tool_name: &str) -> Result<(), ToolError> {
async fn remove_tool(&self, tool_name: &str) -> Result<(), ErrorData> {
let vector_db = self.vector_db.read().await;
vector_db.remove_tool(tool_name).await.map_err(|e| {
ToolError::ExecutionError(format!("Failed to remove tool {}: {}", tool_name, e))
})?;
vector_db
.remove_tool(tool_name)
.await
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to remove tool {}: {}", tool_name, e)),
data: None,
})?;
Ok(())
}
async fn record_tool_call(&self, tool_name: &str) -> Result<(), ToolError> {
async fn record_tool_call(&self, tool_name: &str) -> Result<(), ErrorData> {
let mut recent_calls = self.recent_tool_calls.write().await;
if recent_calls.len() >= 100 {
recent_calls.pop_front();
@@ -235,7 +263,7 @@ impl RouterToolSelector for VectorToolSelector {
Ok(())
}
async fn get_recent_tool_calls(&self, limit: usize) -> Result<Vec<String>, ToolError> {
async fn get_recent_tool_calls(&self, limit: usize) -> Result<Vec<String>, ErrorData> {
let recent_calls = self.recent_tool_calls.read().await;
Ok(recent_calls.iter().rev().take(limit).cloned().collect())
}
@@ -263,11 +291,15 @@ impl LLMToolSelector {
#[async_trait]
impl RouterToolSelector for LLMToolSelector {
async fn select_tools(&self, params: Value) -> Result<Vec<Content>, ToolError> {
async fn select_tools(&self, params: Value) -> Result<Vec<Content>, ErrorData> {
let query = params
.get("query")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidParameters("Missing 'query' parameter".to_string()))?;
.ok_or_else(|| ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: Cow::from("Missing 'query' parameter"),
data: None,
})?;
let extension_name = params
.get("extension_name")
@@ -297,8 +329,10 @@ impl RouterToolSelector for LLMToolSelector {
};
let user_prompt =
render_global_file("router_tool_selector.md", &context).map_err(|e| {
ToolError::ExecutionError(format!("Failed to render prompt template: {}", e))
render_global_file("router_tool_selector.md", &context).map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to render prompt template: {}", e)),
data: None,
})?;
let user_message = Message::user().with_text(&user_prompt);
@@ -306,7 +340,11 @@ impl RouterToolSelector for LLMToolSelector {
.llm_provider
.complete("", &[user_message], &[])
.await
.map_err(|e| ToolError::ExecutionError(format!("Failed to search tools: {}", e)))?;
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Failed to search tools: {}", e)),
data: None,
})?;
// Extract just the message content from the response
let (message, _usage) = response;
@@ -325,7 +363,7 @@ impl RouterToolSelector for LLMToolSelector {
}
}
async fn index_tools(&self, tools: &[Tool], extension_name: &str) -> Result<(), ToolError> {
async fn index_tools(&self, tools: &[Tool], extension_name: &str) -> Result<(), ErrorData> {
let mut tool_strings = self.tool_strings.write().await;
for tool in tools {
@@ -354,7 +392,7 @@ impl RouterToolSelector for LLMToolSelector {
Ok(())
}
async fn remove_tool(&self, tool_name: &str) -> Result<(), ToolError> {
async fn remove_tool(&self, tool_name: &str) -> Result<(), ErrorData> {
let mut tool_strings = self.tool_strings.write().await;
if let Some(extension_name) = tool_name.split("__").next() {
tool_strings.remove(extension_name);
@@ -362,7 +400,7 @@ impl RouterToolSelector for LLMToolSelector {
Ok(())
}
async fn record_tool_call(&self, tool_name: &str) -> Result<(), ToolError> {
async fn record_tool_call(&self, tool_name: &str) -> Result<(), ErrorData> {
let mut recent_calls = self.recent_tool_calls.write().await;
if recent_calls.len() >= 100 {
recent_calls.pop_front();
@@ -371,7 +409,7 @@ impl RouterToolSelector for LLMToolSelector {
Ok(())
}
async fn get_recent_tool_calls(&self, limit: usize) -> Result<Vec<String>, ToolError> {
async fn get_recent_tool_calls(&self, limit: usize) -> Result<Vec<String>, ErrorData> {
let recent_calls = self.recent_tool_calls.read().await;
Ok(recent_calls.iter().rev().take(limit).cloned().collect())
}
+189 -93
View File
@@ -6,8 +6,8 @@
use std::sync::Arc;
use chrono::Utc;
use mcp_core::{ToolError, ToolResult};
use rmcp::model::Content;
use mcp_core::ToolResult;
use rmcp::model::{Content, ErrorCode, ErrorData};
use crate::recipe::Recipe;
use crate::scheduler_trait::SchedulerTrait;
@@ -24,8 +24,10 @@ impl Agent {
let scheduler = match self.scheduler_service.lock().await.as_ref() {
Some(s) => s.clone(),
None => {
return Err(ToolError::ExecutionError(
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Scheduler not available. This tool only works in server mode.".to_string(),
None,
))
}
};
@@ -33,7 +35,13 @@ impl Agent {
let action = arguments
.get("action")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'action' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'action' parameter".to_string(),
None,
)
})?;
match action {
"list" => self.handle_list_jobs(scheduler).await,
@@ -46,10 +54,11 @@ impl Agent {
"inspect" => self.handle_inspect_job(scheduler, arguments).await,
"sessions" => self.handle_list_sessions(scheduler, arguments).await,
"session_content" => self.handle_session_content(arguments).await,
_ => Err(ToolError::ExecutionError(format!(
"Unknown action: {}",
action
))),
_ => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Unknown action: {}", action),
None,
)),
}
}
@@ -61,17 +70,22 @@ impl Agent {
match scheduler.list_scheduled_jobs().await {
Ok(jobs) => {
let jobs_json = serde_json::to_string_pretty(&jobs).map_err(|e| {
ToolError::ExecutionError(format!("Failed to serialize jobs: {}", e))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to serialize jobs: {}", e),
None,
)
})?;
Ok(vec![Content::text(format!(
"Scheduled Jobs:\n{}",
jobs_json
))])
}
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to list jobs: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to list jobs: {}", e),
None,
)),
}
}
@@ -85,14 +99,22 @@ impl Agent {
.get("recipe_path")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::ExecutionError("Missing 'recipe_path' parameter".to_string())
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'recipe_path' parameter".to_string(),
None,
)
})?;
let cron_expression = arguments
.get("cron_expression")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::ExecutionError("Missing 'cron_expression' parameter".to_string())
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'cron_expression' parameter".to_string(),
None,
)
})?;
// Get the execution_mode parameter, defaulting to "background" if not provided
@@ -103,18 +125,23 @@ impl Agent {
// Validate execution_mode is either "foreground" or "background"
if execution_mode != "foreground" && execution_mode != "background" {
return Err(ToolError::ExecutionError(format!(
"Invalid execution_mode: {}. Must be 'foreground' or 'background'",
execution_mode
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!(
"Invalid execution_mode: {}. Must be 'foreground' or 'background'",
execution_mode
),
None,
));
}
// Validate recipe file exists and is readable
if !std::path::Path::new(recipe_path).exists() {
return Err(ToolError::ExecutionError(format!(
"Recipe file not found: {}",
recipe_path
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Recipe file not found: {}", recipe_path),
None,
));
}
// Validate it's a valid recipe by trying to parse it
@@ -122,19 +149,28 @@ impl Agent {
Ok(content) => {
if recipe_path.ends_with(".json") {
serde_json::from_str::<Recipe>(&content).map_err(|e| {
ToolError::ExecutionError(format!("Invalid JSON recipe: {}", e))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Invalid JSON recipe: {}", e),
None,
)
})?;
} else {
serde_yaml::from_str::<Recipe>(&content).map_err(|e| {
ToolError::ExecutionError(format!("Invalid YAML recipe: {}", e))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Invalid YAML recipe: {}", e),
None,
)
})?;
}
}
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Cannot read recipe file: {}",
e
)))
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Cannot read recipe file: {}", e),
None,
))
}
}
@@ -158,10 +194,11 @@ impl Agent {
"Successfully created scheduled job '{}' for recipe '{}' with cron expression '{}' in {} mode",
job_id, recipe_path, cron_expression, execution_mode
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to create job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to create job: {}", e),
None,
)),
}
}
@@ -174,17 +211,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.run_now(job_id).await {
Ok(session_id) => Ok(vec![Content::text(format!(
"Successfully started job '{}'. Session ID: {}",
job_id, session_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to run job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to run job: {}", e),
None,
)),
}
}
@@ -197,17 +241,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.pause_schedule(job_id).await {
Ok(()) => Ok(vec![Content::text(format!(
"Successfully paused job '{}'",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to pause job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to pause job: {}", e),
None,
)),
}
}
@@ -220,17 +271,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.unpause_schedule(job_id).await {
Ok(()) => Ok(vec![Content::text(format!(
"Successfully unpaused job '{}'",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to unpause job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to unpause job: {}", e),
None,
)),
}
}
@@ -243,17 +301,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.remove_scheduled_job(job_id).await {
Ok(()) => Ok(vec![Content::text(format!(
"Successfully deleted job '{}'",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to delete job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to delete job: {}", e),
None,
)),
}
}
@@ -266,17 +331,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.kill_running_job(job_id).await {
Ok(()) => Ok(vec![Content::text(format!(
"Successfully killed running job '{}'",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to kill job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to kill job: {}", e),
None,
)),
}
}
@@ -289,7 +361,13 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.get_running_job_info(job_id).await {
Ok(Some((session_id, start_time))) => {
@@ -303,10 +381,11 @@ impl Agent {
"Job '{}' is not currently running",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to inspect job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to inspect job: {}", e),
None,
)),
}
}
@@ -319,7 +398,13 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
let limit = arguments
.get("limit")
@@ -353,10 +438,11 @@ impl Agent {
))])
}
}
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to list sessions: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to list sessions: {}", e),
None,
)),
}
}
@@ -369,7 +455,11 @@ impl Agent {
.get("session_id")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::ExecutionError("Missing 'session_id' parameter".to_string())
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'session_id' parameter".to_string(),
None,
)
})?;
// Get the session file path
@@ -378,29 +468,32 @@ impl Agent {
) {
Ok(path) => path,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Invalid session ID '{}': {}",
session_id, e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Invalid session ID '{}': {}", session_id, e),
None,
));
}
};
// Check if session file exists
if !session_path.exists() {
return Err(ToolError::ExecutionError(format!(
"Session '{}' not found",
session_id
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Session '{}' not found", session_id),
None,
));
}
// Read session metadata
let metadata = match crate::session::storage::read_metadata(&session_path) {
Ok(metadata) => metadata,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Failed to read session metadata: {}",
e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to read session metadata: {}", e),
None,
));
}
};
@@ -408,10 +501,11 @@ impl Agent {
let messages = match crate::session::storage::read_messages(&session_path) {
Ok(messages) => messages,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Failed to read session messages: {}",
e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to read session messages: {}", e),
None,
));
}
};
@@ -419,20 +513,22 @@ impl Agent {
let metadata_json = match serde_json::to_string_pretty(&metadata) {
Ok(json) => json,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Failed to serialize metadata: {}",
e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to serialize metadata: {}", e),
None,
));
}
};
let messages_json = match serde_json::to_string_pretty(&messages) {
Ok(json) => json,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Failed to serialize messages: {}",
e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to serialize messages: {}", e),
None,
));
}
};
+22 -10
View File
@@ -1,7 +1,7 @@
use mcp_core::ToolError;
use rmcp::model::Content;
use rmcp::model::Tool;
use rmcp::model::{Content, ErrorCode, ErrorData};
use serde_json::Value;
use std::borrow::Cow;
use std::collections::HashMap;
use crate::{
@@ -63,7 +63,11 @@ impl SubRecipeManager {
.await;
match result {
Ok(call_result) => ToolCallResult::from(Ok(call_result)),
Err(e) => ToolCallResult::from(Err(ToolError::ExecutionError(e.to_string()))),
Err(e) => ToolCallResult::from(Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(e.to_string()),
data: None,
})),
}
}
@@ -72,25 +76,33 @@ impl SubRecipeManager {
tool_name: &str,
params: Value,
tasks_manager: &TasksManager,
) -> Result<Vec<Content>, ToolError> {
) -> Result<Vec<Content>, ErrorData> {
let sub_recipe = self.sub_recipes.get(tool_name).ok_or_else(|| {
let sub_recipe_name = tool_name
.strip_prefix(SUB_RECIPE_TASK_TOOL_NAME_PREFIX)
.and_then(|s| s.strip_prefix("_"))
.ok_or_else(|| {
ToolError::InvalidParameters(format!(
.ok_or_else(|| ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: Cow::from(format!(
"Invalid sub-recipe tool name format: {}",
tool_name
))
)),
data: None,
})
.unwrap();
ToolError::InvalidParameters(format!("Sub-recipe '{}' not found", sub_recipe_name))
ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: Cow::from(format!("Sub-recipe '{}' not found", sub_recipe_name)),
data: None,
}
})?;
let output = create_sub_recipe_task(sub_recipe, params, tasks_manager)
.await
.map_err(|e| {
ToolError::ExecutionError(format!("Sub-recipe task createion failed: {}", e))
.map_err(|e| ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(format!("Sub-recipe task creation failed: {}", e)),
data: None,
})?;
Ok(vec![Content::text(output)])
}
+11 -3
View File
@@ -8,8 +8,8 @@ use crate::{
};
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use mcp_core::handler::ToolError;
use rmcp::model::Tool;
use rmcp::model::{ErrorCode, ErrorData};
use serde::{Deserialize, Serialize};
// use serde_json::{self};
use crate::conversation::message::{Message, MessageContent, ToolRequest};
@@ -206,7 +206,11 @@ impl SubAgent {
.await
{
Ok(result) => result.result.await,
Err(e) => Err(ToolError::ExecutionError(e.to_string())),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
e.to_string(),
None,
)),
};
match tool_result {
@@ -220,7 +224,11 @@ impl SubAgent {
// Create a user message with the tool error
let tool_error_message = Message::user().with_tool_response(
request.id.clone(),
Err(ToolError::ExecutionError(e.to_string())),
Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
e.to_string(),
None,
)),
);
messages.push(tool_error_message);
}
@@ -1,5 +1,6 @@
use mcp_core::ToolError;
use rmcp::model::{Content, ServerNotification, Tool, ToolAnnotations};
use std::borrow::Cow;
use rmcp::model::{Content, ErrorCode, ErrorData, ServerNotification, Tool, ToolAnnotations};
use serde_json::Value;
use crate::agents::subagent_task_config::TaskConfig;
@@ -90,7 +91,11 @@ pub async fn run_tasks(
let output = serde_json::to_string(&result).unwrap();
Ok(vec![Content::text(output)])
}
Err(e) => Err(ToolError::ExecutionError(e.to_string())),
Err(e) => Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(e.to_string()),
data: None,
}),
}
};
+8 -4
View File
@@ -1,7 +1,7 @@
use crate::agents::subagent::SubAgent;
use crate::agents::subagent_task_config::TaskConfig;
use anyhow::Result;
use mcp_core::ToolError;
use rmcp::model::{ErrorCode, ErrorData};
/// Standalone function to run a complete subagent task
pub async fn run_complete_subagent_task(
@@ -9,9 +9,13 @@ pub async fn run_complete_subagent_task(
task_config: TaskConfig,
) -> Result<String, anyhow::Error> {
// Create the subagent with the parent agent's provider
let subagent = SubAgent::new(task_config.clone())
.await
.map_err(|e| ToolError::ExecutionError(format!("Failed to create subagent: {}", e)))?;
let subagent = SubAgent::new(task_config.clone()).await.map_err(|e| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to create subagent: {}", e),
None,
)
})?;
// Execute the subagent task
let messages = subagent
+10 -8
View File
@@ -10,8 +10,7 @@ use crate::config::Config;
use crate::conversation::message::ToolRequest;
use crate::providers::base::Provider;
use anyhow::{anyhow, Result};
use mcp_core::ToolError;
use rmcp::model::Tool;
use rmcp::model::{ErrorCode, ErrorData, Tool};
use serde_json::Value;
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -52,18 +51,21 @@ impl ToolRouteManager {
pub async fn dispatch_route_search_tool(
&self,
arguments: Value,
) -> Result<ToolCallResult, ToolError> {
) -> Result<ToolCallResult, ErrorData> {
let selector = self.router_tool_selector.lock().await.clone();
match selector.as_ref() {
Some(selector) => match selector.select_tools(arguments).await {
Ok(tools) => Ok(ToolCallResult::from(Ok(tools))),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to select tools: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to select tools: {}", e),
None,
)),
},
None => Err(ToolError::ExecutionError(
None => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"No tool selector available".to_string(),
None,
)),
}
}