Skip to content

session: image content in tool results permanently breaks sessions on text-only models #55044

Closed as duplicate

Description

@neilZhen

Description

When a tool result adds image content (e.g. a screenshot returned by an MCP tool, or reading an image file) to a session whose model only accepts text input, every subsequent request fails with HTTP 400 from the provider. OpenCode surfaces the upstream error verbatim with no way to locate or remove the offending message, leaving the session permanently unusable with that model.

Example error, replayed on every turn:

upstream returned HTTP 400: {"object":"error","message":"Model only supports text input; received unsupported content type 'image_url'.","type":"BadRequestError","param":null,"code":400}

The provider config used (redacted):

{
  "model": "nio_ds/glm-5.3",
  "providers": {
    "nio_ds": {
      "package": "aisdk:@ai-sdk/openai-compatible",
      "settings": { "baseURL": "https://<internal-gateway>/openai/v1", "apiKey": "<redacted>" },
      "models": {
        "glm-5.3": {
          "name": "DS-GLM-5.3",
          "limit": { "context": 1048576, "output": 65536 }
        }
      }
    }
  }
}

Expected behavior:

  • When capabilities.input: ["text"] is declared for a model (or once the provider has rejected image content), image parts should be stripped or replaced with a text placeholder when assembling requests, so the session stays usable.
    • At minimum, when a provider rejects a request because of image content, the error should identify the offending message and offer a recovery path (remove the message, revert to an earlier turn, or retry without image content).
      Additional context: the root trigger is the uncatalogued-model default of assuming image input support ("These are fallback assumptions, not detected capabilities" per the V2 models docs). With private OpenAI-compatible gateways this assumption is frequently wrong, and a single tool result then breaks the session.

Workarounds:

  • Declaring "capabilities": { "tools": true, "input": ["text"], "output": ["text"] } on the model prevents new sessions from hitting this (whether it also repairs an already-affected session by filtering the stored history on send has not been verified).
    • Switching the affected session to a multimodal model (<leader>m) restores it; a compact under that model, then switching back, also unblocks the original model.
      • <leader>u undo back to before the image-producing turn also works, at the cost of losing later turns.

Plugins

superpowers (git+https://github.com/obra/superpowers.git); MCP servers: playwright, chrome-devtools (local)

OpenCode version

2.0.27

Steps to reproduce

  1. Configure a custom OpenAI-compatible provider whose model only accepts text input and is not in models.dev (e.g. a private gateway). Because the model is uncatalogued, OpenCode falls back to assuming text and image input.
    1. Start a session with that model and an MCP server that returns screenshots (e.g. playwright get-screenshot).
    1. Invoke the screenshot tool. The image enters session history as tool-result content.
    1. The next model request fails: upstream returned HTTP 400: ... "Model only supports text input; received unsupported content type 'image_url'".
    1. Every subsequent turn replays the same history, including the image, and fails identically. The session is permanently unusable with this model.

Screenshot and/or share link

No response

Operating System

macOS 15 (Darwin 24.6.0, darwin arm64)

Terminal

iTerm.app (TERM=xterm-256color, COLORTERM=truecolor), shell: /bin/zsh

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report! I think this is a duplicate of #52042.

    In v2, images from tool results and pasted attachments go through the same check before each request (unsupportedParts in packages/core/src/session/model-request.ts). That check replaces any image the model can't accept with a short text note. It only does this when the model's capabilities.input leaves out image. A custom model with no declared capabilities defaults to input: ["text", "image"], so the image is sent and the provider rejects it on every turn. #52042 reports this same root cause and also asks for a clearer error and a way to recover. The only difference is that its image was pasted, while yours came from a tool result.

    Possible duplicates and related issues, ordered by likelihood:

    On your unverified workaround: that check runs over the whole stored history on every request. So declaring "capabilities": { "input": ["text"], "output": ["text"] } on the model should also fix a session that's already affected, not just new ones. Stored images get replaced with an ERROR: Cannot read … note when the request is sent.

    The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different.

  2. opencode-agent commented on Oct 14, 2026

    @opencode-agent
    Contributor

    Closing this as a duplicate of #52042, since we haven't heard back. Both are the same v2 problem: a custom model with no declared capabilities defaults to accepting image input, so images get sent and the provider returns 400 on every turn. Please follow #52042 for progress.

    Until that's resolved, declaring "capabilities": { "input": ["text"], "output": ["text"] } on the model should keep sessions usable. OpenCode should then replace stored images with a text note when it sends each request.

    If your case turns out to be different, reply here and we'll take another look. Thanks again for the detailed report!

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions