# dsh-web-searxng-search **把自建 SearXNG 接入 DeepSeek Harness 的 web 搜索能力。** [![License: MIT](https://img.shields.io/github/license/kkgace/dsh-web-searxng-search)](LICENSE) ![Node](https://img.shields.io/badge/node-%3E%3D22.19-informational) ![dsh plugin](https://img.shields.io/badge/dsh-plugin-4b5563) [English](README.md) | 中文 把 `web_search` 工具指向你自己运维的 [SearXNG](https://docs.searxng.org/) 实例。 无需 API Key、无需厂商账号,查询也不归因到任何账号。 ## 为什么需要它 DeepSeek Harness 自带的搜索 provider 依赖 DeepSeek API,每次搜索都会计费并归因到某个 API Key。 本插件在同一个 `ctx.web` seam 上注册第二个 provider,并把 seam 切到它——搜索因此跑在你可控的 基础设施上,也不需要任何厂商凭证。 ## 特性 - 直接实现 `ctx.web` seam 上的 `WebSearchProvider`,内置的 `web_search` 工具照常可用。 - 自身无运行时依赖;官方 `@deepseek-ai/*` 包声明为 peer dependency,由宿主提供。 - 可配置超时、语言、安全搜索级别、分类与引擎白名单。 - 结果按 URL 去重——元搜索引擎常把同一页面返回多次。 - 区分「实例返回了 HTML」(未开启 JSON 格式)与「确实无结果」,并给出可执行的错误提示。 - 注册是一个 effect(`ctx.effect`),插件卸载与 HMR 都会干净回滚。 - 14 个离线单测,不依赖网络。 ## 环境要求 | 组件 | 版本 / 说明 | |---|---| | DeepSeek Harness | `@deepseek-ai/dsh-web` `0.1.x` | | Node.js | `>= 22.19` | | SearXNG | 需开启 JSON 格式(见下) | ### 开启 SearXNG 的 JSON API 插件读取 [SearXNG 搜索 API](https://docs.searxng.org/dev/search_api.html)。JSON 输出**默认关闭**; 未开启时实例会返回 `200` + 一个 HTML 页面,插件会将其报告为配置错误,而不是"无结果"。 ```yaml # settings.yml search: formats: - html - json ``` 安装前先验证接口: ```bash curl -fsS 'http://localhost:8888/search?q=test&format=json' | head -c 120 ``` ## 安装 ```bash # 从 npm dsh plugin --profile web add dsh-web-searxng-search # 从 GitHub dsh plugin --profile web add github:kkgace/dsh-web-searxng-search # 从本地目录 dsh plugin --profile web add /path/to/dsh-web-searxng-search ``` 然后重启 web 应用: ```bash dsh web ``` 收录之后,也可以在 **设置 → 插件市场** 里一键安装。 ## 配置 bundle 出厂默认指向 `http://localhost:8888`。用 profile 覆盖层指向你的实例—— **只 patch 已存在的行,不要 insert 新行**: ```yaml # ~/.dsh/profiles/web/cordis.patch.yml - id: web-search-searxng config: baseURL: 'https://searxng.example.com' ``` 也可以在 `dsh` 运行的环境里设置 `SEARXNG_BASE_URL`。 | 选项 | 类型 | 默认值 | 说明 | |---|---|---|---| | `baseURL` | `string` | `http://localhost:8888` | 实例根地址,末尾不要带 `/search`。 | | `timeoutMs` | `number` | `15000` | 单次请求超时,1000–120000 毫秒。 | | `language` | `string` | — | 传给实例的界面语言,如 `zh-CN`。 | | `safeSearch` | `0 \| 1 \| 2` | `0` | 关闭 · 适中 · 严格。 | | `categories` | `string` | — | 逗号分隔的 SearXNG 分类,如 `general,it`。 | | `engines` | `string` | — | 逗号分隔的引擎名,用于收窄搜索范围。 | | `apiKey` | `string` | — | 仅当实例需要认证时的 Bearer Token。 | ## 工作原理 1. Harness 中一切皆 Cordis 插件,能力以 *seam* 暴露。web 搜索就是 `ctx.web` seam。 2. 插件实现 `WebSearchProvider` 契约——`id`、`available(): boolean`(纯本地、不发网络)、 `search(req, signal?)`——并在 `ctx.effect(...)` 内通过 `ctx.web.registerSearchProvider(...)` 注册。 3. 只注册还不够:`cordis.patch.yml` 必须把 `web.searchProvider` 切到 `searxng`, 否则 seam 会一直停留在内置 provider 上。 4. 失败以 `WebError(message, code, { cause })` 抛出,由宿主路由处理,而不是静默回退到别的 provider。 ## 开发 ```bash npm test # node --test tests/*.test.mjs ``` 测试会 mock 掉 `globalThis.fetch`,不碰任何网络。官方 `@deepseek-ai/*` 包是由宿主提供的 peer dependency;若要在 Harness profile 之外跑测试,让它们能从 `node_modules` 解析即可。 ## 排错 | 现象 | 原因 / 处理 | |---|---| | `web_search` 仍走别的 provider | `web.searchProvider` 没指向 `searxng`,或改完没有重启 `dsh web`。 | | 启动报 `duplicate loader entry id: web-search-searxng` | profile 覆盖层 `insert` 了一行 bundle 已经插入的行。改用 `- id: …` + `config:` 的 patch 形式。 | | 报"无结果"但实例确实有数据 | 实例未开启 JSON 格式。 | | `WEB_PROVIDER_CONFIGURED_UNAVAILABLE` | `available()` 返回 false——`baseURL` 不是绝对 `http(s)` 地址。 | | `WEB_PROVIDER_TIMEOUT` | 实例未在 `timeoutMs` 内响应。 | ## 许可证 [MIT](LICENSE)