dsh-plugin-vision

by Tianbaidi

1 工具与能力github 检测到 manifest package.json#dsh收录于 08-17

DeepSeek Harness(dsh)的辅助视觉:经外部 OpenAI 兼容视觉端点分析图片并返回文本答案,适配任意主模型

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 —…

安装

dsh plugin --profile web add github:Tianbaidi/dsh-plugin-vision

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

安装与环境配置指引、插件开发教程见 DSH 中文社区文档 ↗

安装即在你的机器上以你的权限运行第三方代码——它可读写文件、使用凭据、访问网络,DSH 的工具审批不会为插件代码加沙箱。「检测到 manifest」仅代表发现 dsh.bundle / dsh.plugin 清单,不构成兼容性或安全审查;安装前请审阅源码,不熟悉的插件先在不含密钥的环境试用。

README

目录

DeepSeek Harness (dsh) 的辅助视觉插件: 把图片发给外部的 OpenAI 兼容视觉端点,返回文字分析结果。任何主模型都能用——包括纯文本的 DeepSeek。

为什么需要它

dsh 内置的 read_image 工具把图片注入主模型上下文, 要求主模型声明支持图像输入;而 DeepSeek 适配器是 inputModalities: ['text'],所以 read_image 在 DeepSeek 下直接拒绝执行。本插件走互补的辅助视觉路线:把图片发给独立的视觉模型,把回答以文字形式 返回给 agent,不依赖主模型的视觉能力。

read_image(内置) vision_analyze(本插件)
图片去向 主模型上下文(原生) 外部视觉端点
主模型需支持视觉 需要 不需要
结果 模型直接看到图片 纯文字回答

工作原理

  1. 读取图片——本地文件路径或 http(s) 链接(有大小上限、魔数 MIME 嗅探)。
  2. 转成 base64 data URL。
  3. 向端点发 OpenAI 兼容的 chat/completions 请求,content 为 [{type:text},{type:image_url}]。
  4. 返回视觉模型的回答(思考型模型无正文时回退到 reasoning_content)。
  5. 转述结果缓存(v0.1.1 新增):同一图片(按内容 sha256 键控,跨粘贴/read_image/恢复历史等 入口统一去重)+ 同一问题,只调用一次视觉端点;后续请求直接复用缓存文本,并把视觉端点采样 温度固定为 0(temperature)保证输出确定。效果:
    • 图片进入会话历史后,后续每一轮不再重复调用视觉端点 → 首字 token 时间(TTFT)保持平稳;
    • 转述文本永不变化 → 主模型(DeepSeek)的前缀缓存不会因转写波动而断裂,缓存命中率不衰减。

四个能力

1. vision_analyze 工具

把图片(本地路径或 URL)发给配置的视觉端点,返回文字回答——任何主模型都能用,包括纯文本的 DeepSeek。

2. 图片附件转述(Web 界面贴图)

Web 界面支持粘贴/拖拽图片,但 DeepSeek 适配器会拒绝图片内容(UNSUPPORTED_CONTENT)。 本插件挂钩 agent/pre-step(官方文档里"替换进入 step 的消息"的接缝):当用户消息携带图片块时, 调用视觉端点描述图片,在消息到达模型之前把每个图片块换成 [User-attached image description] 文字块。DeepSeek 永远只看到文字,贴图就能用了。当主模型路由声明支持图像输入时,自动跳过转述, 走原生视觉。用 attachImages 开关(默认 true)。转述失败会降级为明确提示,不会卡住对话。

attachMode 控制问视觉模型的方式:

  • auto(默认):用户随图写了文字时,把用户的原文原样作为问题传给视觉模型—— "这是谁?""翻译图里的文字""这个页面哪里有问题?"都会被直接作答,另附一行图片概要便于追问; 没有文字时退化为通用描述。
  • describe:永远用通用描述提示词,忽略用户文字。

3. deepseek-vision provider 路由(Web 界面贴图,根治)

Web 界面的上传预检在所选模型未声明图像输入时会拒绝图片——所以在普通 DeepSeek 路由下,粘贴的图片 根本到不了 agent。本插件注册一个 deepseek-vision provider:一个 DeepSeekAdapter 子类,声明 支持图像输入(预检放行),在请求时把附件图片转述为文字,再委托给真正的 DeepSeek chat-completions 端点。主模型仍然是 DeepSeek——同一端点、同一 key、同一批模型。在模型选择器里选 "DeepSeek (vision via plugin)",然后照常粘贴/拖拽图片即可。用 deepseekVision.enabled 开关(默认 true),provider id 为 deepseekVision.providerId(默认 deepseek-vision)。转述走上面的内容哈希缓存,图片在历史中 每轮都只会命中缓存文本,不再重复调用视觉端点。

4. opencode-vision provider 路由(OpenCode Go + 图片转述)

同样的包装思路,套在 OpenCode Go(opencode.ai/zen/go)上——它是 dsh 内置 pi-ai 库的原生 provider。本插件注册一个 opencode-vision provider:一个 PiAiAdapter 子类,提供 opencode-go 全部模型(deepseek-v4-flash/pro、qwen3.7-max/plus、 kimi-k3、glm-5.1/5.2、minimax-m3、grok-4.5 等),声明支持图像输入(Web 上传预检放行),并在 请求时把附件图片转述为文字(针对纯文本模型)。目录里标记为原生支持图像的模型(如 qwen3.7-plus、 minimax-m3)则原样透传图片,不做转述。在模型选择器里选 "OpenCode Go (vision via plugin)",然后照常粘贴/拖拽图片即可。

用 opencodeVision.enabled 开关(默认 true),provider id 为 opencodeVision.providerId (默认 opencode-vision)。凭据与基础 opencode-go 路由共用 OPENCODE_API_KEY,在 Models 页存一次,两条路由都能用。

安装版 pi-ai 目录还没收录的模型(账户可能支持更新的)可以用 opencodeVision.extraModels 直接声明——必须指定线上协议,端点与容量默认走 OpenCode Go 网关:

config:
  opencodeVision:
    extraModels:
      - id: glm-5.3
        api: openai-completions
        contextWindow: 1000000
        maxTokens: 131072
      - id: qwen3.8-max
        api: anthropic-messages

如果还想启用不带视觉的原始 opencode-go 路由(含全部模型),在 $DSH_HOME/settings.yaml 加 一段 llm-pi-ai 配置(热加载,无需重启):

# $DSH_HOME/settings.yaml
llm-pi-ai:
  providers:
    opencode-go:
      apiKeyEnv: OPENCODE_API_KEY
      displayName: OpenCode Go
      # 去掉 `models` 则直接提供安装版 pi-ai 目录里的全部模型
      models:
        - id: deepseek-v4-flash
        - id: deepseek-v4-pro
        - id: qwen3.7-plus
        - id: grok-4.5
        # ……其余你想用的 opencode-go 模型

免第二端口:把插件装进你现有的 GUI

视觉 API key 优先从 harness 凭据服务解析(Web 界面已存的 key),回退到环境变量——GUI 里不需要 额外导出。把插件写进 home 级用户层 patch(对每个 profile 生效,包括你正在用的 web GUI, 无需第二个服务/端口):

# $DSH_HOME/cordis.patch.yml
- insert:
    - id: vision
      name: 'file:///<path-to-plugin>/lib/index.js'

把视觉 API key 加进已存凭据(或导出 VISION_API_KEY),重启一次 GUI 即可。注意用编译后的 lib/index.js——发布版 CLI 能加载 .ts 入口,但解析不了 src/ 里 .js 后缀的兄弟模块。

4. 图片归档(贴图自动存盘 + index.json)

每张粘贴的图片自动保存到 ~/.dsh/image-archive/,按日期编号命名 (2026-08-14_120331_001.png),写入 index.json 清单(路径、sha256、大小、来源、可选备注), 并把位置标注给模型([图片已存档: …])。配套两个工具:

  • image_archive —— agent 把重要图片(用户喜好、票据、关键数据)归档到指定文件夹并可加备注: 保存为 <archiveDir>/<folder>/<名称或日期编号>.png 并更新 index.json。
  • image_archive_find —— 按名称/文件夹/备注检索清单。

用 archive.enabled(默认 true)和 archive.dir(默认 ~/.dsh/image-archive)配置; 按附件 id 去重。

试运行

把 bundle 装进任意 profile(prepare 脚本会在安装时构建):

dsh plugin --profile web add github:Tianbaidi/dsh-plugin-vision

把视觉 API key 存进凭据或环境变量(VISION_API_KEY),重启 GUI。粘贴/拖拽图片提问即可, 或直接用 vision_analyze 工具。要用 opencode-vision 路由,再存一份 OPENCODE_API_KEY (Web Models 页两种 key 都能存)。想用开发 overlay?指向你的本地检出:

- insert:
    - id: vision
      name: 'file:///<path-to-plugin>/lib/index.js'

Windows 注意:overlay 里插件路径必须是 file:// URL(file:///D:/...%20...),不能用裸的 D:/... 路径——ESM loader 会把后者当成 d: 协议拒绝。

配置

键 默认值 含义
baseUrl (空,必须配置) OpenAI 兼容 chat-completions 端点基地址。
apiKeyEnv VISION_API_KEY 存放 API key 的环境变量名(或已存凭据)。
model (空,必须配置) 端点上的视觉模型名。
visionEndpoint (空) 活跃转述端点 id(对应 visionEndpoints 里的某条)。为空或 id 不在注册表时,回退到顶层 baseUrl / apiKeyEnv / model 单端点。
visionEndpoints [] 命名转述端点注册表。每条需 id,可覆盖 baseUrl / apiKeyEnv / model / timeoutMs / maxImageBytes / temperature / seed / attachMode 中的任意子集;未设置的字段继承顶层值。id 同时充当转述缓存命名空间——切换端点后不会复用另一个模型的转述文字。
timeoutMs 120000 单次调用超时(思考型视觉模型需要余量)。
maxImageBytes 8388608(8 MB) 图片大小硬上限。
temperature 0 视觉端点采样温度。0(默认)保证同一图+同一问题输出确定,维持主模型前缀缓存稳定。
seed (未设置) 可选固定随机种子(取决于端点是否支持)。
attachImages true 把粘贴的图片转述为文字(供纯文本主模型使用)。
attachMode auto auto:把用户自己的提示词传给视觉模型;describe:始终用通用描述。
transcriptionCache.enabled true 转述结果缓存:同一图片(按内容 sha256)+ 同一问题只调用一次视觉端点,后续请求直接复用缓存文本。修复 TTFT 膨胀与缓存前缀断裂。
transcriptionCache.file ~/.dsh/vision-transcription-cache.json 缓存落盘文件(原子写入,重启不丢)。
transcriptionCache.maxEntries 1000 缓存条目上限,超出后淘汰最旧条目。
deepseekVision.enabled true 注册 deepseek-vision provider(DeepSeek + 图片转述)。
deepseekVision.providerId deepseek-vision 模型选择器里显示的 provider 路由 id。
opencodeVision.enabled true 注册 opencode-vision provider(OpenCode Go + 图片转述)。
opencodeVision.providerId opencode-vision 模型选择器里显示的 provider 路由 id。
opencodeVision.apiKeyEnv OPENCODE_API_KEY 存放 OpenCode Go API key 的环境变量名(或已存凭据)。
opencodeVision.displayName OpenCode Go (vision via plugin) 选择器/配置界面显示的 provider 名。
opencodeVision.extraModels [] 合并进路由的未收录模型(比安装版 pi-ai 目录更新的账户模型);每条需 id + api(anthropic-messages / openai-completions / openai-responses),可选 baseURL / contextWindow / maxTokens / input / reasoning。

任意 OpenAI 兼容视觉端点都可用。默认值故意留空,不假定任何厂商:

厂商 baseUrl model 说明
智谱 GLM(免费档) https://open.bigmodel.cn/api/paas/v4 glm-4.6v-flash 免费注册即用,零成本开箱
阿里百炼(含 token plan) https://dashscope.aliyuncs.com/compatible-mode/v1(或你的套餐端点) qwen3.7-plus / qwen-vl-max 有套餐就用套餐端点
Ollama(本地离线) http://localhost:11434/v1 qwen3-vl:4b 无需 API key
任意 OpenAI 兼容网关 网关的 /v1 网关的视觉模型 —

按部署配置(profile 的 cordis.patch.yml 或插件行的 config):

- id: vision
  name: dsh-plugin-vision   # 或 file:// 指向 src/index.ts
  config:
    model: qwen-vl-max
    timeoutMs: 120000

多转述端点(注册表)

一个端点不够用(比如 token plan 快到期、想用 OpenCode Go 视觉模型兜底)时,可以注册多个命名端点,用 visionEndpoint 选当前生效的那个。每条预设未设置的字段继承顶层值,所以通常只需写差异项:

- id: vision
  name: dsh-plugin-vision
  config:
    # 当前生效的转述端点(plan 到期时改这里即可)
    visionEndpoint: aliyun
    visionEndpoints:
      - id: aliyun
        baseUrl: https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
        apiKeyEnv: ALIBABA_CODING_PLAN_API_KEY
        model: qwen3.7-plus
      - id: opencode-kimi
        baseUrl: https://opencode.ai/zen/go/v1
        apiKeyEnv: OPENCODE_API_KEY
        model: kimi-k3
        temperature: 1   # 必须:kimi 模型拒绝默认的 temperature 0
      - id: opencode-qwen
        baseUrl: https://opencode.ai/zen/go/v1
        apiKeyEnv: OPENCODE_API_KEY
        model: qwen3.7-plus

OpenCode Go 候选模型已用真实 key 对着线上端点实测(2026-08-16,带图 payload):目录里声明支持图像的 go 模型,目前只有 kimi-k3、kimi-k2.7-code 能真正当转述端点,且两者都必须 temperature: 1——插件默认的 temperature: 0 会被拒("invalid temperature: only 1 is allowed for this model")。mimo-v2.5 能答但偶尔返回空文本;minimax-m3 能答但把推理内联进正文(<think>…)。kimi-k2.6、qwen3.6-plus、qwen3.7-plus 目前上游 503 "Endpoint is unavailable"(纯文本和带图都 503,直连/代理都试过)——不是客户端问题,等网关恢复再试。切换端点不会吃到另一个模型的旧缓存——每条预设的 id 就是转述缓存的命名空间。

开发

pnpm install        # 安装已发布的 @deepseek-ai peer 依赖
pnpm typecheck
pnpm test           # 58 个 vitest 用例:MIME 嗅探、payload、解析、图片加载、execute、转述缓存、provider 路由、端点注册表

更新日志

v0.3.0 —— 转述端点注册表

新增 visionEndpoints / visionEndpoint 配置:注册任意数量的命名转述端点,用 visionEndpoint 切换当前生效的那个。每条预设未设置的字段继承顶层配置,所以通常只需写 baseUrl / apiKeyEnv / model。预设 id 同时是转述缓存命名空间——切换端点不会复用另一个模型的缓存文字。activeVisionEndpoint(config) 解析当前端点并已导出。旧式单端点配置(baseUrl / apiKeyEnv / model)原样兼容(无 id → 默认缓存桶,磁盘上已有条目继续有效)。

v0.2.0(2026-08-16)—— opencode-vision provider 路由

新增 opencode-vision provider 路由:在模型选择器里一条入口提供 pi-ai 内置的 OpenCode Go 全部模型目录,声明图像输入(Web 上传预检放行),并对纯文本模型在请求时把附件图片转述为文字 (原生支持图像的模型如 qwen3.7-plus / minimax-m3 原样透传图片)。实现为 PiAiAdapter 子类、直接复用安装版 pi-ai 目录,三种线上协议(openai-completions / anthropic-messages / openai-responses)与 provider 的推理特化行为全部保留。与基础 opencode-go 路由共用 OPENCODE_API_KEY。opencodeVision.extraModels 可以把目录未收录的账户模型(如 glm-5.3、qwen3.8-max)按显式协议合并进路由;新增 7 个测试(目录列出、extra 合并、图像 声明、转述、原生视觉透传、回放对齐、注册)。

v0.2.0 修复:委托层在线上把 model.provider 改回目录名,导致 pi-ai 在会话回放状态里写的是 opencode-go,而 harness 的 source 记的是 opencode-vision——恢复会话时报 "invalid pi-ai replay state: provider does not match assistant source"。适配器现在把每条 finish 块的 replayState.provider 对齐到路由 id。修复前写入的会话,可把导出 JSONL 里每条消息的 replayState.provider 改成与 source.provider 一致来修复。

v0.1.1(2026-08-16)—— 大修:TTFT 与缓存命中率

修复 deepseek-vision provider 的两个性能问题(由真实会话数据定位):

  • TTFT 膨胀:此前图片一旦进入会话历史,每一轮请求都会同步重调视觉端点(实测首字时间从 ~1s 恶化到 22–69s)。新增 transcriptionCache(内容 sha256 + 问题 sha256 键控,落盘 ~/.dsh/vision-transcription-cache.json),同一 (图片, 问题) 只转写一次,后续直接复用。
  • 缓存命中率衰减:此前转写文本非确定性,导致主模型前缀缓存从图片位置起永久断裂(实测命中率 从 99.8% 衰减到 91.1% 且持续下降)。新增 temperature(默认 0)与可选 seed,保证同一图 输出确定,前缀缓存保持稳定。
  • 新增 5 个测试覆盖缓存命中、内容哈希键控(粘贴 vs read_image 归档副本)、持久化、淘汰、确定性载荷。

v0.1.0

初始发布:vision_analyze / vision_reask / image_archive 工具、图片附件转述、deepseek-vision provider 路由、图片归档。

已知限制

  • 图片来源为本地文件路径(相对 harness 工作目录解析)或纯 http(s) 链接。远程链接直连抓取,未做 SSRF 加固——如果开放 URL 输入,请限制在可信网络内使用。
  • 图片按原样发给端点,超限直接拒绝(不依赖 Pillow 缩放)。超大截图请先压缩。
  • 视觉调用产生的 token 费用计入所配置端点的套餐。
  • 转述缓存以「图片内容 sha256 + 问题文本 sha256」为键:同一图片换一个问题会重新调用一次视觉端点 (结果仍会缓存)。想强制刷新某张图的转述,可以删掉 ~/.dsh/vision-transcription-cache.json 中对应条目或整个文件。
  • 一旦图片进入会话历史(用户粘贴、read_image 工具结果、恢复的会话),后续每一轮请求都会带着它; 转述缓存确保只有首次需要调用视觉端点,但历史中已存在的图片仍会占用转述后的文本 token。

发布

本项目是 bundle 格式(dsh.bundle.patch),可用 dsh plugin add 安装、在 GitHub 上加 dsh-plugin 话题分享、或 npm publish。完整清单见 配套脚手架仓库里的 PUBLISH.md。

License

MIT

原始 README: https://github.com/Tianbaidi/dsh-plugin-vision/blob/main/README.zh.md ↗