Skip to content

fix(llm): emit structured image blocks for tool-result media in Anthropic Messages#28755

Merged
kitlangton merged 2 commits into
anomalyco:devfrom
kitlangton:fix/anthropic-messages-tool-image
May 22, 2026
Merged

fix(llm): emit structured image blocks for tool-result media in Anthropic Messages#28755
kitlangton merged 2 commits into
anomalyco:devfrom
kitlangton:fix/anthropic-messages-tool-image

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

@kitlangton kitlangton commented May 22, 2026

Issue for this PR

Closes #28861

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Anthropic Messages was lowering all tool results through toolResultText. For content-typed tool results, that JSON-stringified the entire content array, including base64 image media, into tool_result.content.

This PR widens the Anthropic request schema so tool_result.content can be either a string or an array of structured text/image blocks. Image tool-result media is now emitted as Anthropic-native image blocks, while text/json/error tool results keep the existing string behavior. Unsupported non-image tool-result media now returns a clear LLMError.

It also adds protocol-level regression tests and a recorded golden scenario for a real image returned from a tool result.

How did you verify your code works?

  • packages/llm: bun run test passed with 209 pass, 28 skip
  • root bun run typecheck passed with 15 successful tasks
  • recorded replay for anthropic-opus-4-7-image-tool-result passed

Screenshots / recordings

Not applicable. This is an LLM protocol wire-shape fix.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

…opic Messages

The Anthropic Messages protocol lowered every tool-result into a single
string via toolResultText, which for content-typed results
(`{ type: 'content', value: [text, media] }`) JSON-stringified the entire
array — including multi-megabyte base64 image data URLs — into the
`tool_result.content` field. The wire request still parsed, but the
giant string silently consumed the context window: in one recorded
session a single screenshot read pushed a later turn over Claude's 1M
token limit with `prompt is too long: 1033591 tokens > 1000000`.

Widen `tool_result.content` in the body schema to match the real API
shape (`string | (TextBlock | ImageBlock)[]`) and add a media-aware
lowering helper that:

- emits Anthropic-native `image` blocks for image media in tool results
- keeps the legacy string path for text / json / error results so existing
  cassettes and provider expectations are unchanged
- raises a clear LLMError for unsupported tool-result media types (e.g.
  audio) instead of silently encoding them

Adds three protocol-level reproducer tests for the lowering and a
RECORD-gated golden scenario (`image-tool-result`) shared with the
sibling OpenAI Responses fix so the next end-to-end refresh covers both
providers.
@github-actions github-actions Bot added contributor needs:compliance This means the issue will auto-close after 2 hours. labels May 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions
Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions
Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Based on my search results, I found one related PR that may be relevant:

Related PR (not a duplicate):

This is a sibling PR that addresses the same underlying issue but for OpenAI Responses instead of Anthropic Messages. Per your PR description, you mentioned "A sibling fix lives on fix/openai-responses-tool-image" and these two PRs are meant to complement each other in fixing tool-result media handling across different LLM providers.

These are separate fixes for different providers (not duplicates), but they're part of the same feature initiative to handle image blocks in tool results properly.

No duplicate PRs found.

@github-actions
Copy link
Copy Markdown
Contributor

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.

Feel free to open a new pull request that follows our guidelines.

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label May 22, 2026
@github-actions github-actions Bot closed this May 22, 2026
@kitlangton kitlangton reopened this May 22, 2026
@kitlangton kitlangton merged commit 9db90a0 into anomalyco:dev May 22, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anthropic Messages JSON-stringifies image media returned from tool results

1 participant