为纯文本 DeepSeek Harness Agent 增加视觉能力:内置免 Key 免费视觉链 + 14 个像素级工具,发图即用,无需 Python。
- 语言
- JavaScript
- License
- MIT
- 分支
- main
安装
$ dsh plugin --profile web add github:ysr666/dsh-vision-router在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程
一句话定位
dsh-vision-router 是 DeepSeek Harness 的视觉增强插件:让原本只能聊文字的 Agent 直接"看见"图片——发图后 Agent 像调用普通工具一样调用 14 个视觉工具(定位、裁剪、像素对比、OCR、矢量化、抠图、HTML 截图等),同时内置免注册、免 Key 的匿名视觉链兜底,整套链路不依赖 Python。
核心能力
- 让图片轮像普通文本轮一样工作:用户粘贴图片,Agent 通过 vision_describe 看图作答,并可连续多步操作(ground → crop → describe → pixel_diff)
- 自动把已有模型组镜像成「+ 自动识图」入口,原模型组保持不变,新模型/包装范围变更后热更新
- 内置 OVHcloud 匿名免费视觉链兜底(5 个 Qwen/Mistral 模型),不填任何 Key 也能用
- 提供像素级视觉工具:定位、裁剪、逐像素对比(差异率 + 红色热力图)、取色、SVG 矢量化、纯色背景抠图
- 视觉答案按附件内容哈希缓存,后续文字轮用记录描述替换历史图片,DeepSeek "记得"之前发过的图而不重复消耗视觉调用
- 自动故障降级链:地区限制/风控/额度/限流/上下文超长/网络故障,逐供应商尝试,429 触发熔断冷却
技术实现
- 语言: JavaScript (ESM) / CommonJS 客户端产物
- 关键依赖: sharp(图像处理)、potrace(位图矢量化)、puppeteer-core + 系统 Chrome(HTML 截图)、undici(HTTP 调用)、@deepseek-ai/schemastery(配置 Schema)
- 架构模式: Cordis 插件 + bundle patch 自挂载,注入
tools、llm;entry.js 标准化 progressiveTools 默认值后调用核心 apply;客户端走dsh.client注入到dsh-client-ui-settings/dsh-client-runtime/dsh-client-connection/dsh-api-remotes - 入口文件: entry.js → index.js(核心 7222 行),客户端 bundle 入口 lib/client.js;同时暴露
dsh-vision-routerCLI(lib/doctor-cli.js)用于诊断/修复
适用场景
适合在 DSH Web 里发图给 DeepSeek 风格 Agent 的用户:粘截图让它看 UI 错误、贴参考图让它复刻页面、贴产品图让它描述差异、贴长聊天截图让它转写。需要 UI 还原的可验证闭环(设计稿 → HTML → vision_pixel_diff 量化差异 → 修复 → 再比),或希望用本地 Ollama/LM Studio 离线识别敏感图片的用户都能直接受益。
前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | >= 0.1.0-rc.6 | peer 依赖声明 @deepseek-ai/dsh-anonymous-user-id 与 @deepseek-ai/dsh-llm-deepseek ^0.1.0-rc.6;安装时需 --profile web |
| Node.js | >= 22 | engines.node 声明 |
| sharp | >= 0.35.3 < 1 | peer 依赖;pnpm onlyBuiltDependencies 允许原生编译;profile 内残留 0.34.0 会与宿主 0.35.3 冲突(DLL 报错) |
| 系统工具 | 视工具而定 | vision_html_screenshot 需要 Chrome/Chromium/Edge;vision_screenshot(macOS/Windows 用系统截屏,Linux 需 ImageMagick import 或 scrot);vision_ocr 本地 tesseract 缺失时自动回退视觉模型 |
| 平台 | macOS / Windows / Linux | 核心工具跨平台;桌面截屏工具按平台有差异 |
安装方式
dsh plugin --profile web add github:ysr666/dsh-vision-router
配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| providers | 数组 | 多供应商视觉后端链,按序尝试;新装用户默认含一条内置免费 vision-http | [{"provider":"vision-http","model":"ovh/Qwen3.5-397B-A17B"}] |
| httpProviders | 数组 | OpenAI 兼容直连端点(智谱/阿里百炼/Groq/OpenRouter 等),优先于内置免费链 | [] |
| autoWrapProviders | 布尔 | 自动把已启用模型镜像成「+ 自动识图」组,模型目录变化热更新 | true |
| wrappedProviders | 数组 | 手动包装范围(关闭自动包装或限定部分模型时使用) | [{provider:"deepseek-official",models:[]}] |
| routing | 布尔 | 旧版整轮链路由(一次性整轮切视觉模型);关闭走工具优先流程(推荐) | false |
| stealth | 布尔 | 接管官方 deepseek-official 路由(仅官方行;自定义路由由自动包装处理) | false |
| progressiveTools | 布尔 | 渐进式工具挂载(首次需要时再展开完整 14 个工具);默认关闭以稳定长会话 prefix/KV 缓存 | false |
| structuredVisionBootstrap | 布尔 | 1+x 结构化预识别(先建立任务无关证据底图再进行后续视觉调用) | false |
| desktopScreenshot | 布尔 | 暴露 vision_screenshot 桌面截屏工具的隐私开关 | false |
| freeFallback | 布尔 | 在显式本地/自定义 HTTP 后端之后追加匿名 OVH 免费链 | true |
| localOllama | 对象 | 本地 Ollama 视觉后端(启/关、地址、模型名、OpenAI/Anthropic 协议、可选 temperature/top_p) | {enabled:false,baseURL:"http://127.0.0.1:11434/v1",model:"qwen2.5vl",format:"openai"} |
| localLmStudio | 对象 | 本地 LM Studio 视觉后端(同 Ollama;model 必须填 Developer 页或 /v1/models 返回的真实标识) | {enabled:false,baseURL:"http://localhost:1234/v1",model:"",format:"openai"} |
| instantDescribe | 布尔 | 图片轮第一模型步之前用本地后端识别无缓存图片(Ollama→LM Studio,失败回退静态工具标记) | false |
| downscale / downscaleMaxPixels | 布尔 / 数字 | 调用前自动压缩超大图(延迟保护) | true / 4000000 |
| cache / cacheTtlSeconds / cacheMaxEntries | 布尔 / 数字 | 视觉答案缓存 | true / 3600 / 200 |
| timeoutMs | 数字 | 单次视觉调用超时 | 120000 |
| proxy / proxyHosts | 字符串 / 数组 | 仅视觉供应商域名走本地代理(DeepSeek 保持直连) | "" / openrouter 等 9 个默认域名 |
| artifactsDir | 字符串 | 产物目录(相对会话工作区) | .dsh-vision-router/artifacts |
常见问题
Q: 安装后需要额外配置吗?
A: 不需要。插件自带 bundle 补丁并默认开启内置 OVH 匿名免费视觉链;装完只需在聊天页右下角切换到带「+ 自动识图」的模型组即可发图。
Q: 不开 API Key 能用吗?
A: 可以。默认链内置 5 个 OVHcloud 匿名视觉模型,免注册免 Key,每 IP 每模型 2 次/分钟。额度不够时可在设置卡里加一条带 Key 的 httpProviders(智谱 glm-4v-flash 等)即可。
Q: 为什么聊天页说"当前模型不支持图片"?
A: 插件不修改原模型组。需要在聊天页右下角的模型选择器里切换到带「+ 自动识图」的组才能发图;选择原纯文本组发图会被宿主直接拦截。
Q: 支持哪些平台?
A: 跨平台运行。vision_screenshot 在 macOS/Windows 使用系统截屏能力,Linux 需要安装 ImageMagick 的 import 或 scrot;vision_html_screenshot 需要 Chrome/Chromium/Edge;其他工具无浏览器也能用。
Q: 视觉工具有哪些?
A: 共 13-14 个默认挂载:vision_describe(看图问答)、vision_ground(像素定位)、vision_detect(元素清单)、vision_crop(裁剪)、vision_pixel_diff(像素对比 + 热力图)、vision_colors(取色)、vision_ocr(文字转写)、vision_trace(SVG 矢量化)、vision_extract_foreground(抠图)、vision_present(持久展示图片)、vision_materialize(附件落盘)、vision_html_screenshot(HTML 截图)、vision_long_screenshot_ocr(长截图转写),加上 vision_bootstrap 结构化预识别;隐私敏感的 vision_screenshot 默认关闭,开启后为第 14 个。
Q: 能否纯本地离线识别?
A: 能。开启 localOllama.enabled 或 localLmStudio.enabled 后,本地后端排在 HTTP 视觉链最前,不通则自动降级;配合 instantDescribe: true 可在第一模型步之前完成本地识别。
Q: 升级后 DSH 启动报"duplicate loader entry id: vision-router"?
A: profile 目录的 cordis.patch.yml 里残留了 v0.x 时代的手动插入块,与插件自带的 bundle 补丁重复。删除整块 insert: 段,或改写为按 id 覆盖行后重启即可。
Q: 如何卸载?
A: 执行 dsh plugin --profile web remove github:ysr666/dsh-vision-router,包装路由、工具、技能与设置卡片会被一并移除,已生成的产物文件保留。
上手难度
入门 — 默认配置即可发图工作,零配置起步;进阶用户可通过设置卡或 profile 补丁调整视觉后端链、本地后端、缓存、代理等参数。
已知问题与限制
- DSH profile 目录的
package.json若被某些编辑器保存为 UTF-8 with BOM,启动时dsh web会报Unexpected token ... is not valid JSON;可用插件自带的npx dsh-vision-router repair --profile web去掉 BOM - 旧版 profile 内残留 sharp 0.34.0 与宿主 0.35.3 同进程 DLL 冲突(issue #42/#75),像素工具报
colourspace: parameter space not set;v1.2.2+ 会在检测到残留时主动告警 - Oh-DSH Desktop ≤ 0.1.5 内置 DSH 0.1.0-rc.5,v1.4.1 及更早版本会让该运行时启动崩溃(报
configurable provider "deepseek-official" is already declared);需安装 v1.4.2+ - 同时安装
dsh-web-ui/dsh-web-ui-all时,其dsh-tool-describe-image发送钩子可能先于本插件改写图片块;需在「图像理解」设置里关闭"发送时改写图片为 describe-image 引用" - pnpm v11 会静默拦下发布不足 24 小时的新版本,导致
update报downloaded 0/added 0;需显式add dsh-vision-router@<版本号>或跑npx dsh-vision-router repair - vision_html_screenshot 依赖系统安装的 Chrome/Chromium/Edge,未检测到时该工具直接报错而不自动安装
- vision_ocr 本地引擎(tesseract)缺失时静默回退到视觉模型,OCR 速度与额度按视觉模型计
- vision_screenshot 默认关闭,开启后 macOS/Windows 走系统截屏能力;Linux 需安装 ImageMagick
import或scrot且必须处于可截取桌面会话(Wayland 支持取决于环境)
dsh-vision-router
Paste an image and it just works — eyes for text-only agents on DeepSeek Harness. Free out of the box, no key, no Python, one command.
DeepSeek keeps thinking; the built-in free vision chain and fourteen deep tools do the seeing. Image turns behave like ordinary tool-calling turns — grounded, measurable, repeatable.
English · 中文
💬 QQ community group: 1105463028
[!WARNING] 📌 Announcement (v1.5.3)
v1.5.3: Fixes the rc.6 update-check routes (#160), guards repetition-loop vision output, rewords misleading copy, and logs every backend vision call.
Contents
- Why this exists
- How it compares
- Design lineage
- Acknowledgements
- Quick start
- Free vision key channels
- Highlights
- How it works
- Tools
- Configuration
- Install and lifecycle
- Troubleshooting
Why this exists
Most DSH vision plugins bridge images to DeepSeek as text descriptions — lossy, one-shot, and blind to pixels. This plugin keeps the original pixels on the vision model's side and DeepSeek on the reasoning side, and makes looking at an image an ordinary tool call:
- One command install. The package ships its own composition patch (
dsh.bundle.patch):dsh plugin addwires the row, the admission wrapper and the attachment limits automatically — zero manual file edits. Taking over the official DeepSeek route is an optional setting (stealth mode, off by default). - Free by default. Vision tools end with a five-model OVHcloud anonymous fallback: no account, no key, 2 requests/minute per IP per model, roughly 10 RPM in theory across independent buckets. User-provided vision models run first.
- No Python. The whole pipeline — downscale, grounding, crop, pixel diff, palette, OCR, SVG trace, cutout, HTML screenshot — runs on sharp / potrace / tesseract / system Chrome.
- Continuous multi-step image work. An image turn is a text turn that calls tools:
vision_ground→vision_crop→vision_describe→vision_pixel_diff→ fix → screenshot again. The agent keeps iterating until the work is done. - DeepSeek stays the brain. Text turns are untouched in model, cost and context. The vision model is only the eyes, called on demand; answers are cached by image content.
- Transparent to the user. Uploaded images keep rendering as images in the conversation UI; the rewrite that points the model at the vision tools happens only inside the model call, never in the session log.
How it compares
One-line take: most dsh vision plugins turn images into text descriptions for DeepSeek (description bridge — lossy); this plugin hands the image turn straight to a vision model (routing bridge — pixel-faithful), with a built-in keyless free fallback.
| Manual model switching | MCP vision bridge | dsh-vision-router | |
|---|---|---|---|
| Pixel fidelity | ✅ full (when switched) | ❌ text description only | ✅ full, on the image turn |
| Automatic | ❌ | ✅ | ✅ |
| Daily model untouched | ❌ (whole session swapped) | ✅ | ✅ |
| Provider failure recovery | ❌ | ❌ | ✅ fallback chains |
| Reusable structured queries | — | partial | ✅ JSON mode + caching |
| Free out-of-the-box | ❌ | ❌ | ✅ built-in keyless endpoint |
| Fits dsh composition | — | external server | ✅ one plugin row |
Difference from existing dsh community projects (all excellent, each with its own focus; descriptions reflect their READMEs as of 2026-08):
| Project | Approach | What this plugin adds |
|---|---|---|
| dsh-vision-sidecar | Pre-describes images with an external VLM; the description joins the session as a message to DeepSeek; LLM7.io anonymous endpoint by default (OVHcloud listed as a no-key alternative) | Description bridge; this plugin adds raw-image routing, with vision_describe covering descriptions on demand |
| dsh-vision-proxy | Wraps a provider route and transcribes images into text in the request stream | Transcription bridge; this plugin wraps no provider — it rewrites routing through agent/request waterfalls |
| dsh-vision-provider | Registers DeepSeek + Vision combined routes: images are described by the chosen vision model before reaching DeepSeek | Two-model bridge idea; this plugin adds automatic routing, fallback chains and tools on top |
| modlens | The first dsh vision plugin; reuses local Claude Code/Codex/OpenCode/Pi logins as vision engines | Engine-reuse idea; this plugin ships its own provider chain and depends on no other local CLI |
| dsh-vision-toolkit | Ten intent-aware visual tools (Q&A/OCR/pixel verification/UI restoration), called explicitly on demand | Broader tool set; this plugin adds whole-turn auto-routing and a keyless free fallback |
| dsh-tool-vision | An inspect_image tool plus an agent/pre-step waterfall bridge (pasted images become tool hints before entering the log) | Similar waterfall bridge; this plugin adds turn routing, fallback chains, caching and the free endpoint |
Design lineage
The deep-vision tool layer and UI-restoration workflow in this project were informed by Anionex/agent-vision-toolkit and its native DSH implementation Anionex/dsh-vision-toolkit. In particular, this project drew on their intent-driven tool selection, progressive tool exposure, pixel-diff verification loop, and parts of the visual-tool decomposition and naming, including long-screenshot OCR, foreground extraction, and HTML screenshot tooling.
All code in dsh-vision-router is independently implemented. On top of those referenced design ideas, this project independently developed its turn-level/tools-first vision routing, DSH admission/wrapper integration, multi-backend provider and failure fallback chains, built-in free vision chain, attachment/image-memory handling, caching, and related runtime resilience mechanisms.
We appreciate Anionex's prior work and the broader DSH community. Clear attribution and independent iteration can coexist; both help keep the DSH ecosystem open, collaborative, and healthy.
Acknowledgements
This project borrows ideas from all of the above — especially the keyless free-endpoint exploration (LLM7.io and OVHcloud anonymous tiers) by dsh-vision-sidecar. Thanks to the authors of dsh-vision-proxy, dsh-vision-provider, modlens, dsh-vision-toolkit, and dsh-tool-vision.
Quick start
1. Install the plugin
For normal npm/npx installs, installation is a single command:
npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router
[!NOTE] Third-party
dsh-web-plugin-manager/dshpmv0.4.2+ is also compatible: its quality gate now correctly allows@deepseek-ai/schemasteryas a runtime dependency. The official DSH CLI above remains the recommended install path.
If you run DeepSeek Harness from a source checkout with pnpm, use the workspace script instead — dsh is not necessarily on your shell PATH:
cd deepseek-harness
pnpm dsh plugin --profile web add dsh-vision-router
If you already installed the DSH CLI globally and dsh is on PATH, the shorter dsh ... form works too. After installation, start or reload DSH Web as you normally do.
[!NOTE] If you install the plugin into a Web process that was already running long-term, let that DSH Web process reload once so the plugin bundle itself is discovered. After the plugin is loaded, adding/removing models or changing wrapper scope hot-updates without further DSH restarts.
2. Switch to a “+ Auto Vision” model group in chat
Once loaded, the plugin discovers the model groups enabled under Settings → Models and creates same-name auto-vision entries. For example:
opencode-go ← original model group, unchanged
opencode-go + Auto Vision ← choose this when sending images
[!IMPORTANT] Before sending an image, open the model selector in the lower-right corner of the chat composer and choose a group marked “+ Auto Vision”.
Vision Router deliberately does not modify the original model group. If the conversation still uses the original text-only opencode / DeepSeek route, DSH can reject the image with “the current model does not support images” before Vision Router gets a chance to handle it. That is a model-entry selection issue, not a broken vision backend.
The auto-vision group follows the live DSH model catalog. Adding models or changing wrapper scope does not require a restart.
3. Paste or upload the image
After choosing the “+ Auto Vision” group, paste or upload an image normally. By default the complete vision tool schema is stable from session start, so the agent can immediately use vision_describe, vision_ground, vision_crop, and the rest across multiple steps when needed.
The built-in anonymous OVH vision fallback is already configured, so normal image use needs no signup or API key. The lower-right chat picker selects only the brain/conversation model; vision backends do not belong there. Advanced options live under Settings → Plugins → Plugin config → 视觉路由(自动识图): each vision-backend row may select any callable generative user model already configured under Settings → Models. DSH image-capability metadata is advisory only: undeclared or text-only-labelled models remain selectable and show a warning. At runtime Vision Router always tries the provider's registered DSH adapter first — including WebSocket, RPC and private transports — and falls through on a real failure. The direct compatibility bridge is used only when an http(s) OpenAI Chat Completions endpoint is positively identified. Leaving every user row empty is valid; the OVH chain remains the final fallback. Vision HTTP is an internal transport route, not a model group users should select.
See it in action
Left: an image turn — the user sends a picture, the agent calls vision_describe through the free chain and answers. Right: the finished structured answer.
Free vision key channels
The built-in OVH fallback is anonymous by design, and OVH caps anonymous use at 2 requests/minute per IP per model. If that feels tight, every channel below offers free vision models with much higher quotas — all of them are free to register, and none charges for the free tier. Free policies rotate often; treat this table as an August 2026 snapshot and double-check each provider's console before relying on it.
| Channel | Free vision model(s) | Free quota | CN direct? | Where to get the key |
|---|---|---|---|---|
| OVHcloud AI Endpoints (access key) | Qwen2.5-VL-72B-Instruct — the same endpoint the built-in fallback uses | 400 req/min per project per model (vs 2 anonymous) | ✅ | OVH account → Public Cloud project (attach a payment method; free models are not charged) → AI Endpoints access key |
| Zhipu (bigmodel.cn) | glm-4.6v-flash · glm-4.1v-thinking-flash · glm-4v-flash — three permanently free models; chaining them triples capacity | uncapped tokens | ✅ | open.bigmodel.cn → API keys |
| DashScope (Aliyun) | qwen3-vl-flash (limited-time free) and the Qwen-VL series | new users: 1M tokens per model series / 90 days | ✅ | bailian.console.aliyun.com |
| Intern AI (Shanghai AI Lab) | internvl-latest · internvl3.5-latest | 30 RPM, 90M tokens/month | ✅ | chat.intern-ai.org.cn |
| Groq | meta-llama/llama-4-scout-17b-16e-instruct (native multimodal, up to 5 images) | 30 RPM / 14,400 req/day, no card | ❌ proxy | console.groq.com |
| Google AI Studio | gemini-2.5-flash · gemini-2.5-flash-lite | 10–30 RPM / 500–1,500 req/day | ❌ proxy | aistudio.google.com |
| NVIDIA NIM | meta/llama-3.2-11b-vision-instruct · nvidia/nemotron-nano-12b-v2-vl | 40 RPM, no card | ⚠️ | build.nvidia.com |
| OpenCode Zen | mimo-v2.5-free (vision + code) | 30 RPM / 500 req/day | ⚠️ | opencode.ai/zen |
| OpenRouter | google/gemma-4-26b-a4b-it:free · google/gemma-4-31b-it:free | 50 req/day on unpaid accounts | ❌ proxy | openrouter.ai |
Any of these channels can join the vision chain as an httpProviders entry (key in the matching environment variable or ~/.dsh/.credentials.yaml), and the chain tries your entries before the anonymous fallback.
[!NOTE] Free-tier policies change without notice — Cerebras retired its free tier in July 2026 (now a one-time $5 credit), SambaNova's free tier is down to 20 requests/day, and Hugging Face's is $0.10/month. Third-party “
:freerelay” aggregators are deliberately not listed: they rotate quickly, lack SLAs, and some resell quota in ways that violate upstream terms.
Highlights
- Original pixels, real answers. The vision chain reads the image at original resolution (auto-downscaled only to protect latency/quota); the agent's question travels with the image, so answers are about your question, not a generic description.
- Automatic failover with classified errors. Region blocks, ToS filtering, 402 quota, 429 rate limits, context overflow, network failures — the chain walks providers one by one and only reports after all of them failed, with actionable advice. A 429 immediately advances to the next backend and opens a Retry-After-aware cooldown instead of sleeping inside the request.
- Image memory. Vision answers are cached by attachment content hash; later text turns substitute the recorded description (marked as untrusted evidence), so DeepSeek genuinely remembers earlier images without re-spending vision calls.
- A verifiable pixel loop. Reference →
vision_html_screenshot→vision_pixel_diff(ratio + red heatmap + worst-region ranking) → fix → repeat until the mismatch converges. UI restoration becomes measurable instead of eyeballed. - Stable tool schema. All fourteen deep tools are registered from session start by default, avoiding a mid-conversation tool-list expansion that can invalidate long-context KV/prefix caches.
progressiveTools: trueremains an advanced boot-time opt-in; only then doesvision_activatemount the tools on demand. Seedocs/progressive-tools-cache.md. - Selective proxy. Only the configured vision provider hosts go through your local proxy; DeepSeek stays direct.
Pixel loop in practice
Click the image to open the full-resolution original.
The agent rebuilt the UI from the reference image, then verified the final result with vision_pixel_diff: 2.54% final diff (32,939 / 1,296,000 differing pixels, threshold 16/channel).
How it works
The vision model is only the eyes; DeepSeek is always the brain. An image turn is never hijacked by a one-shot vision answer — the agent drives the tools itself and can keep operating on the image across as many steps as the task needs.
Tools
Default progressiveTools: false: all fourteen deep tools stay registered from plugin startup, so text and image turns can call them immediately. If you explicitly set progressiveTools: true in the profile/composition cordis.patch.yml, progressive mode is restored: only vision_activate is exposed initially, the full tool set mounts on first use, and the vision-tools skill is registered. This is a boot-time switch; restart DSH after changing it. Built on sharp / potrace / tesseract / system Chrome — no Python:
The diagram covers the eleven image-processing tools. vision_present (durable image delivery) and vision_bootstrap (the optional 1+x structured first pass) bring the default deep-tool set to fourteen. Enabling the privacy-gated vision_screenshot at boot adds an optional fifteenth tool.
| Tool | What it does | Artifact |
|---|---|---|
vision_bootstrap | Optional 1+x structured first visual pass; establishes task-independent evidence before at least one follow-up vision call | — |
vision_describe | Image Q&A / multi-image compare / structured-evidence JSON mode (summary + layout regions + entity inventory + verbatim transcription) | — |
vision_materialize | Copy an authorized attachment into the session workspace and return a filesystem path for local OCR/parser fallbacks; no vision/network call | image copy |
vision_ground | Locate a target → original-pixel box x1/y1/x2/y2 | annotated PNG (optional) |
vision_detect | Numbered inventory of every element of a kind (buttons/inputs/links…) with original-pixel boxes | annotated PNG with numbered boxes |
vision_crop | Crop and zoom into a pixel box | PNG |
vision_present | Publish a generated or edited local image as a durable chat attachment so the user can see it | image attachment |
vision_pixel_diff | Per-pixel comparison: diff ratio + worst 8×8-grid regions | red heatmap PNG + JSON report |
vision_colors | Dominant colors (hex + share) | — |
vision_ocr | Text transcription: local tesseract (chi_sim+eng) first, vision model fallback | — |
vision_trace | SVG vectorization (potrace posterization; icons/logos) | SVG |
vision_extract_foreground | Cutout via border flood fill (uniform backgrounds) | transparent PNG |
vision_html_screenshot | Screenshot a local HTML file (headless system Chrome); fullPage: true captures the whole page and reports pageHeight | PNG |
vision_screenshot | Disabled by default; explicit opt-in required. Capture the Windows virtual screen, macOS main display, or Linux root display. Windows uses PowerShell CopyFromScreen, macOS uses screencapture, and Linux requires ImageMagick import or scrot; optional identify=true tries enabled local recognition backends in order and returns the description with the path | PNG / + description |
vision_long_screenshot_ocr | Long-screenshot transcription: overlapping chunks, tesseract first / vision model fallback, stitched Markdown | chunk PNGs + Markdown + manifest |
Formats are sniffed from magic bytes, so extensionless content-addressed attachment files work everywhere (no .png renaming needed).
Common workflows
vision_ground image="ref.png" target="the send button"
vision_detect image="page.png" target="input fields"
vision_crop image="ref.png" region="1067,841,1108,881"
vision_present path="rebuilt.png"
vision_describe paths=["ref.png","impl.png"] question="list the differences" json=true
vision_pixel_diff original="ref.png" rebuilt="screenshot.png"
vision_ocr image="screenshot.png"
vision_colors image="ref.png" top=8
vision_trace image="icon.png" steps=4
vision_extract_foreground image="logo.png"
vision_html_screenshot source="page.html" width=1200 height=720
vision_html_screenshot source="page.html" width=1200 height=720 fullPage=true
vision_long_screenshot_ocr image="chat-log.png" chunkHeight=1200 overlap=120
Provider fallback chain
The vision tools try backends in order and surface an error only after all of them fail:
- User vision models: one per settings row, top to bottom; only models under Settings → Models that explicitly declare image input are shown;
- Local Ollama (optional, off by default):
localOllama.enabledadds keyless, offline recognition through your local Ollama (for example qwen2.5vl); - Local LM Studio (optional, off by default):
localLmStudio.enabledfollows Ollama and requires the real model identifier shown in LM Studio Developer or returned by/v1/models; - Advanced custom HTTP vision endpoints: legacy/advanced
httpProviders, when present, run after the local backends; - Built-in anonymous OVH fallback: always last and never exposed in a model picker. The current quality-first chain is
Qwen3.5-397B-A17B→Qwen2.5-VL-72B-Instruct→Qwen3.6-27B→Mistral-Small-3.2-24B-Instruct-2506→Qwen3.5-9B. OVH anonymous limits are 2 requests/minute per IP per model. The five models have independent buckets, so spreading requests across them is about 10 RPM in theory, subject to OVH's actual rate limiting. No signup or API key is required. Want more headroom? See Free vision key channels — a free OVH access key lifts this same endpoint to 400 requests/minute.
[!IMPORTANT] This “vision chain” is the eyes used by Vision Router: each settings row selects one user vision model, while the lower-right chat picker selects the brain/conversation model. The two are deliberately separate. Text-only DeepSeek/opencode models are filtered out of the vision-backend dropdown, and the internal
Vision HTTPtransport route is no longer exposed to users.
In the legacy
routing: truemode, the whole-turn chain walks onlyprovider + fallbacks—httpProviders(including the free fallback) do not participate there. The defaultrouting: false(tools-first) tries everything.
Failures are classified (region / tos / quota / rate-limit / context / network) and the final error carries advice; a 429 advances immediately to the next backend and applies a capped, Retry-After-aware circuit-breaker cooldown. Oversized uploads are downscaled before the call (default budget 4 MP) to keep tool calls fast.
Stealth mode
Stealth mode is off by default (explicit opt-in since issue #34): with it off, the official deepseek-official route stays untouched and image turns go through the visible "DeepSeek + 自动识图" wrapper entry in the picker.
With stealth on, the plugin takes over the official deepseek-official route: the model picker looks exactly like stock (same DeepSeek group, same model names), but each entry is the auto-vision wrapper that declares image input and delegates text turns to a rebuilt native DeepSeek adapter (same llm-deepseek settings section and credentials). Old sessions keep working through the hidden deepseek-vision alias. The takeover requires the stock row to be absent — disable it in your profile patch layer (~/.dsh/profiles/<profile>/cordis.patch.yml):
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
disabled: true
With the stock row present, the plugin falls back to the visible wrapper entry. Conversely, with stealth off but the stock row still disabled, the plugin performs a keep-alive takeover so the DeepSeek models don't vanish (the settings card explains this); to restore the fully official route, flip the disabled above back to false and restart.
Stealth mode only affects the official DeepSeek route. Custom/third-party routes such as opencode are auto-wrapped into “+ Auto Vision” groups by default.
Auto-vision model groups and manual wrappers
autoWrapProviders is on by default. The plugin discovers the provider/model entries currently enabled under Settings → Models and registers a same-name “+ Auto Vision” model group for them. The original group is never changed: choose the auto-vision group for images, or keep using the original group for plain text. DSH llm/adapters-updated events are synced live, so adding/removing models does not require a restart.
wrappedProviders is an optional manual scope control, not a required setup step. Use it only when:
- automatic wrapping is off and you want to pick which provider/models receive an auto-vision entry; or
- automatic wrapping remains on but one provider should expose only selected models in its “+ Auto Vision” group.
The settings card uses provider + model dropdowns; an empty model means every model on that route. Add multiple rows to select multiple models. Changes apply immediately with no restart.
Web settings
The Web profile registers a 视觉路由(自动识图) card under Settings → Plugins → Plugin config. Its top callout spells out the only step most users need: return to chat → lower-right model selector → choose a “+ Auto Vision” model group → send the image. The remaining controls are advanced customization:
- Auto-create “+ Auto Vision” model groups: enabled by default; follows the live model catalog with no restart;
- Manual auto-vision scope (optional): only for disabling auto-wrap or limiting selected models;
- Vision backend chain: the real image-capable models used by
vision_describeand friends; the built-in free Qwen is normally enough, and text-only models should not be placed here; - switches for legacy whole-turn routing, vision tools, image-block rewriting and stealth mode (official DeepSeek route only);
- timeout, wrapper/chain route names, proxy and other advanced parameters;
- every field shows an overridden badge with one-click reset plus discard/save;
- a Test connection button prioritizes an enabled local backend, verifies that its configured model appears in
/v1/models, and otherwise probes the first usable vision provider; - artifact-producing tools render dedicated call cards with result facts and open-file buttons.
PR #8 upgrades the panel with catalog-driven model dropdowns, add/remove fallback rows, and proxy settings.
Configuration
Everything is optional; defaults work out of the box. Edit via the Web card or a profile patch:
| Field | Default | Meaning |
|---|---|---|
provider / model | vision-http / ovh/Qwen2.5-VL-72B-Instruct | shorthand vision backend route (adapter-backed provider + model that genuinely accepts images) |
fallbacks | [] | backup image models for the shorthand vision provider |
providers | built-in free vision-http pair | multi-provider vision backend chain { provider, model, fallbacks[] }, tried in order; do not put text-only models here |
httpProviders | built-in OVH entry | direct OpenAI-compatible endpoints { name, baseURL, model, apiKeyEnv, maxTokens } |
autoWrapProviders | true | discover enabled provider/models and live-sync same-name “+ Auto Vision” groups; original groups stay unchanged |
wrappedProviders | [{ provider: 'deepseek-official', models: [] }] | optional manual wrapper scope { provider, models[] }, used after disabling auto-wrap or to restrict one provider to selected models; changes apply live, no restart |
routing | false | legacy whole-turn chain routing (one-shot answer). false = tools-first flow (recommended) |
reverseRouting | true | with routing: true, route text turns back to textProvider |
wrapperRoute / chainRoute | deepseek-vision / vision-chain | admission wrapper route name / fallback chain route name (empty disables) |
stealth | false | take over the official deepseek-official route (official row only; custom routes are auto-wrapped by default) |
textProvider | deepseek-official / deepseek-v4-pro | the model that reasons (your daily model) |
tool / progressiveTools / autoActivateOnImage | true / false / true | vision tools on / progressive mounting (off by default for a stable tool schema) / image-turn auto-mount when progressive mode is enabled; progressiveTools is boot-time config |
rewriteImages | true | rewrite image blocks in the model input (cached description or tool-hint marker); the UI log keeps images |
desktopScreenshot | false | privacy opt-in for the model-callable vision_screenshot desktop-capture tool; checked live before every capture |
freeFallback | true | append the anonymous OVH models after explicit local/custom HTTP backends; turning this off never disables an explicitly configured local backend |
localOllama | { enabled: false, baseURL: 'http://127.0.0.1:11434/v1', model: 'qwen2.5vl', format: 'openai' } | Local vision backend (merged from dsh-vision): when enabled, local-ollama leads the HTTP vision chain; skipped automatically when Ollama is down; format selects openai (/chat/completions) or anthropic (/messages); optional temperature / top_p are sent only when explicitly set, otherwise the local server default is respected |
localLmStudio | { enabled: false, baseURL: 'http://localhost:1234/v1', model: '', format: 'openai' } | Local LM Studio backend (merged from dsh-vision): follows Ollama and precedes custom/cloud HTTP backends; enabling it requires the real model identifier shown in LM Studio Developer or returned by /v1/models; supports the same optional sampling fields, while format: 'anthropic' requires LM Studio 0.4.1+ |
instantDescribe | false | Instant local translation (merged from dsh-vision): when on and at least one local backend is usable, uncached image blocks are recognized before the first model step; Ollama is tried before LM Studio with a shared timeout budget, multi-image batches run concurrently (up to 3), and failures fall back to the static tool-hint marker |
localDescribeStyle | plain | Local recognition output style (merged from dsh-vision): plain = flat description; structured = structured recognition (【初步判断】/【细节】/【空间结构】/【原图尺寸】), better for screenshot analysis |
downscale / downscaleMaxPixels | true / 4000000 | pre-call downscale and its pixel budget (latency guard) |
cache / cacheTtlSeconds / cacheMaxEntries | true / 3600 / 200 | vision answer cache |
timeoutMs | 120000 | per vision call deadline |
artifactsDir | .dsh-vision-router/artifacts | artifact directory (relative to the session workspace) |
proxy / proxyHosts | '' / openrouter hosts | optional proxy for vision provider hosts only |
catalogCorrections | true | built-in catalog-routing corrections: when the installed pi-ai catalog routes a known model to the wrong wire protocol (e.g. opencode-go/qwen3.6-plus to OpenAI chat completions while OpenCode Go only serves it on /v1/messages), the plugin answers that backend directly over the corrected protocol. Each correction disarms itself once the catalog is fixed upstream |
Local Ollama vision backend (merged from dsh-vision)
Incremental author: shaoqiuyuavailable (router local-vision increment)
Design credit: the local vision backends (Ollama / LM Studio dual backends, instant recognition, structured output, screenshot identification, same-image memory dedup, failure-fallback placeholder, concurrency anti-snowball, timeout protection) inherit their design from dsh-vision — merged into the HTTP vision chain here, with per-level fallback and dual-protocol support added on top.
An optional keyless local-first vision path for private, free, offline recognition. It plugs into the existing HTTP vision chain as local-ollama; if it fails, any configured cloud backends can still be tried unless you deliberately configure a local-only chain.
1. Install Ollama and pull a vision model
# https://ollama.com — then:
ollama pull qwen2.5vl
2. Enable it — in the settings card's "Local vision" group, or via a profile patch:
- id: vision-router
config:
localOllama:
enabled: true
baseURL: 'http://127.0.0.1:11434/v1' # OpenAI-compatible endpoint
model: 'qwen2.5vl'
temperature: 0.5 # optional; low temperature is steadier for recognition
top_p: 0.8 # optional; unset = server default
instantDescribe: true # recognize images on the first model step
localDescribeStyle: 'structured' # 'plain' | 'structured'
3. What happens
- When enabled,
local-ollamaheads the HTTP vision chain. For a strict local-only setup, remove cloud vision rows/custom HTTP endpoints and turn offfreeFallback. - LM Studio works the same way — enable
localLmStudioin the same "Local vision" group with its OpenAI-compatible endpoint (defaulthttp://localhost:1234/v1) and enter the exact model identifier shown in Developer or/v1/models. It sits afterlocal-ollamaand before custom/cloud HTTP backends. - Each local backend can speak OpenAI or Anthropic format via
format(defaultopenai). Anthropic mode routes to/v1/messageswithanthropic-versionand base64 image sources;x-api-keyis sent only when a key is configured. LM Studio needs version 0.4.1 or newer for this endpoint. - If a local backend is down or the call times out, its entry is skipped automatically and the chain falls through to the cloud backends — no call breaks.
instantDescribetries enabled local backends in order (Ollama, then LM Studio) before the first model step. Multiple uncached images run concurrently (up to 3); one failed image does not block the others, and attachment-memory hits are reused without another local request.vision_screenshotis disabled by default. After the separate Desktop screenshot opt-in,identify=trueuses the same Ollama → LM Studio fallback.- Verify runtime decisions with
image turn — instantDescribe=… localBackends=…and results withinstant local describe recognized N/M uncached image(s), C cached, F failed attemptsin the log.
Requirements
- DeepSeek Harness Web profile. Normal installs can use
npx @deepseek-ai/dsh ...; source checkouts usepnpm dsh .... A baredsh ...command only works when the CLI is already on your shellPATH. - Node ≥ 22 (host side).
- No API key for the default free chain; a credential reference (
apiKeyEnv) only for paidhttpProviders. - Chrome / Chromium / Edge is needed only for
vision_html_screenshot; every other tool works without a browser. - Desktop capture is opt-in. Windows and macOS use OS-provided capture facilities; Linux needs ImageMagick
importorscrotand a capturable desktop session (Wayland support depends on the environment). - Tesseract is optional:
vision_ocrfalls back to the vision model when the local engine is absent.
Install and lifecycle
Install
Normal npm/npx install — one command:
npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router
From a DeepSeek Harness source checkout:
pnpm dsh plugin --profile web add dsh-vision-router
Optional verification:
npx @deepseek-ai/dsh --profile web --dump-config | grep vision-router
# source checkout: pnpm dsh --profile web --dump-config | grep vision-router
When first adding the plugin to an already long-lived Web profile, let that Web process reload the plugin bundle; the host discovers the browser bundle through dsh.client at startup. After the plugin is loaded, model-catalog and wrapper-scope changes hot-update and do not require a restart.
Oh-DSH Desktop
Oh-DSH Desktop ships its own packaged DSH runtime and its own home layout: the desktop surface runs the desktop profile under ~/.ohdsh and does not load ordinary ~/.dsh profiles. The --profile web commands above therefore install into the wrong environment on that product.
Install into the desktop profile by pointing DSH_HOME at the Oh-DSH home:
DSH_HOME=~/.ohdsh npx @deepseek-ai/dsh plugin --profile desktop add dsh-vision-router
(Windows PowerShell: run $env:DSH_HOME = "$env:USERPROFILE\.ohdsh" first, then the same command.)
[!WARNING] Oh-DSH Desktop ≤ 0.1.5 bundles DSH
0.1.0-rc.5.dsh-vision-routerv1.4.1 and earlier crash that runtime at startup (configurable provider "deepseek-official" is already declared, surfacing asDSH runtime exited before readiness). Install v1.4.2+.
If a broken install already keeps the Desktop from starting, open ~/.ohdsh/profiles/desktop/package.json, remove the dsh-vision-router entry from both dependencies and dsh.profile.bundles, save, and restart the Desktop.
Oh-DSH Desktop's built-in plugin marketplace (search → prepare → isolated preview → apply, with a previous snapshot for recovery) also works once the community catalog lists this plugin; do not mix marketplace installs with the direct command above. The bundled @oh-dsh/vision (view_image) coexists with this plugin — the tool names do not collide.
Disable / re-enable
- id: vision-router
disabled: true
Set it back to false to re-enable. Unloading removes the wrapper routes, tools, skill and settings card; cached artifact files remain.
Upgrade
# normal npm/npx install — install the version you want explicitly; a bare
# `update` is silently held back for releases younger than 24h (pnpm v11)
npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router@<version>
# DeepSeek Harness source checkout
pnpm dsh plugin --profile web add dsh-vision-router@<version>
Settings live in the profile's settings provider and survive upgrades. The settings card's one-click update installs the registry-confirmed version explicitly and verifies the installed manifest afterwards — it never reports success on a package-manager exit code alone.
A fresh release does not take effect (
downloaded 0/added 0): pnpm v11 holds versions younger than 24h back; install the target version explicitly as above (pnpm auto-exempts it), ornpx dsh-vision-router repairfixes the stale version-pinned profile exemption so updates take effect immediately.
Upgrading from a pre-bundle-patch install (v0.x): the package now mounts itself through its own bundle patch, so a leftover manual row in
~/.dsh/profiles/<profile>/cordis.patch.ymlduplicates it anddsh webfails at startup withduplicate loader entry id: vision-router. Delete the old block:- insert: # remove this whole block - id: vision-router name: dsh-vision-routerTo keep custom settings, replace it with a plain by-id override (no
insert):- id: vision-router config: # your overrides …
After upgrading from v1.1.x, pixel tools fail with
colourspace: parameter space not set: a stale sharp 0.34.0 from the v1.1.0 era still sits in the profile and its libvips DLL conflicts with the host's sharp 0.35.3 in the same process (issues #42 / #75). Delete~/.dsh/profiles/<profile>/node_modules/sharpand~/.dsh/profiles/<profile>/node_modules/@imgand restart, or runpnpm installinside the profile. Since v1.2.2 the plugin detects the stale version at runtime and prints the same guidance itself.
Uninstall
# normal npm/npx install
npx @deepseek-ai/dsh plugin --profile web remove dsh-vision-router
# DeepSeek Harness source checkout
pnpm dsh plugin --profile web remove dsh-vision-router
This removes the dependency and the bundle layer. If you disabled the stock DeepSeek row manually, re-enable it in your profile patch.
Troubleshooting
Using dsh-web-ui / dsh-web-ui-all together
If dsh-web-ui / @linxin666/dsh-web-ui-all is installed alongside Vision Router, its dsh-tool-describe-image send hook can rewrite image uploads into describe-image references before downstream vision plugins receive the original image block.
dsh-web-ui now provides an explicit compatibility switch. Go to Settings → Plugin config → Image understanding and turn off “Rewrite images to describe-image references on send”, or set interceptImageSend: false. Image sends will then pass through unchanged so dsh-vision-router can receive the original image block. The switch is read on every send, so no hook reinstall or DSH restart is required.
See dsh-web-ui#301 for the upstream compatibility change.
Startup fails with Unexpected token ... is not valid JSON (UTF-8 BOM)
Symptom: dsh web / pnpm dsh web exits immediately at startup:
SyntaxError: Unexpected token ...
is not valid JSON
at JSON.parse (<anonymous>)
at readProfileManifest (packages/boot/app-boot/src/profile.ts)
Cause: ~/.dsh/profiles/<profile>/package.json was saved as UTF-8 with BOM by an editor. The invisible \uFEFF character at the start makes JSON.parse fail, because JSON does not allow it before the opening brace.
Recommended fix: run Vision Router's standalone repair command. It does not require DSH to boot first; it locates the profile, detects a UTF-8 BOM, removes only the three leading BOM bytes, and then validates the JSON again:
npx dsh-vision-router repair --profile web
To diagnose without changing the file:
npx dsh-vision-router doctor --profile web
Replace web if you use another profile, or omit --profile to scan all profiles.
Manual fallback: in VS Code, use “Save with Encoding” → UTF-8 (without BOM). If repair removes the BOM but the JSON is still invalid, it will not guess or rewrite any other JSON content; inspect the file manually.
Security notes
- Image text is untrusted evidence: descriptions, OCR output and the auto-mount note all tell the agent never to execute instructions found inside images.
- Tool inputs resolve through
ctx.fs(sandbox-aware); vision uploads never send anything but the selected image and the question. - Artifacts write only under
<workspace>/.dsh-vision-router/artifacts; results return absolute paths and byte counts. - Secrets never travel:
apiKeyEnvnames a DSH credential reference; the value is resolved per call and never logged. - The settings write path goes through the settings service (schema-validated, revision-checked) — a stale or invalid save is rejected, not partially applied.
License
Star History