dsh-free-search 0.4.36 → 0.4.37
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 +44 -46
- package/lib/client.js +11 -20
- package/lib/index.js +49 -52
- package/package.json +43 -20
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
30
30
|
|
|
31
31
|
- **零成本** —— 多个免费引擎,无需 key、无需注册
|
|
32
32
|
- **多引擎可选**:DuckDuckGo(html/lite)、Bing、SearXNG(元搜索,支持自定义实例)、AnySearch、Exa、Tavily、Keenable、Firecrawl、Parallel、Perplexity、SerpBase、DeepSeek 官方
|
|
33
|
-
- **网页设置页** —— 引擎切换 + API key 配置(UI 中 key 脱敏显示"已配置")+
|
|
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 会明确报错)
|
|
36
36
|
- **统一引擎回退** —— 任何引擎失败(付费/免费,缺 key/401/限流/网络)自动轮流尝试下一个引擎:首选引擎 → 其他引擎(exa/tavily/keenable/firecrawl/parallel 无 key 也会尝试,因为它们自带 keyless 免费额度)→ 剩余免费引擎,搜索永不直接失败;结果顶部注明实际生效的引擎(如 `Note: perplexity unavailable or failed, using exa.`)
|
|
@@ -114,10 +114,9 @@ dsh web
|
|
|
114
114
|
|
|
115
115
|
#### 网页设置(推荐)
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
安装后打开配置页(DSH 0.1.7-rc.1+):
|
|
118
118
|
|
|
119
|
-
-
|
|
120
|
-
- **DSH 0.1.5 及更早**:**设置 → 插件 → 可配置** 标签页 → **Free Search** 卡片
|
|
119
|
+
- 左侧 **插件** 页 → **已安装** 分组 → `free-search` → 点击组件行 `web-search-free`(行内"配置"入口)
|
|
121
120
|
|
|
122
121
|
配置页提供:
|
|
123
122
|
|
|
@@ -156,24 +155,24 @@ dsh web
|
|
|
156
155
|
|
|
157
156
|
#### 配置文件
|
|
158
157
|
|
|
159
|
-
|
|
158
|
+
DSH 0.1.7-rc.1 起,配置跟随 profile 的插件条目保存:设置页与 `/free-search-engine` 都会写入当前 profile 的 `cordis.patch.yml` 中 `web-search-free`(`dsh-free-search`)条目的 `config`。旧版 `~/.dsh/settings.yaml` 的 `free-search:` 段只会在启动时自动导入一次,随后原文件被重命名为 `settings.yaml.imported`。
|
|
160
159
|
|
|
161
160
|
```yaml
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
161
|
+
# profiles/<profile>/cordis.patch.yml 中该条目的 config:
|
|
162
|
+
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official
|
|
163
|
+
lang: zh # 设置页界面语言(zh / en)
|
|
164
|
+
bingMarket: zh-CN # Bing 市场
|
|
165
|
+
region: cn-zh # DuckDuckGo 区域(可选)
|
|
166
|
+
searxngInstances: # 自定义 SearXNG 实例(可选)
|
|
167
|
+
- https://your-instance.example
|
|
168
|
+
exaApiKey: ... # 或通过设置页填写
|
|
169
|
+
tavilyApiKey: ... # 或通过设置页填写
|
|
170
|
+
keenableApiKey: ... # 或通过设置页填写
|
|
171
|
+
firecrawlApiKey: ... # 或通过设置页填写
|
|
172
|
+
parallelApiKey: ... # 或通过设置页填写
|
|
173
|
+
perplexityApiKey: ...
|
|
174
|
+
serpbaseApiKey: ... # 或通过设置页填写
|
|
175
|
+
deepseekApiKey: ...
|
|
177
176
|
```
|
|
178
177
|
|
|
179
178
|
#### 让 agent 测试所有引擎
|
|
@@ -254,7 +253,7 @@ Search engine test:
|
|
|
254
253
|
|
|
255
254
|
切换后重启 `dsh web` 生效。
|
|
256
255
|
|
|
257
|
-
>
|
|
256
|
+
> 配置卡片挂在左侧「插件」页的 `plugins.row.config` 行配置插槽(dsh 自带),配置读写走插件自建 bridge,**不依赖 dsh-web-ui**,插件可独立使用。
|
|
258
257
|
|
|
259
258
|
### 代理说明(国内用户)
|
|
260
259
|
|
|
@@ -270,8 +269,8 @@ Windows 用户:桌面快捷方式已内置此配置(`set NODE_USE_ENV_PROXY=
|
|
|
270
269
|
|
|
271
270
|
### 工作原理
|
|
272
271
|
|
|
273
|
-
- `lib/index.js`:host 端。实现 `WebSearchProvider`(`id` / `available()` / `search()`),统一引擎路由 + 自动回退(付费引擎优先,免费兜底);解析 `timeRange
|
|
274
|
-
- `lib/client.js`:浏览器端。React 配置卡片(引擎选择 + key 输入 + 连通测试 +
|
|
272
|
+
- `lib/index.js`:host 端。实现 `WebSearchProvider`(`id` / `available()` / `search()`),统一引擎路由 + 自动回退(付费引擎优先,免费兜底);解析 `timeRange`(固定档/相对值/绝对日期)并透传给各引擎;在 `web-search-free` 条目上声明可编辑配置(`.volatile()`)并自带设置页(`plugins.row.config`);提供 `/api/dsh-free-search-settings` 读写桥 + `raw-search` 调试接口;注册 `free_search_test`、`platform_search`、`advanced_search` 工具;动态注入引擎清单到系统提示词(设置变更时自动刷新)。
|
|
273
|
+
- `lib/client.js`:浏览器端。React 配置卡片(引擎选择 + key 输入 + 连通测试 + 中英切换),挂载到左侧「插件」页的 `plugins.row.config` 行配置插槽;注册 `/free-search-engine` 弹出式切换命令(`commandUi` popupSelect,与 `/model` 同机制)。
|
|
275
274
|
- `cordis.patch.yml`:插件 loader 配置。
|
|
276
275
|
|
|
277
276
|
---
|
|
@@ -300,7 +299,7 @@ This plugin provides multiple free search engines with automatic fallback, compl
|
|
|
300
299
|
|
|
301
300
|
- **Zero Cost** — Multiple free engines with no API key or registration required
|
|
302
301
|
- **Multi-Engine Support** — DuckDuckGo (HTML / Lite), Bing, AnySearch AI, SearXNG (meta-search with custom instances), Exa, Tavily, Keenable, Firecrawl, Parallel, Perplexity, SerpBase, and DeepSeek Official
|
|
303
|
-
- **Web Settings UI** — Engine switching, API key configuration (keys masked as "configured" in the UI), and a Chinese/English toggle;
|
|
302
|
+
- **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+)
|
|
304
303
|
- **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)
|
|
305
304
|
- **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)
|
|
306
305
|
- **Unified Engine Fallback** — Any engine failure (paid or free, missing key, 401, rate limit, 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.
|
|
@@ -384,10 +383,9 @@ This plugin intentionally specifies `@deepseek-ai/dsh-settings` and `@deepseek-a
|
|
|
384
383
|
|
|
385
384
|
#### Web Settings (Recommended)
|
|
386
385
|
|
|
387
|
-
After installation, open the config page
|
|
386
|
+
After installation, open the config page (DSH 0.1.7-rc.1+):
|
|
388
387
|
|
|
389
|
-
-
|
|
390
|
-
- **DSH 0.1.5 and earlier**: **Settings → Plugins → Configurable** tab → **Free Search** card
|
|
388
|
+
- Sidebar **Plugins** page → **Installed** group → `free-search` → click the `web-search-free` component row (the row's "configure" entry)
|
|
391
389
|
|
|
392
390
|
The config page provides:
|
|
393
391
|
|
|
@@ -426,24 +424,24 @@ The command only changes the preferred engine; search still goes through `web_se
|
|
|
426
424
|
|
|
427
425
|
#### Configuration File
|
|
428
426
|
|
|
429
|
-
|
|
427
|
+
Since DSH 0.1.7-rc.1 the configuration is stored with the profile's plugin entry: the settings page and `/free-search-engine` both write the `config` of the `web-search-free` (`dsh-free-search`) entry in the active profile's `cordis.patch.yml`. The old `free-search:` section of `~/.dsh/settings.yaml` is imported once at startup; the file is then renamed to `settings.yaml.imported`.
|
|
430
428
|
|
|
431
429
|
```yaml
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
430
|
+
# config of that entry in profiles/<profile>/cordis.patch.yml:
|
|
431
|
+
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official
|
|
432
|
+
lang: zh # settings UI language (zh / en)
|
|
433
|
+
bingMarket: zh-CN # Bing market
|
|
434
|
+
region: cn-zh # DuckDuckGo region (optional)
|
|
435
|
+
searxngInstances: # Custom SearXNG instances (optional)
|
|
436
|
+
- https://your-instance.example
|
|
437
|
+
exaApiKey: ... # Or configure via the web settings UI
|
|
438
|
+
tavilyApiKey: ... # Or configure via the web settings UI
|
|
439
|
+
keenableApiKey: ... # Or configure via the web settings UI
|
|
440
|
+
firecrawlApiKey: ... # Or configure via the web settings UI
|
|
441
|
+
parallelApiKey: ... # Or configure via the web settings UI
|
|
442
|
+
perplexityApiKey: ...
|
|
443
|
+
serpbaseApiKey: ... # Or configure via the web settings UI
|
|
444
|
+
deepseekApiKey: ...
|
|
447
445
|
```
|
|
448
446
|
|
|
449
447
|
#### Asking the Agent to Test All Engines
|
|
@@ -524,7 +522,7 @@ The `tools/` directory includes a lightweight, zero-dependency switcher:
|
|
|
524
522
|
|
|
525
523
|
Restart `dsh web` after switching to apply changes.
|
|
526
524
|
|
|
527
|
-
> The settings card mounts into the official `
|
|
525
|
+
> The settings card mounts into the official `plugins.row.config` component-row slot on the sidebar Plugins page (built into DSH), and configuration reads/writes go through the plugin's own bridge. **No `dsh-web-ui` dependency — the plugin can be used standalone.**
|
|
528
526
|
|
|
529
527
|
### Proxy Note (for Users in Mainland China)
|
|
530
528
|
|
|
@@ -540,8 +538,8 @@ Windows users: The desktop shortcut already includes this configuration (`set NO
|
|
|
540
538
|
|
|
541
539
|
### How It Works
|
|
542
540
|
|
|
543
|
-
- `lib/index.js`: Host side. Implements `WebSearchProvider` (`id` / `available()` / `search()`), unified engine routing + auto-fallback (paid engines first, free as fallback); parses `timeRange` (fixed tiers / relative values / absolute dates) and forwards it to each engine;
|
|
544
|
-
- `lib/client.js`: Browser side. React configuration card (engine select, key inputs, connectivity test, and Chinese/English toggle), mounted into the official `
|
|
541
|
+
- `lib/index.js`: Host side. Implements `WebSearchProvider` (`id` / `available()` / `search()`), unified engine routing + auto-fallback (paid engines first, free as fallback); parses `timeRange` (fixed tiers / relative values / absolute dates) and forwards it to each engine; declares its editable config as volatile fields on the `web-search-free` composition entry and ships its own settings page (`plugins.row.config`); provides the `/api/dsh-free-search-settings` read/write bridge + `raw-search` debug endpoint; registers the `free_search_test`, `platform_search`, and `advanced_search` tools; dynamically injects the engine list into system prompts (auto-refreshes on settings change).
|
|
542
|
+
- `lib/client.js`: Browser side. React configuration card (engine select, key inputs, connectivity test, and Chinese/English toggle), mounted into the official `plugins.row.config` component-row slot on the sidebar Plugins page; registers the `/free-search-engine` popup switch command (`commandUi` popupSelect, the same mechanism as `/model`).
|
|
545
543
|
- `cordis.patch.yml`: Plugin loader configuration.
|
|
546
544
|
|
|
547
545
|
### License
|
package/lib/client.js
CHANGED
|
@@ -83,7 +83,10 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
//#endregion
|
|
84
84
|
|
|
85
85
|
const BRIDGE_PREFIX = "/api/dsh-free-search-settings";
|
|
86
|
-
|
|
86
|
+
// rc.1: the settings namespace is the profile composition entry id (the
|
|
87
|
+
// `web-search-free` row declared by cordis.patch.yml), not the old `free-search`
|
|
88
|
+
// section name. Keep in sync with FREE_SEARCH_NS in lib/index.js.
|
|
89
|
+
const NS = "web-search-free";
|
|
87
90
|
const I18N = {
|
|
88
91
|
zh: {
|
|
89
92
|
description: "免费搜索 —— 无需 API key(Bing / DuckDuckGo / AnySearch / Exa / Tavily / Keenable / Firecrawl / Parallel)",
|
|
@@ -106,7 +109,7 @@ window.__ModuleLoader__.load({
|
|
|
106
109
|
keyStorageCred: "凭据中心(推荐)",
|
|
107
110
|
keyStorageSettings: "设置页(兼容)",
|
|
108
111
|
keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
|
|
109
|
-
keyStorageSettingsHint: "
|
|
112
|
+
keyStorageSettingsHint: "保存后写入当前 profile 的插件条目 config(cordis.patch.yml,优先级低于凭据中心)。",
|
|
110
113
|
platformSearch: "平台搜索(platform_search 工具)",
|
|
111
114
|
platformHint: "为 agent 的 platform_search 工具启用平台。禁用的平台会被跳过。",
|
|
112
115
|
cacheTtl: "结果缓存时长(分钟)",
|
|
@@ -173,7 +176,7 @@ window.__ModuleLoader__.load({
|
|
|
173
176
|
keyStorageCred: "Credential center (recommended)",
|
|
174
177
|
keyStorageSettings: "Settings page (legacy)",
|
|
175
178
|
keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
|
|
176
|
-
keyStorageSettingsHint: "Saved to
|
|
179
|
+
keyStorageSettingsHint: "Saved to the active profile plugin entry config (cordis.patch.yml; lower priority than the credential center).",
|
|
177
180
|
platformSearch: "Platform search (platform_search tool)",
|
|
178
181
|
platformHint: "Enable platforms for the agent's platform_search tool. Disabled platforms are skipped.",
|
|
179
182
|
cacheTtl: "Result cache TTL (minutes)",
|
|
@@ -222,7 +225,7 @@ window.__ModuleLoader__.load({
|
|
|
222
225
|
};
|
|
223
226
|
const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
|
|
224
227
|
// 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致)
|
|
225
|
-
const PLUGIN_VERSION = "0.4.
|
|
228
|
+
const PLUGIN_VERSION = "0.4.37";
|
|
226
229
|
const ENGINES = [
|
|
227
230
|
{ id: "ddg", label: "DuckDuckGo · HTML", badge: "FREE", link: "https://duckduckgo.com" },
|
|
228
231
|
{ id: "ddg-lite", label: "DuckDuckGo · Lite", badge: "FREE", link: "https://duckduckgo.com" },
|
|
@@ -466,7 +469,7 @@ window.__ModuleLoader__.load({
|
|
|
466
469
|
ops.push({ op: "set", path: ["safeSearch"], value: safeSearch });
|
|
467
470
|
ops.push({ op: "set", path: ["bingMarket"], value: bingMarket });
|
|
468
471
|
if (keyStorage !== "credentials") {
|
|
469
|
-
// settings 模式:key
|
|
472
|
+
// settings 模式:key 写入当前 profile 的插件条目 config(cordis.patch.yml)
|
|
470
473
|
for (const [field, value] of keyFields) {
|
|
471
474
|
if (value.trim()) ops.push({ op: "set", path: [field], value: value.trim() });
|
|
472
475
|
}
|
|
@@ -1070,22 +1073,10 @@ react_jsx_runtime.jsx("div", {
|
|
|
1070
1073
|
const inject = ["slots", "commandUi"];
|
|
1071
1074
|
|
|
1072
1075
|
function apply(ctx) {
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
ctx.slots.inject("settings.plugin.item", () =>
|
|
1076
|
-
ctx.slots.register(
|
|
1077
|
-
{
|
|
1078
|
-
name: "settings.plugin.item",
|
|
1079
|
-
key: "free-search",
|
|
1080
|
-
id: "dsh-free-search",
|
|
1081
|
-
order: 120,
|
|
1082
|
-
inject: () => ({}),
|
|
1083
|
-
},
|
|
1084
|
-
FreeSearchCard
|
|
1085
|
-
)
|
|
1086
|
-
);
|
|
1087
|
-
// 挂 0.1.6-alpha.2+ 的插件页 slot:侧栏 插件 → 已安装 → free-search → 行配置页。
|
|
1076
|
+
// 挂插件页 slot:侧栏 插件 → 已安装 → free-search → 行配置页。
|
|
1077
|
+
// rc.1 废弃了旧版设置页的插件配置 slot,只保留 plugins.row.config;
|
|
1088
1078
|
// key 规则见 ui-plugin-manager 的 rowConfigKey:<包名>#<patch 里声明的行 id>。
|
|
1079
|
+
// 配置读写走自建 bridge(/api/dsh-free-search-settings),不依赖 dsh-web-ui。
|
|
1089
1080
|
ctx.slots.inject("plugins.row.config", () =>
|
|
1090
1081
|
ctx.slots.register(
|
|
1091
1082
|
{
|
package/lib/index.js
CHANGED
|
@@ -46,13 +46,16 @@ const MARKET_TO_LANG = {
|
|
|
46
46
|
"ko-KR": "ko-KR,ko;q=0.9,en;q=0.8",
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
// rc.1: settings moved into the profile-owned plugin Config. The namespace is the
|
|
50
|
+
// composition entry id (the `web-search-free` row the bundle's cordis.patch.yml
|
|
51
|
+
// declares), not the old `$DSH_HOME/settings.yaml` section name.
|
|
52
|
+
const FREE_SEARCH_NS = "web-search-free";
|
|
50
53
|
const BRIDGE_PREFIX = "/api/dsh-free-search-settings";
|
|
51
54
|
const FREE_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch"];
|
|
52
55
|
const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official"];
|
|
53
56
|
|
|
54
57
|
// 当前插件版本(发布时与 package.json 同步)
|
|
55
|
-
const PLUGIN_VERSION = "0.4.
|
|
58
|
+
const PLUGIN_VERSION = "0.4.37";
|
|
56
59
|
// 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
|
|
57
60
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
|
|
58
61
|
const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
|
|
@@ -1785,29 +1788,44 @@ const KEY_REF_MAP = {
|
|
|
1785
1788
|
deepseekApiKey: "DEEPSEEK_API_KEY",
|
|
1786
1789
|
};
|
|
1787
1790
|
|
|
1791
|
+
// rc.1 declares editable fields `.volatile()`: the Loader hands `apply` live
|
|
1792
|
+
// references and commits profile edits in place without remounting. `resolveConfig`
|
|
1793
|
+
// unwraps those references so every read sees the latest accepted value.
|
|
1794
|
+
// `.volatile()` requires the scoped `@deepseek-ai/schemastery` (>= 3.18.2).
|
|
1788
1795
|
const Config = z.object({
|
|
1789
|
-
provider: z.string().default("bing"),
|
|
1790
|
-
cache: z.boolean().default(true), // 单 query 结果缓存开关(防限流/省额度)
|
|
1791
|
-
cacheTtl: z.number().default(5), // 缓存时长(分钟),0-5 可配置(使用处再 clamp)
|
|
1792
|
-
keyStorage: z.string().default("credentials"), // key 存储位置:credentials(凭据中心)| settings(设置页)
|
|
1793
|
-
lang: z.string().default("zh"),
|
|
1794
|
-
region: z.string(),
|
|
1795
|
-
bingMarket: z.string().default("zh-CN"),
|
|
1796
|
-
safeSearch: z.string().default("off"),
|
|
1797
|
-
searxngInstances: z.array(z.string()),
|
|
1798
|
-
platforms: z.array(z.string()).default(["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]),
|
|
1799
|
-
exaApiKey: z.string().role("secret"),
|
|
1800
|
-
tavilyApiKey: z.string().role("secret"),
|
|
1801
|
-
keenableApiKey: z.string().role("secret"),
|
|
1802
|
-
firecrawlApiKey: z.string().role("secret"),
|
|
1803
|
-
parallelApiKey: z.string().role("secret"),
|
|
1804
|
-
perplexityApiKey: z.string().role("secret"),
|
|
1805
|
-
serpbaseApiKey: z.string().role("secret"),
|
|
1806
|
-
deepseekApiKey: z.string().role("secret"),
|
|
1796
|
+
provider: z.string().default("bing").volatile(),
|
|
1797
|
+
cache: z.boolean().default(true).volatile(), // 单 query 结果缓存开关(防限流/省额度)
|
|
1798
|
+
cacheTtl: z.number().default(5).volatile(), // 缓存时长(分钟),0-5 可配置(使用处再 clamp)
|
|
1799
|
+
keyStorage: z.string().default("credentials").volatile(), // key 存储位置:credentials(凭据中心)| settings(设置页)
|
|
1800
|
+
lang: z.string().default("zh").volatile(),
|
|
1801
|
+
region: z.string().volatile(),
|
|
1802
|
+
bingMarket: z.string().default("zh-CN").volatile(),
|
|
1803
|
+
safeSearch: z.string().default("off").volatile(),
|
|
1804
|
+
searxngInstances: z.array(z.string()).volatile(),
|
|
1805
|
+
platforms: z.array(z.string()).default(["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]).volatile(),
|
|
1806
|
+
exaApiKey: z.string().role("secret").volatile(),
|
|
1807
|
+
tavilyApiKey: z.string().role("secret").volatile(),
|
|
1808
|
+
keenableApiKey: z.string().role("secret").volatile(),
|
|
1809
|
+
firecrawlApiKey: z.string().role("secret").volatile(),
|
|
1810
|
+
parallelApiKey: z.string().role("secret").volatile(),
|
|
1811
|
+
perplexityApiKey: z.string().role("secret").volatile(),
|
|
1812
|
+
serpbaseApiKey: z.string().role("secret").volatile(),
|
|
1813
|
+
deepseekApiKey: z.string().role("secret").volatile(),
|
|
1807
1814
|
});
|
|
1808
1815
|
|
|
1816
|
+
// Unwrap the live `Volatile<T>` references the Loader passes for `.volatile()`
|
|
1817
|
+
// fields into a plain object, once per read.
|
|
1818
|
+
function resolveConfig(config) {
|
|
1819
|
+
if (config === null || typeof config !== "object") return {};
|
|
1820
|
+
const out = {};
|
|
1821
|
+
for (const [key, value] of Object.entries(config)) {
|
|
1822
|
+
out[key] = value !== null && typeof value === "object" && typeof value.get === "function" ? value.get() : value;
|
|
1823
|
+
}
|
|
1824
|
+
return out;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1809
1827
|
function apply(ctx, config) {
|
|
1810
|
-
|
|
1828
|
+
const current = () => resolveConfig(config);
|
|
1811
1829
|
const logger = ctx.logger;
|
|
1812
1830
|
// credentials 服务可能晚于本插件挂载(跨 bundle 顺序),运行期动态获取而不是 apply 时缓存
|
|
1813
1831
|
const getCredentials = () => ctx.get("credentials");
|
|
@@ -2035,37 +2053,16 @@ function apply(ctx, config) {
|
|
|
2035
2053
|
};
|
|
2036
2054
|
|
|
2037
2055
|
ctx.inject(["settings"], (sctx) => {
|
|
2038
|
-
//
|
|
2039
|
-
//
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
},
|
|
2049
|
-
});
|
|
2050
|
-
} else {
|
|
2051
|
-
void (async () => {
|
|
2052
|
-
const legacy = await import("@deepseek-ai/dsh-settings");
|
|
2053
|
-
if (typeof legacy.installSettingsSection === "function" && legacy.settingsNamespace) {
|
|
2054
|
-
const legacyNs = legacy.settingsNamespace(FREE_SEARCH_NS);
|
|
2055
|
-
legacy.installSettingsSection(ctx, legacyNs, Config, config ?? {}, {
|
|
2056
|
-
setSource: (source) => {
|
|
2057
|
-
current = source;
|
|
2058
|
-
},
|
|
2059
|
-
onChange: () => {
|
|
2060
|
-
if (typeof refreshPrompt === "function") refreshPrompt();
|
|
2061
|
-
},
|
|
2062
|
-
});
|
|
2063
|
-
} else {
|
|
2064
|
-
sctx.logger?.warn?.("free-search: dsh-settings 无可用注册 API(installSection/installSettingsSection 均缺失)");
|
|
2065
|
-
return;
|
|
2066
|
-
}
|
|
2067
|
-
})();
|
|
2068
|
-
}
|
|
2056
|
+
// rc.1 moved plugin configuration into the profile-owned Config, so the old
|
|
2057
|
+
// namespace-registration API is gone. Editable fields are declared `.volatile()`
|
|
2058
|
+
// on `Config` (above); this only records that the plugin ships its own settings
|
|
2059
|
+
// page (`auto: false`) so the host does not also generate a schema form. Values
|
|
2060
|
+
// persist per profile in `cordis.patch.yml` under this entry (`FREE_SEARCH_NS`).
|
|
2061
|
+
sctx.effect(() => sctx.settings.configure({ auto: false }, ctx.fiber));
|
|
2062
|
+
// Re-render the dynamic system-prompt engine list whenever this entry's config changes.
|
|
2063
|
+
sctx.on("settings/document-updated", (ns) => {
|
|
2064
|
+
if (String(ns) === FREE_SEARCH_NS && typeof refreshPrompt === "function") refreshPrompt();
|
|
2065
|
+
});
|
|
2069
2066
|
});
|
|
2070
2067
|
|
|
2071
2068
|
ctx.inject(["webServer", "settings"], (sctx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-free-search",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.37",
|
|
4
4
|
"description": "Free web search for DeepSeek Harness: 13 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Perplexity/SerpBase/DeepSeek with key) + time filtering + platform search + web_fetch, with web settings UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -43,15 +43,47 @@
|
|
|
43
43
|
"node": ">=20"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
46
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@deepseek-ai/
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
49
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.7-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "^0.1.7-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.1",
|
|
53
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
|
|
54
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.1",
|
|
55
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
56
|
+
"react": "^18.2.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"@deepseek-ai/cordis": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-commands": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-renderer": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@deepseek-ai/dsh-settings": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@deepseek-ai/dsh-tools": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@deepseek-ai/schemastery": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"react": {
|
|
81
|
+
"optional": true
|
|
82
|
+
}
|
|
51
83
|
},
|
|
52
84
|
"devDependencies": {
|
|
53
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
54
|
-
"@deepseek-ai/dsh-tools": "0.1.
|
|
85
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
|
|
86
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.1"
|
|
55
87
|
},
|
|
56
88
|
"dsh": {
|
|
57
89
|
"bundle": {
|
|
@@ -59,27 +91,18 @@
|
|
|
59
91
|
},
|
|
60
92
|
"client": {
|
|
61
93
|
"inject": [
|
|
62
|
-
"@deepseek-ai/dsh-client-
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-commands",
|
|
95
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-renderer"
|
|
63
97
|
],
|
|
64
98
|
"platform": "web"
|
|
65
99
|
},
|
|
66
100
|
"engines": {
|
|
67
|
-
"dsh": ">=0.1.
|
|
101
|
+
"dsh": ">=0.1.7-rc.1"
|
|
68
102
|
},
|
|
69
103
|
"compatibility": {
|
|
70
104
|
"dshReleases": {
|
|
71
|
-
"rc.
|
|
72
|
-
"rc.8": "unknown",
|
|
73
|
-
"0.1.1-rc.1": "compatible",
|
|
74
|
-
"0.1.1-rc.2": "compatible",
|
|
75
|
-
"0.1.2-alpha.1": "unknown",
|
|
76
|
-
"0.1.2-alpha.2": "compatible",
|
|
77
|
-
"0.1.2-alpha.3": "compatible",
|
|
78
|
-
"0.1.2-alpha.4": "compatible",
|
|
79
|
-
"0.1.2-alpha.5": "unknown",
|
|
80
|
-
"0.1.2-rc.1": "compatible",
|
|
81
|
-
"0.1.5-rc.1": "compatible",
|
|
82
|
-
"0.1.5-rc.2": "compatible"
|
|
105
|
+
"0.1.7-rc.1": "compatible"
|
|
83
106
|
}
|
|
84
107
|
}
|
|
85
108
|
}
|