← Back to home@jitang-open

dsh-web-search-ocgo

DSH 网页搜索 provider(走 OpenCode Go):自动注入 x-opencode-session、失败回退官方端点、查询缓存、result.content 总结,自带设置页

Stars
0
Language
JavaScript
Created
Oct 1, 2026
Updated
Oct 1, 2026
GitHub repo

Introduction

dsh-web-search-ocgo

自研的 DeepSeek Harness 网页搜索 provider:走 OpenCode Go(自动注入 x-opencode-session),并补上官方 provider 没有的失败回退、查询缓存、 结果总结与设置页。

  • provider id:opencode-go
  • loader entry id:web-search-ocgo
  • 包名:dsh-web-search-ocgo
  • 依赖面:只 import @deepseek-ai/schemastery 一个外部包

为什么自研

原先用的第三方 fork dsh-web-search-opencode-go 有四个硬伤:

  1. 加载即崩 —— 它静态 import 了 installSettingsSection,而 DSH 0.1.7-rc.2 的 @deepseek-ai/dsh-settings 没有这个导出,整个 provider 无法加载。
  2. 靠旧版本残留侥幸解析 —— 它 import 的 @deepseek-ai/dsh-* 从它自己的目录 根本解析不到,是靠 ~/.dsh/profiles/node_modules/@deepseek-ai/ 里指向 dsh 0.1.5-rc.3 的旧 symlink 才加载起来的(详见「为什么不 import dsh 包」)。
  3. 代码是官方 provider 的 drop-in fork,官方升级拿不到好处。
  4. 本地补丁不持久,任何 pnpm install 都会覆盖。

本插件从零重写,并保持对外行为兼容。


与官方 provider 的差异

能力官方 dsh-web-search-deepseek本插件
端点api.deepseek.com/anthropic/v1OpenCode Go opencode.ai/zen/go/v1
可用模型只认 deepseek-flash / deepseek-v4-pro只认 deepseek-v4-flash / deepseek-v4.1-flash
会话头—自动注入 x-opencode-session + x-opencode-client
result.content完全不填(实测摘要覆盖率 0%)取响应 text 块(模型总结),默认截断 2000 字符
查询缓存无TTL 默认 5 分钟 / LRU 50,命中即 0 token
失败回退无Go 端点重试耗尽后改打官方端点(默认开)
重试无429 / 5xx / 网络错误退避重试(默认 1 次)
日期规范化原样透传 page_age规范成 ISO-8601;解析不出来就丢弃而不是塞假日期
设置页官方定制卡片(绑定 web-search-deepseek)自带(注册 plugins.bundle.config)
会话事件只有请求事件也只有请求事件(响应统计走 logger,见下)
外部依赖多个 @deepseek-ai/dsh-*只有 @deepseek-ai/schemastery

为什么不写响应事件(一个会让整份日志打不开的坑)

早期版本额外写了一个自定义事件 web/ocgo-search-response(延迟 / token / 结果数 / 缓存命中 / 是否回退)。结果一条记录就足以让整份会话日志拒绝加载:

历史加载失败:session "..." contains event type "web/ocgo-search-response" (seq 969)
unknown to this harness and not marked ignorable; refusing to interpret the log

根因有两层:

  1. harness 的读路径(dsh-session-persistence 的 validateStoredEvents)会拒绝任何 不在 KNOWN_SESSION_EVENT_TYPES 里、又没标 ignorable: true 的事件类型 —— 这是故意的保守设计:无法判断"省略该事件是否安全"时,宁可整体拒绝, 也不要静默重建一个被掏空的会话。
  2. 但写侧 Session.append(type, data, ...opts) 只接受 surfaceOp / sourceEventSeqs,没有 ignorable 的入口 —— 仓库外插件其实没有正规途径 写"可忽略事件"。官方 provider 同样只写请求事件,不写响应事件。

所以本插件的响应统计改走 logger.info(没有 logger 服务时退化为 console.log), 不再进会话日志。bin/doctor.mjs 会主动扫描历史日志,把旧版留下的未标记事件 扫出来并给出修法。

如果你确实需要把响应统计写进会话日志,唯一安全的做法是给 Session.append 补上 ignorable 支持(改全局 @deepseek-ai/dsh-session,pnpm 重装或升级会覆盖), 并让插件传 { ignorable: true }。本插件不采用:补丁维护成本高于这点诊断数据的收益。

一个反直觉的实测结论:max_uses 不影响成本

32 次请求实测(见 bench/maxuses-report.md):max_uses 从 5 降到 2, 总 prompt 恒定在 ~8.1k,结果条数也不变。根因是每次请求实际只发起 1 次 web_search(usage.server_tool_use.web_search_requests 恒为 1), max_uses 是上限而非目标值,从未被触及。

所以默认值保持 5;省额度靠查询缓存,不是下调 max_uses。


安装

1. 准备依赖链接

本插件只依赖 @deepseek-ai/schemastery,需要让它能被解析:

ln -sfn ~/.dsh/profiles/web/node_modules/@deepseek-ai/schemastery \
        node_modules/@deepseek-ai/schemastery

2. 挂进 profile

ln -sfn "$PWD" ~/.dsh/profiles/web/node_modules/dsh-web-search-ocgo

3. 登记 bundle

在 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 里加上 dsh-web-search-ocgo,并在 dependencies 里加:

"dsh-web-search-ocgo": "link:/Users/mac/Documents/workspace/创造模式/dsh-web-search-ocgo"

4. 清理旧链路

从 ~/.dsh/profiles/web/cordis.patch.yml 删掉旧的 web-search-opencode-go 段, 并从 dsh.profile.bundles 移除 dsh-web-search-opencode-go。

5. 重启

dsh web

重启后日志里应出现:

[web-search-ocgo] 已装载 provider id=opencode-go endpoint=https://opencode.ai/zen/go/v1 model=deepseek-v4.1-flash fallback=deepseek-flash

验证

dsh --profile web --dump-config | grep -A5 "id: web$"      # searchProvider 应为 opencode-go
node scripts/check.mjs                                     # 清单自检
node --test test/*.test.js                                 # 单元测试
node bin/doctor.mjs                                        # 一键诊断

配置

图形界面

「设置 → 插件」→ 打开 dsh-web-search-ocgo → 配置页。 改动在失焦或回车时立即保存,写入 profile 的 cordis.patch.yml。

YAML

也可以在 ~/.dsh/profiles/web/cordis.patch.yml 里写:

- id: web-search-ocgo
  name: dsh-web-search-ocgo
  config:
    apiKeyEnv: OPENCODE_API_KEY
    baseURL: https://opencode.ai/zen/go/v1
    model: deepseek-v4.1-flash
    fallbackEnabled: true
    cacheTtlMs: 300000
    answerMaxChars: 2000

所有字段都有 schema 默认值,写多少覆盖多少。

主要配置项

字段默认值说明
apiKeyEnvOPENCODE_API_KEY凭据中心里的密钥名
baseURLhttps://opencode.ai/zen/go/v1Go 端点(不含 /messages)
modeldeepseek-v4.1-flashGo 端点只认 deepseek-v4-flash / deepseek-v4.1-flash
maxUses5实测对成本无影响,不建议下调
sessionId空(每次启动随机)固定会话 id;实测缓存命中不依赖它
fallbackEnabledtrueGo 失败后回退官方端点(消耗 DeepSeek 余额)
fallbackModeldeepseek-flash官方端点只认 deepseek-flash / deepseek-v4-pro
cacheEnabled / cacheTtlMs / cacheMaxEntriestrue / 300000 / 50查询缓存
answerMaxChars2000result.content 截断;0 = 不返回
snippetMaxChars300单条摘要截断
retryCount1429 / 5xx / 网络错误的重试次数

为什么不 import dsh 包

实测:从插件自己的目录,@deepseek-ai/dsh-* 一个都解析不到 (8 个里只有 schemastery 能解析):

import FAIL @deepseek-ai/dsh-web              ERR_MODULE_NOT_FOUND
import FAIL @deepseek-ai/dsh-credentials      ERR_MODULE_NOT_FOUND
import FAIL @deepseek-ai/dsh-session          ERR_MODULE_NOT_FOUND
import OK   @deepseek-ai/schemastery

把同样的静态 import 写进一个 entry,dsh 日志会出现 failed to import / 1 entry did not activate。

那上游 fork 为什么能加载?因为 Node 从 ~/.dsh/profiles/web/node_modules/dsh-web-search-opencode-go/ 向上查找时会命中 ~/.dsh/profiles/node_modules/@deepseek-ai/ —— 那里有 242 个 symlink, 全部指向 dsh 0.1.5-rc.3 的旧安装,而不是当前运行的 0.1.7-rc.2。 它一直在用旧版本的 WebError / credentialRef 跑,是一次随时会坏的版本错配。

本插件的对策:依赖面压到最小,只 import schemastery。其余契约全部经 ctx 取 (ctx.get('credentials')、ctx.get('agents')),错误对象按 HarnessError 的 形状手工构造(class extends Error { readonly code },见 lib/errors.js), process.env 兜底 launch environment。

好处:插件可以放在任意目录,也不受 dsh 升级的版本错配影响。


卸载与回滚

临时回滚到官方链路(不卸载)

在「设置 → 插件」里把 web 这一行的 searchProvider 改回 deepseek-official, 重启即可。官方 provider 一直留在注册表里,就是为这个逃生舱准备的。

完全卸载

  1. 从 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 移除 dsh-web-search-ocgo
  2. 删除 cordis.patch.yml 里 web-search-ocgo 段,并把 web 的 searchProvider 改回 deepseek-official
  3. 删除 ~/.dsh/profiles/web/node_modules/dsh-web-search-ocgo 这个链接
  4. 重启 dsh web

开发

node scripts/check.mjs            # 清单自检(字段、exports、语法、依赖面)
node --test test/*.test.js        # 单元测试(40 项)
node bin/doctor.mjs               # 诊断:凭据 / 配置 / 依赖 / 语法 / 会话日志遗留污染
node bin/doctor.mjs --probe       # 额外真发一次搜索(会花钱)
node bench/dump-response.mjs "查询词"   # dump 原始响应结构(需要凭据)
node bench/maxuses-bench.mjs      # 复现 max_uses 曲线

目录

路径说明
index.jshost 半:Config schema + 装载自检 + 注册 provider
client.js浏览器半:设置页(手写 __ModuleLoader__ bundle,无构建步骤)
lib/provider.jsprovider 本体:请求、重试、回退、结果映射
lib/cache.js查询缓存(TTL + LRU)
lib/errors.js与 HarnessError 同形状的错误
lib/constants.js协议常量与默认值
cordis.patch.ymlbundle 补丁层:改钉 web.searchProvider、插入本 entry
probe/阶段 0 的一次性探针(可删)

许可

MIT。衍生关系与上游声明见 NOTICE。