用当前会话选中的模型联网搜索并返回来源
29.9/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/9/29 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add qujinting/dsh-web-search-clinepass该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:待复验
是什么
dsh 原生插件 · search
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 35 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 14 处,展示前 12 条)
- 发起网络请求src/gateway.js:176
response = await fetch(target.endpoint, { - 读取本地文件install.mjs:57
return JSON.parse(readFileSync(path, 'utf8').replace(/^ FEFF/u, '')); - 写入本地文件install.mjs:64
copyFileSync(path, backup); - 写入本地文件install.mjs:65
writeFileSync(path, JSON.stringify(value, undefined, 2) + '\n'); - 读取本地文件install.mjs:74
stat = lstatSync(target); - 删除本地文件install.mjs:83
if (!dryRun) unlinkSync(target); - 写入本地文件install.mjs:89
mkdirSync(dirname(target), { recursive: true }); - 读取本地文件install.mjs:102
if (existsSync(target)) { - 读取本地文件install.mjs:107
if (!existsSync(closure)) { - 读取本地文件install.mjs:129
if (!existsSync(clientPath)) throw new Error('the manifest declares dsh.client but ' + clientPath + ' is missing; the host would serve no browser half - 读取本地文件install.mjs:130
console.log(' browser half : ok (' + clientEntry + ', ' + readFileSync(clientPath, 'utf8').length + ' bytes)'); - 删除本地文件install.mjs:149
if (!options.dryRun) unlinkSync(linkPath);
扫描于 2026/11/2(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 用会话选中模型搜索
- 走网关原生搜索工具
- 返回搜索结果来源
DSH 插件(DeepSeek Harness):web_search 服务商,用会话选中的模型、走网关原生搜索工具来搜,替换内置的仅支持 DeepSeek 的服务商。
README
由 DeepSeek 最新模型翻译生成dsh-web-search-clinepass
DSH 插件(DSH plugin) | DeepSeek Harness ctx.web 搜索供应商,替换内置的 web-search-deepseek。
DSH plugin
license
node
一个 DSH(DeepSeek Harness)插件:用你当前选中的模型来联网搜索,替代内置的 web-search-deepseek。
内置搜索供应商(@deepseek-ai/dsh-web-search-deepseek)把模型写死成 deepseek-v4-flash,并且只认 DeepSeek 官方的 Anthropic 兼容端点(https://api.deepseek.com/anthropic/v1)——所以它只花 DeepSeek 官方的额度,也不跟随你在 UI 里选的模型。
展开完整文档(README 剩余 1 节)
dsh-web-search-clinepass
DSH 插件(DSH plugin) | DeepSeek Harness ctx.web 搜索供应商,替换内置的 web-search-deepseek。
DSH plugin
license
node
一个 DSH(DeepSeek Harness)插件:用你当前选中的模型来联网搜索,替代内置的 web-search-deepseek。
内置搜索供应商(@deepseek-ai/dsh-web-search-deepseek)把模型写死成 deepseek-v4-flash,并且只认 DeepSeek 官方的 Anthropic 兼容端点(https://api.deepseek.com/anthropic/v1)——所以它只花 DeepSeek 官方的额度,也不跟随你在 UI 里选的模型。
本插件注册一个 id 为 clinepass 的搜索供应商,它:
1. 从当前会话的请求头读出这一步实际使用的 provider / model;
2. 从 llm-pi-ai 设置里取出这条路由的 baseURL / apiKeyEnv(也就是 Web「模型」页面写入的那份配置);
3. 发一次 OpenAI 兼容的 POST {baseURL}/chat/completions,在 tools 里带上网关的供应商端执行搜索工具(vercel:perplexity_search 等);
4. 把模型回答末尾的 Sources: 段落解析成 ctx.web 需要的来源列表。
搜索跑在你的 ClinePass(或任何 OpenAI 兼容网关)额度上,不碰 DeepSeek 官方 key,也不额外消耗主对话的上下文。
为什么必须改配置而不只是装插件
ctx.web 的供应商选择是「显式 id 优先」:@deepseek-ai/dsh-base 把 web 那一行写死为 searchProvider: deepseek-official,所以仅仅注册一个新供应商是不会被选中的。
本插件打包成一个 bundle(dsh.bundle.patch),它的 cordis.patch.yml 同时做两件事:
- id: web
config:
searchProvider: clinepass
fetchProvider: http # patch 会整体替换 config,所以这行必须重述
- insert:
- id: web-search-clinepass
name: 'dsh-web-search-clinepass'
config: {}
内置的 web-search-deepseek 仍然挂着(不再是选中项),需要时可以随时切回去。
安装
按 dsh 版本选一条路。两条路装的是同一个 bundle,区别只在谁改 profile 清单,以及装完卡片出现在哪。
dsh 0.1.7 及以后(用内置插件管理器)
从 npm
dsh plugin --profile web add dsh-web-search-clinepass
或从本地 checkout
dsh plugin --profile web add file:D:/project/qujinting/dsh-plugin/web-search
- 插件管理器会跑 pnpm:把包装进 /profiles/web/node_modules,并把 dsh-web-search-clinepass 追加进该 profile 的 dsh.profile.bundles(它自己会先备份 package.json.bak-install-)。
- 重启 dsh 后生效(bundle 清单在启动时组装)。可用 dsh --profile web --dump-config 先看拼装结果,不会启动服务。
- 装完的配置页在 左侧「插件」→ 已安装 → dsh-web-search-clinepass → 组件行 web-search-clinepass 的「配置」。
0.1.7 把带配置的插件页搬到了插件管理器页;「设置 → 插件」现在只剩只读的插件列表。
- 注意:dsh plugin add file: 是把代码装进 pnpm store(硬链接),不是目录联接。改完本仓库代码要再跑一次 add 命令才生效(或者干脆用下面 0.1.5 那条 junction 路线做开发)。
- 卸载:dsh plugin --profile web remove dsh-web-search-clinepass。
dsh 0.1.5
1) 装进 web profile(在插件目录里执行)
node install.mjs --profile web
2) 看拼装结果(不会启动服务)
dsh --profile web --dump-config
3) 重启 web 服务后生效
dsh web
install.mjs 只做两件可逆的事:
- 在 /profiles/web/node_modules/dsh-web-search-clinepass 建一个目录联接(junction)指向本目录;
- 把 dsh-web-search-clinepass 追加到该 profile package.json 的 dsh.profile.bundles,并写一条 file: 依赖;改写前会自动备份 package.json.bak-install-。
它建的是目录联接,所以改本仓库代码后刷新页面(浏览器半边)或重启(host 半边)即可,不用重装。
配置页在 设置 → 插件 → 插件配置,设置写在 /settings.yaml 的 web-search-clinepass: 段。
手动安装(等价,供参考):
在 /profiles/web 下 New-Item -ItemType Junction -Path node_modules\dsh-web-search-clinepass -Target D:\project\qujinting\dsh-plugin\web-search 然后把 dsh-web-search-clinepass 加进 package.json 的 dsh.profile.bundles 末尾
卸载
0.1.7:dsh plugin --profile web remove dsh-web-search-clinepass 0.1.5:node install.mjs --profile web --uninstall
两者都会移除包与 bundles 条目(并备份 package.json)。重启后 web.searchProvider 回到 deepseek-official,内置行为完全恢复——插件不改动任何原生文件。
一个包,两套 settings API
dsh 0.1.7 重做了设置:插件的 Config 条目就是它的设置项,可编辑字段要标 .volatile(),写入落在 profile 的
cordis.patch.yml;0.1.5 则是 settings.installSection() 注册一个 plugin-owned section,落在 settings.yaml。
本插件用一个包同时支持两者:
| | dsh 0.1.5 | dsh 0.1.7+ |
| host 半边 | settings.installSection(ctx, 'web-search-clinepass', Config, …) | settings.configure({ auto: false }, ctx.fiber) + 条目自身 Config |
| 可编辑字段 | 整个 section 都进表单 | 只有标了 .volatile() 的字段(src/config.js 的 editable() 包装;老版 schemastery 没有这个方法就自动退化成普通字段) |
| 读值 | section 解析结果(普通值) | volatile 引用,.get() 取当前值;normalizeConfig() 在每个边界统一摊平 |
| 读别人的命名空间(llm-pi-ai / agent-default-model,用来解路由和兜底模型) | settings.get(ns) | settings.describe() 返回的 { ns, value } 投影(0.1.7 的服务没有 get);见「0.3.1 修的那个坑」 |
| 卡片槽位 | settings.plugin.item + ctx.settingsScope | plugins.row.config(key = #)+ 页面传入的 form(state + mutate) |
| 卡片位置 | 设置 → 插件 → 插件配置 | 左侧「插件」→ 已安装 → 该包 → 组件行「配置」 |
| 设置落在 | /settings.yaml | /profiles//cordis.patch.yml |
分流全靠服务探测,没有版本号判断:host 半边看 settings 服务上有没有 configure(有就新、没有就旧),
readSettings() 同样先问 get、没有再问 describe(src/route.js);
浏览器半边分别 ctx.inject(['settingsScope'], …) 与 ctx.inject(['configForms'], …),两边的 inject 只会有一边被满足,所以只会注册一张卡片。
0.3.1 修的那个坑:0.1.7 上 web_search 报 "registered but unavailable"
0.3.0(也就是「同时支持两套 settings」那版)只把自己这一条的 Config 迁到了 0.1.7 的模型,
读别人的命名空间那条路忘了迁:readSettings() 仍然只调 settings.get(ns),而 0.1.7 的
@deepseek-ai/dsh-settings 里根本没有 get(installSection 也没了),异常被 try/catch 吞掉后一律返回 undefined。后果是 llm-pi-ai 永远读不到,resolveTarget() 拿不到 baseURL / apiKeyEnv,
available() 恒为 false,于是每次搜索都变成:
Error: configured web provider "clinepass" is registered but unavailable
(WEB_PROVIDER_CONFIGURED_UNAVAILABLE)
只发生在 dsh 0.1.7+ 上;0.1.5 因为 get 还在,行为不变。修法是给 readSettings() 加上 0.1.7 的读法
(settings.describe() 的 { ns, value } 投影,内部已经把 volatile 引用摊平):
js
if (typeof settings.get === 'function') { /* 0.1.5 / }
return describedSettings(settings, ns); // 0.1.7: describe() 里按 ns 找 value
升级后仍然报这个错、又不能马上重装插件时,可以先在 profile 的 cordis.patch.yml 里把路由钉死(provider / model 是 volatile 字段,写在 patch 里同样生效;baseURL / apiKeyEnv 是普通字段,所以它们本来就只能从 patch 配,卡片里没有这两栏): yaml
- id: web-search-clinepass
config:
provider: clinepass
model: cline-pass/deepseek-v4.1-flash
baseURL: https://api.cline.bot/api/v1
api: openai-completions
apiKeyEnv: CLINEPASS_API_KEY
代价是这条搜索不再跟随 UI 里选的模型;把插件升到 0.3.1 后删掉这段即可恢复跟随。
配置
设置命名空间:web-search-clinepass(会出现在 Web 的「设置 → 插件配置」里,可热改)。
| 字段 | 默认 | 说明 |
| enabled | true | 关掉后本供应商报告为不可用。 |
| tool | vercel:perplexity_search | 发给网关的供应商端搜索工具 id(4 选 1)。按每次检索计费;提示词里的搜索次数上限只是建议,本格式没有硬上限,模型可能检索更多次。 |
| provider / model | 空(跟随会话) | 钉死路由与模型,不再跟随 UI 选择。 |
| baseURL / api / apiKeyEnv / apiKey | 空 | 钉死端点与凭据。 |
| headers | 空 | 额外请求头,合并优先级:llm-pi-ai 这里没有「把搜索记录写进会话日志」的开关:DSH 会因此拒绝加载整份会话,原因见《为什么不再写会话日志事件》。
绝大多数情况什么都不用配:会话模型是 clinepass 这类 OpenAI 兼容网关时,路由信息全部来自 llm-pi-ai.providers.(api / baseURL / apiKeyEnv)——0.1.5 通过 settings.get('llm-pi-ai') 读, 0.1.7 通过 settings.describe() 读(见《0.3.1 修的那个坑》)。
工具选择与花费
AI Gateway 的 Chat Completions API(也就是本插件走的这条)文档里只列了 4 个服务端搜索工具,本插件也只提供这 4 个;改 tool 即可切换:
| 工具 | 搜索后端 | 必填 config | 单价(网关侧) | 适合 |
| vercel:perplexity_search | Perplexity Search API | query | $5 / 1000 次 | 通用首选:自带引用、支持时效/地区/语言/域名过滤、maxResults 1–20 |
| vercel:parallel_search | Parallel AI Search | objective | $5 / 1000 次(含 10 条,超出 $1/1000) | 研究型问题:LLM 优化的摘录,mode 可选 one-shot / agentic |
| vercel:exa_search | Exa | query | $7 / 1000 次(≤10 条) | 要按域名 / 日期 / 类型(news、research paper…)筛选,或要更省 token 的摘录 |
| vercel:tako_search | Tako | query | $7 / 1000 次(instant/fast)、$12 / 1000 次(deep) | 只有需要它的实时知识图谱(金融 / 体育 / 天气 / 宏观 / 政治)时才值 |
为什么不提供 browserbase_search / browserbase_fetch:Vercel 只为 AI SDK 表面提供
gateway.tools.browserbaseSearch(),Chat Completions 的 server-tool 表里没有这两个 id。实测 ClinePass 端点接受
vercel:browserbase_search(HTTP 200),但一次请求里模型自己搜了 34 次、烧掉 17.6 万 prompt token、计费 $0.2645,最后只给出 1314 字符、0 条引用的回答(内容基本是"我再搜一下")。因此从选项里去掉。
计费是按「调用次数」,不是按请求
一次 web_search 里模型可以自己决定搜几次,网关在 message.provider_metadata.gateway.gatewayToolCalls 里报告次数。同一类问题实测(gatewayCost,每次 web_search):
| 工具 | 搜索次数 | 来源 | gatewayCost |
| vercel:perplexity_search | 6 → 4 | 17 → 10 | $0.0430 → $0.0298 |
| vercel:parallel_search | 4 → 2 | 17 → 10 | $0.0361 → $0.0202 |
| vercel:browserbase_search(已移除) | 34 | 0 | $0.2645 |
The right column shows measured values after adding the "maximum 3 searches" from the system prompt; $5/1000 is charged per retrieval,
so 6 retrievals cost $0.03 in tool fees alone—the number of searches is the main cost driver, far more important than output length. The instruction is advisory: in practice there are still cases with 4 searches (it is not a hard cap; the Chat Completions format has no "maximum number of uses" field).
Also: dsh-tool-web runs each query in the queries array concurrently as a separate search,
so one web_search({queries:[q1,q2,q3,q4]}) is 4 model turns. To save money, use a single query.
Each search passes the query to the gateway as the default value
The tools[] entry is not a bare id, but carries the input for this search (the toolEntry() in gateway.js):
json
{ "type": "vercel:perplexity_search", "config": { "query": "…", "max_results": 10 } }
perplexity / exa / tako use query, parallel uses objective (the required field in each schema),
and the number of results uses max_results (for exa it is num_results). The docs say config is a "developer default that overrides the value generated by the model",
so retrieval is anchored to the query the harness actually received, rather than letting the model rewrite it itself. Unknown tool ids still send only the bare id— inventing config for a schema you do not recognize will only make the request start erroring.
How it works (details)
agent calls web_search(queries)
│ dsh-tool-web calls ctx.web.search() concurrently for each query
▼
ctx.web (searchProvider = clinepass)
▼
CurrentModelSearchProvider.search()
├─ select model agent.session.requestHeader().config → agent.options → settings['agent-default-model']
├─ resolve route settings['llm-pi-ai'].providers[provider] + this plugin's routes/explicit pinning
├─ get credentials ctx.credentials.resolve(apiKeyEnv) → launchEnvironment
├─ send request POST {baseURL}/chat/completions
│ tools:[{type:'vercel:perplexity_search', config:{query, max_results}}]
│ (executed on the provider side: the gateway runs retrieval itself, and the model side still has only one assistant output turn)
└─ parse the Sources: section at the end of message.content → markdown links + bare URLs → deduplicate, extract titles, truncate
How sources are obtained
The gateway only reports the number of retrievals in usage.gateway_cost / message.provider_metadata.gateway.gatewayToolCalls,
and does not return structured citation blocks—the real URLs are in the answer body itself. So the search instruction requires the model to end in a fixed shape:
Sources:
- page title
The parser prefers to take links from this section (using the markdown label as the title), and only falls back to scanning the full text if it cannot;
it also strips this section from the body to avoid the same batch of URLs appearing twice in the tool result.
Honesty markers
- gatewayToolCalls exists and is 0: append "the gateway did not actually perform a search; treat this as unverified" to the body;
- finish_reason === 'length': append "the output limit has been reached; the answer and source list may be incomplete" to the body.
How far it has been verified
- Unit tests npm test: 59 items. Parsing and route resolution: 22 items (Chinese punctuation, markdown links, deduplication, truncation, session following, model switching, protocol incompatibility, explicit pinning, fallback,
and the 0.1.7 reading: when there is only describe() and no get(), routes and endpoints can still be resolved, agent-default-model is likewise readable, and when the namespace is absent from the projection it is still judged unavailable),
search path and repair tools: 15 items (search does not write session events, unavailable routes do not touch the session, the schema no longer exposes write switches, the request body carries tools[].config,
the config field mapping for each of the four tools, unknown tool ids send only the bare id, the search count cap in the system prompt, recognition of unsupported events,
envelope markers, frame structure preservation, dry-run, active sessions and session.lock protection, scanning only the current generation),
host half: 5 items (0.1.7 uses configure({auto:false}) and reads live references, 0.1.5 uses installSection and prefers the section,
when neither exists it falls back to the combined entry, normalizeConfig flattens references/passes through ordinary values, editable fields are marked volatile),
browser half: 17 items (one item for each of the two registration paths: 0.1.7's plugins.row.config key #, 0.1.5's settings.plugin.item and waiting for the ledger;
0.1.7 page view renders fields, the summary view does not need a form, unserved entries render empty, read-only entries disable all buttons; the write planner, defaults mean clearing overrides,
default collapse renders only the header, degraded rendering when the namespace is missing, stylesheets read only theme tokens and are all namespaced, Tag/Switch/chevron use the base module,
boolean fields are a toggle row with "label on the left + switch on the right", search tools are a single-select radio list and there is no select on the page, only the four tools from the docs are provided,
and the search tools column explicitly says the count cap is only a suggestion).
10 of these rendering tests require react-dom and are skipped when it is missing (on this machine npm test reports 49 passed + 10 skipped;
when there were 56 items previously, all 56 were run with real React 18.3.1, 0 skipped).
- Real 0.1.7 settings implementation (regression verification for 0.3.1): instead of starting the whole harness, directly use the real
SettingsForms.prototype.describe(未打桩,只喂给它一个 configEditor.configuration() 的替身)跑在真实的 @deepseek-ai/dsh-llm-pi-ai 的 Config schema 与本机 profile 的真实路由值上:describe() 输出
[{ ns: 'llm-pi-ai', value: { providers: { clinepass: { baseURL: 'https://api.cline.bot/api/v1' } } } }]。
再把这同一个 ctx 分别喂给 HEAD(0.3.0)与修好后的 resolveTarget(): 0.3.0 在 0.1.7 服务下 available() === false(就是那条 registered but unavailable),修好后同一个 ctx
available() === true、端点 https://api.cline.bot/api/v1/chat/completions、来源 session request header;
0.1.5 的 get(ns) ctx 在修改前后都照常解出。未做的验证:真实 GUI 里重启 harness 后再跑一次 web_search (插件是 file: 装进 profile 的硬链接副本,改仓库代码要重新 dsh plugin add + 重启才生效)。
- 真实 GUI 验收(与内置卡片逐项对齐):用本机 Chrome 走 CDP 直连正在运行的 dsh web(临时 profile + 用本机 client-connection/browser-session 签名密钥铸的会话 cookie,密钥不出本机),把这张卡和内置「网页搜索」卡放在同一页逐项量:折叠态两张卡都是 564×75(header padding 14/16、gap 12、标题 15px/600、摘要 13px、chevron 14×14 且 viewBox="0 0 14 14");展开态 body 的 border-top / margin 16 / padding-bottom 8、字段 padding 12、label 13px/500、hint 12px、控件 530×36(radius 8 / border 1px / padding 12 / font 13)、footer 与保存按钮(font 13、padding 5/14、
bg = label-primary、文字 = bg-layer-3)逐项相同;「未保存」Tag 就是同一个基座组件,实测 49×19 / 11px / radius 999px 与内置一致;
切到 body[data-ds-dark-theme] 后两张卡的 token 一起变(卡片 44,44,46 / 边框 67,69,74 / 控件 53,54,56 / 文字 249,250,251),控制台 0 error、0 warning;位置始终在内置四张卡片(终端 / Agent 循环 / Subagent / 网页搜索)之后。
两个控件的形态也在同一页对着内置 Subagent 卡量过:开关行 justify-content:space-between / align-items:flex-start / gap:16px、开关外框 36×20 且贴右(右间距 0px),与内置的 toggle row 完全一致;搜索工具的单选组框(border 1px rgba(0,0,0,.16)、radius 8、padding 10、gap 6、max-height 280、overflow auto)与内置 Subagent 卡的选择列表 fieldset 逐项相同,行内 padding 6 / radius 6 / gap 8 / 原生 radio 13×13 也一致; 4 个选项同一个 name、只有一个是 checked,页面里 数量为 0。
- 真实 GUI 验收(dsh 0.1.7,隔离实例):另起一个 DSH_HOME(临时目录、临时 profile、3091 端口)跑真实的
dsh 0.1.7-rc.2 web,装上本插件后用 CDP 走完真实路径:左侧「插件」列出 dsh-web-search-clinepass v0.3.0(已安装 1 个)→
组件行 web-search-clinepass 的「配置」→ 页面渲染出本卡的 8 个字段 / 4 个单选(默认项选中)/ 1 个开关行 / 6 个文本框 / 三颗底部按钮,样式表已注入 style[data-plugin-css];改为 vercel:parallel_search 点保存后显示「已保存(1 项)。」,且 profile 的 cordis.patch.yml 里确实出现
- id: web-search-clinepass\n config:\n tool: vercel:parallel_search;全过程 0 error、0 warning。(验证用的临时实例与临时 home 已删除,没有碰你正在跑的那个 dsh。)
- 真实 seam 集成 node test/live-gateway.mjs:用真实的 WebRuntime(@deepseek-ai/dsh-web)注册本供应商,按 searchProvider: clinepass 选中,向 api.cline.bot 实发一次搜索,断言来源非空且 maxResults 生效。
- 真实网关实测(搜索次数与 tools[].config):走插件自己的 searchWithGateway 打真实网关, perplexity 与 parallel 各自 200 / finish_reason: stop、来源各 10 条、没有截断提示; gatewayToolCalls 分别为 4 与 2,gatewayCost 分别为 $0.0298 / $0.0202 (加「最多 3 次搜索」之前的同一类问题是 6 / 4 次、$0.0430 / $0.0361)。
- 真实 DSH 端到端:临时 profile(@deepseek-ai/dsh-base + @deepseek-ai/dsh-headless + 本插件)跑一次真实任务,带回真实来源链接。
端到端跑通后不再产生 web/clinepass-search-request 事件;早期版本写进会话日志的那些事件已用 tools/repair-session-events.mjs 补上 ignorable: true(见《为什么不再写会话日志事件》),会话可以正常重新加载。
限制与注意事项
- 不写任何会话日志事件。 早期版本每次搜索都会追加一条 web/clinepass-search-request(v0.1.0 里叫 web/current-model-search-request),这会让会话在下次 resume 时被整份拒绝加载;该行为已删除,也没有重新打开的开关。
已被写坏的会话用下一节的工具修复。
- 只对 OpenAI 兼容且支持供应商端工具的路由生效。 路由声明了非 chat-completions 协议(例如 anthropic-messages)时,本供应商直接报告不可用——这是有意的:让网关工具 id 发给不懂它的端点只会得到难懂的错误。
- 切到非网关模型时 web_search 会报 WEB_PROVIDER_CONFIGURED_UNAVAILABLE。
两种处理:在设置里配 fallback.provider / fallback.model 钉一个网关路由兜底;或把 profile 的 cordis.patch.yml 里 web.searchProvider 改回 deepseek-official。
- 凭据只从 ctx.credentials 与启动环境读。 密钥不会进日志、不会进会话记录(记录里只有路由、耗时、费用、检索次数)。
- web_fetch 不受影响,仍然走内置的 http 供应商。
- 供应商端工具是一次完整的模型回合:延迟实测 8–15 秒,费用按上面那张表计。
- 插件不修改任何原生包文件,卸载后原样恢复。
为什么不再写会话日志事件
早期版本每次搜索都会往会话日志追加一条 web/clinepass-search-request。这个行为已经删除,因为它会让会话彻底打不开:
- DSH 的会话读取是 fail-closed 的:session-persistence 的 validateStoredEvents() 遇到 KNOWN_SESSION_EVENT_TYPES 之外、信封上没有 ignorable: true 的事件时,会拒绝解释整份日志,报 SessionFormatUnsupportedError(「unknown to this harness and not marked ignorable; refusing to interpret the log」);
- 白名单由 harness 仓库自己生成,外部插件声明的事件按构造就不在其中;
- 而 Session.append() 的第三个参数只接受 surface 元数据(surfaceOp / sourceEventSeqs),插件没有任何途径给自定义事件写上 ignorable: true——写的时候一声不响,下次 resume 才会发现会话已经打不开。
所以只要「会话必须能恢复」,插件唯一安全的选择就是不写自定义事件:本插件现在只读 seam,不落任何持久数据。
诊断信息(路由、耗时、gatewayCost)不再进会话日志;需要长期账单统计请在外层采集(例如网关后台)。
修复已经被写坏的会话
仓库带一个修复工具 tools/repair-session-events.mjs:它只处理当前代际的 session.v3.jsonl.zstd,找出白名单之外且没有 ignorable: true 的事件,原样保留帧结构与其它每一行,只给这些信封补上 ignorable: true (这正是「纯信息性记录」应有的标记);写盘前先备份、写盘后再解码自检,自检失败自动用备份回滚。
powershell
npm run repair-sessions # 只扫描:列出会被拒绝加载的会话
npm run repair-sessions:apply # 修复(不带 --apply 时是 dry-run,不写盘)
node tools/repair-session-events.mjs repair --session 80bad975-cea1-48af-b509-f2849e8841b0 --apply
- 默认扫描 $DSH_HOME(或 ~/.dsh)下的全部会话;--home 换根目录,--session 只处理一个会话。
- 工具不依赖开发用的那份 node_modules junction:它按 $DSH_HOME/profiles//node_modules 和 DSH 自身的安装目录去找 @deepseek-ai/dsh-session(白名单必须来自真正读日志的那个 harness),所以别人 clone 下来就能直接跑。
- 最近 5 分钟内被写过的会话、或目录里存在 session.lock 的会话会被跳过(可能还开着):先把 harness 停掉再跑;确认无风险时用 --force 覆盖这两道保护。
- 备份写在原文件旁边:session.v3.jsonl.zstd.bak-(harness 不会读取这个文件名,可随时删除)。
- 历史代际(session.jsonl.zstd、session.v2.jsonl.zstd 等)不会被扫描——它们由 harness 自己的迁移链处理。
卡片(浏览器半边)
这个插件是双面的,而且两套设置模型各有一张卡:
- dsh 0.1.7:lib/client.js 往 plugins.row.config 注册,key 是 dsh-web-search-clinepass#web-search-clinepass。
页面(插件管理器)自己画标题、图标、面包屑,并把该条目的 form(state + mutate)当 prop 传进来;列表里还会调用 summary 视图要一句摘要,所以这张卡在行未展开时返回一句话。
- dsh 0.1.5:往 settings.plugin.item 注册,key 是 web-search-clinepass,自己绑 ctx.settingsScope;卡片是 + 带 aria-expanded 的 header 按钮(标题 / 摘要 / 未保存标记 / chevron),默认折叠,保存成功后自动收起,失败则保持展开。
0.1.5 那张卡的排序规则:设置页按 ledger 顺序渲染,而本插件的 apply 比设置插件自己注册卡片更早,所以「立刻注册」会把这张卡顶到所有内置卡片之前。实现改为等 ledger 里已经有卡片再入列 ——于是它排在本机加载时已有的那些配置之后,而不是钉死在最后(之后再注册的卡片依然排在它后面)。
0.1.7 不涉及这个问题:位置由插件页面自己决定。
卡片可改的字段:enabled、tool(文档里的 4 个网关搜索工具)、maxSources、timeoutMs、maxTokens、 provider / model(钉死路由,可选)、instructions。改完点保存;字段恢复成默认值时写的是 unset (清除覆盖、重新继承),每个被覆盖的字段旁边有单独的「恢复默认」。
控件形态跟内置卡片对齐:布尔字段是左标签 + 右开关的一行(内置 Subagent 卡 toggle row 的形态,开关贴最右); tool 是单选列表而不是 —— 全部选项一次看得见,分组框的边框 / 圆角 / padding / 行距照内置 Subagent 卡的选择列表来(原生 radio,不做自定义绘制)。
浏览器半边是手写的 lib/client.js,按所有插件 bundle 的加载格式(window.__ModuleLoader__.load({ id, factory }))写死,所以本仓库没有构建步骤、也不依赖 npm 上的任何运行时包;它用基座模块表里的 react 与 @deepseek-ai/dsh-client-ui-primitives,跨插件协作一律走 cordis 服务或槽位(ctx.slots、0.1.5 的 ctx.settingsScope、0.1.7 的 ctx.configForms 探测)。
改了浏览器半边的内容只要刷新页面(必要时 Ctrl+Shift+R)就生效:bundle 由 Host 每次从磁盘读,实测改完在已运行的 dsh web 里直接看到新样式与新文案。只有增删 bundle 本身(dsh.profile.bundles / profile package.json 的组合变化)才需要重启。
卡片的外观来自宿主,不是自己画的
DSH 里没有「声明式配置卡」这种接口。slot 契约写得很直白:a card draws its own internals; the tab only decides which namespaces to dispatch and stacks what comes back;一个 Host 已服务、但没有卡片认领的 namespace 什么都不渲染(tab-store 的原话:A served namespace no card claims renders nothing)。
所以卡片必须由插件自己贡献,这里没有可选项。
内置卡片共用的那层 chrome(PluginCard + card-form 字段套件 + 对应的 CSS module)在 @deepseek-ai/dsh-client-ui-settings-plugins 内部,而那个包的客户端 bundle 只导出 apply 与 inject (lib/client.js 末尾就是 exports.apply / exports.inject 两行),仓库外的包 import 不到它。
真正共享的是 shell 自己建立的基座模块表 PLATFORM_MODULES:
react、react/jsx-runtime、react-dom、react-dom/client、@deepseek-ai/cordis、 @deepseek-ai/dsh-client-store、@deepseek-ai/dsh-client-ui-slots、 @deepseek-ai/dsh-client-ui-primitives、@deepseek-ai/dsh-client-ui-dockkit
于是这张卡的做法是:
1. require('@deepseek-ai/dsh-client-ui-primitives') —— 拿到的是内置卡片用的同一个实例,用它渲染「未保存」Tag、布尔字段的 Switch、header 的 IconChevronDownOutline14;
2. 其余 chrome 全部用宿主的主题 token(--dsw-alias-)写,规则与内置卡片一条对一条(数值取自带内的 PluginCard.module.css / fields.module.css,不是目测),并以 style[data-plugin-css] 注入、域名限定在 .dswwsc-*。
好处是换主题、切深浅色时卡片跟着变:颜色全是 token,样式表里没有任何十六进制或 rgba 字面量(单元测试对这条做断言)。
明确没有采用的两条路:直接 import 那个内部包(导不出来),以及借用它已注入的哈希类名(.YyYd_a_card 之类)——后者在任何一次宿主重构后都会静默失效。
开发 powershell 本仓库是纯 ESM JS(无构建步骤)。测试需要 DSH 的模块闭包可解析: New-Item -ItemType Junction -Path node_modules -Target $env:USERPROFILE\.dsh\profiles\node_modules
npm test # 单元测试(不花钱)
node test/live-gateway.mjs # 真实网关 + 真实 seam(会花一次搜索的钱)
卡片的两条静态渲染测试需要 react-dom(DSH 的模块闭包里没有它),否则会优雅跳过。
想跑全 36 项就临时装一份并指过去(不写进本仓库): powershell
npm install --prefix "$env:TEMP\dsh-card-verify" react@18.3.1 react-dom@18.3.1
$env:DSH_CARD_TEST_MODULES="$env:TEMP\dsh-card-verify"
npm test # 36 项,0 skipped
| 文件 | 作用 |
| src/index.js | 插件入口:name / inject / apply / 设置命名空间注册。 |
| src/config.js | schemastery 配置 schema 与常量。 |
| src/route.js | 选模型 + 解端点 + 取凭据(无网络调用)。 |
| src/gateway.js | 那一次 HTTP 请求、错误分类、回答与来源的组装。 |
| src/parse.js | Sources: 段落切分、markdown/裸 URL 提取、去重与标题。 |
| src/prompt.js | 搜索指令(固定收尾形状是解析契约的一部分)。 |
| src/provider.js | WebSearchProvider 实现:available() / search()(只读 seam,不写会话日志)。 |
| cordis.patch.yml | bundle 层:改 web.searchProvider + 插入插件行。 |
| lib/client.js | 浏览器半边:手写的加载器 bundle,注册设置卡片(settings.plugin.item)。 |
| tools/repair-session-events.mjs | 扫描/修复被自定义会话事件写坏的会话日志(补 ignorable: true,带备份与自检)。 |
| install.mjs | 装/卸到某个 profile。 |
标注为 DSH 插件
这个仓库用三层标记说明它是 DSH(DeepSeek Harness)的插件项目,前三层是机器可读的:
| 层 | 位置 | 值 |
| 清单角色 | package.json → dsh | { "bundle": { "patch": "./cordis.patch.yml" } } —— profile 启动器据此把这行当作 bundle 层加载。 |
| 浏览器半边 | package.json → dsh.client | { "platform": "web" } + 导出 ./client —— client-modules 据此把它当浏览器插件挂进 boot graph,设置页的卡片就来自这里。 |
| 包身份 | package.json → name / version / author / license | 具名且带版本,才会出现在 DSH 的插件清单(dsh_plugin_packages 请求字段)里。 |
| 检索关键词 | package.json → keywords | dsh-plugin、dsh、deepseek-harness、web-search、search-provider、clinepass、vercel-ai-gateway、openai-compatible。 |
| 仓库主题 | GitHub repo topics | dsh-plugin、deepseek-harness、dsh、web-search、clinepass、vercel-ai-gateway、openai-compatible。 |
以上四项本仓库都已应用(topics 与 description 已通过 GitHub API 写入)。
需要在新 fork / 新仓库上重做时,GitHub 的 topic 与 description 需要仓库权限,gh 未安装时用 API 设置: powershell $token = "" $repo = "qujinting/dsh-web-search-clinepass"
topics
curl.exe -X PUT -H "Authorization: Bearer $token" -H "Accept: application/vnd.github+json"
"https://api.github.com/repos/$repo/topics"
-d '{"names":["dsh-plugin","deepseek-harness","dsh","web-search","clinepass","vercel-ai-gateway","openai-compatible"]}'
description
curl.exe -X PATCH -H "Authorization: Bearer $token" -H "Accept: application/vnd.github+json"
"https://api.github.com/repos/$repo"
-d '{"description":"DSH plugin: web_search provider that searches with the session\u0027s selected model via gateway-native search tools (vercel:perplexity_search), replacing the built-in DeepSeek-only provider."}'
许可
MIT,见 LICENSE。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
qujinting/dsh-web-search-clinepass
用当前会话选中的模型联网搜索并返回来源
相关插件 · 搜索 / 抓取
LLM 知识平台Tencent/WeKnora
概览 ·快速开始 ·新特性 ·功能 ·客户端 ·文档 ·开发
★ 36,161已汉化DSH
72.1
智能体记忆系统MemTensor/MemOS
MemOS 2.0 Stardust星尘
★ 12,195已汉化DSH
71.2
跨代理记忆库zilliztech/memsearch
跨平台语义记忆,专为 AI 编程智能体打造。
★ 2,799已汉化DSH
70.9
技能语料库EverMind-AI/SkillCorpus
SkillCorpus 能给你什么
★ 694已汉化DSH
69.7
代码图谱记忆syncable-dev/memtrace-public
你的智能体值得拥有结构化记忆。
★ 509已汉化MCP
67.9
AnySearch 搜索插件anysearch-team/anysearch-dsh
AnySearch 网页搜索服务商,为 DeepSeek Harness(DSH)提供高级搜索工具
★ 457已汉化DSH
66.6