Vision Router
Route pasted images to a cheap vision model in opencode so text-only main agents work from its text output.
6
近 30 天 +2
519
近 7 天 211
46.8
生态多维模型
2 天前
2026-10-02
快速安装与配置
opencode.json写入当前项目的 opencode.json,只对这个仓库生效。
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-vision-router@0.2.1"]
}写入 ~/.config/opencode/opencode.json,对所有项目生效。
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-vision-router@0.2.1"]
}若你要在本地改造这个插件,先装到项目里再从本地路径引用。
shell
pnpm add -D opencode-vision-routerOpenCode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。
opencode-vision-router
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.
✨ Features
- 🖼️ 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.
🧠 How it works
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 experimental
experimental.chat.messages.transformhook, which may change in future opencode versions. In V2 the equivalent is the stablecontextsession hook.
📦 Installation
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" }]
]
}
⚙️ Configuration
| 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. |
🚀 Usage examples
Basic
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" }]
]
}
Multiple plugins
{
"plugins": [
"@dodopayments/opencode-plugin",
{ "package": "opencode-vision-router", "options": { "model": "opencode-go/qwen3.7-plus" } }
]
}
Custom subagent name and cache directory
{
"plugins": [
{
"package": "opencode-vision-router",
"options": {
"model": "anthropic/claude-3-5-haiku",
"agent": "image-reader",
"tmpDir": "/var/tmp/opencode-vision"
}
}
]
}
Force routing on a multimodal main model
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 }
}
]
}
Consuming the plugin
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.
🖥️ Screenshots
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
🛠️ Development
bun install
bun test # run the test suite
bunx tsc --noEmit # type-check
Project structure
src/
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
🤝 Contributing
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.
🚀 Releasing
Publishing, OIDC/Trusted-Publisher setup, and the build→dist/ flow are documented in
release.md.
📜 License
MIT © opencode-vision-router contributors.
同类生态推荐
Vision Paste
opencode-vision-paste
OpenCode plugin: intercept pasted images → local VL API analysis → replace with text
Subagent Magazine
opencode-subagent-magazine
OpenCode TUI plugin monitoring sub-agent invocation status in real time
Loopd
@bojackduy/opencode-loopd
Codex-inspired background goal engine for OpenCode — autonomous subagents, engine-driven loop, child worker sessions and modal TUI dashboard. Like Claude Code loop for OpenCode.