Bundle
dsh-keyless-search
免 API key 的多引擎搜索 provider for DeepSeek Harness (ctx.web):真实浏览器 Google + 国际版 Bing + 中文 Bing,带代理自动探测与语言感知
- Source
- Wandering233
- License
- MIT
- Updated
- Updated 23 hours ago
Readme
# dsh-keyless-search
[](LICENSE)
[](package.json)
[](#跨平台)
[](#为什么需要它)
[](https://github.com/deepseek-ai/deepseek-harness)
免 API key 的多引擎搜索 provider,为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的 `ctx.web` 搜索接缝提供后端。
装上它之后,内置的 `web_search` 工具照旧可用,但底层不再是"一次搜索 = 一整个模型轮次"的官方服务端搜索,而是由本插件自己发起检索。
## 特性
- **不需要任何 API key** —— 不依赖 `DEEPSEEK_API_KEY`,也不消耗模型轮次
- **真实浏览器 Google** —— 用本机 Chrome 渲染 `google.com`,绕开纯 HTTP 抓取必然撞上的 `enablejs` JS 墙
- **三引擎可降级** —— `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` 时不干预。
## 安装
```bash
# 方式一:从 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`**:
```bash
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 均可运行**。浏览器探测顺序:
1. `chromePath` 显式指定
2. **常见安装路径** —— 覆盖 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
3. **puppeteer 自带缓存** —— `~/.cache/puppeteer/chrome/<version>/…`(macOS 为 `~/Library/Caches/…`),适合没装系统浏览器的 Linux
4. **扫 `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`:
```yaml
- 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 运行环境):
```bash
node selftest.mjs
```
它会依次验证:三个引擎各自可用、**代理自动探测**、**语言感知**(中英文查询的引擎差异)、无代理降级,并打印每条链的 `plan` 与耗时。
改完代码后重新打包并升级:
```bash
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
Install
dsh plugin --profile web add github:Wandering233/dsh-keyless-search
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-keyless-search from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.