give non-vision opencode models the ability to see images and screenshots by routing them to a vision-capable model.
when a user attaches a screenshot to a text-only model, opencode rejects it with an error. This plugin intercepts that flow by registering a see_image tool that sends the image to a vision model and returns a textual description the primary model can reason about.
when the active model is already vision-capable (its capabilities.input.image is true), the plugin stays out of the way: the see_image instructions are not injected, and a stray see_image call returns a harmless "you already saw this natively" note instead of an error. beware that this might lead to issues while switching models mid session, so a clean session might fix most of your problems (if any).
one command (recommended):
opencode plugin opencode-see-image --globalThis installs the package and adds it to your config. Then restart opencode.
edit config manually:
Add the plugin to your opencode config:
Then restart opencode.
ask your agent:
install the opencode-see-image plugin
it'll run opencode plugin opencode-see-image --global and tell you to restart.
you need a connected vision-capable provider. The plugin auto-detects whichever you have connected, either of these work:
- run
/connectin opencode - select opencode (OpenCode Zen)
- paste your API key from opencode.ai/auth
the plugin uses mimo-v2.5-free automatically — if you don't have an OpenCode Go sub, it skips the paid model entirely (no errors) and routes to the free model.
- run
/connectin opencode - select opencode-go
- paste your API key from opencode.ai/auth
the plugin prefers minimax-m3 via opencode-go when available.
set the SEE_IMAGE_* env vars to point at any Anthropic-Messages-compatible endpoint. see Configuration below.
the resolve order: explicit SEE_IMAGE_API_KEY env → configured SEE_IMAGE_PROVIDER → opencode-go (MiniMax M3) if you have a Go sub connected → opencode (mimo-v2.5-free). If no Go sub is connected, opencode-go is skipped so free users never hit a "model not found" error.
user attaches screenshot
|
v
opencode rejects it: 'this model does not support image input'
| (the model only sees the filename, or nothing at all)
v
plugin's system-prompt instructions tell the model to call see_image
|
v
see_image tool resolves the image:
1. asks the opencode server for the session's attachments (exact name,
then unicode-normalized name, then most recent image)
2. falls back to reading opencode's SQLite DB directly (this session,
then any session)
3. falls back to a filesystem search
4. if the name matched nothing but the session has an image, uses the
most recent one anyway
|
v
sends the image to the vision model and returns the textual description
|
v
primary model answers using the description
if everything misses, the error message lists the images actually attached to the session so the model can retry with a correct filename.
the vision call handles attacker-influenceable content (the image itself, plus the question), so the helper model is locked out of the agent toolset on every route:
- sdk route (default): the helper session is created with a wildcard deny (
"*": falseplus explicit per-tool denies), which opencode converts to session permission rules that strip every tool. - cli fallback (free Zen models): the spawned
opencode rungets a deny-allOPENCODE_PERMISSIONconfig and runs without--dangerously-skip-permissions, so no tool is offered and nothing can be auto-approved. - http route (
apiKeyset): a plain Anthropic-Messages API call with notoolsfield at all.
versions before 1.3.1 passed tools: {} on the SDK route, which opencode treats as "no overrides" — the vision model received the full toolset and could actually execute commands (#6). update if you're on an older version.
the plugin registers a see_image tool with two arguments:
| arg | type | required? | description |
|---|---|---|---|
filePath |
string | n | path to the image. Absolute path, or a bare filename like "Screenshot 2026-06-18 at 17.32.24.png" to auto-locate. Omit (or pass "latest") to use the most recent image attached to the conversation. |
question |
string | n | a specific question about the image. Defaults to a general detailed description. Use this to focus on a particular detail (e.g. "What error is shown in the terminal?"). |
your model calls this tool automatically when you attach a screenshot, you don't need to do anything special. The question arg is optional; the model uses it when you ask something specific about the image.
you can configure the vision route two ways: via plugin options in your opencode config (recommended), or via SEE_IMAGE_* env vars. The plugin uses opencode's SDK client by default (handles auth automatically). Set SEE_IMAGE_API_KEY to bypass the SDK and call an HTTP endpoint directly.
Set the vision route in your opencode config (~/.config/opencode/opencode.json or opencode.json in a project) using the tuple form of the plugin array entry:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"opencode-see-image",
{
"provider": "egon-proxy",
"model": "qwen3.6-35b-a3b:latest"
}
]
]
}Supported option keys (all optional):
| option | env var fallback | default | description |
|---|---|---|---|
provider |
SEE_IMAGE_PROVIDER |
opencode-go |
Provider ID for SDK routing |
model |
SEE_IMAGE_MODEL |
minimax-m3 |
Vision model ID |
endpoint |
SEE_IMAGE_ENDPOINT |
https://opencode.ai/zen/go/v1/messages |
HTTP endpoint (only used with apiKey) |
apiKey |
SEE_IMAGE_API_KEY |
(uses SDK) | Bypass SDK, call HTTP endpoint directly |
timeout |
SEE_IMAGE_TIMEOUT |
30000 |
Per-candidate timeout in ms. Prevents hanging on slow models. |
apiVersion |
SEE_IMAGE_API_VERSION |
2023-06-01 |
anthropic-version header (HTTP mode only) |
userAgent |
SEE_IMAGE_USER_AGENT |
(Chrome UA) | User-Agent header (HTTP mode only) |
precedence: per field, resolution is plugin option → SEE_IMAGE_* env var → built-in default. Config options take precedence over env vars; env vars remain the fallback when an option is unset. Existing env-var-only setups are unchanged.
note on secrets: a project-level opencode.json usually ends up in git — keep apiKey out of it and use the SEE_IMAGE_API_KEY env var instead.
same settings as environment variables:
| env var | default | description |
|---|---|---|
SEE_IMAGE_MODEL |
minimax-m3 |
Vision model ID |
SEE_IMAGE_PROVIDER |
opencode-go |
Provider ID for SDK routing |
SEE_IMAGE_API_KEY |
(uses SDK) | Bypass SDK, call HTTP endpoint directly |
SEE_IMAGE_ENDPOINT |
https://opencode.ai/zen/go/v1/messages |
HTTP endpoint (only used if SEE_IMAGE_API_KEY is set) |
SEE_IMAGE_API_VERSION |
2023-06-01 |
anthropic-version header (HTTP mode only) |
SEE_IMAGE_USER_AGENT |
(Chrome UA) | User-Agent header (HTTP mode only) |
SEE_IMAGE_TIMEOUT |
30000 |
Per-candidate timeout in ms. Prevents hanging on slow models. |
any Anthropic-Messages-compatible endpoint works. for example, to use a direct MiniMax key:
export SEE_IMAGE_ENDPOINT="https://api.minimax.io/v1/messages"
export SEE_IMAGE_MODEL="minimax-m3"
export SEE_IMAGE_API_KEY="your-minimax-key"to use a different opencode-go model (e.g. Kimi K2.7):
export SEE_IMAGE_MODEL="kimi-k2.7-code"Free (OpenCode Zen):
| model | Notes |
|---|---|
mimo-v2.5-free |
free. may be a bit slow. default fallback when only Zen is connected (routed via CLI). |
big-pickle |
for some reason, big pickle works as an image capable model when called through the sdk w/ an active opencode go sub. |
paid (OpenCode Go):
| model | speed | notes |
|---|---|---|
minimax-m3 |
~3000ms | default. fast, clean, and accurate. |
kimi-k2.7-code |
~7000ms | clean and accurate. |
kimi-k2.6 |
~12000ms | accurate but slow. |
qwen3.7-plus |
~15000ms | slow, spends a bit more tokens because of thinking. |
auto-update (built in): uses the opencode-plugin-update-kit and shows a toast: "opencode-see-image updated to X.Y.Z, restart opencode to apply". You just need to restart opencode to load the new version.
manual update:
opencode plugin opencode-see-image --force --globalthen restart opencode.
pin a version in your config to opt out of auto-updates:
"plugin": ["opencode-see-image@0.4.2"]works on macOS, Windows, and Linux, in both the CLI/TUI (Bun runtime) and the desktop app (whose server runs on Node inside Electron). The package ships compiled JS and avoids Bun-only APIs at load time, so it loads on either runtime. Attachment lookup goes through the opencode server API first (works everywhere, including remote workspaces), with a direct SQLite read and a filesystem search as fallbacks.
when opencode rejects an image attachment, the model only receives a bare filename. see_image first checks the opencode DB (cross-platform), then falls back to searching these filesystem locations, in order:
macOS
$TMPDIR/TemporaryItems/NSIRD_screencaptureui_*/(where macOS stashes dragged screenshots)$TMPDIR/TemporaryItems/~/Desktop,~/Downloads, current working directory
Windows
%TEMP%/%TMP%(dragged/temp images)%USERPROFILE%\Pictures\Screenshotsand the OneDrive-redirected%USERPROFILE%\OneDrive\Pictures\Screenshots(Win+PrtScn / Snipping Tool)%USERPROFILE%\Pictures~\Desktop,~\Downloads, current working directory
Linux
$TMPDIR//tmp~/Pictures/Screenshots,~/Pictures~/Desktop,~/Downloads, current working directory
pass an absolute filePath to skip the search.
MIT