YuLee-314
dsh-vision-bridge
DeepSeek Harness 视觉补全:孪生路由解锁原生图片体验,本地 Ollama 请求层看图,零云端依赖。Vision twin + local agentic vision tools for DeepSeek Harness.
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 16, 2026
- Updated
- Aug 16, 2026
Introduction
dsh-vision-bridge
A DeepSeek Harness plugin that lets text-only models receive and understand images. The vision work is done by a local model on your machine.
English · 简体中文
What is this?
DeepSeek Harness (dsh) is an open-source AI coding environment built entirely from plugins. Its chat models — deepseek-v4-flash and deepseek-v4-pro — are text-only: their API rejects image data. As a result, in Harness you cannot paste a screenshot into a session that uses them, attach an image to a message, or use the built-in read_image tool.
This plugin fixes that. It works in three layers:
- A second model route that accepts images. The same DeepSeek models are registered again as a "twin" provider (
deepseek-vision). Because the twin declares image support, the normal image features work: pasting produces a thumbnail and an image block, andread_imageis allowed. Before each request is sent to the DeepSeek API, the plugin converts every image in the conversation into a text description produced by a local vision model. The API only ever receives text; the model answers as if it had seen the image. - Nine inspection tools.
describe_image,extract_text,structured_scan,query_region,detect_elements,locate_object,compare_images,read_clipboard, andcheck_healthlet the model look at an image at different levels of detail — from a general description down to per-element coordinates — and let you do the same through chat. - Paste routing. When you paste an image, a small browser component asks the server whether the current model can handle images. If yes (twin route), the paste stays a normal image. If no (official text-only route), the image is saved to a private local file and the path is inserted as text, which the inspection tools can then read.
The vision model (Ollama + qwen2.5vl) runs on your machine. No image bytes are ever sent to DeepSeek's API or to any cloud vision service.
If you only use the official route, the plugin still helps: pasted images become local paths and the inspection tools work on them. If you only want the tools, you can ignore the twin route entirely.
Table of Contents
- What is this?
- The Problem
- The Solution
- Features
- Architecture
- Interaction Logic
- Installation
- Usage
- Configuration
- Vision Tools
- Project Structure
- Security & Privacy
- Requirements
- Troubleshooting
- License
The Problem
DeepSeek's flagship chat models (deepseek-v4-flash, deepseek-v4-pro) are text-only: their API
endpoints cannot receive image bytes, and their model metadata declares inputModalities: ['text'].
DeepSeek Harness builds on that metadata in three hard places:
| Gate | Location | Effect |
|---|---|---|
| Composer admission | api-proxy prompt handler | Pasting an image is rejected: "the current model does not support images" — the image part never enters the conversation |
read_image tool | dsh-tool-fs | Refuses to read an image into context unless the active route declares image input |
| Adapter serializer | llm-deepseek | Core image blocks are rejected (UNSUPPORTED_CONTENT) — images physically cannot reach the provider |
Workarounds in the wild either fork presets (fragile), require cloud vision keys (Gemini etc.), or force you to switch to a weaker vision model for the whole session — losing DeepSeek's coding ability. None of them deliver what users actually want: DeepSeek, plus native image UX, plus the ability to see.
The Solution
One self-contained plugin, three cooperating layers:
- Vision twin route (
deepseek-vision) — the same DeepSeek models re-registered withinputModalities: ['text', 'image']. Every native gate opens: paste admission, thumbnails, durable image blocks,read_image. Under the hood the twin intercepts image blocks at the request layer, runs the local vision bridge, and forwards only text to DeepSeek's API. - Agentic vision tools — nine native tools (
describe_image,structured_scan,query_region, ...) powered by a local Ollama vision model, with structured output, validation-with-retry, and content-addressed caching. - Paste router — a browser half that asks the host for a verdict based on real model metadata: vision-capable routes keep the native photo flow; text-only routes fall back to a local path so the bridge tools can take over.
Features
- Native image experience for text-only models — paste a screenshot, get a thumbnail, image block, and a DeepSeek that actually saw it, without a single image byte ever reaching the API.
- No cloud keys required — the vision engine is local Ollama (
qwen2.5vl); the twin reuses your existingDEEPSEEK_API_KEYcredential with the official route's own resolution logic. - Request-layer transparency — no prompt hacks, no preset forks, no dynamic injection that can race; the interception happens in the adapter, exactly once, per request.
- Structured output with coordinates — element bounding boxes (
[0,1000]normalized), region cropping, two-stage localization, image comparison, clipboard reads, schema-validated output with automatic retry on malformed responses. - Repeated images are cached — repeated images hit the content-hash cache: zero extra inference, stable prefix-cache behavior.
- Self-contained & distributable — a single 29 KB tarball with no machine-specific paths;
installs on any Harness via
dsh plugin --profile web add. - Coexists with the official route — the official provider stays untouched as the fallback; the paste verdict decides per session, from live metadata, which flow runs.
Architecture
┌──────────────────────────── Browser · client.js ────────────────────────────┐
│ paste / drop image │
│ → capture-phase listener (before the composer's own) │
│ → GET /vision-bridge/paste?model=<selector label> (host verdict) │
│ ├─ takeover:true (confirmed text-only route) │
│ │ → POST bytes → host saves private temp file → path text │
│ │ inserted into the composer; bridge tools take over │
│ └─ takeover:false (image-capable route) │
│ → native paste: image part + thumbnail, untouched │
└─────────────────────────────────────┬───────────────────────────────────────┘
▼
┌──────────────────────── Host plugin · lib/index.js ────────────────────────┐
│ · POST /vision-bridge/paste → magic-byte sniff → 0600 temp file → {path} │
│ · 9 tools registered into ctx.tools (native catalog, no mcp__ prefix) │
│ · registerAdapter('deepseek-vision', VisionDeepSeekAdapter) │
└──────────────┬───────────────────────────────────────────┬─────────────────┘
▼ ▼
┌────────────────────┐ ┌──────────────────────────────┐
│ vision-core │ │ VisionDeepSeekAdapter (twin) │
│ · prepare/validate │ │ · listModels/resolveModel: │
│ · queue (serial) │ │ image-capable metadata │
│ · LRU cache │ │ · stream(): ImageBlock ──► │
│ · 9 tool handlers │ │ bridge analysis text ──► │
│ │ │ DeepSeek API (text-only) │
└──────────┬──────────┘ └───────────────┬──────────────┘
▼ ▼
Ollama · qwen2.5vl DeepSeek API · same endpoint,
localhost · private · free same credential as official
lib/core/ is the host-agnostic vision core shared with the author's MCP vision bridge
(scripts/sync-core.mjs re-syncs it — one source of truth, two delivery forms).
Interaction Logic
1. Paste routing (per paste, decided by live model metadata)
| Session model | Verdict | What happens when you paste |
|---|---|---|
DeepSeek-V4-Flash/Pro (视觉桥) (twin) | takeover:false | Native photo: thumbnail + image block; the twin analyses it at the request layer |
DeepSeek-V4-Flash (official) | takeover:true | Paste bytes → private temp file → path text; the model calls bridge tools on the path |
| Any future vision-capable route | takeover:false | Native paste preserved automatically — the verdict is evidence-based, never a name regex |
The client caches verdicts per selector label (60 s TTL) and refreshes on focus, so the first paste of a session is already correct. Unknown metadata never hijacks a paste — the native path is the safe default.
2. Request-layer interception (vision twin only)
user pastes image ──► durable image block in session history
│
▼
next model request ──► VisionDeepSeekAdapter.stream()
│
▼
sanitize(): for every image block ──► attachments.readImage(bytes)
│ │
│ ▼
│ write content-addressed file
│ (~/.dsh/vision-bridge/images/<sha1>.png)
│ │
│ ▼
│ vision-core.describe_image(path) ← cache hit ⇒ zero inference
│ │
│ ▼
│ text: [图片(视觉桥分析)] … + local path
│
▼
text-only request ──► DeepSeek API (identical endpoint/credential as official route)
│
▼
DeepSeek answers WITH the vision analysis; it may also call query_region / extract_text
on the embedded path for deeper, coordinate-accurate inspection.
3. Tool chain (evidence workflow)
structured_scan ──► element list with bboxes (heading/table/chart/button/…)
│
├──► query_region(bbox) ──► the region is REALLY cropped and analyzed alone
├──► extract_text(with_coordinates) ──► OCR blocks with normalized coordinates
└──► locate_object(desc) ──► coarse locate → crop ×1.3 → fine locate → full-image bbox
Installation
# 1. Prerequisites: Ollama running with a vision model (e.g. qwen2.5vl:7b),
# and DEEPSEEK_API_KEY stored (for the twin route).
# 2. Install the plugin (any form works)
dsh plugin --profile web add .\dsh-external-dsh-vision-bridge-2.0.0.tgz # tarball
# dsh plugin --profile web add <directory> # checkout
# dsh plugin --profile web add @yulee-314/dsh-vision-bridge # npm
# 3. Restart dsh web (bundle layers load at boot)
Usage
- In any session, open the model selector and pick:
- DeepSeek (视觉桥) → DeepSeek-V4-Flash (视觉桥) — recommended: coding + native image UX, vision supplied by the bridge.
- Official
DeepSeekroute — paste falls back to path text; bridge tools remain available.
- Paste or drop an image. A thumbnail appears (twin) or a path is inserted (official).
- Ask normally. DeepSeek answers from the bridge analysis; use
query_region/extract_textfor coordinate-level detail.
To make the twin the default for every new session:
# ~/.dsh/settings.yaml
agent-default-model:
provider: deepseek-vision
model: deepseek-v4-flash-vision
reasoningEffort: max
Configuration
| Variable | Default | Purpose |
|---|---|---|
OLLAMA_BASE_URL | http://127.0.0.1:11434/v1 | Vision engine endpoint (OpenAI-compatible) |
OLLAMA_VISION_MODEL | qwen2.5vl:7b-q3_K_M | Vision model used by the bridge tools |
OLLAMA_API_KEY | ollama | Compatible key for the engine |
VISION_MAX_TOKENS / VISION_TEMPERATURE / VISION_MAX_RETRIES / VISION_CONCURRENCY | 8192 / 0.1 / 1 / 1 | Inference parameters |
DEEPSEEK_API_KEY | credentials service | Twin-route key — same source as the official route |
DEEPSEEK_BASE_URL | public API | Twin-route endpoint — same resolution as the official route |
Tool rows are disabled by setting pasteToPath: false on the bundle row if the paste router is
undesired on a specific deployment.
Vision Tools
| Tool | What it does |
|---|---|
describe_image | Full-image understanding in Chinese; accepts paths and URLs |
extract_text | OCR — plain text, or block-level JSON with normalized coordinates |
structured_scan | Element detection (heading/text/table/image/chart/formula/button/list) with bboxes + confidence, schema-validated |
query_region | Region-focused query — the region is really cropped before inference |
detect_elements | Bbox-only localization for selected element types |
locate_object | Two-stage localization: coarse full-image → ×1.3 crop → fine → full-image bbox |
compare_images | Before/after visual regression: side-by-side composite, structured diff JSON |
read_clipboard | Windows clipboard image → exported PNG path |
check_health | Ollama reachability, model presence, config summary, cache size — zero inference |
Project Structure
dsh-vision-bridge/
├── package.json # dsh.bundle + dsh.client manifests, self-contained deps
├── cordis.patch.yml # loader row (bundle layer)
├── client.js # browser half: paste interception + verdict protocol
├── README.md / README.zh-CN.md
├── scripts/sync-core.mjs # re-sync lib/core from the MCP vision bridge
└── lib/
├── index.js # host plugin: tools, paste route, adapter registration
├── deepseek-vision.mjs # vision twin route (extends the official DeepSeek adapter)
└── core/ # host-agnostic vision core (vendored, self-contained)
├── vision-core.mjs # tools + prompts + retry/validation orchestration
├── image.js # preprocessing, crop, data URLs
├── validate.js # schema validation + retry hints
├── cache.js # content-hash LRU
├── queue.js # serial inference queue
├── errors.js # error taxonomy (ollama_down / model_not_found / …)
├── grounding.js # two-stage localization math
├── compare.js # side-by-side comparison composition
└── clipboard.js # Windows clipboard reader
Security & Privacy
- Images never leave your machine. The vision engine is localhost Ollama; the twin route sends only the bridge's text analysis to DeepSeek's API.
- Pasted bytes are magic-byte checked (PNG/JPEG/WebP/GIF), size-capped (25 MB), and stored
0600in fresh unpredictable temp directories. - The twin reuses the official route's credential resolution — no second key, no plaintext config.
- Paste hijacking is strictly evidence-based: without a positive text-only confirmation the native path stays untouched.
Requirements
| Component | Requirement |
|---|---|
| DeepSeek Harness | web profile, rc.5+ (tested on 0.1.0-rc.5) |
| Node.js | ≥ 22.19 |
| Ollama | running, with a vision model (tested: qwen2.5vl:7b) |
| API key | DEEPSEEK_API_KEY for the twin route (same as official) |
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Paste still inserts a path text | The session is on the official route — select the (视觉桥) variant, or check the latest verdict entry in ~/.dsh/vision-bridge-activity.jsonl for the real selector label |
Tools report [ollama_down] | ollama serve not running, or the model missing (ollama pull qwen2.5vl:7b) |
Twin route fails with MISSING_CREDENTIAL | Store DEEPSEEK_API_KEY on the Web Models page or export it in the environment |
read_image refuses on the twin | Only possible if the twin's metadata is not loaded — restart dsh after installing |
| Plugin changes not active | Bundle layers load at boot; restart dsh web after dsh plugin operations |
License
MIT. The vision core originates from the author's MCP vision bridge project.