You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Images pasted with a custom-provider model are silently replaced by a "[Attached image/png: ...]" text placeholder — no warning, even when the upstream model has vision #47480
When a model is defined under provider.<id>.models in opencode.json(c) (custom OpenAI-compatible provider), its attachment capability defaults to false unless explicitly set (the bundled model loader resolves attachment: cfg.attachment ?? false for custom models).
If the user pastes/attaches an image in such a session, OpenCode silently rewrites the media part into a plain text part:
[Attached image/png: screenshot.png]
No warning is shown in the TUI. The LLM only ever receives that placeholder — even when the model behind the endpoint fully supports vision. The model then typically hallucinates an explanation ("the current model does not support image input"), which actively misleads the user into concluding their vision model is broken.
Steps to Reproduce
Define a custom provider with a vision-capable model, withoutattachment:
Paste a screenshot containing visible error text and ask "what does this error say?".
The model answers generically or claims it cannot see images. Inspecting the stored parts shows the image was replaced by the [Attached image/png: ...] text part; the image itself never left OpenCode.
Expected Behavior
A visible warning when media is attached to a model with attachment: false (e.g. "model does not support attachments — the image will be sent as a text placeholder only"), or an explicit error instead of silent degradation.
The attachment / modalities model keys documented on the custom providers page (I could not find them in the docs and only discovered them by inspecting the bundle).
Actual Behavior
Silent placeholder substitution; the conversation proceeds as if the image had been delivered, and the failure only surfaces as wrong/misleading model answers.
After this, images are delivered natively — verified end-to-end on OpenCode 1.18.25 (CLI) and the desktop app bundling 1.18.29 against an OpenAI-compatible gateway with Gemini-class heads: the model transcribes pasted screenshots correctly.
Environment
OpenCode 1.18.25 CLI + desktop app (bundled server 1.18.27–1.18.29), Windows 11
Custom provider using the default @ai-sdk/openai-compatible route
Notes
A related idea: when the active model lacks attachment capability but small_model (or any configured model) is vision-capable, OpenCode could optionally transcribe the image with that model and inject the description as a text part — a built-in fallback that would make images "just work" on text-only models. I currently run this as a chat.message plugin hook and it works well; happy to upstream the approach.
I'd genuinely like to collaborate with the team as a regular contributor — if you point me at where you'd want the warning (TUI vs server) or the fallback, I can prepare a PR.
Unable to read images for some models #5359: Unable to read images for some models — same surface symptom with a custom OpenAI-compatible provider (LiteLLM/Vertex AI), though the root cause there may differ.
If your issue is specifically about the silent text-placeholder substitution with no warning (rather than an outright rejection or missing forwarding), it may be distinct enough to stand on its own — please check the above and close or cross-link as appropriate.
Confirming this on a self-hosted OpenAI-compatible gateway/router, configured as a custom provider that lives only in opencode.json and uses @ai-sdk/openai-compatible. Two paths hit the same capability gate:
Attaching/pasting an image is rewritten to a [Attached image/png: …] placeholder.
Reading a local image via the Read tool returns ERROR: Cannot read "image.png" (this model does not support image input).
The upstream route is vision-capable: sending the exact same image directly to the gateway as a base64 image_url data URI via curl works and the model describes it correctly. So this is client-side capability resolution, not the model or the gateway — the custom model's capabilities.input resolves to ["text"] (no models.dev entry, and the gateway's /v1/models carries no modality metadata), so image/* parts are dropped before they reach the already-functional OpenAI-compatible conversion.
Workaround that works today — declare modalities explicitly per model:
Related: #44639 (same root cause plus the misleading error), #20802, and the proposed defaults in #21627 / #23501. Flipping the default for custom @ai-sdk/openai-compatible models would remove the need for per-model config that most users won't discover.
Still hit this in 1.18.32 (newer than the 1.18.25 / 1.18.29 verified versions in this thread). Same LiteLLM OpenAI-compatible custom-provider scenario as @eddoww — litellm.hhstaging.dev/v1 proxying minimax/MiniMax-M3 (model advertises supports_vision: true in the litellm admin UI and accepts the same base64 image_url payload via direct curl with 200 + correct description).
ERROR: Cannot read "screenshot.png" (this model does not support image input)
Root cause in the binary (verified by strings on the embedded JS bundle in opencode 1.18.32): the conversion cascade for user-defined custom-provider models ends up with Z.capabilities.input as an array["text","image"], but the read tool's filter does Z.capabilities.input[W] (array index-by-string → undefined). The object form {image: true, ...} is only populated when a models.dev registry entry exists for the model (J.input_modalities?.includes("image")); for custom aliases with no registry entry the cascade falls through to Y?.capabilities.input.image, which is never set because the public user schema doesn't expose capabilities.
The exact gating line in the read-tool message filter:
if(Z.capabilities.input[W])returnJ;// W = "image"return{type: "text",text: `ERROR: Cannot read ${G??W} (this model does not support ${W} input). Inform the user.`};
Workarounds tried, both inert in 1.18.32:
attachment: true — sets a separate capabilities.attachment bool used by the UI affordance; does not populate capabilities.input.image.
modalities: { input: ["text", "image"] } — copied into capabilities.input as an array, but the read tool's key lookup still misses.
Suggested fix: in the user-config → internal-model converter, when no models.dev entry exists for the model, translate modalities.input[] into the object form so capabilities.input.image resolves truthy for the read-tool filter. Or expose capabilities (or just capabilities.input: { image?: boolean, ... }) as a user schema field with the same boolean keys the resolver already consumes.
Until then, this binary is unusable for vision via custom-provider aliases — even when the upstream endpoint is fully vision-capable.
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.
Activity
github-actions commentedon Sep 5, 2026
This issue might be a duplicate of existing issues. Please check:
falsefor non-bundled models, image parts rejected/replaced) and identical workaround (explicit per-model capability override inopencode.json). That report also covers the misleading error message path.If your issue is specifically about the silent text-placeholder substitution with no warning (rather than an outright rejection or missing forwarding), it may be distinct enough to stand on its own — please check the above and close or cross-link as appropriate.
eddoww commentedon Sep 18, 2026
Confirming this on a self-hosted OpenAI-compatible gateway/router, configured as a custom provider that lives only in
opencode.jsonand uses@ai-sdk/openai-compatible. Two paths hit the same capability gate:[Attached image/png: …]placeholder.ERROR: Cannot read "image.png" (this model does not support image input).Minimal repro:
{ "provider": { "mygateway": { "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "https://gateway.example.com/v1" }, "models": { "my-vision-alias": { "name": "my-vision-alias" } } } } }The upstream route is vision-capable: sending the exact same image directly to the gateway as a base64
image_urldata URI viacurlworks and the model describes it correctly. So this is client-side capability resolution, not the model or the gateway — the custom model'scapabilities.inputresolves to["text"](no models.dev entry, and the gateway's/v1/modelscarries no modality metadata), soimage/*parts are dropped before they reach the already-functional OpenAI-compatible conversion.Workaround that works today — declare modalities explicitly per model:
Related: #44639 (same root cause plus the misleading error), #20802, and the proposed defaults in #21627 / #23501. Flipping the default for custom
@ai-sdk/openai-compatiblemodels would remove the need for per-model config that most users won't discover.saiqulhaq-hh commentedon Sep 25, 2026
Still hit this in 1.18.32 (newer than the 1.18.25 / 1.18.29 verified versions in this thread). Same LiteLLM OpenAI-compatible custom-provider scenario as @eddoww —
litellm.hhstaging.dev/v1proxyingminimax/MiniMax-M3(model advertisessupports_vision: truein the litellm admin UI and accepts the same base64 image_url payload via directcurlwith200 + correct description).opencode.jsonc:Read tool still returns:
Root cause in the binary (verified by
stringson the embedded JS bundle inopencode 1.18.32): the conversion cascade for user-defined custom-provider models ends up withZ.capabilities.inputas an array["text","image"], but the read tool's filter doesZ.capabilities.input[W](array index-by-string →undefined). The object form{image: true, ...}is only populated when amodels.devregistry entry exists for the model (J.input_modalities?.includes("image")); for custom aliases with no registry entry the cascade falls through toY?.capabilities.input.image, which is never set because the public user schema doesn't exposecapabilities.The exact gating line in the read-tool message filter:
Workarounds tried, both inert in 1.18.32:
attachment: true— sets a separatecapabilities.attachmentbool used by the UI affordance; does not populatecapabilities.input.image.modalities: { input: ["text", "image"] }— copied intocapabilities.inputas an array, but the read tool's key lookup still misses.Suggested fix: in the user-config → internal-model converter, when no
models.deventry exists for the model, translatemodalities.input[]into the object form socapabilities.input.imageresolves truthy for the read-tool filter. Or exposecapabilities(or justcapabilities.input: { image?: boolean, ... }) as a user schema field with the same boolean keys the resolver already consumes.Until then, this binary is unusable for vision via custom-provider aliases — even when the upstream endpoint is fully vision-capable.
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 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊