@deepseek-ai/dsh-read-image
Plug-and-play image reading for text-only models: admits pasted images, projects them as [Image #N], and provides a first-class read_image tool backed by a configurable vision model, with a 读图 settings page (设置 → 读图) bridged over typert Remote.
AI 분석
让不支持多模态的纯文本模型也能通过调用外部视觉模型间接“看懂”图片。适合需要向 DeepSeek 等文本模型发送截图进行代码分析的用户。
설치
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:OoWJZZoO/dsh-read-imageConfiguration
Configuration lives in $DSH_HOME/settings.yaml (hot-reloaded, no restart) and can also be edited in Web at Settings → Read image (writes to the user layer, overriding the corresponding settings.yaml keys):
# 2) This plugin's configuration
dsh-read-image:
visionProvider: # the route provider hosting the vision model
visionModel: # the multimodal model id (must declare input: [text, image])
| Key | Default | Description |
|---|---|---|
visionProvider | '' | Route provider of the vision model (dropdown fed by the Models page) |
visionModel | '' | The multimodal model that performs read_image tasks (dropdown under that provider) |
defaultPrompt | English step-by-step description prompt (classify → verbatim text to Markdown / visual description) | Used when read_image is called without prompt |
defaultReasoningEffort | low | Default reasoning intensity. low is the lowest tier widely accepted and honored; on many adapters off is equivalent to "omit the field" and does not disable thinking on models that default it on, so thinking eats into max_tokens and small budgets come back empty |
defaultTimeoutMs | 300000 | Default vision-call timeout (5 minutes, generous) |
defaultMaxThinkingTokens | 4096 | Default max thinking tokens (separate budget, not counted against output); when thinking exceeds the budget and the output comes back empty, read_image reports an explicit error instead of silently returning nothing |
defaultMaxTokens | 8192 | Default actual output token cap; the API max_tokens sent is this + defaultMaxThinkingTokens (budget is 0 with reasoning_effort=off, passed through unchanged) |
guard.enabled | true | Environment self-check switch; false skips the self-check and force-loads the plugin (see Safety) |
Why not the settings protocol? The harness allowlists
WEB_SETTINGS_NAMESPACES, so a pluginsettings.register()namespace only answerssettings-not-exposedto the browser. The "Read image" page therefore talks to a host-side typert Remote bridge (readImageConfig.get/set, seelib/config-remote.js) instead — settings.yaml stays the base layer, Web writes go to the user layer,scope.watchhot-syncs the runtime, and headless and Web stay consistent.
Usage
- Paste an image: the text model sees
[Image #1]; callread_image image_index=1to view it. read_image file_path=/path/to/image.pngreads an image file.- Pass
prompt(a specific question for the vision model),reasoning_effort,timeout_ms,max_tokens,max_thinking_tokensto override the defaults. - Call it repeatedly, including re-reading the same image.
Safety
This plugin rides on harness internals that can change shape between releases. On startup it runs an environment self-check over every harness contract it touches. If any check fails, the plugin fails safe: it loads nothing and the harness boots normally — the full diagnostics are written to ~/.dsh/logs/dsh-read-image-guard.log and one short notice is logged. To force-load anyway (at your own risk): guard.enabled: false under the dsh-read-image section of settings.yaml.
How it works
| Mechanism | Layer | What it does |
|---|---|---|
| M1 admission | host | Wraps llm.resolveModelInfo so text-only routes report image input (restored on dispose) → send/switch-model gates let images through |
| M2 projection | host | Synchronous llm/stream waterfall listener: on text-only routes, image blocks become [Image #N] text, then re-dispatches the harness-deepFreezed request |
| M3 tool | per-agent | Registers read_image into the agent's own scope at session/created (shadowing the built-in tool) plus a prompt section |
| Settings | host | Registers the dsh-read-image settings namespace (schema defaults = fallback), hot-syncs runtime config via scope.watch |
| Config bridge | host | ReadImageConfigService (typert Remote, readImageConfig.get/set) reads/writes the same namespace; auto-discovered by the gateway |
| Config page | browser | settings.section registers the "Read image" page (order 12); ctx.remote.$mount self-mounts remote.readImageConfig |
| G1 self-check | host + browser | Total fuse: probes every harness contract at apply; any failure → plugin loads nothing, harness unaffected (see Safety) |
- Capability truth table: built from the original
resolveModelInfo(never the M1-wrapped one), rebuilt onllm/adapters-updatedwith startup retries; - Vision calls reuse
ctx.llm(same credentials / retries / logging as the harness), withAbortSignal.any([exec.signal, timeout])for timeouts; - Session logs are the single source of truth: image references persist as usual, and only the model-visible boundary performs the
[Image #N]replacement.
Development
Contributor guide and the full list of harness-workaround hacks (and the reasons they exist) live in AGENTS.md and docs/known-hacks.md. Tests: node --test test/ (run from the dev-profile copy — see the test file headers).
License
MIT
Usage
- Paste an image: the text model sees
[Image #1]; callread_image image_index=1to view it. read_image file_path=/path/to/image.pngreads an image file.- Pass
prompt(a specific question for the vision model),reasoning_effort,timeout_ms,max_tokens,max_thinking_tokensto override the defaults. - Call it repeatedly, including re-reading the same image.