Fix empty tool results from resource content (e.g. auto visualiser) (#7866)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2026-03-18 14:53:48 -04:00
committed by GitHub
parent 8d72344602
commit 0cd41bebbd
4 changed files with 232 additions and 11 deletions
@@ -1,4 +1,5 @@
use crate::conversation::message::{Message, MessageContent};
use crate::mcp_utils::extract_text_from_resource;
use crate::model::ModelConfig;
use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
@@ -142,7 +143,18 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
let text = result
.content
.iter()
.filter_map(|c| c.as_text().map(|t| t.text.clone()))
.filter_map(|c| {
if let Some(t) = c.as_text() {
return Some(t.text.clone());
}
if let Some(r) = c.as_resource() {
let text = extract_text_from_resource(&r.resource);
if !text.is_empty() {
return Some(text);
}
}
None
})
.collect::<Vec<_>>()
.join("\n");
@@ -1170,6 +1182,70 @@ mod tests {
assert_eq!(assistant_content[0]["type"], "tool_use");
}
#[test]
fn test_tool_response_with_resource_content() {
use rmcp::model::{CallToolResult, Content};
let resource_content = Content::embedded_text(
"file:///test/file.txt",
"This is the file content from a resource",
);
let messages = vec![
Message::assistant().with_tool_request(
"tool_1",
Ok(CallToolRequestParams::new("view_file")
.with_arguments(object!({"path": "/test/file.txt"}))),
),
Message::user().with_tool_response(
"tool_1",
Ok(CallToolResult::success(vec![resource_content])),
),
];
let spec = format_messages(&messages);
assert_eq!(spec.len(), 2);
assert_eq!(spec[1]["role"], "user");
assert_eq!(spec[1]["content"][0]["type"], "tool_result");
assert_eq!(spec[1]["content"][0]["tool_use_id"], "tool_1");
assert_eq!(
spec[1]["content"][0]["content"],
"This is the file content from a resource"
);
}
#[test]
fn test_tool_response_with_mixed_content() {
use rmcp::model::{CallToolResult, Content};
let text_content = Content::text("Summary: file loaded");
let resource_content = Content::embedded_text("file:///test/file.txt", "File content here");
let messages = vec![
Message::assistant().with_tool_request(
"tool_1",
Ok(CallToolRequestParams::new("view_file")
.with_arguments(object!({"path": "/test/file.txt"}))),
),
Message::user().with_tool_response(
"tool_1",
Ok(CallToolResult::success(vec![
text_content,
resource_content,
])),
),
];
let spec = format_messages(&messages);
assert_eq!(spec[1]["content"][0]["type"], "tool_result");
assert_eq!(
spec[1]["content"][0]["content"],
"Summary: file loaded\nFile content here"
);
}
fn cfg(name: &str) -> ModelConfig {
ModelConfig {
model_name: name.to_string(),
@@ -1,4 +1,5 @@
use crate::conversation::message::{Message, MessageContent};
use crate::mcp_utils::extract_text_from_resource;
use crate::model::ModelConfig;
use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
@@ -38,7 +39,18 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
let text = result
.content
.iter()
.filter_map(|c| c.as_text().map(|t| t.text.clone()))
.filter_map(|c| {
if let Some(t) = c.as_text() {
return Some(t.text.clone());
}
if let Some(r) = c.as_resource() {
let text = extract_text_from_resource(&r.resource);
if !text.is_empty() {
return Some(text);
}
}
None
})
.collect::<Vec<_>>()
.join("\n");