dsh-web-search-plugin 0.3.0 → 0.3.1

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,21 @@
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.3.1] - 2026-08-30
6
+
7
+ ### 修复
8
+
9
+ - **Tavily keyless 模式失效**:通用 REST 后端不再在 keyless 模式下强制解析 API key;无 key 时只发送 `x-tavily-access-mode: keyless`,可正常使用免费额度。
10
+ - **POST 类 provider 的 count 参数类型错误**:Tavily `max_results`、Serper `num`、Exa `num_results`、Firecrawl `limit` 等在 JSON body 中恢复为数字,不再被强转字符串。
11
+ - **Scavio / Firecrawl 设置卡严重卡死**:补齐浏览器端缺失的 `scavioApiKeyEnv` / `scavioBaseURL` / `firecrawlApiKeyEnv` / `firecrawlBaseURL` 表单字段与投影,修复切换后输入框无法输入、引擎无法切换的问题。
12
+ - **Scavio / Firecrawl 默认凭据名错误**:未显式填写 keyEnv 时分别回退到 `SCAVIO_API_KEY` / `FIRECRAWL_API_KEY`,不再错误使用 `EXA_API_KEY`。
13
+ - **Brave 环境代理回退丢失**:恢复 `HTTPS_PROXY` / `HTTP_PROXY` 环境变量回退,未显式配置 `proxy` 时也能按环境代理请求。
14
+ - `User-Agent` 更新为 `0.3.1`。
15
+
16
+ ### 优化
17
+
18
+ - 内置 REST 渠道(Serper / SerpApi / Exa / SearXNG / Scavio / Firecrawl)的 Base URL 默认值预填到设置卡,切换渠道后不再是空 URL。
19
+
5
20
  ## [0.3.0] - 2026-08-25
6
21
 
7
22
  ### 新增
package/lib/client.js CHANGED
@@ -27,12 +27,12 @@ window.__ModuleLoader__.load({
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) {
@@ -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;
@@ -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();
@@ -669,6 +673,12 @@ window.__ModuleLoader__.load({
669
673
  exaApiKeyEnv: this.form.field("exaApiKeyEnv"),
670
674
  exaBaseURL: this.form.field("exaBaseURL"),
671
675
  searxngBaseURL: this.form.field("searxngBaseURL"),
676
+ scavioApiKey: this.form.field("scavioApiKey"),
677
+ scavioApiKeyEnv: this.form.field("scavioApiKeyEnv"),
678
+ scavioBaseURL: this.form.field("scavioBaseURL"),
679
+ firecrawlApiKey: this.form.field("firecrawlApiKey"),
680
+ firecrawlApiKeyEnv: this.form.field("firecrawlApiKeyEnv"),
681
+ firecrawlBaseURL: this.form.field("firecrawlBaseURL"),
672
682
  apiKeyConfigured: this.form.credential.configured,
673
683
  apiKeyWritable: this.form.credential.writable,
674
684
  toastKey: this.form.toastKey
@@ -1075,7 +1085,7 @@ window.__ModuleLoader__.load({
1075
1085
  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
1086
  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
1087
  ) : 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) })),
1088
+ 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
1089
  maxResultsRow
1080
1090
  ) : null,
1081
1091
  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" } },
package/lib/index.js CHANGED
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import z from "@deepseek-ai/schemastery";
20
20
  import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
21
+ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
21
22
  import { MAX_RESULTS_CAP } from "./shared.js";
22
23
  import { TAVILY_DEFAULT_API_KEY_ENV, TAVILY_DEFAULT_BASE_URL, TavilySearchProvider, resolveTavilyOptions } from "./tavily.js";
23
24
  import { BRAVE_DEFAULT_API_KEY_ENV, BRAVE_DEFAULT_BASE_URL, BraveSearchProvider, resolveBraveOptions } from "./brave.js";
@@ -73,33 +74,33 @@ const Config = z.object({
73
74
  /** Credential reference for Serper search. */
74
75
  serperApiKeyEnv: z.string().role("credential-ref").default("SERPER_API_KEY"),
75
76
  /** Serper endpoint base. `/search` is appended. */
76
- serperBaseURL: z.string().default(""),
77
+ serperBaseURL: z.string().default("https://google.serper.dev"),
77
78
  /** Literal SerpApi API key; prefer {@link serpapiApiKeyEnv}. */
78
79
  serpapiApiKey: z.string().role("secret").default(""),
79
80
  /** Credential reference for SerpApi search. */
80
81
  serpapiApiKeyEnv: z.string().role("credential-ref").default("SERPAPI_API_KEY"),
81
82
  /** SerpApi endpoint base. `/search.json` is appended. */
82
- serpapiBaseURL: z.string().default(""),
83
+ serpapiBaseURL: z.string().default("https://serpapi.com"),
83
84
  /** Literal Exa API key; prefer {@link exaApiKeyEnv}. */
84
85
  exaApiKey: z.string().role("secret").default(""),
85
86
  /** Credential reference for Exa search. */
86
87
  exaApiKeyEnv: z.string().role("credential-ref").default("EXA_API_KEY"),
87
88
  /** Exa endpoint base. `/search` is appended. */
88
- exaBaseURL: z.string().default(""),
89
+ exaBaseURL: z.string().default("https://api.exa.ai"),
89
90
  /** Optional SearXNG instance base URL (self-hosted). `/search` is appended. */
90
- searxngBaseURL: z.string().default(""),
91
+ searxngBaseURL: z.string().default("https://searx.be"),
91
92
  /** Literal Scavio API key; prefer {@link scavioApiKeyEnv}. */
92
93
  scavioApiKey: z.string().role("secret").default(""),
93
94
  /** Credential reference for Scavio search. */
94
95
  scavioApiKeyEnv: z.string().role("credential-ref").default("SCAVIO_API_KEY"),
95
96
  /** Scavio endpoint base. `/api/v2/google` is appended. */
96
- scavioBaseURL: z.string().default(""),
97
+ scavioBaseURL: z.string().default("https://api.scavio.dev"),
97
98
  /** Literal Firecrawl API key; prefer {@link firecrawlApiKeyEnv}. */
98
99
  firecrawlApiKey: z.string().role("secret").default(""),
99
100
  /** Credential reference for Firecrawl search. */
100
101
  firecrawlApiKeyEnv: z.string().role("credential-ref").default("FIRECRAWL_API_KEY"),
101
102
  /** Firecrawl endpoint base. `/v2/search` is appended. */
102
- firecrawlBaseURL: z.string().default(""),
103
+ firecrawlBaseURL: z.string().default("https://api.firecrawl.dev"),
103
104
  /** Literal DeepSeek API key; prefer {@link deepseekApiKeyEnv}. */
104
105
  deepseekApiKey: z.string().role("secret").default(""),
105
106
  /** Credential reference for DeepSeek search. */
@@ -176,7 +177,7 @@ function resolveOptions(ctx, config) {
176
177
  // metadata row's default is not enough (self-hosted SearXNG, proxied
177
178
  // providers, custom key refs).
178
179
  const keySourceByProvider = {
179
- brave: { literal: config.braveApiKey, envName: config.braveApiKeyEnv ?? BRAVE_DEFAULT_API_KEY_ENV, baseURL: config.braveBaseURL ?? "", proxy: config.proxy },
180
+ 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
181
  tavily: { literal: config.apiKey, envName: config.apiKeyEnv ?? TAVILY_DEFAULT_API_KEY_ENV, baseURL: config.baseURL ?? "" },
181
182
  serper: { literal: config.serperApiKey, envName: config.serperApiKeyEnv ?? "SERPER_API_KEY", baseURL: config.serperBaseURL ?? "" },
182
183
  serpapi: { literal: config.serpapiApiKey, envName: config.serpapiApiKeyEnv ?? "SERPAPI_API_KEY", baseURL: config.serpapiBaseURL ?? "" },
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.3.1";
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.3.1",
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",