dsh-free-search 0.9.2 → 0.9.3
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 +4 -2
- package/lib/client.js +1 -1
- package/lib/index.js +74 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -436,7 +436,7 @@ Search engine test:
|
|
|
436
436
|
|
|
437
437
|
#### 离线自检断言(tools/)
|
|
438
438
|
|
|
439
|
-
仓库自带 **
|
|
439
|
+
仓库自带 **17 个**零依赖断言脚本(合计 **501 项**),**不需要 API key、不联网、不需要测试框架**,Node ≥ 20 即可跑:
|
|
440
440
|
|
|
441
441
|
| 脚本 | 覆盖 | 运行(项数) |
|
|
442
442
|
|---|---|---|
|
|
@@ -456,6 +456,7 @@ Search engine test:
|
|
|
456
456
|
| `assert-killtree-win32.mjs` | Windows 子进程树终止(#74):不再依赖 `taskkill` | `node tools/assert-killtree-win32.mjs`(8) |
|
|
457
457
|
| `assert-killtree-portable.mjs` | 跨平台 `killTree` 便携版契约 | `node tools/assert-killtree-portable.mjs`(5) |
|
|
458
458
|
| `assert-parallel-mcp.mjs` | Parallel MCP 引擎:25 s 超时/调用方取消覆盖**响应体读取**、共享 SSE 解析器(通知/多行帧)、6 类错误路径、REST 精确时间过滤 | `node tools/assert-parallel-mcp.mjs`(15) |
|
|
459
|
+
| `assert-provider-takeover.mjs` | provider 接管:旧属性 API 与 rc.2 的 `config.*` ref 两套兼容、`settings.mutate(web)` 单批写入、显式指定的 provider 不被抢占 | `node tools/assert-provider-takeover.mjs`(6) |
|
|
459
460
|
|
|
460
461
|
任何一个失败会以非 0 退出码结束并列出失败项;把某个旧版本文件路径作为参数传进去,可以看到修复前必红(用来证明断言不是空转)。改动发布相关逻辑后建议先跑一遍再发版。
|
|
461
462
|
|
|
@@ -912,7 +913,7 @@ Restart `dsh web` after switching to apply changes.
|
|
|
912
913
|
|
|
913
914
|
#### Offline self-check assertions (`tools/`)
|
|
914
915
|
|
|
915
|
-
**
|
|
916
|
+
**Seventeen** zero-dependency assertion scripts ship with the repo (**501 assertions** total): **no API keys, no network, no test framework**, Node ≥ 20:
|
|
916
917
|
|
|
917
918
|
| Script | Covers | Run (count) |
|
|
918
919
|
|---|---|---|
|
|
@@ -932,6 +933,7 @@ Restart `dsh web` after switching to apply changes.
|
|
|
932
933
|
| `assert-killtree-win32.mjs` | Windows process-tree termination (#74): no longer relies on `taskkill` | `node tools/assert-killtree-win32.mjs` (8) |
|
|
933
934
|
| `assert-killtree-portable.mjs` | portable `killTree` contract | `node tools/assert-killtree-portable.mjs` (5) |
|
|
934
935
|
| `assert-parallel-mcp.mjs` | Parallel MCP engine: the 25 s timeout / caller abort covers the **body read**, shared SSE parser (notifications, multiline frames), six error paths, REST precise time filtering | `node tools/assert-parallel-mcp.mjs` (15) |
|
|
936
|
+
| `assert-provider-takeover.mjs` | provider takeover: both the legacy property API and the rc.2 `config.*` refs, a single batched `settings.mutate(web)`, and explicit foreign providers left untouched | `node tools/assert-provider-takeover.mjs` (6) |
|
|
935
937
|
|
|
936
938
|
Any failure exits non-zero with the failing items listed. Passing the path of an older file shows the pre-fix failures (proving the assertions are not vacuous). Worth running before a release.
|
|
937
939
|
|
package/lib/client.js
CHANGED
|
@@ -388,7 +388,7 @@ window.__ModuleLoader__.load({
|
|
|
388
388
|
};
|
|
389
389
|
const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
|
|
390
390
|
// 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致;跟随上游 v0.7.5)
|
|
391
|
-
const PLUGIN_VERSION = "0.9.
|
|
391
|
+
const PLUGIN_VERSION = "0.9.3";
|
|
392
392
|
const ENGINES = [
|
|
393
393
|
{ id: "auto", label: "Auto · 智能路由", badge: "AUTO", link: null },
|
|
394
394
|
{ id: "multi", label: "Multi Search · 多引擎", badge: "MULTI", link: null },
|
package/lib/index.js
CHANGED
|
@@ -245,7 +245,7 @@ function routeEngines(query, { timeRange, disabledEngines = [], fallbackOrder =
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
// 当前插件版本(发布时与 package.json 同步;跟随上游 v0.7.5,不在 PR 里 bump)
|
|
248
|
-
const PLUGIN_VERSION = "0.9.
|
|
248
|
+
const PLUGIN_VERSION = "0.9.3";
|
|
249
249
|
// 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
|
|
250
250
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
|
|
251
251
|
const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
|
|
@@ -3943,6 +3943,26 @@ function apply(ctx, config) {
|
|
|
3943
3943
|
// dsh-base bundle 会在 `- id: web` 里把它设为搜索的出厂默认值。
|
|
3944
3944
|
const OFFICIAL_SEARCH_PROVIDER_ID = "deepseek-official";
|
|
3945
3945
|
|
|
3946
|
+
// provider 选择的读取与接管。DSH 0.2.1-rc.2 起,web 的 provider 选择收进了
|
|
3947
|
+
// config 的 volatile ref(config.searchProvider / config.fetchProvider),
|
|
3948
|
+
// 只能通过 settings.mutate 写;更早的版本是 searchProviderId / fetchProviderId 属性。
|
|
3949
|
+
// 两套都兼容,否则接管会写到一个没人读的属性上,搜索静默回退官方 provider。
|
|
3950
|
+
const providerSelection = (web, key) => {
|
|
3951
|
+
const ref = web.config?.[key];
|
|
3952
|
+
if (ref !== undefined && ref !== null && typeof ref.get === "function") return { ref, current: ref.get() };
|
|
3953
|
+
return { ref: null, current: web[key === "searchProvider" ? "searchProviderId" : "fetchProviderId"] };
|
|
3954
|
+
};
|
|
3955
|
+
// ref 形态(rc.2+)把写入攒进 ops;属性形态(旧版)就地写。
|
|
3956
|
+
const takeOverOps = (web, key, id, ops) => {
|
|
3957
|
+
const { ref } = providerSelection(web, key);
|
|
3958
|
+
if (ref !== null) {
|
|
3959
|
+
ops.push({ op: "set", path: [key], value: id });
|
|
3960
|
+
return;
|
|
3961
|
+
}
|
|
3962
|
+
if (key === "searchProvider") web.searchProviderId = id;
|
|
3963
|
+
else web.fetchProviderId = id;
|
|
3964
|
+
};
|
|
3965
|
+
|
|
3946
3966
|
ctx.web.registerSearchProvider(provider);
|
|
3947
3967
|
|
|
3948
3968
|
// web_fetch 质量链接管:注册 fs-quality-fetch(直连 → exa contents → firecrawl → crawl4ai),
|
|
@@ -3950,43 +3970,60 @@ function apply(ctx, config) {
|
|
|
3950
3970
|
// 与 web_search 的接管规则同构:运行时兜底,不依赖 bundle patch 是否落地。
|
|
3951
3971
|
const fetchProvider = createFetchProvider({ resolveApiKey, logger });
|
|
3952
3972
|
ctx.web.registerFetchProvider(fetchProvider);
|
|
3953
|
-
if (!ctx.web.fetchProviderId || ctx.web.fetchProviderId === "http") {
|
|
3954
|
-
const prevFetch = ctx.web.fetchProviderId;
|
|
3955
|
-
ctx.web.fetchProviderId = fetchProvider.id;
|
|
3956
|
-
logger.info(
|
|
3957
|
-
prevFetch === undefined || prevFetch === null
|
|
3958
|
-
? `free-search: web.fetchProvider was unset, taking over as "${fetchProvider.id}" (direct → exa → firecrawl → crawl4ai)`
|
|
3959
|
-
: `free-search: taking over from the default fetch provider "${prevFetch}" as "${fetchProvider.id}" (direct → exa → firecrawl → crawl4ai)`
|
|
3960
|
-
);
|
|
3961
|
-
} else if (ctx.web.fetchProviderId !== fetchProvider.id) {
|
|
3962
|
-
logger.warn(
|
|
3963
|
-
`free-search: fetch is served by provider "${ctx.web.fetchProviderId}", not "${fetchProvider.id}" — web_fetch keeps its current provider. ` +
|
|
3964
|
-
`To use this plugin's fetch chain, set this in the profile's cordis.patch.yml:\n` +
|
|
3965
|
-
`- id: web\n config:\n fetchProvider: ${fetchProvider.id}`
|
|
3966
|
-
);
|
|
3967
|
-
}
|
|
3968
3973
|
|
|
3969
|
-
//
|
|
3970
|
-
//
|
|
3971
|
-
//
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3974
|
+
// 接管必须先等 settings 服务就绪:rc.2 起 provider 选择是 config 的 volatile ref,
|
|
3975
|
+
// 只能通过 settings.mutate 写;apply 期间 settings 尚未挂载,直接取会永远拿不到。
|
|
3976
|
+
// 两处接管合并成一次 mutate —— 同一 tick 里连发两次,第二次会被设置文档的版本号顶掉。
|
|
3977
|
+
ctx.inject(["settings"], (sctx) => {
|
|
3978
|
+
const ops = [];
|
|
3979
|
+
|
|
3980
|
+
const fetchSelection = providerSelection(ctx.web, "fetchProvider");
|
|
3981
|
+
if (!fetchSelection.current || fetchSelection.current === "http") {
|
|
3982
|
+
takeOverOps(ctx.web, "fetchProvider", fetchProvider.id, ops);
|
|
3983
|
+
logger.info(
|
|
3984
|
+
fetchSelection.current === undefined || fetchSelection.current === null
|
|
3985
|
+
? `free-search: web.fetchProvider was unset, taking over as "${fetchProvider.id}" (direct → exa → firecrawl → crawl4ai)`
|
|
3986
|
+
: `free-search: taking over from the default fetch provider "${fetchSelection.current}" as "${fetchProvider.id}" (direct → exa → firecrawl → crawl4ai)`
|
|
3987
|
+
);
|
|
3988
|
+
} else if (fetchSelection.current !== fetchProvider.id) {
|
|
3989
|
+
logger.warn(
|
|
3990
|
+
`free-search: fetch is served by provider "${fetchSelection.current}", not "${fetchProvider.id}" — web_fetch keeps its current provider. ` +
|
|
3991
|
+
`To use this plugin's fetch chain, set this in the profile's cordis.patch.yml:\n` +
|
|
3992
|
+
`- id: web\n config:\n fetchProvider: ${fetchProvider.id}`
|
|
3993
|
+
);
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
// 接管规则(运行时兜底,不依赖 bundle patch 是否落地):
|
|
3997
|
+
// - 未设置 searchProvider:接管;
|
|
3998
|
+
// - 仍是出厂默认的官方搜索 deepseek-official:接管(安装本插件就是为了替掉它);
|
|
3999
|
+
// - 已被显式指向其他 provider:不抢占,只警告并给出切换用的 YAML。
|
|
4000
|
+
// 这样即使用户把插件当普通依赖手工安装(bundle patch 未生效)、或 profile patch
|
|
4001
|
+
// 整体覆盖 config 抹掉了 searchProvider,搜索也不会静默退回官方导致"余额不足"。
|
|
4002
|
+
const searchSelection = providerSelection(ctx.web, "searchProvider");
|
|
4003
|
+
if (!searchSelection.current || searchSelection.current === OFFICIAL_SEARCH_PROVIDER_ID) {
|
|
4004
|
+
takeOverOps(ctx.web, "searchProvider", provider.id, ops);
|
|
4005
|
+
logger.info(
|
|
4006
|
+
searchSelection.current === undefined || searchSelection.current === null
|
|
4007
|
+
? `free-search: web.searchProvider was unset, taking over as "${provider.id}"`
|
|
4008
|
+
: `free-search: taking over from the default provider "${searchSelection.current}" as "${provider.id}"`
|
|
4009
|
+
);
|
|
4010
|
+
} else if (searchSelection.current !== provider.id) {
|
|
4011
|
+
logger.warn(
|
|
4012
|
+
`free-search: search is served by provider "${searchSelection.current}", not "${provider.id}" — web_search will NOT use this plugin. ` +
|
|
4013
|
+
`Add this to the profile's cordis.patch.yml (patch replaces the whole entry config, so keep any other web.* fields):\n` +
|
|
4014
|
+
`- id: web\n config:\n searchProvider: ${provider.id}\n fetchProvider: http`
|
|
4015
|
+
);
|
|
4016
|
+
}
|
|
4017
|
+
|
|
4018
|
+
if (ops.length === 0) return;
|
|
4019
|
+
if (typeof sctx.settings?.mutate !== "function") {
|
|
4020
|
+
logger.warn("free-search: cannot take over the web providers — the settings service is unavailable");
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
void Promise.resolve(sctx.settings.mutate("web", ops)).catch((error) => {
|
|
4024
|
+
logger.warn(`free-search: failed to take over the web providers (${error?.message ?? error})`);
|
|
4025
|
+
});
|
|
4026
|
+
});
|
|
3990
4027
|
|
|
3991
4028
|
// 测试工具:让 agent 逐个测试所有搜索引擎,报告可用性
|
|
3992
4029
|
const runEngineTest = async (engine, query, timeRange, maxResults = 2) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-free-search",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Free web search for DeepSeek Harness: 27 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Zhihu global/site via MCP and Perplexity/SerpBase/DeepSeek (official + native tools/search)/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 + editable zh/en language pools + multi mode (parallel full language-pool merge) + multi_search cross-source tool + failure-aware fallback (quota/auth cooldown, transient retry, fallbackOn) + time filtering + platform search + domain blocklist (blockedDomains) + web_fetch, with web settings UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|