Repository navigation
Conversation
…blocks separately When a turn issued multiple tool_use blocks and tool results carried image blocks (read_file on images, screenshots, feedback images), presentAssistantMessage pushed each tool_result followed immediately by its images, yielding tool_result/image interleaving in userMessageContent. Task.ts then consumed the array verbatim, and the Anthropic API rejected the request with a 400: "tool_use ids were found without tool_result blocks immediately after" (Zoo-Code-Org#1307). pushToolResultToUserContent is the single choke point every tool_result push flows through, so it now maintains the invariant: when a new tool_result is appended, image blocks already accumulated are moved after it. The resulting user message is tool_results first (in tool_use order), then images, then other blocks — matching the shape the issue expects. A single tool result with images and image-free messages are unchanged.
…er-tool-results Textually clean merge; no conflict resolution was required.
…nterleave Addresses edelauna's two review asks on Zoo-Code-Org#1705. 1. presentAssistantMessage-images.spec.ts bound a hand-copied partition of Task.pushToolResultToUserContent, so the Zoo-Code-Org#1307 tests were tautological: deleting the grouping from Task.ts still passed them. The mock now binds the real Task.prototype.pushToolResultToUserContent via vi.importActual. Verified: removing the partition from Task.ts fails both Zoo-Code-Org#1307 tests. 2. Task.spec.ts only covered "images after the first result". Added the mixed interleave [image, text, r1, text, r2] -> [r1, r2, image, text, text2], which fails under an image-only grouping and only passes with the full tool_result-first partition. Also carries main's new policySnapshot argument at the 2 integration call sites, and drops this spec's no-explicit-any suppressions from 16 to 14.
📝 Summary
Merge Risk:
|
Review statusThanks for contributing. This comment tracks the review sequence and the next action. Current step: Awaiting fresh human maintainer or CODEOWNER approval. Automated review is complete for the latest commit but does not replace human approval. Review-state labels are managed by this workflow; do not edit them manually. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…er-tool-results
Supersedes #1705 (stale-bot closed; conflicts now resolved vs current main)
Related GitHub Issue
Closes: #1307
Description
When a turn executes multiple
tool_useblocks and at least one tool returns images,userMessageContentended up astool_result, image, tool_result, image. Anthropic rejects that user message with a hard 400 (tool_use ids were found without tool_result blocks immediately after) becausetool_resultblocks must be the first, contiguous content of the user message. Both consumers (Task.flushPendingToolResultsToHistoryand the main request assembly) passed the array through verbatim.Fix at the single choke point every
tool_resultpush flows through (Task.pushToolResultToUserContent, covering all 8 call sites: the main tool-result callback, MCP-native dispatch, rejection/validation-error paths, and the task-interruption pre-injection path): after appending a newtool_result, any image blocks already inuserMessageContentare relocated after it. Result shape:[trA, trB, imgA, imgB, text]— Anthropic accepts non-tool_resultcontent after the contiguous result run, so this is spec-compliant and matches the issue's expected shape.Why not at the image-push sites (as the issue suggested): if tool A returns images and a later tool result has no images,
[trA, imgA, trB]still interleaves — grouping at the result push is the only complete minimal fix, and it keeps the consumer untouched.Behavior preserved: multiple images keep per-tool order; single-tool-with-images and image-free turns are byte-identical; images with no tool results are never relocated.
Test Procedure
src/core/task/__tests__/Task.spec.ts(+1 regression test, 1 test updated to the new correct order with full block equality — stricter, not weaker):tr1 + 2 images + image-less tr2→ asserts[tr1, tr2, img1, img2].src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts(+2 integration tests through the realpresentAssistantMessage): two image-returning tools →[trA, trB, imgA, imgB]; first tool returns images, second returns none → images relocated after the second result. Both fail onmain.cd src && ./node_modules/.bin/vitest run core/assistant-message→ 67/67 passed.cd src && ./node_modules/.bin/vitest run core/task/__tests__/Task.spec.ts→ 151/151 passed.Pre-Submission Checklist
Visual Snapshots
N/A.
Videos (interaction / animation only)
N/A.
Documentation Updates
Additional Notes
Pre-existing follow-up (out of scope): a repetition-limit feedback text block can still land between tool results (
presentAssistantMessage.ts~684); this PR improves that case too (wastr, text, img, trbefore). Happy to address separately if it ever manifests.Get in Touch
GitHub: @myk1yt — please tag me here; I monitor notifications.