dsh-free-search 0.4.37 → 0.4.39

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/lib/client.js CHANGED
@@ -96,6 +96,7 @@ window.__ModuleLoader__.load({
96
96
  getKey: "获取 API Key →",
97
97
  engineHint: "Bing 是最稳定的免费引擎。DuckDuckGo 在共享 IP 上可能限流。API KEY 引擎需在下方填写凭据。",
98
98
  apiKeys: "API 密钥(可选)",
99
+ anysearchPh: (c) => c ? "AnySearch API 密钥(已配置)" : "AnySearch API 密钥(可选,不填免费匿名,填了提额)",
99
100
  exaPh: (c) => c ? "Exa API 密钥(已配置)" : "Exa API 密钥(可选,不填也可免费使用)",
100
101
  tavilyPh: (c) => c ? "Tavily API 密钥(已配置)" : "Tavily API 密钥(可选,不填也可免费使用)",
101
102
  keenablePh: (c) => c ? "Keenable API 密钥(已配置)" : "Keenable API 密钥(可选,不填也可免费使用)",
@@ -108,7 +109,7 @@ window.__ModuleLoader__.load({
108
109
  keyStorage: "Key 存储位置",
109
110
  keyStorageCred: "凭据中心(推荐)",
110
111
  keyStorageSettings: "设置页(兼容)",
111
- keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
112
+ keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
112
113
  keyStorageSettingsHint: "保存后写入当前 profile 的插件条目 config(cordis.patch.yml,优先级低于凭据中心)。",
113
114
  platformSearch: "平台搜索(platform_search 工具)",
114
115
  platformHint: "为 agent 的 platform_search 工具启用平台。禁用的平台会被跳过。",
@@ -116,6 +117,7 @@ window.__ModuleLoader__.load({
116
117
  cacheTtlHint: "0 关闭缓存,最长 5 分钟。缩短可加快时效,延长可防限流、省额度。",
117
118
  unavailable: "设置不可用 —— free-search 桥接未暴露。",
118
119
  saveFailed: "保存失败",
120
+ saveFailedDetail: (d) => `保存失败:${d}`,
119
121
  testing: "测试中…",
120
122
  testEngine: "测试引擎",
121
123
  useBing: "恢复 Bing 默认",
@@ -163,6 +165,7 @@ window.__ModuleLoader__.load({
163
165
  getKey: "Get API Key →",
164
166
  engineHint: "Bing is the most stable FREE engine. DuckDuckGo may rate-limit on shared IPs. API KEY engines need credentials below.",
165
167
  apiKeys: "API keys (optional)",
168
+ anysearchPh: (c) => c ? "AnySearch API key (configured)" : "AnySearch API key (optional, free anonymous without; key raises quota)",
166
169
  exaPh: (c) => c ? "Exa API key (configured)" : "Exa API key (optional, free without)",
167
170
  tavilyPh: (c) => c ? "Tavily API key (configured)" : "Tavily API key (optional, free without)",
168
171
  keenablePh: (c) => c ? "Keenable API key (configured)" : "Keenable API key (optional, free without)",
@@ -175,7 +178,7 @@ window.__ModuleLoader__.load({
175
178
  keyStorage: "Key storage",
176
179
  keyStorageCred: "Credential center (recommended)",
177
180
  keyStorageSettings: "Settings page (legacy)",
178
- keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
181
+ keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
179
182
  keyStorageSettingsHint: "Saved to the active profile plugin entry config (cordis.patch.yml; lower priority than the credential center).",
180
183
  platformSearch: "Platform search (platform_search tool)",
181
184
  platformHint: "Enable platforms for the agent's platform_search tool. Disabled platforms are skipped.",
@@ -183,6 +186,7 @@ window.__ModuleLoader__.load({
183
186
  cacheTtlHint: "0 disables caching, max 5 minutes. Lower = fresher results, higher = less rate-limiting / fewer credits used.",
184
187
  unavailable: "Settings unavailable — the free-search bridge is not exposed.",
185
188
  saveFailed: "save failed",
189
+ saveFailedDetail: (d) => `save failed: ${d}`,
186
190
  testing: "Testing…",
187
191
  testEngine: "Test engine",
188
192
  useBing: "Use Bing default",
@@ -225,7 +229,7 @@ window.__ModuleLoader__.load({
225
229
  };
226
230
  const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
227
231
  // 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致)
228
- const PLUGIN_VERSION = "0.4.37";
232
+ const PLUGIN_VERSION = "0.4.39";
229
233
  const ENGINES = [
230
234
  { id: "ddg", label: "DuckDuckGo · HTML", badge: "FREE", link: "https://duckduckgo.com" },
231
235
  { id: "ddg-lite", label: "DuckDuckGo · Lite", badge: "FREE", link: "https://duckduckgo.com" },
@@ -342,6 +346,7 @@ window.__ModuleLoader__.load({
342
346
  const [provider, setProvider] = react.useState("bing");
343
347
  const [safeSearch, setSafeSearch] = react.useState("off");
344
348
  const [bingMarket, setBingMarket] = react.useState("zh-CN");
349
+ const [anysearchKey, setAnysearchKey] = react.useState("");
345
350
  const [exaKey, setExaKey] = react.useState("");
346
351
  const [tavilyKey, setTavilyKey] = react.useState("");
347
352
  const [keenableKey, setKeenableKey] = react.useState("");
@@ -361,6 +366,8 @@ window.__ModuleLoader__.load({
361
366
  const [dirty, setDirty] = react.useState(false);
362
367
  const [saving, setSaving] = react.useState(false);
363
368
  const [failed, setFailed] = react.useState(false);
369
+ // 保存失败明细(credentials-set / mutate 的 code+message),成功或改表单时清空
370
+ const [saveError, setSaveError] = react.useState("");
364
371
  const [testing, setTesting] = react.useState(false);
365
372
  const [testResult, setTestResult] = react.useState(null);
366
373
  const [checkingUpdate, setCheckingUpdate] = react.useState(false);
@@ -378,6 +385,7 @@ window.__ModuleLoader__.load({
378
385
  setSafeSearch(v.safeSearch === "strict" || v.safeSearch === "moderate" ? v.safeSearch : "off");
379
386
  setBingMarket(v.bingMarket === undefined ? "zh-CN" : v.bingMarket);
380
387
  setLang(v.lang === "en" ? "en" : "zh");
388
+ setAnysearchKey(v.anysearchApiKey ?? "");
381
389
  setExaKey(v.exaApiKey ?? "");
382
390
  setTavilyKey(v.tavilyApiKey ?? "");
383
391
  setKeenableKey(v.keenableApiKey ?? "");
@@ -393,6 +401,7 @@ window.__ModuleLoader__.load({
393
401
  for (const secret of view.secrets ?? []) {
394
402
  if (secret.set) {
395
403
  const path = secret.path.join(".");
404
+ if (path === "anysearchApiKey") configured.anysearch = true;
396
405
  if (path === "exaApiKey") configured.exa = true;
397
406
  if (path === "tavilyApiKey") configured.tavily = true;
398
407
  if (path === "keenableApiKey") configured.keenable = true;
@@ -412,7 +421,7 @@ window.__ModuleLoader__.load({
412
421
  const cred = await bridgeCredentialsStatus();
413
422
  if (cred.ok) {
414
423
  const cc = {};
415
- const map = { exaApiKey: "exa", tavilyApiKey: "tavily", keenableApiKey: "keenable", firecrawlApiKey: "firecrawl", parallelApiKey: "parallel", perplexityApiKey: "perplexity", deepseekApiKey: "deepseek", serpbaseApiKey: "serpbase" };
424
+ const map = { anysearchApiKey: "anysearch", exaApiKey: "exa", tavilyApiKey: "tavily", keenableApiKey: "keenable", firecrawlApiKey: "firecrawl", parallelApiKey: "parallel", perplexityApiKey: "perplexity", deepseekApiKey: "deepseek", serpbaseApiKey: "serpbase" };
416
425
  for (const [k, v] of Object.entries(cred.value.configured ?? {})) {
417
426
  if (map[k]) cc[map[k]] = v;
418
427
  }
@@ -438,14 +447,18 @@ window.__ModuleLoader__.load({
438
447
  setProvider(value);
439
448
  setDirty(true);
440
449
  setFailed(false);
450
+ setSaveError("");
441
451
  };
442
452
 
443
453
  const save = async () => {
444
454
  setSaving(true);
445
455
  setFailed(false);
456
+ setSaveError("");
457
+ const errors = [];
446
458
  try {
447
459
  // key 存储分流:凭据中心(默认)走 credentials-set;设置页走 settings mutate(兼容)
448
460
  const keyFields = [
461
+ ["anysearchApiKey", anysearchKey],
449
462
  ["exaApiKey", exaKey],
450
463
  ["tavilyApiKey", tavilyKey],
451
464
  ["keenableApiKey", keenableKey],
@@ -455,12 +468,15 @@ window.__ModuleLoader__.load({
455
468
  ["deepseekApiKey", deepseekKey],
456
469
  ["serpbaseApiKey", serpbaseKey],
457
470
  ];
458
- let credFailed = false;
459
471
  if (keyStorage === "credentials") {
460
472
  for (const [field, value] of keyFields) {
461
473
  if (!value.trim()) continue;
462
474
  const r = await bridgeCredentialsSet(field, value.trim());
463
- if (!r.ok) credFailed = true;
475
+ if (!r || !r.ok) {
476
+ const code = (r && r.code) || "error";
477
+ const msg = (r && r.message) || "";
478
+ errors.push(`credentials-set ${field}: ${code}${msg ? " — " + msg : ""}`);
479
+ }
464
480
  }
465
481
  }
466
482
  const ops = [{ op: "set", path: ["provider"], value: provider }];
@@ -477,16 +493,28 @@ window.__ModuleLoader__.load({
477
493
  ops.push({ op: "set", path: ["platforms"], value: platforms });
478
494
  ops.push({ op: "set", path: ["cacheTtl"], value: Math.min(Math.max(Number(cacheTtl) ?? 5, 0), 5) });
479
495
  const result = await bridgeMutate({ ns: NS, ops });
480
- if (result.ok && !credFailed) {
496
+ if (!result || !result.ok) {
497
+ const code = (result && result.code) || "error";
498
+ const msg = (result && result.message) || "";
499
+ errors.push(`mutate: ${code}${msg ? " — " + msg : ""}`);
500
+ }
501
+ if (errors.length === 0) {
481
502
  setDirty(false);
482
503
  setProvider(result.value.value.provider ?? provider);
483
504
  setFailed(false);
505
+ setSaveError("");
484
506
  load();
485
507
  } else {
508
+ const detail = errors.join("; ");
486
509
  setFailed(true);
510
+ setSaveError(detail);
511
+ console.error("[dsh-free-search] save failed:", detail);
487
512
  }
488
- } catch {
513
+ } catch (e) {
514
+ const msg = e && e.message ? e.message : String(e);
489
515
  setFailed(true);
516
+ setSaveError(`exception: ${msg}`);
517
+ console.error("[dsh-free-search] save exception:", e);
490
518
  } finally {
491
519
  setSaving(false);
492
520
  }
@@ -496,12 +524,14 @@ window.__ModuleLoader__.load({
496
524
  load();
497
525
  setDirty(false);
498
526
  setFailed(false);
527
+ setSaveError("");
499
528
  };
500
529
 
501
530
  const runTest = async () => {
502
531
  setTesting(true);
503
532
  setTestResult(null);
504
533
  setFailed(false);
534
+ setSaveError("");
505
535
  try {
506
536
  const result = await bridgeRawSearch({
507
537
  query: "DeepSeek Harness",
@@ -531,6 +561,7 @@ window.__ModuleLoader__.load({
531
561
  setCheckingUpdate(true);
532
562
  setUpdateInfo(null);
533
563
  setFailed(false);
564
+ setSaveError("");
534
565
  try {
535
566
  const result = await bridgeCheckUpdate();
536
567
  if (result.ok) {
@@ -549,6 +580,7 @@ window.__ModuleLoader__.load({
549
580
  setUpgrading(true);
550
581
  setUpdateInfo(null);
551
582
  setFailed(false);
583
+ setSaveError("");
552
584
  try {
553
585
  const response = await fetch(`${BRIDGE_PREFIX}/update`, {
554
586
  method: "POST",
@@ -581,6 +613,7 @@ window.__ModuleLoader__.load({
581
613
  setLang((prev) => (prev === "en" ? "zh" : "en"));
582
614
  setDirty(true);
583
615
  setFailed(false);
616
+ setSaveError("");
584
617
  };
585
618
 
586
619
  return react_jsx_runtime.jsx("li", {
@@ -719,6 +752,19 @@ react_jsx_runtime.jsx("div", {
719
752
  className: "dshfs-label",
720
753
  children: t.apiKeys,
721
754
  }),
755
+ react_jsx_runtime.jsx("input", {
756
+ className: "dshfs-input",
757
+ type: "password",
758
+ placeholder: t.anysearchPh(keysConfigured.anysearch),
759
+ value: anysearchKey,
760
+ disabled: !ready || saving,
761
+ onChange: (e) => {
762
+ setAnysearchKey(e.target.value);
763
+ setDirty(true);
764
+ setFailed(false);
765
+ setSaveError("");
766
+ },
767
+ }),
722
768
  react_jsx_runtime.jsx("input", {
723
769
  className: "dshfs-input",
724
770
  type: "password",
@@ -729,6 +775,7 @@ react_jsx_runtime.jsx("div", {
729
775
  setExaKey(e.target.value);
730
776
  setDirty(true);
731
777
  setFailed(false);
778
+ setSaveError("");
732
779
  },
733
780
  }),
734
781
  react_jsx_runtime.jsx("input", {
@@ -741,6 +788,7 @@ react_jsx_runtime.jsx("div", {
741
788
  setTavilyKey(e.target.value);
742
789
  setDirty(true);
743
790
  setFailed(false);
791
+ setSaveError("");
744
792
  },
745
793
  }),
746
794
  react_jsx_runtime.jsx("input", {
@@ -753,6 +801,7 @@ react_jsx_runtime.jsx("div", {
753
801
  setKeenableKey(e.target.value);
754
802
  setDirty(true);
755
803
  setFailed(false);
804
+ setSaveError("");
756
805
  },
757
806
  }),
758
807
  react_jsx_runtime.jsx("input", {
@@ -765,6 +814,7 @@ react_jsx_runtime.jsx("div", {
765
814
  setFirecrawlKey(e.target.value);
766
815
  setDirty(true);
767
816
  setFailed(false);
817
+ setSaveError("");
768
818
  },
769
819
  }),
770
820
  react_jsx_runtime.jsx("input", {
@@ -777,6 +827,7 @@ react_jsx_runtime.jsx("div", {
777
827
  setParallelKey(e.target.value);
778
828
  setDirty(true);
779
829
  setFailed(false);
830
+ setSaveError("");
780
831
  },
781
832
  }),
782
833
  react_jsx_runtime.jsx("input", {
@@ -789,6 +840,7 @@ react_jsx_runtime.jsx("div", {
789
840
  setPerplexityKey(e.target.value);
790
841
  setDirty(true);
791
842
  setFailed(false);
843
+ setSaveError("");
792
844
  },
793
845
  }),
794
846
  react_jsx_runtime.jsx("input", {
@@ -801,6 +853,7 @@ react_jsx_runtime.jsx("div", {
801
853
  setSerpbaseKey(e.target.value);
802
854
  setDirty(true);
803
855
  setFailed(false);
856
+ setSaveError("");
804
857
  },
805
858
  }),
806
859
  react_jsx_runtime.jsx("input", {
@@ -813,6 +866,7 @@ react_jsx_runtime.jsx("div", {
813
866
  setDeepseekKey(e.target.value);
814
867
  setDirty(true);
815
868
  setFailed(false);
869
+ setSaveError("");
816
870
  },
817
871
  }),
818
872
  react_jsx_runtime.jsx("p", {
@@ -835,6 +889,7 @@ react_jsx_runtime.jsx("div", {
835
889
  setKeyStorage(e.target.value);
836
890
  setDirty(true);
837
891
  setFailed(false);
892
+ setSaveError("");
838
893
  },
839
894
  children: [
840
895
  react_jsx_runtime.jsx("option", { value: "credentials", children: t.keyStorageCred }),
@@ -875,6 +930,7 @@ react_jsx_runtime.jsx("div", {
875
930
  );
876
931
  setDirty(true);
877
932
  setFailed(false);
933
+ setSaveError("");
878
934
  },
879
935
  }),
880
936
  label,
@@ -907,6 +963,7 @@ react_jsx_runtime.jsx("div", {
907
963
  setCacheTtl(Number(e.target.value));
908
964
  setDirty(true);
909
965
  setFailed(false);
966
+ setSaveError("");
910
967
  },
911
968
  }),
912
969
  react_jsx_runtime.jsx("p", {
@@ -918,7 +975,12 @@ react_jsx_runtime.jsx("div", {
918
975
  react_jsx_runtime.jsx("div", {
919
976
  className: "dshfs-resultRow",
920
977
  children: [
921
- failed ? react_jsx_runtime.jsx("span", { className: "dshfs-failed", children: t.saveFailed }) : null,
978
+ failed
979
+ ? react_jsx_runtime.jsx("span", {
980
+ className: "dshfs-failed",
981
+ children: saveError ? t.saveFailedDetail(saveError) : t.saveFailed,
982
+ })
983
+ : null,
922
984
  testResult
923
985
  ? react_jsx_runtime.jsx("span", {
924
986
  className: testResult.ok ? "dshfs-testOk" : "dshfs-failed",
@@ -1041,6 +1103,7 @@ react_jsx_runtime.jsx("div", {
1041
1103
  setProvider("bing");
1042
1104
  setDirty(true);
1043
1105
  setFailed(false);
1106
+ setSaveError("");
1044
1107
  },
1045
1108
  disabled: saving || !ready || provider === "bing",
1046
1109
  children: t.useBing,
package/lib/index.js CHANGED
@@ -55,7 +55,7 @@ const FREE_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch"];
55
55
  const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official"];
56
56
 
57
57
  // 当前插件版本(发布时与 package.json 同步)
58
- const PLUGIN_VERSION = "0.4.37";
58
+ const PLUGIN_VERSION = "0.4.39";
59
59
  // 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
60
60
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
61
61
  const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
@@ -479,20 +479,39 @@ async function searchSearxng(query, maxResults, options, signal) {
479
479
  const ANYSEARCH_URL = "https://api.anysearch.com/v1/search";
480
480
  const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
481
481
 
482
- // AnySearch: 免费匿名额度(无 key),结构化 JSON 结果
483
- async function searchAnysearch(query, maxResults, signal) {
482
+ // AnySearch: 免费匿名额度(无 key),可选 Bearer key 提额;结构化 JSON 结果
483
+ // 401/403:本进程忽略该 key 值(不删除存储),改回匿名免费;不自动清空用户配置
484
+ let ignoredAnysearchKey = "";
485
+ const ANYSEARCH_KEY_INVALID_NOTE = "AnySearch key 无效,本次已忽略该 key,改回免费匿名";
486
+
487
+ async function searchAnysearch(query, maxResults, signal, apiKey) {
488
+ const trimmedKey = typeof apiKey === "string" ? apiKey.trim() : "";
489
+ // 本进程已判定无效的 key 不再发送;若配置里仍有该值,结果仍带忽略提示
490
+ const keyIgnoredSticky = Boolean(trimmedKey) && trimmedKey === ignoredAnysearchKey;
491
+ const sendKey = trimmedKey && !keyIgnoredSticky ? trimmedKey : "";
484
492
  const controller = new AbortController();
485
493
  const timer = setTimeout(() => controller.abort(), 12000);
486
494
  const onAbort = () => controller.abort();
487
495
  signal?.addEventListener("abort", onAbort);
488
- let response;
489
- try {
490
- response = await fetch(ANYSEARCH_URL, {
496
+ const doFetch = async (key) => {
497
+ const headers = { "content-type": "application/json" };
498
+ if (key) headers.authorization = `Bearer ${key}`;
499
+ return await fetch(ANYSEARCH_URL, {
491
500
  method: "POST",
492
- headers: { "content-type": "application/json" },
501
+ headers,
493
502
  body: JSON.stringify({ query, max_results: maxResults ?? 5 }),
494
503
  signal: controller.signal,
495
504
  });
505
+ };
506
+ let response;
507
+ let keyRejected = false;
508
+ try {
509
+ response = await doFetch(sendKey);
510
+ if ((response.status === 401 || response.status === 403) && sendKey) {
511
+ ignoredAnysearchKey = sendKey;
512
+ keyRejected = true;
513
+ response = await doFetch("");
514
+ }
496
515
  } catch (error) {
497
516
  if (signal?.aborted) throw error;
498
517
  throw new Error(`AnySearch request failed: ${error?.message ?? String(error)}`);
@@ -500,6 +519,9 @@ async function searchAnysearch(query, maxResults, signal) {
500
519
  clearTimeout(timer);
501
520
  signal?.removeEventListener("abort", onAbort);
502
521
  }
522
+ if (response.status === 401 || response.status === 403) {
523
+ throw new Error(keyRejected ? `${ANYSEARCH_KEY_INVALID_NOTE} (HTTP ${response.status})` : `AnySearch API error (HTTP ${response.status})`);
524
+ }
503
525
  if (!response.ok) throw new Error(`AnySearch API error (HTTP ${response.status})`);
504
526
  const data = await response.json();
505
527
  if (data.code !== 0) throw new Error(`AnySearch API error: ${data.message ?? data.code}`);
@@ -513,6 +535,7 @@ async function searchAnysearch(query, maxResults, signal) {
513
535
  ...(r.snippet ? { snippet: String(r.snippet).slice(0, 300) } : {}),
514
536
  })),
515
537
  truncated: false,
538
+ ...(keyRejected || keyIgnoredSticky ? { content: ANYSEARCH_KEY_INVALID_NOTE } : {}),
516
539
  };
517
540
  }
518
541
 
@@ -1611,6 +1634,10 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
1611
1634
  const expectedRevision = typeof body.expectedRevision === "number" ? body.expectedRevision : undefined;
1612
1635
  try {
1613
1636
  await settings.mutate(ns, body.ops, expectedRevision);
1637
+ // 设置页保存了 AnySearch key:允许下次再试(不删除存储,仅解除本进程忽略)
1638
+ if (body.ops.some((op) => op && op.op === "set" && Array.isArray(op.path) && op.path[0] === "anysearchApiKey")) {
1639
+ ignoredAnysearchKey = "";
1640
+ }
1614
1641
  } catch (error) {
1615
1642
  if (error instanceof SettingsConflictError) {
1616
1643
  return { ok: false, code: "settings-conflict", message: error.message };
@@ -1651,6 +1678,8 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
1651
1678
  }
1652
1679
  try {
1653
1680
  await credentials.set(ref, value.trim());
1681
+ // 用户写入新 AnySearch key:允许下次请求再试(仅忽略同值坏 key,不删除存储)
1682
+ if (key === "anysearchApiKey") ignoredAnysearchKey = "";
1654
1683
  return { ok: true, value: { ref, set: true } };
1655
1684
  } catch (error) {
1656
1685
  return { ok: false, code: "credentials-write-failed", message: error instanceof Error ? error.message : String(error) };
@@ -1778,6 +1807,7 @@ const inject = ["web"];
1778
1807
 
1779
1808
  // 引擎 key 与凭据中心 ref 的映射(白名单:只允许这些 ref 被 UI 读写凭据中心)
1780
1809
  const KEY_REF_MAP = {
1810
+ anysearchApiKey: "ANYSEARCH_API_KEY",
1781
1811
  exaApiKey: "EXA_API_KEY",
1782
1812
  tavilyApiKey: "TAVILY_API_KEY",
1783
1813
  keenableApiKey: "KEENABLE_API_KEY",
@@ -1803,6 +1833,7 @@ const Config = z.object({
1803
1833
  safeSearch: z.string().default("off").volatile(),
1804
1834
  searxngInstances: z.array(z.string()).volatile(),
1805
1835
  platforms: z.array(z.string()).default(["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]).volatile(),
1836
+ anysearchApiKey: z.string().role("secret").volatile(),
1806
1837
  exaApiKey: z.string().role("secret").volatile(),
1807
1838
  tavilyApiKey: z.string().role("secret").volatile(),
1808
1839
  keenableApiKey: z.string().role("secret").volatile(),
@@ -1945,7 +1976,8 @@ function apply(ctx, config) {
1945
1976
  } else if (engine === "searxng") {
1946
1977
  result = await searchSearxng(request.query, request.maxResults, { ...cfg, timeRange }, effSignal);
1947
1978
  } else if (engine === "anysearch") {
1948
- result = await searchAnysearch(request.query, request.maxResults, effSignal);
1979
+ const key = await resolveApiKey("ANYSEARCH_API_KEY", "anysearchApiKey");
1980
+ result = await searchAnysearch(request.query, request.maxResults, effSignal, key);
1949
1981
  } else if (engine === "exa") {
1950
1982
  // exa:有 key 走 REST,无 key 走 keyless MCP(免费)
1951
1983
  const key = await resolveApiKey("EXA_API_KEY", "exaApiKey");
@@ -2105,8 +2137,10 @@ function apply(ctx, config) {
2105
2137
  return await searchBing(q, 2, cfg);
2106
2138
  case "searxng":
2107
2139
  return await searchSearxng(q, 2, { ...cfg, timeRange: tr });
2108
- case "anysearch":
2109
- return await searchAnysearch(q, 2);
2140
+ case "anysearch": {
2141
+ const key = await resolveApiKey("ANYSEARCH_API_KEY", "anysearchApiKey");
2142
+ return await searchAnysearch(q, 2, undefined, key);
2143
+ }
2110
2144
  case "exa": {
2111
2145
  const key = await resolveApiKey("EXA_API_KEY", "exaApiKey");
2112
2146
  if (key) return await searchExa(q, 2, key, tr);
@@ -2163,6 +2197,7 @@ function apply(ctx, config) {
2163
2197
  s.snippet ? { ...s, snippet: cleanSnippet(s.snippet) } : s
2164
2198
  ),
2165
2199
  truncated: result.truncated ?? false,
2200
+ ...(typeof result.content === "string" && result.content ? { content: result.content } : {}),
2166
2201
  };
2167
2202
  } catch (error) {
2168
2203
  return { ok: false, error: error instanceof Error ? error.message : String(error) };
@@ -2459,7 +2494,7 @@ function apply(ctx, config) {
2459
2494
  "- ddg-lite (DuckDuckGo Lite) - FREE, no key (may be rate-limited)",
2460
2495
  "- bing (Bing) - FREE, no key (most stable)",
2461
2496
  "- searxng (meta-search, multi-instance) - FREE, no key",
2462
- "- anysearch (AI search) - FREE, no key",
2497
+ "- anysearch (AI search) - FREE no key, or optional ANYSEARCH_API_KEY for higher limits",
2463
2498
  "- exa - FREE keyless (MCP) or EXA_API_KEY for higher limits",
2464
2499
  "- tavily - FREE keyless or TAVILY_API_KEY for higher limits",
2465
2500
  "- keenable - FREE keyless (MCP) or KEENABLE_API_KEY for higher limits",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-free-search",
3
- "version": "0.4.37",
4
- "description": "Free web search for DeepSeek Harness: 13 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Perplexity/SerpBase/DeepSeek with key) + time filtering + platform search + web_fetch, with web settings UI.",
3
+ "version": "0.4.39",
4
+ "description": "Free web search for DeepSeek Harness: 13 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Perplexity/SerpBase/DeepSeek with key) + optional AnySearch key for higher quota + time filtering + platform search + web_fetch, with web settings UI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {