feat: ToolError migration to ErrorData (#4051)
This commit is contained in:
@@ -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,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(¶ms, "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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(¶ms);
|
||||
|
||||
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;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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)])
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 +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,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user