dsh-web-search-ddg
aooyoo
Zero-token DuckDuckGo search provider for the DeepSeek Harness (DSH) web seam — local headless browser, no API key, no m…
PROJECT TOPICS
PROJECT README
Auxiliary vision for DeepSeek Harness (dsh): analyze images through an external OpenAI-compatible vision endpoint and get a text answer back. Works with any main model — including DeepSeek, whose own API is text-only.
dsh's built-in read_image tool injects the image
into the main model's context, which requires the main model to declare image input. The DeepSeek
adapter does not (inputModalities: ['text']), so read_image refuses to run with it. This plugin
takes the complementary auxiliary vision route: it sends the image to a separate vision model and
returns the answer as text. No main-model vision support needed.
read_image (built-in) |
vision_analyze (this plugin) |
|
|---|---|---|
| Image goes to | the main model's context (native) | an external vision endpoint |
| Main model must be vision-capable | yes | no |
| Result | image block the model sees | plain text answer |
http(s) URL (size-capped, magic-byte MIME sniffing).chat/completions request with [{type:text}, {type:image_url}] content.reasoning_content for thinking models).read_image archive copies / resumed history) plus
question sha256 — and later requests reuse the cached text. The vision endpoint sampling temperature is
pinned to 0 (temperature) for deterministic output. The result:vision_analyze toolSend an image (local path or URL) to the configured vision endpoint and get a text answer — works with any main model, including text-only DeepSeek.
The Web UI accepts pasted/dropped images, but the DeepSeek adapter rejects
image content (UNSUPPORTED_CONTENT). This plugin hooks agent/pre-step —
the documented seam for replacing the messages that enter a step — and, when a
user message carries image blocks, calls the vision endpoint to describe them,
then swaps each image block for a [User-attached image description] text
block before the request reaches the model. DeepSeek only ever sees text,
so image attachments just work. When the active model route declares image
input, transcription is skipped and native vision takes over.
Toggle with attachImages (default true). Transcription failures degrade to
an explicit note instead of blocking the turn.
attachMode controls how the vision model is asked about attached images:
auto (default): when the user wrote text with the image, that text is passed
to the vision model verbatim as its question — "who is this?", "translate
the text", "what is wrong on this page?" are answered directly, plus a
one-line image summary for follow-ups. Without user text, a generic
description is produced.describe: always use the generic description prompt, ignoring user text.deepseek-vision provider route (pasting images in the Web UI, root fix)The Web UI's upload preflight rejects images when the selected model does not
declare image input — so pasted images never even reach the agent on the
plain DeepSeek route. This plugin registers a deepseek-vision provider: a
DeepSeekAdapter subclass that declares image input (preflight passes) and
transcribes attached images to text at request time before delegating to the
real DeepSeek chat-completions endpoint. The main model is still DeepSeek —
same endpoint, same key, same models. Select "DeepSeek (vision via plugin)"
in the model picker, then paste/drop images as usual.
Toggle with deepseekVision.enabled (default true); the provider id is
deepseekVision.providerId (default deepseek-vision). Transcription goes
through the content-hash cache, so once an image is in the history every later
turn reuses the cached text instead of calling the vision endpoint again.
The vision API key resolves through the harness credentials service first (the
Web UI's stored keys) and falls back to the environment — no export needed in
the GUI. Write the plugin into the home-level user patch, which applies to
every profile (including the web GUI you already run):
# $DSH_HOME/cordis.patch.yml
- insert:
- id: vision
name: 'file:///<path-to-plugin>/lib/index.js'
Store your vision API key in credentials (or export VISION_API_KEY), then restart the
GUI once. Use the built lib/index.js — the published CLI loads .ts
entries but cannot resolve their .js-suffixed sibling imports from src/.
Every pasted image is automatically saved to ~/.dsh/image-archive/ with a
date-numbered name (2026-08-14_120331_001.png), recorded in an index.json
manifest (path, sha256, size, source, optional note), and its location is
annotated for the model ([图片已存档: …]). Two tools complete the flow:
image_archive — the agent archives an important image (user preferences,
receipts, key data) into a named folder with an optional note: it saves
<archiveDir>/<folder>/<name-or-date>.png and updates index.json.image_archive_find — search the manifest by name, folder, or note.Configure with archive.enabled (default true) and archive.dir (default
~/.dsh/image-archive). Deduplicated by attachment id.
Install the bundle into any profile (the prepare script builds on install):
dsh plugin --profile web add github:Tianbaidi/dsh-plugin-vision
Store the vision API key in your credentials or environment
(ALIBABA_CODING_PLAN_API_KEY), then restart the GUI. Paste/drop an image and
ask — or use the vision_analyze tool directly.
Prefer a dev overlay instead? Point it at your local checkout:
- insert:
- id: vision
name: 'file:///<path-to-plugin>/lib/index.js'
Windows note: plugin paths in overlays must be
file://URLs (file:///D:/...%20...), not bareD:/...paths — the ESM loader rejects the latter as schemed:.
| Key | Default | Meaning |
|---|---|---|
baseUrl |
(empty — required) | OpenAI-compatible chat-completions endpoint base URL. |
apiKeyEnv |
VISION_API_KEY |
Env var (or stored credential) holding the API key. |
model |
(empty — required) | Vision model id on the endpoint. |
timeoutMs |
120000 |
Per-call timeout (thinking vision models need headroom). |
maxImageBytes |
8388608 (8 MB) |
Hard cap on image size. |
temperature |
0 |
Vision endpoint sampling temperature. 0 (default) keeps transcriptions deterministic so the main model's prompt-prefix cache stays stable. |
seed |
(unset) | Optional fixed random seed (if the endpoint supports it). |
attachImages |
true |
Transcribe pasted images to text for text-only main models. |
attachMode |
auto |
auto: pass the user's own prompt to the vision model; describe: always generic description. |
transcriptionCache.enabled |
true |
Cache transcriptions keyed by image content sha256 + question sha256: each (image, question) pair calls the vision endpoint once; later requests reuse the cached text. Fixes TTFT inflation and prompt-prefix cache breaks. |
transcriptionCache.file |
~/.dsh/vision-transcription-cache.json |
Cache persistence file (atomic writes, survives restarts). |
transcriptionCache.maxEntries |
1000 |
Cache entry cap; oldest entries are evicted first. |
deepseekVision.enabled |
true |
Register the deepseek-vision provider route (DeepSeek + image transcription). |
deepseekVision.providerId |
deepseek-vision |
Provider route id shown in the model picker. |
Any OpenAI-compatible vision endpoint works. The defaults are deliberately empty so no provider is assumed; pick one:
| Provider | baseUrl |
model |
Notes |
|---|---|---|---|
| Zhipu GLM (free tier) | https://open.bigmodel.cn/api/paas/v4 |
glm-4.6v-flash |
Free registration, zero cost out of the box |
| Alibaba DashScope (incl. token plans) | https://dashscope.aliyuncs.com/compatible-mode/v1 (or your plan's endpoint) |
qwen3.7-plus / qwen-vl-max |
Your own plan's endpoint if you have one |
| Ollama (local, offline) | http://localhost:11434/v1 |
qwen3-vl:4b |
No API key needed |
| Any OpenAI-compatible gateway | your gateway's /v1 |
the gateway's vision model | — |
Configure per deployment (e.g., your profile's cordis.patch.yml or the plugin
row's config):
- id: vision
name: dsh-plugin-vision
config:
baseUrl: https://open.bigmodel.cn/api/paas/v4
apiKeyEnv: VISION_API_KEY
model: glm-4.6v-flash
timeoutMs: 120000
pnpm install # installs the published @deepseek-ai peer packages
pnpm typecheck
pnpm test # 43 vitest cases: MIME sniffing, payload, parsing, source loading, execute, transcription cache
Two performance issues in the deepseek-vision provider, located from real
session telemetry:
transcriptionCache (image content sha256 + question sha256,
persisted to ~/.dsh/vision-transcription-cache.json): each (image, question)
pair is transcribed once, later requests reuse the cached text.temperature (default 0) and optional seed for deterministic output.read_image
archive copy), persistence, eviction, and deterministic payloads.Initial release: vision_analyze / vision_reask / image_archive tools,
image-attachment transcription, deepseek-vision provider route, image archive.
http(s) URLs. Remote
URLs are fetched directly without SSRF hardening — restrict this tool to trusted networks if you
use it with URL inputs.~/.dsh/vision-transcription-cache.json.read_image tool result, or
a resumed session), every later request carries it; the cache ensures the vision endpoint is only
called on the first occurrence, but the transcribed text still occupies tokens in the history.This project is a bundle (dsh.bundle.patch). Install with dsh plugin add, share on GitHub with
the dsh-plugin topic, or npm publish. See
PUBLISH.md in the companion scaffold repo for the full checklist.
MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。