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
@@ -3,8 +3,8 @@ use crate::model::ModelConfig;
use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
use anyhow::{anyhow, Result};
use mcp_core::tool::ToolCall;
use rmcp::model::{Role, Tool};
use mcp_core::ToolCall;
use rmcp::model::{ErrorCode, ErrorData, Role, Tool};
use serde_json::{json, Value};
use std::collections::HashSet;
@@ -572,8 +572,10 @@ where
Ok(parsed) => parsed,
Err(_) => {
// If parsing fails, create an error tool request
let error = mcp_core::handler::ToolError::InvalidParameters(
format!("Could not parse tool arguments: {}", args)
let error = ErrorData::new(
ErrorCode::INVALID_PARAMS,
format!("Could not parse tool arguments: {}", args),
None,
);
let mut message = Message::new(
Role::Assistant,
@@ -985,7 +987,7 @@ mod tests {
#[test]
fn test_tool_error_handling_maintains_pairing() {
use crate::conversation::message::Message;
use mcp_core::handler::ToolError;
use rmcp::model::{ErrorCode, ErrorData};
let messages = vec![
Message::assistant().with_tool_request(
@@ -994,7 +996,11 @@ mod tests {
),
Message::user().with_tool_response(
"tool_1",
Err(ToolError::ExecutionError("Tool failed".to_string())),
Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Tool failed".to_string(),
None,
)),
),
];
@@ -1012,7 +1018,7 @@ mod tests {
assert_eq!(spec[1]["content"][0]["tool_use_id"], "tool_1");
assert_eq!(
spec[1]["content"][0]["content"],
"Error: Execution failed: Tool failed"
"Error: -32603: Tool failed"
);
assert_eq!(spec[1]["content"][0]["is_error"], true);
}
+13 -8
View File
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path;
@@ -6,8 +7,8 @@ use aws_sdk_bedrockruntime::types as bedrock;
use aws_smithy_types::{Document, Number};
use base64::Engine;
use chrono::Utc;
use mcp_core::{ToolCall, ToolError, ToolResult};
use rmcp::model::{Content, RawContent, ResourceContents, Role, Tool};
use mcp_core::{ToolCall, ToolResult};
use rmcp::model::{Content, ErrorCode, ErrorData, RawContent, ResourceContents, Role, Tool};
use serde_json::Value;
use super::super::base::Usage;
@@ -286,9 +287,11 @@ pub fn from_bedrock_content_block(block: &bedrock::ContentBlock) -> Result<Messa
bedrock::ContentBlock::ToolResult(tool_res) => MessageContent::tool_response(
tool_res.tool_use_id.to_string(),
if tool_res.content.is_empty() {
Err(ToolError::ExecutionError(
"Empty content for tool use from Bedrock".to_string(),
))
Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from("Empty content for tool use from Bedrock".to_string()),
data: None,
})
} else {
tool_res
.content
@@ -307,9 +310,11 @@ pub fn from_bedrock_tool_result_content_block(
Ok(match content {
bedrock::ToolResultContentBlock::Text(text) => Content::text(text.to_string()),
_ => {
return Err(ToolError::ExecutionError(
"Unsupported tool result from Bedrock".to_string(),
))
return Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from("Unsupported tool result from Bedrock".to_string()),
data: None,
})
}
})
}
@@ -5,10 +5,13 @@ use crate::providers::utils::{
sanitize_function_name, ImageFormat,
};
use anyhow::{anyhow, Error};
use mcp_core::{ToolCall, ToolError};
use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents, Role, Tool};
use mcp_core::ToolCall;
use rmcp::model::{
AnnotateAble, Content, ErrorCode, ErrorData, RawContent, ResourceContents, Role, Tool,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::borrow::Cow;
#[derive(Serialize)]
struct DatabricksMessage {
@@ -356,10 +359,14 @@ pub fn response_to_message(response: &Value) -> anyhow::Result<Message> {
};
if !is_valid_function_name(&function_name) {
let error = ToolError::NotFound(format!(
"The provided function name '{}' had invalid characters, it must match this regex [a-zA-Z0-9_-]+",
function_name
));
let error = ErrorData {
code: ErrorCode::INVALID_REQUEST,
message: Cow::from(format!(
"The provided function name '{}' had invalid characters, it must match this regex [a-zA-Z0-9_-]+",
function_name
)),
data: None,
};
content.push(MessageContent::tool_request(id, Err(error)));
} else {
match safely_parse_json(&arguments_str) {
@@ -370,10 +377,14 @@ pub fn response_to_message(response: &Value) -> anyhow::Result<Message> {
));
}
Err(e) => {
let error = ToolError::InvalidParameters(format!(
"Could not interpret tool use parameters for id {}: {}. Raw arguments: '{}'",
id, e, arguments_str
));
let error = ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: Cow::from(format!(
"Could not interpret tool use parameters for id {}: {}. Raw arguments: '{}'",
id, e, arguments_str
)),
data: None,
};
content.push(MessageContent::tool_request(id, Err(error)));
}
}
@@ -963,7 +974,11 @@ mod tests {
if let MessageContent::ToolRequest(request) = &message.content[0] {
match &request.tool_call {
Err(ToolError::NotFound(msg)) => {
Err(ErrorData {
code: ErrorCode::INVALID_REQUEST,
message: msg,
data: None,
}) => {
assert!(msg.starts_with("The provided function name"));
}
_ => panic!("Expected ToolNotFound error"),
@@ -985,7 +1000,11 @@ mod tests {
if let MessageContent::ToolRequest(request) = &message.content[0] {
match &request.tool_call {
Err(ToolError::InvalidParameters(msg)) => {
Err(ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: msg,
data: None,
}) => {
assert!(msg.starts_with("Could not interpret tool use parameters"));
}
_ => panic!("Expected InvalidParameters error"),
+19 -7
View File
@@ -3,9 +3,10 @@ use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
use crate::providers::utils::{is_valid_function_name, sanitize_function_name};
use anyhow::Result;
use mcp_core::tool::ToolCall;
use mcp_core::ToolCall;
use rand::{distributions::Alphanumeric, Rng};
use rmcp::model::{AnnotateAble, RawContent, Role, Tool};
use rmcp::model::{AnnotateAble, ErrorCode, ErrorData, RawContent, Role, Tool};
use std::borrow::Cow;
use crate::conversation::message::{Message, MessageContent};
use serde_json::{json, Map, Value};
@@ -254,10 +255,14 @@ pub fn response_to_message(response: Value) -> Result<Message> {
.unwrap_or_default()
.to_string();
if !is_valid_function_name(&name) {
let error = mcp_core::ToolError::NotFound(format!(
"The provided function name '{}' had invalid characters, it must match this regex [a-zA-Z0-9_-]+",
name
));
let error = ErrorData {
code: ErrorCode::INVALID_REQUEST,
message: Cow::from(format!(
"The provided function name '{}' had invalid characters, it must match this regex [a-zA-Z0-9_-]+",
name
)),
data: None,
};
content.push(MessageContent::tool_request(id, Err(error)));
} else {
let parameters = function_call.get("args");
@@ -741,7 +746,14 @@ mod tests {
assert_eq!(message.role, Role::Assistant);
assert_eq!(message.content.len(), 1);
if let Err(error) = &message.content[0].as_tool_request().unwrap().tool_call {
assert!(matches!(error, mcp_core::ToolError::NotFound(_)));
assert!(matches!(
error,
ErrorData {
code: ErrorCode::INVALID_REQUEST,
message: _,
data: None,
}
));
} else {
panic!("Expected tool request error");
}
+39 -16
View File
@@ -8,10 +8,13 @@ use crate::providers::utils::{
use anyhow::{anyhow, Error};
use async_stream::try_stream;
use futures::Stream;
use mcp_core::{ToolCall, ToolError};
use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents, Role, Tool};
use mcp_core::ToolCall;
use rmcp::model::{
AnnotateAble, Content, ErrorCode, ErrorData, RawContent, ResourceContents, Role, Tool,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::borrow::Cow;
use std::ops::Deref;
#[derive(Serialize, Deserialize, Debug)]
@@ -299,10 +302,14 @@ pub fn response_to_message(response: &Value) -> anyhow::Result<Message> {
};
if !is_valid_function_name(&function_name) {
let error = ToolError::NotFound(format!(
"The provided function name '{}' had invalid characters, it must match this regex [a-zA-Z0-9_-]+",
function_name
));
let error = ErrorData {
code: ErrorCode::INVALID_REQUEST,
message: Cow::from(format!(
"The provided function name '{}' had invalid characters, it must match this regex [a-zA-Z0-9_-]+",
function_name
)),
data: None,
};
content.push(MessageContent::tool_request(id, Err(error)));
} else {
match safely_parse_json(&arguments_str) {
@@ -313,10 +320,14 @@ pub fn response_to_message(response: &Value) -> anyhow::Result<Message> {
));
}
Err(e) => {
let error = ToolError::InvalidParameters(format!(
"Could not interpret tool use parameters for id {}: {}. Raw arguments: '{}'",
id, e, arguments_str
));
let error = ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: Cow::from(format!(
"Could not interpret tool use parameters for id {}: {}. Raw arguments: '{}'",
id, e, arguments_str
)),
data: None,
};
content.push(MessageContent::tool_request(id, Err(error)));
}
}
@@ -508,10 +519,14 @@ where
)
},
Err(e) => {
let error = ToolError::InvalidParameters(format!(
"Could not interpret tool use parameters for id {}: {}",
id, e
));
let error = ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: Cow::from(format!(
"Could not interpret tool use parameters for id {}: {}",
id, e
)),
data: None,
};
MessageContent::tool_request(id.clone(), Err(error))
}
};
@@ -991,7 +1006,11 @@ mod tests {
if let MessageContent::ToolRequest(request) = &message.content[0] {
match &request.tool_call {
Err(ToolError::NotFound(msg)) => {
Err(ErrorData {
code: ErrorCode::INVALID_REQUEST,
message: msg,
data: None,
}) => {
assert!(msg.starts_with("The provided function name"));
}
_ => panic!("Expected ToolNotFound error"),
@@ -1013,7 +1032,11 @@ mod tests {
if let MessageContent::ToolRequest(request) = &message.content[0] {
match &request.tool_call {
Err(ToolError::InvalidParameters(msg)) => {
Err(ErrorData {
code: ErrorCode::INVALID_PARAMS,
message: msg,
data: None,
}) => {
assert!(msg.starts_with("Could not interpret tool use parameters"));
}
_ => panic!("Expected InvalidParameters error"),