Repository navigation
Open
Labels
Description
Summary
When using a Google Gemini model through OpenRouter (openrouter/google/gemini-4-argon) in OpenCode V2 (2.0.29), every step immediately following a read tool call on image files (.png) fails with HTTP 400 (Requests ending with a model turn are not supported.). In addition, OpenCode surfaces only the generic wrapper message Error: Provider returned error instead of the upstream error in error.metadata.raw.
Environment
- opencode version:
2.0.29 - OS:
Darwin 27.0.0 (darwin arm64) - Terminal:
TERM_PROGRAM=ghostty, TERM=xterm-ghostty, COLORTERM=truecolor - Shell:
/opt/homebrew/bin/zsh - Install/channel:
latest - Active plugins: None found in config
Reproduction
- Select
openrouter/google/gemini-4-argon(varianthigh). - Run a task where the model executes normal text-returning tools (e.g.,
executeorreadon a directory/text file) — these continuations succeed normally. - Have the model call the
readtool on one or more.pngimage files (which returns[{ type: "text", text: "Image read successfully" }, { type: "file", mime: "image/png", uri: "data:image/png;base64,..." }]). - On the immediately following continuation step, the request fails with HTTP 400 (
Error: Provider returned error/Requests ending with a model turn are not supported.), stopping the run. - Sending a new user text prompt allows the session to continue until the next time
readis called on an image.
Expected Behavior
- Tool continuations after
readreturns image attachments (type: "file") should lower into a valid turn sequence that OpenRouter / Google Gemini accepts without failing the run. - When OpenRouter returns
"message": "Provider returned error",providerErrorMessageinpackages/ai/src/provider-error.tsshould surface the upstream provider details fromerror.metadata.raw(anderror.metadata.provider_name) rather than only the generic"Provider returned error"string.
Actual Behavior
The continuation step immediately following any image read tool call deterministically fails (4/4 occurrences in a single session, while 20+ text-only tool continuations in the same session succeeded) with:
{
"type": "provider.invalid-request",
"message": "Provider returned error",
"status": 400,
"response": {
"body": "{\"error\":{\"message\":\"Provider returned error\",\"code\":400,\"metadata\":{\"raw\":\"{\\n \\\"error\\\": {\\n \\\"code\\\": 400,\\n \\\"message\\\": \\\"Requests ending with a model turn are not supported.\\\",\\n \\\"status\\\": \\\"INVALID_ARGUMENT\\\"\\n }\\n}\\n\",\"provider_name\":\"Google\",\"is_byok\":false,\"provider_error_code\":\"400\"}},\"user_id\":\"[REDACTED]\"}"
}
}Server log (~/.local/share/opencode/log/opencode.log):
timestamp=2026-10-15T10:51:39.116Z level=ERROR run=0415584b message="Failed to drain Session" cause="AI.Error: Provider returned error
at jQ (/$bunfs/root/chunk-42dcx4y2.js:6:3619)
at <anonymous> (/$bunfs/root/chunk-42dcx4y2.js:6:3312)
at SessionStep.attempt (/$bunfs/root/chunk-yxtj88vc.js:382:3574)
at SessionStep.attempt (definition) (/$bunfs/root/chunk-yxtj88vc.js:367:302)
at SessionRunner.runStep (/$bunfs/root/chunk-yxtj88vc.js:382:2672)
at SessionRunner.runStep (definition) (/$bunfs/root/chunk-yxtj88vc.js:382:2826)
at SessionRunner.drain (/$bunfs/root/chunk-j0gfnjyp.js:18:2552)
at SessionRunner.drain (definition) (/$bunfs/root/chunk-yxtj88vc.js:382:516)
at ServerProcess.start (/$bunfs/root/chunk-hjn5kn92.js:2:7501)
at ServerProcess.start (definition) (/$bunfs/root/chunk-yxtj88vc.js:453:25628) {
[cause]: AI.Error.InvalidRequest: Provider returned error
at Cl (/$bunfs/root/chunk-42dcx4y2.js:6:1145)
at jQ (/$bunfs/root/chunk-42dcx4y2.js:6:3630)
at <anonymous> (/$bunfs/root/chunk-42dcx4y2.js:6:3312)
}" role=server sessionID=[REDACTED]
Additional Context
- Reproducibility: Consistent (reproduced 4 out of 4 times in the session immediately after
readtool calls returning['text', 'file']on.pngimages). - Preceding assistant message shape before each failure:
{ "type": "assistant", "model": { "id": "google/gemini-4-argon", "providerID": "openrouter", "variant": "high" }, "content": [ { "type": "reasoning", "text": "", "state": { "reasoningDetails": [ { "type": "reasoning.encrypted", "data": "<REDACTED>", "format": "google-gemini-v1", "id": "call_1039747", "index": 0 } ] } }, { "type": "tool", "id": "call_1039747", "name": "read", "executed": false, "state": { "status": "completed", "input": { "path": "/path/to/render_1.png" }, "content": [ { "type": "text", "text": "Image read successfully" }, { "type": "file", "mime": "image/png", "name": "/path/to/render_1.png", "uri": "data:image/png;base64,<REDACTED>" } ] } }, { "type": "tool", "id": "call_1039759", "name": "read", "executed": false, "state": { "status": "completed", "input": { "path": "/path/to/render_2.png" }, "content": [ { "type": "text", "text": "Image read successfully" }, { "type": "file", "mime": "image/png", "name": "/path/to/render_2.png", "uri": "data:image/png;base64,<REDACTED>" } ] } } ] } - Code pointers:
- In
packages/ai/src/protocols/openai-chat.ts(lowerToolMessages/flushAttachments), tool-result file attachments are split out ofrole: "tool"messages and flushed at the end of the turn as a synthetic{ role: "user", content: [{ type: "image_url", ... }] }message with no text part (unlikepackages/ai/src/protocols/gemini.ts, whoseflushMediaincludes{ text: "Attached media from tool result:" }). - In
packages/ai/src/provider-error.ts,decodeMessageextractsbody.error.message("Provider returned error") without checkingbody.error.metadata.rawfor OpenRouter's upstream error message.
- In
- Related issues: Requests ending with a model turn are not supported #45359, gemini-3.8-flash: 400 'Requests ending with a model turn are not supported' after a model turn #47034, Gemini 400 "Requests ending with a model turn are not supported" when a step is stored out of order: Identifier.create is not monotonic across backward clock steps #55514 (which tracked V1 occurrences of
"Requests ending with a model turn are not supported").
Activity
opencode-agent commentedon Oct 15, 2026
Thanks for the detailed report and the code pointers. I reproduced both parts with a mock OpenRouter endpoint on 2.0.29, and they still happen on the latest
dev(4397ee4).What I did: I configured
openrouter/google/gemini-4-argonagainst a local mock. The mock makes the model callreadon a PNG, then answers the next step with the same 400 body you posted.assistant (tool_calls) → tool ("Image read successfully") → user [image_url]. That last synthetic user message contains only the image, with no text part. It comes fromflushAttachmentsinpackages/ai/src/protocols/openai-chat.ts, as you pointed out.Error: Provider returned error. Google's message ("Requests ending with a model turn are not supported.") is inerror.metadata.rawand never reaches you.I couldn't confirm against the real OpenRouter/Google API that this shape is what gets rejected, because that needs a real account. Your 4/4 failures right after image reads point that way, though.
The script below reproduces this without credentials.
Related, but on v1 with different triggers: #55514, #47034 and #45359 (the same Gemini error), and #23665 ("Provider returned error" hiding the upstream message for a different provider).
Reproduction script (repro.sh)
Run it:
bash repro.shspragginsdesigns commentedon Oct 15, 2026
Confirmed against the real OpenRouter API, and narrowed down to the exact cause.
Setup: OpenCode 2.0.29 on Windows 11,
openrouter/google/gemini-4-argon, a clean config (no plugins, isolated HOME). A logging pass-through proxy sat between OpenCode and openrouter.ai. Prompt: "Use your read tool to open render_1.png, then tell me in one sentence what the image shows."The first request returned 200 (the model calls
read). The continuation request returned 400 every time:I then replayed that exact captured request (non-streaming), changing one thing per run:
reasoning_detailsfrom the assistant messageSo the 400 needs both of these:
image_urlpartsThe same shape with no
reasoning_detailspasses, which is probably why a simple mock or a single-turn test doesn't show it. Google's error presumably comes from how OpenRouter translates an image-only user turn when there are thought signatures.Suggested fix: have
flushAttachmentsinpackages/ai/src/protocols/openai-chat.tsalways emit a short text part with the attachments, for example[{type:"text", text:"Attachments from the read tool:"}, ...images]. In my replays that alone turns the 400 into a 200 without dropping the reasoning. Surfacingerror.metadata.rawwould also have made this much faster to find, since OpenCode only shows "Provider returned error".In real use this makes Gemini 4 Argon effectively unusable in OpenCode for any task that reads an image. I had 8 of 8 failures right after an image
readover two long sessions. The successful steps around those failures still bill the full context, so a session racks up cost while getting nowhere.