Skip to content

bug: tool-result media 400s whole request for models without attachment capability #41163

Description

@Qiiks

Problem

supportsMediaInToolResult in message-v2.ts returns true unconditionally for @ai-sdk/anthropic and @ai-sdk/openai npm packages. For models with attachment: false (no vision — e.g. GLM-5.2 served through an Anthropic-compatible endpoint), image/PDF attachments from tool results stay inside the tool result and are re-sent on every subsequent turn as part of history replay. The provider then rejects the entire request with a 400 (e.g. Model ... does not appear to support image inputs.), killing the response.

User-visible symptom: a session where a tool returned a screenshot works fine until a later turn — then every response dies with Bad Request: Model ... does not appear to support image inputs., even when the user sends only text.

Why this is a regression: with @ai-sdk/openai-compatible, media was extracted to a user message where unsupportedParts() converted it to a graceful text note ("ERROR: Cannot read image ... Inform the user.") and the model replied normally.

Expected behavior

Media from tool results should be extracted to a user message (degraded gracefully by unsupportedParts()) when the model's attachment capability is false — regardless of the SDK npm package.

Environment

  • opencode version: 1.18.14+ (reproducible on dev)
  • Model: any with attachment: false served via @ai-sdk/anthropic or @ai-sdk/openai

Activity

github-actions commented on Aug 8, 2026

@github-actions
Contributor

This issue might be a duplicate of existing issues. Please check:

If these issues are distinct (e.g. the root cause or fix path differs), feel free to clarify and we can keep both open.

eenlars commented on Aug 8, 2026

@eenlars

Not a duplicate of #34113 in my case, and I hit this today on a different provider.

Setup: opencode 1.18.15, provider kimi-for-coding (https://api.kimi.com/coding/v1, npm @ai-sdk/anthropic), model k3, which models.dev lists as attachment: false.

What happens: read on a PDF stores the file as state.attachments[0].url = data:application/pdf;base64,... inside the tool part (4.9 MB in my case). The tool output itself is just PDF read successfully. From the next turn on, every request 400s at step 0:

level=ERROR message="stream error" providerID=kimi-for-coding modelID=k3 error.error="AI_APICallError: Invalid request Error"

The session is then permanently dead. Retry, continue, a text-only prompt, all fail in about two seconds. Four sessions died this way before I traced it.

Isolated it with curl against the same endpoint and key:

  • text only: 200
  • 1x1 PNG as an image block: 200
  • 200 byte PDF as a document block: 400, {"error":{"type":"invalid_request_error","message":"Invalid request Error"},"type":"error"}

So it is not size, not the context limit, and not the subscription tier. The endpoint rejects document blocks outright, and a single PDF read poisons the history for good.

One caveat on the proposed fix: gating purely on attachment: false would also strip images that k3 does accept, per the PNG probe above. The check likely needs to be per format, input.pdf vs input.image, which is what #21908 was getting at.

Recovery for anyone stuck: the payload sits in ~/.local/share/opencode/opencode.db, table part, at json_extract(data,'$.state.attachments'). Find the row with data like '%data:application/pdf;base64%', remove that key, and the session streams again. I replaced it with pdftotext -layout output so the session kept the document content instead of losing it.

vokasug commented on Aug 29, 2026

@vokasug

Confirmed on latest release 1.18.25 (2026-08-28), provider kimi-for-coding, model k3 (attachment: false via models.dev).

Repro: agent generated a 608 KB PDF via headless Chrome, then called read on it. Tool output was just "PDF read successfully", but the tool part stored attachments[0].url = data:application/pdf;base64,... From the next turn on, every request fails with AI_APICallError 400 "Invalid request Error".

Beyond the capability-check bug itself, the recovery UX is a dead end:

  • retry / "continue" re-send the poisoned history → same 400;
  • /compact also requires a model call with the same history → same 400;
  • UI revert only offers user messages as restore points, so a mid-session tool part cannot be removed without reverting the entire session.

The only recovery was manual surgery on opencode.db: deleting the read tool-part and the trailing error messages. Consider (a) degrading tool-result media to a text note for attachment:false models, and (b) a way to drop a poisonous part without a model call (e.g. "retry without this attachment").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions