Repository navigation
Description
Description
Description
When a session contains a tool result from the read tool executed on a PDF file, replaying that session against an OpenAI-compatible provider (in my case opencode-go / GLM-5.3-Flash via the zen/go gateway) fails deterministically with a 422 validation error from the gateway. The session becomes permanently stuck: every subsequent message fails until the PDF attachment is manually removed from the local database.
Environment
- opencode version: 1.18.22
- Provider:
opencode-go(@ai-sdk/openai-compatible, APIhttps://opencode.ai/zen/go/v1) - Model:
glm-5.3-flash - OS: Windows 10
Steps to reproduce
- Start a session with a model served through the
opencode-goprovider. - Have the agent run the
readtool on a PDF file (e.g.read C:\...\report.pdf). The tool stores its result withstate.attachments = [{ type: "file", mime: "application/pdf", url: "data:application/pdf;base64,..." }]. - Send any follow-up message so the full history is replayed to the provider.
Actual result
The request fails with HTTP 422 and the session is bricked (retries produce the same error):
APIError: Error from provider (Console Go): Upstream request failed: invalid_request_error Input should be a valid string
statusCode: 422
responseBody: {"error":{"param":"messages.297.user.content.str","type":"invalid_request_error","message":"Error from provider (Console Go): Upstream request failed: invalid_request_error Input should be a valid string"}}
url: https://opencode.ai/zen/go/v1/chat/completions
messages.297 corresponds exactly to the synthetic user message described below. The gateway only accepts content: string for user role messages, but the history contains a user message whose content is an array of blocks including a file block with the base64 PDF.
Root cause analysis (verified against source)
The chain is:
packages/opencode/src/tool/read.ts:306-324—readreturns PDFs as base64 attachments ("PDF read successfully"+state.attachments).packages/opencode/src/session/message-v2.ts:147-159—supportsMediaInToolResult()returnsfalsefor npm@ai-sdk/openai-compatible, so the PDF is extracted from the tool result (lines 296-313) and re-injected as a synthetic user message containing[text part, file part](lines 380-399).convertToModelMessages()produces a userModelMessagewhosecontentis an array of blocks.packages/opencode/src/provider/transform.ts:409-445—unsupportedParts()does not save the case:- The model declares
pdfin its input modalities on models.dev (verified:glm-5.3-flash→modalities.input: ["text","image","video","pdf"]), so thefileblock passes through untouched. - Even when a block is degraded to text, the message
contentremains an array;@ai-sdk/openai-compatibleonly collapses arrays to a plain string when there is exactly onetextblock. The synthetic message always has ≥2 parts.
- The model declares
So the extraction workaround assumes the provider accepts media in user messages as content arrays — true for Anthropic/Google/xAI SDKs, but not for strict OpenAI-compatible gateways like zen/go that only accept content: string on user messages. Result: any session with a prior PDF read fails forever on these providers, regardless of the model's declared capabilities.
Note: the experimental native runtime path fails earlier and client-side with "OpenAI Chat does not support media type application/pdf" (packages/llm/src/protocols/openai-chat.ts + shared.ts, IMAGE_MIMES doesn't include PDF) — same underlying gap.
Workaround applied (for other users hitting this)
With opencode closed, remove the PDF attachment from the stored tool part so the history replays as plain string content:
-- locate the offending part (a tool part with attachments mime application/pdf)
-- then rewrite its data JSON setting state.attachments = []After clearing state.attachments for that single part, the session works again.
Suggested fixes (maintainer's choice)
- In
message-v2.ts(extraction logic): treat@ai-sdk/openai-compatibleas supporting no media anywhere — do not create the synthetic user message withfileparts at all. Instead append a textual marker to the tool output, e.g.[Attached application/pdf: report.pdf](this pattern already exists incompaction.ts:73-78and in thestripMediapath). - In
transform.ts(unsupportedParts), defense-in-depth: after filtering, collapse any user message whose content array is now all-textblocks back into a plaincontent: string(joined with\n\n). This fixes the "everything degraded to text but still an array" case for every strict OpenAI-compatible gateway. - Optionally move the
model.capabilities.input[modality]check into the extraction step inmessage-v2.tsso unsupported media is never extracted into a synthetic message that must be degraded later.
Happy to provide the full DB row structure, logs, or test repro if useful. Thank you!
Plugins
No response
OpenCode version
1.18.22
Screenshot and/or share link
No response
Operating System
Windows 11 HSL
Terminal
Windows Terminal
Activity
github-actions commentedon Sep 13, 2026
This issue might be a duplicate of existing issues. Please check:
supportsMediaInToolResultlogic inmessage-v2.ts; your issue extends this specifically to PDF attachments on strict OpenAI-compatible gateways via the synthetic user message content array.readtool stored as base64 in history and re-sent on every turn (memory/OOM angle, but same data flow).If your issue is distinct enough (422 on user message content array specifically), feel free to clarify how it differs from the above.
pipaacebedo commentedon Sep 13, 2026
Thanks for the triage! I checked the three referenced issues. Mine is related but has a distinct failure surface — happy to consolidate if maintainers prefer, but flagging what is unique here:
vs. #41163 — same
supportsMediaInToolResultpath, but in my case the tool result itself was correctly degraded to a plain string ("PDF read successfully"). The failure happens one step later: the synthetic user message built atmessage-v2.ts:380-399produces acontentarray ([text, file]), and strict OpenAI-compatible gateways reject user-role content arrays entirely:422 messages.297.user.content.str: Input should be a valid string
Note the model's declared modalities say PDF is supported (
glm-5.3-flash→input: ["text","image","video","pdf"]), sounsupportedParts()never degrades thefileblock — and even if it did, the array would still not collapse to a string (the@ai-sdk/openai-compatibleSDK only stringifies single-text-block arrays). A session with a prior PDFreadfails permanently on these gateways.vs. #42263 — that one is about the memory/OOM cost of re-sending base64 attachments each turn. Mine is a hard request rejection (422): the session is bricked, not just heavy. Same data flow, different symptom and different fix locus (message serialization vs. storage/compaction).
vs. #39824 — that one is about media bypassing modality checks. Here the modality check passed (the model does declare PDF support), and that is precisely what sends the
fileblock downstream to a gateway that doesn't implement it regardless of declared capabilities.Concrete, provider-agnostic fix unique to this report:
message-v2.ts(extraction logic, lines ~296-313 and ~380-399): treat@ai-sdk/openai-compatibleas file-block-incapable — don't create the synthetic user message withfileparts; append a textual marker to the tool output instead (pattern already used incompaction.ts:73-78and thestripMediapath).transform.ts(unsupportedParts, lines ~409-445), defense-in-depth: after filtering, collapse any user message whose content array is all-textblocks back into a plaincontent: string(joined with\n\n).If the team prefers merging this into #41163 as a tracking issue, fine by me — just noting the 422-on-user-content-array failure mode isn't currently covered by the three referenced reports.
thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊