feat!: relax tool result structuredContent type (#919)

This commit is contained in:
Dale Seo 2026-06-26 14:57:18 -04:00 committed by GitHub
parent 415852806d
commit b8a936c4f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 38 additions and 30 deletions

View file

@ -10,7 +10,7 @@
// ToolUseContent/ToolResultContent are SEP-2577-deprecated; internal references are expected. // ToolUseContent/ToolResultContent are SEP-2577-deprecated; internal references are expected.
#![expect(deprecated)] #![expect(deprecated)]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::{Value, json};
use super::{Annotations, Meta, resource::ResourceContents}; use super::{Annotations, Meta, resource::ResourceContents};
@ -207,7 +207,7 @@ pub struct ToolResultContent {
pub tool_use_id: String, pub tool_use_id: String,
pub content: Vec<ContentBlock>, pub content: Vec<ContentBlock>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub structured_content: Option<super::JsonObject>, pub structured_content: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>, pub is_error: Option<bool>,
} }

View file

@ -2352,13 +2352,7 @@
"null" "null"
] ]
}, },
"structuredContent": { "structuredContent": true,
"type": [
"object",
"null"
],
"additionalProperties": true
},
"toolUseId": { "toolUseId": {
"type": "string" "type": "string"
} }

View file

@ -2352,13 +2352,7 @@
"null" "null"
] ]
}, },
"structuredContent": { "structuredContent": true,
"type": [
"object",
"null"
],
"additionalProperties": true
},
"toolUseId": { "toolUseId": {
"type": "string" "type": "string"
} }

View file

@ -3454,13 +3454,7 @@
"null" "null"
] ]
}, },
"structuredContent": { "structuredContent": true,
"type": [
"object",
"null"
],
"additionalProperties": true
},
"toolUseId": { "toolUseId": {
"type": "string" "type": "string"
} }

View file

@ -3454,13 +3454,7 @@
"null" "null"
] ]
}, },
"structuredContent": { "structuredContent": true,
"type": [
"object",
"null"
],
"additionalProperties": true
},
"toolUseId": { "toolUseId": {
"type": "string" "type": "string"
} }

View file

@ -370,6 +370,38 @@ fn test_tool_result_content_requires_content() {
assert!(err.to_string().contains("missing field `content`")); assert!(err.to_string().contains("missing field `content`"));
} }
#[tokio::test]
async fn test_tool_result_content_with_array_structured_content() -> Result<()> {
let structured =
serde_json::json!([{ "city": "SF", "temp": 72 }, { "city": "NY", "temp": 65 }]);
let mut tool_result = ToolResultContent::new("call_123", vec![ContentBlock::text("forecast")]);
tool_result.structured_content = Some(structured);
let json = serde_json::to_string(&tool_result)?;
let deserialized: ToolResultContent = serde_json::from_str(&json)?;
assert_eq!(tool_result, deserialized);
assert!(deserialized.structured_content.unwrap().is_array());
Ok(())
}
#[tokio::test]
async fn test_tool_result_content_with_primitive_structured_content() -> Result<()> {
let structured = serde_json::json!(42);
let mut tool_result = ToolResultContent::new("call_123", vec![ContentBlock::text("count")]);
tool_result.structured_content = Some(structured);
let json = serde_json::to_string(&tool_result)?;
let deserialized: ToolResultContent = serde_json::from_str(&json)?;
assert_eq!(tool_result, deserialized);
assert!(matches!(
deserialized.structured_content,
Some(serde_json::Value::Number(_))
));
Ok(())
}
#[tokio::test] #[tokio::test]
async fn test_sampling_message_with_tool_use() -> Result<()> { async fn test_sampling_message_with_tool_use() -> Result<()> {
let message = SamplingMessage::assistant_tool_use( let message = SamplingMessage::assistant_tool_use(