Route pasted images to a cheap vision model in opencode so a text-only main agent can work from the vision model's text output.
opencode attaches a pasted image to the main (text-only) model's message and drops or errors on it before any skill or subagent runs. A skill alone cannot fix this. The only reliable fix is a plugin hook that intercepts the image at the harness level, resolves it to a readable path, and lets a cheap vision model analyze it.
opencode-vision-router is a self-contained, zero-config plugin that does exactly that β and injects
the vision subagent for you, so there are no separate agent or skill files to manage.
- πΌοΈ Pasted-image routing β
data:URLs,file://paths, absolute paths, and OpenCode V2 raw media parts all supported. - πΈ Multiple images β handles several pasted images in a single message, routing each one to the vision subagent.
- πΈ Cheap vision model β point it at any image-capable model (
provider/model). - π§ Multimodal-aware β if your main model already sees images, routing is skipped automatically. Set
forceto always route (e.g. to a cheaper vision model). - π§© Self-contained β injects the vision subagent and system instruction at load time.
- π Safe by default β the vision subagent can read the image but is denied edit/bash/webfetch.
- β‘ OpenCode 1 & 2 β one default export supports both the V1 plugin API (
server()) and the V2 plugin API (setup()), plus V2 model-registry capability detection and V2 message media parts.
The plugin registers the following behavior at load time, in whichever API shape your opencode version supports:
- Vision subagent injection β declare the chosen model as image-capable and inject the
visionsubagent (V1confighook / V2agent.transform). - Capability detection β per model, learn whether the main model can see images
(V1
chat.paramslearning / V2 model-registry lookup), so multimodal main models are skipped unlessforceis set. - Image rewrite β strip the image from the user message and replace it with a text
pointer containing the resolved path, so a text-only model never sees the bytes:
- V1:
chat.message(primary) +experimental.chat.messages.transform(backup). - V2: a
session.hook("context")registered insetup(), which rewritesmediaparts in the assembled messages immediately before each agent model request and covers both fresh attachments and history.
- V1:
β οΈ V1 relies on opencode's experimentalexperimental.chat.messages.transformhook, which may change in future opencode versions. In V2 the equivalent is the stablecontextsession hook.
Add it to your opencode.json(c) and restart opencode β plugins are not hot-reloaded.
OpenCode 2 uses the plugins key with a package/options object:
{
"plugins": [
{ "package": "opencode-vision-router", "options": { "model": "opencode-go/qwen3.7-plus" } }
]
}OpenCode 1 uses the plugin key with a package/options tuple
(OpenCode 1.18.29 or newer is required for the object-form entrypoint this plugin ships):
{
"plugin": [
["opencode-vision-router", { "model": "opencode-go/qwen3.7-plus" }]
]
}| Option | Required | Default | Description |
|---|---|---|---|
model |
yes | β | Vision-capable model as provider/model (e.g. opencode-go/qwen3.7-plus). If omitted, routing is disabled (a warning is logged). |
agent |
no | vision |
Name of the injected vision subagent. |
tmpDir |
no | os.tmpdir() |
Directory under which decoded images are cached (content-hashed, reused across calls). |
force |
no | false |
Route images to the vision subagent even when the main model is multimodal (e.g. to use a cheaper vision model). By default the subagent is skipped when the main model can already see images. |
OpenCode 2:
{
"plugins": [
{ "package": "opencode-vision-router", "options": { "model": "opencode-go/qwen3.7-plus" } }
]
}OpenCode 1 (>= 1.18.29):
{
"plugin": [
["opencode-vision-router", { "model": "opencode-go/qwen3.7-plus" }]
]
}{
"plugins": [
"@dodopayments/opencode-plugin",
{ "package": "opencode-vision-router", "options": { "model": "opencode-go/qwen3.7-plus" } }
]
}{
"plugins": [
{
"package": "opencode-vision-router",
"options": {
"model": "anthropic/claude-3-5-haiku",
"agent": "image-reader",
"tmpDir": "/var/tmp/opencode-vision"
}
}
]
}If your main model can already see images, routing is skipped by default. Set force: true
to always route β e.g. to send images to a cheaper vision model while keeping a stronger
text model as main:
{
"plugins": [
{
"package": "opencode-vision-router",
"options": { "model": "openai/gpt-4o-mini", "force": true }
}
]
}opencode-vision-router is an opencode plugin and is wired up only through your opencode.json
(see Installation / Configuration above). Its helper functions (image.ts, transform.ts,
agent.ts) are plain, dependency-free implementation details used by the plugin itself and
covered by the test suite β they are intentionally not part of the package's public API, so
import them from the source tree only if you are extending the plugin, not from the published
package.
A pasted image is intercepted and routed to the vision subagent:
Request flow:
sequenceDiagram
participant U as User
participant M as Main agent (text-only)
participant V as vision subagent
participant L as Vision LLM
U->>M: paste image + question
M->>M: image stripped β path pointer
M->>V: Task(image path, question)
V->>L: read(path) + analyze
L-->>V: text analysis
V-->>M: text analysis
M-->>U: answer
bun install
bun test # run the test suite
bunx tsc --noEmit # type-checksrc/
index.ts # plugin entrypoint β dual V1/V2 default export (no public re-exports)
types.ts # shared option & message types
image.ts # resolveImagePath / resolveMediaPath, decodeDataUrl, extForMime
transform.ts # transformMessages / transformV2Messages, imagePointer (pure)
agent.ts # buildVisionAgentConfig, applyConfig, applyAgent, delegationInstruction
index.test.ts # Bun tests
Contributions welcome! This is a small, single-purpose plugin, so the bar for patches is low.
- Fork the repo and create a branch:
git checkout -b fix/my-change. - Install deps and run the checks:
bun install && bun test && bunx tsc --noEmit. - Add tests for any new behavior.
- Keep the plugin self-contained β prefer extending the injected subagent over adding new files users must wire up.
- Open a PR with a clear description of the problem and the fix.
Please file issues for bugs, hook-contract changes in opencode, or model-compatibility reports.
Publishing, OIDC/Trusted-Publisher setup, and the buildβdist/ flow are documented in
release.md.
MIT Β© opencode-vision-router contributors.

