dsh-free-search 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/lib/client.js +25 -5
- package/lib/index.js +89 -5
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
29
29
|
### 特性
|
|
30
30
|
|
|
31
31
|
- **零成本** —— 多个免费引擎,无需 key、无需注册
|
|
32
|
-
- **多引擎可选**:DuckDuckGo(html/lite)、Bing、SearXNG(元搜索,支持自定义实例)、AnySearch、Exa、Tavily、Keenable、Firecrawl、Parallel、Perplexity、SerpBase、DeepSeek 官方、You.com、百度千帆、Kimi、阿里云百炼、火山联网搜索(豆包搜索)、OpenAI / Gemini / Claude 模型内置搜索
|
|
32
|
+
- **多引擎可选**:DuckDuckGo(html/lite)、Bing、SearXNG(元搜索,支持自定义实例)、AnySearch、Exa、Tavily、Keenable、Firecrawl、Parallel、Perplexity、SerpBase、Serply、DeepSeek 官方、You.com、百度千帆、Kimi、阿里云百炼、火山联网搜索(豆包搜索)、OpenAI / Gemini / Claude 模型内置搜索
|
|
33
33
|
- **网页设置页** —— 引擎切换 + API key 配置(UI 中 key 脱敏显示"已配置")+ 中英文切换;入口在左侧「插件」页的组件行配置(`plugins.row.config`,DSH 0.1.7-rc.1+)
|
|
34
34
|
- **弹出式切换命令** —— 聊天框输入 `/free-search-engine`,弹出引擎选择窗口,点选即切换(等效设置页 + 保存)
|
|
35
35
|
- **引擎测试** —— `free_search_test` 工具让 agent 一键测试所有引擎;设置页也有"测试引擎"按钮(直测当前引擎,不走回退链,付费引擎无 key 会明确报错)
|
|
@@ -66,6 +66,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
66
66
|
| `parallel` | Parallel | 免费 | **无 key 也可用**(官方 MCP 匿名额度),配 key 提升额度并支持精确时间过滤 |
|
|
67
67
|
| `perplexity` | Perplexity | 付费 | 需 `PERPLEXITY_API_KEY` |
|
|
68
68
|
| `serpbase` | SerpBase | 付费 | 需 `SERPBASE_API_KEY`(serpbase.dev,注册送 100 次免费额度) |
|
|
69
|
+
| `serply` | Serply | 付费 | 需 `SERPLY_API_KEY`(serply.io,新账户 30 天内 2,500 次免费额度);Google 网页结果,按设置页语言本地化 |
|
|
69
70
|
| `deepseek-official` | DeepSeek 官方 | 付费 | 需 `DEEPSEEK_API_KEY` |
|
|
70
71
|
| `you` | You.com | 付费 | 需 `YOUCOM_API_KEY`(you.com/platform/api-keys,注册即送免费额度) |
|
|
71
72
|
| `baidu` | 百度千帆 AI 搜索 | 额度 | 需 `BAIDU_API_KEY`(千帆 AI 搜索,每日赠 50 次,超额按量后付费);中文全网,支持时间过滤 |
|
|
@@ -85,6 +86,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
85
86
|
- Parallel:<https://platform.parallel.ai>
|
|
86
87
|
- Perplexity:<https://www.perplexity.ai/settings/api>
|
|
87
88
|
- SerpBase:<https://serpbase.dev>
|
|
89
|
+
- Serply:<https://serply.io>(API 文档:<https://serply.io/docs>)
|
|
88
90
|
- DeepSeek:<https://platform.deepseek.com/api_keys>
|
|
89
91
|
- 豆包搜索(火山联网搜索):<https://console.volcengine.com/search-infinity/web-search>
|
|
90
92
|
- You.com:<https://you.com/platform/api-keys>
|
|
@@ -165,7 +167,7 @@ Error: configured web provider "ddg" is not registered
|
|
|
165
167
|
配置页提供:
|
|
166
168
|
|
|
167
169
|
- **Search engine**:下拉框切换引擎,保存即生效
|
|
168
|
-
- **API keys**:为 Exa / Tavily / Keenable / Firecrawl / Parallel / Perplexity / DeepSeek / SerpBase / You.com 填写 key(密码框,保存后只显示"已配置";Exa / Tavily / Keenable / Firecrawl / Parallel 不填也可免 key 使用)
|
|
170
|
+
- **API keys**:为 Exa / Tavily / Keenable / Firecrawl / Parallel / Perplexity / DeepSeek / SerpBase / Serply / You.com 填写 key(密码框,保存后只显示"已配置";Exa / Tavily / Keenable / Firecrawl / Parallel 不填也可免 key 使用)
|
|
169
171
|
- **推荐**:付费引擎 key 建议写入 harness 凭据中心 `~/.dsh/.credentials.yaml`(如 `DEEPSEEK_API_KEY: sk-...`,与官方 LLM provider 一致,一处管理所有 key)。插件读取优先级:凭据中心 > 设置页 > 环境变量,设置页填的 key 仅作为遗留兼容。
|
|
170
172
|
- **Test engine**:直测当前引擎可用性(不走回退链,付费引擎无 key 会明确报错)
|
|
171
173
|
- **Use Bing default**:把当前搜索引擎切回稳定的免费 Bing;`Discard` 只撤销尚未保存的编辑
|
|
@@ -229,7 +231,7 @@ DSH 0.1.7-rc.1 起,配置跟随 profile 的插件条目保存:设置页与 `
|
|
|
229
231
|
|
|
230
232
|
```yaml
|
|
231
233
|
# profiles/<profile>/cordis.patch.yml 中该条目的 config:
|
|
232
|
-
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you / baidu / kimi / aliyun / doubao / openai / gemini / claude / auto / multi
|
|
234
|
+
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / serply / deepseek-official / you / baidu / kimi / aliyun / doubao / openai / gemini / claude / auto / multi
|
|
233
235
|
fallbackOn: [quota, auth, bot-wall, transient, invalid-response, unknown] # 允许触发回退的失败类别;[] = 任何失败都立即中止(默认全部)
|
|
234
236
|
lang: zh # 设置页界面语言(zh / en)
|
|
235
237
|
bingMarket: zh-CN # Bing 市场
|
|
@@ -243,6 +245,7 @@ firecrawlApiKey: ... # 或通过设置页填写
|
|
|
243
245
|
parallelApiKey: ... # 或通过设置页填写
|
|
244
246
|
perplexityApiKey: ...
|
|
245
247
|
serpbaseApiKey: ... # 或通过设置页填写
|
|
248
|
+
serplyApiKey: ... # 或通过设置页填写
|
|
246
249
|
deepseekApiKey: ...
|
|
247
250
|
```
|
|
248
251
|
|
|
@@ -391,7 +394,7 @@ This plugin provides multiple free search engines with automatic fallback, compl
|
|
|
391
394
|
### Features
|
|
392
395
|
|
|
393
396
|
- **Zero Cost** — Multiple free engines with no API key or registration required
|
|
394
|
-
- **Multi-Engine Support** — DuckDuckGo (HTML / Lite), Bing, AnySearch AI, SearXNG (meta-search with custom instances), Exa, Tavily, Keenable, Firecrawl, Parallel, Perplexity, SerpBase, DeepSeek Official, You.com, Baidu Qianfan, Kimi, Aliyun Bailian, Volcano Doubao, and the model-based OpenAI / Gemini / Claude searches
|
|
397
|
+
- **Multi-Engine Support** — DuckDuckGo (HTML / Lite), Bing, AnySearch AI, SearXNG (meta-search with custom instances), Exa, Tavily, Keenable, Firecrawl, Parallel, Perplexity, SerpBase, Serply, DeepSeek Official, You.com, Baidu Qianfan, Kimi, Aliyun Bailian, Volcano Doubao, and the model-based OpenAI / Gemini / Claude searches
|
|
395
398
|
- **Web Settings UI** — Engine switching, API key configuration (keys masked as "configured" in the UI), and a Chinese/English toggle; the entry is the component-row config (`plugins.row.config`) on the sidebar Plugins page (DSH 0.1.7-rc.1+)
|
|
396
399
|
- **Popup Switch Command** — Type `/free-search-engine` in the chat: a picker opens with all engines; click one to switch (equivalent to the settings page + save)
|
|
397
400
|
- **Engine Testing** — `free_search_test` for the agent to check all engines in one call; the settings UI also has a "Test engine" button that tests the selected engine directly (no fallback chain; paid engines without a key report an explicit error)
|
|
@@ -428,6 +431,7 @@ If this plugin has been helpful, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh
|
|
|
428
431
|
| `parallel` | Parallel | Free | **Works without a key** (official MCP anonymous quota); a key raises limits and enables precise time filtering |
|
|
429
432
|
| `perplexity` | Perplexity | Paid | Requires `PERPLEXITY_API_KEY` |
|
|
430
433
|
| `serpbase` | SerpBase | Paid | Requires `SERPBASE_API_KEY` (serpbase.dev, 100 free queries on signup) |
|
|
434
|
+
| `serply` | Serply | Paid | Requires `SERPLY_API_KEY` (serply.io, 2,500 free credits for the first 30 days); Google web results localized to the settings language |
|
|
431
435
|
| `deepseek-official` | DeepSeek Official | Paid | Requires `DEEPSEEK_API_KEY` |
|
|
432
436
|
| `you` | You.com | Paid | Requires `YOUCOM_API_KEY` (you.com/platform/api-keys, free tier on signup) |
|
|
433
437
|
| `baidu` | Baidu Qianfan AI Search | Quota | Requires `BAIDU_API_KEY` (Qianfan AI Search, 50 free calls/day then pay-as-you-go); Chinese web-wide, supports time filtering |
|
|
@@ -447,6 +451,7 @@ If this plugin has been helpful, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh
|
|
|
447
451
|
- Parallel: <https://platform.parallel.ai>
|
|
448
452
|
- Perplexity: <https://www.perplexity.ai/settings/api>
|
|
449
453
|
- SerpBase: <https://serpbase.dev>
|
|
454
|
+
- Serply: <https://serply.io> (API docs: <https://serply.io/docs>)
|
|
450
455
|
- DeepSeek: <https://platform.deepseek.com/api_keys>
|
|
451
456
|
- Doubao / Volcano Web Search: <https://console.volcengine.com/search-infinity/web-search>
|
|
452
457
|
- You.com: <https://you.com/platform/api-keys>
|
|
@@ -591,7 +596,7 @@ The old `free-search:` section of `~/.dsh/settings.yaml` is **not** imported by
|
|
|
591
596
|
|
|
592
597
|
```yaml
|
|
593
598
|
# config of that entry in profiles/<profile>/cordis.patch.yml:
|
|
594
|
-
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you / baidu / kimi / aliyun / doubao / openai / gemini / claude / auto / multi
|
|
599
|
+
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / serply / deepseek-official / you / baidu / kimi / aliyun / doubao / openai / gemini / claude / auto / multi
|
|
595
600
|
fallbackOn: [quota, auth, bot-wall, transient, invalid-response, unknown] # failure classes allowed to trigger fallback; [] = abort on any failure (default: all)
|
|
596
601
|
lang: zh # settings UI language (zh / en)
|
|
597
602
|
bingMarket: zh-CN # Bing market
|
|
@@ -605,6 +610,7 @@ firecrawlApiKey: ... # Or configure via the web settings UI
|
|
|
605
610
|
parallelApiKey: ... # Or configure via the web settings UI
|
|
606
611
|
perplexityApiKey: ...
|
|
607
612
|
serpbaseApiKey: ... # Or configure via the web settings UI
|
|
613
|
+
serplyApiKey: ... # Or configure via the web settings UI
|
|
608
614
|
deepseekApiKey: ...
|
|
609
615
|
```
|
|
610
616
|
|
package/lib/client.js
CHANGED
|
@@ -144,6 +144,7 @@ window.__ModuleLoader__.load({
|
|
|
144
144
|
perplexityPh: (c) => c ? "Perplexity API 密钥(已配置)" : "Perplexity API 密钥(pplx-...)",
|
|
145
145
|
deepseekPh: (c) => c ? "DeepSeek API 密钥(已配置)" : "DeepSeek API 密钥(sk-...)",
|
|
146
146
|
serpbasePh: (c) => c ? "SerpBase API 密钥(已配置)" : "SerpBase API 密钥(serpbase.dev 获取)",
|
|
147
|
+
serplyPh: (c) => c ? "Serply API 密钥(已配置)" : "Serply API 密钥(serply.io 获取)",
|
|
147
148
|
youcomPh: (c) => c ? "You.com API 密钥(已配置)" : "You.com API 密钥(you.com/platform/api-keys 获取)",
|
|
148
149
|
baiduPh: (c) => c ? "Baidu API 密钥(已配置)" : "百度千帆 API 密钥(BAIDU_API_KEY)",
|
|
149
150
|
kimiPh: (c) => c ? "Kimi API 密钥(已配置)" : "Kimi API 密钥(MOONSHOT_API_KEY)",
|
|
@@ -156,7 +157,7 @@ window.__ModuleLoader__.load({
|
|
|
156
157
|
keyStorage: "Key 存储位置",
|
|
157
158
|
keyStorageCred: "凭据中心(推荐)",
|
|
158
159
|
keyStorageSettings: "设置页(兼容)",
|
|
159
|
-
keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun", "doubao", "openai", "gemini", "claude"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
|
|
160
|
+
keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "serply", "deepseek", "youcom", "baidu", "kimi", "aliyun", "doubao", "openai", "gemini", "claude"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
|
|
160
161
|
keyStorageSettingsHint: "保存后写入当前 profile 的插件条目 config(cordis.patch.yml,优先级低于凭据中心)。",
|
|
161
162
|
engineEnable: "全局启用的搜索引擎",
|
|
162
163
|
engineEnableHint: "取消勾选后,该引擎会从普通 web_search 回退链、Auto 智能路由、advanced_search、multi_search 和引擎测试中全局排除。至少保留一个引擎。",
|
|
@@ -235,6 +236,7 @@ window.__ModuleLoader__.load({
|
|
|
235
236
|
perplexityPh: (c) => c ? "Perplexity API key (configured)" : "Perplexity API key (pplx-...)",
|
|
236
237
|
deepseekPh: (c) => c ? "DeepSeek API key (configured)" : "DeepSeek API key (sk-...)",
|
|
237
238
|
serpbasePh: (c) => c ? "SerpBase API key (configured)" : "SerpBase API key (from serpbase.dev)",
|
|
239
|
+
serplyPh: (c) => c ? "Serply API key (configured)" : "Serply API key (from serply.io)",
|
|
238
240
|
youcomPh: (c) => c ? "You.com API key (configured)" : "You.com API key (from you.com/platform/api-keys)",
|
|
239
241
|
baiduPh: (c) => c ? "Baidu API key (configured)" : "Baidu Qianfan API key (BAIDU_API_KEY)",
|
|
240
242
|
kimiPh: (c) => c ? "Kimi API key (configured)" : "Kimi API key (MOONSHOT_API_KEY)",
|
|
@@ -247,7 +249,7 @@ window.__ModuleLoader__.load({
|
|
|
247
249
|
keyStorage: "Key storage",
|
|
248
250
|
keyStorageCred: "Credential center (recommended)",
|
|
249
251
|
keyStorageSettings: "Settings page (legacy)",
|
|
250
|
-
keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun", "doubao", "openai", "gemini", "claude"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
|
|
252
|
+
keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "serply", "deepseek", "youcom", "baidu", "kimi", "aliyun", "doubao", "openai", "gemini", "claude"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
|
|
251
253
|
keyStorageSettingsHint: "Saved to the active profile plugin entry config (cordis.patch.yml; lower priority than the credential center).",
|
|
252
254
|
engineEnable: "Globally enabled search engines",
|
|
253
255
|
engineEnableHint: "Unchecked engines are excluded globally from web_search fallback, Auto routing, advanced_search, multi_search, and engine tests. At least one engine must remain enabled.",
|
|
@@ -312,7 +314,7 @@ window.__ModuleLoader__.load({
|
|
|
312
314
|
};
|
|
313
315
|
const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
|
|
314
316
|
// 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致)
|
|
315
|
-
const PLUGIN_VERSION = "0.7.
|
|
317
|
+
const PLUGIN_VERSION = "0.7.4";
|
|
316
318
|
const ENGINES = [
|
|
317
319
|
{ id: "auto", label: "Auto · 智能路由", badge: "AUTO", link: null },
|
|
318
320
|
{ id: "multi", label: "Multi Search · 多引擎", badge: "MULTI", link: null },
|
|
@@ -328,6 +330,7 @@ window.__ModuleLoader__.load({
|
|
|
328
330
|
{ id: "parallel", label: "Parallel", badge: "FREE", link: "https://platform.parallel.ai" },
|
|
329
331
|
{ id: "perplexity", label: "Perplexity", badge: "API KEY", link: "https://www.perplexity.ai/settings/api" },
|
|
330
332
|
{ id: "serpbase", label: "SerpBase · Google", badge: "API KEY", link: "https://serpbase.dev" },
|
|
333
|
+
{ id: "serply", label: "Serply · Google", badge: "API KEY", link: "https://serply.io" },
|
|
331
334
|
{ id: "deepseek-official", label: "DeepSeek Official", badge: "API KEY", link: "https://platform.deepseek.com/api_keys" },
|
|
332
335
|
{ id: "you", label: "You.com", badge: "API KEY", link: "https://you.com/platform/api-keys" },
|
|
333
336
|
{ id: "baidu", label: "Baidu · 百度千帆", badge: "API KEY", link: "https://console.bce.baidu.com/qianfan" },
|
|
@@ -339,7 +342,7 @@ window.__ModuleLoader__.load({
|
|
|
339
342
|
{ id: "claude", label: "Claude · 内置搜索", badge: "API KEY", link: "https://console.anthropic.com/settings/keys" },
|
|
340
343
|
];
|
|
341
344
|
const ENGINE_IDS = ENGINES.filter((engine) => engine.id !== "auto" && engine.id !== "multi").map((engine) => engine.id);
|
|
342
|
-
const DEFAULT_FALLBACK_ORDER = ["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao", "bing", "anysearch", "ddg", "ddg-lite", "searxng"];
|
|
345
|
+
const DEFAULT_FALLBACK_ORDER = ["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "serply", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao", "bing", "anysearch", "ddg", "ddg-lite", "searxng"];
|
|
343
346
|
const normalizeFallbackOrder = (value) => {
|
|
344
347
|
const configured = Array.isArray(value) ? value.filter((id) => ENGINE_IDS.includes(id)) : [];
|
|
345
348
|
return [...new Set([...configured, ...DEFAULT_FALLBACK_ORDER, ...ENGINE_IDS])];
|
|
@@ -469,6 +472,7 @@ window.__ModuleLoader__.load({
|
|
|
469
472
|
const [perplexityKey, setPerplexityKey] = react.useState("");
|
|
470
473
|
const [deepseekKey, setDeepseekKey] = react.useState("");
|
|
471
474
|
const [serpbaseKey, setSerpbaseKey] = react.useState("");
|
|
475
|
+
const [serplyKey, setSerplyKey] = react.useState("");
|
|
472
476
|
const [youcomKey, setYoucomKey] = react.useState("");
|
|
473
477
|
const [baiduKey, setBaiduKey] = react.useState("");
|
|
474
478
|
const [kimiKey, setKimiKey] = react.useState("");
|
|
@@ -523,6 +527,7 @@ window.__ModuleLoader__.load({
|
|
|
523
527
|
setPerplexityKey(v.perplexityApiKey ?? "");
|
|
524
528
|
setDeepseekKey(v.deepseekApiKey ?? "");
|
|
525
529
|
setSerpbaseKey(v.serpbaseApiKey ?? "");
|
|
530
|
+
setSerplyKey(v.serplyApiKey ?? "");
|
|
526
531
|
setYoucomKey(v.youcomApiKey ?? "");
|
|
527
532
|
setBaiduKey(v.baiduApiKey ?? "");
|
|
528
533
|
setKimiKey(v.kimiApiKey ?? "");
|
|
@@ -547,6 +552,7 @@ window.__ModuleLoader__.load({
|
|
|
547
552
|
if (path === "perplexityApiKey") configured.perplexity = true;
|
|
548
553
|
if (path === "deepseekApiKey") configured.deepseek = true;
|
|
549
554
|
if (path === "serpbaseApiKey") configured.serpbase = true;
|
|
555
|
+
if (path === "serplyApiKey") configured.serply = true;
|
|
550
556
|
if (path === "youcomApiKey") configured.youcom = true;
|
|
551
557
|
if (path === "baiduApiKey") configured.baidu = true;
|
|
552
558
|
if (path === "kimiApiKey") configured.kimi = true;
|
|
@@ -566,7 +572,7 @@ window.__ModuleLoader__.load({
|
|
|
566
572
|
const cred = await bridgeCredentialsStatus();
|
|
567
573
|
if (cred.ok) {
|
|
568
574
|
const cc = {};
|
|
569
|
-
const map = { anysearchApiKey: "anysearch", exaApiKey: "exa", tavilyApiKey: "tavily", keenableApiKey: "keenable", firecrawlApiKey: "firecrawl", parallelApiKey: "parallel", perplexityApiKey: "perplexity", deepseekApiKey: "deepseek", serpbaseApiKey: "serpbase", youcomApiKey: "youcom", baiduApiKey: "baidu", kimiApiKey: "kimi", aliyunApiKey: "aliyun", doubaoApiKey: "doubao", openaiApiKey: "openai", geminiApiKey: "gemini", claudeApiKey: "claude" };
|
|
575
|
+
const map = { anysearchApiKey: "anysearch", exaApiKey: "exa", tavilyApiKey: "tavily", keenableApiKey: "keenable", firecrawlApiKey: "firecrawl", parallelApiKey: "parallel", perplexityApiKey: "perplexity", deepseekApiKey: "deepseek", serpbaseApiKey: "serpbase", serplyApiKey: "serply", youcomApiKey: "youcom", baiduApiKey: "baidu", kimiApiKey: "kimi", aliyunApiKey: "aliyun", doubaoApiKey: "doubao", openaiApiKey: "openai", geminiApiKey: "gemini", claudeApiKey: "claude" };
|
|
570
576
|
for (const [k, v] of Object.entries(cred.value.configured ?? {})) {
|
|
571
577
|
if (map[k]) cc[map[k]] = v;
|
|
572
578
|
}
|
|
@@ -612,6 +618,7 @@ window.__ModuleLoader__.load({
|
|
|
612
618
|
["perplexityApiKey", perplexityKey],
|
|
613
619
|
["deepseekApiKey", deepseekKey],
|
|
614
620
|
["serpbaseApiKey", serpbaseKey],
|
|
621
|
+
["serplyApiKey", serplyKey],
|
|
615
622
|
["youcomApiKey", youcomKey],
|
|
616
623
|
["baiduApiKey", baiduKey],
|
|
617
624
|
["kimiApiKey", kimiKey],
|
|
@@ -1185,6 +1192,19 @@ react_jsx_runtime.jsx("div", {
|
|
|
1185
1192
|
setSaveError("");
|
|
1186
1193
|
},
|
|
1187
1194
|
}),
|
|
1195
|
+
react_jsx_runtime.jsx("input", {
|
|
1196
|
+
className: "dshfs-input",
|
|
1197
|
+
type: "password",
|
|
1198
|
+
placeholder: t.serplyPh(keysConfigured.serply),
|
|
1199
|
+
value: serplyKey,
|
|
1200
|
+
disabled: !ready || saving,
|
|
1201
|
+
onChange: (e) => {
|
|
1202
|
+
setSerplyKey(e.target.value);
|
|
1203
|
+
setDirty(true);
|
|
1204
|
+
setFailed(false);
|
|
1205
|
+
setSaveError("");
|
|
1206
|
+
},
|
|
1207
|
+
}),
|
|
1188
1208
|
react_jsx_runtime.jsx("input", {
|
|
1189
1209
|
className: "dshfs-input",
|
|
1190
1210
|
type: "password",
|
package/lib/index.js
CHANGED
|
@@ -6,6 +6,28 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { exec } from "node:child_process";
|
|
8
8
|
|
|
9
|
+
// 兼容 guard(#55):Node 会优先解析插件目录下的嵌套依赖,若那里残留旧的
|
|
10
|
+
// @deepseek-ai/schemastery(如 node_modules/dsh-free-search/node_modules/... -> 3.18.1),
|
|
11
|
+
// 它会遮蔽顶层正确的 3.18.4,而旧版没有 .volatile(),导致 import 时报
|
|
12
|
+
// "z.string(...).volatile is not a function" 且完全不知道原因。这里提前检测并给出可操作的修复提示。
|
|
13
|
+
function assertSchemasterySupportsVolatile(schemaLib) {
|
|
14
|
+
let ok = false;
|
|
15
|
+
try {
|
|
16
|
+
ok = typeof schemaLib.string().default("x").volatile === "function";
|
|
17
|
+
} catch {
|
|
18
|
+
ok = false;
|
|
19
|
+
}
|
|
20
|
+
if (!ok) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"[dsh-free-search] the resolved @deepseek-ai/schemastery has no `.volatile()` (needs >= 3.18.2; this plugin declares ^3.18.4). " +
|
|
23
|
+
"This usually means a stale nested copy shadows the top-level install - check " +
|
|
24
|
+
"`node_modules/dsh-free-search/node_modules/@deepseek-ai/schemastery` (often a symlink to an old 3.18.x); " +
|
|
25
|
+
"delete that stale directory (or reinstall) so the top-level 3.18.4 is used, then fully restart DSH. See issue #55."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
assertSchemasterySupportsVolatile(z);
|
|
30
|
+
|
|
9
31
|
const DDG_HTML_URL = "https://html.duckduckgo.com/html/";
|
|
10
32
|
const DDG_LITE_URL = "https://lite.duckduckgo.com/lite/";
|
|
11
33
|
const BING_URL = "https://www.bing.com/search";
|
|
@@ -16,6 +38,7 @@ const PARALLEL_MCP_URL = "https://search.parallel.ai/mcp";
|
|
|
16
38
|
const KEENABLE_URL = "https://api.keenable.ai/v1/search";
|
|
17
39
|
const KEENABLE_MCP_URL = "https://api.keenable.ai/mcp";
|
|
18
40
|
const SERPBASE_URL = "https://api.serpbase.dev/google/search";
|
|
41
|
+
const SERPLY_URL = "https://api.serply.io/v1/search";
|
|
19
42
|
const YOUCOM_URL = "https://ydc-index.io/v1/search";
|
|
20
43
|
const DOUBAO_SEARCH_URL = "https://open.feedcoopapi.com/search_api/web_search";
|
|
21
44
|
const USER_AGENT =
|
|
@@ -55,7 +78,7 @@ const MARKET_TO_LANG = {
|
|
|
55
78
|
const FREE_SEARCH_NS = "web-search-free";
|
|
56
79
|
const BRIDGE_PREFIX = "/api/dsh-free-search-settings";
|
|
57
80
|
const FREE_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch"];
|
|
58
|
-
const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao", "openai", "gemini", "claude"];
|
|
81
|
+
const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "serply", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao", "openai", "gemini", "claude"];
|
|
59
82
|
|
|
60
83
|
// 模型内置搜索(按次计费):只在你显式选中该引擎时使用;自动回退链与 auto 路由都不会兜底到它们。
|
|
61
84
|
const EXPLICIT_ONLY_ENGINES = ["openai", "gemini", "claude"];
|
|
@@ -134,7 +157,7 @@ const TIME_ENGINES = ["tavily", "exa", "keenable", "firecrawl", "parallel", "sea
|
|
|
134
157
|
// 虚拟搜索模式:不是单个引擎,而是 provider.search 内部的路由/并发策略
|
|
135
158
|
const SEARCH_MODES = ["auto", "multi"];
|
|
136
159
|
|
|
137
|
-
const DEFAULT_FALLBACK_ORDER = ["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao", "bing", "anysearch", "ddg", "ddg-lite", "searxng"];
|
|
160
|
+
const DEFAULT_FALLBACK_ORDER = ["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "serply", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao", "bing", "anysearch", "ddg", "ddg-lite", "searxng"];
|
|
138
161
|
|
|
139
162
|
function normalizeDisabledEngines(value) {
|
|
140
163
|
if (!Array.isArray(value)) return [];
|
|
@@ -191,7 +214,7 @@ function routeEngines(query, { timeRange, disabledEngines = [], fallbackOrder =
|
|
|
191
214
|
}
|
|
192
215
|
|
|
193
216
|
// 当前插件版本(发布时与 package.json 同步)
|
|
194
|
-
const PLUGIN_VERSION = "0.7.
|
|
217
|
+
const PLUGIN_VERSION = "0.7.4";
|
|
195
218
|
// 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
|
|
196
219
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
|
|
197
220
|
const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
|
|
@@ -1784,6 +1807,47 @@ async function searchSerpbase(query, maxResults, apiKey, options, signal) {
|
|
|
1784
1807
|
return { sources: uniqueSources(sources, maxResults ?? 10), truncated: false };
|
|
1785
1808
|
}
|
|
1786
1809
|
|
|
1810
|
+
// Serply: 必须配置 SERPLY_API_KEY(无 key 跳过,同 serpbase)
|
|
1811
|
+
// Google SERP API:GET + X-Api-Key 头,返回 results[](title/link/description);num 上限 10。
|
|
1812
|
+
// 错误走 HTTP 状态码,401 时 JSON 为 {"detail": "Invalid API key"}。
|
|
1813
|
+
async function searchSerply(query, maxResults, apiKey, options, signal) {
|
|
1814
|
+
if (!apiKey) throw new Error("Serply search requires SERPLY_API_KEY");
|
|
1815
|
+
// hl/gl 跟随设置页的 lang,与 SerpBase 同一套 Google 本地化参数,默认 en/us
|
|
1816
|
+
const locale = SERPBASE_LOCALE[options?.lang] ?? SERPBASE_LOCALE.en;
|
|
1817
|
+
const params = new URLSearchParams({
|
|
1818
|
+
q: query,
|
|
1819
|
+
num: String(Math.min(Math.max(maxResults ?? 10, 1), 10)),
|
|
1820
|
+
hl: locale.hl,
|
|
1821
|
+
gl: locale.gl,
|
|
1822
|
+
});
|
|
1823
|
+
const response = await fetch(`${SERPLY_URL}?${params}`, {
|
|
1824
|
+
method: "GET",
|
|
1825
|
+
redirect: "error",
|
|
1826
|
+
headers: {
|
|
1827
|
+
"X-Api-Key": apiKey,
|
|
1828
|
+
accept: "application/json",
|
|
1829
|
+
"user-agent": "deepseek-harness/free-search",
|
|
1830
|
+
},
|
|
1831
|
+
signal: AbortSignal.any([...(signal !== undefined ? [signal] : []), AbortSignal.timeout(15000)]),
|
|
1832
|
+
});
|
|
1833
|
+
if (!response.ok) {
|
|
1834
|
+
const detail = await response.text().catch(() => "");
|
|
1835
|
+
if (response.status === 401 || response.status === 403) {
|
|
1836
|
+
throw new Error(`Serply API key is invalid (HTTP ${response.status}) - update it in Settings > Plugins > Free Search`);
|
|
1837
|
+
}
|
|
1838
|
+
throw new Error(`Serply API error (HTTP ${response.status}): ${detail.slice(0, 200)}`);
|
|
1839
|
+
}
|
|
1840
|
+
const data = await response.json();
|
|
1841
|
+
const sources = (data.results ?? [])
|
|
1842
|
+
.filter((r) => r.link)
|
|
1843
|
+
.map((r) => ({
|
|
1844
|
+
url: r.link,
|
|
1845
|
+
...(r.title ? { title: String(r.title) } : {}),
|
|
1846
|
+
...(r.description ? { snippet: String(r.description) } : {}),
|
|
1847
|
+
}));
|
|
1848
|
+
return { sources: uniqueSources(sources, maxResults ?? 10), truncated: false };
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1787
1851
|
// You.com: requires YOUCOM_API_KEY (get a free key at https://you.com/platform/api-keys)
|
|
1788
1852
|
async function searchYoucom(query, maxResults, apiKey, signal) {
|
|
1789
1853
|
if (!apiKey) throw new Error("You.com search requires YOUCOM_API_KEY");
|
|
@@ -2768,6 +2832,7 @@ const KEY_REF_MAP = {
|
|
|
2768
2832
|
parallelApiKey: "PARALLEL_API_KEY",
|
|
2769
2833
|
perplexityApiKey: "PERPLEXITY_API_KEY",
|
|
2770
2834
|
serpbaseApiKey: "SERPBASE_API_KEY",
|
|
2835
|
+
serplyApiKey: "SERPLY_API_KEY",
|
|
2771
2836
|
deepseekApiKey: "DEEPSEEK_API_KEY",
|
|
2772
2837
|
youcomApiKey: "YOUCOM_API_KEY",
|
|
2773
2838
|
baiduApiKey: "BAIDU_API_KEY",
|
|
@@ -2805,6 +2870,7 @@ const Config = z.object({
|
|
|
2805
2870
|
parallelApiKey: z.string().role("secret").volatile(),
|
|
2806
2871
|
perplexityApiKey: z.string().role("secret").volatile(),
|
|
2807
2872
|
serpbaseApiKey: z.string().role("secret").volatile(),
|
|
2873
|
+
serplyApiKey: z.string().role("secret").volatile(),
|
|
2808
2874
|
deepseekApiKey: z.string().role("secret").volatile(),
|
|
2809
2875
|
youcomApiKey: z.string().role("secret").volatile(),
|
|
2810
2876
|
baiduApiKey: z.string().role("secret").volatile(),
|
|
@@ -2852,6 +2918,7 @@ const LEGACY_MIGRATABLE_KEYS = [
|
|
|
2852
2918
|
"parallelApiKey",
|
|
2853
2919
|
"perplexityApiKey",
|
|
2854
2920
|
"serpbaseApiKey",
|
|
2921
|
+
"serplyApiKey",
|
|
2855
2922
|
"deepseekApiKey",
|
|
2856
2923
|
"youcomApiKey",
|
|
2857
2924
|
"baiduApiKey",
|
|
@@ -3200,6 +3267,16 @@ function apply(ctx, config) {
|
|
|
3200
3267
|
continue; // 无 key 跳过
|
|
3201
3268
|
}
|
|
3202
3269
|
result = await searchSerpbase(request.query, request.maxResults, key, cfg, effSignal);
|
|
3270
|
+
} else if (engine === "serply") {
|
|
3271
|
+
// serply:必须配置 SERPLY_API_KEY(无 key 跳过)
|
|
3272
|
+
const key = await resolveApiKey("SERPLY_API_KEY", "serplyApiKey");
|
|
3273
|
+
if (!key) {
|
|
3274
|
+
lastError = new Error("Serply requires SERPLY_API_KEY (get one at serply.io)");
|
|
3275
|
+
if (engine === preferred) preferredFailure = "SERPLY_API_KEY is not configured";
|
|
3276
|
+
logger.warn(`free-search: engine "${engine}" skipped (no key), trying next engine`);
|
|
3277
|
+
continue; // 无 key 跳过
|
|
3278
|
+
}
|
|
3279
|
+
result = await searchSerply(request.query, request.maxResults, key, cfg, effSignal);
|
|
3203
3280
|
} else if (engine === "you") {
|
|
3204
3281
|
// you.com:必须配置 YOUCOM_API_KEY(无 key 跳过)
|
|
3205
3282
|
const key = await resolveApiKey("YOUCOM_API_KEY", "youcomApiKey");
|
|
@@ -3478,6 +3555,11 @@ function apply(ctx, config) {
|
|
|
3478
3555
|
if (!key) return { ok: false, error: "SERPBASE_API_KEY not configured" };
|
|
3479
3556
|
return await searchSerpbase(q, n, key, cfg);
|
|
3480
3557
|
}
|
|
3558
|
+
case "serply": {
|
|
3559
|
+
const key = await resolveApiKey("SERPLY_API_KEY", "serplyApiKey");
|
|
3560
|
+
if (!key) return { ok: false, error: "SERPLY_API_KEY not configured" };
|
|
3561
|
+
return await searchSerply(q, n, key, cfg);
|
|
3562
|
+
}
|
|
3481
3563
|
case "baidu": {
|
|
3482
3564
|
const key = await resolveApiKey("BAIDU_API_KEY", "baiduApiKey");
|
|
3483
3565
|
if (!key) return { ok: false, error: "BAIDU_API_KEY not configured" };
|
|
@@ -3661,7 +3743,7 @@ function apply(ctx, config) {
|
|
|
3661
3743
|
parameters: {
|
|
3662
3744
|
engines: {
|
|
3663
3745
|
type: "array",
|
|
3664
|
-
description: "Which engines to test (default: all enabled engines). Options: ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, deepseek-official, you, baidu, kimi, aliyun, doubao, openai, gemini, claude; the virtual modes auto and multi are also accepted and run the real routing/concurrent path. openai/gemini/claude are model-based and bill per search, so they are only used when explicitly named.",
|
|
3746
|
+
description: "Which engines to test (default: all enabled engines). Options: ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, serply, deepseek-official, you, baidu, kimi, aliyun, doubao, openai, gemini, claude; the virtual modes auto and multi are also accepted and run the real routing/concurrent path. openai/gemini/claude are model-based and bill per search, so they are only used when explicitly named.",
|
|
3665
3747
|
items: { type: "string" },
|
|
3666
3748
|
},
|
|
3667
3749
|
query: {
|
|
@@ -3983,7 +4065,7 @@ function apply(ctx, config) {
|
|
|
3983
4065
|
},
|
|
3984
4066
|
engine: {
|
|
3985
4067
|
type: "string",
|
|
3986
|
-
description: "Optional search mode/engine: multi (parallel top-3 merge), auto, ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, deepseek-official, you, baidu, kimi, aliyun, doubao, openai, gemini, claude (model-based; explicit selection only).",
|
|
4068
|
+
description: "Optional search mode/engine: multi (parallel top-3 merge), auto, ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, serply, deepseek-official, you, baidu, kimi, aliyun, doubao, openai, gemini, claude (model-based; explicit selection only).",
|
|
3987
4069
|
},
|
|
3988
4070
|
},
|
|
3989
4071
|
output: {
|
|
@@ -4166,6 +4248,7 @@ function apply(ctx, config) {
|
|
|
4166
4248
|
export {
|
|
4167
4249
|
ALL_ENGINES,
|
|
4168
4250
|
EXPLICIT_ONLY_ENGINES,
|
|
4251
|
+
assertSchemasterySupportsVolatile,
|
|
4169
4252
|
COOLDOWN_SESSION_MS,
|
|
4170
4253
|
engineCooldowns,
|
|
4171
4254
|
classifyFailure,
|
|
@@ -4229,6 +4312,7 @@ export {
|
|
|
4229
4312
|
searchReddit,
|
|
4230
4313
|
searchSearxng,
|
|
4231
4314
|
searchSerpbase,
|
|
4315
|
+
searchSerply,
|
|
4232
4316
|
searchYoucom,
|
|
4233
4317
|
searchStackOverflow,
|
|
4234
4318
|
searchTavily,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-free-search",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Free web search for DeepSeek Harness: 21 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Perplexity/SerpBase/DeepSeek/You.com/Baidu/Kimi/Aliyun/Doubao with key; OpenAI/Gemini/Claude model built-in search, explicit-only) + auto smart routing + global engine enable/disable & fallback priority + multi mode (parallel top-3 merge) + multi_search cross-source tool + failure-aware fallback (quota/auth cooldown, transient retry, fallbackOn) + time filtering + platform search + web_fetch, with web settings UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"parallel",
|
|
29
29
|
"perplexity",
|
|
30
30
|
"serpbase",
|
|
31
|
+
"serply",
|
|
31
32
|
"search",
|
|
32
33
|
"free",
|
|
33
34
|
"baidu",
|