dsh-web-search-plugin 0.3.0 → 0.4.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,30 @@
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
+ ## [0.4.0] - 2026-09-02
6
+
7
+ ### 变更
8
+
9
+ - **适配 DeepSeek Harness 最新 main(`0.1.2-alpha.4`)**:`@deepseek-ai/dsh-settings` 已移除顶层 `installSettingsSection` / `settingsNamespace`,Host 侧改为在 `ctx.inject(['settings'])` 后调用 `settingsCtx.settings.installSection(ctx, ...)`;settings namespace 直接使用普通字符串。
10
+ - **浏览器端移除对 `@deepseek-ai/dsh-client-runtime` / 旧 `connection` 的依赖**:该包已不在最新 Harness workspace / 客户端模块表中。`createSnapshotStore` 改从平台常驻模块 `@deepseek-ai/dsh-client-store` 导入;`dsh.client.inject` 与客户端 `inject` 对齐为 `slots` / `locale` / `remote` / `settingsScope`。
11
+ - **凭据远端接口迁移到 `ctx.remote`**:`credentials.describe(refs[])` / `set(ref, value)` 使用最新 Remote 参数与响应形状(`{ ok, value }`);凭据变更事件从 `credentials/updated` 改为 `credentials/reference-updated`;不再经旧 `connection.api` 调用。
12
+ - peerDependencies 中 dsh 包版本从 `0.1.0-rc.7` 提升到 `0.1.2-alpha.4`;`@deepseek-ai/cordis` / `@deepseek-ai/schemastery` 分别对齐 `^4.0.2` / `^3.18.2`;版本号升至 `0.4.0`,`User-Agent` 同步。
13
+
14
+ ## [0.3.1] - 2026-08-30
15
+
16
+ ### 修复
17
+
18
+ - **Tavily keyless 模式失效**:通用 REST 后端不再在 keyless 模式下强制解析 API key;无 key 时只发送 `x-tavily-access-mode: keyless`,可正常使用免费额度。
19
+ - **POST 类 provider 的 count 参数类型错误**:Tavily `max_results`、Serper `num`、Exa `num_results`、Firecrawl `limit` 等在 JSON body 中恢复为数字,不再被强转字符串。
20
+ - **Scavio / Firecrawl 设置卡严重卡死**:补齐浏览器端缺失的 `scavioApiKeyEnv` / `scavioBaseURL` / `firecrawlApiKeyEnv` / `firecrawlBaseURL` 表单字段与投影,修复切换后输入框无法输入、引擎无法切换的问题。
21
+ - **Scavio / Firecrawl 默认凭据名错误**:未显式填写 keyEnv 时分别回退到 `SCAVIO_API_KEY` / `FIRECRAWL_API_KEY`,不再错误使用 `EXA_API_KEY`。
22
+ - **Brave 环境代理回退丢失**:恢复 `HTTPS_PROXY` / `HTTP_PROXY` 环境变量回退,未显式配置 `proxy` 时也能按环境代理请求。
23
+ - `User-Agent` 更新为 `0.3.1`。
24
+
25
+ ### 优化
26
+
27
+ - 内置 REST 渠道(Serper / SerpApi / Exa / SearXNG / Scavio / Firecrawl)的 Base URL 默认值预填到设置卡,切换渠道后不再是空 URL。
28
+
5
29
  ## [0.3.0] - 2026-08-25
6
30
 
7
31
  ### 新增
@@ -84,7 +108,7 @@
84
108
 
85
109
  ### 修复
86
110
 
87
- - **客户端 bundle 缺少 `exports.inject`**(`lib/client.js`)。浏览器插件原先只导出 `apply`,cordis 的 `fiber.inject` 为空,访问 `ctx.locale` 等服务会抛 `cannot get property "locale" without inject`。现在导出 `inject = ["slots", "locale", "connection", "remote", "settingsScope"]`。
111
+ - **客户端 bundle 缺少 `exports.inject`**(`lib/client.js`)。浏览器插件原先只导出 `apply`,cordis 的 `fiber.inject` 为空,访问 `ctx.locale` 等服务会抛 `cannot get property "locale" without inject`。现在导出 `inject = ["slots", "locale", "remote", "settingsScope"]`。
88
112
  - **插件卡片的 slot 注册**(`lib/client.js`)。卡片按 rc.6 的 `id`/`order` 形状注册进 `settings.plugin.item`;该 slot 从 rc.6+ 起是 keyed 的,必须带 `key: "dsh-web-search-plugin"`。
89
113
  - **`CardForm.field("apiKey")` 崩溃**(`lib/client.js`)。只写凭据字段没有 section spec,对 `undefined` 调用 `spec.format(...)` 会在卡片投影时抛 `Cannot read properties of undefined (reading 'format')`。已加上 secret 字段分支(对齐官方 `CardForm`)。
90
114
  - **`dsh.client.inject` 补上 `@deepseek-ai/dsh-client-ui-slots`**(`package.json`),与社区插件的加载集合一致。
package/README.md CHANGED
@@ -17,19 +17,19 @@
17
17
 
18
18
  ## 特性
19
19
 
20
- - 与官方 `@deepseek-ai/dsh-web-search-deepseek` 相同的提供方约定:`inject: ['web']` + `installSettingsSection` + `ctx.web.registerSearchProvider`。
20
+ - 与官方 `@deepseek-ai/dsh-web-search-deepseek` 相同的提供方约定:`inject: ['web']` + `ctx.settings.installSection` + `ctx.web.registerSearchProvider`。
21
21
  - 顶层 **设置 → 网页搜索** 分区:两列布局、未保存草稿、保存 toast;结果数量为 1–20 下拉。
22
22
  - **默认引擎为 DeepSeek(官方)**:新装 / 未显式改动时,搜索直接走 `deepseek-official`,不再依赖 keyless 的 Tavily。
23
23
  - 纯 REST 后端由**静态元数据表 + 通用后端**驱动,新增 provider 即"表里加一行 + 官方跳转链接",无定制执行代码。
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` 通过 rc.7 的 `settings.describe()` 动态暴露,不需要宿主白名单补丁,也不需要自建回环 settings 桥。
27
+ - 设置段 `dsh-web-search-plugin` 通过 dsh 的 `settings.installSection` 注册、由 `settings.describe()` 动态暴露,不需要宿主白名单补丁,也不需要自建回环 settings 桥。
28
28
  - 错误映射为 `WEB_PROVIDER_ERROR` / `WEB_ABORTED` / `WEB_PROVIDER_CREDENTIAL_MISSING`。
29
29
 
30
30
  ## 运行要求
31
31
 
32
- - DeepSeek Harness `0.1.0-rc.7` 或更新
32
+ - DeepSeek Harness `0.1.2-alpha.4`(最新 main)或更新
33
33
  - pnpm,用于通过 `dsh plugin` 把插件装进 profile
34
34
 
35
35
  ## 安装
@@ -189,7 +189,7 @@ dsh plugin --profile web remove @dsh-ltctfer/dsh-web-search-brave
189
189
  npm run check
190
190
  ```
191
191
 
192
- 客户端 bundle 必须保持 `window.__ModuleLoader__.load({ id, factory })` 线格式 — 由 `dsh-client-modules` 加载,不是打包器。必须同时导出 `apply` 和 `inject`(`slots`、`locale`、`connection`、`remote`、`settingsScope`),并把分区注册进 `settings.section`(`id: "web-search"`)。
192
+ 客户端 bundle 必须保持 `window.__ModuleLoader__.load({ id, factory })` 线格式 — 由 `dsh-client-modules` 加载,不是打包器。必须同时导出 `apply` 和 `inject`(`slots`、`locale`、`remote`、`settingsScope`),并把分区注册进 `settings.section`(`id: "web-search"`)。
193
193
 
194
194
  ## 发布
195
195
 
package/lib/client.js CHANGED
@@ -21,18 +21,18 @@ window.__ModuleLoader__.load({
21
21
  var exports = module.exports;
22
22
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
23
23
  let react = require("react");
24
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
24
+ let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
25
25
 
26
26
  /** Settings namespace of this plugin. */
27
27
  const NS = "dsh-web-search-plugin";
28
28
  /** Built-in REST providers (mirrors `lib/providers.js`; browser bundle is self-contained). */
29
29
  const BUILTIN_REST_PROVIDERS = [
30
- { id: "serper", name: "Serper", officialUrl: "https://serper.dev/", keyRequired: true, keyField: "serperApiKey", keyEnvField: "serperApiKeyEnv", baseURLField: "serperBaseURL" },
31
- { id: "serpapi", name: "SerpApi", officialUrl: "https://serpapi.com/", keyRequired: true, keyField: "serpapiApiKey", keyEnvField: "serpapiApiKeyEnv", baseURLField: "serpapiBaseURL" },
32
- { id: "exa", name: "Exa", officialUrl: "https://dashboard.exa.ai/", keyRequired: true, keyField: "exaApiKey", keyEnvField: "exaApiKeyEnv", baseURLField: "exaBaseURL" },
33
- { id: "searxng", name: "SearXNG", officialUrl: "https://docs.searxng.org/", keyRequired: false, keyField: "", keyEnvField: "", baseURLField: "searxngBaseURL" },
34
- { id: "scavio", name: "Scavio", officialUrl: "https://scavio.dev/", keyRequired: true, keyField: "scavioApiKey", keyEnvField: "scavioApiKeyEnv", baseURLField: "scavioBaseURL" },
35
- { id: "firecrawl", name: "Firecrawl", officialUrl: "https://firecrawl.dev/", keyRequired: true, keyField: "firecrawlApiKey", keyEnvField: "firecrawlApiKeyEnv", baseURLField: "firecrawlBaseURL" }
30
+ { id: "serper", name: "Serper", officialUrl: "https://serper.dev/", defaultBaseURL: "https://google.serper.dev", keyRequired: true, keyField: "serperApiKey", keyEnvField: "serperApiKeyEnv", baseURLField: "serperBaseURL" },
31
+ { id: "serpapi", name: "SerpApi", officialUrl: "https://serpapi.com/", defaultBaseURL: "https://serpapi.com", keyRequired: true, keyField: "serpapiApiKey", keyEnvField: "serpapiApiKeyEnv", baseURLField: "serpapiBaseURL" },
32
+ { id: "exa", name: "Exa", officialUrl: "https://dashboard.exa.ai/", defaultBaseURL: "https://api.exa.ai", keyRequired: true, keyField: "exaApiKey", keyEnvField: "exaApiKeyEnv", baseURLField: "exaBaseURL" },
33
+ { id: "searxng", name: "SearXNG", officialUrl: "https://docs.searxng.org/", defaultBaseURL: "https://searx.be", keyRequired: false, keyField: "", keyEnvField: "", baseURLField: "searxngBaseURL" },
34
+ { id: "scavio", name: "Scavio", officialUrl: "https://scavio.dev/", defaultBaseURL: "https://api.scavio.dev", keyRequired: true, keyField: "scavioApiKey", keyEnvField: "scavioApiKeyEnv", baseURLField: "scavioBaseURL" },
35
+ { id: "firecrawl", name: "Firecrawl", officialUrl: "https://firecrawl.dev/", defaultBaseURL: "https://api.firecrawl.dev", keyRequired: true, keyField: "firecrawlApiKey", keyEnvField: "firecrawlApiKeyEnv", baseURLField: "firecrawlBaseURL" }
36
36
  ];
37
37
  /** Find a built-in REST provider by id. */
38
38
  function builtinRest(id) {
@@ -334,9 +334,9 @@ window.__ModuleLoader__.load({
334
334
  * spec and are written through the credentials domain.
335
335
  */
336
336
  var SearchCardForm = class {
337
- constructor(scope, api) {
337
+ constructor(scope, remote) {
338
338
  this.scope = scope;
339
- this.api = api;
339
+ this.remote = remote;
340
340
  this.specs = new Map([
341
341
  choiceField("provider"),
342
342
  choiceField("mode"),
@@ -364,7 +364,11 @@ window.__ModuleLoader__.load({
364
364
  textField("serpapiBaseURL"),
365
365
  textField("exaApiKeyEnv"),
366
366
  textField("exaBaseURL"),
367
- textField("searxngBaseURL")
367
+ textField("searxngBaseURL"),
368
+ textField("scavioApiKeyEnv"),
369
+ textField("scavioBaseURL"),
370
+ textField("firecrawlApiKeyEnv"),
371
+ textField("firecrawlBaseURL")
368
372
  ].map((spec) => [spec.field, spec]));
369
373
  this.staged = new Map();
370
374
  this.saving = false;
@@ -387,7 +391,7 @@ window.__ModuleLoader__.load({
387
391
  this.readCredential();
388
392
  }
389
393
  bind(project) {
390
- const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(project());
394
+ const store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(project());
391
395
  this.listeners.add(() => {
392
396
  store.set(project());
393
397
  });
@@ -577,7 +581,7 @@ window.__ModuleLoader__.load({
577
581
  restKeyRef(rest) {
578
582
  const declared = this.sectionValue(rest.keyEnvField);
579
583
  if (declared !== void 0 && declared.length > 0) return declared;
580
- return rest.id === "serper" ? "SERPER_API_KEY" : rest.id === "serpapi" ? "SERPAPI_API_KEY" : "EXA_API_KEY";
584
+ return rest.id === "serper" ? "SERPER_API_KEY" : rest.id === "serpapi" ? "SERPAPI_API_KEY" : rest.id === "exa" ? "EXA_API_KEY" : rest.id === "scavio" ? "SCAVIO_API_KEY" : "FIRECRAWL_API_KEY";
581
585
  }
582
586
  async readCredential() {
583
587
  const ref = this.refOf();
@@ -587,12 +591,12 @@ window.__ModuleLoader__.load({
587
591
  }
588
592
  let response;
589
593
  try {
590
- response = await this.api.credentials.describe({ refs: [ref] });
594
+ response = await this.remote.credentials.describe([ref]);
591
595
  } catch (_credentialReadFailure) {
592
596
  return;
593
597
  }
594
- if (!response.result.ok || ref !== this.refOf()) return;
595
- const view = response.result.value.credentials[ref];
598
+ if (!response.ok || ref !== this.refOf()) return;
599
+ const view = response.value[ref];
596
600
  const next = {
597
601
  ref,
598
602
  configured: view?.configured ?? false,
@@ -615,7 +619,8 @@ window.__ModuleLoader__.load({
615
619
  ref = rest !== void 0 ? this.restKeyRef(rest) : this.tavilyRef();
616
620
  }
617
621
  try {
618
- await this.api.credentials.set({ ref, value });
622
+ const response = await this.remote.credentials.set(ref, value);
623
+ if (!response.ok) return false;
619
624
  } catch (_credentialWriteFailure) {}
620
625
  await this.readCredential();
621
626
  return this.credential.ref === ref ? this.credential.configured : true;
@@ -624,10 +629,10 @@ window.__ModuleLoader__.load({
624
629
 
625
630
  /** Bridges the plugin scope and the credentials domain onto the card. */
626
631
  var SearchCardController = class {
627
- constructor(scope, api) {
632
+ constructor(scope, remote) {
628
633
  this.scope = scope;
629
- this.api = api;
630
- this.form = new SearchCardForm(scope, api);
634
+ this.remote = remote;
635
+ this.form = new SearchCardForm(scope, remote);
631
636
  this.store = this.form.bind(() => this.projection());
632
637
  scope.subscribe(() => {
633
638
  this.form.readCredential();
@@ -669,6 +674,12 @@ window.__ModuleLoader__.load({
669
674
  exaApiKeyEnv: this.form.field("exaApiKeyEnv"),
670
675
  exaBaseURL: this.form.field("exaBaseURL"),
671
676
  searxngBaseURL: this.form.field("searxngBaseURL"),
677
+ scavioApiKey: this.form.field("scavioApiKey"),
678
+ scavioApiKeyEnv: this.form.field("scavioApiKeyEnv"),
679
+ scavioBaseURL: this.form.field("scavioBaseURL"),
680
+ firecrawlApiKey: this.form.field("firecrawlApiKey"),
681
+ firecrawlApiKeyEnv: this.form.field("firecrawlApiKeyEnv"),
682
+ firecrawlBaseURL: this.form.field("firecrawlBaseURL"),
672
683
  apiKeyConfigured: this.form.credential.configured,
673
684
  apiKeyWritable: this.form.credential.writable,
674
685
  toastKey: this.form.toastKey
@@ -1075,7 +1086,7 @@ window.__ModuleLoader__.load({
1075
1086
  fieldRow(t("apiKey"), keyHint, secretInput({ id: "ws-" + restMeta.id + "-key", value: state[restMeta.keyField]?.text ?? "", disabled: !state.apiKeyWritable, placeholder: state.apiKeyConfigured ? t("apiKeySet") : t("apiKeyUnset"), onChange: (e) => actions.edit(restMeta.keyField, e.target.value) })),
1076
1087
  row(restMeta.keyEnvField, t("apiKeyEnv"), t("apiKeyEnvHint"), textInput({ id: "ws-" + restMeta.id + "-keyref", value: state[restMeta.keyEnvField]?.text ?? "", disabled, onChange: (e) => actions.edit(restMeta.keyEnvField, e.target.value) }))
1077
1088
  ) : null,
1078
- row(restMeta.baseURLField, t("baseURL"), t("baseURLHint"), textInput({ id: "ws-" + restMeta.id + "-base", value: state[restMeta.baseURLField]?.text ?? "", disabled, onChange: (e) => actions.edit(restMeta.baseURLField, e.target.value) })),
1089
+ row(restMeta.baseURLField, t("baseURL"), t("baseURLHint"), textInput({ id: "ws-" + restMeta.id + "-base", value: state[restMeta.baseURLField]?.text || restMeta.defaultBaseURL || "", disabled, onChange: (e) => actions.edit(restMeta.baseURLField, e.target.value) })),
1079
1090
  maxResultsRow
1080
1091
  ) : null,
1081
1092
  react.createElement("div", { style: { borderTop: "1px solid var(--dsw-alias-border-l2)", justifyContent: "flex-end", alignItems: "center", gap: "8px", padding: "12px 0 4px", display: "flex" } },
@@ -1140,11 +1151,10 @@ window.__ModuleLoader__.load({
1140
1151
 
1141
1152
  /** Mount the top-level "Web search" settings section. */
1142
1153
  function apply(ctx) {
1143
- const { api } = ctx.get("connection");
1144
1154
  const t = ctx.locale.bind(NS);
1145
1155
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-web-search-plugin: card dictionaries");
1146
- const controller = new SearchCardController(ctx.settingsScope.bind({ namespace: NS }), api);
1147
- ctx.effect(() => ctx.remote.$on("credentials/updated", (ref) => {
1156
+ const controller = new SearchCardController(ctx.settingsScope.bind({ namespace: NS }), ctx.remote);
1157
+ ctx.effect(() => ctx.remote.$on("credentials/reference-updated", (ref) => {
1148
1158
  controller.form.refreshCredential(ref);
1149
1159
  }), "dsh-web-search-plugin: credential invalidations");
1150
1160
  ctx.effect(() => usageStore.start(), "dsh-web-search-plugin: usage poll");
@@ -1158,7 +1168,7 @@ window.__ModuleLoader__.load({
1158
1168
  }, makeSection(ctx, controller)));
1159
1169
  }
1160
1170
 
1161
- const inject = ["slots", "locale", "connection", "remote", "settingsScope"];
1171
+ const inject = ["slots", "locale", "remote", "settingsScope"];
1162
1172
 
1163
1173
  exports.apply = apply;
1164
1174
  exports.inject = inject;
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@
17
17
  * @module dsh-web-search-plugin
18
18
  */
19
19
  import z from "@deepseek-ai/schemastery";
20
- import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
20
+ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
21
21
  import { MAX_RESULTS_CAP } from "./shared.js";
22
22
  import { TAVILY_DEFAULT_API_KEY_ENV, TAVILY_DEFAULT_BASE_URL, TavilySearchProvider, resolveTavilyOptions } from "./tavily.js";
23
23
  import { BRAVE_DEFAULT_API_KEY_ENV, BRAVE_DEFAULT_BASE_URL, BraveSearchProvider, resolveBraveOptions } from "./brave.js";
@@ -33,6 +33,8 @@ const SEARCH_PROVIDER_ID = "dsh-web-search";
33
33
  const name = "dsh-web-search-plugin";
34
34
  /** The web seam this provider registers into. */
35
35
  const inject = ["web"];
36
+ /** Settings namespace carrying this plugin's backend switch and options. */
37
+ const WEB_SEARCH_SETTINGS_NAMESPACE = "dsh-web-search-plugin";
36
38
 
37
39
  /** Plugin config (all optional — `apply` fills defaults). */
38
40
  const Config = z.object({
@@ -73,33 +75,33 @@ const Config = z.object({
73
75
  /** Credential reference for Serper search. */
74
76
  serperApiKeyEnv: z.string().role("credential-ref").default("SERPER_API_KEY"),
75
77
  /** Serper endpoint base. `/search` is appended. */
76
- serperBaseURL: z.string().default(""),
78
+ serperBaseURL: z.string().default("https://google.serper.dev"),
77
79
  /** Literal SerpApi API key; prefer {@link serpapiApiKeyEnv}. */
78
80
  serpapiApiKey: z.string().role("secret").default(""),
79
81
  /** Credential reference for SerpApi search. */
80
82
  serpapiApiKeyEnv: z.string().role("credential-ref").default("SERPAPI_API_KEY"),
81
83
  /** SerpApi endpoint base. `/search.json` is appended. */
82
- serpapiBaseURL: z.string().default(""),
84
+ serpapiBaseURL: z.string().default("https://serpapi.com"),
83
85
  /** Literal Exa API key; prefer {@link exaApiKeyEnv}. */
84
86
  exaApiKey: z.string().role("secret").default(""),
85
87
  /** Credential reference for Exa search. */
86
88
  exaApiKeyEnv: z.string().role("credential-ref").default("EXA_API_KEY"),
87
89
  /** Exa endpoint base. `/search` is appended. */
88
- exaBaseURL: z.string().default(""),
90
+ exaBaseURL: z.string().default("https://api.exa.ai"),
89
91
  /** Optional SearXNG instance base URL (self-hosted). `/search` is appended. */
90
- searxngBaseURL: z.string().default(""),
92
+ searxngBaseURL: z.string().default("https://searx.be"),
91
93
  /** Literal Scavio API key; prefer {@link scavioApiKeyEnv}. */
92
94
  scavioApiKey: z.string().role("secret").default(""),
93
95
  /** Credential reference for Scavio search. */
94
96
  scavioApiKeyEnv: z.string().role("credential-ref").default("SCAVIO_API_KEY"),
95
97
  /** Scavio endpoint base. `/api/v2/google` is appended. */
96
- scavioBaseURL: z.string().default(""),
98
+ scavioBaseURL: z.string().default("https://api.scavio.dev"),
97
99
  /** Literal Firecrawl API key; prefer {@link firecrawlApiKeyEnv}. */
98
100
  firecrawlApiKey: z.string().role("secret").default(""),
99
101
  /** Credential reference for Firecrawl search. */
100
102
  firecrawlApiKeyEnv: z.string().role("credential-ref").default("FIRECRAWL_API_KEY"),
101
103
  /** Firecrawl endpoint base. `/v2/search` is appended. */
102
- firecrawlBaseURL: z.string().default(""),
104
+ firecrawlBaseURL: z.string().default("https://api.firecrawl.dev"),
103
105
  /** Literal DeepSeek API key; prefer {@link deepseekApiKeyEnv}. */
104
106
  deepseekApiKey: z.string().role("secret").default(""),
105
107
  /** Credential reference for DeepSeek search. */
@@ -116,11 +118,6 @@ const Config = z.object({
116
118
  maxUses: z.number().step(1).min(1).default(5)
117
119
  });
118
120
 
119
- /** Settings namespace carrying this plugin's backend switch and options. */
120
- const WEB_SEARCH_SETTINGS_NAMESPACE = settingsNamespace("dsh-web-search-plugin");
121
- /** @deprecated Use {@link WEB_SEARCH_SETTINGS_NAMESPACE}. */
122
- const WEB_SEARCH_TAVILY_SETTINGS_NAMESPACE = WEB_SEARCH_SETTINGS_NAMESPACE;
123
-
124
121
  /**
125
122
  * One seam-facing provider whose `id` stays `dsh-web-search`. The active
126
123
  * backend is read from the live settings section on every `available` /
@@ -176,7 +173,7 @@ function resolveOptions(ctx, config) {
176
173
  // metadata row's default is not enough (self-hosted SearXNG, proxied
177
174
  // providers, custom key refs).
178
175
  const keySourceByProvider = {
179
- brave: { literal: config.braveApiKey, envName: config.braveApiKeyEnv ?? BRAVE_DEFAULT_API_KEY_ENV, baseURL: config.braveBaseURL ?? "", proxy: config.proxy },
176
+ brave: { literal: config.braveApiKey, envName: config.braveApiKeyEnv ?? BRAVE_DEFAULT_API_KEY_ENV, baseURL: config.braveBaseURL ?? "", proxy: config.proxy || launchEnvironmentOf(ctx).get("HTTPS_PROXY")?.value || launchEnvironmentOf(ctx).get("HTTP_PROXY")?.value || "" },
180
177
  tavily: { literal: config.apiKey, envName: config.apiKeyEnv ?? TAVILY_DEFAULT_API_KEY_ENV, baseURL: config.baseURL ?? "" },
181
178
  serper: { literal: config.serperApiKey, envName: config.serperApiKeyEnv ?? "SERPER_API_KEY", baseURL: config.serperBaseURL ?? "" },
182
179
  serpapi: { literal: config.serpapiApiKey, envName: config.serpapiApiKeyEnv ?? "SERPAPI_API_KEY", baseURL: config.serpapiBaseURL ?? "" },
@@ -253,11 +250,13 @@ function sendJson(res, statusCode, data) {
253
250
  /** Register the dispatched search provider with `ctx.web`. */
254
251
  function apply(ctx, config) {
255
252
  let current = () => config;
256
- installSettingsSection(ctx, WEB_SEARCH_SETTINGS_NAMESPACE, Config, config, {
257
- setSource: (source) => {
258
- current = source;
259
- },
260
- onChange: () => {}
253
+ ctx.inject(["settings"], (settingsCtx) => {
254
+ settingsCtx.settings.installSection(ctx, WEB_SEARCH_SETTINGS_NAMESPACE, Config, config, {
255
+ setSource: (source) => {
256
+ current = source;
257
+ },
258
+ onChange: () => {}
259
+ });
261
260
  });
262
261
  const live = () => resolveOptions(ctx, current());
263
262
  const usage = new UsageTracker({
@@ -334,7 +333,6 @@ export {
334
333
  TavilySearchProvider,
335
334
  UsageTracker,
336
335
  WEB_SEARCH_SETTINGS_NAMESPACE,
337
- WEB_SEARCH_TAVILY_SETTINGS_NAMESPACE,
338
336
  apply,
339
337
  findProvider,
340
338
  inject,
package/lib/rest.js CHANGED
@@ -188,7 +188,7 @@ export class RestSearchProvider {
188
188
  const query = {
189
189
  [queryParam]: request.query
190
190
  };
191
- if (countParam !== "") query[countParam] = String(count);
191
+ if (countParam !== "") query[countParam] = queryIn === "query" ? String(count) : count;
192
192
  const keyed = options.keyed === true;
193
193
  for (const param of options.params ?? []) {
194
194
  const resolved = resolveParam(param, { keyed, ...options.settings ?? {} });
@@ -295,8 +295,12 @@ export class RestSearchProvider {
295
295
  */
296
296
  export function resolveRestProvider(ctx, row, opts) {
297
297
  const { config, settings } = opts;
298
- const auth = row.auth ?? { type: "none" };
298
+ let auth = row.auth ?? { type: "none" };
299
299
  const keyed = opts.keyed === true;
300
+ // A provider can be keyless by default (e.g. Tavily keyless mode); when no
301
+ // key is expected, drop the auth template so the generic backend does not
302
+ // demand a credential before the request.
303
+ if (row.keyRequired === false && keyed !== true) auth = { type: "none" };
300
304
  const base = {
301
305
  baseURL: row.baseURL,
302
306
  method: row.method ?? "POST",
package/lib/shared.js CHANGED
@@ -7,7 +7,7 @@ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
7
7
  import { WebError } from "@deepseek-ai/dsh-web";
8
8
 
9
9
  /** Attribution header sent on every request. */
10
- export const USER_AGENT = "dsh-web-search-plugin/0.2.1";
10
+ export const USER_AGENT = "dsh-web-search-plugin/0.4.0";
11
11
  /** Shared cap: Tavily `max_results` and Brave `count` both accept 1..20. */
12
12
  export const MAX_RESULTS_CAP = 20;
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-web-search-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Unified web search for the DeepSeek Harness seam (ctx.web): DeepSeek (official), Tavily, Brave, Serper, SerpApi, Exa, SearXNG, Scavio, and Firecrawl backends",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -43,9 +43,7 @@
43
43
  "client": {
44
44
  "platform": "web",
45
45
  "inject": [
46
- "@deepseek-ai/dsh-client-connection",
47
46
  "@deepseek-ai/dsh-client-locale",
48
- "@deepseek-ai/dsh-client-runtime",
49
47
  "@deepseek-ai/dsh-client-ui-settings",
50
48
  "@deepseek-ai/dsh-client-ui-slots",
51
49
  "@deepseek-ai/dsh-api-remotes"
@@ -59,12 +57,12 @@
59
57
  "undici": "^6.21.0"
60
58
  },
61
59
  "peerDependencies": {
62
- "@deepseek-ai/dsh-web": "^0.1.0-rc.7",
63
- "@deepseek-ai/dsh-credentials": "^0.1.0-rc.7",
64
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
65
- "@deepseek-ai/dsh-launch-environment": "^0.1.0-rc.7",
66
- "@deepseek-ai/schemastery": "^3.18.1",
67
- "@deepseek-ai/cordis": "^4.0.1"
60
+ "@deepseek-ai/dsh-web": "^0.1.2-alpha.4",
61
+ "@deepseek-ai/dsh-credentials": "^0.1.2-alpha.4",
62
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.4",
63
+ "@deepseek-ai/dsh-launch-environment": "^0.1.2-alpha.4",
64
+ "@deepseek-ai/schemastery": "^3.18.2",
65
+ "@deepseek-ai/cordis": "^4.0.2"
68
66
  },
69
67
  "license": "MIT",
70
68
  "repository": {