Preserve MCP tool output as content items (#40737)

## What changed

- Convert unstructured MCP results into typed function-call output items instead of serializing the entire content array as a text string.
- Keep structured MCP results as serialized text and preserve media, encrypted content, and unknown content through their existing item conversions.
- Drop empty text items during output truncation so they do not consume API array slots.

## Testing

- Cover text-only, mixed unstructured, structured, and image-sanitized MCP results.
- Verify empty text items are discarded under byte- and token-based truncation policies.

GitOrigin-RevId: fa1c5b7dee6f003a094265379dcabdd060386a48
This commit is contained in:
pakrym-oai authored and copyberry committed 2026-08-25 23:42:50 +00:00
1 parent 62aacbb2c9
commit 75cb7c903d
12 files changed
+288 -104

No files matched your search

@@ -907,20 +907,19 @@ impl ElicitationRoundTripFixture {
function_call_output.get("call_id"),
Some(&Value::String(call_id.to_string()))
);
let output = function_call_output
.get("output")
.and_then(Value::as_str)
.expect("function_call_output output should be a JSON string");
let payload = assert_regex_match(
r#"(?s)^Wall time: [0-9]+(?:\.[0-9]+)? seconds\nOutput:\n(.*)$"#,
output,
)
.get(1)
.expect("wall-time wrapped output should include payload")
.as_str();
let header = function_call_output["output"][0]["text"]
.as_str()
.expect("first content item should contain the wall-time header");
assert_regex_match(
r#"^Wall time: [0-9]+(?:\.[0-9]+)? seconds\nOutput:$"#,
header,
);
assert_eq!(
serde_json::from_str::<Value>(payload)?,
json!([{ "type": "text", "text": expected_text }])
function_call_output["output"],
json!([
{ "type": "input_text", "text": header },
{ "type": "input_text", "text": expected_text },
])
);
if matches!(self.scenario, ElicitationScenario::Strict(Review::Approved)) && !self.next_turn