The DeepSeek API is text-only. The variant you actually run — deepseek-v4-flash — returns "this model does not support image input" the second you paste a screenshot. Vision exists only in the web chat, not in the API. observer is a two-file plugin that works around it — it catches every image you paste, has a tiny vision model read it, and hands the words back. No image pollution. No vision budget on your main model.
the problem
The api has exactly two names, deepseek-v4-pro and deepseek-v4-flash. Their official capability list is JSON, function calling, thinking mode, and 1M context — the word "image" isn't in it. If you paste a screenshot and ask DeepSeek what's on screen, the model answers honestly: "this model does not support image input."
The vision DeepSeek rolled out lives in the free web chat only; the API exposes no image endpoint at all. So on your normal main model — deepseek-v4-flash, cheap and long-context — a screenshot is just a blob it can't take in. observer steps in: a tiny vision model elsewhere reads the image and hands back plain text DeepSeek can use.
the mechanism
No diagram beats a line of prose. Here's the whole shape of it:
you paste a screenshot
│
observer saves it, replaces the part with "Image saved: /path/a1b2c3.png"
│
your model — text-only, can't take images —
calls @observer, the vision sub-agent
│
@observer opens the file, writes a description in plain text
v
words come back — your model answers you.
The image itself never enters your main context, so a base64 blob never touches DeepSeek's big context window. You pay for pixels exactly once, only for the single image in that message.
setup
model: google/gemini-3.5-flash-lite # free tier # or: openai/gpt-4o, anthropic/claude-sonnet-4-6, moonshotai/kimi-k2.6
{
"permission": {
"external_directory": {
"~/.local/share/opencode/images/**": "allow"
}
}
}keys
Only the tiny vision sub-agent needs an API key — it's the one thing that actually reads pixels. Your DeepSeek main model keeps whatever key it already has.
model: google/gemini-3.5-flash-lite # free-tier friendly # or: openai/gpt-4o, anthropic/claude-sonnet-4-6, moonshotai/kimi-k2.6
# gemini export OPENCODE_GOOGLE_API_KEY=... # openai export OPENCODE_OPENAI_API_KEY=... # anthropic export OPENCODE_ANTHROPIC_API_KEY=...
what's inside
| file | job |
|---|---|
| observer.js | The plugin — decodes pasted images, saves them, routes reading through the sub-agent. |
| observer.md | The vision sub-agent that decided what to read (error log → chart → markup → text). |
| README.md | How it fits in all your codebases, cost, troubleshooting. |