# 🌐 dsh-9router-web-search > **为 DeepSeek Harness 提供由 9router 背书的网页搜索与抓取。** > 将原生 `web_search` 与 `web_fetch` 工具接入 9router 的 `/v1/search` 与 `/v1/web/fetch` 端点 —— 无需服务端搜索工具。 [![Version](https://img.shields.io/badge/version-0.2.3-green)](https://github.com/lordraiden/dsh-9router-web-search/releases) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org) [![ESM](https://img.shields.io/badge/module-ESM-8e44ad)](https://nodejs.org/api/esm.html) [![GitHub stars](https://img.shields.io/github/stars/lordraiden/dsh-9router-web-search?style=social)](https://github.com/lordraiden/dsh-9router-web-search) [🇺🇸 English](./README.md) · 🇨🇳 **简体中文** --- ## ✨ 功能 - 🔎 **搜索 provider** — 将 DSH 原生 `web_search` 路由到 9router 的 `/v1/search`,返回去重后的 `WebSource` 结果(标题、摘要、发布日期)。 - 📄 **抓取 provider** — 将 DSH 原生 `web_fetch` 路由到 9router 的 `/v1/web/fetch`,返回干净的文本/Markdown 正文。 - 🎛️ **设置卡片** — 原生 DSH 设置面板,用于配置 API 密钥、基础地址、模型、超时与限额。 - 🧩 **零配置接入** — bundle patch 已把 web seam 的 search 与 fetch 固定到 `9router`,无需手动配置 provider。 - 🔐 **凭据感知、密钥可选** — 从 DSH 凭据服务(`NINE_ROUTER_API_KEY`)或字面配置值解析 API 密钥;无密钥的本地 9router 实例开箱即用;绝不提交到仓库。 - 🧱 **零构建** — 纯 ESM,纯 JavaScript,无需打包器。 ## 🧭 为什么需要它 DSH 默认的 `web_search` 使用 `web-search-deepseek` provider,它要求上游实现 Anthropic 的 `web_search_20250305` **服务端**搜索工具并返回 `web_search_tool_result` 块。 9router 是一个 **OpenAI 兼容网关**:它把该工具定义当作普通函数交给模型,*而不在服务端执行搜索* —— 因此它本身无法提供原生搜索。 | | `web-search-deepseek`(默认) | **9router(本插件)** | |---|---|---| | 依赖 | Anthropic `web_search_20250305` 服务端工具 | 任意 9router 兼容网关 | | 支持 9router | ❌ | ✅ | | 搜索端点 | 由模型提供 | `POST /v1/search` | | 抓取端点 | 由模型提供 | `POST /v1/web/fetch` | 本插件直接调用 9router 自有的端点,并将其响应映射为 DSH web seam 所期望的 `WebSource` 与 `WebFetchBody` 类型。 ## ⚙️ 工作原理 ```text web_search / web_fetch(原生 DSH 工具) │ ▼ ctx.web seam │ provider = "9router" ▼ dsh-9router-web-search ├── search → POST {baseURL}/search → WebSource[] └── fetch → POST {baseURL}/web/fetch → WebFetchBody │ ▼ 9router gateway │ ▼ 9router providers / model combos ``` 错误会以机器可路由的 `WebError` 代码(`WEB_PROVIDER_ERROR`、`WEB_TIMEOUT`、`WEB_ABORTED`、`WEB_PROVIDER_CREDENTIAL_MISSING`)呈现,瞬态网络与 5xx 故障按退避重试,非 2xx 的抓取响应会作为结果返回 —— 绝不静默抛出。 ## 🚀 快速开始 **1. 安装插件** ```bash npx @deepseek-ai/dsh plugin --profile web add github:lordraiden/dsh-9router-web-search ``` **2. 接入配置** - 插件的 bundle patch 已把 `web.searchProvider` 与 `web.fetchProvider` 固定为 `9router` —— 无需手动接入(profile 自己的 `cordis.patch.yml` 可覆盖该行)。 - 在设置卡片中设置 `baseURL`(或 `NINE_ROUTER_BASE_URL` 环境变量),指向你的 9router 网关 —— 没有隐式回退端点;未配置端点时 provider 会报告自身不可用。 - 如果你的 9router 实例需要密钥,将 `NINE_ROUTER_API_KEY` 存入 DSH 凭据服务 —— 设置卡片中的 API key 字段会写入该凭据 —— 或在插件设置中设置字面 `apiKey`。该密钥在 UI 中为只写,绝不会被存储到本仓库。无密钥的实例开箱即用。 **3. 重启并刷新** 重启 Harness,然后强制刷新浏览器。web 设置卡片中会出现 `9router` 选项。 > 🔒 **可复现安装** — 追加 commit SHA 以锁定精确版本: > > ```bash > npx @deepseek-ai/dsh plugin --profile web add github:lordraiden/dsh-9router-web-search#44d1936 > ``` ## 🛠️ 配置 | 键 | 类型 | 默认值 | 说明 | |---|---|---|---| | `baseURL` | string | — | 你的 9router 兼容 API 的基础地址(仅限 http/https);解析顺序:显式设置 → `NINE_ROUTER_BASE_URL` 环境变量 → 缺失(provider 不可用) | | `searchModel` | string | `search-combo` | 搜索端点使用的模型名 | | `fetchModel` | string | `fetch-combo` | 抓取端点使用的模型名 | | `searchType` | string | `web` | 发送到搜索端点的 `search_type` | | `defaultMaxResults` | number | `8` | 仅当搜索请求未指定 `maxResults` 时使用的来源上限(DSH 接缝负责执行每操作限制) | | `timeoutMs` | number | `30000` | 共享的每请求超时(各能力默认值) | | `searchTimeoutMs` | number | `30000` | 仅搜索的超时;留空沿用 `timeoutMs` | | `fetchTimeoutMs` | number | `30000` | 仅抓取的超时;留空沿用 `timeoutMs` | | `fetchFormat` | string | `markdown` | 请求抓取端点返回的正文格式(`markdown`、`text`、`html`) | | `maxCharacters` | number | `50000` | 抓取正文的字符上限;会发送给网关并在本地强制执行 | | `apiKey` | secret | — | 字面 API 密钥(可选;无密钥 = 不发送 `Authorization` 头) | | `apiKeyEnv` | credential-ref | `NINE_ROUTER_API_KEY` | 凭据服务的键名 | 自托管 9router 按自己的 provider 配置选择搜索/抓取后端,会忽略 `searchModel`/`fetchModel`;按模型路由的云网关请保留这两个字段。 **端点优先级** — 显式 `baseURL` 设置优先于 `NINE_ROUTER_BASE_URL` 环境变量;两者都未设置时不存在端点,provider 会报告自身不可用。不会静默回退到仓库选定的远端。schema 会在任何请求发出前拒绝非 http(s) URL、空的模型/格式字符串和非正的数值限制。 **凭据** — `NINE_ROUTER_API_KEY` 通过 DSH 凭据服务解析;字面 `apiKey` 可选,设置后优先。`apiKeyEnv` 是凭据服务中的引用名,不是密钥本身。该密钥在设置 UI 中为只写,绝不存储到 git,也不会出现在日志、诊断信息或错误消息中。 ## 📌 DSH 兼容性 | | 版本 | |---|---| | 最低支持版本 | `0.1.7-rc.1`(peer 范围 `>=0.1.7-rc.1 <1.0.0`) | | 最新已验证版本 | 最新发布的 `0.2.x` —— CI 会安装它(当前为 `0.2.0-rc.2`)并针对其运行完整测试套件 | CI 会运行两次契约测试套件:一次针对已提交的 peer 范围,一次针对最新的 `0.2.x`。声明范围之外的版本 —— 包括未来的 `1.x` 发布 —— 在以前述方式实际验证之前,**不会**声称兼容。 ## 🛡️ 9router 安全 `web_fetch` 能力将 URL 提取与 SSRF 防护委托给 9router **服务端**:插件把目标 URL 发送到 `POST /v1/web/fetch`,并信任网关的抓取边界。插件自身不实现任何本地 SSRF 策略。 请运行已打补丁的、当前版本的 9router —— 抓取端点存在已发布的安全公告: - [GHSA-qj3v-64wj-q825](https://github.com/decolua/9router/security/advisories/GHSA-qj3v-64wj-q825) ## 🧪 开发 ```bash pnpm install # 安装依赖 pnpm test # 运行测试套件(node:test) node scripts/validate-pack.mjs # 校验打包产物 ``` Node `>=20`(CI 使用 Node 22)与 pnpm(CI 使用 pnpm 10)。 **仓库结构** ```text dsh-9router-web-search/ ├── src/ # 服务端插件:index.js + nine-router-client.js ├── client.js # 设置卡片 ├── cordis.patch.yml # 注册插件的 bundle patch ├── test/ # node --test 契约测试套件 ├── scripts/ # validate-pack.mjs、sync-storefront.mjs ├── .github/workflows/ # CI(测试 + 打包、DSH 0.2.x 兼容)与发布 └── package.json # ESM 插件清单 ``` **发布** — 稳定版本以与 `package.json` 匹配的 `v` GitHub Releases 发布;发布 workflow 会在发布前针对 tag 重新运行完整测试套件与打包校验。 **Storefront 同步** — `node scripts/sync-storefront.mjs` 将插件条目同步到 storefront 仓库(手动执行;需要向已配置的 storefront fork 拥有推送权限)。 ## 🌱 生态与发布 - 📦 **分发** — GitHub 仓库是唯一的分发来源;稳定版本以 `v` GitHub Releases 发布。 - 🐛 **问题与需求** — 请提交 [issue](https://github.com/lordraiden/dsh-9router-web-search/issues)。 ## 📄 许可证 [MIT](./LICENSE) ## 🙏 致谢 本项目最初 fork 自 [rebron1900/dsh-web-search-9router](https://github.com/rebron1900/dsh-web-search-9router) —— 感谢原作者为 DeepSeek Harness(DSH)提供 9router 背书的网页搜索与抓取 provider。