dsh-vision-router: pixel-level vision tools for text-only DeepSeek Harness agents
Eyes for text-only DeepSeek Harness agents: built-in free vision chain (no key) + pixel-level vision tools (Q&A, grounding, crop, pixel diff, colors, OCR, SVG trace, cutout, screenshots). One-command install, no Python, image turns work like ordinary tool-calling turns.
At a glance
- What is it?
- A DSH plugin that routes image turns to a vision model instead of flattening them into text descriptions, with a keyless free fallback chain and fourteen pixel tools. MIT licensed, Node 22 or 24, no Python.
- Who is it for?
- Adopt it if you run DeepSeek Harness agents that need to look at UI screenshots, region-level image questions or OCR output without swapping your daily reasoning model. Skip it if you need the uploader's original encoded bytes preserved, if your workflow is one-shot image captioning that a text description bridge already handles, or if you cannot run Node 22.19 or newer.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 16, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem: text descriptions of images are lossy by construction
DeepSeek Harness agents that run on text-only models have no way to look at an image. The common workaround is a bridge that hands the image to some model and returns a paragraph of prose. The README calls this a description bridge and argues it is lossy, one-shot and blind to pixels. That framing is fair for a specific class of work. If you ask where the send button is, a prose answer gives you a sentence. It does not give you coordinates you can feed into a crop, compare against a later screenshot, or test for equality after a fix.
The plugin's answer is to keep the image pixels on the vision model's side and DeepSeek on the reasoning side. Text turns are unchanged in model, cost and context. The vision model is only the eyes, called on demand. An image turn becomes a text turn that calls tools, and the agent can iterate: locate a region, crop it, describe it, diff it against the previous state, fix the code, take another screenshot.
The intended audience is narrow but real. It is for people already running DeepSeek Harness who need repeatable, measurable image operations inside an agent loop, not people who want a general multimodal assistant. The README states the plugin is MIT licensed and requires Node.js ^22.19.0 or >=24.0.0.
Routing versus description: what actually moves through the pipeline
The README's comparison table draws the line clearly. Manual model switching has image pixels available, but only when you switch, and the whole session is swapped. An MCP vision bridge is automatic but delivers text descriptions only. dsh-vision-router claims automatic routing with the Host-canonical raster on the image turn, provider failure recovery through fallback chains, and reusable structured queries via JSON mode plus caching.
The mechanism has a constraint worth reading twice. On DSH 0.1.2-alpha.1 and later, attachments stay Host-owned. The plugin consumes the Host-persisted canonical image. Clean single-frame 8-bit sRGB or sRGBA images inside the configured normalization limits can pass through byte-identically, but images needing orientation, color-space, metadata, animation or size normalization may be re-encoded. The README is explicit that the pixel tools promise the Host-canonical raster, not the uploader's original encoded bytes. If your work depends on hashing the exact file a user uploaded, this design will not give you that.
The package ships its own composition patch, declared as dsh.bundle.patch, so dsh plugin add wires the row, the admission wrapper and the attachment limits without manual file edits. Taking over the official DeepSeek route is described as an optional setting called stealth mode, off by default. That default matters: the plugin does not silently intercept your existing DeepSeek traffic.
Installing dsh-vision-router and running a first grounding call
The README's quick start entry point is the dsh plugin add command, which installs the package and applies the bundled composition patch. The package also exposes a bin entry named dsh-vision-router that points at lib/doctor-cli-p0.js, which the repository layout suggests is a diagnostic command.
dsh plugin add dsh-vision-routerAfter that, the README says to enable the composer's Vision control and then use image turns like ordinary tool-calling turns. The plugin ships fourteen tools; the demo described in the README walks through vision_ground, vision_crop and vision_pixel_diff to locate a send button and answer with coordinates.
The tool names appear in the README as vision_ground, vision_crop, vision_describe and vision_pixel_diff. A typical sequence pastes an image, asks the agent to find a control, crops the region, and diffs it against a later screenshot. The README states answers are cached by image content, so repeating the same query on the same raster should not re-hit the provider.
dsh-vision-routerThe bin above is the package's own CLI, mapped in package.json. The README does not document its flags or output in the excerpt available, so treat it as the entry point named in the manifest rather than a documented workflow.
The free fallback chain and its real ceiling
Vision tools end with a five-model OVHcloud anonymous fallback: no account, no key, 2 requests per minute per IP per model, roughly 10 RPM in theory across independent buckets. User-provided vision models run first. That ordering is the sensible part. If you configure your own provider, the anonymous chain is a safety net rather than the primary path.
The ceiling is the anonymous tier. Two requests per minute per IP per model is a rate a human can work at and an automated test loop cannot. The README's own framing of roughly 10 RPM in theory across independent buckets is doing a lot of work in that sentence: the buckets are per model, so the practical throughput depends on how the fallback distributes requests, and the plugin does not promise a floor. If you are building anything that processes a folder of screenshots in a loop, you will want a keyed provider configured, and the README does not document which providers are supported beyond naming OpenRouter in the package keywords.
There is a second cost that is easy to miss. The plugin declares peer dependencies on @deepseek-ai/dsh-llm-deepseek and @deepseek-ai/dsh-anonymous-user-id across a specific set of versions, from 0.1.0-rc.8 through 0.1.6-alpha.1. v2.1.7 is described in the release announcement as hardening DSH 0.1.5 connection and catalog compatibility without raising the rc.8 Host floor. A Host version outside that enumerated range is not covered by the manifest.
What the runtime actually depends on
The dependency list is short: @deepseek-ai/schemastery, potrace, puppeteer-core and undici. The README states the whole pipeline runs on sharp, potrace, tesseract and system Chrome, with no Python anywhere. Puppeteer-core is the reason system Chrome matters: it does not ship a browser, so the HTML screenshot tool depends on a Chrome installation the plugin can find. That is a deployment constraint the README names but does not resolve.
Tesseract is the OCR path, and potrace handles SVG trace. Sharp does downscale, crop and palette work. The package is ESM, with "type": "module" and a main entry at lib/public-entry.js. It also exports a client subpath at ./lib/client.js and the patch file at ./cordis.patch.yml, which suggests the UI side and the composition patch are both part of the published surface.
The repository carries tests, a quality directory, presets and docs, and the README badge claims verification on Node 22 and 24. That is a claim from the project's own badge, not an independent result. The engines field is stricter than the badge implies: ^22.19.0 or >=24.0.0, so Node 22.0 through 22.18 is out.
Where this is the wrong tool
The byte-identity limitation is the sharpest one. If your pipeline hashes uploaded files, stores originals for audit, or compares images by their encoded bytes, the Host-canonical raster promise is not the same thing. The README says clean single-frame 8-bit sRGB or sRGBA images inside the normalization limits can pass through byte-identically, and everything else may be re-encoded. That is a conditional guarantee, and the conditions depend on your Host configuration rather than on the plugin.
Animated images are the clearest failure case. The normalization list explicitly includes animation, so a GIF is a candidate for re-encoding, and a pixel diff against the original file will not match. Metadata is on the same list. If you need EXIF preserved, this is not the path.
A second case is one-shot captioning. If all you ever need is a sentence describing an image, the description bridge the README argues against is simpler and has no Node version floor, no system Chrome requirement and no rate limit to think about. The plugin's value comes from repeatability and pixel access, and you pay for it in moving parts.
A third case is platform. The README names sharp, tesseract and system Chrome as the runtime, and does not document a Windows-specific installation path in the excerpt available. Teams on Windows should verify the tesseract and Chrome discovery behaviour before committing.
Alternatives and how the approach differs
The README's own comparison names two alternatives. Manual model switching keeps the session on a multimodal model for the whole conversation; you get pixels, but you lose the daily text model, its cost profile and its context. The plugin's stealth mode is the optional inverse of that: routing the official DeepSeek path through the plugin, off by default.
The second alternative is an MCP vision bridge. It is automatic, like this plugin, but the README states it delivers text descriptions only. The difference is not speed or accuracy in the abstract. It is whether the agent receives something it can operate on. A description cannot be cropped, diffed or grounded with coordinates. That is the whole argument, and it holds as long as your task actually needs those operations.
The third option is doing nothing and keeping text-only agents text-only. That is legitimate for many codebases. The plugin's cost is a Node 22.19 floor, four runtime dependencies, a system Chrome requirement for screenshots, and a peer dependency range that has to line up with your DSH Host.
Maintenance, licence and upgrade cost
The repository is not archived and the last push was on 2026-09-16, the same day as the most recent release line. Releases v2.1.5, v2.1.6 and v2.1.7 landed on 2026-09-10, 2026-09-11 and 2026-09-14 respectively, so the project is moving on a days-scale cadence. That cuts both ways. Fast releases mean fixes arrive quickly, and they also mean the version you pin can be superseded within a week.
The v2.1.7 announcement describes hardening of remote and local capability boundaries, long-lived browser and persistent-cache resource ownership, DSH 0.1.5 connection and catalog compatibility, property-based fuzzing and release provenance, without raising the rc.8 Host floor. The phrase long-lived browser and persistent-cache resource ownership is the one to note operationally: it implies a browser process and a cache that live beyond a single call.
Licensing is MIT, per both the package.json license field and the README badge. MIT is permissive and imposes no source-disclosure obligation on your own code, but the plugin depends on sharp, potrace, puppeteer-core and undici, and those carry their own licences. Check them against your distribution model rather than assuming the MIT label covers the whole dependency tree. This is not legal advice.
Upgrade cost concentrates in the peer dependency range. The manifest enumerates exact DSH versions for @deepseek-ai/dsh-llm-deepseek and @deepseek-ai/dsh-anonymous-user-id, and a Host upgrade outside that list is an untested combination until the plugin widens it.
Editorial conclusion
Adopt it if you run DeepSeek Harness agents that need to look at UI screenshots, region-level image questions or OCR output without swapping your daily reasoning model. Skip it if you need the uploader's original encoded bytes preserved, if your workflow is one-shot image captioning that a text description bridge already handles, or if you cannot run Node 22.19 or newer. Verify first that your DSH Host version falls inside the peer dependency range for @deepseek-ai/dsh-llm-deepseek and @deepseek-ai/dsh-anonymous-user-id, and confirm your Host normalizes attachments the way the README describes, because the pixel tools promise the Host-canonical raster rather than the file the user uploaded.
Frequently asked questions
Does dsh-vision-router need an API key to work?
No. The README states the vision tools end with a five-model OVHcloud anonymous fallback that needs no account and no key, at 2 requests per minute per IP per model. User-provided vision models run first if you configure them.
Which Node.js version does dsh-vision-router require?
The package.json engines field specifies ^22.19.0 or >=24.0.0. The README badge claims verification on Node 22 and Node 24, but the engines range excludes Node 22.0 through 22.18.
Does dsh-vision-router preserve the original bytes of an uploaded image?
Not unconditionally. The README states the plugin consumes the Host-persisted canonical image: clean single-frame 8-bit sRGB or sRGBA images inside the configured normalization limits can pass through byte-identically, while images needing orientation, color-space, metadata, animation or size normalization may be re-encoded.
Community notes