dsh-free-search 0.4.3 → 0.4.5
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.en.md +191 -163
- package/README.md +191 -163
- package/lib/client.js +55 -0
- package/lib/index.js +61 -17
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -1,163 +1,191 @@
|
|
|
1
|
-
# dsh-free-search
|
|
2
|
-
|
|
3
|
-
**Free web search
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
1
|
+
# dsh-free-search
|
|
2
|
+
|
|
3
|
+
**Free web search plugin for DeepSeek Harness — no API key required, zero cost, multi-engine switcher.** A plugin that adds multi-engine search providers to DeepSeek Harness (dsh), registered into the `ctx.web` seam. The built-in `web_search` tool picks it up automatically. Supports switching engines via the web settings UI, configuring API keys, and one-click testing of all search engines.
|
|
4
|
+
|
|
5
|
+
English | [中文](./README.md)
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
<a href="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-free1.png">
|
|
9
|
+
<img src="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-free1.png" alt="Free Engine Settings (Bing)" width="820" />
|
|
10
|
+
</a>
|
|
11
|
+
<br>
|
|
12
|
+
<sub>▲ Free engine (using Bing as an example)</sub>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## Why You Need It
|
|
16
|
+
|
|
17
|
+
dsh's default search provider relies on the official DeepSeek API key (`DEEPSEEK_API_KEY`). If you:
|
|
18
|
+
- Do not have (or prefer not to use) an official DeepSeek key,
|
|
19
|
+
- Use a gateway like opencode-go (whose OpenAI-compatible endpoint does not support the `web_search` tool),
|
|
20
|
+
|
|
21
|
+
...then the built-in search will inevitably fail, and the agent will tell you "I cannot access the internet."
|
|
22
|
+
|
|
23
|
+
This plugin provides multiple free search engines with automatic fallback, completely freeing you from relying on DeepSeek's official key.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **Zero Cost** — Multiple free engines with no API key or registration required
|
|
28
|
+
- **Multi-Engine Support** — DuckDuckGo (HTML / Lite), Bing, AnySearch AI, SearXNG (meta-search with custom instances), Exa, Perplexity, and DeepSeek Official
|
|
29
|
+
- **Web Settings UI** — Engine switching and API key configuration (API keys are masked in the UI and displayed as "configured")
|
|
30
|
+
- **Engine Testing Tool** — `free_search_test`, allowing the agent to test the availability of all engines in a single call
|
|
31
|
+
- **Automatic Fallback** — Automatically falls back to the next available free engine if one fails or gets rate-limited
|
|
32
|
+
- **System Prompt Injection** — The agent is aware of the currently active engine and which engines require API keys
|
|
33
|
+
- **Visual Badges** — Free engines feature a green `FREE` badge, while paid engines show an orange `API KEY` badge in the settings UI
|
|
34
|
+
- **Webpage Fetching (`web_fetch`)** — Allows the agent to read full webpage contents (official `dsh-web-fetch-http` provider, pure JS, zero extra dependencies)
|
|
35
|
+
- **Platform Search (`platform_search`)** — Search GitHub / V2EX / Bilibili (public APIs, zero extra dependencies)
|
|
36
|
+
- **Clean Integration** — Implements the official `WebSearchProvider` seam interface, coexisting seamlessly with official plugins
|
|
37
|
+
|
|
38
|
+
## Supported Engines
|
|
39
|
+
|
|
40
|
+
| id | Engine | Cost | Description |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `ddg` | DuckDuckGo HTML | Free | Occasional rate limits (anti-bot challenges); recovers automatically |
|
|
43
|
+
| `ddg-lite` | DuckDuckGo Lite | Free | Lightweight version; same rate-limit behavior as above |
|
|
44
|
+
| `bing` | Bing | Free | **Default engine**, most stable, optimized for Chinese (`zh-CN`) |
|
|
45
|
+
| `anysearch` | AnySearch AI | Free | AI search, no key needed (anonymous quota) |
|
|
46
|
+
| `searxng` | SearXNG Meta Search | Free | Multi-instance automatic failover; supports custom instances |
|
|
47
|
+
| `exa` | Exa | Free | **Usable without a key** (anonymous MCP); configure a key for higher quota |
|
|
48
|
+
| `perplexity` | Perplexity | Paid | Requires `PERPLEXITY_API_KEY` |
|
|
49
|
+
| `deepseek-official` | DeepSeek Official | Paid | Requires `DEEPSEEK_API_KEY` |
|
|
50
|
+
|
|
51
|
+
- **Default engine is `bing`** (free and most stable), ready to use out of the box after installation.
|
|
52
|
+
- **Auto-failover**: any engine failure (rate-limited free engine, or missing/invalid paid key, network error) automatically falls back to a working free engine (Bing/AnySearch etc.) with a note attached to the results — a search never fails outright because of engine issues.
|
|
53
|
+
- **Official Links in Settings**: Free engines display "Visit Website →", while paid engines display "Get API Key →" (opens in a new tab):
|
|
54
|
+
- Exa: <https://dashboard.exa.ai/api-keys>
|
|
55
|
+
- Perplexity: <https://www.perplexity.ai/settings/api>
|
|
56
|
+
- DeepSeek: <https://platform.deepseek.com/api_keys>
|
|
57
|
+
|
|
58
|
+
### Why is AnySearch free?
|
|
59
|
+
|
|
60
|
+
AnySearch (anysearch.com) is an AI search gateway that provides **anonymous public search quota** — it can be called directly via its `v1/search` REST endpoint without registration or an API key. Quotas are rate-limited (suitable for daily queries), but as one of the free engines with mutual fallback, it offers a reliable experience.
|
|
61
|
+
|
|
62
|
+
The same applies to Exa: its public MCP endpoint (`mcp.exa.ai/mcp`) supports anonymous requests, allowing it to work without an API key. Configuring `EXA_API_KEY` grants a higher usage quota.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
git clone https://github.com/DDDMUC/dsh-free-search.git
|
|
68
|
+
dsh plugin --profile web add /path/to/dsh-free-search
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then restart:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
dsh web
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Dependency Note
|
|
78
|
+
|
|
79
|
+
This plugin intentionally specifies `@deepseek-ai/dsh-settings` and `@deepseek-ai/dsh-tools` as `peerDependencies`: the DSH runtime must use a single instance from the installation tree. Always install the plugin using `dsh plugin --profile <profile> add ...`. Do **not** copy DSH core packages into a profile-local `node_modules`, as duplicate copies can break the tool scheduler.
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
### Web Settings (Recommended)
|
|
84
|
+
|
|
85
|
+
After installation, navigate to **Settings → Plugins → Configurable** tab → **Free Search** card (the official settings page):
|
|
86
|
+
|
|
87
|
+
- **Search engine**: Select an engine from the dropdown; changes take effect immediately upon saving.
|
|
88
|
+
- **API keys**: Enter keys for Exa / Perplexity / DeepSeek (password fields; displayed as "configured" once saved).
|
|
89
|
+
- **Use Bing default**: stage a switch back to the stable free Bing engine; `Discard` only cancels unsaved edits
|
|
90
|
+
- **Platform search**: check GitHub / V2EX / Bilibili to enable them for the `platform_search` tool (disabled platforms are skipped).
|
|
91
|
+
|
|
92
|
+
<table align="center" style="border: none; border-collapse: collapse;">
|
|
93
|
+
<tr style="border: none;">
|
|
94
|
+
<td align="center" width="50%" style="border: none; padding: 6px;">
|
|
95
|
+
<a href="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-free.png">
|
|
96
|
+
<img src="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-free.png" alt="Free Engine Settings" width="100%" />
|
|
97
|
+
</a>
|
|
98
|
+
<br>
|
|
99
|
+
<sub>▲ <b>Free Engine</b> (shows green FREE badge and official website link)</sub>
|
|
100
|
+
</td>
|
|
101
|
+
<td align="center" width="50%" style="border: none; padding: 6px;">
|
|
102
|
+
<a href="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-apikey.png">
|
|
103
|
+
<img src="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-apikey.png" alt="Paid/API Key Engine Settings" width="100%" />
|
|
104
|
+
</a>
|
|
105
|
+
<br>
|
|
106
|
+
<sub>▲ <b>Paid / API Key Engine</b> (shows orange API KEY badge and link to get an API key)</sub>
|
|
107
|
+
</td>
|
|
108
|
+
</tr>
|
|
109
|
+
</table>
|
|
110
|
+
|
|
111
|
+
### Configuration File / CLI
|
|
112
|
+
|
|
113
|
+
Configuration is stored in `~/.dsh/settings.yaml`:
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
free-search:
|
|
117
|
+
provider: bing # ddg / ddg-lite / bing / searxng / exa / perplexity / deepseek-official
|
|
118
|
+
bingMarket: zh-CN # Bing market
|
|
119
|
+
region: cn-zh # DuckDuckGo region (optional)
|
|
120
|
+
searxngInstances: # Custom SearXNG instances (optional)
|
|
121
|
+
- https://your-instance.example
|
|
122
|
+
exaApiKey: ... # Or configure via the web settings UI
|
|
123
|
+
perplexityApiKey: ...
|
|
124
|
+
deepseekApiKey: ...
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Asking the Agent to Test All Engines
|
|
128
|
+
|
|
129
|
+
Tell the agent *"Test all search engines"*, and it will call the `free_search_test` tool to check each engine sequentially and report back:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Search engine test:
|
|
133
|
+
- ddg: FAIL - DuckDuckGo is rate-limited right now (anti-bot challenge, usually temporary) - Bing works
|
|
134
|
+
- bing: OK (2 results, e.g. "DeepSeek Harness developer preview...")
|
|
135
|
+
- exa: FAIL - EXA_API_KEY not configured
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Fetch Webpage Content (`web_fetch`)
|
|
139
|
+
|
|
140
|
+
After searching, the agent can **read full webpage content** (e.g., *"Open the first link and summarize it"*). The `web_fetch` tool is enabled by default (official `dsh-web-fetch-http` provider):
|
|
141
|
+
|
|
142
|
+
- Automatically follows redirects and decodes HTML to plain text.
|
|
143
|
+
- Supports timeout and response size limits.
|
|
144
|
+
- ⚠️ Note: `web_fetch` does not have SSRF protection; the agent could theoretically access internal network addresses. Use as needed.
|
|
145
|
+
|
|
146
|
+
### Platform Search (`platform_search`)
|
|
147
|
+
|
|
148
|
+
Ask the agent to search specific platforms (e.g., *"Search GitHub for deepseek harness"*, *"Find related videos on Bilibili"*, or *"Discussions about dsh on V2EX"*). The `platform_search` tool supports:
|
|
149
|
+
|
|
150
|
+
| Platform | Purpose |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `github` | GitHub repository search (public API, free, no key required) |
|
|
153
|
+
| `v2ex` | V2EX hot / relevant topics |
|
|
154
|
+
| `bilibili` | Bilibili video / content search (public API) |
|
|
155
|
+
|
|
156
|
+
All platform searches rely on public endpoints with zero external dependencies and work out of the box.
|
|
157
|
+
|
|
158
|
+
## Local Engine Switcher (`tools/`)
|
|
159
|
+
|
|
160
|
+
The `tools/` directory includes a lightweight, zero-dependency switcher:
|
|
161
|
+
|
|
162
|
+
- **`启动搜索引擎切换器.cmd`** (Windows) — Double-click to launch a local Node server (`http://127.0.0.1:4789`) and automatically open the engine selector page in your browser.
|
|
163
|
+
- **`switch-engine.html`** — The selector UI: displays current engine status and allows one-click switching.
|
|
164
|
+
- **`server.mjs`** — The local backend service responsible for reading/writing `~/.dsh/profiles/web/cordis.patch.yml`.
|
|
165
|
+
- **`switch-engine.ps1`** — Headless PowerShell script: `powershell -File tools/switch-engine.ps1 -Engine bing`.
|
|
166
|
+
|
|
167
|
+
Restart `dsh web` after switching to apply changes.
|
|
168
|
+
|
|
169
|
+
> The settings card mounts into the official `settings.plugin.item` slot (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.**
|
|
170
|
+
|
|
171
|
+
## Proxy Note (for Users in Mainland China)
|
|
172
|
+
|
|
173
|
+
Engines like DuckDuckGo may require a proxy. Since Node.js `fetch` does not use the system proxy by default, set the following environment variables for the dsh process (Node 24+):
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
export NODE_USE_ENV_PROXY=1
|
|
177
|
+
export HTTPS_PROXY=http://127.0.0.1:7897 # Your proxy address
|
|
178
|
+
export HTTP_PROXY=http://127.0.0.1:7897
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Windows users: The desktop shortcut already includes this configuration (`set NODE_USE_ENV_PROXY=1&& set HTTPS_PROXY=...`).
|
|
182
|
+
|
|
183
|
+
## How It Works
|
|
184
|
+
|
|
185
|
+
- `lib/index.js`: Host side. Implements `WebSearchProvider` (`id` / `available()` / `search()`), multi-engine routing + auto-fallback; registers the `free-search` settings namespace; provides the `/api/dsh-free-search-settings` read/write bridge; registers the `free_search_test` tool; injects the engine list into system prompts.
|
|
186
|
+
- `lib/client.js`: Browser side. React configuration card (engine select + key inputs), mounted into the official `settings.plugin.item` slot (Settings → Plugins → Configurable), without requiring `dsh-web-ui`.
|
|
187
|
+
- `cordis.patch.yml`: Plugin loader configuration.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,164 +1,192 @@
|
|
|
1
|
-
# dsh-free-search
|
|
2
|
-
|
|
3
|
-
**DeepSeek Harness 免费搜索插件 —— 无需 API key,零成本,多引擎可切换。**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
dsh web
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
###
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
# dsh-free-search
|
|
2
|
+
|
|
3
|
+
**DeepSeek Harness 免费搜索插件 —— 无需 API key,零成本,多引擎可切换。** 一个给 DeepSeek Harness (dsh) 添加多引擎搜索 provider 的插件,注册进 `ctx.web` seam。内置 `web_search` 工具自动选用,支持网页设置页切换引擎、配置 API key、一键测试所有引擎。
|
|
4
|
+
|
|
5
|
+
[English](./README.en.md) | 中文
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
<a href="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-free1.png">
|
|
9
|
+
<img src="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-free1.png" alt="免费引擎设置 (Bing)" width="820" />
|
|
10
|
+
</a>
|
|
11
|
+
<br>
|
|
12
|
+
<sub>▲ 免费引擎(以Bing为例)</sub>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## 为什么需要它
|
|
16
|
+
|
|
17
|
+
dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`)。如果你:
|
|
18
|
+
- 没有(或不想用)DeepSeek 官方 key,
|
|
19
|
+
- 用的是 opencode-go 这类网关(其 OpenAI 兼容端点不支持 `web_search` 工具),
|
|
20
|
+
|
|
21
|
+
……那么内置搜索必然失败,agent 会告诉你"无法联网"。
|
|
22
|
+
|
|
23
|
+
这个插件提供多个免费引擎 + 自动回退,彻底摆脱 DeepSeek 官方 key 的依赖。
|
|
24
|
+
|
|
25
|
+
## 特性
|
|
26
|
+
|
|
27
|
+
- **零成本** —— 多个免费引擎,无需 key、无需注册
|
|
28
|
+
- **多引擎可选**:DuckDuckGo(html/lite)、Bing、SearXNG(元搜索,支持自定义实例)、Exa、Perplexity、DeepSeek 官方
|
|
29
|
+
- **网页设置页** —— 引擎切换 + API key 配置(UI 中 key 脱敏显示"已配置")
|
|
30
|
+
- **引擎测试工具** —— `free_search_test`,让 agent 一键测试所有引擎可用性
|
|
31
|
+
- **自动回退** —— 免费引擎失败/限流时自动切换到下一个可用引擎
|
|
32
|
+
- **系统提示词注入** —— agent 知道当前用哪个引擎、哪些需要 key
|
|
33
|
+
- **免费标注** —— 设置页中免费引擎带绿色 `FREE` 徽章,付费引擎带橙色 `API KEY` 徽章
|
|
34
|
+
- **网页抓取(web_fetch)** —— 让 agent 抓取网页内容(官方 `dsh-web-fetch-http` provider,纯 JS,零额外依赖)
|
|
35
|
+
- **平台搜索(platform_search)** —— 搜 GitHub / V2EX / B站(公开 API,零依赖)
|
|
36
|
+
- **干净集成** —— 实现官方 `WebSearchProvider` seam 接口,与官方插件共存
|
|
37
|
+
|
|
38
|
+
## 引擎列表
|
|
39
|
+
|
|
40
|
+
| id | 引擎 | 费用 | 说明 |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `ddg` | DuckDuckGo HTML | 免费 | 偶发限流(反爬),解封自动恢复 |
|
|
43
|
+
| `ddg-lite` | DuckDuckGo Lite | 免费 | 轻量版,同上 |
|
|
44
|
+
| `bing` | Bing | 免费 | **默认引擎**,最稳定,中文优化(zh-CN) |
|
|
45
|
+
| `anysearch` | AnySearch AI | 免费 | AI 搜索,无 key(匿名额度) |
|
|
46
|
+
| `searxng` | SearXNG 元搜索 | 免费 | 多实例自动切换,支持自定义实例 |
|
|
47
|
+
| `exa` | Exa | 免费 | **无 key 也可用**(MCP 匿名),配 key 提升额度 |
|
|
48
|
+
| `perplexity` | Perplexity | 付费 | 需 `PERPLEXITY_API_KEY` |
|
|
49
|
+
| `deepseek-official` | DeepSeek 官方 | 付费 | 需 `DEEPSEEK_API_KEY` |
|
|
50
|
+
|
|
51
|
+
- **默认引擎为 `bing`**(免费且最稳定),安装后开箱即用。
|
|
52
|
+
- **自动回退**:任何引擎失败(免费引擎限流/反爬,付费引擎缺 key/无效/网络错误)都会自动回退到可用的免费引擎(Bing/AnySearch 等),并在结果中附带回退提示——搜索不会因引擎问题直接失败。
|
|
53
|
+
- **设置页有官网链接**:免费引擎显示"访问官网 →",付费引擎显示"获取 API Key →"(新标签页打开):
|
|
54
|
+
- Exa:<https://dashboard.exa.ai/api-keys>
|
|
55
|
+
- Perplexity:<https://www.perplexity.ai/settings/api>
|
|
56
|
+
- DeepSeek:<https://platform.deepseek.com/api_keys>
|
|
57
|
+
|
|
58
|
+
### 为什么 AnySearch 免费?
|
|
59
|
+
|
|
60
|
+
AnySearch(anysearch.com)是一个 AI 搜索网关,提供**匿名的公共搜索额度**——通过其 `v1/search` REST 接口可以直接调用,无需注册或 API key。额度有限流(适合日常搜索),但作为免费引擎之一,与其他免费引擎互相回退,体验稳定。
|
|
61
|
+
|
|
62
|
+
Exa 同理:其公开 MCP 端点(`mcp.exa.ai/mcp`)支持匿名调用,因此不配置 key 也能用;配置 `EXA_API_KEY` 后可获得更高额度。
|
|
63
|
+
|
|
64
|
+
## 安装
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
git clone https://github.com/DDDMUC/dsh-free-search.git
|
|
68
|
+
dsh plugin --profile web add /path/to/dsh-free-search
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
然后重启:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
dsh web
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 依赖说明
|
|
78
|
+
|
|
79
|
+
插件对 `@deepseek-ai/dsh-settings` 和 `@deepseek-ai/dsh-tools` 使用 `peerDependencies`,这是刻意的:DSH 运行时必须使用安装树中的唯一实例。请通过 `dsh plugin --profile <profile> add ...` 安装插件,不要把 DSH 核心包复制进 profile 的本地 `node_modules`;重复副本会导致工具调度器失效。
|
|
80
|
+
|
|
81
|
+
## 使用
|
|
82
|
+
|
|
83
|
+
### 网页设置(推荐)
|
|
84
|
+
|
|
85
|
+
安装后,打开 **设置 → 插件 → 可配置** 标签页 → **Free Search** 卡片(官方设置页):
|
|
86
|
+
|
|
81
87
|
- **Search engine**:下拉框切换引擎,保存即生效
|
|
82
|
-
- **API keys**:为 Exa / Perplexity / DeepSeek 填写 key(密码框,保存后只显示"已配置")
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
free-search
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
88
|
+
- **API keys**:为 Exa / Perplexity / DeepSeek 填写 key(密码框,保存后只显示"已配置")
|
|
89
|
+
- **Use Bing default**:把当前搜索引擎切回稳定的免费 Bing;`Discard` 只撤销尚未保存的编辑
|
|
90
|
+
- **Platform search**:勾选启用 GitHub / V2EX / Bilibili 平台搜索(`platform_search` 工具按此过滤)
|
|
91
|
+
|
|
92
|
+
<table align="center" style="border: none; border-collapse: collapse;">
|
|
93
|
+
<tr style="border: none;">
|
|
94
|
+
<td align="center" width="50%" style="border: none; padding: 6px;">
|
|
95
|
+
<a href="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-free.png">
|
|
96
|
+
<img src="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-free.png" alt="免费引擎设置" width="100%" />
|
|
97
|
+
</a>
|
|
98
|
+
<br>
|
|
99
|
+
<sub>▲ <b>免费引擎</b>(显示绿色 FREE 徽章与官网链接)</sub>
|
|
100
|
+
</td>
|
|
101
|
+
<td align="center" width="50%" style="border: none; padding: 6px;">
|
|
102
|
+
<a href="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-apikey.png">
|
|
103
|
+
<img src="https://raw.githubusercontent.com/DDDMUC/dsh-free-search/master/assets/settings-apikey.png" alt="付费引擎设置" width="100%" />
|
|
104
|
+
</a>
|
|
105
|
+
<br>
|
|
106
|
+
<sub>▲ <b>付费/API Key 引擎</b>(显示橙色 API KEY 徽章与获取链接)</sub>
|
|
107
|
+
</td>
|
|
108
|
+
</tr>
|
|
109
|
+
</table>
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### 命令行 / 配置文件
|
|
113
|
+
|
|
114
|
+
配置存在 `~/.dsh/settings.yaml`:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
free-search:
|
|
118
|
+
provider: bing # ddg / ddg-lite / bing / searxng / exa / perplexity / deepseek-official
|
|
119
|
+
bingMarket: zh-CN # Bing 市场
|
|
120
|
+
region: cn-zh # DuckDuckGo 区域(可选)
|
|
121
|
+
searxngInstances: # 自定义 SearXNG 实例(可选)
|
|
122
|
+
- https://your-instance.example
|
|
123
|
+
exaApiKey: ... # 或通过设置页填写
|
|
124
|
+
perplexityApiKey: ...
|
|
125
|
+
deepseekApiKey: ...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 让 agent 测试所有引擎
|
|
129
|
+
|
|
130
|
+
对 agent 说"测试一下所有搜索引擎",它会调用 `free_search_test` 工具,逐个测试并报告:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
Search engine test:
|
|
134
|
+
- ddg: FAIL - DuckDuckGo is rate-limited right now (anti-bot challenge, usually temporary) - Bing works
|
|
135
|
+
- bing: OK (2 results, e.g. "DeepSeek Harness developer preview...")
|
|
136
|
+
- exa: FAIL - EXA_API_KEY not configured
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 抓取网页内容(web_fetch)
|
|
140
|
+
|
|
141
|
+
搜索到 URL 后,可以让 agent **读取网页全文**(如"打开第一个链接看看内容")。`web_fetch` 工具已启用(官方 `dsh-web-fetch-http` provider):
|
|
142
|
+
|
|
143
|
+
- 自动跟随重定向、解码正文(HTML 转文本)
|
|
144
|
+
- 支持超时和大小限制
|
|
145
|
+
- ⚠️ 注意:`web_fetch` 无 SSRF 防护,agent 理论上可访问内网地址——按需使用
|
|
146
|
+
|
|
147
|
+
### 平台搜索(platform_search)
|
|
148
|
+
|
|
149
|
+
让 agent 搜特定平台,如"在 GitHub 上搜 deepseek harness"、"看看 B站有什么相关视频"、"V2EX 上关于 dsh 的讨论"。`platform_search` 工具支持:
|
|
150
|
+
|
|
151
|
+
| 平台 | 用途 |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `github` | GitHub 仓库搜索(API,免费无 key) |
|
|
154
|
+
| `v2ex` | V2EX 热门/相关主题 |
|
|
155
|
+
| `bilibili` | B站视频/内容搜索(公开接口) |
|
|
156
|
+
|
|
157
|
+
全部走公开 API,零外部依赖,开箱即用。
|
|
158
|
+
|
|
159
|
+
## 本地引擎切换工具(tools/)
|
|
160
|
+
|
|
161
|
+
`tools/` 目录附带了一个本地切换小工具(零依赖):
|
|
162
|
+
|
|
163
|
+
- **`启动搜索引擎切换器.cmd`**(Windows)——双击启动本地 Node 服务(`http://127.0.0.1:4789`)并自动打开浏览器选择页面
|
|
164
|
+
- **`switch-engine.html`** —— 选择页面:显示当前引擎,点选新引擎,一键写入配置
|
|
165
|
+
- **`server.mjs`** —— 本地服务,负责读写 `~/.dsh/profiles/web/cordis.patch.yml`
|
|
166
|
+
- **`switch-engine.ps1`** —— 无界面命令行版:`powershell -File tools/switch-engine.ps1 -Engine bing`
|
|
167
|
+
|
|
168
|
+
切换后重启 `dsh web` 生效。
|
|
169
|
+
|
|
170
|
+
> 配置卡片挂在官方设置页的 `settings.plugin.item` 插槽(dsh 自带),配置读写走插件自建 bridge,**不依赖 dsh-web-ui**,插件可独立使用。
|
|
171
|
+
|
|
172
|
+
## 代理说明(国内用户)
|
|
173
|
+
|
|
174
|
+
DuckDuckGo 等引擎可能需要代理才能访问,而 Node.js 的 `fetch` 默认不走系统代理。需要给 dsh 进程设置(Node 24+):
|
|
175
|
+
|
|
176
|
+
```sh
|
|
177
|
+
export NODE_USE_ENV_PROXY=1
|
|
178
|
+
export HTTPS_PROXY=http://127.0.0.1:7897 # 你的代理地址
|
|
179
|
+
export HTTP_PROXY=http://127.0.0.1:7897
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Windows 用户:桌面快捷方式已内置此配置(`set NODE_USE_ENV_PROXY=1&& set HTTPS_PROXY=...`)。
|
|
183
|
+
|
|
184
|
+
## 工作原理
|
|
185
|
+
|
|
186
|
+
- `lib/index.js`:host 端。实现 `WebSearchProvider`(`id` / `available()` / `search()`),多引擎路由 + 自动回退;注册 `free-search` settings namespace;提供 `/api/dsh-free-search-settings` 读写桥;注册 `free_search_test` 工具;注入引擎清单到系统提示词。
|
|
187
|
+
- `lib/client.js`:浏览器端。React 配置卡片(引擎选择 + key 输入),挂载到官方设置页的 `settings.plugin.item` 插槽(设置 → 插件 → 可配置),不依赖 dsh-web-ui。
|
|
188
|
+
- `cordis.patch.yml`:插件 loader 配置。
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
package/lib/client.js
CHANGED
|
@@ -31,6 +31,9 @@ window.__ModuleLoader__.load({
|
|
|
31
31
|
".dshfs-input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}",
|
|
32
32
|
".dshfs-input:disabled{opacity:.6;cursor:default}",
|
|
33
33
|
".dshfs-hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px}",
|
|
34
|
+
".dshfs-platforms{display:flex;gap:10px;flex-wrap:wrap}",
|
|
35
|
+
".dshfs-platform{display:flex;align-items:center;gap:5px;color:var(--dsw-alias-label-primary);font-size:13px;cursor:pointer}",
|
|
36
|
+
".dshfs-platform input{accent-color:var(--dsw-alias-state-business-primary)}",
|
|
34
37
|
".dshfs-link{color:var(--dsw-alias-state-business-primary);font-size:12px;text-decoration:none;align-self:flex-start;padding:2px 0}",
|
|
35
38
|
".dshfs-link:hover{text-decoration:underline}",
|
|
36
39
|
".dshfs-btn{font:inherit;cursor:pointer;border-radius:6px;padding:5px 12px;font-size:13px;transition:background-color .13s,border-color .13s,color .13s}",
|
|
@@ -89,6 +92,7 @@ window.__ModuleLoader__.load({
|
|
|
89
92
|
const [exaKey, setExaKey] = react.useState("");
|
|
90
93
|
const [perplexityKey, setPerplexityKey] = react.useState("");
|
|
91
94
|
const [deepseekKey, setDeepseekKey] = react.useState("");
|
|
95
|
+
const [platforms, setPlatforms] = react.useState(["github", "v2ex", "bilibili"]);
|
|
92
96
|
const [keysConfigured, setKeysConfigured] = react.useState({});
|
|
93
97
|
const [dirty, setDirty] = react.useState(false);
|
|
94
98
|
const [saving, setSaving] = react.useState(false);
|
|
@@ -105,6 +109,7 @@ window.__ModuleLoader__.load({
|
|
|
105
109
|
setExaKey(v.exaApiKey ?? "");
|
|
106
110
|
setPerplexityKey(v.perplexityApiKey ?? "");
|
|
107
111
|
setDeepseekKey(v.deepseekApiKey ?? "");
|
|
112
|
+
setPlatforms(Array.isArray(v.platforms) && v.platforms.length > 0 ? v.platforms : ["github", "v2ex", "bilibili"]);
|
|
108
113
|
// secrets 字段标记哪些 key 已配置(值被脱敏,仅显示"已配置")
|
|
109
114
|
const configured = {};
|
|
110
115
|
for (const secret of view.secrets ?? []) {
|
|
@@ -146,6 +151,7 @@ window.__ModuleLoader__.load({
|
|
|
146
151
|
if (exaKey.trim()) ops.push({ op: "set", path: ["exaApiKey"], value: exaKey.trim() });
|
|
147
152
|
if (perplexityKey.trim()) ops.push({ op: "set", path: ["perplexityApiKey"], value: perplexityKey.trim() });
|
|
148
153
|
if (deepseekKey.trim()) ops.push({ op: "set", path: ["deepseekApiKey"], value: deepseekKey.trim() });
|
|
154
|
+
ops.push({ op: "set", path: ["platforms"], value: platforms });
|
|
149
155
|
const result = await bridgeMutate({ ns: NS, ops });
|
|
150
156
|
if (result.ok) {
|
|
151
157
|
setDirty(false);
|
|
@@ -291,6 +297,44 @@ window.__ModuleLoader__.load({
|
|
|
291
297
|
}),
|
|
292
298
|
],
|
|
293
299
|
}),
|
|
300
|
+
react_jsx_runtime.jsx("div", {
|
|
301
|
+
className: "dshfs-field",
|
|
302
|
+
children: [
|
|
303
|
+
react_jsx_runtime.jsx("div", {
|
|
304
|
+
className: "dshfs-label",
|
|
305
|
+
children: "Platform search (platform_search tool)",
|
|
306
|
+
}),
|
|
307
|
+
react_jsx_runtime.jsx("div", {
|
|
308
|
+
className: "dshfs-platforms",
|
|
309
|
+
children: [
|
|
310
|
+
["github", "GitHub"], ["v2ex", "V2EX"], ["bilibili", "Bilibili"],
|
|
311
|
+
].map(([id, label]) =>
|
|
312
|
+
react_jsx_runtime.jsx("label", {
|
|
313
|
+
className: "dshfs-platform",
|
|
314
|
+
children: [
|
|
315
|
+
react_jsx_runtime.jsx("input", {
|
|
316
|
+
type: "checkbox",
|
|
317
|
+
checked: platforms.includes(id),
|
|
318
|
+
disabled: !ready || saving,
|
|
319
|
+
onChange: (e) => {
|
|
320
|
+
setPlatforms((prev) =>
|
|
321
|
+
e.target.checked ? [...prev, id] : prev.filter((p) => p !== id)
|
|
322
|
+
);
|
|
323
|
+
setDirty(true);
|
|
324
|
+
setFailed(false);
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
label,
|
|
328
|
+
],
|
|
329
|
+
}, id)
|
|
330
|
+
),
|
|
331
|
+
}),
|
|
332
|
+
react_jsx_runtime.jsx("p", {
|
|
333
|
+
className: "dshfs-hint",
|
|
334
|
+
children: "Enable platforms for the agent's platform_search tool. Disabled platforms are skipped.",
|
|
335
|
+
}),
|
|
336
|
+
],
|
|
337
|
+
}),
|
|
294
338
|
!ready
|
|
295
339
|
? react_jsx_runtime.jsx("p", {
|
|
296
340
|
className: "dshfs-hint",
|
|
@@ -301,6 +345,17 @@ window.__ModuleLoader__.load({
|
|
|
301
345
|
className: "dshfs-footer",
|
|
302
346
|
children: [
|
|
303
347
|
failed ? react_jsx_runtime.jsx("p", { className: "dshfs-failed", children: "save failed" }) : null,
|
|
348
|
+
react_jsx_runtime.jsx("button", {
|
|
349
|
+
className: "dshfs-btn",
|
|
350
|
+
type: "button",
|
|
351
|
+
onClick: () => {
|
|
352
|
+
setProvider("bing");
|
|
353
|
+
setDirty(true);
|
|
354
|
+
setFailed(false);
|
|
355
|
+
},
|
|
356
|
+
disabled: saving || !ready || provider === "bing",
|
|
357
|
+
children: "Use Bing default",
|
|
358
|
+
}),
|
|
304
359
|
react_jsx_runtime.jsx("button", {
|
|
305
360
|
className: "dshfs-btn",
|
|
306
361
|
type: "button",
|
package/lib/index.js
CHANGED
|
@@ -742,6 +742,7 @@ const Config = z.object({
|
|
|
742
742
|
region: z.string(),
|
|
743
743
|
bingMarket: z.string().default("zh-CN"),
|
|
744
744
|
searxngInstances: z.array(z.string()),
|
|
745
|
+
platforms: z.array(z.string()).default(["github", "v2ex", "bilibili"]),
|
|
745
746
|
exaApiKey: z.string().role("secret"),
|
|
746
747
|
perplexityApiKey: z.string().role("secret"),
|
|
747
748
|
deepseekApiKey: z.string().role("secret"),
|
|
@@ -766,7 +767,8 @@ function apply(ctx, config) {
|
|
|
766
767
|
};
|
|
767
768
|
|
|
768
769
|
// 总控 provider:按 settings 的 provider 字段路由到任意引擎。
|
|
769
|
-
//
|
|
770
|
+
// 任何引擎失败(缺 key / 401 / 限流 / 网络)都会自动回退到可用的免费引擎,
|
|
771
|
+
// 并在结果里附带回退提示,避免 agent 搜索直接失败。
|
|
770
772
|
const provider = {
|
|
771
773
|
id: "ddg",
|
|
772
774
|
available() {
|
|
@@ -776,22 +778,46 @@ function apply(ctx, config) {
|
|
|
776
778
|
const cfg = current();
|
|
777
779
|
const preferred = cfg.provider ?? "bing";
|
|
778
780
|
|
|
779
|
-
//
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
781
|
+
// 付费引擎:先尝试;失败(缺 key / 401 / 网络)则回退免费引擎链
|
|
782
|
+
let fallbackReason = null;
|
|
783
|
+
if (preferred === "exa" || preferred === "perplexity" || preferred === "deepseek-official") {
|
|
784
|
+
try {
|
|
785
|
+
if (preferred === "exa") {
|
|
786
|
+
const key = await resolveApiKey("EXA_API_KEY", "exaApiKey");
|
|
787
|
+
if (key) {
|
|
788
|
+
const result = await searchExa(request.query, request.maxResults, key, signal);
|
|
789
|
+
if (result.sources.length > 0) return result;
|
|
790
|
+
fallbackReason = "Exa returned no results";
|
|
791
|
+
} else {
|
|
792
|
+
// exa 无 key 时走 keyless MCP(免费)
|
|
793
|
+
try {
|
|
794
|
+
const result = await searchExaMCP(request.query, request.maxResults, signal);
|
|
795
|
+
if (result.sources.length > 0) return result;
|
|
796
|
+
fallbackReason = "Exa keyless MCP returned no results";
|
|
797
|
+
} catch (mcpError) {
|
|
798
|
+
fallbackReason = mcpError instanceof Error ? mcpError.message : String(mcpError);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
} else if (preferred === "perplexity") {
|
|
802
|
+
const key = await resolveApiKey("PERPLEXITY_API_KEY", "perplexityApiKey");
|
|
803
|
+
if (!key) throw new Error("Perplexity search requires PERPLEXITY_API_KEY");
|
|
804
|
+
const result = await searchPerplexity(request.query, request.maxResults, key, signal);
|
|
805
|
+
if (result.sources.length > 0) return result;
|
|
806
|
+
fallbackReason = "Perplexity returned no results";
|
|
807
|
+
} else {
|
|
808
|
+
const key = await resolveApiKey("DEEPSEEK_API_KEY", "deepseekApiKey");
|
|
809
|
+
if (!key) throw new Error("DeepSeek search requires DEEPSEEK_API_KEY");
|
|
810
|
+
const result = await searchDeepSeekOfficial(request.query, request.maxResults, key, signal);
|
|
811
|
+
if (result.sources.length > 0) return result;
|
|
812
|
+
fallbackReason = "DeepSeek search returned no results";
|
|
813
|
+
}
|
|
814
|
+
} catch (error) {
|
|
815
|
+
fallbackReason = error instanceof Error ? error.message : String(error);
|
|
816
|
+
logger.warn(`free-search: paid engine "${preferred}" failed (${fallbackReason}), falling back to free engines`);
|
|
817
|
+
}
|
|
792
818
|
}
|
|
793
819
|
|
|
794
|
-
//
|
|
820
|
+
// 免费引擎链(付费引擎失败时也走这里)
|
|
795
821
|
const chain =
|
|
796
822
|
preferred === "ddg-lite"
|
|
797
823
|
? ["ddg-lite", "ddg", "anysearch", "searxng", "bing"]
|
|
@@ -801,7 +827,7 @@ function apply(ctx, config) {
|
|
|
801
827
|
? ["searxng", "anysearch", "bing", "ddg", "ddg-lite"]
|
|
802
828
|
: preferred === "anysearch"
|
|
803
829
|
? ["anysearch", "bing", "searxng", "ddg", "ddg-lite"]
|
|
804
|
-
: ["
|
|
830
|
+
: ["bing", "anysearch", "searxng", "ddg", "ddg-lite"];
|
|
805
831
|
let lastError = null;
|
|
806
832
|
for (const engine of chain) {
|
|
807
833
|
try {
|
|
@@ -825,7 +851,13 @@ function apply(ctx, config) {
|
|
|
825
851
|
default:
|
|
826
852
|
result = await searchDdgHtml(request.query, request.maxResults, cfg, signal);
|
|
827
853
|
}
|
|
828
|
-
if (result.sources.length > 0)
|
|
854
|
+
if (result.sources.length > 0) {
|
|
855
|
+
// 付费引擎失败回退时,在结果里附上提示(agent 会看到)
|
|
856
|
+
if (fallbackReason) {
|
|
857
|
+
result.content = `Note: ${preferred} search unavailable (${fallbackReason}). Results from ${engine}.`;
|
|
858
|
+
}
|
|
859
|
+
return result;
|
|
860
|
+
}
|
|
829
861
|
lastError = new Error(`engine "${engine}" returned 0 results`);
|
|
830
862
|
logger.warn(`free-search: ${engine} returned 0 results, trying next engine`);
|
|
831
863
|
} catch (error) {
|
|
@@ -1044,10 +1076,22 @@ function apply(ctx, config) {
|
|
|
1044
1076
|
if (!PLATFORMS[platform]) {
|
|
1045
1077
|
throw new Error(`unknown platform "${platform}" - use one of: ${Object.keys(PLATFORMS).join(", ")}`);
|
|
1046
1078
|
}
|
|
1079
|
+
// 平台开关:settings 里禁用某平台时,工具明确告知
|
|
1080
|
+
const enabled = current().platforms ?? ["github", "v2ex", "bilibili"];
|
|
1081
|
+
if (!enabled.includes(platform)) {
|
|
1082
|
+
throw new Error(
|
|
1083
|
+
`platform "${platform}" is disabled in Free Search settings - enable it in Settings > Plugins > Free Search to use it`
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1047
1086
|
const limit = Math.min(args.maxResults ?? 5, 10);
|
|
1048
1087
|
const result = await searchPlatform(platform, args.query, limit);
|
|
1049
1088
|
return { platform, sources: result.sources };
|
|
1050
1089
|
},
|
|
1090
|
+
finalizeContent(exec, result) {
|
|
1091
|
+
// Tool-result content must be an array of content blocks, not a raw string.
|
|
1092
|
+
const text = result.content;
|
|
1093
|
+
return typeof text === "string" && text.length > 0 ? [{ type: "text", text }] : undefined;
|
|
1094
|
+
},
|
|
1051
1095
|
})
|
|
1052
1096
|
);
|
|
1053
1097
|
return () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-free-search",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Free web search for DeepSeek Harness: 8 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa keyless) + platform search + web_fetch, with web settings UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|