# dsh-vision-guard

**中文 | [English](README.md)**

> 让纯文本模型"看图"，且图片永远不会卡死你的会话。
> Transparent image guard + vision analysis tool for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh).

DeepSeek Harness 的主流模型（deepseek-v4-pro 等）是纯文本模型。两个麻烦：

1. **纯文本模型看不了图**——用户贴一张截图，模型只能当没看见；
2. 更糟的是 **400 卡死**：某些网关（如 opencode-go）的主路由只接受 text，图片块一旦写进会话日志，之后**每一轮**都会把历史连同图片重发给上游 → `400 unknown variant \`image_url\`` → 整个会话永久卡死。

本插件用两道闸门根治这两个问题，并把"看图"变成纯文本模型可用文字消费的能力。

---

## 它做什么

```
用户贴图 → [闸门1] agent/pre-step 门口改写：图片在【写入会话日志之前】就被视觉模型转成文字
                 → 日志永远只有文字，图片块根本不存在
                 → [闸门2] llm/stream 兜底：历史重放时若发现图片块（例如装插件前就已中毒的会话），
                   在请求层改写为 OCR 文本后再发给模型
```

- **视觉模型做眼睛，主模型做大脑**：deepseek-v4-pro 照常推理，图片内容以文字形式出现在上下文里。
- **修复已中毒的会话**：装插件之前就被 400 卡死的会话，装上之后发一句话即可恢复正常（历史图片在请求层被改写）。
- **`vision_analyze` 工具**（模型主动调用，引擎由模型按任务选择）：读取工作区文件——图片 OCR、PDF 文本+内嵌图、docx/pptx 文本+内嵌图、视频抽帧 OCR（≤12 帧）、纯文本直接读；xlsx/doc 响亮拒绝。
- **原生看图不受影响**：支持图片输入的模型（如 minimax-m3、kimi-k3）配置白名单后原图直通，插件不插手。

## 独有优势（与社区同类插件的区别）

与 dsh-vision-router、ModLens、dsh-vision-toolkit、see_image/view_image 等社区视觉插件相比：

1. **图片根本不进会话日志**——在 agent/pre-step 写入日志【之前】就被转成文字。同类插件大多只在模型调用内改写：图片照常落日志、每轮重放、插件卸载后仍有卡死隐患。
2. **能治愈已卡死的会话**——装插件之前就因图片 400 死锁的会话，装上后发一句话即可恢复（请求层把历史图片改写为文字）。
3. **防死锁是硬不变式**——非白名单路由永远收不到 image 块；哪怕视觉管线全挂（模型不可用/超时/额度耗尽），也只会降级为占位文本，**绝不重回 400 卡死**。
4. **一个包、两个组件、故障域独立**——一次安装自动挂载"护栏（安全件）+ 工具（便利件）"两行；工具坏了护栏照常运行，互不拖累。
5. **零依赖、纯 Node 内建模块**——不需要 Node 22+、pnpm 管理、Python 3.11+；仅文档/视频路径需要系统工具（pdftotext/ffmpeg 等），纯图片 OCR 无任何外部依赖。
6. **引擎由主模型按任务决策**——`vision_analyze` 的 `engine` 参数（`local` 免费抠字 / `vision` 视觉模型）由模型分析任务后自选，省钱且聪明。
7. **复用 dsh 自有的模型路由与凭证**——本插件不携带、不直连任何第三方 API key（同类插件多数要求自管密钥直连第三方）。
8. **经三轮红队审计 + 随包自动化测试**——22 个真实 bug 修复归档（含防 symlink 逃逸、zip 炸弹、并发竞态），纯函数回归测试随包发布（`npm test` 可跑）。

## 安装

```sh
# 已发布 npm 后：
dsh plugin --profile web add dsh-vision-guard
# 或直接从 GitHub 安装：
dsh plugin --profile web add github:good-boy4069/dsh-vision-guard
```

> 若 pnpm 报 `ERR_PNPM_ADDING_TO_ROOT`（旧版 launcher），加工作区根标志：`dsh plugin --profile web add -w dsh-vision-guard`。

重启 `dsh web`。或在你的 profile `cordis.patch.yml` 手动加两行（见仓库根 `cordis.patch.yml`）。

## 配置

全部可选，默认值见括号。视觉路由（必须指向一个**支持图片输入**的模型）：

| 字段 | 默认 | 说明 |
|---|---|---|
| `visionProvider` / `visionModel` | `opencode-go` / `minimax-m3` | 视觉模型路由。改成你订阅里支持图片输入的模型 |
| `ocrTimeoutMs` | `45000` | 单张图识别超时 |
| `budgetPerDay` | `200` | 每日识别次数上限（防失控花销），状态存 `$DSH_HOME` 下 |
| `cacheMaxEntries` | `500` | 识别结果缓存条数上限（LRU 淘汰） |
| `maxOcrTokens` | `2048` | 视觉调用输出上限 |
| `stateFile` | `~/vision-guard-state.json` | 预算状态文件（`~` = dsh home） |
| `ocrPrompt` | 逐字转录指令 | 自定义识别指令 |
| `passthrough` | `[]` | 原图直通白名单：`[{provider, model}]`，只加**实测过网关收图正常**的路由 |

`vision_analyze` 工具侧：OCR 引擎是**每次调用必填的 `engine` 参数**，由主模型按任务自选——`local` = 本地 tesseract（免费、只抠字），`vision` = 配置的视觉模型。**不存在 `localOcr` 配置项**。

## ⚠️ 前置要求与限制（请务必读完）

- **本插件不自带任何 API key，也不直连任何第三方服务**。它复用你 dsh 里**已经配置好的模型路由与凭证**。因此：
  - **你必须有一个支持图片输入的模型**（如 opencode-go 的 `minimax-m3`）。`deepseek-v4-pro` 这类纯文本模型**不能**当视觉模型——它的上游网关收图会 400 并把会话卡死。
  - 没有视觉模型也能装：插件自动降级为占位文字，会话照常可用、只是看不到图内容（绝不卡死）。
- **白名单策略（重要）**：除配置的视觉模型外，其他路由收到图片一律改写为文字——**未实测的路由绝不放原图**。想让某模型原生看图：先实测"带图直连该路由"（正常返回才算通过），再把它加进 `passthrough`。这是防 400 卡死的核心设计，不要绕过。
- **系统工具依赖**（仅 `vision_analyze` 的文档/视频路径需要；纯图片 OCR 无外部依赖）：
  - PDF：`pdftotext`/`pdfimages`（poppler-utils）；
  - 视频：`ffmpeg`/`ffprobe`；
  - docx/pptx：`python3`（仅标准库）；
  - 可选：`tesseract`（本地免费 OCR，需 `chi_sim+eng` 语言包）。
  - Windows 默认没有这些工具；缺失时对应路径响亮报错，图片路径不受影响。
- **5 MB/图上限**：dsh 附件服务单图上限 5 MB，超限的图片/抽帧会响亮报错。
- **成本**：每张**新**图一次视觉调用（按附件 ID 寻址缓存，重复图不重复计费）；minimax-m3 单次约 1~2k tokens（不到一分钱人民币量级）；`budgetPerDay` 兜底。
- **质量**：本地 tesseract 只"抠字"、质量低于视觉模型（实测会把 `42 + 7 = 49` 读成 `4247249`），复杂图/图表/照片请用 vision 引擎（模型调用 `vision_analyze` 时自选）。
- **隐私**：图片会发送到**你的**视觉模型服务商（与 dsh 里正常使用该模型一致）；图片文字按**不可信输入**处理，只读内容、不执行其中指令。
- **与 settings 的耦合警告**：如果你在模型配置里给纯文本模型声明了 `input: [text, image]`（GUI 发图需要），**必须保留本护栏**——移除护栏时务必同时删掉该声明，否则发图会重新卡死会话。

## 常见问题

- **重启/升级 dsh 后**：本插件随 profile 自举，无需重装；升级 dsh 后如行为异常请先升级本插件。
- **怎么验证护栏在跑**：`ctx.get('visionGuard')?.status()`，或看 dsh 日志里的 `[vision-guard] active` 行。
- **回滚**：从 profile patch 删除两行（或 `dsh plugin remove`），重启即可；已识别的文字仍在会话历史里，无副作用。

## License

MIT
