Skip to content
    ↑↓ select↵ openesc close
    Chathula

    Vision Router

    opencode-vision-router·v0.2.2·Agent Orchestration

    Route pasted images to a cheap vision model in opencode so text-only main agents work from its text output.

    GitHub stars

    6

    Monthly installs

    367

    62 in 7 days

    Composite score

    42.6

    Multi-signal model

    Last commit

    6 days ago

    2026-10-14

    Install and configure

    opencode.json

    Writes to this project's opencode.json — applies to this repository only.

    opencode.json

    {
      "$schema": "https://opencode.ai/config.json",
      "plugin": ["opencode-vision-router@0.2.2"]
    }

    OpenCode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.

    opencode-vision-router

    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.

    npm version license types downloads bun


    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 media parts (both the flat and the newer nested media.source shapes) all supported.
    • 📸 Multiple images — handles several pasted images in a single message, routing each one to the vision subagent.
    • 🔎 History-safe — also strips images embedded in tool results (e.g. the read tool), so they aren't replayed to a text-only model on later steps.
    • 💸 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 force to 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 the V1 plugin API (server()) and the V2 plugin API (setup()), including V2 releases before and after 2.0.25 (flat vs. nested media parts, catalog.model vs. model registry, and both agent-permission shapes).

    🧠 How it works

    The plugin registers the following behavior at load time, in whichever API shape your opencode version supports:

    1. Vision subagent injection — declare the chosen model as image-capable and inject the vision subagent (V1 config hook / V2 agent.transform).
    2. Capability detection — per model, learn whether the main model can see images (V1 chat.params learning / V2 model-registry lookup), so multimodal main models are skipped unless force is set.
    3. 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 in setup(), which rewrites media/file parts in the assembled messages immediately before each agent model request and covers both fresh attachments and history.
      • Images embedded in tool results (e.g. a read of an image file) are stripped the same way, so they are not replayed to a text-only model on later steps.

    ⚠️ V1 relies on opencode's experimental experimental.chat.messages.transform hook, which may change in future opencode versions. In V2 the equivalent is the stable context session 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). Model IDs may themselves contain slashes (e.g. bifrost/vllm/zai/glm-5.3-flash) — the provider is everything before the first slash. 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:

    Pasted image routed to the vision subagentVision subagent analyzing the image and returning text

    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.

    1. Fork the repo and create a branch: git checkout -b fix/my-change.
    2. Install deps and run the checks: bun install && bun test && bunx tsc --noEmit.
    3. Add tests for any new behavior.
    4. Keep the plugin self-contained — prefer extending the injected subagent over adding new files users must wire up.
    5. 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.

    Similar plugins