dsh-web-search-enhanced 0.1.9 → 0.1.10

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
@@ -4,6 +4,35 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.10] - 2026-10-19
8
+
9
+ ### 新增
10
+
11
+ - Loader 配置 `providerId`(默认 `deepseek-official`):与 dsh-web-fetch-enhanced 一样,可改用其他 ID,与重新启用的原生 DeepSeek 搜索共存,见[配置参考](docs/configuration.md#provider-id-与原生搜索共存)。它只能在 Profile 中修改,修改后插件重新挂载;设置页不显示也不改动它。
12
+ - 设置页在 `web_search` 不经过本插件时给出提示:`web` 条目没有指定 `searchProvider`(会自动选择,可能报 `WEB_PROVIDER_AMBIGUOUS`)、被 DSH 自带「网页搜索」页面的开关改为 DeepSeek Search API,或指向其他 provider。判断方式与 DSH 相同,也考虑 `DSH_WEB_SEARCH_PROVIDER`。
13
+ - `searchConnections/provider` Remote 方法:返回本插件注册的 provider ID 和 `DSH_WEB_SEARCH_PROVIDER`,供设置页判断。
14
+
15
+ ### 修复
16
+
17
+ - 与 dsh-web-fetch-enhanced 等抓取插件同时安装时 `web_fetch` 无法正常使用:安装补丁不再改写共享的 `web` 条目。旧补丁只写 `searchProvider`,补丁整体替换条目配置,`fetchProvider: http` 随之丢失,抓取落入自动选择。
18
+ - 设置页把某项改回默认值后,Profile 中会留下空的覆盖对象(例如关掉再打开一个服务后的 `builtin:tavily: {}`);现在一并删除。
19
+
20
+ ### 开发
21
+
22
+ - 新增端到端测试 `pnpm run test:e2e`(迁移自 dsh-web-fetch-enhanced):在隔离的真实 DSH 中与 dsh-web-fetch-enhanced 一起安装,验证合成结果、Host 路由、共存配置和 Web 界面。见[开发指南](docs/development.md#端到端测试)。
23
+
24
+ ### 变更
25
+
26
+ - 搜索 provider 的 ID 由 `enhanced-search` 改为原生 DeepSeek 搜索的 `deepseek-official`,由 base profile 的 `web.searchProvider` 直接选中。安装补丁只停用 `web-search-deepseek` 并插入本插件条目,与 dsh-web-fetch-enhanced 的做法一致。
27
+ - 不再停用 `web-deepseek-api`:两个选择都已固定,它的 provider 只在用户显式选择时使用。
28
+ - 如果在 Profile 中手写过 `searchProvider: enhanced-search`,或设置了 `DSH_WEB_SEARCH_PROVIDER=enhanced-search`,升级后会报 `WEB_PROVIDER_CONFIGURED_MISSING`,删除这条覆盖即可。
29
+
30
+ ### 升级说明
31
+
32
+ - DSH 要求不变(`>=0.2.1-rc.2 <0.3.0`),设置格式仍为 version 2,会话存储不变,无需迁移。卸载旧插件后安装 0.1.10 并刷新 Web 页面;设置、密钥和会话选择都会保留。
33
+ - 用 `link:` 安装的开发环境需要重启 DSH:热更新会替换插件代码,但不会重新读取链接包的安装补丁。
34
+ - 如果以前改过 DSH 内置「网页搜索」页面的开关,用户层 `web` 条目中残留的 `deepseek-search-api` / `deepseek-fetch-api` 现在会让搜索和抓取改走 DeepSeek Search / Fetch API(0.1.9 时是报错);设置页会提示,删除这两个字段即可。详见[升级指南](docs/upgrading.md)。
35
+
7
36
  ## [0.1.9] - 2026-10-19
8
37
 
9
38
  ### 宿主兼容性
package/README.md CHANGED
@@ -50,13 +50,13 @@ dsh-web-search-enhanced 是 [DeepSeek Harness (DSH)](https://github.com/deepseek
50
50
  从 npm 安装:
51
51
 
52
52
  ```sh
53
- dsh plugin --profile web add dsh-web-search-enhanced@0.1.9
53
+ dsh plugin --profile web add dsh-web-search-enhanced@0.1.10
54
54
  ```
55
55
 
56
56
  或安装本地构建的包(例如用 `pnpm pack` 生成),路径需为绝对路径:
57
57
 
58
58
  ```sh
59
- dsh plugin --profile web add /absolute/path/to/dsh-web-search-enhanced-0.1.9.tgz
59
+ dsh plugin --profile web add /absolute/path/to/dsh-web-search-enhanced-0.1.10.tgz
60
60
  ```
61
61
 
62
62
  也可以在 Web 界面的 **插件** 页安装第三方插件时填入包名 `dsh-web-search-enhanced`。安装后刷新 Web 页面。
@@ -124,11 +124,17 @@ DSH 0.2.1 起,凭据存储中的值优先于环境变量。如果你习惯用
124
124
 
125
125
  输入栏空间不足时,按钮先隐藏实时性,再隐藏连接名,最后只保留地球图标,点开后的菜单不变。也可以随时用 `/setup-search` 切换。按钮只有在设置中关闭「在输入框显示搜索按钮」时才会完全隐藏。
126
126
 
127
- ### 内置的“网页搜索”设置页不见了,搜索报 WEB_PROVIDER_CONFIGURED_MISSING
127
+ ### 能和 dsh-web-fetch-enhanced 一起安装吗?
128
128
 
129
- 本插件的安装补丁停用了 DSH 自带的 `web-search-deepseek` 和 `web-deepseek-api`,所以 **插件 → 内置插件配置 → 网页搜索** 页面不再显示。DeepSeek 搜索可以改用本插件的 DeepSeek 连接。
129
+ 可以,两种安装顺序都行。两个插件各自占用 DSH 原生 provider 的 ID(本插件是搜索的 `deepseek-official`,dsh-web-fetch-enhanced 是抓取的 `http`),只停用被替换的原生条目,都不改动共享的 `web` 条目。用 `dsh --profile web --dump-config` 检查时,`web` 条目应保持 `searchProvider: deepseek-official` 和 `fetchProvider: http`。
130
130
 
131
- 如果以前在那个页面打开或关闭过「启用新版 Web 搜索 API」,用户配置的 `web` 条目中会残留 `searchProvider` / `fetchProvider`,指向已停用的 provider,于是搜索或抓取报 `WEB_PROVIDER_CONFIGURED_MISSING`。用 `dsh --profile web --dump-config` 查看 `web` 条目,删除用户层中的这两个字段即可。
131
+ ### 内置的“网页搜索”设置页不见了,或搜索没有使用所选连接
132
+
133
+ 本插件替换了 DSH 自带的 DeepSeek 搜索(`web-search-deepseek`),所以 **插件 → 内置插件配置 → 网页搜索** 页面不再显示。DeepSeek 搜索可以改用本插件的 DeepSeek 连接;确实要保留原生搜索时,见[配置参考的共存说明](docs/configuration.md#provider-id-与原生搜索共存)。
134
+
135
+ `web_search` 没有经过本插件时,插件设置页顶部会提示原因和改法。
136
+
137
+ 如果以前在那个页面打开过「启用新版 Web 搜索 API」,用户配置的 `web` 条目中会残留 `searchProvider: deepseek-search-api` 和 `fetchProvider: deepseek-fetch-api`:搜索和抓取都由 DeepSeek Search / Fetch API 完成,本插件和抓取插件都不参与,输入框中选择的连接不生效。用 `dsh --profile web --dump-config` 查看 `web` 条目,删除用户层中的这两个字段即可。
132
138
 
133
139
  ### 跟随会话模型报 WEB_SEARCH_FOLLOW_UNSUPPORTED
134
140
 
@@ -139,7 +145,7 @@ DSH 0.2.1 起,凭据存储中的值优先于环境变量。如果你习惯用
139
145
  | 文档 | 内容 |
140
146
  | --- | --- |
141
147
  | [配置参考](docs/configuration.md) | 所有字段、内置连接、参数白名单、自定义连接、跟随模型规则、错误码 |
142
- | [升级指南](docs/upgrading.md) | 版本对应关系、升级到 0.1.9 的变化、旧配置迁移、回退 |
148
+ | [升级指南](docs/upgrading.md) | 版本对应关系、升级到 0.1.10 的变化、旧配置迁移、回退 |
143
149
  | [架构](docs/architecture.md) | 模块划分、请求流程、如何新增搜索服务 |
144
150
  | [开发](docs/development.md) | 本地开发、测试、在真实 DSH 中验证、发布 |
145
151
  | [更新日志](CHANGELOG.md) | 各版本的变化 |
package/cordis.patch.yml CHANGED
@@ -1,26 +1,22 @@
1
- # Bundle patch layered over the shipped base profile.
1
+ # Bundle patch layered over the shipped base profile: take the native
2
+ # web_search provider's seat in place.
2
3
  #
3
- # A patch replaces the whole `config` of the row it targets, so this `web` row
4
- # keeps only `searchProvider`; `fetchProvider` falls back to auto-selection of
5
- # the single usable fetch provider. That holds only while no other always-usable
6
- # provider is mounted, hence the two rows disabled below. Fetch plugins that
7
- # leave the `web` row alone (e.g. dsh-web-fetch-enhanced) compose with this one.
8
- - id: web
9
- config:
10
- searchProvider: enhanced-search
11
-
12
- # DeepSeek Messages search (`deepseek-official`). Its role is covered by this
13
- # plugin's model connections; keeping it would also show the built-in Web search
14
- # settings page whose switch rewrites `web` in the user layer.
4
+ # The plugin registers its provider as `deepseek-official`, the id the shipped
5
+ # `web` row already selects with `searchProvider: deepseek-official`, so the
6
+ # shared `web` row is left untouched. A patch replaces a row's whole config:
7
+ # rewriting `searchProvider` alone would drop `fetchProvider: http` and leave
8
+ # web_fetch to auto-selection, breaking fetch plugins such as
9
+ # dsh-web-fetch-enhanced that rely on the shipped selection.
10
+ #
11
+ # DeepSeek Messages search registers the same id, so its row is disabled. Its
12
+ # role is covered by this plugin's DeepSeek and model connections; disabling it
13
+ # also hides the built-in Web search settings page, whose switch rewrites `web`
14
+ # in the user layer. `web-deepseek-api` stays mounted: both selections remain
15
+ # pinned, so its providers are reached only when a user selects them, and its
16
+ # `deepseek-fetch-api` belongs to web_fetch, not to this plugin.
15
17
  - id: web-search-deepseek
16
18
  disabled: true
17
19
 
18
- # DeepSeek Search / Fetch API (`deepseek-search-api`, `deepseek-fetch-api`). Both
19
- # report available() even without a key, which makes fetch auto-selection
20
- # ambiguous. DeepSeek search stays reachable as the `builtin:deepseek` connection.
21
- - id: web-deepseek-api
22
- disabled: true
23
-
24
20
  - insert:
25
21
  - id: web-search-enhanced
26
22
  name: dsh-web-search-enhanced
package/lib/client.js CHANGED
@@ -391,6 +391,14 @@ window.__ModuleLoader__.load({
391
391
  }).strict(),
392
392
  connections: zod.z.array(connectionView)
393
393
  }).strict();
394
+ const providerRequest = zod.z.object({}).strict();
395
+ /** Facts only the Host knows about how web_search reaches this plugin. */
396
+ const providerView = zod.z.object({
397
+ /** Id this plugin registered as a web_search provider. */
398
+ providerId: zod.z.string(),
399
+ /** `$DSH_WEB_SEARCH_PROVIDER`, which selects the provider while the `web` row names none. */
400
+ envProvider: zod.z.string().nullable()
401
+ }).strict();
394
402
  const strict = (typeSymbol, create) => ({
395
403
  mode: "strict",
396
404
  typeSymbol,
@@ -417,6 +425,12 @@ window.__ModuleLoader__.load({
417
425
  invocation: { kind: "direct" },
418
426
  parameters: [request("SearchSelectionChange", () => selectionChange)],
419
427
  result: strict("SearchSelectionView", () => selectionView)
428
+ },
429
+ "searchConnections/provider": {
430
+ service: "searchConnections",
431
+ invocation: { kind: "direct" },
432
+ parameters: [request("SearchProviderRequest", () => providerRequest)],
433
+ result: strict("SearchProviderView", () => providerView)
420
434
  }
421
435
  }
422
436
  }, zod.z);
@@ -683,6 +697,9 @@ window.__ModuleLoader__.load({
683
697
  "settings.unsupported": "These settings were written by a newer plugin version or are malformed. Upgrade the plugin before editing them here.",
684
698
  "settings.refused": "Not saved. The settings may have changed elsewhere; check them and try again.",
685
699
  "settings.failed": "Not saved. Check the connection to DSH and try again.",
700
+ "route.auto": "The Profile’s web row names no searchProvider, so web_search picks among all usable providers and fails when there are several; nothing on this page takes effect. Set searchProvider: {id} on the web row and keep its fetchProvider.",
701
+ "route.other": "web_search is served by the provider “{provider}”, not by this plugin ({id}), so this page and each session’s search choice have no effect. Set searchProvider: {id} on the Profile’s web row.",
702
+ "route.deepseekApi": "web_search is served by the DeepSeek Search API, a setting left by the switch on DSH’s built-in Web search page, so this page and each session’s search choice have no effect. Set searchProvider on the Profile’s web row back to {id}; the switch also set fetchProvider to deepseek-fetch-api, so change that back to http if needed.",
686
703
  "defaults.title": "Defaults",
687
704
  "defaults.connection": "Default connection",
688
705
  "defaults.connectionHint": "For new sessions. If the session model was used with a connection before, that connection is used instead. When not set, the only ready connection is used, or search starts off.",
@@ -889,6 +906,9 @@ window.__ModuleLoader__.load({
889
906
  "settings.unsupported": "设置由更新版本的插件写入或格式有误。请先升级插件,再在此修改。",
890
907
  "settings.refused": "未保存:设置可能已在别处更改,请检查后重试。",
891
908
  "settings.failed": "未保存,请检查与 DSH 的连接后重试。",
909
+ "route.auto": "Profile 的 web 条目没有指定 searchProvider,web_search 会在所有可用的 provider 中自动选择,存在多个时会失败,本页设置也不会生效。请在 web 条目中写明 searchProvider: {id},并保留 fetchProvider。",
910
+ "route.other": "web_search 当前由 provider「{provider}」提供,而不是本插件({id}),本页设置和各会话的搜索选择都不会生效。请把 Profile 中 web 条目的 searchProvider 设为 {id}。",
911
+ "route.deepseekApi": "web_search 当前由 DeepSeek Search API 提供,这是 DSH 内置「网页搜索」页面的开关留下的设置,本页设置和各会话的搜索选择都不会生效。请把 Profile 中 web 条目的 searchProvider 改回 {id};开关同时把 fetchProvider 改成了 deepseek-fetch-api,需要时一并改回 http。",
892
912
  "defaults.title": "默认值",
893
913
  "defaults.connection": "默认连接",
894
914
  "defaults.connectionHint": "用于新会话。若当前模型此前搭配过某个连接,则优先使用该连接;未设置时,只有一个可用连接则使用它,否则默认关闭搜索。",
@@ -1207,36 +1227,36 @@ window.__ModuleLoader__.load({
1207
1227
  document.head.appendChild(style);
1208
1228
  }
1209
1229
  var SearchSelector_module_css_default = {
1210
- "attention": "jewuqq_attention",
1211
- "warningText": "jewuqq_warningText",
1212
- "caption": "jewuqq_caption",
1213
- "freshness": "jewuqq_freshness",
1230
+ "status": "jewuqq_status",
1231
+ "warningAction": "jewuqq_warningAction",
1232
+ "triggerCaption": "jewuqq_triggerCaption",
1214
1233
  "chevron": "jewuqq_chevron",
1215
- "manage": "jewuqq_manage",
1216
- "captionWarn": "jewuqq_captionWarn",
1234
+ "warning": "jewuqq_warning",
1235
+ "warningText": "jewuqq_warningText",
1217
1236
  "warningIcon": "jewuqq_warningIcon",
1218
- "warningAction": "jewuqq_warningAction",
1219
1237
  "menu": "jewuqq_menu",
1220
- "rows": "jewuqq_rows",
1221
- "segments": "jewuqq_segments",
1222
- "sectionLabel": "jewuqq_sectionLabel",
1223
- "root": "jewuqq_root",
1238
+ "attention": "jewuqq_attention",
1224
1239
  "trigger": "jewuqq_trigger",
1225
- "status": "jewuqq_status",
1226
1240
  "option": "jewuqq_option",
1227
- "label": "jewuqq_label",
1228
- "detail": "jewuqq_detail",
1241
+ "manage": "jewuqq_manage",
1229
1242
  "hint": "jewuqq_hint",
1230
1243
  "icon": "jewuqq_icon",
1231
- "check": "jewuqq_check",
1232
- "busy": "jewuqq_busy",
1244
+ "segments": "jewuqq_segments",
1245
+ "detail": "jewuqq_detail",
1233
1246
  "separator": "jewuqq_separator",
1234
- "triggerCaption": "jewuqq_triggerCaption",
1235
- "chevronOpen": "jewuqq_chevronOpen",
1247
+ "rows": "jewuqq_rows",
1248
+ "footer": "jewuqq_footer",
1236
1249
  "optionLabel": "jewuqq_optionLabel",
1250
+ "captionWarn": "jewuqq_captionWarn",
1251
+ "freshness": "jewuqq_freshness",
1252
+ "sectionLabel": "jewuqq_sectionLabel",
1253
+ "root": "jewuqq_root",
1254
+ "caption": "jewuqq_caption",
1255
+ "chevronOpen": "jewuqq_chevronOpen",
1256
+ "check": "jewuqq_check",
1237
1257
  "error": "jewuqq_error",
1238
- "footer": "jewuqq_footer",
1239
- "warning": "jewuqq_warning"
1258
+ "label": "jewuqq_label",
1259
+ "busy": "jewuqq_busy"
1240
1260
  };
1241
1261
  //#endregion
1242
1262
  //#region src/client/selector/SearchSelector.tsx
@@ -1928,6 +1948,79 @@ window.__ModuleLoader__.load({
1928
1948
  }
1929
1949
  };
1930
1950
  //#endregion
1951
+ //#region src/client/settings/route.ts
1952
+ function searchRoute(web, host) {
1953
+ if (host === void 0 || web.status !== "ready") return { kind: "unknown" };
1954
+ const pinned = web.value?.searchProvider ?? host.envProvider;
1955
+ if (pinned === null) return {
1956
+ kind: "auto",
1957
+ providerId: host.providerId
1958
+ };
1959
+ return pinned === host.providerId ? { kind: "this" } : {
1960
+ kind: "other",
1961
+ provider: pinned,
1962
+ providerId: host.providerId
1963
+ };
1964
+ }
1965
+ var SearchRouteStatus = class {
1966
+ web;
1967
+ source;
1968
+ store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({ kind: "unknown" });
1969
+ host;
1970
+ reader;
1971
+ observers = 0;
1972
+ unwatch;
1973
+ epoch = 0;
1974
+ constructor(web) {
1975
+ this.web = web;
1976
+ this.source = {
1977
+ getSnapshot: () => this.store.getSnapshot(),
1978
+ subscribe: (listener) => {
1979
+ const unsubscribe = this.store.subscribe(listener);
1980
+ this.observers += 1;
1981
+ if (this.observers === 1) {
1982
+ this.unwatch = web.subscribe(() => {
1983
+ this.publish();
1984
+ });
1985
+ this.publish();
1986
+ this.refresh();
1987
+ }
1988
+ return () => {
1989
+ this.observers -= 1;
1990
+ if (this.observers === 0) this.unwatch?.();
1991
+ unsubscribe();
1992
+ };
1993
+ }
1994
+ };
1995
+ }
1996
+ /** Read the Host facts through a mounted Remote until the returned disposer runs. */
1997
+ attach(reader) {
1998
+ this.reader = reader;
1999
+ this.refresh();
2000
+ return () => {
2001
+ if (this.reader !== reader) return;
2002
+ this.reader = void 0;
2003
+ this.host = void 0;
2004
+ this.epoch += 1;
2005
+ this.publish();
2006
+ };
2007
+ }
2008
+ /** Ask the Host again when someone is looking. */
2009
+ refresh() {
2010
+ const reader = this.reader;
2011
+ if (reader === void 0 || this.observers === 0) return;
2012
+ const epoch = ++this.epoch;
2013
+ reader.provider({}).then((result) => {
2014
+ if (!result.ok || epoch !== this.epoch) return;
2015
+ this.host = result.value;
2016
+ this.publish();
2017
+ });
2018
+ }
2019
+ publish() {
2020
+ this.store.set(searchRoute(this.web.getSnapshot(), this.host));
2021
+ }
2022
+ };
2023
+ //#endregion
1931
2024
  //#region src/client/settings/connection-form.ts
1932
2025
  /**
1933
2026
  * The custom connection form and its conversion to a stored connection. Every
@@ -2078,66 +2171,66 @@ window.__ModuleLoader__.load({
2078
2171
  document.head.appendChild(style);
2079
2172
  }
2080
2173
  var Settings_module_css_default = {
2081
- "selectChevron": "_08985W_selectChevron",
2082
- "disclosureIcon": "_08985W_disclosureIcon",
2083
- "optionKey": "_08985W_optionKey",
2084
- "formGrid": "_08985W_formGrid",
2085
- "sectionTitle": "_08985W_sectionTitle",
2086
- "success": "_08985W_success",
2087
- "sectionHead": "_08985W_sectionHead",
2088
- "intro": "_08985W_intro",
2089
- "select": "_08985W_select",
2090
- "fieldError": "_08985W_fieldError",
2091
- "danger": "_08985W_danger",
2092
- "selectAnchor": "_08985W_selectAnchor",
2093
- "link": "_08985W_link",
2094
- "row": "_08985W_row",
2095
- "disclosure": "_08985W_disclosure",
2096
2174
  "actions": "_08985W_actions",
2097
- "prefixedInput": "_08985W_prefixedInput",
2098
- "reset": "_08985W_reset",
2099
- "panel": "_08985W_panel",
2100
- "field": "_08985W_field",
2175
+ "sectionTitle": "_08985W_sectionTitle",
2176
+ "rowMain": "_08985W_rowMain",
2177
+ "editorTitle": "_08985W_editorTitle",
2178
+ "editor": "_08985W_editor",
2179
+ "prefixed": "_08985W_prefixed",
2180
+ "prefixedDisabled": "_08985W_prefixedDisabled",
2101
2181
  "optionName": "_08985W_optionName",
2102
- "list": "_08985W_list",
2182
+ "selectAnchor": "_08985W_selectAnchor",
2183
+ "empty": "_08985W_empty",
2103
2184
  "status": "_08985W_status",
2104
- "settingText": "_08985W_settingText",
2105
- "fieldState": "_08985W_fieldState",
2185
+ "optionKey": "_08985W_optionKey",
2186
+ "section": "_08985W_section",
2187
+ "rowMeta": "_08985W_rowMeta",
2188
+ "danger": "_08985W_danger",
2106
2189
  "formField": "_08985W_formField",
2107
- "confirmRow": "_08985W_confirmRow",
2108
- "prefixedDisabled": "_08985W_prefixedDisabled",
2109
- "error": "_08985W_error",
2110
- "inline": "_08985W_inline",
2190
+ "reset": "_08985W_reset",
2191
+ "option": "_08985W_option",
2192
+ "row": "_08985W_row",
2111
2193
  "sectionActions": "_08985W_sectionActions",
2112
- "empty": "_08985W_empty",
2113
- "hint": "_08985W_hint",
2114
- "note": "_08985W_note",
2115
- "optionGrid": "_08985W_optionGrid",
2116
- "fieldLabel": "_08985W_fieldLabel",
2117
- "section": "_08985W_section",
2118
- "disclosureOpen": "_08985W_disclosureOpen",
2119
- "warning": "_08985W_warning",
2194
+ "page": "_08985W_page",
2195
+ "settingText": "_08985W_settingText",
2120
2196
  "input": "_08985W_input",
2121
- "rowMain": "_08985W_rowMain",
2197
+ "segmented": "_08985W_segmented",
2198
+ "settingRow": "_08985W_settingRow",
2199
+ "sectionHead": "_08985W_sectionHead",
2200
+ "fieldHead": "_08985W_fieldHead",
2201
+ "fieldError": "_08985W_fieldError",
2122
2202
  "formWide": "_08985W_formWide",
2123
- "editor": "_08985W_editor",
2124
- "option": "_08985W_option",
2125
2203
  "rowTitle": "_08985W_rowTitle",
2204
+ "disclosureOpen": "_08985W_disclosureOpen",
2205
+ "inline": "_08985W_inline",
2206
+ "disclosure": "_08985W_disclosure",
2126
2207
  "settingTitle": "_08985W_settingTitle",
2127
- "rowActions": "_08985W_rowActions",
2128
- "editorTitle": "_08985W_editorTitle",
2208
+ "confirmRow": "_08985W_confirmRow",
2209
+ "optionGrid": "_08985W_optionGrid",
2210
+ "rowLine": "_08985W_rowLine",
2211
+ "hint": "_08985W_hint",
2212
+ "note": "_08985W_note",
2213
+ "success": "_08985W_success",
2214
+ "prefixedInput": "_08985W_prefixedInput",
2215
+ "selectChevron": "_08985W_selectChevron",
2129
2216
  "selectValue": "_08985W_selectValue",
2130
- "selectCompact": "_08985W_selectCompact",
2131
- "prefixed": "_08985W_prefixed",
2132
- "settingRow": "_08985W_settingRow",
2133
- "page": "_08985W_page",
2134
- "rowMeta": "_08985W_rowMeta",
2217
+ "field": "_08985W_field",
2218
+ "fieldLabel": "_08985W_fieldLabel",
2135
2219
  "prefix": "_08985W_prefix",
2136
- "fieldHead": "_08985W_fieldHead",
2137
- "segmented": "_08985W_segmented",
2138
- "confirmText": "_08985W_confirmText",
2139
- "rowLine": "_08985W_rowLine",
2140
- "settingControl": "_08985W_settingControl"
2220
+ "select": "_08985W_select",
2221
+ "link": "_08985W_link",
2222
+ "formGrid": "_08985W_formGrid",
2223
+ "panel": "_08985W_panel",
2224
+ "fieldState": "_08985W_fieldState",
2225
+ "settingControl": "_08985W_settingControl",
2226
+ "warning": "_08985W_warning",
2227
+ "intro": "_08985W_intro",
2228
+ "selectCompact": "_08985W_selectCompact",
2229
+ "error": "_08985W_error",
2230
+ "disclosureIcon": "_08985W_disclosureIcon",
2231
+ "rowActions": "_08985W_rowActions",
2232
+ "list": "_08985W_list",
2233
+ "confirmText": "_08985W_confirmText"
2141
2234
  };
2142
2235
  //#endregion
2143
2236
  //#region src/client/settings/parts.tsx
@@ -2491,14 +2584,13 @@ window.__ModuleLoader__.load({
2491
2584
  */
2492
2585
  function sparseSettingOperations(layers, path, target, fallback) {
2493
2586
  assertCurrentVersion(layers);
2494
- const at = (root) => path.reduce((value, key) => objectOf(value)[key], root);
2495
- const inherited = at(layers.base) ?? fallback;
2496
- const user = at(layers.user);
2587
+ const inherited = valueAt(layers.base, path) ?? fallback;
2588
+ const user = valueAt(layers.user, path);
2497
2589
  const ops = [];
2498
2590
  if (target === void 0 || equal(target, inherited)) {
2499
2591
  if (user !== void 0) ops.push({
2500
2592
  op: "unset",
2501
- path: [...path]
2593
+ path: prunedPath(layers.user, path)
2502
2594
  });
2503
2595
  } else {
2504
2596
  const sparse = delta(target, inherited);
@@ -2516,6 +2608,18 @@ window.__ModuleLoader__.load({
2516
2608
  });
2517
2609
  return ops;
2518
2610
  }
2611
+ function valueAt(root, path) {
2612
+ return path.reduce((value, key) => objectOf(value)[key], root);
2613
+ }
2614
+ /**
2615
+ * The path to unset so no empty object stays behind: removing the only key of
2616
+ * a user-layer object removes that object instead, up to a top-level field.
2617
+ */
2618
+ function prunedPath(user, path) {
2619
+ let end = path.length;
2620
+ while (end > 1 && Object.keys(objectOf(valueAt(user, path.slice(0, end - 1)))).length === 1) end -= 1;
2621
+ return path.slice(0, end);
2622
+ }
2519
2623
  const FIELDS = [
2520
2624
  "version",
2521
2625
  "freshness",
@@ -3317,9 +3421,10 @@ window.__ModuleLoader__.load({
3317
3421
  * control writes immediately as a sparse operation against the revision the
3318
3422
  * page shows; keys go to DSH Credentials only.
3319
3423
  */
3320
- function SettingsPage({ useSettings, useCredentials, mutate, saveCredential, t }) {
3424
+ function SettingsPage({ useSettings, useCredentials, useRoute, mutate, saveCredential, t }) {
3321
3425
  const snapshot = useSettings((value) => value);
3322
3426
  const credentials = useCredentials((value) => value);
3427
+ const route = useRoute((value) => value);
3323
3428
  if (snapshot.status === "unavailable") return null;
3324
3429
  if (snapshot.value === void 0 || snapshot.revision === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3325
3430
  className: Settings_module_css_default.status,
@@ -3356,6 +3461,10 @@ window.__ModuleLoader__.load({
3356
3461
  role: "status",
3357
3462
  children: notice
3358
3463
  }),
3464
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteNotice, {
3465
+ route,
3466
+ t
3467
+ }),
3359
3468
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultsSection, { ...sections }),
3360
3469
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ServicesSection, {
3361
3470
  ...sections,
@@ -3372,6 +3481,30 @@ window.__ModuleLoader__.load({
3372
3481
  ]
3373
3482
  });
3374
3483
  }
3484
+ /** Provider id of the DeepSeek Search API, which the switch on DSH's own Web search page selects. */
3485
+ const DEEPSEEK_SEARCH_API = "deepseek-search-api";
3486
+ /** Says so when web_search does not reach this plugin, so nothing on this page takes effect. */
3487
+ function RouteNotice({ route, t }) {
3488
+ const text = routeNotice(route, t);
3489
+ return text === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3490
+ className: Settings_module_css_default.warning,
3491
+ role: "status",
3492
+ children: text
3493
+ });
3494
+ }
3495
+ function routeNotice(route, t) {
3496
+ switch (route.kind) {
3497
+ case "unknown":
3498
+ case "this": return;
3499
+ case "auto": return t("route.auto", { id: route.providerId });
3500
+ case "other":
3501
+ if (route.provider === DEEPSEEK_SEARCH_API) return t("route.deepseekApi", { id: route.providerId });
3502
+ return t("route.other", {
3503
+ provider: route.provider,
3504
+ id: route.providerId
3505
+ });
3506
+ }
3507
+ }
3375
3508
  function ModelSection({ doc, write, t }) {
3376
3509
  const id = (0, react.useId)();
3377
3510
  const [open, setOpen] = (0, react.useState)(false);
@@ -3525,6 +3658,8 @@ window.__ModuleLoader__.load({
3525
3658
  const ENTRY = "web-search-enhanced";
3526
3659
  /** The bundle's package name, which keys its Plugins page slots. */
3527
3660
  const PACKAGE = "dsh-web-search-enhanced";
3661
+ /** The shared `@deepseek-ai/dsh-web` row, as DSH's own Web search page also assumes. */
3662
+ const WEB_ENTRY = "web";
3528
3663
  async function apply(ctx) {
3529
3664
  ctx.effect(() => ctx.locale.register(NS, {
3530
3665
  zh,
@@ -3539,10 +3674,12 @@ window.__ModuleLoader__.load({
3539
3674
  ctx.effect(() => ctx.remote.$on("credentials/reference-updated", () => {
3540
3675
  credentials.refresh();
3541
3676
  }), "web-search: credential states");
3677
+ const route = new SearchRouteStatus(ctx.configForms.get(WEB_ENTRY));
3542
3678
  const page = {
3543
3679
  hooks: {
3544
3680
  settings,
3545
- credentials: credentials.source
3681
+ credentials: credentials.source,
3682
+ route: route.source
3546
3683
  },
3547
3684
  mutate: (ops, revision) => settings.mutate(ops, revision),
3548
3685
  saveCredential: (ref, value) => ctx.remote.credentials.set(ref, value)
@@ -3572,13 +3709,15 @@ window.__ModuleLoader__.load({
3572
3709
  const unmount = await ctx.remote.$mount(searchConnectionsRemote);
3573
3710
  ctx.effect(() => unmount, "web-search: searchConnections remote");
3574
3711
  ctx.inject(["remote.searchConnections"], (scope) => {
3575
- mountSession(scope, settings, navigation, t);
3712
+ mountSession(scope, settings, route, navigation, t);
3576
3713
  });
3577
3714
  }
3578
- function mountSession(ctx, settings, navigation, t) {
3715
+ function mountSession(ctx, settings, route, navigation, t) {
3579
3716
  const selections = new SearchSelections(ctx.remote.searchConnections);
3717
+ ctx.effect(() => route.attach(ctx.remote.searchConnections), "web-search: provider facts");
3580
3718
  const invalidate = () => {
3581
3719
  selections.invalidate();
3720
+ route.refresh();
3582
3721
  };
3583
3722
  ctx.effect(() => ctx.remote.$on("settings/document-updated", invalidate), "web-search: settings invalidation");
3584
3723
  ctx.effect(() => ctx.remote.$on("credentials/reference-updated", invalidate), "web-search: credential invalidation");