dsh-web-search-plugin 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,47 @@
2
2
 
3
3
  本项目的重要变更都记录在此。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [未发布]
6
+
7
+ ## [0.5.0] - 2026-10-09
8
+
9
+ ### 新增
10
+
11
+ - **适配 DeepSeek Harness `0.2.0-rc.2`**:四个 DSH peerDependencies 提升到 `^0.2.0-rc.2`,`@deepseek-ai/cordis` / `@deepseek-ai/schemastery` 对齐 `~4.0.4` / `~3.18.4`;版本号与 `User-Agent` 更新为 `0.5.0`。
12
+ - DeepSeek 后端支持账号令牌认证:会话 provider 为 `deepseek-account` 时通过 `deepseekAccount.resolveToken()` 获取令牌并以 `x-dsh-auth-token` 发送,未授权时回落到 `DEEPSEEK_API_KEY`。
13
+ - 有会话发起者时,DeepSeek 搜索在派发前记录脱敏请求到会话事件 `web/deepseek-search-llm-request`,与官方 provider 行为一致。
14
+ - 插件列表页与详情页的标题、描述随界面语言切换:`locale/en.json` 与 `locale/zh.json`,经 `"./locale/*.json"` 导出(与官方 bundle 同一机制)。卡片内文案由 `lib/client.js` 的 `zh` / `en` 字典提供。
15
+ - README 面向开源使用者整理:删除仅适用于本机的排查笔记与旧个人包迁移章节,修正与实现脱节的仓库布局与安装说明。
16
+
17
+ ### 移除
18
+
19
+ - **`settings.installSection()` 已从 Harness 移除**:Host 侧改为全部配置字段声明 `volatile()`,并在 `apply` 中通过 `config.<field>.get()` 读取实时值。插件不再调用 `ctx.inject(["settings"])`,也不再维护 `setSource` / `current()` 这条配置来源链。
20
+ - **客户端 `settingsScope` 服务已移除**:浏览器端改用 `ctx.configForms`。设置分区通过 `configForms.whileServed([namespace], register)` 条件注册,Host 未下发该命名空间时不留痕迹。`inject` 中的 `settingsScope` 同步替换为 `configForms`。
21
+ - 设置分区不再自行注册 Host 设置卡:0.2.x 的设置由当前 Profile 的插件配置保存,`client.js` 的分区只负责展示与写入。
22
+
23
+ ### 修复
24
+
25
+ - Exa 请求改用 `numResults` 与对象形态的 `contents.highlights`;SearXNG 不再发送其 API 未定义的 `count` 参数。
26
+ - 插件 `maxResults` 与调用方上限取较小值,避免常规 `web_search` 请求覆盖设置卡上限;未选中的引擎不再解析其凭据引用,设置卡校验引用名格式。
27
+ - Tavily 用量只保留当前凭据的内存数据,换 key 后立即重新对账;手动刷新限速改为 10 秒,自动刷新不受手动限速约束。
28
+ - Firecrawl 搜索按 API 要求发送数组形态的 `sources`;REST 元数据的空 `countParam` 不再被强制改为 `max_results`。
29
+ - DeepSeek 与 REST 后端按配置的 `maxResults` 截取超额结果,并标记 `truncated`。
30
+ - 网页搜索设置分区随 Host 配置命名空间出现和撤销;凭据写入失败时保留草稿并显示失败提示。
31
+ - 同时修改凭据引用名与 API key 时,密钥写入新的引用名;DeepSeek 账号令牌预检响应取消并统一映射错误。
32
+ - Tavily 与 DeepSeek 的空端点配置恢复环境变量回退;Tavily 手动刷新额度增加最短请求间隔。
33
+ - 设置卡把配置改动按同一 revision 原子提交;Host 拒绝写入时保留草稿,保存期间锁定编辑。写入新凭据时清空该引擎的字面量 key 覆盖,使新凭据生效。
34
+ - 更新 Serper 冒烟脚本,使用当前导出的 `RestSearchProvider` 与内置元数据。
35
+
36
+ ### 文档
37
+
38
+ - 澄清本插件注册的接缝 id 是 `dsh-web-search`,`deepseek-official` 只是内部引擎选项;同步修正 bundle patch、卸载说明和会话事件说明。
39
+ - 对齐 README 的端点默认值、环境变量优先级和 API key 保存位置。
40
+
41
+ ### 说明
42
+
43
+ - `cordis.patch.yml` 禁用内置 `web-search-deepseek`,使此 bundle 统一管理 DeepSeek 搜索配置;本插件注册的接缝 id 为 `dsh-web-search`。
44
+ - Bundle patch 仍使用单文件声明(0.1.7 起支持的多文件有序声明为可选能力,未启用)。
45
+
5
46
  ## [0.4.1] - 2026-09-10
6
47
 
7
48
  ### 变更
@@ -82,7 +123,7 @@
82
123
  - Brave 月限额 `0` 的套餐按控制台 **Capacity** 展示每秒窗口(例如 50 次/秒),不再把月配额渲染成「已用 0 / 0」。Brave 没有花费/credits 接口。
83
124
  - 数字框只接受不小于 1 的整数;非法输入用红色错误文案替换灰色 hint,保存会被拦住。超出 20 的结果数量会夹到上限。
84
125
 
85
- ## [0.2.0] - 2026-08-19
126
+ ## [0.2.0] - 2026-08-18
86
127
 
87
128
  ### 新增
88
129
 
package/README.md CHANGED
@@ -2,40 +2,41 @@
2
2
 
3
3
  面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) web 能力接缝(`ctx.web`)的统一网页搜索插件,内置 **DeepSeek(官方,默认)/ Tavily / [Brave Search](https://brave.com/search/api/) / Serper / SerpApi / Exa / SearXNG / Scavio / Firecrawl 九个后端**。本包只向接缝注册 **一个** `WebSearchProvider`,稳定 id 为 `dsh-web-search`。在 **设置 → 网页搜索** 里切换引擎即可,不必再改 `web.searchProvider`。
4
4
 
5
- - **DeepSeek(官方,默认)** — 走 DeepSeek 的 Anthropic 兼容 Messages API(原生 `web_search_20250305` 工具,凭据名 `DEEPSEEK_API_KEY`),一次搜索消耗一个模型轮次。
5
+ - **DeepSeek(官方,默认)** — 走 DeepSeek 的 Anthropic 兼容 Messages API(原生 `web_search_20250305` 工具,支持账号令牌或 `DEEPSEEK_API_KEY`),一次搜索消耗一个模型轮次。
6
6
  - **Tavily** — `keyless`(免费、限流、无需账号)或 `keyed`(`TAVILY_API_KEY`,Bearer token)。keyed 会在设置卡显示额度进度条。
7
7
  - **Brave Search** — `GET https://api.search.brave.com/res/v1/web/search`,请求头 `X-Subscription-Token`(凭据名 `BRAVE_API_KEY`)。一次搜索就是一次 HTTP 请求,不走模型轮次;设置卡按响应头展示 Capacity / 月配额。
8
8
  - **Serper** — Google 结果(`POST /search`,头 `X-API-KEY`,凭据名 `SERPER_API_KEY`)。
9
9
  - **SerpApi** — `GET /search.json`,key 走 query(`api_key=`),凭据名 `SERPAPI_API_KEY`。
10
- - **Exa** — 语义/神经搜索(`POST /search`,Bearer,凭据名 `EXA_API_KEY`)。
11
- - **SearXNG** — 自建/协议免 key 的元搜索(`GET /search?format=json`),可填自托管实例地址。
10
+ - **Exa** — 语义/神经搜索(`POST /search`,Bearer,凭据名 `EXA_API_KEY`),请求 `contents.highlights` 作为结果摘要。
11
+ - **SearXNG** — 自建/协议免 key 的元搜索(`GET /search?format=json`),可填自托管实例地址;实例须启用 JSON 输出。
12
12
  - **Scavio** — Google SERP(`POST /api/v2/google`,Bearer,凭据名 `SCAVIO_API_KEY`),响应与 SerpApi 同构。
13
- - **Firecrawl** — 搜索+抓取(`POST /v2/search`,Bearer,凭据名 `FIRECRAWL_API_KEY`),`sources: ["web"]`。
13
+ - **Firecrawl** — 网页搜索(`POST /v2/search`,Bearer,凭据名 `FIRECRAWL_API_KEY`),`sources: ["web"]`;当前请求没有启用整页抓取。
14
14
  - **DuckDuckGo** — 无官方搜索 API,不提供独立后端;下拉中以禁用项提示:选 SearXNG 并指向聚合了 DuckDuckGo 的实例即可。
15
15
  - **官方 key 跳转** — 需要 key 的内置 provider 在设置卡带「获取 API key ↗」一键跳官方控制台;免 key 的(SearXNG / Tavily keyless)不渲染。
16
- - **不写自定义 session 事件** — 工具结果已经走接缝自己的事件,无需多余信封。
16
+ - **会话事件** — 有会话发起者时,DeepSeek 在派发前记录脱敏模型请求到 `web/deepseek-search-llm-request`;REST 后端不追加自定义事件。
17
17
 
18
18
  ## 特性
19
19
 
20
- - 与官方 `@deepseek-ai/dsh-web-search-deepseek` 相同的提供方约定:`inject: ['web']` + `ctx.settings.installSection` + `ctx.web.registerSearchProvider`。
21
- - 顶层 **设置 → 网页搜索** 分区:两列布局、未保存草稿、保存 toast;结果数量为 1–20 下拉。
20
+ - 与官方 `@deepseek-ai/dsh-web-search-deepseek` 相同的提供方约定:`inject: ['web']` + 全`volatile()` 配置字段 + `ctx.web.registerSearchProvider`。
21
+ - 自定义配置卡片位于 **设置 → 网页搜索**。
22
22
  - **默认引擎为 DeepSeek(官方)**:新装 / 未显式改动时,搜索直接走 `deepseek-official`,不再依赖 keyless 的 Tavily。
23
- - 纯 REST 后端由**静态元数据表 + 通用后端**驱动,新增 provider 即"表里加一行 + 官方跳转链接",无定制执行代码。
23
+ - **9 个后端**中,8 个纯 REST 后端由**静态元数据表 + 通用后端**驱动;只有 DeepSeek 官方走模型轮次。新增内置 REST 引擎还需同步配置字段和客户端引擎列表。
24
24
  - Tavily keyless 无需密钥即可用;Brave 需要订阅 token(若本机已有 `BRAVE_API_KEY` 凭据,可直接复用)。
25
25
  - Tavily keyed / Brave 在设置卡展示额度进度条(DeepSeek 官方与 Tavily keyless / 其余 REST 后端不展示)。
26
26
  - 各引擎结果都规范化为接缝的 `WebSearchResult`(可选 `content` + `sources[]`),按 URL 去重。
27
- - 设置段 `dsh-web-search-plugin` 通过 dsh 的 `settings.installSection` 注册、由 `settings.describe()` 动态暴露,不需要宿主白名单补丁,也不需要自建回环 settings 桥。
27
+ - 配置由当前 Profile 的插件配置保存,全部字段声明为 `volatile()`:改引擎或端点后无需重启插件即可生效。
28
+ - 插件列表页与详情页的标题、描述随界面语言切换(`locale/*.json`);卡片内文案自带中英字典。
28
29
  - 错误映射为 `WEB_PROVIDER_ERROR` / `WEB_ABORTED` / `WEB_PROVIDER_CREDENTIAL_MISSING`。
29
30
 
30
31
  ## 运行要求
31
32
 
32
- - DeepSeek Harness `0.1.5-rc.1` 或兼容版本
33
+ - DeepSeek Harness `0.2.0-rc.2` 或兼容的 `0.2.x` 版本
33
34
  - Node.js `^22.19.0` 或 `>=24.0.0`
34
35
  - pnpm,用于通过 `dsh plugin` 把插件装进 profile
35
36
 
36
37
  ## 安装
37
38
 
38
- 本包是 **bundle**:`dsh.bundle.patch` + `cordis.patch.yml` 会插入 Host 行、把 `web.searchProvider` 设为 `dsh-web-search`,并**禁用内置 `web-search-deepseek` Host 插件**(本插件已自行托管 DeepSeek 后端,禁用它能移除旧的「插件 → 网页搜索」卡片、避免重复注册 provider)。没有这一声明时,`dsh plugin add` 只写入依赖,插件不会挂载。
39
+ 本包是 **bundle**:`dsh.bundle.patch` + `cordis.patch.yml` 会插入 Host 行、把 `web.searchProvider` 设为 `dsh-web-search`,并禁用内置 `web-search-deepseek` Host 插件,使此 profile 的 DeepSeek 搜索由本插件统一配置。本插件注册到接缝的 id 是 `dsh-web-search`;`deepseek-official` 是内部引擎选项,和内置插件注册的 id 不同。没有 bundle 声明时,`dsh plugin add` 只写入依赖,插件不会挂载。
39
40
 
40
41
  ### 从 npm 安装
41
42
 
@@ -47,39 +48,39 @@ dsh plugin --profile web add dsh-web-search-plugin
47
48
 
48
49
  ## 配置
49
50
 
50
- 设置卡编辑的是 `dsh-web-search-plugin` 命名空间:
51
+ 设置卡编辑的是 `dsh-web-search-plugin` 命名空间。设置卡中的 API key 输入框把密钥写入凭据服务;若直接在插件配置中填写 `*ApiKey` 字段,则该字面量保存在插件配置中。建议使用设置卡或凭据引用名,避免在配置文件中保存密钥。
51
52
 
52
53
  | 键 | 默认值 | 含义 |
53
54
  |---|---|---|
54
55
  | `provider` | `deepseek-official` | `tavily`、`brave`、`deepseek-official`、`serper`、`serpapi`、`exa`、`searxng`、`scavio` 或 `firecrawl` |
55
56
  | `mode` | `keyless` | 仅 Tavily:`keyless` 或 `keyed` |
56
- | `apiKey` | — | Tavily API key 字面量(走凭据域,不会回显) |
57
+ | `apiKey` | — | Tavily API key 配置字面量;设置卡的 key 输入框写入凭据服务 |
57
58
  | `apiKeyEnv` | `TAVILY_API_KEY` | keyed Tavily 使用的凭据/环境变量名 |
58
- | `baseURL` | `https://api.tavily.com` | Tavily REST 基址;会再拼 `/search` |
59
- | `maxResults` | `8` | 每次搜索返回的源数量,各后端共用。设置卡为 1–20 下拉,超出上限会夹到 20 |
59
+ | `baseURL` | 空 | Tavily REST 基址;留空时依次使用 `TAVILY_BASE_URL` 和 `https://api.tavily.com`,再拼 `/search` |
60
+ | `maxResults` | `8` | 插件返回的源数量上限,与调用方的 `request.maxResults` 取较小值。设置卡为 1–20 下拉;SearXNG 不支持请求级数量参数,只在收到结果后截取 |
60
61
  | `searchDepth` | `basic` | 仅 Tavily:`basic` 或 `advanced` |
61
62
  | `includeAnswer` | `true` | 仅 Tavily:请求生成摘要,写入结果 `content` |
62
63
  | `topic` | `general` | 仅 Tavily:`general` 或 `news` |
63
- | `deepseekApiKey` | — | DeepSeek API key 字面量(走凭据域) |
64
+ | `deepseekApiKey` | — | DeepSeek API key 配置字面量;设置卡的 key 输入框写入凭据服务 |
64
65
  | `deepseekApiKeyEnv` | `DEEPSEEK_API_KEY` | DeepSeek 使用的凭据/环境变量名 |
65
- | `deepseekBaseURL` | `https://api.deepseek.com/anthropic/v1` | DeepSeek Anthropic 兼容 Messages 基址;再拼 `/messages` |
66
+ | `deepseekBaseURL` | 空 | DeepSeek Anthropic 兼容 Messages 基址;留空时依次使用 `DEEPSEEK_SEARCH_BASE_URL` 和 `https://api.deepseek.com/anthropic/v1`,再拼 `/messages` |
66
67
  | `model` | `deepseek-v4-flash` | Anthropic 格式模型名 |
67
68
  | `apiVersion` | `2023-06-01` | `anthropic-version` 请求头 |
68
69
  | `maxTokens` | `4096` | Messages 请求生成 token 上限 |
69
70
  | `maxUses` | `5` | 每次请求 `web_search` 工具的最大调用次数 |
70
- | `braveApiKey` | — | Brave 订阅 token 字面量(走凭据域) |
71
+ | `braveApiKey` | — | Brave 订阅 token 配置字面量;设置卡的 key 输入框写入凭据服务 |
71
72
  | `braveApiKeyEnv` | `BRAVE_API_KEY` | Brave 使用的凭据/环境变量名 |
72
73
  | `braveBaseURL` | `https://api.search.brave.com/res/v1/web/search` | Brave 网页搜索接口 |
73
74
  | `country` | — | Brave 的 `country`(ISO 两位码,如 `cn`);留空使用 Brave 默认 |
74
75
  | `searchLang` | — | Brave 的 `search_lang`(如 `zh-hans`);留空使用 Brave 默认 |
75
76
  | `freshness` | — | Brave 的 `freshness`:`pd` / `pw` / `pm` / `py` |
76
77
  | `proxy` | — | Brave 使用的 HTTP(S) 代理覆盖;留空继承 DSH 的全局 `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` / `NO_PROXY` 策略 |
77
- | `serperApiKey` / `serperApiKeyEnv` / `serperBaseURL` | — / `SERPER_API_KEY` / — | Serper 的 key 字面量 / 凭据引用名 / 端点覆盖 |
78
- | `serpapiApiKey` / `serpapiApiKeyEnv` / `serpapiBaseURL` | — / `SERPAPI_API_KEY` / — | SerpApi 的 key 字面量 / 凭据引用名 / 端点覆盖 |
79
- | `exaApiKey` / `exaApiKeyEnv` / `exaBaseURL` | — / `EXA_API_KEY` / — | Exa 的 key 字面量 / 凭据引用名 / 端点覆盖 |
80
- | `searxngBaseURL` | — | SearXNG 自托管实例基址;留空用 `https://searx.be` |
81
- | `scavioApiKey` / `scavioApiKeyEnv` / `scavioBaseURL` | — / `SCAVIO_API_KEY` / — | Scavio 的 key 字面量 / 凭据引用名 / 端点覆盖 |
82
- | `firecrawlApiKey` / `firecrawlApiKeyEnv` / `firecrawlBaseURL` | — / `FIRECRAWL_API_KEY` / — | Firecrawl 的 key 字面量 / 凭据引用名 / 端点覆盖 |
78
+ | `serperApiKey` / `serperApiKeyEnv` / `serperBaseURL` | — / `SERPER_API_KEY` / `https://google.serper.dev` | Serper 的配置字面量 key / 凭据引用名 / REST 基址 |
79
+ | `serpapiApiKey` / `serpapiApiKeyEnv` / `serpapiBaseURL` | — / `SERPAPI_API_KEY` / `https://serpapi.com` | SerpApi 的配置字面量 key / 凭据引用名 / REST 基址 |
80
+ | `exaApiKey` / `exaApiKeyEnv` / `exaBaseURL` | — / `EXA_API_KEY` / `https://api.exa.ai` | Exa 的配置字面量 key / 凭据引用名 / REST 基址 |
81
+ | `searxngBaseURL` | `https://searx.be` | SearXNG 实例基址;可改为自托管地址 |
82
+ | `scavioApiKey` / `scavioApiKeyEnv` / `scavioBaseURL` | — / `SCAVIO_API_KEY` / `https://api.scavio.dev` | Scavio 的配置字面量 key / 凭据引用名 / REST 基址 |
83
+ | `firecrawlApiKey` / `firecrawlApiKeyEnv` / `firecrawlBaseURL` | — / `FIRECRAWL_API_KEY` / `https://api.firecrawl.dev` | Firecrawl 的配置字面量 key / 凭据引用名 / REST 基址 |
83
84
 
84
85
  ## 未纳入的服务
85
86
 
@@ -92,40 +93,48 @@ dsh plugin --profile web add dsh-web-search-plugin
92
93
  | **SERPJET** | 官网当前不可访问,暂不接入 |
93
94
  | **DuckDuckGo** | 无官方搜索 API(HTML/社区库抓取不符合元数据表"纯 REST"边界)。下拉中有禁用提示,指引经 SearXNG 使用 |
94
95
 
95
- 环境变量:启动时 `DSH_WEB_SEARCH_PROVIDER=dsh-web-search` 会选中本接缝 id。未设置 `baseURL` 时,Tavily 基址回退 `$TAVILY_BASE_URL`。
96
+ 环境变量:启动时 `DSH_WEB_SEARCH_PROVIDER=dsh-web-search` 会选中本接缝 id。`baseURL` 留空时,Tavily 基址回退 `TAVILY_BASE_URL`;`deepseekBaseURL` 留空时回退 `DEEPSEEK_SEARCH_BASE_URL`。
96
97
 
97
98
  ## 额度
98
99
 
99
100
  - **Tavily keyless / DeepSeek 官方**:不展示额度条。前者是免费限流、没有账户配额;后者按次扣费、没有月度限额。
100
- - **Tavily keyed**:搜索时请求 `include_usage`,把本次 credits 累加到 `%DSH_HOME%\storages\dsh-web-search-usage.json`。Host 每 10 分钟(以及设置卡点刷新)调用 `GET /usage`,用 `account.current_plan` / `plan_limit` 做限额,用量取本地累计与远端的较大值。换套餐或远端用量回落会重置本地计数。
101
+ - **Tavily keyed**:搜索时请求 `include_usage`,在内存中累加当前凭据的 credits。Host 定时调用 `GET /usage`,设置卡也可手动刷新;手动刷新之间至少间隔 10 秒,自动对账不受此限制。用 `account.current_plan` / `plan_limit` 做限额,用量取当前凭据的本地累计与远端的较大值;换 key 时清空旧账户用量并立即重新对账。旧 Tavily 额度不再持久化到磁盘。
101
102
  - **Brave**:没有 usage / 花费接口。控制台 **Capacity** 就是响应头里的每秒窗口(例如 50 次/秒)。月限额 `0` 表示不限请求次数,不是额度用完。计费 credits 只能看 [Brave API 控制台](https://api-dashboard.search.brave.com/)。
102
103
  - 浏览器只读 `GET /dsh-web-search/usage`(不直打上游)。进度条:剩余超过 20% 为绿色,不超过 20% 为黄色,不超过 10% 为红色。
103
104
 
104
- ## 切回内置 DeepSeek
105
+ ## 卸载
105
106
 
106
- DeepSeek 已并入本插件(`provider: deepseek-official`),无需切回。若确要恢复 DSH 内置的 DeepSeek host 插件,请在 profile patch 里去掉对 `web-search-deepseek` 的 `disabled` 并把 `searchProvider` 设回 `deepseek-official`;本插件可以继续挂着,只是不会被选中。
107
+ 若要改用 DSH 内置的 DeepSeek host 插件,先卸载本插件及其 bundle patch:
108
+
109
+ ```powershell
110
+ dsh plugin --profile web remove dsh-web-search-plugin
111
+ ```
112
+
113
+ 随后检查 profile 中实际生效的 `web.searchProvider`;若曾手工覆盖为 `dsh-web-search`,将该覆盖改回 `deepseek-official`。
107
114
 
108
115
  ## 工作方式
109
116
 
110
- - **DeepSeek** — `POST {deepseekBaseURL}/messages`,请求头 `x-api-key` / `authorization: Bearer`,工具 `web_search_20250305`。
111
- - **纯 REST 类(Tavily / Brave / Serper / SerpApi / Exa / SearXNG)** — 由静态元数据表(`lib/providers.js`)+ 通用后端(`lib/rest.js`)驱动,无独立定制代码。请求方法 / 路径 / 查询字段名 / 鉴权(bearer / header / none / query)/ 固定参数 / 响应形态全部由表里的行决定。
117
+ - **DeepSeek** — `POST {deepseekBaseURL}/messages`,工具 `web_search_20250305`。会话运行在 DeepSeek 官方账号路由(provider 为 `deepseek-account`)且账号服务允许该端点时,用账号令牌发 `x-dsh-auth-token`;否则回落到 API key(`x-api-key` / `authorization: Bearer`,凭据名 `DEEPSEEK_API_KEY`)。有会话发起者时,在派发前把脱敏请求记录为会话事件 `web/deepseek-search-llm-request`。
118
+ - **纯 REST 类(Tavily / Brave / Serper / SerpApi / Exa / SearXNG / Scavio / Firecrawl)** — 由静态元数据表(`lib/providers.js`)+ 通用后端(`lib/rest.js`)驱动。请求方法 / 路径 / 查询字段名 / 鉴权(bearer / header / none / query)/ 固定参数 / 响应形态由表中的行决定。
112
119
  - **Tavily** — `POST {baseURL}/search`。keyless 发送 `x-tavily-access-mode: keyless`;keyed 发送 `authorization: Bearer <key>`,并带 `include_usage` 回传 credits。
113
120
  - **Brave** — `GET {braveBaseURL}?q=&count=`,请求头 `x-subscription-token`。不向 session 追加自定义事件。
114
- - **Serper / SerpApi / Exa / SearXNG** — 按各自表的 `method` / `auth` / `params` 约定请求。
121
+ - **其余六种 REST 引擎** — 按各自表的 `method` / `auth` / `params` 约定请求。
115
122
  - 非 2xx 映射为 `WEB_PROVIDER_ERROR`;调用方取消映射为 `WEB_ABORTED`。
116
123
 
117
124
  ## 仓库布局
118
125
 
119
126
  ```
120
- lib/index.js Host 插件:Config、按元数据分发提供方、设置段、额度路由
121
- lib/providers.js 静态元数据表:内置 REST provider 的请求/鉴权/响应/官方跳转(纯数据,无定制代码)
122
- lib/rest.js 通用 REST 后端:按元数据驱动请求构造与响应规范化(可选 hooks)
123
- lib/deepseek.js DeepSeek 后端(Anthropic Messages + web_search_20250305,模型工具型专用)
124
- lib/tavily.js Tavily 选项解析 / 响应映射(额度由 Tavily hook 回传)
125
- lib/brave.js Brave 选项解析 / 响应映射(解析 X-RateLimit-*)
127
+ lib/index.js Host 插件:volatile Config、按元数据分发提供方、额度路由
128
+ lib/providers.js 静态元数据表:8 个 REST provider(另加 deepseek.js 的 DeepSeek,共9 个后端)的请求/鉴权/响应/官方跳转(纯数据,无定制代码)
129
+ lib/rest.js 通用 REST 后端:按元数据驱动请求构造与响应规范化(含 Tavily / Brave 的额度与限流 hook)
130
+ lib/deepseek.js DeepSeek 后端(Anthropic Messages + web_search_20250305 + 账号令牌)
131
+ lib/tavily.js Tavily 专用后端与选项解析(当前分发使用 rest 通用后端)
132
+ lib/brave.js Brave 专用后端与选项解析(当前分发使用 rest 通用后端)
126
133
  lib/usage.js Tavily/Brave 用量本地缓存与 /usage 对账
127
134
  lib/shared.js 中止 / 凭据解析辅助
128
- lib/client.js 浏览器 bundle:顶层「网页搜索」分区 + 引擎切换 + 官方 key 跳转 + 额度条
135
+ lib/client.js 浏览器 bundle:设置分区 + 引擎切换 + 官方 key 跳转 + 额度条
136
+ locale/en.json 插件列表页 / 详情页的英文标题与描述
137
+ locale/zh.json 同上,中文
129
138
  cordis.patch.yml Bundle patch:插入 Host 行、设 searchProvider、禁用内置 deepseek
130
139
  ```
131
140
 
@@ -139,14 +148,11 @@ cordis.patch.yml Bundle patch:插入 Host 行、设 searchProvider、禁用
139
148
  dsh plugin --profile web add ".\dsh-web-search-plugin"
140
149
  ```
141
150
 
142
- **Windows 跨盘不要 `dsh plugin add` 绝对路径。** Profile 在 `C:`、仓库在 `D:` 时,它会写成 `link:d:/...`;pnpm 把盘符当成相对路径,junction 会指到 `profiles\web\D:\...` 并链坏。也不要写 `file:D:/...`:pnpm 10 同样会把跨盘绝对路径拼进 profile 目录。
143
-
144
- 正确做法是快照到与 profile **同盘**,再用 `file:`:
151
+ **Windows 跨盘不要 `dsh plugin add` 绝对路径。** Profile 与仓库不在同一盘时,它会写成 `link:` 或 `file:` 加绝对盘符;pnpm 会把盘符当成相对路径,junction 指向一个不存在的目录。做法是先把仓库快照到与 profile **同盘**,再用相对 `file:`:
145
152
 
146
153
  ```powershell
147
154
  $dst = "$env:USERPROFILE\.dsh\profiles\web\.local\dsh-web-search-plugin"
148
- New-Item -ItemType Directory -Force -Path $dst | Out-Null
149
- robocopy "D:\sample\dsh-web-search-plugin" $dst /E /XD node_modules .git .github /NFL /NDL /NJH /NJS
155
+ robocopy "<仓库路径>" $dst /E /XD node_modules .git .github .snapshots .workbuddy docs /NFL /NDL /NJH /NJS
150
156
  ```
151
157
 
152
158
  在 profile 的 `package.json` 里:
@@ -161,51 +167,49 @@ robocopy "D:\sample\dsh-web-search-plugin" $dst /E /XD node_modules .git .github
161
167
  dsh plugin --profile web install
162
168
  ```
163
169
 
164
- `file:` 是快照:改完仓库后要再 robocopy + `install` 并重启 DSH。
170
+ `file:` 是快照:改完仓库后要再 robocopy + `install` 并重启 DSH。改了 `package.json` 的 `files` 字段时,pnpm 可能认为快照未变化而跳过更新,需要先删掉 `node_modules/dsh-web-search-plugin` 再装。
165
171
 
166
172
  用 `dsh --profile web --dump-config` 确认:组成树里应有 `dsh-web-search-plugin` 行,且 `web.searchProvider` 为 `dsh-web-search`。
167
173
 
168
- > **关于 `file:`(本地)安装** — pnpm 会把 `file:` 依赖做成快照。改完本仓库后,需要再同步快照并 `dsh plugin --profile web install`,然后重启。Windows 上 profile 与仓库不在同一盘时,用同盘 `file:.local/...`,不要 `link:` / `file:` 指向另一块盘。
174
+ ### 语法检查
169
175
 
170
- ### 迁移旧后端包
176
+ ```powershell
177
+ npm run check
178
+ npm test
179
+ ```
171
180
 
172
- 若 profile 已经覆盖了 `web`(例如旧包 `@dsh-ltctfer/dsh-web-search-brave` 留下的 `brave-official`),后打的 patch 仍会生效。请改指向本插件,并卸掉旧包:
181
+ 客户端 bundle 必须保持 `window.__ModuleLoader__.load({ id, factory })` 线格式 — 由 `dsh-client-modules` 加载,不是打包器。必须同时导出 `apply` 和 `inject`(`slots`、`locale`、`remote`、`remote.credentials`、`configForms`)。
173
182
 
174
- ```yaml
175
- - id: web
176
- name: '@deepseek-ai/dsh-web'
177
- config:
178
- searchProvider: dsh-web-search
179
- ```
183
+ 配置卡片的宿主就是**设置 → 网页搜索**(`settings.section`,`id: "web-search"`),数据来自 `ctx.configForms.get("dsh-web-search-plugin")`,并通过 `configForms.whileServed([namespace], register)` 条件注册:Host 未下发该命名空间时不留痕迹。
180
184
 
181
- ```powershell
182
- dsh plugin --profile web remove @dsh-ltctfer/dsh-web-search-brave
183
- ```
185
+ 本插件不在插件详情页(`plugins.bundle.config`)显示配置。
184
186
 
185
- 然后在 **设置 → 网页搜索** 里把引擎切到想要的提供方,即可继续使用对应的 API key。
187
+ ## 本地化
186
188
 
187
- ### 语法检查
189
+ 插件列表页与详情页的标题和描述走 `locale/<lang>.json`,随界面语言切换:
188
190
 
189
- ```powershell
190
- npm run check
191
+ ```
192
+ locale/en.json {"meta": {"title": "Web search", "description": "…"}}
193
+ locale/zh.json {"meta": {"title": "网页搜索", "description": "…"}}
191
194
  ```
192
195
 
193
- 客户端 bundle 必须保持 `window.__ModuleLoader__.load({ id, factory })` 线格式 — 由 `dsh-client-modules` 加载,不是打包器。必须同时导出 `apply` 和 `inject`(`slots`、`locale`、`remote`、`settingsScope`),并把分区注册进 `settings.section`(`id: "web-search"`)。
196
+ 卡片内的文案由 `lib/client.js` 的 `zh` / `en` 两份字典提供,改动时须保持键名对齐。
194
197
 
195
198
  ## 发布
196
199
 
197
- 发布由 GitHub Actions(`.github/workflows/publish.yml`)自动完成:**推送 `v*` 标签**(例如 `v0.3.0`)会带 provenance 发到 npm。向 `main` 的普通推送不会发布。
200
+ 发布由 GitHub Actions(`.github/workflows/publish.yml`)自动完成:**推送与 `package.json` 版本一致的 `v*` 标签**会带 provenance 发到 npm。向 `main` 的普通推送不会发布。
198
201
 
199
- 1. 确认 `package.json` 的 `version` 与即将推送的标签一致(例如 `0.3.0` → `v0.3.0`)。
202
+ 1. 确认 `package.json` 的 `version` 与即将推送的标签一致,并在 `CHANGELOG.md` 中为该版本填写非空的 `## [版本号]` 章节。
200
203
  2. 在 GitHub **Settings → Secrets and variables → Actions** 里配置有 publish 权限的 npm automation token(仓库密钥 `NPM_TOKEN`),并允许 Actions 运行。
201
204
  3. 打标签并推送:
202
205
 
203
206
  ```powershell
204
- git tag v0.3.0
205
- git push origin v0.3.0
207
+ $version = node -p "require('./package.json').version"
208
+ git tag "v$version"
209
+ git push origin "v$version"
206
210
  ```
207
211
 
208
- 工作流会先核对标签与 `package.json` 的 `version`,再执行 `npm publish --provenance --access public`。
212
+ 工作流会先核对标签与 `package.json` 的 `version`,确认 `CHANGELOG.md` 中对应版本的发布说明非空,再执行 `npm publish --provenance --access public`。
209
213
 
210
214
  ## 参与贡献
211
215
 
package/cordis.patch.yml CHANGED
@@ -14,9 +14,10 @@
14
14
  searchProvider: dsh-web-search
15
15
  fetchProvider: http
16
16
 
17
- # This plugin re-hosts the DeepSeek backend, so the in-box DeepSeek host
18
- # plugin is redundant: disabling it removes its `deepseek-official` provider
19
- # (no duplicate) and hides the legacy "Plugins → web search" card.
17
+ # This bundle selects its own `dsh-web-search` seam provider and configures
18
+ # DeepSeek through that provider. Disable the in-box DeepSeek host plugin so
19
+ # the profile has one active DeepSeek configuration surface. Its provider id
20
+ # is `deepseek-official`, distinct from this bundle's `dsh-web-search` id.
20
21
  - id: web-search-deepseek
21
22
  disabled: true
22
23