@klarkxy/dsh-web-search-manager 0.2.0 → 0.2.2
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 +8 -30
- package/docs/README.zh-CN.md +8 -30
- package/lib/client.inner.cjs +147 -26
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +147 -26
- package/lib/contracts.d.ts +3 -25
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +19 -59
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
package/lib/client.js
CHANGED
|
@@ -8,6 +8,47 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let react = require("react");
|
|
10
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
|
+
//#region src/client-error.ts
|
|
12
|
+
/** Local adapter diagnostics only; third-party descriptions remain verbatim. */
|
|
13
|
+
const localErrors = {
|
|
14
|
+
"网络搜索设置格式无效。": "Invalid web search settings format.",
|
|
15
|
+
"未知操作。": "Unknown operation.",
|
|
16
|
+
"网络设置操作失败,请刷新后重试。": "The network settings operation failed. Refresh and try again.",
|
|
17
|
+
"查询须为 1 至 4000 字符。": "The query must contain 1 to 4,000 characters.",
|
|
18
|
+
"网络搜索插件已停止。": "The web search plugin has stopped.",
|
|
19
|
+
"请在设置 → 网络搜索中配置凭据并启用。": "Configure credentials and enable web search in Settings → Web search.",
|
|
20
|
+
"网络访问设置已改变,请重新发起请求。": "Network access settings changed. Start the request again.",
|
|
21
|
+
"网络请求已取消。": "The network request was cancelled.",
|
|
22
|
+
"网络请求已取消或超时。": "The network request was cancelled or timed out.",
|
|
23
|
+
"设置已被其他页面修改,请刷新后重试。": "Settings changed in another page. Refresh and try again.",
|
|
24
|
+
"供应商不支持自定义地址。": "The provider does not support a custom address.",
|
|
25
|
+
"供应商地址须为无凭据、无查询参数的 HTTPS 地址。": "The provider address must use HTTPS without credentials or query parameters.",
|
|
26
|
+
"需选择已安装的供应商并填写有效凭据。": "Choose an installed provider and enter valid credentials.",
|
|
27
|
+
"保存失败,本次运行已暂停网络访问以防意外联网。": "Saving failed. Network access is paused for this run to prevent unintended requests.",
|
|
28
|
+
"供应商未提供错误描述。": "The provider did not supply an error description.",
|
|
29
|
+
"供应商请求失败。": "Provider request failed.",
|
|
30
|
+
"搜索凭据已删除或不可用,请重新配置。": "Search credentials were deleted or are unavailable. Configure them again.",
|
|
31
|
+
"供应商配置不可用。": "Provider configuration is unavailable.",
|
|
32
|
+
"查询无效。": "Invalid query.",
|
|
33
|
+
"结果数须为 1 至 20。": "The result count must be from 1 to 20.",
|
|
34
|
+
"搜索已取消。": "Search cancelled.",
|
|
35
|
+
"Tavily 搜索已取消。": "Tavily search cancelled.",
|
|
36
|
+
"响应格式无效。": "Invalid response shape",
|
|
37
|
+
"响应缺失。": "Missing response",
|
|
38
|
+
"响应过大。": "Response too large",
|
|
39
|
+
"地址无效。": "Invalid endpoint"
|
|
40
|
+
};
|
|
41
|
+
function localErrorText(message, locale) {
|
|
42
|
+
const prefix = message.match(/^(\[(?:search|fetch):[^\]]+\] )(.*)$/s);
|
|
43
|
+
if (prefix) return prefix[1] + localErrorText(prefix[2], locale);
|
|
44
|
+
if (locale === "zh") return Object.entries(localErrors).find(([, english]) => english === message)?.[0] ?? message;
|
|
45
|
+
if (localErrors[message]) return localErrors[message];
|
|
46
|
+
const http = message.match(/^供应商请求失败(HTTP (\d{3}))。$/);
|
|
47
|
+
if (http) return `Provider request failed (HTTP ${http[1]}).`;
|
|
48
|
+
const config = message.match(/^(brave|bocha|firecrawl|serper|Tavily) 配置不可用。$/);
|
|
49
|
+
if (config) return `${config[1]} configuration is unavailable.`;
|
|
50
|
+
return message;
|
|
51
|
+
}
|
|
11
52
|
Object.values({
|
|
12
53
|
/** Surfaces, from the page up to the topmost nested card. */
|
|
13
54
|
surface: [
|
|
@@ -305,7 +346,7 @@ ${scope} {
|
|
|
305
346
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
306
347
|
border-radius: var(--dsw-radius-md);
|
|
307
348
|
background: var(--dsw-alias-bg-layer-1);
|
|
308
|
-
font-family: var(--ds-font-family-code
|
|
349
|
+
font-family: var(--ds-font-family-code);
|
|
309
350
|
font-size: 12px;
|
|
310
351
|
line-height: 20px;
|
|
311
352
|
color: var(--dsw-alias-label-secondary);
|
|
@@ -468,6 +509,58 @@ const providerInfo = {
|
|
|
468
509
|
pricingUrl: "https://docs.tavily.com/documentation/api-credits"
|
|
469
510
|
}
|
|
470
511
|
};
|
|
512
|
+
const englishInfo = {
|
|
513
|
+
ddg: {
|
|
514
|
+
description: "Search public DuckDuckGo pages without registration or an API key.",
|
|
515
|
+
pricing: "Free to use; no search API fees."
|
|
516
|
+
},
|
|
517
|
+
bocha: {
|
|
518
|
+
description: "Search Chinese web content, returning titles, links and summaries.",
|
|
519
|
+
pricing: "Charged per request: listed price ¥0.036/request; trial credits depend on official offers."
|
|
520
|
+
},
|
|
521
|
+
brave: {
|
|
522
|
+
description: "Search web pages, news and more with Brave’s independent index.",
|
|
523
|
+
pricing: "$5 in monthly credits (about 1,000 requests); then $5/1,000 requests. A payment card is required to activate."
|
|
524
|
+
},
|
|
525
|
+
"deepseek-official": {
|
|
526
|
+
description: "Call built-in search through a DeepSeek model using the model API key.",
|
|
527
|
+
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."
|
|
528
|
+
},
|
|
529
|
+
exa: {
|
|
530
|
+
description: "Web search for AI with relevant pages and content snippets.",
|
|
531
|
+
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."
|
|
532
|
+
},
|
|
533
|
+
firecrawl: {
|
|
534
|
+
description: "Search the web and extract page content.",
|
|
535
|
+
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."
|
|
536
|
+
},
|
|
537
|
+
serper: {
|
|
538
|
+
description: "Get Google web search results.",
|
|
539
|
+
pricing: "2,500 one-time signup requests; $50/50,000 requests ($1/1,000). Paid credits expire after 6 months."
|
|
540
|
+
},
|
|
541
|
+
tavily: {
|
|
542
|
+
description: "Web search for AI, currently using basic search.",
|
|
543
|
+
pricing: "1,000 free credits/month; basic search uses 1 credit/request. Pay as you go: $0.008/credit ($8/1,000 requests)."
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
const bundledDescriptions = {
|
|
547
|
+
ddg: ["无需注册。默认后端。", "No registration required. Default backend."],
|
|
548
|
+
"deepseek-official": ["使用已配置的 DeepSeek API Key,可能产生额外搜索费用。", "Uses the configured DeepSeek API key; additional search fees may apply."],
|
|
549
|
+
exa: ["独立 Search API。", "Independent Search API."],
|
|
550
|
+
brave: ["Brave Search API。", "Brave Search API."],
|
|
551
|
+
bocha: ["中文网页搜索 API。", "Chinese web search API."],
|
|
552
|
+
serper: ["Google 结果的 Search API。", "Search API for Google results."],
|
|
553
|
+
firecrawl: ["网页搜索与提取 API。", "Web search and extraction API."],
|
|
554
|
+
tavily: ["Search API;固定 basic 检索,不自动升级搜索深度。", "Search API using basic search; search depth is never upgraded automatically."]
|
|
555
|
+
};
|
|
556
|
+
/** Translate only manager-owned labels; externally supplied metadata stays verbatim. */
|
|
557
|
+
function providerDisplayLabel(provider, locale) {
|
|
558
|
+
if (!bundledCatalogId(provider)) return provider.label;
|
|
559
|
+
if (provider.label === "DeepSeek 搜索") return locale === "zh" ? provider.label : "DeepSeek search";
|
|
560
|
+
if (provider.label === "博查") return locale === "zh" ? provider.label : "Bocha";
|
|
561
|
+
if (provider.label === "网页读取") return locale === "zh" ? provider.label : "Read web pages";
|
|
562
|
+
return provider.label;
|
|
563
|
+
}
|
|
471
564
|
function bundledCatalogId(provider) {
|
|
472
565
|
if (!(typeof provider.configurationOwned === "boolean" ? provider.configurationOwned : !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager")) return;
|
|
473
566
|
return provider.id === "deepseek-managed" ? "deepseek-official" : provider.id;
|
|
@@ -482,8 +575,13 @@ function usesBundledPricing(provider) {
|
|
|
482
575
|
}
|
|
483
576
|
function searchProviderInfo(provider, locale = "zh") {
|
|
484
577
|
const catalogId = bundledCatalogId(provider);
|
|
485
|
-
const
|
|
486
|
-
const
|
|
578
|
+
const base = catalogId && Object.hasOwn(providerInfo, catalogId) ? providerInfo[catalogId] : void 0;
|
|
579
|
+
const known = base && locale === "en" ? {
|
|
580
|
+
...base,
|
|
581
|
+
...englishInfo[catalogId]
|
|
582
|
+
} : base;
|
|
583
|
+
const ownedCopy = catalogId ? bundledDescriptions[catalogId] : void 0;
|
|
584
|
+
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;
|
|
487
585
|
if (provider.pricing?.trim()) return {
|
|
488
586
|
description,
|
|
489
587
|
pricing: provider.pricing,
|
|
@@ -789,6 +887,26 @@ const copy = {
|
|
|
789
887
|
unknownOwner: "Configure this service in its plugin"
|
|
790
888
|
}
|
|
791
889
|
};
|
|
890
|
+
var UiError = class extends Error {
|
|
891
|
+
key;
|
|
892
|
+
constructor(key) {
|
|
893
|
+
super(key);
|
|
894
|
+
this.key = key;
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
function uiMessage(message, locale, providers = []) {
|
|
898
|
+
if (typeof message === "string") return localErrorText(message, locale);
|
|
899
|
+
if ("key" in message) return copy[locale][message.key];
|
|
900
|
+
if ("sources" in message) return testOkText(locale, message.sources);
|
|
901
|
+
if ("moved" in message) {
|
|
902
|
+
const provider = providers.find((item) => item.id === message.moved);
|
|
903
|
+
return movedText(locale, provider ? providerDisplayLabel(provider, locale) : message.moved, message.position);
|
|
904
|
+
}
|
|
905
|
+
return message.invalid.map((key) => {
|
|
906
|
+
const [, min, max] = LIMITS.find((item) => item[0] === key);
|
|
907
|
+
return limitInvalidText(locale, copy[locale][key], min, max);
|
|
908
|
+
}).join(" ");
|
|
909
|
+
}
|
|
792
910
|
function movedText(locale, label, position) {
|
|
793
911
|
return locale === "zh" ? `${label} 已移至第 ${position} 位` : `${label} moved to position ${position}`;
|
|
794
912
|
}
|
|
@@ -974,7 +1092,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
974
1092
|
return next;
|
|
975
1093
|
} catch (cause) {
|
|
976
1094
|
if (!mountedRef.current || mine !== requestSeq.current) return;
|
|
977
|
-
setLoadError(cause instanceof Error && cause.message ? cause.message :
|
|
1095
|
+
setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" });
|
|
978
1096
|
if (source === "poll") return;
|
|
979
1097
|
throw cause;
|
|
980
1098
|
} finally {
|
|
@@ -985,7 +1103,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
985
1103
|
(0, react.useEffect)(() => {
|
|
986
1104
|
mountedRef.current = true;
|
|
987
1105
|
readGate.reset();
|
|
988
|
-
load().catch((cause) => setLoadError(cause instanceof Error && cause.message ? cause.message :
|
|
1106
|
+
load().catch((cause) => setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" }));
|
|
989
1107
|
const bind = () => bindStatusRefresh({
|
|
990
1108
|
isHidden: () => isPageHidden(hiddenRef.current),
|
|
991
1109
|
tick: async () => {
|
|
@@ -1040,7 +1158,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1040
1158
|
try {
|
|
1041
1159
|
await run();
|
|
1042
1160
|
} catch (cause) {
|
|
1043
|
-
setError(cause instanceof Error && cause.message ? cause.message :
|
|
1161
|
+
setError(cause instanceof UiError ? { key: cause.key } : cause instanceof Error && cause.message ? cause.message : { key: "failed" });
|
|
1044
1162
|
await load().catch(() => {});
|
|
1045
1163
|
} finally {
|
|
1046
1164
|
busyRef.current = false;
|
|
@@ -1073,7 +1191,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1073
1191
|
const snapshot = keys[provider.id] ?? "";
|
|
1074
1192
|
const typed = snapshot.trim();
|
|
1075
1193
|
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || !typed) continue;
|
|
1076
|
-
if (writable[provider.credentialRef] === false) throw new
|
|
1194
|
+
if (writable[provider.credentialRef] === false) throw new UiError("readOnly");
|
|
1077
1195
|
unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
|
|
1078
1196
|
setKeys((current) => current[provider.id] === snapshot ? {
|
|
1079
1197
|
...current,
|
|
@@ -1089,14 +1207,14 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1089
1207
|
[provider.id]: ""
|
|
1090
1208
|
}));
|
|
1091
1209
|
await load();
|
|
1092
|
-
setNote(
|
|
1210
|
+
setNote({ key: "keyDeleted" });
|
|
1093
1211
|
}
|
|
1094
1212
|
if (!status || !limitDraft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1095
1213
|
className: "web-search-settings ws-root dsh-ui-panel",
|
|
1096
1214
|
children: [loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1097
1215
|
role: "alert",
|
|
1098
1216
|
className: "dsh-ui-error",
|
|
1099
|
-
children: [text.loadFailed, loadError]
|
|
1217
|
+
children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
|
|
1100
1218
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1101
1219
|
role: "status",
|
|
1102
1220
|
className: "dsh-ui-hint",
|
|
@@ -1135,7 +1253,10 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1135
1253
|
pendingRef.current = next;
|
|
1136
1254
|
setPendingOrder(next);
|
|
1137
1255
|
if (keyboard) refocusRef.current = id;
|
|
1138
|
-
setSortAnnouncement(
|
|
1256
|
+
setSortAnnouncement({
|
|
1257
|
+
moved: id,
|
|
1258
|
+
position: nextIndex + 1
|
|
1259
|
+
});
|
|
1139
1260
|
clearTimeout(timerRef.current);
|
|
1140
1261
|
timerRef.current = setTimeout(() => {
|
|
1141
1262
|
timerRef.current = void 0;
|
|
@@ -1156,13 +1277,13 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1156
1277
|
const value = parseLimit(limitDraft[key], min, max);
|
|
1157
1278
|
if (value === void 0) {
|
|
1158
1279
|
nextInvalid[key] = true;
|
|
1159
|
-
messages.push(
|
|
1280
|
+
messages.push(key);
|
|
1160
1281
|
} else parsed[key] = value;
|
|
1161
1282
|
}
|
|
1162
1283
|
setInvalid(nextInvalid);
|
|
1163
1284
|
if (messages.length) {
|
|
1164
1285
|
setNote("");
|
|
1165
|
-
setError(messages
|
|
1286
|
+
setError({ invalid: messages });
|
|
1166
1287
|
return;
|
|
1167
1288
|
}
|
|
1168
1289
|
action(async () => {
|
|
@@ -1176,7 +1297,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1176
1297
|
expectedRevision: current.settings.revision
|
|
1177
1298
|
}), true);
|
|
1178
1299
|
await load();
|
|
1179
|
-
setNote(
|
|
1300
|
+
setNote({ key: "saved" });
|
|
1180
1301
|
});
|
|
1181
1302
|
}
|
|
1182
1303
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -1203,19 +1324,19 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1203
1324
|
className: "ws-sr-only",
|
|
1204
1325
|
role: "status",
|
|
1205
1326
|
"aria-live": "polite",
|
|
1206
|
-
children: sortAnnouncement
|
|
1327
|
+
children: uiMessage(sortAnnouncement, locale, saved.providers)
|
|
1207
1328
|
}),
|
|
1208
1329
|
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1209
1330
|
role: "alert",
|
|
1210
1331
|
className: "dsh-ui-error",
|
|
1211
|
-
children: error
|
|
1332
|
+
children: uiMessage(error, locale, saved.providers)
|
|
1212
1333
|
}),
|
|
1213
1334
|
loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1214
1335
|
className: "dsh-ui-stack",
|
|
1215
1336
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1216
1337
|
role: "alert",
|
|
1217
1338
|
className: "dsh-ui-error",
|
|
1218
|
-
children: [text.loadFailed, loadError]
|
|
1339
|
+
children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
|
|
1219
1340
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1220
1341
|
variant: "outline",
|
|
1221
1342
|
size: "sm",
|
|
@@ -1229,7 +1350,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1229
1350
|
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1230
1351
|
role: "status",
|
|
1231
1352
|
className: "dsh-ui-notice",
|
|
1232
|
-
children: note
|
|
1353
|
+
children: uiMessage(note, locale, saved.providers)
|
|
1233
1354
|
}),
|
|
1234
1355
|
saved.storageFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1235
1356
|
role: "alert",
|
|
@@ -1355,7 +1476,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1355
1476
|
}),
|
|
1356
1477
|
draggable: !busy,
|
|
1357
1478
|
"aria-disabled": busy,
|
|
1358
|
-
"aria-label": text.dragPrefix + provider
|
|
1479
|
+
"aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
|
|
1359
1480
|
title: text.dragHint,
|
|
1360
1481
|
onDragStart: (event) => {
|
|
1361
1482
|
if (busy) {
|
|
@@ -1387,7 +1508,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1387
1508
|
children: [
|
|
1388
1509
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1389
1510
|
className: "dsh-ui-heading",
|
|
1390
|
-
children: provider
|
|
1511
|
+
children: providerDisplayLabel(provider, locale)
|
|
1391
1512
|
}),
|
|
1392
1513
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1393
1514
|
className: "ws-provider-note",
|
|
@@ -1399,7 +1520,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1399
1520
|
}),
|
|
1400
1521
|
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1401
1522
|
url: provider.signupUrl,
|
|
1402
|
-
label: provider
|
|
1523
|
+
label: providerDisplayLabel(provider, locale) + " " + text.signup,
|
|
1403
1524
|
children: text.signup
|
|
1404
1525
|
})
|
|
1405
1526
|
]
|
|
@@ -1408,7 +1529,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1408
1529
|
className: "web-search-provider-info",
|
|
1409
1530
|
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, {
|
|
1410
1531
|
url: info.pricingUrl,
|
|
1411
|
-
label: provider
|
|
1532
|
+
label: providerDisplayLabel(provider, locale) + " " + text.pricing,
|
|
1412
1533
|
children: provider.id === "ddg" ? text.website : text.pricing
|
|
1413
1534
|
})] })] })]
|
|
1414
1535
|
}),
|
|
@@ -1428,7 +1549,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1428
1549
|
}) : null,
|
|
1429
1550
|
shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1430
1551
|
className: "dsh-ui-meta",
|
|
1431
|
-
children: provider.credentialHint ?? text.sharedKey
|
|
1552
|
+
children: provider.credentialHint === "与模型设置共用 Key" ? locale === "zh" ? provider.credentialHint : "Shares a key with model settings" : provider.credentialHint ?? text.sharedKey
|
|
1432
1553
|
}) : null,
|
|
1433
1554
|
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1434
1555
|
className: "dsh-ui-field",
|
|
@@ -1458,7 +1579,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1458
1579
|
onClick: () => void action(async () => {
|
|
1459
1580
|
await saveKeys(provider);
|
|
1460
1581
|
await load();
|
|
1461
|
-
setNote(
|
|
1582
|
+
setNote({ key: "keySaved" });
|
|
1462
1583
|
}),
|
|
1463
1584
|
children: text.saveKey
|
|
1464
1585
|
}) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
|
|
@@ -1474,7 +1595,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1474
1595
|
className: "web-search-rank-move",
|
|
1475
1596
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1476
1597
|
checked: participating,
|
|
1477
|
-
label: provider
|
|
1598
|
+
label: providerDisplayLabel(provider, locale),
|
|
1478
1599
|
disabled: busy,
|
|
1479
1600
|
onChange: (next) => {
|
|
1480
1601
|
const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
|
|
@@ -1500,7 +1621,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1500
1621
|
await saveKeys();
|
|
1501
1622
|
const result = await call("test");
|
|
1502
1623
|
await load();
|
|
1503
|
-
setNote(
|
|
1624
|
+
setNote({ sources: result.sources });
|
|
1504
1625
|
}),
|
|
1505
1626
|
children: text.test
|
|
1506
1627
|
})
|
|
@@ -1583,7 +1704,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1583
1704
|
]
|
|
1584
1705
|
}), saved.providers.filter((provider) => provider.kind === "fetch").map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1585
1706
|
value: provider.id,
|
|
1586
|
-
children: [provider
|
|
1707
|
+
children: [providerDisplayLabel(provider, locale), provider.configured ? "" : " · " + text.unavailable]
|
|
1587
1708
|
}, provider.id))]
|
|
1588
1709
|
})]
|
|
1589
1710
|
}),
|
package/lib/contracts.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WebFetchProvider, WebSearchProvider } from "@deepseek-ai/dsh-web";
|
|
2
|
+
import { DshWebManagementDescriptor } from "@klarkxy/dsh-plugin-kit/contracts";
|
|
2
3
|
//#region src/contracts.d.ts
|
|
3
4
|
declare const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
|
|
4
5
|
type RpcResult<T = unknown> = {
|
|
@@ -13,31 +14,8 @@ type RpcResult<T = unknown> = {
|
|
|
13
14
|
};
|
|
14
15
|
};
|
|
15
16
|
type ProviderKind = 'search' | 'fetch';
|
|
16
|
-
/** Management metadata only.
|
|
17
|
-
interface ProviderDescriptor {
|
|
18
|
-
readonly id: string;
|
|
19
|
-
readonly label: string;
|
|
20
|
-
readonly description: string;
|
|
21
|
-
readonly defaultBaseURL?: string;
|
|
22
|
-
readonly credentialRef?: string;
|
|
23
|
-
/** True when the key is shared with another surface. The page must not delete it. */
|
|
24
|
-
readonly credentialShared?: boolean;
|
|
25
|
-
/** Optional notice naming the settings surface that owns a shared credential. */
|
|
26
|
-
readonly credentialHint?: string;
|
|
27
|
-
readonly billing: 'request' | 'model-and-tools' | 'none' | 'unknown';
|
|
28
|
-
/** Optional provider-supplied billing summary. */
|
|
29
|
-
readonly pricing?: string;
|
|
30
|
-
/** Public HTTPS page explaining provider charges. */
|
|
31
|
-
readonly pricingUrl?: string;
|
|
32
|
-
/** Public HTTPS page where the user can create an API key. */
|
|
33
|
-
readonly signupUrl?: string;
|
|
34
|
-
/** Plugin that owns configuration. Native providers keep their settings there. */
|
|
35
|
-
readonly configurationOwner?: string;
|
|
36
|
-
/** Explicit settings link supplied by the provider, never inferred from its id. */
|
|
37
|
-
readonly configurationUrl?: string;
|
|
38
|
-
/** Fixed legacy route migrated by a bundle-owned adapter; not used by native plugins. */
|
|
39
|
-
readonly legacyId?: string;
|
|
40
|
-
}
|
|
17
|
+
/** Management metadata only. Field contract lives in plugin-kit; execution stays with ctx.web. */
|
|
18
|
+
interface ProviderDescriptor extends DshWebManagementDescriptor {}
|
|
41
19
|
declare const WEB_MANAGER_OWNER = "@klarkxy/dsh-web-search-manager";
|
|
42
20
|
/** Optional display metadata on a provider registered directly with ctx.web. */
|
|
43
21
|
type DiscoverableSearchProvider = WebSearchProvider & {
|
package/lib/contracts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { WebFetchProvider, WebSearchProvider } from '@deepseek-ai/dsh-web'\n\nexport const WEB_SEARCH_RPC_CHANNEL = '/web-search-manager'\nexport type RpcResult<T = unknown> = { ok: true; value: T } | {\n ok: false; error: { code: string; message: string; details: Record<string, unknown> }\n}\nexport type ProviderKind = 'search' | 'fetch'\n/** Management metadata only.
|
|
1
|
+
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { WebFetchProvider, WebSearchProvider } from '@deepseek-ai/dsh-web'\nimport type { DshWebManagementDescriptor } from '@klarkxy/dsh-plugin-kit/contracts'\n\nexport const WEB_SEARCH_RPC_CHANNEL = '/web-search-manager'\nexport type RpcResult<T = unknown> = { ok: true; value: T } | {\n ok: false; error: { code: string; message: string; details: Record<string, unknown> }\n}\nexport type ProviderKind = 'search' | 'fetch'\n/** Management metadata only. Field contract lives in plugin-kit; execution stays with ctx.web. */\nexport interface ProviderDescriptor extends DshWebManagementDescriptor {}\nexport const WEB_MANAGER_OWNER = '@klarkxy/dsh-web-search-manager'\n/** Optional display metadata on a provider registered directly with ctx.web. */\nexport type DiscoverableSearchProvider = WebSearchProvider & { readonly dshWebManagement?: ProviderDescriptor }\nexport type DiscoverableFetchProvider = WebFetchProvider & { readonly dshWebManagement?: ProviderDescriptor }\nexport interface ProviderOptions {\n readonly apiKey?: string\n readonly baseURL?: string\n readonly timeoutMs: number\n readonly maxFetchChars: number\n}\nexport type SearchProviderFactory = (options: ProviderOptions) => WebSearchProvider\nexport type FetchProviderFactory = (options: ProviderOptions) => WebFetchProvider\n/** Never store credential values here. Keys are resolved by reference per call. */\nexport interface WebSettings {\n revision: number\n searchEnabled: boolean\n fetchEnabled: boolean\n searchProvider: string\n fetchProvider: string\n searchOrder: string[]\n maxResults: number\n maxQueries: number\n timeoutMs: number\n maxFetchChars: number\n endpoints: Record<string, string>\n}\nexport const DEFAULT_SEARCH_ORDER = ['ddg'] as const\n\nexport const defaultSettings = (): WebSettings => ({\n revision: 0, searchEnabled: true, fetchEnabled: true,\n searchProvider: 'ddg', fetchProvider: 'http', searchOrder: [...DEFAULT_SEARCH_ORDER],\n maxResults: 5, maxQueries: 1,\n timeoutMs: 30_000, maxFetchChars: 100_000, endpoints: {},\n})\n\n/** Keyed backends first, keyless last — same idea as OpenClaw auto-detect. */\nexport function defaultSearchOrder(ids: readonly string[], keyed: (id: string) => boolean): string[] {\n return [...ids.filter(keyed), ...ids.filter(id => !keyed(id))]\n}\n\nexport function migrateSearchOrder(settings: { searchOrder?: string[]; searchProvider?: string }): string[] {\n return settings.searchOrder ?? (settings.searchProvider ? [settings.searchProvider] : [...DEFAULT_SEARCH_ORDER])\n}\n\nexport function resolveSearchOrder(\n knownIds: readonly string[],\n settings: Pick<WebSettings, 'searchOrder' | 'searchProvider'>,\n _keyed: (id: string) => boolean,\n): string[] {\n return [...new Set(settings.searchOrder.filter(id => knownIds.includes(id)))]\n}\n\nexport function pickActiveSearch(order: readonly string[], configured: (id: string) => boolean): string {\n return order.find(configured) ?? ''\n}\nexport interface ProviderView extends ProviderDescriptor {\n kind: ProviderKind\n configured: boolean\n /** True only for configuration explicitly owned by this manager bundle. */\n configurationOwned?: boolean\n baseURL?: string\n calls: number\n failures: number\n}\nexport interface WebStatus {\n settings: WebSettings\n providers: ProviderView[]\n searchActive: boolean\n fetchActive: boolean\n storageFailed: boolean\n}\nexport function providerKey(kind: ProviderKind, id: string): string { return `${kind}:${id}` }\nexport function safeConfigurationUrl(value: unknown): string | undefined {\n if (typeof value !== 'string' || !value || /[\\u0000-\\u0020\\\\]/.test(value)) return\n try {\n const url = new URL(value, 'https://dsh.invalid')\n if (url.username || url.password) return\n if ((value.startsWith('/') && !value.startsWith('//')) || value.startsWith('#')) {\n return url.origin === 'https://dsh.invalid' ? value : undefined\n }\n return /^https:\\/\\//i.test(value) && url.protocol === 'https:' ? value : undefined\n } catch { return }\n}\nexport function validateBaseURL(value: string): string {\n const url = new URL(value)\n if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) {\n throw new Error('供应商地址必须是 HTTPS,且不能包含用户名、密码、查询参数或片段。')\n }\n return url.href.replace(/\\/+$/, '')\n}\n"],"mappings":";AAGA,MAAa,yBAAyB;AAOtC,MAAa,oBAAoB;AA0BjC,MAAa,uBAAuB,CAAC,KAAK;AAE1C,MAAa,yBAAsC;CACjD,UAAU;CAAG,eAAe;CAAM,cAAc;CAChD,gBAAgB;CAAO,eAAe;CAAQ,aAAa,CAAC,GAAG,oBAAoB;CACnF,YAAY;CAAG,YAAY;CAC3B,WAAW;CAAQ,eAAe;CAAS,WAAW,CAAC;AACzD;;AAGA,SAAgB,mBAAmB,KAAwB,OAA0C;CACnG,OAAO,CAAC,GAAG,IAAI,OAAO,KAAK,GAAG,GAAG,IAAI,QAAO,OAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/D;AAEA,SAAgB,mBAAmB,UAAyE;CAC1G,OAAO,SAAS,gBAAgB,SAAS,iBAAiB,CAAC,SAAS,cAAc,IAAI,CAAC,GAAG,oBAAoB;AAChH;AAEA,SAAgB,mBACd,UACA,UACA,QACU;CACV,OAAO,CAAC,GAAG,IAAI,IAAI,SAAS,YAAY,QAAO,OAAM,SAAS,SAAS,EAAE,CAAC,CAAC,CAAC;AAC9E;AAEA,SAAgB,iBAAiB,OAA0B,YAA6C;CACtG,OAAO,MAAM,KAAK,UAAU,KAAK;AACnC;AAiBA,SAAgB,YAAY,MAAoB,IAAoB;CAAE,OAAO,GAAG,KAAK,GAAG;AAAK;AAC7F,SAAgB,qBAAqB,OAAoC;CACvE,IAAI,OAAO,UAAU,YAAY,CAAC,SAAS,oBAAoB,KAAK,KAAK,GAAG;CAC5E,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,OAAO,qBAAqB;EAChD,IAAI,IAAI,YAAY,IAAI,UAAU;EAClC,IAAK,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,WAAW,IAAI,KAAM,MAAM,WAAW,GAAG,GAC5E,OAAO,IAAI,WAAW,wBAAwB,QAAQ,KAAA;EAExD,OAAO,eAAe,KAAK,KAAK,KAAK,IAAI,aAAa,WAAW,QAAQ,KAAA;CAC3E,QAAQ;EAAE;CAAO;AACnB;AACA,SAAgB,gBAAgB,OAAuB;CACrD,MAAM,MAAM,IAAI,IAAI,KAAK;CACzB,IAAI,IAAI,aAAa,YAAY,IAAI,YAAY,IAAI,YAAY,IAAI,UAAU,IAAI,MACjF,MAAM,IAAI,MAAM,qCAAqC;CAEvD,OAAO,IAAI,KAAK,QAAQ,QAAQ,EAAE;AACpC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FetchProviderFactory, ProviderDescriptor, ProviderOptions, SearchProviderFactory, WebSettings, WebStatus } from "./contracts.js";
|
|
2
2
|
import { WebFetchProvider, WebSearchProvider } from "@deepseek-ai/dsh-web";
|
|
3
|
+
import { WEB_PROVIDER_AVAILABILITY_EVENT } from "@klarkxy/dsh-plugin-kit/contracts";
|
|
3
4
|
import { Context } from "@deepseek-ai/cordis";
|
|
4
5
|
//#region src/web-registry.d.ts
|
|
5
6
|
interface WebRegistry {
|
|
@@ -67,7 +68,7 @@ declare function apply(ctx: Context): Promise<void>;
|
|
|
67
68
|
declare module '@deepseek-ai/cordis' {
|
|
68
69
|
interface Events {
|
|
69
70
|
/** Repository extension: native providers announce a local availability change. */
|
|
70
|
-
|
|
71
|
+
[WEB_PROVIDER_AVAILABILITY_EVENT](kind: 'search' | 'fetch', id: string): void;
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WEB_MANAGER_OWNER, WEB_SEARCH_RPC_CHANNEL, defaultSettings, migrateSearchOrder, pickActiveSearch, providerKey, resolveSearchOrder, safeConfigurationUrl, validateBaseURL } from "./contracts.js";
|
|
2
2
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
3
3
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
4
|
+
import { WEB_PROVIDER_AVAILABILITY_EVENT } from "@klarkxy/dsh-plugin-kit/contracts";
|
|
4
5
|
import { DEEPSEEK_DEFAULT_API_VERSION, DEEPSEEK_DEFAULT_BASE_URL, DEEPSEEK_DEFAULT_MAX_TOKENS, DEEPSEEK_DEFAULT_MAX_USES, DEEPSEEK_DEFAULT_MODEL, DeepSeekSearchProvider } from "@deepseek-ai/dsh-web-search-deepseek";
|
|
5
6
|
import { ExaSearchProvider } from "@deepseek-ai/dsh-web-search-exa";
|
|
6
7
|
import { HttpFetchProvider } from "@deepseek-ai/dsh-web-fetch-http";
|
|
@@ -129,7 +130,7 @@ function providerError(error, secrets = []) {
|
|
|
129
130
|
return new WebError(redactDescription(descriptions.join(";") || "供应商未提供错误描述。", secrets), redactDescription(code || "WEB_PROVIDER_ERROR", secrets));
|
|
130
131
|
}
|
|
131
132
|
const SEARCH_ENGINE_PAGE = "https://lite.duckduckgo.com/lite/";
|
|
132
|
-
const MAX_RESPONSE_BYTES$
|
|
133
|
+
const MAX_RESPONSE_BYTES$1 = 2e6;
|
|
133
134
|
const USER_AGENT = "Mozilla/5.0 (compatible; dsh-editor-search/1.0)";
|
|
134
135
|
function searchEngineUrl(query, page = SEARCH_ENGINE_PAGE) {
|
|
135
136
|
const url = new URL(page);
|
|
@@ -196,7 +197,7 @@ async function readText(response, signal) {
|
|
|
196
197
|
const { value, done } = await reader.read();
|
|
197
198
|
if (done) break;
|
|
198
199
|
size += value.length;
|
|
199
|
-
if (size > MAX_RESPONSE_BYTES$
|
|
200
|
+
if (size > MAX_RESPONSE_BYTES$1) throw new Error("Response too large");
|
|
200
201
|
chunks.push(value);
|
|
201
202
|
}
|
|
202
203
|
} finally {
|
|
@@ -252,10 +253,10 @@ var SearchEngineProvider = class {
|
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
};
|
|
255
|
-
function record
|
|
256
|
+
function record(value) {
|
|
256
257
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
257
258
|
}
|
|
258
|
-
async function readJSON
|
|
259
|
+
async function readJSON(response, signal) {
|
|
259
260
|
const reader = response.body?.getReader();
|
|
260
261
|
if (!reader) throw new Error("Missing response");
|
|
261
262
|
let size = 0;
|
|
@@ -287,7 +288,7 @@ function httpsEndpoint(base, path = "") {
|
|
|
287
288
|
return `${url.href.replace(/\/+$/, "")}${path}`;
|
|
288
289
|
}
|
|
289
290
|
function sourceFrom(raw, urlKey, titleKey, snippetKey) {
|
|
290
|
-
const row = record
|
|
291
|
+
const row = record(raw);
|
|
291
292
|
if (typeof row?.[urlKey] !== "string") return;
|
|
292
293
|
let url;
|
|
293
294
|
try {
|
|
@@ -351,7 +352,7 @@ function createRestSearch(spec) {
|
|
|
351
352
|
await response.body?.cancel().catch(() => {});
|
|
352
353
|
throw new HttpStatusError(response.status);
|
|
353
354
|
}
|
|
354
|
-
const sources = spec.parse(await readJSON
|
|
355
|
+
const sources = spec.parse(await readJSON(response, signal));
|
|
355
356
|
return {
|
|
356
357
|
sources: sources.slice(0, count),
|
|
357
358
|
truncated: sources.length > count
|
|
@@ -374,7 +375,7 @@ const BraveSearchProvider = createRestSearch({
|
|
|
374
375
|
count: String(count)
|
|
375
376
|
}),
|
|
376
377
|
parse(payload) {
|
|
377
|
-
const results = record
|
|
378
|
+
const results = record(record(payload)?.web)?.results;
|
|
378
379
|
return Array.isArray(results) ? results.flatMap((row) => sourceFrom(row, "url", "title", "description") ?? []) : [];
|
|
379
380
|
}
|
|
380
381
|
});
|
|
@@ -394,7 +395,7 @@ const BochaSearchProvider = createRestSearch({
|
|
|
394
395
|
summary: false
|
|
395
396
|
}),
|
|
396
397
|
parse(payload) {
|
|
397
|
-
const pages = record
|
|
398
|
+
const pages = record(record(record(payload)?.data)?.webPages)?.value ?? record(record(payload)?.webPages)?.value;
|
|
398
399
|
return Array.isArray(pages) ? pages.flatMap((row) => sourceFrom(row, "url", "name", "snippet") ?? []) : [];
|
|
399
400
|
}
|
|
400
401
|
});
|
|
@@ -412,7 +413,7 @@ const SerperSearchProvider = createRestSearch({
|
|
|
412
413
|
num: count
|
|
413
414
|
}),
|
|
414
415
|
parse(payload) {
|
|
415
|
-
const organic = record
|
|
416
|
+
const organic = record(payload)?.organic;
|
|
416
417
|
return Array.isArray(organic) ? organic.flatMap((row) => sourceFrom(row, "link", "title", "snippet") ?? []) : [];
|
|
417
418
|
}
|
|
418
419
|
});
|
|
@@ -430,47 +431,12 @@ const FirecrawlSearchProvider = createRestSearch({
|
|
|
430
431
|
limit: count
|
|
431
432
|
}),
|
|
432
433
|
parse(payload) {
|
|
433
|
-
const data = record
|
|
434
|
+
const data = record(payload)?.data;
|
|
434
435
|
return Array.isArray(data) ? data.flatMap((row) => sourceFrom(row, "url", "title", "description") ?? []) : [];
|
|
435
436
|
}
|
|
436
437
|
});
|
|
437
438
|
//#endregion
|
|
438
439
|
//#region src/tavily.ts
|
|
439
|
-
const MAX_RESPONSE_BYTES = 2e6;
|
|
440
|
-
function endpoint(base) {
|
|
441
|
-
const url = new URL(base);
|
|
442
|
-
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error("Invalid endpoint");
|
|
443
|
-
return `${url.href.replace(/\/+$/, "")}/search`;
|
|
444
|
-
}
|
|
445
|
-
function record(value) {
|
|
446
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
447
|
-
}
|
|
448
|
-
async function readJSON(response, signal) {
|
|
449
|
-
const reader = response.body?.getReader();
|
|
450
|
-
if (!reader) throw new Error("Missing response");
|
|
451
|
-
let size = 0;
|
|
452
|
-
const chunks = [];
|
|
453
|
-
try {
|
|
454
|
-
while (true) {
|
|
455
|
-
signal?.throwIfAborted();
|
|
456
|
-
const { value, done } = await reader.read();
|
|
457
|
-
if (done) break;
|
|
458
|
-
size += value.length;
|
|
459
|
-
if (size > MAX_RESPONSE_BYTES) throw new Error("Response too large");
|
|
460
|
-
chunks.push(value);
|
|
461
|
-
}
|
|
462
|
-
const bytes = new Uint8Array(size);
|
|
463
|
-
let offset = 0;
|
|
464
|
-
for (const chunk of chunks) {
|
|
465
|
-
bytes.set(chunk, offset);
|
|
466
|
-
offset += chunk.length;
|
|
467
|
-
}
|
|
468
|
-
return JSON.parse(new TextDecoder().decode(bytes));
|
|
469
|
-
} finally {
|
|
470
|
-
await reader.cancel().catch(() => {});
|
|
471
|
-
reader.releaseLock();
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
440
|
/** Direct REST adapter. No auxiliary LLM, retries, anonymous fallback or auto-depth. */
|
|
475
441
|
var TavilySearchProvider = class {
|
|
476
442
|
options;
|
|
@@ -480,7 +446,7 @@ var TavilySearchProvider = class {
|
|
|
480
446
|
}
|
|
481
447
|
available() {
|
|
482
448
|
try {
|
|
483
|
-
|
|
449
|
+
httpsEndpoint(this.options.baseURL ?? "https://api.tavily.com", "/search");
|
|
484
450
|
return Boolean(this.options.apiKey.trim());
|
|
485
451
|
} catch {
|
|
486
452
|
return false;
|
|
@@ -493,7 +459,7 @@ var TavilySearchProvider = class {
|
|
|
493
459
|
if (!Number.isInteger(count) || count < 1 || count > 20) throw new WebError("结果数须为 1 至 20。", "WEB_INVALID_REQUEST");
|
|
494
460
|
try {
|
|
495
461
|
signal?.throwIfAborted();
|
|
496
|
-
const response = await (this.options.fetch ?? fetch)(
|
|
462
|
+
const response = await (this.options.fetch ?? fetch)(httpsEndpoint(this.options.baseURL ?? "https://api.tavily.com", "/search"), {
|
|
497
463
|
method: "POST",
|
|
498
464
|
redirect: "error",
|
|
499
465
|
signal,
|
|
@@ -522,18 +488,12 @@ var TavilySearchProvider = class {
|
|
|
522
488
|
const sources = [];
|
|
523
489
|
for (const raw of payload.results) {
|
|
524
490
|
const row = record(raw);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
url = new URL(row.url);
|
|
529
|
-
} catch {
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password) continue;
|
|
491
|
+
const source = sourceFrom(row, "url", "title", "content");
|
|
492
|
+
if (!source || !row) continue;
|
|
533
493
|
sources.push({
|
|
534
|
-
|
|
535
|
-
...
|
|
536
|
-
...
|
|
494
|
+
...source,
|
|
495
|
+
...row.title === "" ? { title: "" } : {},
|
|
496
|
+
...row.content === "" ? { snippet: "" } : {},
|
|
537
497
|
...typeof row.published_date === "string" && Number.isFinite(Date.parse(row.published_date)) ? { publishedAt: new Date(row.published_date).toISOString() } : {}
|
|
538
498
|
});
|
|
539
499
|
}
|
|
@@ -1298,7 +1258,7 @@ async function apply(ctx) {
|
|
|
1298
1258
|
ctx.on("credentials/reference-updated", () => {
|
|
1299
1259
|
manager.refresh();
|
|
1300
1260
|
});
|
|
1301
|
-
ctx.on(
|
|
1261
|
+
ctx.on(WEB_PROVIDER_AVAILABILITY_EVENT, () => {
|
|
1302
1262
|
manager.refresh();
|
|
1303
1263
|
});
|
|
1304
1264
|
ctx.effect(() => registerBuiltins(ctx.web, {
|