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 +72 -9
- package/lib/index.js +46 -11
- package/package.json +2 -2
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.
|
|
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)
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
483
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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": {
|