dsh-keyless-search
免 API key 的多引擎搜索 provider,为 DeepSeek Harness(dsh)的 ctx.web 搜索接缝提供后端。
装上它之后,内置的 web_search 工具照旧可用,但底层不再是"一次搜索 = 一整个模型轮次"的官方服务端搜索,而是由本插件自己发起检索。
特性
- 不需要任何 API key —— 不依赖
DEEPSEEK_API_KEY,也不消耗模型轮次 - 真实浏览器 Google —— 用本机 Chrome 渲染
google.com,绕开纯 HTTP 抓取必然撞上的enablejsJS 墙 - 三引擎可降级 ——
google-browser/bing-global/bing并发合并,代理不可用时自动收敛为直连 - 代理自动探测 —— 不填
proxyUrl也会自动扫本地常见端口,换机器零配置 - 语言感知 —— 中文查询自动跳过国际版 Bing,避免无关英文结果污染
- 多主题查询自动拆分 —— 一条查询塞多个主题(如"内存、8TB 硬盘、OCuLink 卡的 2026 年现状")时,自动按分隔符拆成最多 8 个子查询、各主题独立检索后合并去重,避免搜索引擎只匹配主体词而丢掉其余主题
- 浏览器实例自动回收 —— 空闲(默认 5 分钟)后自动关闭自己拉起的那个 Chrome,不碰你自己的浏览器
- 零 host 依赖 —— 只用
node:内置模块,不从@deepseek-ai/*import 任何东西 - 不拖垮 dsh ——
puppeteer-core缺失或 Chrome 找不到时只降级单个引擎,绝不影响 dsh 启动
目录
为什么需要它
dsh 内置的搜索 provider(dsh-web-search-deepseek)依赖 DeepSeek 的服务端 web_search 工具:
- 需要
DEEPSEEK_API_KEY - 每次搜索会消耗一个完整的 Messages 模型轮次(延迟 + 生成 token 都要付)
本插件改为自己发请求,因此不需要任何 API key、不消耗模型轮次。
三个引擎
| 引擎 id | 通道 | 结果特征 | 实测 |
|---|---|---|---|
google-browser |
真实 Chrome 渲染 google.com,经代理 |
真实 URL + 带日期的摘要,Google 独有排序(reddit / x.com / huggingface 等) | ✅ 可用 |
bing-global |
HTTP 经代理访问 www.bing.com |
国际版英文结果(此时不会跳转 CN 版) | ✅ 可用 |
bing |
HTTP 直连 cn.bing.com |
中文结果,无代理时的兜底 | ✅ 可用 |
默认引擎链:[google-browser, bing-global, bing]。
Google 为什么要用浏览器
直接 HTTP 抓 Google 是行不通的:无论用 gbv=1、udm=14、现代 UA 还是换代理出口 IP,返回的都是 httpservice/retry/enablejs 的 JS 墙,0 条结果(四种参数组合均实测)。
真实浏览器会执行 JS,于是能拿到结果页。而 Google 的结果链接是加密的 /goto?url=CAES...,插件用 CDP 的 redirectChain 在不加载目标页的前提下解出真实地址(实测 8/8 成功、438ms),并配合请求拦截丢弃无关流量。
语言感知
国际版 Bing 对中文查询的索引质量差——实测搜"上海天气"会混入秘鲁股票,搜"arcprize"会混入 Seattle 时间站。
因此插件在查询包含中日韩字符时自动跳过 bing-global,只保留 google-browser + bing(Google 对中文的相关性排序很好,bing 直连做中文兜底)。显式指定 engine / engines 时不干预。
安装
# 方式一:从 tarball 安装(推荐,依赖会一并装上)
npm pack # 生成 dsh-keyless-search-<version>.tgz
dsh plugin --profile <profile> add ./dsh-keyless-search-1.0.0.tgz
# 方式二:从本地目录安装(开发用,见下方注意事项)
dsh plugin --profile <profile> add <本包绝对路径>
# 方式三:从 git 安装
dsh plugin --profile <profile> add github:Wandering233/dsh-keyless-search
<profile> 换成你实际使用的 profile 名(如 web、desktop)。
安装后重启 dsh 生效。本包声明了 dsh.bundle.patch,dsh plugin add 会自动把它加入 profile 的 bundle 列表并应用 patch,无需手工改 cordis.patch.yml。
两种安装方式的差异(实测)
| tarball 安装 | 本地目录安装 | |
|---|---|---|
| pnpm 行为 | 解包到 store,并安装其 dependencies(实测 25 个包) | 仅建 symlink(link: 语义),不安装 dependencies |
import('puppeteer-core') |
✅ 能解析(依赖就在旁边) | ❌ 解析失败 —— 因为 ESM 基于真实路径,symlink 指向的是源目录 |
| 因此本地目录安装时 | — | 插件改为扫描 $DSH_HOME/profiles/*/node_modules 找 puppeteer-core |
也就是说:本地目录安装时,请确保 profile 里已存在 puppeteer-core:
dsh plugin --profile <profile> add puppeteer-core
若两处都找不到,google-browser 引擎会自动降级(其余引擎照常工作),并在错误里提示修复命令,不会影响 dsh 启动。
运行要求
- Node ≥ 22
- Chrome / Chromium / Edge:自动探测(跨平台,见下节);其他位置用
chromePath指定 puppeteer-core:见上表;本地目录安装时建议显式装上- HTTP 代理:仅
google-browser引擎需要(能访问google.com即可)
本插件刻意不声明任何
@deepseek-ai/*的 peerDependencies。它不从 host 包 import 任何东西(只用node:内置模块),因此不会出现"profile 的 node_modules 解析不到 asar 内依赖"而导致整个 profile 启动失败的问题。
跨平台
插件只用 node: 内置模块,Windows / macOS / Linux 均可运行。浏览器探测顺序:
chromePath显式指定- 常见安装路径 —— 覆盖 Arch/Debian/Fedora/Snap 等布局(
/usr/bin/chromium、/usr/bin/google-chrome-stable、/usr/lib64/chromium-browser/chromium-browser、/snap/bin/chromium…)、macOS 的.app路径、Windows 的 Program Files - puppeteer 自带缓存 ——
~/.cache/puppeteer/chrome/<version>/…(macOS 为~/Library/Caches/…),适合没装系统浏览器的 Linux - 扫
PATH——google-chrome/chromium/chromium-browser/brave-browser等,各平台文件名差异已覆盖
PATH 分隔符(: 与 ;)差异已处理;路径转 URL 用 pathToFileURL,含空格与非 ASCII 的路径也安全。
Linux 注意事项
--no-sandbox与--disable-dev-shm-usage已默认启用(root / 容器环境通常必需)- 需要 Chromium 运行库:Arch 直接装
chromium包即可;Debian 系常见缺libnss3 libatk-bridge2.0-0 libgbm1 libasound2 - 没装浏览器也不会崩:
google-browser引擎失败后自动降级到bing,搜索照常可用 - 代理端口按 Linux 习惯也覆盖了(
1080/7890/7897)—— v2rayN 在 Windows 常用10808,Clash 常用7890,proxyUrl留空时会自动探测
配置
写入 profile 的 cordis.patch.yml 中本插件行的 config:
- insert:
- id: web-search-local
name: 'dsh-keyless-search'
config:
engines: [google-browser, bing-global, bing]
proxyUrl: '' # '' = 自动探测;'off' = 强制直连;或 'http://host:port'
maxSources: 16
browserTimeoutMs: 30000
searchTimeoutMs: 15000
chromePath: '' # 留空自动探测
puppeteerPath: '' # 留空走包名解析,失败再兜底已知路径
searxngBaseUrl: '' # 可选,见下
| 字段 | 默认 | 说明 |
|---|---|---|
engines |
[google-browser, bing-global, bing] |
引擎链;其他可选:duckduckgo、searxng |
proxyUrl |
'' |
空 = 依次探测 10808 / 10809 / 7890 / 7897 / 1080 / 8888 / 8118 这些本地端口,命中即用 |
maxSources |
16 |
单次搜索最多返回的来源数 |
searchTimeoutMs |
15000 |
HTTP 引擎超时 |
browserTimeoutMs |
30000 |
浏览器引擎超时(含冷启动) |
browserIdleTimeoutMs |
300000 |
浏览器实例空闲多久后自动回收(毫秒);0 表示禁用、实例常驻 |
chromePath |
自动 | Chrome/Edge 可执行文件 |
puppeteerPath |
自动 | 显式指定 puppeteer-core 入口文件 |
proxyProbeTtlMs |
60000 |
代理探测结果的缓存时长 |
proxyProbeTimeoutMs |
1200 |
单次代理探测超时 |
代理行为
proxyUrl留空 → 自动探测本地常见端口,找到就并发跑境外引擎,找不到就只跑直连bing(省时间、省带宽、避免重复结果)'off'→ 强制直连,只用bing- 显式 URL → 只探测该地址
代理只作用于需要它的引擎(google-browser / bing-global / searxng);bing 始终直连——把国内引擎塞进境外出口 IP 反而会触发验证墙。
关于 SearXNG(可选)
searxngBaseUrl 填一个自建 SearXNG 实例即可启用元搜索(它内置 google 引擎)。但请注意实测结论:
- 公共实例基本不可用:
searxng.site对 JSON 返回 403,searx.be不返回 JSON(需在实例的settings.yml里开启search.formats包含json) - Windows 上跑不起来:
uwsgi需要os.uname()、SearXNG 的valkeydb.py直接import pwd,都是 Unix 专有依赖
所以除非你在 Linux/WSL/Docker 里有实例,否则建议直接用 google-browser。
已知限制
- 浏览器引擎有冷启动成本:首次搜索要拉起 Chrome(约 3-6s),之后实例复用(约 3-4s)。实例空闲 5 分钟后自动回收(
browserIdleTimeoutMs可调,0表示常驻),插件卸载时也会关闭。 - DuckDuckGo 已不可用:实测返回 HTTP 202 +
cc=botnet反爬页(html 与 lite 端点均如此),因此默认不启用。 mojeek实测返回 0 条(选择器失效),未实现。- 工具层最多只给模型 8 条结果:即使插件返回更多(实测 10 条),
dsh-tool-web仍会截断到searchMaxResults的默认值 8。 注意:这个上限无法通过 profile 的cordis.patch.yml调高。 原因(查自app.asar内的 patch):dsh-base挂载的tool-web是 host 行,而dsh-web-app明确把它disabled: true,改为在每个 agent preset 里组合这两个工具——preset 在 asar 内、随 dsh 安装只读。想调整需自定义 agent preset($DSH_HOME/.agent-presets),收益有限,故不建议。
开发与自检
包内自带 selftest.mjs,可以直接用纯 Node 跑(插件零 host 依赖,不需要 dsh 运行环境):
node selftest.mjs
它会依次验证:三个引擎各自可用、代理自动探测、语言感知(中英文查询的引擎差异)、无代理降级,并打印每条链的 plan 与耗时。
改完代码后重新打包并升级:
npm pack
dsh plugin --profile <profile> add ./dsh-keyless-search-<version>.tgz
实现要点
| 文件 | 说明 |
|---|---|
index.js |
全部实现:HTTP 层、代理 CONNECT 隧道、三个引擎、分层合并、cordis 入口 |
cordis.patch.yml |
bundle patch:插入插件行并选中 provider |
selftest.mjs |
真实网络自检 |
几个踩过坑的设计:
- HTTP 层是手写的(基于
node:net/node:tls),刻意不用https.request:把已建立的代理隧道 socket 交给它会导致二次 TLS 包装,表现为ECONNREFUSED。 - Google 的真实 URL 用 CDP
redirectChain解出,并abort掉目标页请求——既不渲染目标页,也不浪费流量(实测 8/8 成功、438ms)。 - Chrome 实例复用 + 空闲回收:避免每次搜索重付启动开销;空闲超过
browserIdleTimeoutMs后自动关闭。 - 回收只作用于自己启动的实例:走 puppeteer 的
proc.close()(连带收尾该实例的子进程与临时 profile),失败才退化为对该实例自身进程句柄的kill()。代码里不存在taskkill/Stop-Process -Name chrome这类按进程名批量匹配的写法,因此绝不会碰到你手动打开的浏览器;只要有搜索正在使用该实例,回收就会被推迟。 puppeteer-core三档解析:显式puppeteerPath→ 包名解析 → 扫描$DSH_HOME/profiles/*/node_modules。
故障排查
| 现象 | 原因与处理 |
|---|---|
web_fetch 报 configured web provider ... is not registered |
你的 patch 覆盖了 web 行却漏掉 fetchProvider。整行替换语义下必须重述 fetchProvider: http |
搜索报 WEB_PROVIDER_AMBIGUOUS |
同时有多个可用 provider 且未指定。在 web 行设 searchProvider: local-multi |
| 所有引擎失败且提到 proxy | 代理没起来。检查 proxyUrl/端口,或设 'off' 只走直连 |
puppeteer-core not loadable |
依赖没装上(手工 file:// 加载时常见)。执行 dsh plugin add 让 pnpm 安装,或用 puppeteerPath 指定入口 |
no Chrome found |
用 chromePath 指定 Chrome/Edge 路径 |
| Google 结果为空 | 代理不可用,或 Google 改版导致 DOM 选择器失效(改版时更新 #rso h3 与 .t2Cxc 相关选择器) |
许可
MIT
No comments yet. Be the first to write one.