dsh-tool-vision 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -171,6 +171,51 @@ Example endpoints (`baseURL`):
171
171
  > - Settings changes hot-apply (no restart needed).
172
172
  > - Tested against DSH `0.1.0-rc.6`.
173
173
 
174
+ ## Pixel-level vision tools (v0.6.0, ported from dsh-vision-router)
175
+
176
+ 14 `vision_*` tools driven by the **same** configured endpoint as
177
+ `inspect_image` (baseURL/apiKey/model) — no provider chain, no local models,
178
+ no extra settings:
179
+
180
+ | Tool | Purpose |
181
+ |---|---|
182
+ | `vision_describe` | Image Q&A / multi-image comparison (optional structured JSON) |
183
+ | `vision_ground` | Locate a target and return its ORIGINAL-pixel bounding box |
184
+ | `vision_detect` | Enumerate elements (buttons, inputs, icons…) with numbered boxes |
185
+ | `vision_crop` | Crop a pixel region to a PNG artifact |
186
+ | `vision_pixel_diff` | Per-pixel comparison: ratio, worst regions, heatmap, report |
187
+ | `vision_colors` | Dominant-color quantization for palette matching |
188
+ | `vision_ocr` | Verbatim text transcription (letters only — not scene analysis) |
189
+ | `vision_long_screenshot_ocr` | Chunked long-screenshot transcription into Markdown |
190
+ | `vision_trace` | Potrace vectorization into colored SVG (worker-thread, safe) |
191
+ | `vision_extract_foreground` | Solid-background removal → transparent PNG |
192
+ | `vision_html_screenshot` | Headless render of a local .html (network blocked) |
193
+ | `vision_screenshot` | Desktop capture (privacy-gated: enable `desktopScreenshot` in settings; Win: PowerShell / macOS: screencapture / Linux: import/scrot) |
194
+ | `vision_present` | Publish a generated image to the user via the host attachment store |
195
+ | `vision_materialize` | Copy an attachment/local image into the workspace as a real path |
196
+
197
+ Quality & safety details:
198
+
199
+ - **Content-hash cache** keyed by endpoint+model+image+question (no stale
200
+ answers across model switches, failures are never cached).
201
+ - **Uniform 4MP downscale** before every model call; oversized inputs are
202
+ rejected with a clear error (stat pre-check, 20MB cap on both file and
203
+ attachment paths).
204
+ - **Rate-limit / 5xx auto-retry** with Retry-After-aware backoff; endpoint
205
+ **content-safety rejections** are surfaced as `VISION_CONTENT_FILTERED`
206
+ instead of a generic backend error.
207
+ - **Long-OCR bounds**: 120s total budget, 40-chunk cap, cancellation checks,
208
+ stop-on-first-backend-failure.
209
+ - **Path containment** for relative inputs; artifacts land in
210
+ `<workspace>/.dsh-tool-vision/`.
211
+
212
+ Requires `sharp` / `potrace` / `puppeteer-core` (declared as optional
213
+ dependencies: a failed platform install never blocks the plugin; missing ones
214
+ degrade lazily with an install hint and never break other tools).
215
+
216
+ `vision_screenshot` is privacy-sensitive and therefore **not registered by
217
+ default** — set `desktopScreenshot: true` in the tool-vision settings to
218
+ enable desktop capture.
174
219
 
175
220
  ## Limitations
176
221
 
@@ -183,4 +228,6 @@ Example endpoints (`baseURL`):
183
228
 
184
229
  ## License
185
230
 
186
- MIT
231
+ MIT — bridge preview & integration: xing666173. Pixel vision tools ported
232
+ from [dsh-vision-router](https://github.com/ysr666/dsh-vision-router)
233
+ (© ysr666, MIT) with gratitude.
package/README.zh.md CHANGED
@@ -113,6 +113,46 @@ DeepSeek 自家模型是纯文本的,而且 harness 的每次模型请求都**
113
113
  - **Moonshot(Kimi)**:`https://api.moonshot.cn/v1` —— `moonshot-v1-8k-vision-preview`
114
114
  - **Ollama 本地**:`http://localhost:11434/v1` —— `llama3.2-vision`(无需密钥)
115
115
 
116
+ ## 像素级视觉工具(v0.6.0,移植自 dsh-vision-router)
117
+
118
+ 14 个 `vision_*` 工具由**同一个** `inspect_image` 配置的端点驱动
119
+ (baseURL/apiKey/model)——无 provider 链、无本地模型、零新增配置:
120
+
121
+ | 工具 | 用途 |
122
+ |---|---|
123
+ | `vision_describe` | 看图问答 / 多图对比(可选结构化 JSON) |
124
+ | `vision_ground` | 定位目标,返回原图像素坐标框 |
125
+ | `vision_detect` | 枚举元素(按钮/输入框/图标…),带编号框 |
126
+ | `vision_crop` | 按像素区域裁剪出 PNG 产物 |
127
+ | `vision_pixel_diff` | 逐像素对比:差异比例、最差区域、热图、报告 |
128
+ | `vision_colors` | 主色量化,还原 UI 调色板 |
129
+ | `vision_ocr` | 逐字转写文字(只读字,不做场景识别) |
130
+ | `vision_long_screenshot_ocr` | 长截图分块转写为 Markdown |
131
+ | `vision_trace` | potrace 矢量化输出彩色 SVG(worker 线程,安全) |
132
+ | `vision_extract_foreground` | 纯色背景抠图 → 透明 PNG |
133
+ | `vision_html_screenshot` | 本地 HTML 无头渲染截图(禁网) |
134
+ | `vision_screenshot` | 桌面截屏(隐私门控:需在设置中开启 `desktopScreenshot`;Win: PowerShell / macOS: screencapture / Linux: import/scrot) |
135
+ | `vision_present` | 通过宿主附件库把生成的图片正式展示给用户 |
136
+ | `vision_materialize` | 把附件/本地图片落盘为工作区真实路径 |
137
+
138
+ 质量与安全细节:
139
+
140
+ - **内容哈希缓存**按 端点+模型+图片+问题 取键(切模型不吃旧答案,失败结果
141
+ 不入缓存);
142
+ - **统一 4MP 降采样**后再调用模型;超大输入 stat 预检直接拒绝(文件与
143
+ 附件路径统一 20MB 上限);
144
+ - **限流/5xx 自动重试**(感知 Retry-After 退避);端点**内容安全拒绝**明确
145
+ 返回 `VISION_CONTENT_FILTERED`,不再误报后端不可用;
146
+ - **长截图 OCR 边界**:120s 总预算、40 块上限、取消检查、首块失败即停;
147
+ - **路径 containment**(相对输入禁止逃逸工作区);产物写入
148
+ `<工作区>/.dsh-tool-vision/`。
149
+
150
+ 依赖 `sharp` / `potrace` / `puppeteer-core`(声明为**可选依赖**:平台安装失败
151
+ 不会阻断插件安装;缺失时懒加载降级并给出安装提示,不影响其他工具)。
152
+
153
+ `vision_screenshot` 属于隐私敏感能力,**默认不注册**——在 tool-vision 设置中
154
+ 开启 `desktopScreenshot: true` 后才会注册桌面截屏工具。
155
+
116
156
  ## 限制
117
157
 
118
158
  - 被桥接的图片以文本指引进入对话(转录而非像素)——文本模型无法做像素级上下文推理;视觉模型的描述通过 `inspect_image` 回传。
@@ -121,4 +161,6 @@ DeepSeek 自家模型是纯文本的,而且 harness 的每次模型请求都**
121
161
 
122
162
  ## License
123
163
 
124
- MIT
164
+ MIT —— 桥接预览与整合:xing666173。像素级视觉工具移植自
165
+ [dsh-vision-router](https://github.com/ysr666/dsh-vision-router)(© ysr666,MIT),
166
+ 在此致谢。
package/client.js CHANGED
@@ -79,10 +79,12 @@ window.__ModuleLoader__.load({
79
79
  fieldBridgePreviewScanIntervalMs: "预览兜底扫描间隔(毫秒,0 = 关闭兜底)",
80
80
  fieldBridgePreviewHideHint: "图片加载成功后隐藏桥接提示文本",
81
81
  fieldBridgeAutoImage: "自动声明图片能力(纯文本模型也能粘贴图片)",
82
+ fieldDesktopScreenshot: "桌面截屏工具(vision_screenshot,默认关闭)",
82
83
  hintBridgePreview: "纯展示层:不影响模型侧文本与 inspect_image 调用。",
83
84
  hintBridgePreviewScanIntervalMs: "默认 2000ms;越小响应越快,越大越省资源。",
84
85
  hintBridgePreviewHideHint: "加载失败时保留文本(安全降级,绝不出现既无图又无字)。",
85
86
  hintBridgeAutoImage: "桥接开启时向宿主报告所有模型支持图片,绕过“模型不支持图片”的发送拦截;实际仍走桥接转文本。",
87
+ hintDesktopScreenshot: "隐私敏感能力:仅在开启后注册 vision_screenshot 桌面截屏工具,模型才可截取用户屏幕。",
86
88
  save: "保存",
87
89
  reset: "恢复默认",
88
90
  saved: "已保存",
@@ -116,10 +118,12 @@ window.__ModuleLoader__.load({
116
118
  fieldBridgePreviewScanIntervalMs: "Preview fallback scan interval (ms, 0 = disable)",
117
119
  fieldBridgePreviewHideHint: "Hide the bridged hint text once the image has loaded",
118
120
  fieldBridgeAutoImage: "Auto-declare image capability (paste images on text-only models)",
121
+ fieldDesktopScreenshot: "Desktop screenshot tool (vision_screenshot, off by default)",
119
122
  hintBridgePreview: "Pure display layer: the model-facing text and the inspect_image chain are untouched.",
120
123
  hintBridgePreviewScanIntervalMs: "Default 2000ms; lower is snappier, higher is cheaper.",
121
124
  hintBridgePreviewHideHint: "Text is kept on load failure (safe degradation, never no image AND no text).",
122
125
  hintBridgeAutoImage: "While the bridge is on, report image support for every model to bypass the host image-send gate; images still travel as bridged text hints.",
126
+ hintDesktopScreenshot: "Privacy-sensitive: the vision_screenshot desktop-capture tool is only registered (and visible to the model) when enabled.",
123
127
  save: "Save",
124
128
  reset: "Reset",
125
129
  saved: "Saved",
@@ -145,7 +149,8 @@ window.__ModuleLoader__.load({
145
149
  { key: "bridgePreview", label: "fieldBridgePreview", type: "checkbox" },
146
150
  { key: "bridgePreviewScanIntervalMs", label: "fieldBridgePreviewScanIntervalMs", type: "number" },
147
151
  { key: "bridgePreviewHideHint", label: "fieldBridgePreviewHideHint", type: "checkbox" },
148
- { key: "bridgeAutoImage", label: "fieldBridgeAutoImage", type: "checkbox" }
152
+ { key: "bridgeAutoImage", label: "fieldBridgeAutoImage", type: "checkbox" },
153
+ { key: "desktopScreenshot", label: "fieldDesktopScreenshot", type: "checkbox" }
149
154
  ];
150
155
  var ZH_HINTS = {
151
156
  apiKey: "apiKeyHint",
@@ -158,7 +163,8 @@ window.__ModuleLoader__.load({
158
163
  bridgePreview: "hintBridgePreview",
159
164
  bridgePreviewScanIntervalMs: "hintBridgePreviewScanIntervalMs",
160
165
  bridgePreviewHideHint: "hintBridgePreviewHideHint",
161
- bridgeAutoImage: "hintBridgeAutoImage"
166
+ bridgeAutoImage: "hintBridgeAutoImage",
167
+ desktopScreenshot: "hintDesktopScreenshot"
162
168
  };
163
169
 
164
170
  function labelOf(f, t) {
package/index.js CHANGED
@@ -43,6 +43,7 @@ import z from "@deepseek-ai/schemastery";
43
43
  import { defineTool } from "@deepseek-ai/dsh-tools";
44
44
  import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
45
45
  import { ensureSettingsNamespaceExposed } from "./vendor/dsh-settings-expose.js";
46
+ import { registerVisionTools, CONTENT_FILTER_RE } from "./lib/vision-tools.js";
46
47
 
47
48
  /** Cordis plugin name. */
48
49
  const name = "tool-vision";
@@ -110,6 +111,8 @@ const Config = z.object({
110
111
  bridgePreviewScanIntervalMs: z.number().default(2000),
111
112
  /** Hide the bridged hint text once the preview image has loaded (kept on failure — never "no image AND no text"). */
112
113
  bridgePreviewHideHint: z.boolean().default(true),
114
+ /** Privacy gate for vision_screenshot: desktop capture is only registered when explicitly enabled. */
115
+ desktopScreenshot: z.boolean().default(false),
113
116
  /**
114
117
  * Advertise image input capability for every model while the bridge is on.
115
118
  * The host admission gate (host-apiproxy `prompt`/`selectModel`) refuses
@@ -611,10 +614,27 @@ function apply(ctx, config) {
611
614
  const cfg = getConfig();
612
615
  const cwd = exec.agent?.session?.header?.cwd ?? process.cwd();
613
616
  const { url, note } = await toImageUrl(args.path, cwd, cfg);
614
- const answer = await callVision(cfg, url, args.question, args.detail, exec.signal);
615
- return note === url ? answer : `${answer}\n\n(image: ${note})`;
617
+ try {
618
+ const answer = await callVision(cfg, url, args.question, args.detail, exec.signal);
619
+ return note === url ? answer : `${answer}\n\n(image: ${note})`;
620
+ } catch (error) {
621
+ const raw = error && error.message ? String(error.message) : String(error);
622
+ if (CONTENT_FILTER_RE.test(raw)) {
623
+ throw new Error(
624
+ "inspect_image: 图片被视觉端点的内容安全策略拒绝(检测到敏感或不安全内容)。" +
625
+ "这不是网络或配置问题,请换一张图片或调整图片内容后再试。",
626
+ );
627
+ }
628
+ throw error;
629
+ }
616
630
  },
617
631
  }));
632
+
633
+ // ── pixel-level vision tools (ported from dsh-vision-router) ─────────────
634
+ // 14 vision_* tools driven by the SAME configured endpoint as inspect_image
635
+ // (baseURL/apiKey/model). No provider chain, no local models, no extra
636
+ // settings: everything comes from the existing tool-vision configuration.
637
+ registerVisionTools(ctx, getConfig);
618
638
  }
619
639
 
620
640
  export {