@klarkxy/dsh-web-search-manager 0.2.0 → 0.2.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/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @klarkxy/dsh-web-search-manager
2
2
 
3
+ <!-- release-state-note -->
4
+ The [repository registry](../../README.md#plugin-lifecycle) tracks Draft → Testing → Published, moving forward only. Fixes, testing and version updates keep published packages Published; each update still passes release checks. First publication explicitly selects Testing packages and records Published only after npm archive verification. See the [release guide](../../RELEASING.md).
5
+
3
6
  Choose which service answers the agent's web searches, set the limits every request runs under, and let the agent read public pages — all from one settings page.
4
7
 
5
8
  [简体中文](docs/README.zh-CN.md)
@@ -1,5 +1,8 @@
1
1
  # @klarkxy/dsh-web-search-manager
2
2
 
3
+ <!-- release-state-note -->
4
+ 插件生命周期见[仓库状态清单](../../../README.zh-CN.md#插件生命周期):草稿 → 测试中 → 已发布,只向前推进。已发布插件的修复、测试和升版保持已发布;每次更新仍须通过发布检查。首次发布须明确选择测试中的包,并在 npm 归档验证成功后登记。详见[发布规则](../../../RELEASING.zh-CN.md)。
5
+
3
6
  在一个设置页里挑选用哪个服务回答模型的网络搜索、设定每次请求的上限,并让模型读取公开网页。
4
7
 
5
8
  [English](../README.md)
@@ -2,6 +2,47 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
3
3
  let react = require("react");
4
4
  let react_jsx_runtime = require("react/jsx-runtime");
5
+ //#region src/client-error.ts
6
+ /** Local adapter diagnostics only; third-party descriptions remain verbatim. */
7
+ const localErrors = {
8
+ "网络搜索设置格式无效。": "Invalid web search settings format.",
9
+ "未知操作。": "Unknown operation.",
10
+ "网络设置操作失败,请刷新后重试。": "The network settings operation failed. Refresh and try again.",
11
+ "查询须为 1 至 4000 字符。": "The query must contain 1 to 4,000 characters.",
12
+ "网络搜索插件已停止。": "The web search plugin has stopped.",
13
+ "请在设置 → 网络搜索中配置凭据并启用。": "Configure credentials and enable web search in Settings → Web search.",
14
+ "网络访问设置已改变,请重新发起请求。": "Network access settings changed. Start the request again.",
15
+ "网络请求已取消。": "The network request was cancelled.",
16
+ "网络请求已取消或超时。": "The network request was cancelled or timed out.",
17
+ "设置已被其他页面修改,请刷新后重试。": "Settings changed in another page. Refresh and try again.",
18
+ "供应商不支持自定义地址。": "The provider does not support a custom address.",
19
+ "供应商地址须为无凭据、无查询参数的 HTTPS 地址。": "The provider address must use HTTPS without credentials or query parameters.",
20
+ "需选择已安装的供应商并填写有效凭据。": "Choose an installed provider and enter valid credentials.",
21
+ "保存失败,本次运行已暂停网络访问以防意外联网。": "Saving failed. Network access is paused for this run to prevent unintended requests.",
22
+ "供应商未提供错误描述。": "The provider did not supply an error description.",
23
+ "供应商请求失败。": "Provider request failed.",
24
+ "搜索凭据已删除或不可用,请重新配置。": "Search credentials were deleted or are unavailable. Configure them again.",
25
+ "供应商配置不可用。": "Provider configuration is unavailable.",
26
+ "查询无效。": "Invalid query.",
27
+ "结果数须为 1 至 20。": "The result count must be from 1 to 20.",
28
+ "搜索已取消。": "Search cancelled.",
29
+ "Tavily 搜索已取消。": "Tavily search cancelled.",
30
+ "响应格式无效。": "Invalid response shape",
31
+ "响应缺失。": "Missing response",
32
+ "响应过大。": "Response too large",
33
+ "地址无效。": "Invalid endpoint"
34
+ };
35
+ function localErrorText(message, locale) {
36
+ const prefix = message.match(/^(\[(?:search|fetch):[^\]]+\] )(.*)$/s);
37
+ if (prefix) return prefix[1] + localErrorText(prefix[2], locale);
38
+ if (locale === "zh") return Object.entries(localErrors).find(([, english]) => english === message)?.[0] ?? message;
39
+ if (localErrors[message]) return localErrors[message];
40
+ const http = message.match(/^供应商请求失败(HTTP (\d{3}))。$/);
41
+ if (http) return `Provider request failed (HTTP ${http[1]}).`;
42
+ const config = message.match(/^(brave|bocha|firecrawl|serper|Tavily) 配置不可用。$/);
43
+ if (config) return `${config[1]} configuration is unavailable.`;
44
+ return message;
45
+ }
5
46
  Object.values({
6
47
  /** Surfaces, from the page up to the topmost nested card. */
7
48
  surface: [
@@ -299,7 +340,7 @@ ${scope} {
299
340
  border: 0.5px solid var(--dsw-alias-border-l3);
300
341
  border-radius: var(--dsw-radius-md);
301
342
  background: var(--dsw-alias-bg-layer-1);
302
- font-family: var(--ds-font-family-code, ui-monospace, 'Cascadia Mono', Consolas, monospace);
343
+ font-family: var(--ds-font-family-code);
303
344
  font-size: 12px;
304
345
  line-height: 20px;
305
346
  color: var(--dsw-alias-label-secondary);
@@ -462,6 +503,58 @@ const providerInfo = {
462
503
  pricingUrl: "https://docs.tavily.com/documentation/api-credits"
463
504
  }
464
505
  };
506
+ const englishInfo = {
507
+ ddg: {
508
+ description: "Search public DuckDuckGo pages without registration or an API key.",
509
+ pricing: "Free to use; no search API fees."
510
+ },
511
+ bocha: {
512
+ description: "Search Chinese web content, returning titles, links and summaries.",
513
+ pricing: "Charged per request: listed price ¥0.036/request; trial credits depend on official offers."
514
+ },
515
+ brave: {
516
+ description: "Search web pages, news and more with Brave’s independent index.",
517
+ pricing: "$5 in monthly credits (about 1,000 requests); then $5/1,000 requests. A payment card is required to activate."
518
+ },
519
+ "deepseek-official": {
520
+ description: "Call built-in search through a DeepSeek model using the model API key.",
521
+ pricing: "Default Flash token pricing per million: input ¥1–2 (cache hits ¥0.02–0.04), output ¥4–8, varying by peak/off-peak hours. Platform terms govern promotional balance and tool fees."
522
+ },
523
+ exa: {
524
+ description: "Web search for AI with relevant pages and content snippets.",
525
+ pricing: "$20 on signup, then $10 monthly credits; $7/1,000 requests (up to 10 results each), plus $1/1,000 additional results beyond 10."
526
+ },
527
+ firecrawl: {
528
+ description: "Search the web and extract page content.",
529
+ pricing: "1,000 free credits/month; 2 credits per 10 search results. Hobby includes 5,000 credits/month at $16/month billed annually; extra credits cost $5/1,000."
530
+ },
531
+ serper: {
532
+ description: "Get Google web search results.",
533
+ pricing: "2,500 one-time signup requests; $50/50,000 requests ($1/1,000). Paid credits expire after 6 months."
534
+ },
535
+ tavily: {
536
+ description: "Web search for AI, currently using basic search.",
537
+ pricing: "1,000 free credits/month; basic search uses 1 credit/request. Pay as you go: $0.008/credit ($8/1,000 requests)."
538
+ }
539
+ };
540
+ const bundledDescriptions = {
541
+ ddg: ["无需注册。默认后端。", "No registration required. Default backend."],
542
+ "deepseek-official": ["使用已配置的 DeepSeek API Key,可能产生额外搜索费用。", "Uses the configured DeepSeek API key; additional search fees may apply."],
543
+ exa: ["独立 Search API。", "Independent Search API."],
544
+ brave: ["Brave Search API。", "Brave Search API."],
545
+ bocha: ["中文网页搜索 API。", "Chinese web search API."],
546
+ serper: ["Google 结果的 Search API。", "Search API for Google results."],
547
+ firecrawl: ["网页搜索与提取 API。", "Web search and extraction API."],
548
+ tavily: ["Search API;固定 basic 检索,不自动升级搜索深度。", "Search API using basic search; search depth is never upgraded automatically."]
549
+ };
550
+ /** Translate only manager-owned labels; externally supplied metadata stays verbatim. */
551
+ function providerDisplayLabel(provider, locale) {
552
+ if (!bundledCatalogId(provider)) return provider.label;
553
+ if (provider.label === "DeepSeek 搜索") return locale === "zh" ? provider.label : "DeepSeek search";
554
+ if (provider.label === "博查") return locale === "zh" ? provider.label : "Bocha";
555
+ if (provider.label === "网页读取") return locale === "zh" ? provider.label : "Read web pages";
556
+ return provider.label;
557
+ }
465
558
  function bundledCatalogId(provider) {
466
559
  if (!(typeof provider.configurationOwned === "boolean" ? provider.configurationOwned : !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager")) return;
467
560
  return provider.id === "deepseek-managed" ? "deepseek-official" : provider.id;
@@ -476,8 +569,13 @@ function usesBundledPricing(provider) {
476
569
  }
477
570
  function searchProviderInfo(provider, locale = "zh") {
478
571
  const catalogId = bundledCatalogId(provider);
479
- const known = catalogId && Object.hasOwn(providerInfo, catalogId) ? providerInfo[catalogId] : void 0;
480
- const description = provider.description.trim() ? provider.description : known?.description ?? provider.description;
572
+ const base = catalogId && Object.hasOwn(providerInfo, catalogId) ? providerInfo[catalogId] : void 0;
573
+ const known = base && locale === "en" ? {
574
+ ...base,
575
+ ...englishInfo[catalogId]
576
+ } : base;
577
+ const ownedCopy = catalogId ? bundledDescriptions[catalogId] : void 0;
578
+ const description = ownedCopy && provider.description === ownedCopy[0] ? ownedCopy[locale === "zh" ? 0 : 1] : base && provider.description === base.description ? known.description : provider.description.trim() ? provider.description : known?.description ?? provider.description;
481
579
  if (provider.pricing?.trim()) return {
482
580
  description,
483
581
  pricing: provider.pricing,
@@ -783,6 +881,26 @@ const copy = {
783
881
  unknownOwner: "Configure this service in its plugin"
784
882
  }
785
883
  };
884
+ var UiError = class extends Error {
885
+ key;
886
+ constructor(key) {
887
+ super(key);
888
+ this.key = key;
889
+ }
890
+ };
891
+ function uiMessage(message, locale, providers = []) {
892
+ if (typeof message === "string") return localErrorText(message, locale);
893
+ if ("key" in message) return copy[locale][message.key];
894
+ if ("sources" in message) return testOkText(locale, message.sources);
895
+ if ("moved" in message) {
896
+ const provider = providers.find((item) => item.id === message.moved);
897
+ return movedText(locale, provider ? providerDisplayLabel(provider, locale) : message.moved, message.position);
898
+ }
899
+ return message.invalid.map((key) => {
900
+ const [, min, max] = LIMITS.find((item) => item[0] === key);
901
+ return limitInvalidText(locale, copy[locale][key], min, max);
902
+ }).join(" ");
903
+ }
786
904
  function movedText(locale, label, position) {
787
905
  return locale === "zh" ? `${label} 已移至第 ${position} 位` : `${label} moved to position ${position}`;
788
906
  }
@@ -968,7 +1086,7 @@ function NetworkSearchSettings({ client, hidden }) {
968
1086
  return next;
969
1087
  } catch (cause) {
970
1088
  if (!mountedRef.current || mine !== requestSeq.current) return;
971
- setLoadError(cause instanceof Error && cause.message ? cause.message : text.failed);
1089
+ setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" });
972
1090
  if (source === "poll") return;
973
1091
  throw cause;
974
1092
  } finally {
@@ -979,7 +1097,7 @@ function NetworkSearchSettings({ client, hidden }) {
979
1097
  (0, react.useEffect)(() => {
980
1098
  mountedRef.current = true;
981
1099
  readGate.reset();
982
- load().catch((cause) => setLoadError(cause instanceof Error && cause.message ? cause.message : text.failed));
1100
+ load().catch((cause) => setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" }));
983
1101
  const bind = () => bindStatusRefresh({
984
1102
  isHidden: () => isPageHidden(hiddenRef.current),
985
1103
  tick: async () => {
@@ -1034,7 +1152,7 @@ function NetworkSearchSettings({ client, hidden }) {
1034
1152
  try {
1035
1153
  await run();
1036
1154
  } catch (cause) {
1037
- setError(cause instanceof Error && cause.message ? cause.message : text.failed);
1155
+ setError(cause instanceof UiError ? { key: cause.key } : cause instanceof Error && cause.message ? cause.message : { key: "failed" });
1038
1156
  await load().catch(() => {});
1039
1157
  } finally {
1040
1158
  busyRef.current = false;
@@ -1067,7 +1185,7 @@ function NetworkSearchSettings({ client, hidden }) {
1067
1185
  const snapshot = keys[provider.id] ?? "";
1068
1186
  const typed = snapshot.trim();
1069
1187
  if (!ownsProviderConfiguration(provider) || !provider.credentialRef || !typed) continue;
1070
- if (writable[provider.credentialRef] === false) throw new Error(text.readOnly);
1188
+ if (writable[provider.credentialRef] === false) throw new UiError("readOnly");
1071
1189
  unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
1072
1190
  setKeys((current) => current[provider.id] === snapshot ? {
1073
1191
  ...current,
@@ -1083,14 +1201,14 @@ function NetworkSearchSettings({ client, hidden }) {
1083
1201
  [provider.id]: ""
1084
1202
  }));
1085
1203
  await load();
1086
- setNote(text.keyDeleted);
1204
+ setNote({ key: "keyDeleted" });
1087
1205
  }
1088
1206
  if (!status || !limitDraft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1089
1207
  className: "web-search-settings ws-root dsh-ui-panel",
1090
1208
  children: [loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1091
1209
  role: "alert",
1092
1210
  className: "dsh-ui-error",
1093
- children: [text.loadFailed, loadError]
1211
+ children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
1094
1212
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1095
1213
  role: "status",
1096
1214
  className: "dsh-ui-hint",
@@ -1129,7 +1247,10 @@ function NetworkSearchSettings({ client, hidden }) {
1129
1247
  pendingRef.current = next;
1130
1248
  setPendingOrder(next);
1131
1249
  if (keyboard) refocusRef.current = id;
1132
- setSortAnnouncement(movedText(locale, backends.find((provider) => provider.id === id)?.label ?? id, nextIndex + 1));
1250
+ setSortAnnouncement({
1251
+ moved: id,
1252
+ position: nextIndex + 1
1253
+ });
1133
1254
  clearTimeout(timerRef.current);
1134
1255
  timerRef.current = setTimeout(() => {
1135
1256
  timerRef.current = void 0;
@@ -1150,13 +1271,13 @@ function NetworkSearchSettings({ client, hidden }) {
1150
1271
  const value = parseLimit(limitDraft[key], min, max);
1151
1272
  if (value === void 0) {
1152
1273
  nextInvalid[key] = true;
1153
- messages.push(limitInvalidText(locale, text[key], min, max));
1274
+ messages.push(key);
1154
1275
  } else parsed[key] = value;
1155
1276
  }
1156
1277
  setInvalid(nextInvalid);
1157
1278
  if (messages.length) {
1158
1279
  setNote("");
1159
- setError(messages.join(" "));
1280
+ setError({ invalid: messages });
1160
1281
  return;
1161
1282
  }
1162
1283
  action(async () => {
@@ -1170,7 +1291,7 @@ function NetworkSearchSettings({ client, hidden }) {
1170
1291
  expectedRevision: current.settings.revision
1171
1292
  }), true);
1172
1293
  await load();
1173
- setNote(text.saved);
1294
+ setNote({ key: "saved" });
1174
1295
  });
1175
1296
  }
1176
1297
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -1197,19 +1318,19 @@ function NetworkSearchSettings({ client, hidden }) {
1197
1318
  className: "ws-sr-only",
1198
1319
  role: "status",
1199
1320
  "aria-live": "polite",
1200
- children: sortAnnouncement
1321
+ children: uiMessage(sortAnnouncement, locale, saved.providers)
1201
1322
  }),
1202
1323
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1203
1324
  role: "alert",
1204
1325
  className: "dsh-ui-error",
1205
- children: error
1326
+ children: uiMessage(error, locale, saved.providers)
1206
1327
  }),
1207
1328
  loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1208
1329
  className: "dsh-ui-stack",
1209
1330
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1210
1331
  role: "alert",
1211
1332
  className: "dsh-ui-error",
1212
- children: [text.loadFailed, loadError]
1333
+ children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
1213
1334
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1214
1335
  variant: "outline",
1215
1336
  size: "sm",
@@ -1223,7 +1344,7 @@ function NetworkSearchSettings({ client, hidden }) {
1223
1344
  note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1224
1345
  role: "status",
1225
1346
  className: "dsh-ui-notice",
1226
- children: note
1347
+ children: uiMessage(note, locale, saved.providers)
1227
1348
  }),
1228
1349
  saved.storageFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1229
1350
  role: "alert",
@@ -1349,7 +1470,7 @@ function NetworkSearchSettings({ client, hidden }) {
1349
1470
  }),
1350
1471
  draggable: !busy,
1351
1472
  "aria-disabled": busy,
1352
- "aria-label": text.dragPrefix + provider.label,
1473
+ "aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
1353
1474
  title: text.dragHint,
1354
1475
  onDragStart: (event) => {
1355
1476
  if (busy) {
@@ -1381,7 +1502,7 @@ function NetworkSearchSettings({ client, hidden }) {
1381
1502
  children: [
1382
1503
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
1383
1504
  className: "dsh-ui-heading",
1384
- children: provider.label
1505
+ children: providerDisplayLabel(provider, locale)
1385
1506
  }),
1386
1507
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1387
1508
  className: "ws-provider-note",
@@ -1393,7 +1514,7 @@ function NetworkSearchSettings({ client, hidden }) {
1393
1514
  }),
1394
1515
  provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
1395
1516
  url: provider.signupUrl,
1396
- label: provider.label + " " + text.signup,
1517
+ label: providerDisplayLabel(provider, locale) + " " + text.signup,
1397
1518
  children: text.signup
1398
1519
  })
1399
1520
  ]
@@ -1402,7 +1523,7 @@ function NetworkSearchSettings({ client, hidden }) {
1402
1523
  className: "web-search-provider-info",
1403
1524
  children: [info.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: info.description }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [info.pricing, info.pricingUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
1404
1525
  url: info.pricingUrl,
1405
- label: provider.label + " " + text.pricing,
1526
+ label: providerDisplayLabel(provider, locale) + " " + text.pricing,
1406
1527
  children: provider.id === "ddg" ? text.website : text.pricing
1407
1528
  })] })] })]
1408
1529
  }),
@@ -1422,7 +1543,7 @@ function NetworkSearchSettings({ client, hidden }) {
1422
1543
  }) : null,
1423
1544
  shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
1424
1545
  className: "dsh-ui-meta",
1425
- children: provider.credentialHint ?? text.sharedKey
1546
+ children: provider.credentialHint === "与模型设置共用 Key" ? locale === "zh" ? provider.credentialHint : "Shares a key with model settings" : provider.credentialHint ?? text.sharedKey
1426
1547
  }) : null,
1427
1548
  showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1428
1549
  className: "dsh-ui-field",
@@ -1452,7 +1573,7 @@ function NetworkSearchSettings({ client, hidden }) {
1452
1573
  onClick: () => void action(async () => {
1453
1574
  await saveKeys(provider);
1454
1575
  await load();
1455
- setNote(text.keySaved);
1576
+ setNote({ key: "keySaved" });
1456
1577
  }),
1457
1578
  children: text.saveKey
1458
1579
  }) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
@@ -1468,7 +1589,7 @@ function NetworkSearchSettings({ client, hidden }) {
1468
1589
  className: "web-search-rank-move",
1469
1590
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
1470
1591
  checked: participating,
1471
- label: provider.label,
1592
+ label: providerDisplayLabel(provider, locale),
1472
1593
  disabled: busy,
1473
1594
  onChange: (next) => {
1474
1595
  const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
@@ -1494,7 +1615,7 @@ function NetworkSearchSettings({ client, hidden }) {
1494
1615
  await saveKeys();
1495
1616
  const result = await call("test");
1496
1617
  await load();
1497
- setNote(testOkText(locale, result.sources));
1618
+ setNote({ sources: result.sources });
1498
1619
  }),
1499
1620
  children: text.test
1500
1621
  })
@@ -1577,7 +1698,7 @@ function NetworkSearchSettings({ client, hidden }) {
1577
1698
  ]
1578
1699
  }), saved.providers.filter((provider) => provider.kind === "fetch").map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1579
1700
  value: provider.id,
1580
- children: [provider.label, provider.configured ? "" : " · " + text.unavailable]
1701
+ children: [providerDisplayLabel(provider, locale), provider.configured ? "" : " · " + text.unavailable]
1581
1702
  }, provider.id))]
1582
1703
  })]
1583
1704
  }),