dsh-free-search 0.4.18 → 0.4.20
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 +80 -80
- package/lib/index.js +126 -124
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
2
|
id: "dsh-free-search",
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
@@ -30,8 +30,8 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
".dshfs-label{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}",
|
|
31
31
|
".dshfs-select{border:1px solid var(--dsw-alias-border-l2);font:inherit;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border-radius:6px;padding:6px 8px;font-size:13px;transition:border-color .13s,box-shadow .13s;width:100%}",
|
|
32
32
|
".dshfs-select:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed)}",
|
|
33
|
-
//
|
|
34
|
-
// color-scheme
|
|
33
|
+
// 涓嬫媺閫夐」鍒楄〃閰嶈壊閿佹锛氫笉闅忕毊鑲ゅ彉閲忓彉鍖栵紙鐨偆鍙奖鍝?select 妗嗕綋鏈韩锛?
|
|
34
|
+
// color-scheme 璁╂祻瑙堝櫒鍘熺敓涓嬫媺鎸変富棰樻覆鏌擄紱option 鏄惧紡鍥哄畾搴曡壊/鏂囧瓧鑹插厹搴?
|
|
35
35
|
".dshfs-select{color-scheme:light dark}",
|
|
36
36
|
".dshfs-select option,.dshfs-select optgroup{background-color:#ffffff;color:#1f2328}",
|
|
37
37
|
"@media (prefers-color-scheme:dark){.dshfs-select{color-scheme:dark}.dshfs-select option,.dshfs-select optgroup{background-color:#1e1f24;color:#e8e8ea}}",
|
|
@@ -78,57 +78,57 @@ window.__ModuleLoader__.load({
|
|
|
78
78
|
const NS = "free-search";
|
|
79
79
|
const I18N = {
|
|
80
80
|
zh: {
|
|
81
|
-
description: "
|
|
82
|
-
unsaved: "
|
|
83
|
-
searchEngine: "
|
|
84
|
-
visit: "
|
|
85
|
-
getKey: "
|
|
86
|
-
engineHint: "Bing
|
|
87
|
-
apiKeys: "API
|
|
88
|
-
exaPh: (c) => c ? "Exa API
|
|
89
|
-
tavilyPh: (c) => c ? "Tavily API
|
|
90
|
-
keenablePh: (c) => c ? "Keenable API
|
|
91
|
-
perplexityPh: (c) => c ? "Perplexity API
|
|
92
|
-
deepseekPh: (c) => c ? "DeepSeek API
|
|
93
|
-
keysHint: "
|
|
94
|
-
keyStorage: "Key
|
|
95
|
-
keyStorageCred: "
|
|
96
|
-
keyStorageSettings: "
|
|
97
|
-
keyStorageCredHint: (c) =>
|
|
98
|
-
keyStorageSettingsHint: "
|
|
99
|
-
platformSearch: "
|
|
100
|
-
platformHint: "
|
|
101
|
-
cacheTtl: "
|
|
102
|
-
cacheTtlHint: "0
|
|
103
|
-
unavailable: "
|
|
104
|
-
saveFailed: "
|
|
105
|
-
testing: "
|
|
106
|
-
testEngine: "
|
|
107
|
-
useBing: "
|
|
108
|
-
discard: "
|
|
109
|
-
saving: "
|
|
110
|
-
save: "
|
|
111
|
-
testOk: (r) =>
|
|
112
|
-
testFail: (e) =>
|
|
81
|
+
description: "鍏嶈垂鎼滅储 鈥斺€?鏃犻渶 API key锛圔ing / DuckDuckGo / AnySearch / Exa / Tavily / Keenable锛?,
|
|
82
|
+
unsaved: "鏈繚瀛?,
|
|
83
|
+
searchEngine: "鎼滅储寮曟搸",
|
|
84
|
+
visit: "璁块棶瀹樼綉 鈫?,
|
|
85
|
+
getKey: "鑾峰彇 API Key 鈫?,
|
|
86
|
+
engineHint: "Bing 鏄渶绋冲畾鐨勫厤璐瑰紩鎿庛€侱uckDuckGo 鍦ㄥ叡浜?IP 涓婂彲鑳介檺娴併€侫PI KEY 寮曟搸闇€鍦ㄤ笅鏂瑰~鍐欏嚟鎹€?,
|
|
87
|
+
apiKeys: "API 瀵嗛挜锛堝彲閫夛級",
|
|
88
|
+
exaPh: (c) => c ? "Exa API 瀵嗛挜锛堝凡閰嶇疆锛? : "Exa API 瀵嗛挜锛堝彲閫夛紝涓嶅~涔熷彲鍏嶈垂浣跨敤锛?,
|
|
89
|
+
tavilyPh: (c) => c ? "Tavily API 瀵嗛挜锛堝凡閰嶇疆锛? : "Tavily API 瀵嗛挜锛堝彲閫夛紝涓嶅~涔熷彲鍏嶈垂浣跨敤锛?,
|
|
90
|
+
keenablePh: (c) => c ? "Keenable API 瀵嗛挜锛堝凡閰嶇疆锛? : "Keenable API 瀵嗛挜锛堝彲閫夛紝涓嶅~涔熷彲鍏嶈垂浣跨敤锛?,
|
|
91
|
+
perplexityPh: (c) => c ? "Perplexity API 瀵嗛挜锛堝凡閰嶇疆锛? : "Perplexity API 瀵嗛挜锛坧plx-...锛?,
|
|
92
|
+
deepseekPh: (c) => c ? "DeepSeek API 瀵嗛挜锛堝凡閰嶇疆锛? : "DeepSeek API 瀵嗛挜锛坰k-...锛?,
|
|
93
|
+
keysHint: "瀵嗛挜璇诲彇浼樺厛绾э細.credentials.yaml 鍑嵁涓績 > 杩欓噷 > 鐜鍙橀噺銆傛帹鑽愭妸 key 鍐欒繘鍑嵁涓績锛堜笌瀹樻柟 LLM 涓€鑷达紝涓€澶勭鐞嗭級銆?,
|
|
94
|
+
keyStorage: "Key 瀛樺偍浣嶇疆",
|
|
95
|
+
keyStorageCred: "鍑嵁涓績锛堟帹鑽愶級",
|
|
96
|
+
keyStorageSettings: "璁剧疆椤碉紙鍏煎锛?,
|
|
97
|
+
keyStorageCredHint: (c) => `淇濆瓨鍚庡啓鍏?~/.dsh/.credentials.yaml锛堟渶楂樹紭鍏堢骇锛夈€傚綋鍓嶅凡閰嶇疆锛?{["exa", "tavily", "keenable", "perplexity", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "鏃?}`,
|
|
98
|
+
keyStorageSettingsHint: "淇濆瓨鍚庡啓鍏?settings.yaml锛堝悜鍚庡吋瀹硅矾寰勶紝浼樺厛绾т綆浜庡嚟鎹腑蹇冿級銆?,
|
|
99
|
+
platformSearch: "骞冲彴鎼滅储锛坧latform_search 宸ュ叿锛?,
|
|
100
|
+
platformHint: "涓?agent 鐨?platform_search 宸ュ叿鍚敤骞冲彴銆傜鐢ㄧ殑骞冲彴浼氳璺宠繃銆?,
|
|
101
|
+
cacheTtl: "缁撴灉缂撳瓨鏃堕暱锛堝垎閽燂級",
|
|
102
|
+
cacheTtlHint: "0 鍏抽棴缂撳瓨锛屾渶闀?5 鍒嗛挓銆傜缉鐭彲鍔犲揩鏃舵晥锛屽欢闀垮彲闃查檺娴併€佺渷棰濆害銆?,
|
|
103
|
+
unavailable: "璁剧疆涓嶅彲鐢?鈥斺€?free-search 妗ユ帴鏈毚闇层€?,
|
|
104
|
+
saveFailed: "淇濆瓨澶辫触",
|
|
105
|
+
testing: "娴嬭瘯涓€?,
|
|
106
|
+
testEngine: "娴嬭瘯寮曟搸",
|
|
107
|
+
useBing: "鎭㈠ Bing 榛樿",
|
|
108
|
+
discard: "鎾ら攢",
|
|
109
|
+
saving: "淇濆瓨涓€?,
|
|
110
|
+
save: "淇濆瓨",
|
|
111
|
+
testOk: (r) => `鉁?${r.count} 鏉$粨鏋滐紙寮曟搸: ${r.engine}锛?{r.content ? ` 鈥?${r.content}` : ""}${r.sample ? ` 路 渚嬪 "${r.sample.slice(0, 40)}"` : ""}`,
|
|
112
|
+
testFail: (e) => `鉁?${e}`,
|
|
113
113
|
toggleLang: "EN",
|
|
114
|
-
checkUpdate: "
|
|
115
|
-
checkingUpdate: "
|
|
116
|
-
updateAvailable: (c, l) =>
|
|
117
|
-
updateLatest: (c) =>
|
|
118
|
-
updateCheckFailed: "
|
|
119
|
-
updateView: "
|
|
120
|
-
upgrade: "
|
|
121
|
-
upgrading: "
|
|
122
|
-
upgradeLinkMode: "
|
|
123
|
-
upgradeDone: (l) =>
|
|
124
|
-
upgradeFailed: (m) =>
|
|
114
|
+
checkUpdate: "妫€鏌ユ洿鏂?,
|
|
115
|
+
checkingUpdate: "妫€鏌ヤ腑鈥?,
|
|
116
|
+
updateAvailable: (c, l) => `鍙戠幇鏂扮増鏈?v${l}锛堝綋鍓?v${c}锛塦,
|
|
117
|
+
updateLatest: (c) => `宸叉槸鏈€鏂扮増鏈?v${c}`,
|
|
118
|
+
updateCheckFailed: "妫€鏌ユ洿鏂板け璐ワ紙鏃犳硶璁块棶 npm registry锛?,
|
|
119
|
+
updateView: "鏌ョ湅 鈫?,
|
|
120
|
+
upgrade: "鍗囩骇",
|
|
121
|
+
upgrading: "鍗囩骇涓€?,
|
|
122
|
+
upgradeLinkMode: "锛堟湰鍦板紑鍙戞ā寮忥紝鍗囩骇璇风敤 git pull锛?,
|
|
123
|
+
upgradeDone: (l) => `鍗囩骇鍒?v${l} 瀹屾垚锛岄噸鍚?dsh 鍚庣敓鏁坄,
|
|
124
|
+
upgradeFailed: (m) => `鍗囩骇澶辫触锛?{m}`,
|
|
125
125
|
},
|
|
126
126
|
en: {
|
|
127
|
-
description: "Free web search
|
|
127
|
+
description: "Free web search 鈥?no API key needed (Bing / DuckDuckGo / AnySearch / Exa / Tavily / Keenable)",
|
|
128
128
|
unsaved: "unsaved",
|
|
129
129
|
searchEngine: "Search engine",
|
|
130
|
-
visit: "Visit website
|
|
131
|
-
getKey: "Get API Key
|
|
130
|
+
visit: "Visit website 鈫?,
|
|
131
|
+
getKey: "Get API Key 鈫?,
|
|
132
132
|
engineHint: "Bing is the most stable FREE engine. DuckDuckGo may rate-limit on shared IPs. API KEY engines need credentials below.",
|
|
133
133
|
apiKeys: "API keys (optional)",
|
|
134
134
|
exaPh: (c) => c ? "Exa API key (configured)" : "Exa API key (optional, free without)",
|
|
@@ -146,39 +146,39 @@ window.__ModuleLoader__.load({
|
|
|
146
146
|
platformHint: "Enable platforms for the agent's platform_search tool. Disabled platforms are skipped.",
|
|
147
147
|
cacheTtl: "Result cache TTL (minutes)",
|
|
148
148
|
cacheTtlHint: "0 disables caching, max 5 minutes. Lower = fresher results, higher = less rate-limiting / fewer credits used.",
|
|
149
|
-
unavailable: "Settings unavailable
|
|
149
|
+
unavailable: "Settings unavailable 鈥?the free-search bridge is not exposed.",
|
|
150
150
|
saveFailed: "save failed",
|
|
151
|
-
testing: "Testing
|
|
151
|
+
testing: "Testing鈥?,
|
|
152
152
|
testEngine: "Test engine",
|
|
153
153
|
useBing: "Use Bing default",
|
|
154
154
|
discard: "Discard",
|
|
155
|
-
saving: "Saving
|
|
155
|
+
saving: "Saving鈥?,
|
|
156
156
|
save: "Save",
|
|
157
|
-
testOk: (r) =>
|
|
158
|
-
testFail: (e) =>
|
|
159
|
-
toggleLang: "
|
|
157
|
+
testOk: (r) => `鉁?${r.count} results (engine: ${r.engine})${r.content ? ` 鈥?${r.content}` : ""}${r.sample ? ` 路 e.g. "${r.sample.slice(0, 40)}"` : ""}`,
|
|
158
|
+
testFail: (e) => `鉁?${e}`,
|
|
159
|
+
toggleLang: "涓枃",
|
|
160
160
|
checkUpdate: "Check update",
|
|
161
|
-
checkingUpdate: "Checking
|
|
161
|
+
checkingUpdate: "Checking鈥?,
|
|
162
162
|
updateAvailable: (c, l) => `New version v${l} available (current v${c})`,
|
|
163
163
|
updateLatest: (c) => `You're on the latest version v${c}`,
|
|
164
164
|
updateCheckFailed: "Update check failed (cannot reach npm registry)",
|
|
165
|
-
updateView: "View
|
|
165
|
+
updateView: "View 鈫?,
|
|
166
166
|
upgrade: "Upgrade",
|
|
167
|
-
upgrading: "Upgrading
|
|
167
|
+
upgrading: "Upgrading鈥?,
|
|
168
168
|
upgradeLinkMode: "(local dev install - use git pull to update)",
|
|
169
169
|
upgradeDone: (l) => `Upgraded to v${l} - restart dsh to apply`,
|
|
170
170
|
upgradeFailed: (m) => `Upgrade failed: ${m}`,
|
|
171
171
|
},
|
|
172
172
|
};
|
|
173
173
|
const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
|
|
174
|
-
//
|
|
175
|
-
const PLUGIN_VERSION = "0.4.
|
|
174
|
+
// 褰撳墠鎻掍欢鐗堟湰锛堜笌 lib/index.js 鐨?PLUGIN_VERSION 淇濇寔涓€鑷达級
|
|
175
|
+
const PLUGIN_VERSION = "0.4.20";
|
|
176
176
|
const ENGINES = [
|
|
177
|
-
{ id: "ddg", label: "DuckDuckGo
|
|
178
|
-
{ id: "ddg-lite", label: "DuckDuckGo
|
|
177
|
+
{ id: "ddg", label: "DuckDuckGo 路 HTML", badge: "FREE", link: "https://duckduckgo.com" },
|
|
178
|
+
{ id: "ddg-lite", label: "DuckDuckGo 路 Lite", badge: "FREE", link: "https://duckduckgo.com" },
|
|
179
179
|
{ id: "bing", label: "Bing", badge: "FREE", link: "https://www.bing.com" },
|
|
180
|
-
{ id: "anysearch", label: "AnySearch
|
|
181
|
-
{ id: "searxng", label: "SearXNG
|
|
180
|
+
{ id: "anysearch", label: "AnySearch 路 AI", badge: "FREE", link: "https://anysearch.com" },
|
|
181
|
+
{ id: "searxng", label: "SearXNG 路 鍏冩悳绱?, badge: "FREE", link: "https://github.com/searxng/searxng" },
|
|
182
182
|
{ id: "exa", label: "Exa", badge: "FREE", link: "https://dashboard.exa.ai/api-keys" },
|
|
183
183
|
{ id: "tavily", label: "Tavily", badge: "FREE", link: "https://app.tavily.com/home" },
|
|
184
184
|
{ id: "keenable", label: "Keenable", badge: "FREE", link: "https://keenable.ai/login" },
|
|
@@ -250,7 +250,7 @@ window.__ModuleLoader__.load({
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
function FreeSearchCard(props) {
|
|
253
|
-
//
|
|
253
|
+
// 妫€娴嬪簲鐢ㄤ富棰樻繁娴咃紙璇?body 鐨?--dsw-alias-bg-base 鍙橀噺浜害锛夛紝鐢ㄤ簬閿佸畾鍘熺敓涓嬫媺閰嶈壊
|
|
254
254
|
const isDarkScheme = react.useMemo(() => {
|
|
255
255
|
try {
|
|
256
256
|
const root = document.body || document.documentElement;
|
|
@@ -282,9 +282,9 @@ window.__ModuleLoader__.load({
|
|
|
282
282
|
const [platforms, setPlatforms] = react.useState(["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]);
|
|
283
283
|
const [cacheTtl, setCacheTtl] = react.useState(5);
|
|
284
284
|
const [keysConfigured, setKeysConfigured] = react.useState({});
|
|
285
|
-
// key
|
|
285
|
+
// key 瀛樺偍浣嶇疆锛歝redentials锛堝嚟鎹腑蹇冿紝榛樿锛墊 settings锛堣缃〉锛屽吋瀹规棫琛屼负锛?
|
|
286
286
|
const [keyStorage, setKeyStorage] = react.useState("credentials");
|
|
287
|
-
//
|
|
287
|
+
// 鍑嵁涓績閲屽凡閰嶇疆鐨?key锛坉escribe 涓嶈繑鍥烇紝闇€鍗曠嫭鏌ワ級
|
|
288
288
|
const [credConfigured, setCredConfigured] = react.useState({});
|
|
289
289
|
const [lang, setLang] = react.useState("zh");
|
|
290
290
|
const [dirty, setDirty] = react.useState(false);
|
|
@@ -313,7 +313,7 @@ window.__ModuleLoader__.load({
|
|
|
313
313
|
setDeepseekKey(v.deepseekApiKey ?? "");
|
|
314
314
|
setPlatforms(Array.isArray(v.platforms) && v.platforms.length > 0 ? v.platforms : ["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]);
|
|
315
315
|
setCacheTtl(v.cacheTtl === undefined ? 5 : Math.min(Math.max(Number(v.cacheTtl) ?? 5, 0), 5));
|
|
316
|
-
// secrets
|
|
316
|
+
// secrets 瀛楁鏍囪鍝簺 key 宸查厤缃紙鍊艰鑴辨晱锛屼粎鏄剧ず"宸查厤缃?锛?
|
|
317
317
|
const configured = {};
|
|
318
318
|
for (const secret of view.secrets ?? []) {
|
|
319
319
|
if (secret.set) {
|
|
@@ -326,10 +326,10 @@ window.__ModuleLoader__.load({
|
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
setKeysConfigured(configured);
|
|
329
|
-
// key
|
|
329
|
+
// key 瀛樺偍浣嶇疆锛堥粯璁ゅ嚟鎹腑蹇冿級
|
|
330
330
|
setKeyStorage(v.keyStorage === "settings" ? "settings" : "credentials");
|
|
331
331
|
setState({ status: "ready", writable: result.value.writable });
|
|
332
|
-
//
|
|
332
|
+
// 鏌ヨ鍑嵁涓績閲屽悇 key 鐨勯厤缃姸鎬?
|
|
333
333
|
try {
|
|
334
334
|
const cred = await bridgeCredentialsStatus();
|
|
335
335
|
if (cred.ok) {
|
|
@@ -366,7 +366,7 @@ window.__ModuleLoader__.load({
|
|
|
366
366
|
setSaving(true);
|
|
367
367
|
setFailed(false);
|
|
368
368
|
try {
|
|
369
|
-
// key
|
|
369
|
+
// key 瀛樺偍鍒嗘祦锛氬嚟鎹腑蹇冿紙榛樿锛夎蛋 credentials-set锛涜缃〉璧?settings mutate锛堝吋瀹癸級
|
|
370
370
|
const keyFields = [
|
|
371
371
|
["exaApiKey", exaKey],
|
|
372
372
|
["tavilyApiKey", tavilyKey],
|
|
@@ -387,7 +387,7 @@ window.__ModuleLoader__.load({
|
|
|
387
387
|
ops.push({ op: "set", path: ["keyStorage"], value: keyStorage });
|
|
388
388
|
ops.push({ op: "set", path: ["safeSearch"], value: safeSearch });
|
|
389
389
|
if (keyStorage !== "credentials") {
|
|
390
|
-
// settings
|
|
390
|
+
// settings 妯″紡锛歬ey 浠嶅啓 settings.yaml锛堟棫琛屼负锛?
|
|
391
391
|
for (const [field, value] of keyFields) {
|
|
392
392
|
if (value.trim()) ops.push({ op: "set", path: [field], value: value.trim() });
|
|
393
393
|
}
|
|
@@ -527,7 +527,7 @@ window.__ModuleLoader__.load({
|
|
|
527
527
|
}),
|
|
528
528
|
react_jsx_runtime.jsx("span", {
|
|
529
529
|
className: open ? "dshfs-chevron dshfs-chevronOpen" : "dshfs-chevron",
|
|
530
|
-
children: "
|
|
530
|
+
children: "鈻?,
|
|
531
531
|
}),
|
|
532
532
|
],
|
|
533
533
|
}),
|
|
@@ -890,8 +890,8 @@ react_jsx_runtime.jsx("div", {
|
|
|
890
890
|
const inject = ["slots", "commandUi"];
|
|
891
891
|
|
|
892
892
|
function apply(ctx) {
|
|
893
|
-
//
|
|
894
|
-
//
|
|
893
|
+
// 鎸傚畼鏂规彃妲?settings.plugin.item锛堣缃?鈫?鎻掍欢 鈫?鍙厤缃爣绛鹃〉锛夈€?
|
|
894
|
+
// 涓嶄緷璧?dsh-web-ui锛氶厤缃鍐欒蛋鑷缓 bridge锛?api/dsh-free-search-settings锛夈€?
|
|
895
895
|
ctx.slots.inject("settings.plugin.item", () =>
|
|
896
896
|
ctx.slots.register(
|
|
897
897
|
{
|
|
@@ -904,14 +904,14 @@ react_jsx_runtime.jsx("div", {
|
|
|
904
904
|
FreeSearchCard
|
|
905
905
|
)
|
|
906
906
|
);
|
|
907
|
-
// /free-search-engine
|
|
908
|
-
//
|
|
907
|
+
// /free-search-engine 寮瑰嚭寮忓懡浠わ細杈撳叆 "/" 閫変腑鍚庡脊鍑哄紩鎿庡垪琛紝鐐归€夊嵆鍒囨崲銆?
|
|
908
|
+
// 绛夋晥浜庤缃〉鍒囨崲寮曟搸+淇濆瓨锛涘懡浠ゅ彧鏀?provider 閰嶇疆锛屾悳绱粛璧板洖閫€閾俱€?
|
|
909
909
|
ctx.inject(["commandUi"], (sctx) => {
|
|
910
910
|
const command = sctx.get("commandUi");
|
|
911
911
|
sctx.effect(() => {
|
|
912
912
|
const dispose = command.register({
|
|
913
913
|
name: "free-search-engine",
|
|
914
|
-
description: "
|
|
914
|
+
description: "鍒囨崲鎼滅储寮曟搸 / Switch web search engine",
|
|
915
915
|
available: () => true,
|
|
916
916
|
ui: {
|
|
917
917
|
kind: "popupSelect",
|
|
@@ -921,8 +921,8 @@ react_jsx_runtime.jsx("div", {
|
|
|
921
921
|
const current = view?.value?.provider ?? "bing";
|
|
922
922
|
return ENGINES.map((e) => ({
|
|
923
923
|
id: e.id,
|
|
924
|
-
label: `${e.label}${e.badge === "FREE" ? "
|
|
925
|
-
detail: e.id === current ? (view?.value?.lang === "en" ? "current" : "
|
|
924
|
+
label: `${e.label}${e.badge === "FREE" ? " 路 鍏嶈垂" : " 路 API Key"}`,
|
|
925
|
+
detail: e.id === current ? (view?.value?.lang === "en" ? "current" : "褰撳墠") : undefined,
|
|
926
926
|
active: e.id === current,
|
|
927
927
|
}));
|
|
928
928
|
},
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SettingsConflictError,
|
|
1
|
+
import { SettingsConflictError, SettingsProvider } from "@deepseek-ai/dsh-settings";
|
|
2
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import fs from "node:fs";
|
|
@@ -15,19 +15,19 @@ const USER_AGENT =
|
|
|
15
15
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
16
16
|
const ACCEPT_LANG = "zh-CN,zh;q=0.9,en;q=0.8";
|
|
17
17
|
|
|
18
|
-
const FREE_SEARCH_NS =
|
|
18
|
+
const FREE_SEARCH_NS = "free-search";
|
|
19
19
|
const BRIDGE_PREFIX = "/api/dsh-free-search-settings";
|
|
20
20
|
const FREE_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch"];
|
|
21
21
|
const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "tavily", "keenable", "perplexity", "deepseek-official"];
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
const PLUGIN_VERSION = "0.4.
|
|
25
|
-
//
|
|
23
|
+
// 褰撳墠鎻掍欢鐗堟湰锛堝彂甯冩椂涓?package.json 鍚屾锛?
|
|
24
|
+
const PLUGIN_VERSION = "0.4.20";
|
|
25
|
+
// 妫€鏌ユ洿鏂扮殑 npm registry 鍏冩暟鎹湴鍧€锛坉sh-free-search 鏄?npmjs 涓婄殑鍏紑鍖咃級
|
|
26
26
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
|
|
27
27
|
const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
|
|
28
28
|
const PLUGIN_REPO_URL = "https://github.com/DDDMUC/dsh-free-search";
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// 鏌ヨ npm registry 鐨勬渶鏂扮増鏈紱澶辫触鏃惰繑鍥?null锛堢綉缁?浠g悊闂涓嶉樆濉炶缃〉锛?
|
|
31
31
|
async function fetchLatestVersion(signal) {
|
|
32
32
|
const controller = new AbortController();
|
|
33
33
|
const timer = setTimeout(() => controller.abort(), 10000);
|
|
@@ -49,7 +49,7 @@ async function fetchLatestVersion(signal) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
//
|
|
52
|
+
// 绠€鍗曠殑 semver 姣旇緝锛堜粎澶勭悊 x.y.z 涓?娆?琛ヤ竵锛屽拷鐣ラ鍙戝竷鏍囩锛夛紱a>b 杩斿洖 1, a<b 杩斿洖 -1, 鐩哥瓑杩斿洖 0
|
|
53
53
|
function compareVersions(a, b) {
|
|
54
54
|
const na = String(a).split(".").map((n) => parseInt(n, 10) || 0);
|
|
55
55
|
const nb = String(b).split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -60,8 +60,8 @@ function compareVersions(a, b) {
|
|
|
60
60
|
return 0;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
//
|
|
64
|
-
// symlink
|
|
63
|
+
// 妫€娴嬫彃浠剁殑瀹夎妯″紡锛氶亶鍘?profiles/*/node_modules/dsh-free-search锛?
|
|
64
|
+
// symlink锛坙ink: 鏈湴寮€鍙戯級鈫?isLink=true锛沶pm 鐪熷畨瑁?鈫?isLink=false锛涙壘涓嶅埌 鈫?null
|
|
65
65
|
function detectInstallMode() {
|
|
66
66
|
const profilesDir = path.join(process.cwd(), "profiles");
|
|
67
67
|
let found = null;
|
|
@@ -80,7 +80,7 @@ function detectInstallMode() {
|
|
|
80
80
|
return found;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// time_range
|
|
83
|
+
// time_range 鏀寔锛氬浐瀹氭。 day/week/month/year锛屾垨鑷畾涔夛紙鐩稿 12h/3d/2mo/1y銆佺粷瀵?YYYY-MM-DD锛?
|
|
84
84
|
const TIME_RANGES = ["day", "week", "month", "year"];
|
|
85
85
|
const DAYS_BY_RANGE = { day: 1, week: 7, month: 30, year: 365 };
|
|
86
86
|
const KEENABLE_REL = { day: "1d", week: "7d", month: "1mo", year: "1y" };
|
|
@@ -90,12 +90,12 @@ function isoDaysAgo(days) {
|
|
|
90
90
|
return new Date(Date.now() - days * 86_400_000).toISOString().replace(/\.\d{3}Z$/, ".000Z");
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
93
|
+
// 鎶婄敤鎴?agent 缁欑殑 timeRange 瑙f瀽鎴愮粺涓€瀵硅薄锛歿 days } 鐩稿澶╂暟锛屾垨 { after } 缁濆鏃ユ湡銆?
|
|
94
|
+
// 杈撳叆鏀寔锛歞ay/week/month/year銆?2h/3d/2mo/1y銆?026-07-01锛屾垨宸茶В鏋愮殑 {days}/{after} 瀵硅薄銆?
|
|
95
|
+
// 鏃犳晥杩斿洖 undefined銆?
|
|
96
96
|
function parseTimeRange(input) {
|
|
97
97
|
if (input === undefined || input === null) return undefined;
|
|
98
|
-
//
|
|
98
|
+
// 宸茶В鏋愬璞★細鐩存帴閫忎紶
|
|
99
99
|
if (typeof input === "object") {
|
|
100
100
|
if (typeof input.after === "string" && /^\d{4}-\d{2}-\d{2}$/.test(input.after)) return { after: input.after };
|
|
101
101
|
if (typeof input.days === "number" && Number.isFinite(input.days) && input.days > 0) return { days: input.days };
|
|
@@ -116,7 +116,7 @@ function parseTimeRange(input) {
|
|
|
116
116
|
return undefined;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
//
|
|
119
|
+
// 鎶婅嚜瀹氫箟澶╂暟鏄犲皠鍒板彧鏀寔鍥哄畾妗g殑寮曟搸锛圱avily / SearXNG / DDG锛夌殑鏈€杩戜技妗d綅
|
|
120
120
|
function approximateTimeRange(days) {
|
|
121
121
|
if (days <= 2) return "day";
|
|
122
122
|
if (days <= 14) return "week";
|
|
@@ -136,18 +136,18 @@ function decodeEntities(text) {
|
|
|
136
136
|
.replace(/&#(\d+);/g, (_, n) => String.fromCharCode(Number(n)));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
//#region
|
|
139
|
+
//#region 缁撴灉缂撳瓨锛堥槻闄愭祦/鐪侀搴︼紝LRU 50 鏉★紝TTL 鍙厤缃?0-5 鍒嗛挓锛?
|
|
140
140
|
const CACHE_MAX_ENTRIES = 50;
|
|
141
|
-
// fallback
|
|
142
|
-
//
|
|
141
|
+
// fallback 鏉$洰锛堝疄闄呭紩鎿?鈮?棣栭€夊紩鎿庢椂锛塗TL = 閰嶇疆 TTL 鐨?1/5锛堥粯璁?5 鍒嗛挓 鈫?60s锛夛細
|
|
142
|
+
// 棣栭€夊紩鎿庢仮澶嶅悗鏈€澶?1 鍒嗛挓鍗冲彲鎷垮埌鏂扮粨鏋滐紝閬垮厤鍥為€€缁撴灉琚畬鏁?TTL 閽夋锛涢閫夋垚鍔熸潯鐩粛鐢ㄥ畬鏁?TTL銆?
|
|
143
143
|
|
|
144
144
|
function buildCacheKey(query, maxResults, timeRangeLabel, preferred) {
|
|
145
145
|
return [query ?? "", maxResults ?? 5, timeRangeLabel ?? "", preferred].join("\u0000");
|
|
146
146
|
}
|
|
147
147
|
//#endregion
|
|
148
148
|
|
|
149
|
-
//
|
|
150
|
-
//
|
|
149
|
+
// 缁熶竴鐨?snippet 娓呮礂锛氬墧闄ょ櫥褰?浠樿垂澧?璁㈤槄绛夊櫔闊崇煭璇紝鎶樺彔绌虹櫧锛岄檺鍒堕暱搴︺€?
|
|
150
|
+
// 鍙湪鍥為€€閾惧嚭鍙g粺涓€搴旂敤锛屽悇寮曟搸鍐呴儴涓嶅仛锛岄伩鍏嶉噸澶嶅鐞嗐€?
|
|
151
151
|
const SNIPPET_NOISE =
|
|
152
152
|
/\b(sign up|sign in|log in|login|subscribe( to| for)?|member[- ]?only|become a member|create (a )?free account|read more|continue reading|story continues|get started|install (the )?app|view on|medium membership|join \w+ for free|get updates from this writer|stories in your inbox|remember me for|unlock this|free to read|become a patron)\b/gi;
|
|
153
153
|
|
|
@@ -193,7 +193,7 @@ function uniqueSources(sources, limit) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
async function fetchHtml(url, signal) {
|
|
196
|
-
//
|
|
196
|
+
// 鍗曟璇锋眰瓒呮椂 12s锛岄伩鍏嶆寕璧疯褰撴垚 Connection error
|
|
197
197
|
let response;
|
|
198
198
|
try {
|
|
199
199
|
const controller = new AbortController();
|
|
@@ -215,14 +215,14 @@ async function fetchHtml(url, signal) {
|
|
|
215
215
|
throw new Error(`HTTP ${response.status} from ${url.split("?")[0]}`);
|
|
216
216
|
}
|
|
217
217
|
const html = await response.text();
|
|
218
|
-
// DuckDuckGo
|
|
218
|
+
// DuckDuckGo 鍙嶇埇楠岃瘉椤垫娴嬶紙HTTP 202 鎴栭獙璇佸叧閿瓧锛?
|
|
219
219
|
if (response.status === 202 || /anomaly|captcha|unusual traffic|robot check/i.test(html.slice(0, 4000))) {
|
|
220
220
|
throw new Error("DuckDuckGo is rate-limited right now (anti-bot challenge, usually temporary) - Bing works");
|
|
221
221
|
}
|
|
222
222
|
return html;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
//
|
|
225
|
+
// 甯﹂噸璇曠殑鎶撳彇锛氱綉缁滈敊璇?绌虹粨鏋滄椂閲嶈瘯锛岄棿闅?1.5s锛屾渶澶?3 娆?
|
|
226
226
|
async function fetchHtmlWithRetry(url, signal) {
|
|
227
227
|
let lastError;
|
|
228
228
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
@@ -241,10 +241,10 @@ async function fetchHtmlWithRetry(url, signal) {
|
|
|
241
241
|
async function searchDdgHtml(query, maxResults, options, signal) {
|
|
242
242
|
const params = new URLSearchParams({ q: query });
|
|
243
243
|
if (options?.region) params.set("kl", options.region);
|
|
244
|
-
// DDG
|
|
244
|
+
// DDG 瀹夊叏鎼滅储锛歰ff(adlt=-1) / moderate(adlt=0) / strict(adlt=1)
|
|
245
245
|
const adlt = options?.safeSearch ?? "off";
|
|
246
246
|
params.set("adlt", adlt === "strict" ? "1" : adlt === "moderate" ? "0" : "-1");
|
|
247
|
-
// DDG
|
|
247
|
+
// DDG 鏃堕棿杩囨护锛歞f=d/w/m/y锛堝彧鏀寔鍥哄畾妗o紝鑷畾涔夊彇杩戜技妗o級
|
|
248
248
|
if (options?.timeRange) {
|
|
249
249
|
const df = { day: "d", week: "w", month: "m", year: "y" }[approximateTimeRange(options.timeRange.days ?? 7)];
|
|
250
250
|
if (df) params.set("df", df);
|
|
@@ -273,7 +273,7 @@ async function searchDdgLite(query, maxResults, options, signal) {
|
|
|
273
273
|
const params = new URLSearchParams({ q: query });
|
|
274
274
|
const adlt = options?.safeSearch ?? "off";
|
|
275
275
|
params.set("adlt", adlt === "strict" ? "1" : adlt === "moderate" ? "0" : "-1");
|
|
276
|
-
// DDG Lite
|
|
276
|
+
// DDG Lite 鍚屾牱鏀寔 df 鏃堕棿杩囨护
|
|
277
277
|
if (options?.timeRange) {
|
|
278
278
|
const df = { day: "d", week: "w", month: "m", year: "y" }[approximateTimeRange(options.timeRange.days ?? 7)];
|
|
279
279
|
if (df) params.set("df", df);
|
|
@@ -336,12 +336,12 @@ async function searchSearxng(query, maxResults, options, signal) {
|
|
|
336
336
|
const instances = options?.searxngInstances?.length
|
|
337
337
|
? options.searxngInstances
|
|
338
338
|
: SEARXNG_INSTANCES;
|
|
339
|
-
//
|
|
339
|
+
// 鑱氬悎鎵€鏈夊疄渚嬬殑澶辫触鍘熷洜锛岄伩鍏嶅彧鏄剧ず鏈€鍚庝竴涓疄渚嬬殑閿欒
|
|
340
340
|
const errors = [];
|
|
341
341
|
for (const base of instances) {
|
|
342
342
|
try {
|
|
343
343
|
const params = new URLSearchParams({ q: query, format: "json" });
|
|
344
|
-
// SearXNG
|
|
344
|
+
// SearXNG 鍘熺敓鏀寔 time_range 杩囨护锛堝彧鏀寔鍥哄畾妗o紝鑷畾涔夊彇杩戜技妗o級
|
|
345
345
|
if (options?.timeRange) {
|
|
346
346
|
const tr = SEARXNG_TIME[approximateTimeRange(options.timeRange.days ?? 7)];
|
|
347
347
|
if (tr) params.set("time_range", tr);
|
|
@@ -381,9 +381,9 @@ async function searchSearxng(query, maxResults, options, signal) {
|
|
|
381
381
|
errors.push(`${base}: ${message}`);
|
|
382
382
|
}
|
|
383
383
|
}
|
|
384
|
-
//
|
|
384
|
+
// 绌哄疄渚嬪垪琛ㄥ厹搴曪細閬垮厤 "all SearXNG instances failed: " 灏惧反鎮┖
|
|
385
385
|
const detail = errors.length > 0 ? errors.join(", ") : "no instances configured";
|
|
386
|
-
// Note
|
|
386
|
+
// Note 浼氬紩鐢ㄨ繖涓敊璇秷鎭紝鎴柇閬垮厤 6 瀹炰緥鍏ㄦ寕鏃跺埛灞?
|
|
387
387
|
throw new Error(`all SearXNG instances failed: ${detail.slice(0, 300)}`);
|
|
388
388
|
}
|
|
389
389
|
//#endregion
|
|
@@ -392,7 +392,7 @@ async function searchSearxng(query, maxResults, options, signal) {
|
|
|
392
392
|
const ANYSEARCH_URL = "https://api.anysearch.com/v1/search";
|
|
393
393
|
const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
|
|
394
394
|
|
|
395
|
-
// AnySearch:
|
|
395
|
+
// AnySearch: 鍏嶈垂鍖垮悕棰濆害锛堟棤 key锛夛紝缁撴瀯鍖?JSON 缁撴灉
|
|
396
396
|
async function searchAnysearch(query, maxResults, signal) {
|
|
397
397
|
const controller = new AbortController();
|
|
398
398
|
const timer = setTimeout(() => controller.abort(), 12000);
|
|
@@ -429,7 +429,7 @@ async function searchAnysearch(query, maxResults, signal) {
|
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
// Exa MCP:
|
|
432
|
+
// Exa MCP: 鍖垮悕鍏紑 MCP锛堟棤 key锛夛紝web_search_exa 宸ュ叿
|
|
433
433
|
async function searchExaMCP(query, maxResults, signal) {
|
|
434
434
|
const controller = new AbortController();
|
|
435
435
|
const timer = setTimeout(() => controller.abort(), 20000);
|
|
@@ -457,7 +457,7 @@ async function searchExaMCP(query, maxResults, signal) {
|
|
|
457
457
|
}
|
|
458
458
|
if (!response.ok) throw new Error(`Exa MCP error (HTTP ${response.status})`);
|
|
459
459
|
const text = await response.text();
|
|
460
|
-
//
|
|
460
|
+
// 瑙f瀽 SSE 鏍煎紡锛歟vent: message\ndata: {...}
|
|
461
461
|
const lines = text.split("\n");
|
|
462
462
|
let json = null;
|
|
463
463
|
for (const line of lines) {
|
|
@@ -477,7 +477,7 @@ async function searchExaMCP(query, maxResults, signal) {
|
|
|
477
477
|
.filter((b) => b.type === "text")
|
|
478
478
|
.map((b) => b.text)
|
|
479
479
|
.join("\n");
|
|
480
|
-
//
|
|
480
|
+
// 瑙f瀽 "Title: X\nURL: Y\nPublished: Z\nHighlights:\n..."
|
|
481
481
|
const blocks = textBlocks.split(/\n(?=Title:)/);
|
|
482
482
|
for (const block of blocks) {
|
|
483
483
|
const title = block.match(/^Title: (.+)$/m)?.[1];
|
|
@@ -489,7 +489,7 @@ async function searchExaMCP(query, maxResults, signal) {
|
|
|
489
489
|
url,
|
|
490
490
|
...(title ? { title } : {}),
|
|
491
491
|
...(highlights ? { snippet: highlights.slice(0, 300) } : {}),
|
|
492
|
-
//
|
|
492
|
+
// 鍙繚鐣欐棩鏈熷舰鎬侊紙ISO 鎴?YYYY-MM-DD锛夛紝杩囨护 "N/A" 绛夊崰浣嶇
|
|
493
493
|
...(published && /^\d{4}-\d{2}-\d{2}/.test(published) ? { publishedAt: published } : {}),
|
|
494
494
|
});
|
|
495
495
|
}
|
|
@@ -523,7 +523,7 @@ async function searchGithub(query, maxResults, signal) {
|
|
|
523
523
|
sources: (data.items ?? []).map((item) => ({
|
|
524
524
|
url: item.html_url,
|
|
525
525
|
title: item.full_name ?? item.name,
|
|
526
|
-
snippet: `${item.description ?? ""}${item.stargazers_count ? `
|
|
526
|
+
snippet: `${item.description ?? ""}${item.stargazers_count ? ` 猸?{item.stargazers_count}` : ""}`.trim(),
|
|
527
527
|
})),
|
|
528
528
|
truncated: false,
|
|
529
529
|
};
|
|
@@ -617,11 +617,11 @@ async function searchHackerNews(query, maxResults, signal) {
|
|
|
617
617
|
sources: (data.hits ?? [])
|
|
618
618
|
.filter((h) => h.title || h.story_title)
|
|
619
619
|
.map((h) => ({
|
|
620
|
-
//
|
|
620
|
+
// 鏈夊閾剧敤澶栭摼锛岀函璁ㄨ甯栫敤 HN 璁ㄨ椤?
|
|
621
621
|
url: h.url ?? `https://news.ycombinator.com/item?id=${h.objectID}`,
|
|
622
622
|
title: h.title ?? h.story_title,
|
|
623
623
|
...((h.points !== undefined && h.points !== null) || (h.num_comments !== undefined && h.num_comments !== null)
|
|
624
|
-
? { snippet: `HN discussion
|
|
624
|
+
? { snippet: `HN discussion 路 ${h.points ?? 0} points 路 ${h.num_comments ?? 0} comments` }
|
|
625
625
|
: {}),
|
|
626
626
|
})),
|
|
627
627
|
truncated: false,
|
|
@@ -644,7 +644,7 @@ async function searchStackOverflow(query, maxResults, signal) {
|
|
|
644
644
|
url: it.link,
|
|
645
645
|
title: it.title,
|
|
646
646
|
...(it.score !== undefined || it.answer_count !== undefined
|
|
647
|
-
? { snippet: `${it.is_answered ? "
|
|
647
|
+
? { snippet: `${it.is_answered ? "鉁?answered" : "unanswered"} 路 score ${it.score ?? 0} 路 ${it.answer_count ?? 0} answers` }
|
|
648
648
|
: {}),
|
|
649
649
|
})),
|
|
650
650
|
truncated: false,
|
|
@@ -666,7 +666,7 @@ async function searchWikipedia(query, maxResults, signal, lang) {
|
|
|
666
666
|
sources: (data.query?.search ?? []).map((s) => ({
|
|
667
667
|
url: `https://${host}/wiki/${encodeURIComponent(String(s.title).replace(/ /g, "_"))}`,
|
|
668
668
|
title: s.title,
|
|
669
|
-
// snippet
|
|
669
|
+
// snippet 鍚?<span class="searchmatch"> 楂樹寒鏍囩锛屽墺鎺?
|
|
670
670
|
...(s.snippet ? { snippet: stripTags(s.snippet).slice(0, 200) } : {}),
|
|
671
671
|
})),
|
|
672
672
|
truncated: false,
|
|
@@ -691,7 +691,7 @@ async function searchNpm(query, maxResults, signal) {
|
|
|
691
691
|
url: p.links?.npm ?? `https://www.npmjs.com/package/${p.name}`,
|
|
692
692
|
title: p.name,
|
|
693
693
|
...((p.description || p.version)
|
|
694
|
-
? { snippet: `v${p.version ?? "?"}${p.description ? `
|
|
694
|
+
? { snippet: `v${p.version ?? "?"}${p.description ? ` 鈥?${String(p.description).slice(0, 160)}` : ""}` }
|
|
695
695
|
: {}),
|
|
696
696
|
})),
|
|
697
697
|
truncated: false,
|
|
@@ -731,7 +731,7 @@ async function searchExa(query, maxResults, apiKey, timeRange, signal) {
|
|
|
731
731
|
contents: { highlights: { highlightsPerUrl: 1 } },
|
|
732
732
|
...(maxResults !== undefined ? { numResults: maxResults } : {}),
|
|
733
733
|
};
|
|
734
|
-
// Exa
|
|
734
|
+
// Exa 鏃堕棿杩囨护锛歴tartPublishedDate锛圛SO 鏃ユ湡锛涙敮鎸佷换鎰忓ぉ鏁板拰缁濆鏃ユ湡锛?
|
|
735
735
|
if (timeRange) {
|
|
736
736
|
if (timeRange.after) body.startPublishedDate = timeRange.after;
|
|
737
737
|
else if (timeRange.days !== undefined) body.startPublishedDate = isoDaysAgo(timeRange.days);
|
|
@@ -771,7 +771,7 @@ async function searchExa(query, maxResults, apiKey, timeRange, signal) {
|
|
|
771
771
|
return { sources: uniqueSources(sources, maxResults ?? 10), truncated: false };
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
-
// Tavily:
|
|
774
|
+
// Tavily: 鏃?key 璧?keyless锛堝厤璐瑰尶鍚嶉搴︼級锛屾湁 key 璧拌处鍙锋。锛圔earer锛?
|
|
775
775
|
async function searchTavily(query, maxResults, apiKey, timeRange, signal) {
|
|
776
776
|
const controller = new AbortController();
|
|
777
777
|
const timer = setTimeout(() => controller.abort(), 15000);
|
|
@@ -784,7 +784,7 @@ async function searchTavily(query, maxResults, apiKey, timeRange, signal) {
|
|
|
784
784
|
max_results: Math.min(maxResults ?? 5, 20),
|
|
785
785
|
search_depth: "basic",
|
|
786
786
|
};
|
|
787
|
-
// Tavily
|
|
787
|
+
// Tavily 鏃堕棿杩囨护锛歵ime_range 鍙敮鎸佸浐瀹氭。锛岃嚜瀹氫箟澶╂暟鍙栨渶杩戜技妗d綅
|
|
788
788
|
if (timeRange) {
|
|
789
789
|
const tr = approximateTimeRange(timeRange.days ?? 7);
|
|
790
790
|
if (tr) body.time_range = tr;
|
|
@@ -825,7 +825,7 @@ async function searchTavily(query, maxResults, apiKey, timeRange, signal) {
|
|
|
825
825
|
return { sources: uniqueSources(sources, maxResults ?? 10), truncated: false };
|
|
826
826
|
}
|
|
827
827
|
|
|
828
|
-
//
|
|
828
|
+
// 鎶婁换鎰忓ぉ鏁拌浆鎴?Keenable 鐨勭浉瀵规椂闂存牸寮忥紙12h / Nd / Nmo / Ny锛?
|
|
829
829
|
function formatKeenableRelative(days) {
|
|
830
830
|
if (days <= 0.5) return "12h";
|
|
831
831
|
if (days < 1) return `${Math.round(days * 24)}h`;
|
|
@@ -834,7 +834,7 @@ function formatKeenableRelative(days) {
|
|
|
834
834
|
return `${Math.round(days / 365)}y`;
|
|
835
835
|
}
|
|
836
836
|
|
|
837
|
-
// Keenable:
|
|
837
|
+
// Keenable: 鏈?key 璧?REST API锛圶-API-Key锛夛紝鏃?key 璧?keyless MCP锛堝厤璐瑰尶鍚嶉搴︼級
|
|
838
838
|
function extractKeenableSources(text, maxResults) {
|
|
839
839
|
const sources = [];
|
|
840
840
|
const blocks = String(text).split(/\n(?=Title:)/);
|
|
@@ -848,7 +848,7 @@ function extractKeenableSources(text, maxResults) {
|
|
|
848
848
|
url,
|
|
849
849
|
...(title ? { title } : {}),
|
|
850
850
|
...(snippets ? { snippet: snippets.slice(0, 300) } : {}),
|
|
851
|
-
//
|
|
851
|
+
// 涓?Exa MCP 涓€鑷达細鍙繚鐣欐棩鏈熷舰鎬侊紝杩囨护 "N/A" 绛夊崰浣嶇
|
|
852
852
|
...(published && /^\d{4}-\d{2}-\d{2}/.test(published) ? { publishedAt: published } : {}),
|
|
853
853
|
});
|
|
854
854
|
}
|
|
@@ -863,7 +863,7 @@ async function searchKeenableREST(query, maxResults, apiKey, timeRange, signal)
|
|
|
863
863
|
let response;
|
|
864
864
|
try {
|
|
865
865
|
const body = { query, mode: "realtime" };
|
|
866
|
-
// Keenable
|
|
866
|
+
// Keenable 鏃堕棿杩囨护锛歱ublished_after锛堢浉瀵?12h/7d/1mo/1y 鎴栫粷瀵?YYYY-MM-DD锛?
|
|
867
867
|
if (timeRange) {
|
|
868
868
|
if (timeRange.after) body.published_after = timeRange.after;
|
|
869
869
|
else if (timeRange.days !== undefined) body.published_after = formatKeenableRelative(timeRange.days);
|
|
@@ -908,7 +908,7 @@ async function searchKeenableMCP(query, maxResults, timeRange, signal) {
|
|
|
908
908
|
let response;
|
|
909
909
|
try {
|
|
910
910
|
const arguments_ = { query };
|
|
911
|
-
// Keenable MCP
|
|
911
|
+
// Keenable MCP 鏀寔 published_after锛堢浉瀵规垨缁濆鏃ユ湡锛?
|
|
912
912
|
if (timeRange) {
|
|
913
913
|
if (timeRange.after) arguments_.published_after = timeRange.after;
|
|
914
914
|
else if (timeRange.days !== undefined) arguments_.published_after = formatKeenableRelative(timeRange.days);
|
|
@@ -935,7 +935,7 @@ async function searchKeenableMCP(query, maxResults, timeRange, signal) {
|
|
|
935
935
|
const data = await response.json();
|
|
936
936
|
if (data.error) throw new Error(`Keenable MCP error: ${data.error?.message ?? "unknown"}`);
|
|
937
937
|
const content = data.result?.content ?? [];
|
|
938
|
-
// isError=true
|
|
938
|
+
// isError=true 鏃?content 閲屾槸閿欒鏂囨湰
|
|
939
939
|
const text = content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
940
940
|
if (data.result?.isError) throw new Error(`Keenable MCP error: ${text.slice(0, 200)}`);
|
|
941
941
|
return { sources: extractKeenableSources(text, maxResults ?? 10), truncated: false };
|
|
@@ -948,7 +948,7 @@ async function searchKeenable(query, maxResults, apiKey, timeRange, signal) {
|
|
|
948
948
|
|
|
949
949
|
async function searchPerplexity(query, maxResults, apiKey, signal) {
|
|
950
950
|
if (!apiKey) throw new Error("Perplexity search requires PERPLEXITY_API_KEY");
|
|
951
|
-
//
|
|
951
|
+
// 鍐呯疆 20s 瓒呮椂锛堜笌澶栭儴 signal 缁勫悎锛夛細璋冪敤鏂逛笉浼?signal 鏃朵篃涓嶄細姘镐箙鍗′綇
|
|
952
952
|
const response = await fetch("https://api.perplexity.ai/chat/completions", {
|
|
953
953
|
method: "POST",
|
|
954
954
|
redirect: "error",
|
|
@@ -984,7 +984,7 @@ async function searchPerplexity(query, maxResults, apiKey, signal) {
|
|
|
984
984
|
|
|
985
985
|
async function searchDeepSeekOfficial(query, maxResults, apiKey, signal) {
|
|
986
986
|
if (!apiKey) throw new Error("DeepSeek search requires DEEPSEEK_API_KEY");
|
|
987
|
-
//
|
|
987
|
+
// 鍐呯疆 20s 瓒呮椂锛堜笌澶栭儴 signal 缁勫悎锛夛細璋冪敤鏂逛笉浼?signal 鏃朵篃涓嶄細姘镐箙鍗′綇
|
|
988
988
|
const response = await fetch("https://api.deepseek.com/anthropic/v1/messages", {
|
|
989
989
|
method: "POST",
|
|
990
990
|
redirect: "error",
|
|
@@ -1131,13 +1131,13 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
1131
1131
|
hasUpdate: cmp > 0,
|
|
1132
1132
|
updateUrl: PLUGIN_NPM_URL,
|
|
1133
1133
|
repoUrl: PLUGIN_REPO_URL,
|
|
1134
|
-
//
|
|
1134
|
+
// 鍙竴閿崌绾э細npm 鐪熷畨瑁呮椂 true锛涙湰鍦?link 寮€鍙戞ā寮?false锛堝崌绾ц git pull 婧愮爜锛?
|
|
1135
1135
|
installable: mode !== null && !mode.isLink,
|
|
1136
1136
|
installMode: mode?.isLink ? "link" : mode !== null ? "registry" : "unknown",
|
|
1137
1137
|
},
|
|
1138
1138
|
};
|
|
1139
1139
|
},
|
|
1140
|
-
//
|
|
1140
|
+
// 涓€閿崌绾э細浠呭湪 npm 鐪熷畨瑁呮ā寮忎笅鎵ц pnpm 鍗囩骇锛沴ink 妯″紡鎷掔粷锛堥伩鍏嶇牬鍧忔湰鍦板紑鍙戦摼璺級
|
|
1141
1141
|
async updatePlugin() {
|
|
1142
1142
|
const mode = detectInstallMode();
|
|
1143
1143
|
if (mode === null) {
|
|
@@ -1177,7 +1177,7 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
1177
1177
|
}
|
|
1178
1178
|
const maxResults = Math.min(Math.max(Number(request.maxResults) || 5, 1), 10);
|
|
1179
1179
|
const timeRange = parseTimeRange(request.timeRange);
|
|
1180
|
-
//
|
|
1180
|
+
// 鎸囧畾 engine锛氱洿娴嬭寮曟搸鏈韩锛堜笉璧板洖閫€閾撅級锛屾姤鍛婂畠鑷繁鐨勫彲鐢ㄦ€?
|
|
1181
1181
|
if (typeof request.engine === "string" && request.engine.length > 0) {
|
|
1182
1182
|
if (typeof testEngine !== "function") {
|
|
1183
1183
|
return { ok: false, code: "search-unavailable", message: "engine test is not wired" };
|
|
@@ -1207,11 +1207,11 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
1207
1207
|
return {
|
|
1208
1208
|
ok: true,
|
|
1209
1209
|
value: {
|
|
1210
|
-
//
|
|
1210
|
+
// 瀹為檯浣跨敤鐨勫紩鎿庯細provider.search 鍦ㄦ垚鍔熸椂杩斿洖 provider 瀛楁
|
|
1211
1211
|
provider: result.provider ?? request.engine ?? request.provider ?? "bing",
|
|
1212
1212
|
sources: result.sources ?? [],
|
|
1213
1213
|
content: result.content ?? "",
|
|
1214
|
-
//
|
|
1214
|
+
// 缂撳瓨鍛戒腑鏍囪锛歱rovider.search 鎴愬姛璺緞鏍囪 _cache锛坔it=鍛戒腑缂撳瓨锛宮iss=鐪熷疄鎼滅储锛?
|
|
1215
1215
|
cache: result._cache === "hit" ? "hit" : "miss",
|
|
1216
1216
|
},
|
|
1217
1217
|
};
|
|
@@ -1243,7 +1243,7 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
1243
1243
|
}
|
|
1244
1244
|
const expectedRevision = typeof body.expectedRevision === "number" ? body.expectedRevision : undefined;
|
|
1245
1245
|
try {
|
|
1246
|
-
await settings.mutate(
|
|
1246
|
+
await settings.mutate(ns, body.ops, expectedRevision);
|
|
1247
1247
|
} catch (error) {
|
|
1248
1248
|
if (error instanceof SettingsConflictError) {
|
|
1249
1249
|
return { ok: false, code: "settings-conflict", message: error.message };
|
|
@@ -1257,7 +1257,7 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
1257
1257
|
}
|
|
1258
1258
|
return { ok: true, value: toView(descriptor) };
|
|
1259
1259
|
},
|
|
1260
|
-
//
|
|
1260
|
+
// 鍑嵁涓績锛氭煡璇㈠悇寮曟搸 key 鐨勯厤缃姸鎬侊紙value 涓嶈繑鍥烇紝鍙繑鍥炴槸鍚﹀凡閰嶇疆锛?
|
|
1261
1261
|
async credentialsStatus() {
|
|
1262
1262
|
const credentials = getCredentials();
|
|
1263
1263
|
if (!credentials) return { ok: false, code: "credentials-unavailable", message: "credentials service is not available" };
|
|
@@ -1272,7 +1272,7 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
1272
1272
|
}
|
|
1273
1273
|
return { ok: true, value: { configured, available: true } };
|
|
1274
1274
|
},
|
|
1275
|
-
//
|
|
1275
|
+
// 鍑嵁涓績锛氬啓鍏ヤ竴涓紩鎿?key锛坮ef 鐧藉悕鍗曢檺瀹氾級
|
|
1276
1276
|
async credentialsSet(request) {
|
|
1277
1277
|
const credentials = getCredentials();
|
|
1278
1278
|
if (!credentials) return { ok: false, code: "credentials-unavailable", message: "credentials service is not available" };
|
|
@@ -1289,7 +1289,7 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
1289
1289
|
return { ok: false, code: "credentials-write-failed", message: error instanceof Error ? error.message : String(error) };
|
|
1290
1290
|
}
|
|
1291
1291
|
},
|
|
1292
|
-
//
|
|
1292
|
+
// 鍑嵁涓績锛氬垹闄や竴涓紩鎿?key
|
|
1293
1293
|
async credentialsUnset(request) {
|
|
1294
1294
|
const credentials = getCredentials();
|
|
1295
1295
|
if (!credentials) return { ok: false, code: "credentials-unavailable", message: "credentials service is not available" };
|
|
@@ -1409,7 +1409,7 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
1409
1409
|
const name = "web-search-free";
|
|
1410
1410
|
const inject = ["web"];
|
|
1411
1411
|
|
|
1412
|
-
//
|
|
1412
|
+
// 寮曟搸 key 涓庡嚟鎹腑蹇?ref 鐨勬槧灏勶紙鐧藉悕鍗曪細鍙厑璁歌繖浜?ref 琚?UI 璇诲啓鍑嵁涓績锛?
|
|
1413
1413
|
const KEY_REF_MAP = {
|
|
1414
1414
|
exaApiKey: "EXA_API_KEY",
|
|
1415
1415
|
tavilyApiKey: "TAVILY_API_KEY",
|
|
@@ -1420,9 +1420,9 @@ const KEY_REF_MAP = {
|
|
|
1420
1420
|
|
|
1421
1421
|
const Config = z.object({
|
|
1422
1422
|
provider: z.string().default("bing"),
|
|
1423
|
-
cache: z.boolean().default(true), //
|
|
1424
|
-
cacheTtl: z.number().default(5), //
|
|
1425
|
-
keyStorage: z.string().default("credentials"), // key
|
|
1423
|
+
cache: z.boolean().default(true), // 鍗?query 缁撴灉缂撳瓨寮€鍏筹紙闃查檺娴?鐪侀搴︼級
|
|
1424
|
+
cacheTtl: z.number().default(5), // 缂撳瓨鏃堕暱锛堝垎閽燂級锛?-5 鍙厤缃紙浣跨敤澶勫啀 clamp锛?
|
|
1425
|
+
keyStorage: z.string().default("credentials"), // key 瀛樺偍浣嶇疆锛歝redentials锛堝嚟鎹腑蹇冿級| settings锛堣缃〉锛?
|
|
1426
1426
|
lang: z.string().default("zh"),
|
|
1427
1427
|
region: z.string(),
|
|
1428
1428
|
bingMarket: z.string().default("zh-CN"),
|
|
@@ -1439,16 +1439,16 @@ const Config = z.object({
|
|
|
1439
1439
|
function apply(ctx, config) {
|
|
1440
1440
|
let current = () => config ?? {};
|
|
1441
1441
|
const logger = ctx.logger;
|
|
1442
|
-
// credentials
|
|
1442
|
+
// credentials 鏈嶅姟鍙兘鏅氫簬鏈彃浠舵寕杞斤紙璺?bundle 椤哄簭锛夛紝杩愯鏈熷姩鎬佽幏鍙栬€屼笉鏄?apply 鏃剁紦瀛?
|
|
1443
1443
|
const getCredentials = () => ctx.get("credentials");
|
|
1444
1444
|
|
|
1445
|
-
//
|
|
1445
|
+
// 绯荤粺鎻愮ず璇嶅姩鎬佸埛鏂帮細璁剧疆鍙樻洿鏃堕噸鏂扮敓鎴愶紝閬垮厤鏄剧ず鏃у紩鎿?
|
|
1446
1446
|
let refreshPrompt = null;
|
|
1447
1447
|
|
|
1448
|
-
//
|
|
1448
|
+
// 鍗?query 缁撴灉缂撳瓨锛坧rovider.search 鍐呴棴鍖呮寔鏈夛級锛歀RU 50 鏉?/ TTL 鍙厤缃?
|
|
1449
1449
|
const searchCache = new Map(); // key -> { value, expiresAt }
|
|
1450
1450
|
|
|
1451
|
-
// key
|
|
1451
|
+
// key 浼樺厛绾э細credentials锛?credentials.yaml 鍑嵁涓績锛屽畼鏂规帹鑽愶級> settings 鐨?free-search.<x>ApiKey锛堥仐鐣欏吋瀹癸級> 鐜鍙橀噺
|
|
1452
1452
|
const resolveApiKey = async (envName, settingsKey) => {
|
|
1453
1453
|
const credentials = getCredentials();
|
|
1454
1454
|
if (credentials) {
|
|
@@ -1462,32 +1462,32 @@ function apply(ctx, config) {
|
|
|
1462
1462
|
return process.env[envName] ?? "";
|
|
1463
1463
|
};
|
|
1464
1464
|
|
|
1465
|
-
//
|
|
1466
|
-
//
|
|
1467
|
-
//
|
|
1465
|
+
// 鎬绘帶 provider锛氭寜 settings 鐨?provider 瀛楁璺敱鍒颁换鎰忓紩鎿庛€?
|
|
1466
|
+
// 浠讳綍寮曟搸澶辫触锛堢己 key / 401 / 闄愭祦 / 缃戠粶锛夐兘浼氳嚜鍔ㄨ疆娴佸皾璇曚笅涓€涓紩鎿庯紝
|
|
1467
|
+
// 鐩村埌鎴愬姛鎴栧叏閮ㄥけ璐ャ€傚苟鍦ㄧ粨鏋滈噷闄勫甫鍥為€€鎻愮ず锛岄伩鍏?agent 鎼滅储鐩存帴澶辫触銆?
|
|
1468
1468
|
const provider = {
|
|
1469
1469
|
id: "ddg",
|
|
1470
1470
|
available() {
|
|
1471
1471
|
return true;
|
|
1472
1472
|
},
|
|
1473
|
-
//
|
|
1473
|
+
// 鍗?query 缁撴灉缂撳瓨锛歬ey=query+maxResults+timeRangeLabel+preferred锛孧ap 澶╃劧 LRU
|
|
1474
1474
|
async search(request, signal) {
|
|
1475
|
-
//
|
|
1475
|
+
// 鍏叡鍜藉枆鏍¢獙锛歸eb_search / advanced_search / raw-search 涓夋潯璺緞閮界粡杩囪繖閲?
|
|
1476
1476
|
if (request === null || typeof request !== "object" || typeof request.query !== "string" || request.query.trim().length === 0) {
|
|
1477
1477
|
throw new Error("query is required");
|
|
1478
1478
|
}
|
|
1479
1479
|
const cfg = current();
|
|
1480
|
-
//
|
|
1480
|
+
// 棣栭€夊紩鎿庯細free_search 宸ュ叿鏄惧紡鎸囧畾锛坮equest.engine锛変紭鍏堜簬璁剧疆锛坈fg.provider锛?
|
|
1481
1481
|
const preferred =
|
|
1482
1482
|
typeof request.engine === "string" && ALL_ENGINES.includes(request.engine)
|
|
1483
1483
|
? request.engine
|
|
1484
1484
|
: cfg.provider ?? "bing";
|
|
1485
|
-
// time_range
|
|
1486
|
-
//
|
|
1485
|
+
// time_range 杩囨护锛堜粎 advanced_search 宸ュ叿閫忎紶锛涙爣鍑?web_search 鏃犳鍙傛暟锛?
|
|
1486
|
+
// 淇濈暀鍘熷瀛楃涓茬敤浜?Note 灞曠ず锛況aw-search 妗ュ彲鑳藉凡鎶?timeRange 瑙f瀽鎴愬璞?
|
|
1487
1487
|
const timeRange = parseTimeRange(request.timeRange);
|
|
1488
1488
|
const timeRangeLabel = typeof request.timeRange === "string" ? request.timeRange : String(timeRange?.days ?? timeRange?.after ?? "");
|
|
1489
1489
|
|
|
1490
|
-
//
|
|
1490
|
+
// 缂撳瓨 TTL锛堝垎閽燂紝0-5 鍙厤缃級锛沜ache=false 鎴?ttl<=0 鏃跺畬鍏ㄧ鐢?
|
|
1491
1491
|
const cacheTtlMs = (Math.min(Math.max(Number(cfg.cacheTtl) ?? 5, 0), 5)) * 60 * 1000;
|
|
1492
1492
|
const cacheEnabled = cfg.cache !== false && cacheTtlMs > 0;
|
|
1493
1493
|
const cacheKey = cacheEnabled
|
|
@@ -1499,31 +1499,31 @@ function apply(ctx, config) {
|
|
|
1499
1499
|
if (signal?.aborted) throw new Error("search aborted");
|
|
1500
1500
|
searchCache.delete(cacheKey);
|
|
1501
1501
|
searchCache.set(cacheKey, hit);
|
|
1502
|
-
//
|
|
1502
|
+
// 娴呮嫹璐?+ 绉佹湁鏍囪锛歴ources 鏁扮粍涔熷鍒朵竴灞傦紝褰诲簳闅旂缂撳瓨瀵硅薄锛堣皟鐢ㄦ柟 push/鏀瑰厓绱犱笉褰卞搷缂撳瓨锛?
|
|
1503
1503
|
return { ...hit.value, sources: hit.value.sources?.slice(), _cache: "hit" };
|
|
1504
1504
|
}
|
|
1505
1505
|
if (hit) searchCache.delete(cacheKey);
|
|
1506
1506
|
}
|
|
1507
1507
|
|
|
1508
|
-
//
|
|
1508
|
+
// 缁熶竴寮曟搸閾撅細棣栭€変紭鍏堬紝鐒跺悗鍏朵粬浠樿垂寮曟搸锛堟湁 key 鐨勪紭鍏堝皾璇曪級锛屾渶鍚庡厤璐瑰紩鎿?
|
|
1509
1509
|
const paidEngines = ["exa", "tavily", "keenable", "perplexity", "deepseek-official"];
|
|
1510
1510
|
const freeEngines = ["bing", "anysearch", "ddg", "ddg-lite", "searxng"];
|
|
1511
|
-
//
|
|
1511
|
+
// 鏀寔 time_range 杩囨护鐨勫紩鎿庯細tavily / exa / keenable / searxng / ddg / ddg-lite
|
|
1512
1512
|
const timeEngines = ["tavily", "exa", "keenable", "searxng", "ddg", "ddg-lite"];
|
|
1513
1513
|
let chain;
|
|
1514
|
-
//
|
|
1515
|
-
// - "time-filter"
|
|
1516
|
-
// - "failed"
|
|
1517
|
-
// - null
|
|
1514
|
+
// 棣栭€夊紩鎿庤璺宠繃鐨勫師鍥狅紙鐢ㄤ簬鐢熸垚鍑嗙‘鐨?Note锛岄伩鍏嶈瀵?agent/鐢ㄦ埛锛夛細
|
|
1515
|
+
// - "time-filter"锛氬甫 timeRange 涓旈閫夊紩鎿庝笉鏀寔鏃堕棿杩囨护锛堟牴鏈病灏濊瘯锛?
|
|
1516
|
+
// - "failed"锛氶閫夊紩鎿庣‘瀹炶灏濊瘯浣嗗け璐ワ紙缂?key / 401 / 闄愭祦 / 0 缁撴灉 / 缃戠粶锛?
|
|
1517
|
+
// - null锛氶閫夊紩鎿庢垚鍔熸垨鏃犲洖閫€
|
|
1518
1518
|
let preferredSkippedReason = null;
|
|
1519
1519
|
if (timeRange) {
|
|
1520
|
-
//
|
|
1520
|
+
// 鏈夋椂闂磋繃婊ら渶姹傛椂锛屾妸鏀寔杩囨护鐨勫紩鎿庢帓鍓嶉潰锛堥閫夊紩鎿庤嫢鏀寔浠嶄紭鍏堬級
|
|
1521
1521
|
const preferredFirst = [preferred].filter((e) => timeEngines.includes(e));
|
|
1522
1522
|
const otherTime = timeEngines.filter((e) => e !== preferred);
|
|
1523
1523
|
const noTime = [...paidEngines, ...freeEngines].filter((e) => !timeEngines.includes(e) && e !== preferred);
|
|
1524
1524
|
chain = [...preferredFirst, ...otherTime, ...noTime];
|
|
1525
1525
|
if (!timeEngines.includes(preferred)) {
|
|
1526
|
-
//
|
|
1526
|
+
// 棣栭€夊紩鎿庝笉鏀寔鏃堕棿杩囨护 鈫?瀹冧笉鍦ㄩ摼閲岋紝涓嶄細琚皾璇曪紙杩欎笉绛変簬澶辫触锛?
|
|
1527
1527
|
preferredSkippedReason = "time-filter";
|
|
1528
1528
|
}
|
|
1529
1529
|
} else {
|
|
@@ -1534,9 +1534,9 @@ function apply(ctx, config) {
|
|
|
1534
1534
|
|
|
1535
1535
|
let lastError = null;
|
|
1536
1536
|
let usedEngine = null;
|
|
1537
|
-
//
|
|
1537
|
+
// 棣栭€夊紩鎿庤嫢琚皾璇曞悗澶辫触锛岃褰曞け璐ヨ鎯咃紙鐢ㄤ簬 Note锛?
|
|
1538
1538
|
let preferredFailure = null;
|
|
1539
|
-
//
|
|
1539
|
+
// 鎬昏秴鏃堕绠楋細涓茶鍥為€€鏃堕檺鍒舵暣鏉″紩鎿庨摼鐨勬€绘椂闀匡紝闃叉鍚勫紩鎿庤秴鏃剁疮鍔犺揪鍒嗛挓绾?
|
|
1540
1540
|
const BUDGET_MS = 30000;
|
|
1541
1541
|
const deadline = Date.now() + BUDGET_MS;
|
|
1542
1542
|
for (const engine of chain) {
|
|
@@ -1544,7 +1544,7 @@ function apply(ctx, config) {
|
|
|
1544
1544
|
if (remaining <= 0) {
|
|
1545
1545
|
throw new Error(`search timed out after ${BUDGET_MS / 1000}s`);
|
|
1546
1546
|
}
|
|
1547
|
-
//
|
|
1547
|
+
// 缁勫悎澶栭儴鍙栨秷 signal + 鍓╀綑棰勭畻瓒呮椂锛氬畼鏂?web_search 鍙栨秷銆佸紩鎿庤秴鏃躲€佹€婚绠楅兘鑳借Е鍙?
|
|
1548
1548
|
const effSignal = AbortSignal.any([...(signal !== undefined ? [signal] : []), AbortSignal.timeout(remaining)]);
|
|
1549
1549
|
try {
|
|
1550
1550
|
let result;
|
|
@@ -1559,7 +1559,7 @@ function apply(ctx, config) {
|
|
|
1559
1559
|
} else if (engine === "anysearch") {
|
|
1560
1560
|
result = await searchAnysearch(request.query, request.maxResults, effSignal);
|
|
1561
1561
|
} else if (engine === "exa") {
|
|
1562
|
-
// exa
|
|
1562
|
+
// exa锛氭湁 key 璧?REST锛屾棤 key 璧?keyless MCP锛堝厤璐癸級
|
|
1563
1563
|
const key = await resolveApiKey("EXA_API_KEY", "exaApiKey");
|
|
1564
1564
|
if (key) {
|
|
1565
1565
|
result = await searchExa(request.query, request.maxResults, key, timeRange, effSignal);
|
|
@@ -1567,11 +1567,11 @@ function apply(ctx, config) {
|
|
|
1567
1567
|
result = await searchExaMCP(request.query, request.maxResults, effSignal);
|
|
1568
1568
|
}
|
|
1569
1569
|
} else if (engine === "tavily") {
|
|
1570
|
-
// tavily
|
|
1570
|
+
// tavily锛氭湁 key 璧拌处鍙锋。锛屾棤 key 璧?keyless锛堝厤璐瑰尶鍚嶉搴︼級
|
|
1571
1571
|
const key = await resolveApiKey("TAVILY_API_KEY", "tavilyApiKey");
|
|
1572
1572
|
result = await searchTavily(request.query, request.maxResults, key, timeRange, effSignal);
|
|
1573
1573
|
} else if (engine === "keenable") {
|
|
1574
|
-
// keenable
|
|
1574
|
+
// keenable锛氭湁 key 璧?REST锛屾棤 key 璧?keyless MCP锛堝厤璐癸級
|
|
1575
1575
|
const key = await resolveApiKey("KEENABLE_API_KEY", "keenableApiKey");
|
|
1576
1576
|
result = await searchKeenable(request.query, request.maxResults, key, timeRange, effSignal);
|
|
1577
1577
|
} else if (engine === "perplexity") {
|
|
@@ -1580,7 +1580,7 @@ function apply(ctx, config) {
|
|
|
1580
1580
|
lastError = new Error("Perplexity requires PERPLEXITY_API_KEY");
|
|
1581
1581
|
if (engine === preferred) preferredFailure = "PERPLEXITY_API_KEY is not configured";
|
|
1582
1582
|
logger.warn(`free-search: engine "${engine}" skipped (no key), trying next engine`);
|
|
1583
|
-
continue; //
|
|
1583
|
+
continue; // 鏃?key 璺宠繃
|
|
1584
1584
|
}
|
|
1585
1585
|
result = await searchPerplexity(request.query, request.maxResults, key, effSignal);
|
|
1586
1586
|
} else if (engine === "deepseek-official") {
|
|
@@ -1589,7 +1589,7 @@ function apply(ctx, config) {
|
|
|
1589
1589
|
lastError = new Error("DeepSeek requires DEEPSEEK_API_KEY");
|
|
1590
1590
|
if (engine === preferred) preferredFailure = "DEEPSEEK_API_KEY is not configured";
|
|
1591
1591
|
logger.warn(`free-search: engine "${engine}" skipped (no key), trying next engine`);
|
|
1592
|
-
continue; //
|
|
1592
|
+
continue; // 鏃?key 璺宠繃
|
|
1593
1593
|
}
|
|
1594
1594
|
result = await searchDeepSeekOfficial(request.query, request.maxResults, key, effSignal);
|
|
1595
1595
|
} else {
|
|
@@ -1598,11 +1598,11 @@ function apply(ctx, config) {
|
|
|
1598
1598
|
|
|
1599
1599
|
if (result.sources.length > 0) {
|
|
1600
1600
|
usedEngine = engine;
|
|
1601
|
-
//
|
|
1601
|
+
// 缁熶竴娓呮礂 snippet锛氬幓鐧诲綍/浠樿垂澧?璁㈤槄鍣煶锛屾姌鍙犵┖鐧斤紙鏈夊€肩殑鎵嶅鐞嗭紝淇濇寔 lossless JSON锛?
|
|
1602
1602
|
result.sources = result.sources.map((s) =>
|
|
1603
1603
|
s.snippet ? { ...s, snippet: cleanSnippet(s.snippet) } : s
|
|
1604
1604
|
);
|
|
1605
|
-
//
|
|
1605
|
+
// 鐢ㄤ簡闈為閫夊紩鎿庢椂锛屽湪缁撴灉閲岄檮涓婂噯纭彁绀猴紙鍖哄垎"涓嶆敮鎸佹椂闂磋繃婊よ璺宠繃"涓?鐪熷疄澶辫触"锛?
|
|
1606
1606
|
if (engine !== preferred) {
|
|
1607
1607
|
if (preferredSkippedReason === "time-filter") {
|
|
1608
1608
|
result.content = `Note: ${preferred} does not support time filtering (timeRange=${timeRangeLabel}), using ${engine}.`;
|
|
@@ -1612,10 +1612,10 @@ function apply(ctx, config) {
|
|
|
1612
1612
|
result.content = `Note: ${preferred} unavailable or failed, using ${engine}.`;
|
|
1613
1613
|
}
|
|
1614
1614
|
}
|
|
1615
|
-
//
|
|
1615
|
+
// 鍐欏叆缂撳瓨锛堝彧缂撳瓨鎴愬姛缁撴灉锛屽け璐ヨ蛋 throw 澶╃劧涓嶇紦瀛橈級
|
|
1616
1616
|
const cached = { ...result, provider: engine, engine: engine };
|
|
1617
1617
|
if (cacheKey !== null) {
|
|
1618
|
-
// fallback
|
|
1618
|
+
// fallback 鏉$洰锛堝疄闄呭紩鎿庘墵棣栭€夛級鐢ㄩ厤缃?TTL 鐨?1/5锛岄閫夋垚鍔熶繚鎸佸畬鏁?TTL
|
|
1619
1619
|
const entryTtlMs = engine !== preferred ? Math.max(cacheTtlMs / 5, 1000) : cacheTtlMs;
|
|
1620
1620
|
searchCache.set(cacheKey, {
|
|
1621
1621
|
value: cached,
|
|
@@ -1642,14 +1642,16 @@ function apply(ctx, config) {
|
|
|
1642
1642
|
},
|
|
1643
1643
|
};
|
|
1644
1644
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1645
|
+
ctx.inject(["settings"], (sctx) => {
|
|
1646
|
+
sctx.settings.installSection(ctx, FREE_SEARCH_NS, Config, config ?? {}, {
|
|
1647
|
+
setSource: (source) => {
|
|
1648
|
+
current = source;
|
|
1649
|
+
},
|
|
1650
|
+
onChange: () => {
|
|
1651
|
+
// settings 鍙樻洿鏃跺埛鏂扮郴缁熸彁绀鸿瘝锛堟樉绀烘渶鏂板紩鎿庯級
|
|
1652
|
+
if (typeof refreshPrompt === "function") refreshPrompt();
|
|
1653
|
+
},
|
|
1654
|
+
});
|
|
1653
1655
|
});
|
|
1654
1656
|
|
|
1655
1657
|
ctx.inject(["webServer", "settings"], (sctx) => {
|
|
@@ -1668,16 +1670,16 @@ function apply(ctx, config) {
|
|
|
1668
1670
|
|
|
1669
1671
|
ctx.web.registerSearchProvider(provider);
|
|
1670
1672
|
|
|
1671
|
-
//
|
|
1672
|
-
//
|
|
1673
|
-
//
|
|
1674
|
-
//
|
|
1673
|
+
// 杩愯鏃跺厹搴曪細DSH 0.1.1+ 涓?profile patch 鐨?config 浼氭暣浣撹鐩?bundle patch 鐨?config锛?
|
|
1674
|
+
// 鐢ㄦ埛鐨?`- id: web` patch锛堝鍙 fetchProvider锛変細闈欓粯鎶规帀 searchProvider锛屽鑷村洖閫€ DeepSeek 瀹樻柟鎼滅储銆?
|
|
1675
|
+
// 杩欓噷鍦?provider 娉ㄥ唽鍚庢鏌ワ細鏈寚鍚戜换浣?provider锛坲ndefined锛夋椂鑷姩鎺ョ涓烘湰鎻掍欢锛?
|
|
1676
|
+
// 鐢ㄦ埛鏄惧紡閰嶇疆浜嗗叾浠?provider 鍒欎笉鍔ㄣ€?
|
|
1675
1677
|
if (!ctx.web.searchProviderId) {
|
|
1676
1678
|
ctx.web.searchProviderId = provider.id;
|
|
1677
1679
|
logger.info(`free-search: web.searchProvider was unset (patch override or missing config), taking over as "${provider.id}"`);
|
|
1678
1680
|
}
|
|
1679
1681
|
|
|
1680
|
-
//
|
|
1682
|
+
// 娴嬭瘯宸ュ叿锛氳 agent 閫愪釜娴嬭瘯鎵€鏈夋悳绱㈠紩鎿庯紝鎶ュ憡鍙敤鎬?
|
|
1681
1683
|
const runEngineTest = async (engine, query, timeRange) => {
|
|
1682
1684
|
const cfg = current();
|
|
1683
1685
|
const q = query || "DeepSeek Harness";
|
|
@@ -1723,9 +1725,9 @@ function apply(ctx, config) {
|
|
|
1723
1725
|
};
|
|
1724
1726
|
try {
|
|
1725
1727
|
const result = await attempt();
|
|
1726
|
-
//
|
|
1728
|
+
// 浠樿垂寮曟搸鏃?key锛氱洿鎺ラ€忎紶澶辫触缁撴灉
|
|
1727
1729
|
if (result.ok === false) return result;
|
|
1728
|
-
//
|
|
1730
|
+
// 鍏嶈垂寮曟搸鍋跺彂鍙嶇埇/绌虹粨鏋滄椂閲嶈瘯涓€娆?
|
|
1729
1731
|
if (result.sources && result.sources.length === 0) {
|
|
1730
1732
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
1731
1733
|
return await attempt();
|
|
@@ -1813,7 +1815,7 @@ function apply(ctx, config) {
|
|
|
1813
1815
|
return { results };
|
|
1814
1816
|
},
|
|
1815
1817
|
finalizeContent(exec, result) {
|
|
1816
|
-
//
|
|
1818
|
+
// 鎶?render 杈撳嚭鍖呰鎴愬悎娉曠殑 text block锛坈ontent 蹇呴』鏄?block 鏁扮粍锛?
|
|
1817
1819
|
const text = result.content;
|
|
1818
1820
|
if (typeof text === "string" && text.length > 0) {
|
|
1819
1821
|
return [{ type: "text", text }];
|
|
@@ -1828,7 +1830,7 @@ function apply(ctx, config) {
|
|
|
1828
1830
|
}, "free-search: test engines tool");
|
|
1829
1831
|
});
|
|
1830
1832
|
|
|
1831
|
-
//
|
|
1833
|
+
// 骞冲彴鎼滅储宸ュ叿锛欸itHub / V2EX / Bilibili / Reddit锛堝叕寮€ API锛岄浂渚濊禆锛?
|
|
1832
1834
|
ctx.inject(["tools"], (sctx) => {
|
|
1833
1835
|
sctx.effect(() => {
|
|
1834
1836
|
const dispose = sctx.tools.register(
|
|
@@ -1880,7 +1882,7 @@ function apply(ctx, config) {
|
|
|
1880
1882
|
if (!PLATFORMS[platform]) {
|
|
1881
1883
|
throw new Error(`unknown platform "${platform}" - use one of: ${Object.keys(PLATFORMS).join(", ")}`);
|
|
1882
1884
|
}
|
|
1883
|
-
//
|
|
1885
|
+
// 骞冲彴寮€鍏筹細settings 閲岀鐢ㄦ煇骞冲彴鏃讹紝宸ュ叿鏄庣‘鍛婄煡
|
|
1884
1886
|
const enabled = current().platforms ?? ["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"];
|
|
1885
1887
|
if (!enabled.includes(platform)) {
|
|
1886
1888
|
throw new Error(
|
|
@@ -1889,7 +1891,7 @@ function apply(ctx, config) {
|
|
|
1889
1891
|
}
|
|
1890
1892
|
const limit = Math.min(args.maxResults ?? 5, 10);
|
|
1891
1893
|
const result = await searchPlatform(platform, args.query, limit, undefined, current().lang);
|
|
1892
|
-
// lossless JSON
|
|
1894
|
+
// lossless JSON 涓嶅厑璁?undefined 瀛楁锛氬墧闄ょ己澶卞瓧娈?
|
|
1893
1895
|
const sources = (result.sources ?? []).map((s) => {
|
|
1894
1896
|
const source = {};
|
|
1895
1897
|
if (s.url !== undefined && s.url !== null && s.url !== "") source.url = s.url;
|
|
@@ -1912,8 +1914,8 @@ function apply(ctx, config) {
|
|
|
1912
1914
|
}, "free-search: platform search tool");
|
|
1913
1915
|
});
|
|
1914
1916
|
|
|
1915
|
-
//
|
|
1916
|
-
//
|
|
1917
|
+
// 楂樼骇鎼滅储宸ュ叿锛氭敮鎸佹椂闂磋繃婊わ紙time_range锛夊拰鎸囧畾寮曟搸锛坋ngine锛夈€?
|
|
1918
|
+
// 璧颁笌 web_search 鐩稿悓鐨勭粺涓€鍥為€€閾撅紝浣嗗厑璁?agent 鏄惧紡璇锋眰"鏈€杩?N 澶?鐨勭粨鏋溿€?
|
|
1917
1919
|
ctx.inject(["tools"], (sctx) => {
|
|
1918
1920
|
sctx.effect(() => {
|
|
1919
1921
|
const dispose = sctx.tools.register(
|
|
@@ -1973,10 +1975,10 @@ function apply(ctx, config) {
|
|
|
1973
1975
|
maxResults: Math.min(args.maxResults ?? 5, 10),
|
|
1974
1976
|
};
|
|
1975
1977
|
if (parseTimeRange(args.timeRange) !== undefined) request.timeRange = args.timeRange;
|
|
1976
|
-
// engine
|
|
1978
|
+
// engine 鎸囧畾鏃讹細浠呭綋璇ュ紩鎿庡彲鐢ㄦ墠浼樺厛锛堜粛璧板洖閫€閾撅紝澶辫触鑷姩鎹㈠紩鎿庯級
|
|
1977
1979
|
if (args.engine && ALL_ENGINES.includes(args.engine)) request.engine = args.engine;
|
|
1978
1980
|
const result = await provider.search(request);
|
|
1979
|
-
// lossless JSON
|
|
1981
|
+
// lossless JSON 涓嶅厑璁?undefined 瀛楁锛氭寜瀛樺湪鐨勫€兼瀯閫犲璞★紝缂哄瓧娈电洿鎺ョ渷鐣?
|
|
1980
1982
|
return {
|
|
1981
1983
|
provider: result.provider ?? result._provider ?? "bing",
|
|
1982
1984
|
content: typeof result.content === "string" ? result.content : "",
|
|
@@ -2005,7 +2007,7 @@ function apply(ctx, config) {
|
|
|
2005
2007
|
}, "free-search: advanced search tool");
|
|
2006
2008
|
});
|
|
2007
2009
|
|
|
2008
|
-
//
|
|
2010
|
+
// 璁?agent 鐭ラ亾鍙敤鎼滅储寮曟搸锛堝姩鎬佺敓鎴愶紝闅?key/璁剧疆鍙樺寲锛?
|
|
2009
2011
|
ctx.inject(["systemPrompt"], (sctx) => {
|
|
2010
2012
|
let disposeSection = null;
|
|
2011
2013
|
refreshPrompt = () => {
|
|
@@ -2043,7 +2045,7 @@ function apply(ctx, config) {
|
|
|
2043
2045
|
"",
|
|
2044
2046
|
"For platform-specific searches (GitHub repos, V2EX threads, Bilibili videos, Reddit posts, Hacker News discussions, Stack Overflow questions, Wikipedia articles, npm packages), use the platform_search tool with platform: github|v2ex|bilibili|reddit|hn|stackoverflow|wikipedia|npm.",
|
|
2045
2047
|
"",
|
|
2046
|
-
"The user can switch the search engine themselves by typing /free-search-engine in the chat
|
|
2048
|
+
"The user can switch the search engine themselves by typing /free-search-engine in the chat 鈥?it opens a picker to choose an engine, just like the settings page. This changes the preferred engine; search still falls back to other engines automatically if it fails. You should not switch engines on your own; let the user decide.",
|
|
2047
2049
|
].join("\n"),
|
|
2048
2050
|
});
|
|
2049
2051
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
2
|
"name": "dsh-free-search",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.20",
|
|
4
4
|
"description": "Free web search for DeepSeek Harness: 10 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable keyless) + time filtering + platform search + web_fetch, with web settings UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|