fix(llm): preserve structured tool result text across providers#97450
fix(llm): preserve structured tool result text across providers#97450snowzlmbot wants to merge 0 commit into
Conversation
|
@clawsweeper review-pr |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Codex review: needs real behavior proof before merge. Reviewed June 28, 2026, 8:04 PM ET / 00:04 UTC. Summary PR surface: Source +195, Tests +760. Total +955 across 18 files. Reproducibility: yes. Source inspection shows current provider converters still filter tool-result replay to explicit text blocks, and current main has focused tests for the empty non-image fallback that this PR head would regress if carried forward unchanged. Review metrics: 1 noteworthy metric.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Rebase or resolve conflicts onto current main, preserve #97423 no-output fallback, keep the shared structured serializer, attach redacted current-head provider replay proof, and leave the broader report open for remaining symptoms. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current provider converters still filter tool-result replay to explicit text blocks, and current main has focused tests for the empty non-image fallback that this PR head would regress if carried forward unchanged. Is this the best way to solve the issue? No as-is. The shared serializer is the right layer for the provider-serializer slice, but this head must preserve current main's empty non-image fallback and add real replay proof before it is the best fix. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 305fa9c4ddbe. Label changesLabel justifications:
Evidence reviewedPR surface: Source +195, Tests +760. Total +955 across 18 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Updated the PR body with the final problem statement and CI/evidence summary. @clawsweeper review-pr |
1587650 to
a5bc35b
Compare
|
Updated again after rebasing this PR branch onto the latest What changed in the pushed branch:
Current-head proof summary: @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Updated PR body and pushed provider/runtime sanitizer proof for current head 5ee5552: added authored What Problem This Solves / Evidence sections, shared bounded structured tool-result serialization, and affected OpenAI provider payload assertions for sensitive-field redaction, opaque/binary omission, circular handling, and truncation. @clawsweeper review-pr |
|
Current head c788cc7: updated PR Evidence with the committed affected-path replay proof script (scripts/proofs/provider-tool-result-replay-proof.ts), exact reproduction command, and edited OpenAI Chat Completions / OpenAI Responses replay-output excerpt showing retained structured text plus sensitive-field redaction, opaque/binary omission, data URI redaction, image exclusion, and final aggregate truncation before provider replay. @clawsweeper review-pr |
|
Current head c788cc7: refreshed PR Evidence after adding the replay proof artifact and clearing CI. The body now points to this head and includes the proof script path, reproduction command, edited OpenAI Chat Completions / OpenAI Responses replay-output excerpt, and broader #96857 out-of-scope/open wording. @clawsweeper review-pr |
e8eddd4 to
c788cc7
Compare
c788cc7 to
a344a56
Compare
|
Focused rollback cleanup for current head a344a56: the PR branch is restored to the high-quality provider serializer patch surface, and proof-script/workflow-only artifacts have been removed from the PR diff/body. The body now reflects that any additional dependency-backed replay transcript should be attached externally without expanding the code surface. I am waiting for the remaining checks/review state before requesting another review. |
|
One additional edge beyond the MIME-alias finding: the new provider replay helper only skips This matters for replay quality and the security boundary: |
|
Current head a344a56: PR diff/body have been cleaned back to the focused provider serializer patch surface. Workflow/proof-script-only artifacts are no longer in the diff/body; latest Real behavior proof and check-lint are successful for this head. Please re-review the clean patch state. @clawsweeper review-pr |
|
Current head 4a92599: media-only replay guard and MIME alias binary guard are now pushed and documented. The PR diff remains source/test-only (no workflow/proof-script artifacts), provider text replay skips image/image_url/audio, and mime_type is treated as a binary MIME alias. Please re-review this clean current head. @clawsweeper review-pr |
887e20f to
87db23e
Compare
|
Rebased this PR on the latest Highlights now covered in the PR body:
@clawsweeper review-pr |
|
🦞🧹 Reason: re-review requires an open issue or PR. |
Summary
Preserves structured non-image tool-result content in provider replay/model-visible payloads instead of dropping it into empty or image-placeholder-style output. This addresses the provider-serializer slice of #96857 across OpenAI Chat Completions, OpenAI Responses / transport replay, Anthropic Messages / transport replay, Google / Vertex, and Mistral conversion paths.
This PR intentionally does not claim to resolve the broader provider-failover, truncation, persistent session-degradation, or empty non-image result placeholder paths tracked separately in #96857 and related PRs.
What Problem This Solves
Fixes an issue where provider serializers only preserved explicit
type: "text"tool-result blocks when building model-visible replay context. Structured non-image results such as MCPresourceblocks, JSON status payloads, and runtime adapter output could be silently dropped before reaching the model, so the agent could receive an empty result or a placeholder instead of the actual structured output.The affected paths include OpenAI Chat Completions, OpenAI Responses / runtime transport replay, Anthropic Messages / runtime transport replay, Google / Vertex, and Mistral provider conversion.
Why This Change Was Made
The fix extracts a shared bounded structured tool-result serializer and uses it in provider replay plus the existing runtime visible-output structured fallback. That keeps the provider and runtime behavior aligned while ensuring structured payloads are safe before replay: sensitive fields are redacted, opaque/binary values are omitted, circular references are handled, and very large payloads are capped.
The provider extraction helper also applies a final aggregate cap after merging all text/structured chunks, so many individually bounded blocks cannot create an oversized model-visible provider payload.
User Impact
Agents can now read ordinary structured tool output in affected provider contexts instead of losing it during replay. Existing text blocks remain preferred, and image/audio payloads continue using the existing multimodal handling rather than being serialized as text.
Implementation
src/logging/tool-result-serialization.tsas the shared bounded structured tool-result serializer and exports the shared truncation helper used by provider aggregation.src/llm/providers/tool-result-text.tsbefore provider replay, with a final cap on the merged provider text.src/agents/embedded-agent-subscribe.tools.tsfor runtime structured-result visible output.image,image_url, andaudio) out of provider text replay so multimodal/media payloads do not become model-visible JSON text.media_type,mime_type, andmimeTypemetadata as media MIME aliases when deciding whether structureddatashould be omitted as binary.encrypted_content/encrypted_stdoutand binary fields such as blobs or mediadatapayloads.metadata:readyordata: is ordinary prose.CHANGELOG.md; release-owned changelog content is not changed by this PR.Evidence
MIME alias binary guard:
src/llm/providers/tool-result-text.test.tscovers snake-casemime_typeon structured resources and proves binarydatais omitted before provider replay.Media-only replay guard:
src/llm/providers/tool-result-text.test.tscoversimage,image_url, andaudioblocks together and proves only the structured non-media resource remains in provider text extraction.Code path proof — OpenAI Chat Completions provider payload:
src/llm/providers/openai-completions.test.tscaptures the actual Chat Completions request payload viaonPayloadand verifies structured tool output is replayed as tool text after sanitization. The assertions prove:messages[].contentfield.authorization,token, andpasswordvalues are redacted to***.application/octet-streamdata is replaced with[binary omitted: ... chars].encrypted_contentis replaced with[opaque data omitted: ... chars].[Circular].…(truncated)….Code path proof — OpenAI Responses provider conversion:
src/llm/providers/openai-responses-shared.test.tsexercisesconvertResponsesMessages()and verifies the actualfunction_call_output.outputpayload contains sanitized structured text instead of raw secrets/binary/opaque data.Affected runtime path alignment:
src/agents/embedded-agent-subscribe.tools.tsdelegates structured fallback serialization to the same shared serializer used by provider replay, preserving runtime visible-output semantics while sharing redaction, omission, circular handling, and per-block caps.Shared sanitizer and aggregate-cap proof:
src/llm/providers/tool-result-text.test.tscovers structured-only blocks, mixed text/structured/image blocks, ordinarydata:prose preservation, actual data URI token redaction, nested sensitive field redaction, opaque/binary omission, circular references, per-block length caps, shared-helper aggregate truncation, and many-block aggregate truncation after merge.External replay artifact status: If maintainers still require a dependency-backed terminal/log artifact before merge, it should be attached outside the patch diff and linked here without expanding the code surface.
Local lightweight validation on current head
4a92599d3ff07724e955627e594b5310be12c738:All listed lightweight checks passed locally. This checkout has no
node_modules, so focused Vitest/oxlint were not run locally; GitHub Actions remain the dependency-backed validation path for this PR.Labels considered
agents— affected behavior is model-visible agent/provider replay.P1— fixes loss of ordinary structured tool output in affected provider paths.merge-risk: 🚨 security-boundary— relevant because provider replay now serializes structured payloads, so redaction/omission/caps are part of the patch.merge-risk: 🚨 message-delivery— relevant because agent-visible tool output can affect message/tool workflows.Breaking changes
None.
Risk
Low/medium. This changes model-visible serialization for structured non-image tool-result blocks. Risk is bounded by preserving explicit text-first behavior, continuing to skip image/audio payloads for text extraction, and applying shared redaction/omission/circular/per-block/final-aggregate cap handling before provider replay.
Rollback
Revert this PR to restore provider serializers to the previous text-only extraction behavior. No migration, persistent state format change, dependency change, or configuration rollback is required.
Docs updated
No user-facing docs were changed.
CHANGELOG.mdis not part of this PR diff; release notes can be handled by the release process or squash/merge message.Related issue / PR
Not tested / Limitations
The broader Normal tool text outputs can degrade to “(see attached image)” placeholders in agent context #96857 provider-failover, session-corruption, truncation, and empty non-image placeholder paths are out of scope and remain open unless maintainers explicitly decide to consolidate them here.
No live external provider request was made for this update.
Local dependency-backed Vitest/oxlint were not run because this checkout does not have
node_modules.The broader Normal tool text outputs can degrade to “(see attached image)” placeholders in agent context #96857 provider-failover/session-corruption/truncation paths are not proven by this PR and should remain tracked separately unless maintainers decide this provider serializer slice is sufficient for part of the issue.