fix old sessions with tool results not loading (#6094)

This commit is contained in:
Zane
2025-12-12 13:25:53 -08:00
committed by GitHub
parent 8bf18b1638
commit 7455ed576b
2 changed files with 160 additions and 3 deletions
+84 -1
View File
@@ -86,7 +86,7 @@ impl ToolRequest {
#[derive(ToSchema)]
pub struct ToolResponse {
pub id: String,
#[serde(with = "tool_result_serde")]
#[serde(with = "tool_result_serde::call_tool_result")]
#[schema(value_type = Object)]
pub tool_result: ToolResult<CallToolResult>,
}
@@ -1303,4 +1303,87 @@ mod tests {
assert!(metadata.user_visible);
assert!(metadata.agent_visible);
}
#[test]
fn test_legacy_tool_response_deserialization() {
let legacy_json = r#"{
"role": "user",
"created": 1640995200,
"content": [{
"type": "toolResponse",
"id": "tool123",
"toolResult": {
"status": "success",
"value": [
{
"type": "text",
"text": "Tool output text"
}
]
}
}],
"metadata": { "agentVisible": true, "userVisible": true }
}"#;
let message: Message = serde_json::from_str(legacy_json).unwrap();
assert_eq!(message.content.len(), 1);
if let MessageContent::ToolResponse(response) = &message.content[0] {
assert_eq!(response.id, "tool123");
if let Ok(result) = &response.tool_result {
assert_eq!(result.content.len(), 1);
assert_eq!(
result.content[0].as_text().unwrap().text,
"Tool output text"
);
} else {
panic!("Expected successful tool result");
}
} else {
panic!("Expected ToolResponse content");
}
}
#[test]
fn test_new_tool_response_deserialization() {
let new_json = r#"{
"role": "user",
"created": 1640995200,
"content": [{
"type": "toolResponse",
"id": "tool456",
"toolResult": {
"status": "success",
"value": {
"content": [
{
"type": "text",
"text": "New format output"
}
],
"isError": false
}
}
}],
"metadata": { "agentVisible": true, "userVisible": true }
}"#;
let message: Message = serde_json::from_str(new_json).unwrap();
assert_eq!(message.content.len(), 1);
if let MessageContent::ToolResponse(response) = &message.content[0] {
assert_eq!(response.id, "tool456");
if let Ok(result) = &response.tool_result {
assert_eq!(result.content.len(), 1);
assert_eq!(
result.content[0].as_text().unwrap().text,
"New format output"
);
} else {
panic!("Expected successful tool result");
}
} else {
panic!("Expected ToolResponse content");
}
}
}
@@ -25,13 +25,11 @@ where
}
}
// For deserialization, let's use a simpler approach that works with the format we're serializing to
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<ToolResult<T>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
// Define a helper enum to handle the two possible formats
#[derive(Deserialize)]
#[serde(untagged)]
enum ResultFormat<T> {
@@ -68,3 +66,79 @@ where
}
}
}
pub mod call_tool_result {
use super::*;
use rmcp::model::{CallToolResult, Content};
pub fn serialize<S>(
value: &ToolResult<CallToolResult>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
super::serialize(value, serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<ToolResult<CallToolResult>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum ResultFormat {
NewSuccess {
status: String,
value: CallToolResult,
},
LegacySuccess {
status: String,
value: Vec<Content>,
},
Error {
status: String,
error: String,
},
}
let format = ResultFormat::deserialize(deserializer)?;
match format {
ResultFormat::NewSuccess { status, value } => {
if status == "success" {
Ok(Ok(value))
} else {
Err(serde::de::Error::custom(format!(
"Expected status 'success', got '{}'",
status
)))
}
}
ResultFormat::LegacySuccess { status, value } => {
if status == "success" {
Ok(Ok(CallToolResult::success(value)))
} else {
Err(serde::de::Error::custom(format!(
"Expected status 'success', got '{}'",
status
)))
}
}
ResultFormat::Error { status, error } => {
if status == "error" {
Ok(Err(ErrorData {
code: ErrorCode::INTERNAL_ERROR,
message: Cow::from(error),
data: None,
}))
} else {
Err(serde::de::Error::custom(format!(
"Expected status 'error', got '{}'",
status
)))
}
}
}
}
}