dsh-free-search 0.5.6 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/cordis.patch.yml +1 -1
- package/lib/client.js +24 -4
- package/lib/index.js +138 -13
- package/package.json +6 -3
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
|
|
32
|
+
- **多引擎可选**:DuckDuckGo(html/lite)、Bing、SearXNG(元搜索,支持自定义实例)、AnySearch、Exa、Tavily、Keenable、Firecrawl、Parallel、Perplexity、SerpBase、DeepSeek 官方、You.com、百度千帆、Kimi、阿里云百炼、火山联网搜索(豆包搜索)
|
|
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 会明确报错)
|
|
@@ -68,6 +68,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
68
68
|
| `baidu` | 百度千帆 AI 搜索 | 额度 | 需 `BAIDU_API_KEY`(千帆 AI 搜索,每日赠 50 次,超额按量后付费);中文全网,支持时间过滤 |
|
|
69
69
|
| `kimi` | Kimi(Moonshot)联网搜索 | 付费 | 需 `MOONSHOT_API_KEY`(basic 约 ¥0.01/次),返回带正文 chunks 的中文结果 |
|
|
70
70
|
| `aliyun` | 阿里云百炼 EnhancedSearch | 付费 | 需 `DASHSCOPE_API_KEY`(MCP search_pro,约 ¥0.03/次,新用户 200 次免费包);中文全网带来源 hostname |
|
|
71
|
+
| `doubao` | 火山引擎联网搜索(豆包搜索) | 免费额度 | 需 `DOUBAO_SEARCH_API_KEY`(联网搜索控制台开通,**每月 500 次免费**,超出按量付费);中文时效内容强,支持时间/站点过滤,返回千字级摘要 |
|
|
71
72
|
|
|
72
73
|
- **默认引擎为 `bing`**(免费且最稳定),安装后开箱即用。
|
|
73
74
|
- **自动回退**:任何引擎失败(免费限流/反爬,付费缺 key/无效/网络错误)都会自动轮流尝试下一个引擎——先试其他已配 key 的付费引擎,再试免费引擎(Bing/AnySearch 等),并在结果中附带回退提示——搜索不会因引擎问题直接失败。
|
|
@@ -79,6 +80,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
79
80
|
- Perplexity:<https://www.perplexity.ai/settings/api>
|
|
80
81
|
- SerpBase:<https://serpbase.dev>
|
|
81
82
|
- DeepSeek:<https://platform.deepseek.com/api_keys>
|
|
83
|
+
- 豆包搜索(火山联网搜索):<https://console.volcengine.com/search-infinity/web-search>
|
|
82
84
|
- You.com:<https://you.com/platform/api-keys>
|
|
83
85
|
|
|
84
86
|
#### 为什么免费引擎不需要 key?
|
|
@@ -192,7 +194,7 @@ DSH 0.1.7-rc.1 起,配置跟随 profile 的插件条目保存:设置页与 `
|
|
|
192
194
|
|
|
193
195
|
```yaml
|
|
194
196
|
# profiles/<profile>/cordis.patch.yml 中该条目的 config:
|
|
195
|
-
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you
|
|
197
|
+
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you / baidu / kimi / aliyun / doubao
|
|
196
198
|
lang: zh # 设置页界面语言(zh / en)
|
|
197
199
|
bingMarket: zh-CN # Bing 市场
|
|
198
200
|
region: cn-zh # DuckDuckGo 区域(可选)
|
|
@@ -340,7 +342,7 @@ This plugin provides multiple free search engines with automatic fallback, compl
|
|
|
340
342
|
### Features
|
|
341
343
|
|
|
342
344
|
- **Zero Cost** — Multiple free engines with no API key or registration required
|
|
343
|
-
- **Multi-Engine Support** — DuckDuckGo (HTML / Lite), Bing, AnySearch AI, SearXNG (meta-search with custom instances), Exa, Tavily, Keenable, Firecrawl, Parallel, Perplexity, SerpBase, DeepSeek Official,
|
|
345
|
+
- **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, and Volcano Doubao
|
|
344
346
|
- **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+)
|
|
345
347
|
- **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)
|
|
346
348
|
- **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)
|
|
@@ -379,6 +381,7 @@ If this plugin has been helpful, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh
|
|
|
379
381
|
| `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 |
|
|
380
382
|
| `kimi` | Kimi (Moonshot) Web Search | Paid | Requires `MOONSHOT_API_KEY` (basic ~¥0.01/call); returns Chinese results with body chunks |
|
|
381
383
|
| `aliyun` | Aliyun Bailian EnhancedSearch | Paid | Requires `DASHSCOPE_API_KEY` (MCP search_pro, ~¥0.03/call, 200 free calls for new users); Chinese web-wide with source hostnames |
|
|
384
|
+
| `doubao` | Volcano Web Search (Doubao) | Free quota | Requires `DOUBAO_SEARCH_API_KEY` (open it in the Web Search console; **500 free queries/month**, pay-as-you-go beyond); strong for fresh Chinese content, supports time/site filters, returns long summaries |
|
|
382
385
|
|
|
383
386
|
- **Default engine is `bing`** (free and most stable), ready to use out of the box after installation.
|
|
384
387
|
- **Auto-failover**: any engine failure (rate-limited free engine, or missing/invalid paid key, network error) automatically tries the next engine — the configured engine first, then other engines (exa/tavily/keenable/firecrawl/parallel are tried even without a key because they have built-in keyless quota), then the remaining free engines (Bing/AnySearch etc.) — with a note attached to the results naming the engine that actually served them (e.g. `Note: perplexity unavailable or failed, using exa.`). Search never fails outright because of engine issues.
|
|
@@ -390,6 +393,7 @@ If this plugin has been helpful, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh
|
|
|
390
393
|
- Perplexity: <https://www.perplexity.ai/settings/api>
|
|
391
394
|
- SerpBase: <https://serpbase.dev>
|
|
392
395
|
- DeepSeek: <https://platform.deepseek.com/api_keys>
|
|
396
|
+
- Doubao / Volcano Web Search: <https://console.volcengine.com/search-infinity/web-search>
|
|
393
397
|
- You.com: <https://you.com/platform/api-keys>
|
|
394
398
|
|
|
395
399
|
#### Why are some engines free?
|
|
@@ -503,7 +507,7 @@ The old `free-search:` section of `~/.dsh/settings.yaml` is **not** imported by
|
|
|
503
507
|
|
|
504
508
|
```yaml
|
|
505
509
|
# config of that entry in profiles/<profile>/cordis.patch.yml:
|
|
506
|
-
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you
|
|
510
|
+
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you / baidu / kimi / aliyun / doubao
|
|
507
511
|
lang: zh # settings UI language (zh / en)
|
|
508
512
|
bingMarket: zh-CN # Bing market
|
|
509
513
|
region: cn-zh # DuckDuckGo region (optional)
|
package/cordis.patch.yml
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# The engine is chosen from the sidebar Plugins page -> Free Search row config
|
|
5
5
|
# (plugins.row.config, DSH 0.1.7-rc.1+), from the /free-search-engine command,
|
|
6
6
|
# or directly via this entry's config, e.g.:
|
|
7
|
-
# provider: bing # engine id: ddg / bing / baidu / kimi / aliyun / auto / ...
|
|
7
|
+
# provider: bing # engine id: ddg / bing / baidu / kimi / aliyun / doubao / auto / ...
|
|
8
8
|
# region: cn-zh # DuckDuckGo region
|
|
9
9
|
# bingMarket: zh-CN # Bing market
|
|
10
10
|
# A legacy ~/.dsh/settings.yaml "free-search:" section is migrated into this
|
package/lib/client.js
CHANGED
|
@@ -137,11 +137,12 @@ window.__ModuleLoader__.load({
|
|
|
137
137
|
baiduPh: (c) => c ? "Baidu API 密钥(已配置)" : "百度千帆 API 密钥(BAIDU_API_KEY)",
|
|
138
138
|
kimiPh: (c) => c ? "Kimi API 密钥(已配置)" : "Kimi API 密钥(MOONSHOT_API_KEY)",
|
|
139
139
|
aliyunPh: (c) => c ? "Aliyun API 密钥(已配置)" : "阿里云 DashScope 密钥(DASHSCOPE_API_KEY)",
|
|
140
|
+
doubaoPh: (c) => c ? "豆包搜索密钥(已配置)" : "豆包搜索 / 火山联网搜索密钥(DOUBAO_SEARCH_API_KEY,每月 500 次免费)",
|
|
140
141
|
keysHint: "密钥读取优先级:.credentials.yaml 凭据中心 > 这里 > 环境变量。推荐把 key 写进凭据中心(与官方 LLM 一致,一处管理)。",
|
|
141
142
|
keyStorage: "Key 存储位置",
|
|
142
143
|
keyStorageCred: "凭据中心(推荐)",
|
|
143
144
|
keyStorageSettings: "设置页(兼容)",
|
|
144
|
-
keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
|
|
145
|
+
keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun", "doubao"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
|
|
145
146
|
keyStorageSettingsHint: "保存后写入当前 profile 的插件条目 config(cordis.patch.yml,优先级低于凭据中心)。",
|
|
146
147
|
platformSearch: "平台搜索(platform_search 工具)",
|
|
147
148
|
platformHint: "为 agent 的 platform_search 工具启用平台。禁用的平台会被跳过。",
|
|
@@ -210,11 +211,12 @@ window.__ModuleLoader__.load({
|
|
|
210
211
|
baiduPh: (c) => c ? "Baidu API key (configured)" : "Baidu Qianfan API key (BAIDU_API_KEY)",
|
|
211
212
|
kimiPh: (c) => c ? "Kimi API key (configured)" : "Kimi API key (MOONSHOT_API_KEY)",
|
|
212
213
|
aliyunPh: (c) => c ? "Aliyun API key (configured)" : "Aliyun DashScope API key (DASHSCOPE_API_KEY)",
|
|
214
|
+
doubaoPh: (c) => c ? "Doubao search key (configured)" : "Doubao / Volcano Web Search key (DOUBAO_SEARCH_API_KEY, 500 free/month)",
|
|
213
215
|
keysHint: "Key resolution: .credentials.yaml credential center > here > environment variables. Recommended: store keys in the credential center (same as official LLM providers, one place for all).",
|
|
214
216
|
keyStorage: "Key storage",
|
|
215
217
|
keyStorageCred: "Credential center (recommended)",
|
|
216
218
|
keyStorageSettings: "Settings page (legacy)",
|
|
217
|
-
keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
|
|
219
|
+
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"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
|
|
218
220
|
keyStorageSettingsHint: "Saved to the active profile plugin entry config (cordis.patch.yml; lower priority than the credential center).",
|
|
219
221
|
platformSearch: "Platform search (platform_search tool)",
|
|
220
222
|
platformHint: "Enable platforms for the agent's platform_search tool. Disabled platforms are skipped.",
|
|
@@ -265,7 +267,7 @@ window.__ModuleLoader__.load({
|
|
|
265
267
|
};
|
|
266
268
|
const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
|
|
267
269
|
// 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致)
|
|
268
|
-
const PLUGIN_VERSION = "0.
|
|
270
|
+
const PLUGIN_VERSION = "0.6.0";
|
|
269
271
|
const ENGINES = [
|
|
270
272
|
{ id: "auto", label: "Auto · 智能路由", badge: "AUTO", link: null },
|
|
271
273
|
{ id: "ddg", label: "DuckDuckGo · HTML", badge: "FREE", link: "https://duckduckgo.com" },
|
|
@@ -285,6 +287,7 @@ window.__ModuleLoader__.load({
|
|
|
285
287
|
{ id: "baidu", label: "Baidu · 百度千帆", badge: "API KEY", link: "https://console.bce.baidu.com/qianfan" },
|
|
286
288
|
{ id: "kimi", label: "Kimi · Moonshot", badge: "API KEY", link: "https://platform.moonshot.cn" },
|
|
287
289
|
{ id: "aliyun", label: "Aliyun · 百炼", badge: "API KEY", link: "https://bailian.console.aliyun.com" },
|
|
290
|
+
{ id: "doubao", label: "Doubao · 豆包搜索", badge: "API KEY", link: "https://console.volcengine.com/search-infinity/web-search" },
|
|
288
291
|
];
|
|
289
292
|
|
|
290
293
|
async function bridgeDescribe() {
|
|
@@ -400,6 +403,7 @@ window.__ModuleLoader__.load({
|
|
|
400
403
|
const [baiduKey, setBaiduKey] = react.useState("");
|
|
401
404
|
const [kimiKey, setKimiKey] = react.useState("");
|
|
402
405
|
const [aliyunKey, setAliyunKey] = react.useState("");
|
|
406
|
+
const [doubaoKey, setDoubaoKey] = react.useState("");
|
|
403
407
|
const [platforms, setPlatforms] = react.useState(["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]);
|
|
404
408
|
const [cacheTtl, setCacheTtl] = react.useState(5);
|
|
405
409
|
const [keysConfigured, setKeysConfigured] = react.useState({});
|
|
@@ -443,6 +447,7 @@ window.__ModuleLoader__.load({
|
|
|
443
447
|
setBaiduKey(v.baiduApiKey ?? "");
|
|
444
448
|
setKimiKey(v.kimiApiKey ?? "");
|
|
445
449
|
setAliyunKey(v.aliyunApiKey ?? "");
|
|
450
|
+
setDoubaoKey(v.doubaoApiKey ?? "");
|
|
446
451
|
setPlatforms(Array.isArray(v.platforms) && v.platforms.length > 0 ? v.platforms : ["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]);
|
|
447
452
|
setCacheTtl(v.cacheTtl === undefined ? 5 : Math.min(Math.max(Number(v.cacheTtl) ?? 5, 0), 5));
|
|
448
453
|
// secrets 字段标记哪些 key 已配置(值被脱敏,仅显示"已配置")
|
|
@@ -463,6 +468,7 @@ window.__ModuleLoader__.load({
|
|
|
463
468
|
if (path === "baiduApiKey") configured.baidu = true;
|
|
464
469
|
if (path === "kimiApiKey") configured.kimi = true;
|
|
465
470
|
if (path === "aliyunApiKey") configured.aliyun = true;
|
|
471
|
+
if (path === "doubaoApiKey") configured.doubao = true;
|
|
466
472
|
}
|
|
467
473
|
}
|
|
468
474
|
setKeysConfigured(configured);
|
|
@@ -474,7 +480,7 @@ window.__ModuleLoader__.load({
|
|
|
474
480
|
const cred = await bridgeCredentialsStatus();
|
|
475
481
|
if (cred.ok) {
|
|
476
482
|
const cc = {};
|
|
477
|
-
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" };
|
|
483
|
+
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" };
|
|
478
484
|
for (const [k, v] of Object.entries(cred.value.configured ?? {})) {
|
|
479
485
|
if (map[k]) cc[map[k]] = v;
|
|
480
486
|
}
|
|
@@ -524,6 +530,7 @@ window.__ModuleLoader__.load({
|
|
|
524
530
|
["baiduApiKey", baiduKey],
|
|
525
531
|
["kimiApiKey", kimiKey],
|
|
526
532
|
["aliyunApiKey", aliyunKey],
|
|
533
|
+
["doubaoApiKey", doubaoKey],
|
|
527
534
|
];
|
|
528
535
|
if (keyStorage === "credentials") {
|
|
529
536
|
for (const [field, value] of keyFields) {
|
|
@@ -982,6 +989,19 @@ react_jsx_runtime.jsx("div", {
|
|
|
982
989
|
setSaveError("");
|
|
983
990
|
},
|
|
984
991
|
}),
|
|
992
|
+
react_jsx_runtime.jsx("input", {
|
|
993
|
+
className: "dshfs-input",
|
|
994
|
+
type: "password",
|
|
995
|
+
placeholder: t.doubaoPh(keysConfigured.doubao),
|
|
996
|
+
value: doubaoKey,
|
|
997
|
+
disabled: !ready || saving,
|
|
998
|
+
onChange: (e) => {
|
|
999
|
+
setDoubaoKey(e.target.value);
|
|
1000
|
+
setDirty(true);
|
|
1001
|
+
setFailed(false);
|
|
1002
|
+
setSaveError("");
|
|
1003
|
+
},
|
|
1004
|
+
}),
|
|
985
1005
|
react_jsx_runtime.jsx("p", {
|
|
986
1006
|
className: "dshfs-hint",
|
|
987
1007
|
children: t.keysHint,
|
package/lib/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const KEENABLE_URL = "https://api.keenable.ai/v1/search";
|
|
|
17
17
|
const KEENABLE_MCP_URL = "https://api.keenable.ai/mcp";
|
|
18
18
|
const SERPBASE_URL = "https://api.serpbase.dev/google/search";
|
|
19
19
|
const YOUCOM_URL = "https://ydc-index.io/v1/search";
|
|
20
|
+
const DOUBAO_SEARCH_URL = "https://open.feedcoopapi.com/search_api/web_search";
|
|
20
21
|
const USER_AGENT =
|
|
21
22
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
22
23
|
const ACCEPT_LANG = "zh-CN,zh;q=0.9,en;q=0.8";
|
|
@@ -54,11 +55,11 @@ const MARKET_TO_LANG = {
|
|
|
54
55
|
const FREE_SEARCH_NS = "web-search-free";
|
|
55
56
|
const BRIDGE_PREFIX = "/api/dsh-free-search-settings";
|
|
56
57
|
const FREE_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch"];
|
|
57
|
-
const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun"];
|
|
58
|
+
const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao"];
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
// 支持 time_range 过滤的引擎
|
|
61
|
-
const TIME_ENGINES = ["tavily", "exa", "keenable", "firecrawl", "parallel", "searxng", "ddg", "ddg-lite", "baidu"];
|
|
62
|
+
const TIME_ENGINES = ["tavily", "exa", "keenable", "firecrawl", "parallel", "searxng", "ddg", "ddg-lite", "baidu", "doubao"];
|
|
62
63
|
|
|
63
64
|
// 智能路由(provider=auto 时启用):纯确定性规则重排引擎顺序,末尾接上其余全部引擎保证回退链完整
|
|
64
65
|
function routeEngines(query, { timeRange } = {}) {
|
|
@@ -82,7 +83,7 @@ function routeEngines(query, { timeRange } = {}) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
// 当前插件版本(发布时与 package.json 同步)
|
|
85
|
-
const PLUGIN_VERSION = "0.
|
|
86
|
+
const PLUGIN_VERSION = "0.6.0";
|
|
86
87
|
// 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
|
|
87
88
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
|
|
88
89
|
const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
|
|
@@ -212,14 +213,21 @@ function buildCacheKey(query, maxResults, timeRangeLabel, preferred) {
|
|
|
212
213
|
const SNIPPET_NOISE =
|
|
213
214
|
/\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;
|
|
214
215
|
|
|
215
|
-
function cleanSnippet(text) {
|
|
216
|
+
function cleanSnippet(text, max = 300) {
|
|
216
217
|
if (!text) return text;
|
|
217
218
|
return String(text)
|
|
218
219
|
.replace(SNIPPET_NOISE, " ")
|
|
219
220
|
.replace(/^\s*(#{1,6}\s*|\[\s*x?\s*\]\s*|-\s*\[\s*x?\s*\]\s*|>\s*)/gm, " ")
|
|
220
221
|
.replace(/\s+/g, " ")
|
|
221
222
|
.trim()
|
|
222
|
-
.slice(0,
|
|
223
|
+
.slice(0, max);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 各引擎的摘要展示上限:豆包搜索返回的是千字级 Summary(实测 1300-1800 字),放宽到 2000;
|
|
227
|
+
// 其余引擎维持 300。该上限同时用于 provider 成功后的清洗与 runEngineTest(喂 multi_search)。
|
|
228
|
+
const SNIPPET_MAX_BY_ENGINE = { doubao: 2000 };
|
|
229
|
+
function snippetCap(engine) {
|
|
230
|
+
return SNIPPET_MAX_BY_ENGINE[engine] ?? 300;
|
|
223
231
|
}
|
|
224
232
|
|
|
225
233
|
// 提示注入防护:插件自有工具(advanced_search / platform_search / free_search_test)的
|
|
@@ -1763,6 +1771,98 @@ async function searchAliyun(query, maxResults, apiKey, timeRange, signal) {
|
|
|
1763
1771
|
|
|
1764
1772
|
return { sources: uniqueSources(sources, maxResults ?? 10), truncated: false };
|
|
1765
1773
|
}
|
|
1774
|
+
|
|
1775
|
+
// 火山引擎「联网搜索」(豆包搜索,AskEcho):
|
|
1776
|
+
// key = DOUBAO_SEARCH_API_KEY(联网搜索控制台 / Ark Coding Plan;每月 500 次免费额度)
|
|
1777
|
+
// 注意:错误也返回 HTTP 200,必须解析 ResponseMetadata.Error。
|
|
1778
|
+
// 时间过滤:OneDay | OneWeek | OneMonth | OneYear | "YYYY-MM-DD..YYYY-MM-DD"
|
|
1779
|
+
function doubaoTimeRange(timeRange) {
|
|
1780
|
+
if (!timeRange) return undefined;
|
|
1781
|
+
if (typeof timeRange.after === "string") {
|
|
1782
|
+
const end = new Date().toISOString().slice(0, 10);
|
|
1783
|
+
return `${timeRange.after}..${end}`;
|
|
1784
|
+
}
|
|
1785
|
+
const days = Number(timeRange.days);
|
|
1786
|
+
if (!Number.isFinite(days) || days <= 0) return undefined;
|
|
1787
|
+
if (days <= 1) return "OneDay";
|
|
1788
|
+
if (days <= 7) return "OneWeek";
|
|
1789
|
+
if (days <= 31) return "OneMonth";
|
|
1790
|
+
return "OneYear";
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
const DOUBAO_ERROR_HINTS = {
|
|
1794
|
+
10400: "invalid request parameters (check Query/Count/TimeRange)",
|
|
1795
|
+
10402: "invalid SearchType (only web|image)",
|
|
1796
|
+
10403: "account or permission problem - make sure the key is from the Web Search console",
|
|
1797
|
+
10406: "free quota exhausted (500 requests/month on the free tier) - check your plan",
|
|
1798
|
+
10407: "no available free-tier policy - check the account status",
|
|
1799
|
+
10500: "Web Search service internal error - retry later",
|
|
1800
|
+
700429: "free-tier rate limit hit - slow down and retry",
|
|
1801
|
+
100013: "sub-account is not granted TorchlightApiFullAccess",
|
|
1802
|
+
700901: "invalid api key",
|
|
1803
|
+
};
|
|
1804
|
+
|
|
1805
|
+
async function searchDoubao(query, maxResults, apiKey, timeRange, signal) {
|
|
1806
|
+
if (!apiKey) throw new Error("Doubao search requires DOUBAO_SEARCH_API_KEY");
|
|
1807
|
+
const count = Math.min(Math.max(maxResults ?? 10, 1), 50);
|
|
1808
|
+
const body = {
|
|
1809
|
+
Query: String(query).slice(0, 100),
|
|
1810
|
+
SearchType: "web",
|
|
1811
|
+
Count: count,
|
|
1812
|
+
NeedSummary: true,
|
|
1813
|
+
Filter: { NeedUrl: true },
|
|
1814
|
+
};
|
|
1815
|
+
const range = doubaoTimeRange(timeRange);
|
|
1816
|
+
if (range !== undefined) body.TimeRange = range;
|
|
1817
|
+
|
|
1818
|
+
const response = await fetch(DOUBAO_SEARCH_URL, {
|
|
1819
|
+
method: "POST",
|
|
1820
|
+
redirect: "error",
|
|
1821
|
+
headers: {
|
|
1822
|
+
"content-type": "application/json",
|
|
1823
|
+
authorization: `Bearer ${apiKey}`,
|
|
1824
|
+
"X-Traffic-Tag": "dsh-free-search",
|
|
1825
|
+
"user-agent": "deepseek-harness/free-search",
|
|
1826
|
+
},
|
|
1827
|
+
body: JSON.stringify(body),
|
|
1828
|
+
signal: AbortSignal.any([...(signal !== undefined ? [signal] : []), AbortSignal.timeout(20000)]),
|
|
1829
|
+
});
|
|
1830
|
+
if (!response.ok) {
|
|
1831
|
+
const detail = await response.text().catch(() => "");
|
|
1832
|
+
if (response.status === 401 || response.status === 403) {
|
|
1833
|
+
throw new Error(
|
|
1834
|
+
`Doubao search auth failed (HTTP ${response.status}) - check DOUBAO_SEARCH_API_KEY (Web Search console)`
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
throw new Error(`Doubao search API error (HTTP ${response.status}): ${detail.slice(0, 200)}`);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
const data = await response.json();
|
|
1841
|
+
// HTTP 200 也可能携带错误信封
|
|
1842
|
+
const err = data?.ResponseMetadata?.Error;
|
|
1843
|
+
if (err && (err.Code || err.CodeN || err.Message)) {
|
|
1844
|
+
const code = err.CodeN ?? err.Code;
|
|
1845
|
+
const hint = DOUBAO_ERROR_HINTS[Number(code)] ?? DOUBAO_ERROR_HINTS[String(code)];
|
|
1846
|
+
throw new Error(
|
|
1847
|
+
`Doubao search API error (${code ?? "unknown"}): ${err.Message ?? "request failed"}${hint ? ` - ${hint}` : ""}`
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
const hits = data?.Result?.WebResults ?? [];
|
|
1852
|
+
const sources = hits
|
|
1853
|
+
.filter((item) => item && item.Url)
|
|
1854
|
+
.map((item) => {
|
|
1855
|
+
const raw = item.Summary || item.Content || item.Snippet || "";
|
|
1856
|
+
const snippet = stripTags(String(raw)).replace(/\s+/g, " ").trim();
|
|
1857
|
+
return {
|
|
1858
|
+
url: String(item.Url),
|
|
1859
|
+
...(item.Title ? { title: stripTags(String(item.Title)).trim() } : {}),
|
|
1860
|
+
...(snippet ? { snippet: snippet.slice(0, snippetCap("doubao")) } : {}),
|
|
1861
|
+
...(item.PublishTime ? { publishedAt: String(item.PublishTime) } : {}),
|
|
1862
|
+
};
|
|
1863
|
+
});
|
|
1864
|
+
return { sources: uniqueSources(sources, maxResults ?? 10), truncated: false };
|
|
1865
|
+
}
|
|
1766
1866
|
//#endregion
|
|
1767
1867
|
|
|
1768
1868
|
//#region bridge
|
|
@@ -2152,6 +2252,7 @@ const KEY_REF_MAP = {
|
|
|
2152
2252
|
baiduApiKey: "BAIDU_API_KEY",
|
|
2153
2253
|
kimiApiKey: "MOONSHOT_API_KEY",
|
|
2154
2254
|
aliyunApiKey: "DASHSCOPE_API_KEY",
|
|
2255
|
+
doubaoApiKey: "DOUBAO_SEARCH_API_KEY",
|
|
2155
2256
|
};
|
|
2156
2257
|
|
|
2157
2258
|
// rc.1 declares editable fields `.volatile()`: the Loader hands `apply` live
|
|
@@ -2182,6 +2283,7 @@ const Config = z.object({
|
|
|
2182
2283
|
baiduApiKey: z.string().role("secret").volatile(),
|
|
2183
2284
|
kimiApiKey: z.string().role("secret").volatile(),
|
|
2184
2285
|
aliyunApiKey: z.string().role("secret").volatile(),
|
|
2286
|
+
doubaoApiKey: z.string().role("secret").volatile(),
|
|
2185
2287
|
// 旧版 settings.yaml `free-search:` 段的一次性迁移标记(见 readLegacyFreeSearchSection)
|
|
2186
2288
|
legacyYamlMigrated: z.boolean().default(false).volatile(),
|
|
2187
2289
|
});
|
|
@@ -2215,6 +2317,7 @@ const LEGACY_MIGRATABLE_KEYS = [
|
|
|
2215
2317
|
"baiduApiKey",
|
|
2216
2318
|
"kimiApiKey",
|
|
2217
2319
|
"aliyunApiKey",
|
|
2320
|
+
"doubaoApiKey",
|
|
2218
2321
|
];
|
|
2219
2322
|
|
|
2220
2323
|
function dshHomeDir() {
|
|
@@ -2337,6 +2440,12 @@ function apply(ctx, config) {
|
|
|
2337
2440
|
if (settingsKey && cfg[settingsKey]) return cfg[settingsKey];
|
|
2338
2441
|
return process.env[envName] ?? "";
|
|
2339
2442
|
};
|
|
2443
|
+
// 豆包搜索:主 key 名 DOUBAO_SEARCH_API_KEY;官方示例也用 WEB_SEARCH_API_KEY,作为回退
|
|
2444
|
+
const resolveDoubaoKey = async () => {
|
|
2445
|
+
const primary = await resolveApiKey("DOUBAO_SEARCH_API_KEY", "doubaoApiKey");
|
|
2446
|
+
if (primary) return primary;
|
|
2447
|
+
return resolveApiKey("WEB_SEARCH_API_KEY", undefined);
|
|
2448
|
+
};
|
|
2340
2449
|
|
|
2341
2450
|
// 总控 provider:按 settings 的 provider 字段路由到任意引擎。
|
|
2342
2451
|
// 任何引擎失败(缺 key / 401 / 限流 / 网络)都会自动轮流尝试下一个引擎,
|
|
@@ -2391,7 +2500,7 @@ function apply(ctx, config) {
|
|
|
2391
2500
|
chain = routeEngines(request.query, { timeRange: Boolean(timeRange) });
|
|
2392
2501
|
} else {
|
|
2393
2502
|
// 统一引擎链:首选优先,然后其他付费引擎(有 key 的优先尝试),最后免费引擎
|
|
2394
|
-
const paidEngines = ["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun"];
|
|
2503
|
+
const paidEngines = ["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao"];
|
|
2395
2504
|
const freeEngines = ["bing", "anysearch", "ddg", "ddg-lite", "searxng"];
|
|
2396
2505
|
const timeEngines = TIME_ENGINES;
|
|
2397
2506
|
if (timeRange) {
|
|
@@ -2531,6 +2640,15 @@ function apply(ctx, config) {
|
|
|
2531
2640
|
continue;
|
|
2532
2641
|
}
|
|
2533
2642
|
result = await searchAliyun(request.query, request.maxResults, key, timeRange, effSignal);
|
|
2643
|
+
} else if (engine === "doubao") {
|
|
2644
|
+
const key = await resolveDoubaoKey();
|
|
2645
|
+
if (!key) {
|
|
2646
|
+
lastError = new Error("Doubao requires DOUBAO_SEARCH_API_KEY");
|
|
2647
|
+
if (engine === preferred) preferredFailure = "DOUBAO_SEARCH_API_KEY is not configured";
|
|
2648
|
+
logger.warn(`free-search: engine "${engine}" skipped (no key), trying next engine`);
|
|
2649
|
+
continue;
|
|
2650
|
+
}
|
|
2651
|
+
result = await searchDoubao(request.query, request.maxResults, key, timeRange, effSignal);
|
|
2534
2652
|
} else {
|
|
2535
2653
|
continue;
|
|
2536
2654
|
}
|
|
@@ -2539,7 +2657,7 @@ function apply(ctx, config) {
|
|
|
2539
2657
|
usedEngine = engine;
|
|
2540
2658
|
// 统一清洗 snippet:去登录/付费墙/订阅噪音,折叠空白(有值的才处理,保持 lossless JSON)
|
|
2541
2659
|
result.sources = result.sources.map((s) =>
|
|
2542
|
-
s.snippet ? { ...s, snippet: cleanSnippet(s.snippet) } : s
|
|
2660
|
+
s.snippet ? { ...s, snippet: cleanSnippet(s.snippet, snippetCap(engine)) } : s
|
|
2543
2661
|
);
|
|
2544
2662
|
// 用了非首选引擎时,在结果里附上准确提示(区分"不支持时间过滤被跳过"与"真实失败")。
|
|
2545
2663
|
// provider=auto 时 preferred 不是引擎而是路由模式:命中的引擎由 provider 字段体现,
|
|
@@ -2717,6 +2835,11 @@ function apply(ctx, config) {
|
|
|
2717
2835
|
if (!key) return { ok: false, error: "DASHSCOPE_API_KEY not configured" };
|
|
2718
2836
|
return await searchAliyun(q, n, key, tr);
|
|
2719
2837
|
}
|
|
2838
|
+
case "doubao": {
|
|
2839
|
+
const key = await resolveDoubaoKey();
|
|
2840
|
+
if (!key) return { ok: false, error: "DOUBAO_SEARCH_API_KEY not configured" };
|
|
2841
|
+
return await searchDoubao(q, n, key, tr);
|
|
2842
|
+
}
|
|
2720
2843
|
case "you": {
|
|
2721
2844
|
const key = await resolveApiKey("YOUCOM_API_KEY", "youcomApiKey");
|
|
2722
2845
|
if (!key) return { ok: false, error: "YOUCOM_API_KEY not configured" };
|
|
@@ -2738,7 +2861,7 @@ function apply(ctx, config) {
|
|
|
2738
2861
|
return {
|
|
2739
2862
|
ok: true,
|
|
2740
2863
|
sources: (result.sources ?? []).map((s) =>
|
|
2741
|
-
s.snippet ? { ...s, snippet: cleanSnippet(s.snippet) } : s
|
|
2864
|
+
s.snippet ? { ...s, snippet: cleanSnippet(s.snippet, snippetCap(engine)) } : s
|
|
2742
2865
|
),
|
|
2743
2866
|
truncated: result.truncated ?? false,
|
|
2744
2867
|
...(typeof result.content === "string" && result.content ? { content: result.content } : {}),
|
|
@@ -2758,7 +2881,7 @@ function apply(ctx, config) {
|
|
|
2758
2881
|
parameters: {
|
|
2759
2882
|
engines: {
|
|
2760
2883
|
type: "array",
|
|
2761
|
-
description: "Which engines to test (default: all). Options: ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, deepseek-official, you, baidu, kimi, aliyun.",
|
|
2884
|
+
description: "Which engines to test (default: all). Options: ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, deepseek-official, you, baidu, kimi, aliyun, doubao.",
|
|
2762
2885
|
items: { type: "string" },
|
|
2763
2886
|
},
|
|
2764
2887
|
query: {
|
|
@@ -2879,7 +3002,7 @@ function apply(ctx, config) {
|
|
|
2879
3002
|
},
|
|
2880
3003
|
},
|
|
2881
3004
|
render(args, value) {
|
|
2882
|
-
const lines = value.sources.map((s, i) => `- [${s.title ?? s.url}](${s.url})${s.snippet ? ` - ${s.snippet
|
|
3005
|
+
const lines = value.sources.map((s, i) => `- [${s.title ?? s.url}](${s.url})${s.snippet ? ` - ${s.snippet}` : ""}`);
|
|
2883
3006
|
return [{ type: "text", text: `Platform search (${value.platform}):\n${wrapUntrustedBlock(lines.join("\n") || "No results found.")}` }];
|
|
2884
3007
|
},
|
|
2885
3008
|
},
|
|
@@ -2944,7 +3067,7 @@ function apply(ctx, config) {
|
|
|
2944
3067
|
},
|
|
2945
3068
|
engine: {
|
|
2946
3069
|
type: "string",
|
|
2947
|
-
description: "Optional specific engine to try first: ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, deepseek-official, you, baidu, kimi, aliyun.",
|
|
3070
|
+
description: "Optional specific engine to try first: ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, deepseek-official, you, baidu, kimi, aliyun, doubao.",
|
|
2948
3071
|
},
|
|
2949
3072
|
},
|
|
2950
3073
|
output: {
|
|
@@ -2970,7 +3093,7 @@ function apply(ctx, config) {
|
|
|
2970
3093
|
},
|
|
2971
3094
|
},
|
|
2972
3095
|
render(args, value) {
|
|
2973
|
-
const lines = value.sources.map((s, i) => `- [${s.title ?? s.url}](${s.url})${s.snippet ? ` - ${s.snippet
|
|
3096
|
+
const lines = value.sources.map((s, i) => `- [${s.title ?? s.url}](${s.url})${s.snippet ? ` - ${s.snippet}` : ""}${s.publishedAt ? ` (${s.publishedAt})` : ""}`);
|
|
2974
3097
|
const body = `${lines.join("\n") || "No results found."}${value.content ? `\n\n${value.content}` : ""}`;
|
|
2975
3098
|
return [{ type: "text", text: `Search (${value.provider}${args.timeRange ? `, timeRange=${args.timeRange}` : ""}):\n${wrapUntrustedBlock(body)}` }];
|
|
2976
3099
|
},
|
|
@@ -3066,7 +3189,7 @@ function apply(ctx, config) {
|
|
|
3066
3189
|
render(args, value) {
|
|
3067
3190
|
const lines = value.sources.map((s) => {
|
|
3068
3191
|
const seenStr = Array.isArray(s.seenIn) && s.seenIn.length > 0 ? ` [seen in: ${s.seenIn.join(", ")}]` : "";
|
|
3069
|
-
return `- [${s.title ?? s.url}](${s.url})${seenStr}${s.snippet ? ` - ${s.snippet
|
|
3192
|
+
return `- [${s.title ?? s.url}](${s.url})${seenStr}${s.snippet ? ` - ${s.snippet}` : ""}${s.publishedAt ? ` (${s.publishedAt})` : ""}`;
|
|
3070
3193
|
});
|
|
3071
3194
|
const body = `${lines.join("\n") || "No results found."}${value.content ? `\n\n${value.content}` : ""}`;
|
|
3072
3195
|
return [{ type: "text", text: `Multi-Search (${value.provider}):\n${wrapUntrustedBlock(body)}` }];
|
|
@@ -3229,6 +3352,7 @@ function apply(ctx, config) {
|
|
|
3229
3352
|
"- baidu - paid, requires BAIDU_API_KEY",
|
|
3230
3353
|
"- kimi - paid, requires MOONSHOT_API_KEY",
|
|
3231
3354
|
"- aliyun - paid, requires DASHSCOPE_API_KEY",
|
|
3355
|
+
"- doubao - requires DOUBAO_SEARCH_API_KEY (Volcano Web Search / 豆包搜索; 500 free queries/month)",
|
|
3232
3356
|
"",
|
|
3233
3357
|
"IMPORTANT: If the configured engine fails (missing key, invalid key, 401, rate limit, or network error), web_search automatically tries other engines in this order: (1) the configured engine first (or smart route sequence if provider is auto), (2) then other engines with API keys configured (exa/tavily/keenable/firecrawl/parallel work keyless too, so they are tried even without a key), (3) then the remaining free engines (Bing, AnySearch, DuckDuckGo, SearXNG). This applies to ALL engines - paid or free. The results include a note showing which engine was actually used and why the preferred one was skipped. Understand the two note forms: (a) 'Note: X does not support time filtering (timeRange=...), using Y.' means X cannot filter by time so it was skipped BEFORE any attempt (X did NOT fail); (b) 'Note: X unavailable or failed (reason), using Y.' means X was actually tried but failed (missing key / invalid key / 401 / rate limit / network / 0 results). Never tell the user search is unavailable - it always falls back.",
|
|
3234
3358
|
"",
|
|
@@ -3284,6 +3408,7 @@ export {
|
|
|
3284
3408
|
routeEngines,
|
|
3285
3409
|
searchAnysearch,
|
|
3286
3410
|
searchAliyun,
|
|
3411
|
+
searchDoubao,
|
|
3287
3412
|
searchBaidu,
|
|
3288
3413
|
searchKimi,
|
|
3289
3414
|
searchBing,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-free-search",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Free web search for DeepSeek Harness:
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Free web search for DeepSeek Harness: 18 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Perplexity/SerpBase/DeepSeek/You.com/Baidu/Kimi/Aliyun/Doubao with key) + auto smart routing + multi_search cross-source tool + time filtering + platform search + web_fetch, with web settings UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -36,7 +36,10 @@
|
|
|
36
36
|
"aliyun",
|
|
37
37
|
"youcom",
|
|
38
38
|
"multi-search",
|
|
39
|
-
"auto-routing"
|
|
39
|
+
"auto-routing",
|
|
40
|
+
"doubao",
|
|
41
|
+
"volcengine",
|
|
42
|
+
"volcano"
|
|
40
43
|
],
|
|
41
44
|
"license": "MIT",
|
|
42
45
|
"publishConfig": {
|