@tonydua/dsh-web-search-exa 0.1.2 → 0.1.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.i18n.yaml +2 -2
- package/README.md +175 -14
- package/README.zh.md +146 -10
- package/cordis.patch.yml +9 -10
- package/lib/index.d.ts +386 -0
- package/lib/index.js +341 -212
- package/package.json +28 -12
- package/lib/types/index.d.ts +0 -54
package/README.i18n.yaml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record.
|
|
4
|
-
README.md:
|
|
5
|
-
README.zh.md:
|
|
4
|
+
README.md: 1c5d8f365ee4206b20fa5bd7c07e980266324287
|
|
5
|
+
README.zh.md: 8801d405bc41cace604325a7918152394a5627a6
|
package/README.md
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [简体中文](README.zh.md)
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
|
|
5
|
+
[](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
|
|
6
|
+
[](https://github.com/TonyDua/dsh-web-search-exa/releases/latest)
|
|
6
7
|
[](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
|
|
7
8
|
[](LICENSE)
|
|
9
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
10
|
+
[](package.json)
|
|
8
11
|
[](https://github.com/TonyDua/dsh-web-search-exa)
|
|
9
12
|
[](https://github.com/TonyDua/dsh-web-search-exa)
|
|
10
|
-
[](package.json)
|
|
11
13
|
|
|
12
14
|
> Zero-config [Exa](https://exa.ai) web search for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh):
|
|
13
15
|
> **no API key required** — a `WebSearchProvider` for the `ctx.web` seam with an
|
|
@@ -15,6 +17,131 @@
|
|
|
15
17
|
|
|
16
18
|
Built with [deepseek-v4-flash](https://api-docs.deepseek.com) inside DeepSeek Harness (dsh).
|
|
17
19
|
|
|
20
|
+
## Supported versions
|
|
21
|
+
|
|
22
|
+
**Every published dsh version from `0.1.2-alpha.2` to `0.1.7-alpha.1` is
|
|
23
|
+
verified**, not merely declared: each one is installed in isolation, the plugin
|
|
24
|
+
is typechecked against that version's own declarations, and the test suite runs
|
|
25
|
+
against it. Reproduce with `bash scripts/compat-matrix.sh`.
|
|
26
|
+
|
|
27
|
+
| dsh line | Verified | Notes |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `0.1.2-alpha.2` … `0.1.2-alpha.5` | ✅ | oldest supported |
|
|
30
|
+
| `0.1.2-rc.1` | ✅ | |
|
|
31
|
+
| `0.1.3-alpha.2` | ✅ | |
|
|
32
|
+
| `0.1.5-alpha.1`, `0.1.5-alpha.2` | ✅ | |
|
|
33
|
+
| `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | ✅ | `0.1.5-rc.2` also verified end to end: a real `dsh --profile headless` task searched through the anonymous MCP path with no API key present |
|
|
34
|
+
| `0.1.6-alpha.1`, `0.1.6-alpha.2` | ✅ | |
|
|
35
|
+
| `0.1.7-alpha.1` | ✅ | settings service changed shape — see below |
|
|
36
|
+
|
|
37
|
+
### Why the peer range looks like that
|
|
38
|
+
|
|
39
|
+
```jsonc
|
|
40
|
+
"@deepseek-ai/dsh-web": ">=0.1.2-alpha.2 || >=0.1.3-alpha.2 || >=0.1.4-0 || >=0.1.5-alpha.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.1.8"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
That enumeration is not decoration — it is the only form that installs on **every**
|
|
44
|
+
published version under **both** pnpm and npm. The rule that forces it:
|
|
45
|
+
|
|
46
|
+
> A pre-release version satisfies a range only if some comparator in that range
|
|
47
|
+
> carries a pre-release **on the same `major.minor.patch` tuple**.
|
|
48
|
+
|
|
49
|
+
So `>=0.1.2-rc.1` does **not** match `0.1.5-rc.2` — the tuples differ. A single
|
|
50
|
+
open-ended lower bound therefore cannot cover a project published as a series of
|
|
51
|
+
prereleases, and `*` would accept even a breaking `1.0`. Each `0.1.x` line that
|
|
52
|
+
ever shipped a prerelease needs its own comparator; `>=0.1.8` then carries every
|
|
53
|
+
future stable release, so the list only needs a new entry when dsh opens a new
|
|
54
|
+
`0.1.x` prerelease line.
|
|
55
|
+
|
|
56
|
+
Measured, on the real published tarball:
|
|
57
|
+
|
|
58
|
+
| range | npm installs | pnpm |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `>=0.1.2-rc.1` (the earlier attempt) | **1 / 14** versions | 14 / 14 |
|
|
61
|
+
| enumerated (current) | **14 / 14** versions | 14 / 14 |
|
|
62
|
+
|
|
63
|
+
This was found by testing rather than reasoning: the open-ended range is fine on
|
|
64
|
+
pnpm, which is what `dsh plugin add` uses, and fails on npm for 13 of the 14
|
|
65
|
+
versions with `ERESOLVE`. If you install with npm and hit that on an older
|
|
66
|
+
release of this package, either upgrade, or pass `--legacy-peer-deps`.
|
|
67
|
+
|
|
68
|
+
### What differs across versions
|
|
69
|
+
|
|
70
|
+
Auditing the real export surfaces of all 14 versions found the `ctx.web` seam
|
|
71
|
+
completely stable — `WebError` is exported from `dsh-web` and still extends
|
|
72
|
+
`HarnessError`, `launchEnvironmentOf` is present, and the settings service is
|
|
73
|
+
mounted at `ctx.settings` in every version. Two things do differ:
|
|
74
|
+
|
|
75
|
+
1. **`0.1.7-alpha.1` replaced the settings API.** `SettingsProvider.installSection`
|
|
76
|
+
is gone; the service is now `SettingsForms`, which derives a configuration
|
|
77
|
+
page from the Config schema the Loader already holds for the entry
|
|
78
|
+
(`SettingsDescriptor.schema`, `autoGenerate`). Calling the old method
|
|
79
|
+
unconditionally threw a `TypeError` on that host, so the plugin loaded but
|
|
80
|
+
failed. It now probes for the method, calls it only when present, and
|
|
81
|
+
otherwise does nothing — on `0.1.7+` the Loader's schema is what feeds the
|
|
82
|
+
form, so there is nothing to register.
|
|
83
|
+
2. **`0.1.7-alpha.1` peers `@deepseek-ai/cordis` `^4.0.3`** while the cordis
|
|
84
|
+
`latest` dist-tag still points at `4.0.2`. `4.0.3` is published; the tag is
|
|
85
|
+
simply behind. Install `@deepseek-ai/cordis@4.0.3` alongside a `0.1.7` host.
|
|
86
|
+
The matrix script pins this per version.
|
|
87
|
+
|
|
88
|
+
Also supported with: `@deepseek-ai/dsh-web`, `dsh-settings` (optional),
|
|
89
|
+
`dsh-launch-environment` across that whole range, and Node.js `>=22.19.0` (the
|
|
90
|
+
harness's own floor).
|
|
91
|
+
|
|
92
|
+
### Profile-install note
|
|
93
|
+
|
|
94
|
+
dsh profiles set `autoInstallPeers: false`, and the harness's own services are
|
|
95
|
+
supplied at runtime by the dsh host instead of being resolved by pnpm. Add this
|
|
96
|
+
to the profile's `pnpm-workspace.yaml` so `dsh plugin add` stays warning-free:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
peerDependencyRules:
|
|
100
|
+
ignoreMissing:
|
|
101
|
+
- '@deepseek-ai/cordis'
|
|
102
|
+
- '@deepseek-ai/dsh-*'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Degradation and failover
|
|
106
|
+
|
|
107
|
+
The keyless channel is a shared, best-effort endpoint. The provider reports its
|
|
108
|
+
own health rather than pretending to always work:
|
|
109
|
+
|
|
110
|
+
- 3 consecutive transient failures (5xx, 429, network, unparseable body) open a
|
|
111
|
+
circuit breaker for 5 minutes, during which `available()` returns `false`. One
|
|
112
|
+
successful search closes it again.
|
|
113
|
+
- A 4xx other than 429 does not trip it — that failure would repeat forever, so
|
|
114
|
+
hiding it would only delay the same error.
|
|
115
|
+
- Anonymous 429s raise `WEB_RATE_LIMITED` (not a generic `WEB_PROVIDER_ERROR`)
|
|
116
|
+
with a message naming `EXA_API_KEY`.
|
|
117
|
+
- The keyed REST path ignores the breaker: a paid endpoint's failures are yours
|
|
118
|
+
to see.
|
|
119
|
+
|
|
120
|
+
**Whether that turns into automatic failover is a harness-side decision.** The
|
|
121
|
+
seam picks exactly one usable provider and has no priority chain — with two
|
|
122
|
+
usable providers it raises `WEB_PROVIDER_AMBIGUOUS`. So:
|
|
123
|
+
|
|
124
|
+
- Pinning `searchProvider: exa` gives deterministic selection but *no* fallback:
|
|
125
|
+
when the breaker opens you get `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`.
|
|
126
|
+
- Leaving `searchProvider` unset gives up determinism: a degraded Exa stops
|
|
127
|
+
being a candidate, but if another provider (say `deepseek-official` with a
|
|
128
|
+
valid `DEEPSEEK_API_KEY`) is also usable, the seam reports ambiguity instead
|
|
129
|
+
of choosing it.
|
|
130
|
+
|
|
131
|
+
Pick whichever failure mode you prefer; the plugin cannot choose for you.
|
|
132
|
+
|
|
133
|
+
### Building from source
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
pnpm install
|
|
137
|
+
pnpm run build # tsdown -> lib/index.js + lib/index.d.ts
|
|
138
|
+
pnpm run typecheck # tsc --noEmit
|
|
139
|
+
pnpm test # builds, then runs the node:test suite against lib/
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`src/` is the source of truth; `lib/` is committed because both the published
|
|
143
|
+
tarball and git-based installs consume it.
|
|
144
|
+
|
|
18
145
|
## Features
|
|
19
146
|
|
|
20
147
|
- 🆓 **Zero-config, keyless by default** — searches route through Exa's hosted MCP
|
|
@@ -45,7 +172,7 @@ the official one does not have, and keeps the same keyed REST behavior.
|
|
|
45
172
|
| Zero-config install | ❌ | ✅ |
|
|
46
173
|
| Provider id | `exa` (fixed) | `exa` by default, **configurable via `providerId`** |
|
|
47
174
|
| Cordis plugin name | `web-search-exa` | `web-search-exa` |
|
|
48
|
-
| Config keys | `apiKey`, `baseURL`, `searchType`, `numResults`, `highlightsPerResult` | `apiKey`, `apiKeyEnv`, `
|
|
175
|
+
| Config keys | `apiKey`, `baseURL`, `searchType`, `numResults`, `highlightsPerResult` | `apiKey`, `apiKeyEnv`, `baseURL`, `apiURL` (legacy), `mcpURL`, `searchType`, `numResults`, `highlightsPerResult`, `providerId` |
|
|
49
176
|
|
|
50
177
|
## Which one should I use?
|
|
51
178
|
|
|
@@ -61,29 +188,56 @@ the official one does not have, and keeps the same keyed REST behavior.
|
|
|
61
188
|
|
|
62
189
|
| Condition | Path | Endpoint |
|
|
63
190
|
|---|---|---|
|
|
64
|
-
| `apiKey` / `EXA_API_KEY` set | REST `POST /search` with `Authorization: Bearer` | `https://api.exa.ai/search` (configurable) |
|
|
191
|
+
| `apiKey` / `EXA_API_KEY` set | REST `POST /search` with `Authorization: Bearer` | `https://api.exa.ai/search` (`baseURL` configurable) |
|
|
65
192
|
| No key configured | Anonymous MCP `tools/call web_search_exa` (JSON-RPC 2.0, no credentials) | `https://mcp.exa.ai/mcp` (configurable) |
|
|
66
193
|
|
|
67
194
|
The anonymous MCP path sends no credentials; attribution rides the
|
|
68
195
|
`x-exa-source: dsh-anything` header. Results are normalized to the seam's
|
|
69
196
|
`WebSearchSource` shape (`url`, `title`, `snippet`, `publishedAt`) and the seam
|
|
70
197
|
enforces `maxResults` on the way back. Anonymous usage is rate-limited by Exa:
|
|
71
|
-
an HTTP 429 surfaces as a `
|
|
72
|
-
key (which also switches to
|
|
198
|
+
an HTTP 429 surfaces as a distinct `WEB_RATE_LIMITED` code — not a generic
|
|
199
|
+
provider failure — with a hint to configure an API key (which also switches to
|
|
200
|
+
the REST path automatically).
|
|
73
201
|
|
|
74
202
|
## Installation (into a dsh profile)
|
|
75
203
|
|
|
76
|
-
**One
|
|
77
|
-
|
|
78
|
-
|
|
204
|
+
> **One artifact, two doors.** CI packs this version's tarball, verifies it
|
|
205
|
+
> against every supported dsh version, attaches it to the GitHub Release, and
|
|
206
|
+
> publishes **that artifact** to npm — so the release asset and the npm tarball
|
|
207
|
+
> are one file, not two builds that happen to match.
|
|
208
|
+
|
|
209
|
+
**From npm** (v0.1.4+ ships the `dsh.bundle` manifest, so the bundle patch
|
|
210
|
+
inserts the provider row with no manual patch editing):
|
|
79
211
|
|
|
80
212
|
```powershell
|
|
81
213
|
dsh plugin --profile web add @tonydua/dsh-web-search-exa
|
|
82
214
|
```
|
|
83
215
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
216
|
+
**From the GitHub Release** — the same tarball, for when npm is unreachable:
|
|
217
|
+
|
|
218
|
+
```powershell
|
|
219
|
+
dsh plugin --profile web add https://github.com/TonyDua/dsh-web-search-exa/releases/latest/download/dsh-web-search-exa.tgz
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**From the repository** (tracks `main`, includes work not yet released):
|
|
223
|
+
|
|
224
|
+
```powershell
|
|
225
|
+
dsh plugin --profile web add github:TonyDua/dsh-web-search-exa
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Restart `dsh web`. **Without an API key** the official DeepSeek search
|
|
229
|
+
provider is unavailable, so the seam auto-selects this provider — fully
|
|
230
|
+
zero-config. **With a key configured**, select Exa explicitly in your own
|
|
231
|
+
`$DSH_HOME/profiles/web/cordis.patch.yml` (applied after bundle patches):
|
|
232
|
+
|
|
233
|
+
```yaml
|
|
234
|
+
- id: web
|
|
235
|
+
name: '@deepseek-ai/dsh-web'
|
|
236
|
+
config:
|
|
237
|
+
searchProvider: exa
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
…or at runtime with the environment variable `$DSH_WEB_SEARCH_PROVIDER=exa`.
|
|
87
241
|
|
|
88
242
|
**Local development checkout:**
|
|
89
243
|
|
|
@@ -129,7 +283,8 @@ error such as `Cannot read properties of undefined (reading 'prepare')`.
|
|
|
129
283
|
| `providerId` | `exa` | Provider id registered into `ctx.web`. Only change it when both this and the official package are installed (see next section). |
|
|
130
284
|
| `apiKey` | unset | Literal Exa API key. Empty/missing enables the anonymous MCP path. |
|
|
131
285
|
| `apiKeyEnv` | `EXA_API_KEY` | Environment variable consulted when no literal `apiKey` is set. |
|
|
132
|
-
| `
|
|
286
|
+
| `baseURL` | `https://api.exa.ai` | Exa API base URL; `/search` is appended for the keyed REST path. Matches the official dsh provider. |
|
|
287
|
+
| `apiURL` | unset | Deprecated full REST endpoint alias. If set, it takes precedence over `baseURL`. |
|
|
133
288
|
| `mcpURL` | `https://mcp.exa.ai/mcp` | Exa hosted MCP endpoint (anonymous path). |
|
|
134
289
|
| `searchType` | `auto` | REST retrieval mode: `auto` / `keyword` / `neural`. |
|
|
135
290
|
| `numResults` | unset | Default result count when the request carries no `maxResults`. |
|
|
@@ -180,7 +335,7 @@ plugin namespaces. What is true today:
|
|
|
180
335
|
as `web-search-exa` (`@tonydua/dsh-web-search-exa`) once enabled — the
|
|
181
336
|
inventory reads the live Cordis loader, no extra code needed.
|
|
182
337
|
- **Settings namespace** (server-side): the plugin registers the
|
|
183
|
-
`web-search-exa` section via `
|
|
338
|
+
`web-search-exa` section via the current `ctx.settings.installSection` API, so the data layer is
|
|
184
339
|
writable — but **no client card binds to it**, so nothing shows in the UI.
|
|
185
340
|
The built-in "Web search" card edits the official
|
|
186
341
|
`web-search-deepseek` namespace, not this plugin.
|
|
@@ -218,6 +373,12 @@ only; a UI card is planned for the next version. Configure through
|
|
|
218
373
|
`cordis.patch.yml` or environment variables for now (see
|
|
219
374
|
[In the Web panel](#in-the-web-panel)).
|
|
220
375
|
|
|
376
|
+
**Q: Which dsh versions are supported?**
|
|
377
|
+
Every published dsh version from `0.1.2-alpha.2` to `0.1.7-alpha.1`, plus future
|
|
378
|
+
`0.1.8+` stable releases. Each version is installed in isolation, typechecked
|
|
379
|
+
against its own declarations, and run through this package's test suite in CI —
|
|
380
|
+
see [Why the peer range looks like that](#why-the-peer-range-looks-like-that).
|
|
381
|
+
|
|
221
382
|
## Acknowledgements
|
|
222
383
|
|
|
223
384
|
The anonymous MCP integration follows the `web_search` implementation in
|
package/README.zh.md
CHANGED
|
@@ -2,18 +2,127 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **简体中文**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
|
|
6
|
+
[](https://github.com/TonyDua/dsh-web-search-exa/releases/latest)
|
|
7
|
+
[](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
|
|
7
8
|
[](LICENSE)
|
|
9
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
10
|
+
[](package.json)
|
|
8
11
|
[](https://github.com/TonyDua/dsh-web-search-exa)
|
|
9
12
|
[](https://github.com/TonyDua/dsh-web-search-exa)
|
|
10
|
-
[](package.json)
|
|
11
13
|
|
|
12
14
|
> 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)提供**零配置**的 [Exa](https://exa.ai) 网页搜索:
|
|
13
15
|
> **无需 API key** —— 一个 `ctx.web` seam 的 `WebSearchProvider`,内置匿名 MCP 兜底 + 带 key 的 REST 路径。
|
|
14
16
|
|
|
15
17
|
使用 [deepseek-v4-flash](https://api-docs.deepseek.com) 在 DeepSeek Harness(dsh)内开发。
|
|
16
18
|
|
|
19
|
+
## 当前支持的版本
|
|
20
|
+
|
|
21
|
+
**npm 上从 `0.1.2-alpha.2` 到 `0.1.7-alpha.1` 的每一个 dsh 版本都经过实测**,不是只写在文档里:
|
|
22
|
+
每个版本会被独立安装、用**该版本自己的类型声明**做类型检查、并跑完整测试套件。
|
|
23
|
+
复现命令:`bash scripts/compat-matrix.sh`。
|
|
24
|
+
|
|
25
|
+
| dsh 版本线 | 实测 | 说明 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `0.1.2-alpha.2` … `0.1.2-alpha.5` | ✅ | 最老的受支持基线 |
|
|
28
|
+
| `0.1.2-rc.1` | ✅ | |
|
|
29
|
+
| `0.1.3-alpha.2` | ✅ | |
|
|
30
|
+
| `0.1.5-alpha.1`、`0.1.5-alpha.2` | ✅ | |
|
|
31
|
+
| `0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.5-rc.3` | ✅ | `0.1.5-rc.2` 另有端到端验证:无 API key 下用真实 `dsh --profile headless` 走通匿名 MCP |
|
|
32
|
+
| `0.1.6-alpha.1`、`0.1.6-alpha.2` | ✅ | |
|
|
33
|
+
| `0.1.7-alpha.1` | ✅ | settings 服务改了形态,见下 |
|
|
34
|
+
|
|
35
|
+
### 为什么 peer 范围长这样
|
|
36
|
+
|
|
37
|
+
```jsonc
|
|
38
|
+
"@deepseek-ai/dsh-web": ">=0.1.2-alpha.2 || >=0.1.3-alpha.2 || >=0.1.4-0 || >=0.1.5-alpha.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.1.8"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
这串枚举不是装饰,而是**在 pnpm 与 npm 下都能装遍所有已发布版本**的唯一写法。迫使它成立的规则是:
|
|
42
|
+
|
|
43
|
+
> 一个 prerelease 版本要满足某范围,该范围中必须存在一个比较器,其 prerelease 位于
|
|
44
|
+
> **相同的 `major.minor.patch` tuple** 上。
|
|
45
|
+
|
|
46
|
+
所以 `>=0.1.2-rc.1` **匹配不到** `0.1.5-rc.2`——tuple 不同。这意味着单一开区间下界
|
|
47
|
+
无法覆盖"以一串 prerelease 形式发布的项目",而 `*` 又会连未来的破坏性 `1.0` 一起放行。
|
|
48
|
+
凡是发布过 prerelease 的 `0.1.x` 线都需要自己的比较器;`>=0.1.8` 负责承接之后所有
|
|
49
|
+
稳定版,因此只有当 dsh 开出新的 `0.1.x` prerelease 线时才需要追加条目。
|
|
50
|
+
|
|
51
|
+
在**真实发布物**上实测:
|
|
52
|
+
|
|
53
|
+
| 范围 | npm 可安装版本数 | pnpm |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `>=0.1.2-rc.1`(先前写法) | **1 / 14** | 14 / 14 |
|
|
56
|
+
| 枚举写法(当前) | **14 / 14** | 14 / 14 |
|
|
57
|
+
|
|
58
|
+
这个结论是**测出来的,不是推出来的**:开区间在 pnpm(也就是 `dsh plugin add` 实际使用的
|
|
59
|
+
包管理器)下没问题,但在 npm 下会对 14 个版本中的 13 个报 `ERESOLVE`。若你用 npm 安装
|
|
60
|
+
旧版本的本插件时遇到该错误,升级即可,或临时加 `--legacy-peer-deps`。
|
|
61
|
+
|
|
62
|
+
### 各版本之间究竟差在哪
|
|
63
|
+
|
|
64
|
+
我把 14 个版本的**真实导出面**逐个探测过,结论是 `ctx.web` seam **完全稳定**:
|
|
65
|
+
`WebError` 始终由 `dsh-web` 导出且继承 `HarnessError`,`launchEnvironmentOf` 始终存在,
|
|
66
|
+
`ctx.settings` 在每个版本都被挂载。真正有差异的只有两处:
|
|
67
|
+
|
|
68
|
+
1. **`0.1.7-alpha.1` 换掉了 settings API。** `SettingsProvider.installSection` 被移除,
|
|
69
|
+
服务变为 `SettingsForms`,它直接从 Loader 已持有的 Config schema 派生配置页
|
|
70
|
+
(`SettingsDescriptor.schema`、`autoGenerate`)。旧代码无条件调用该方法会在该版本上抛
|
|
71
|
+
`TypeError`——插件能加载但会失败。现在改为探测该方法:存在才调用,不存在则什么都不做;
|
|
72
|
+
在 `0.1.7+` 上由 Loader 的 schema 驱动表单,插件无需注册任何东西。
|
|
73
|
+
2. **`0.1.7-alpha.1` 依赖 `@deepseek-ai/cordis` `^4.0.3`**,而 cordis 的 `latest`
|
|
74
|
+
dist-tag 仍指向 `4.0.2`。`4.0.3` 已发布,只是 tag 落后。搭配 `0.1.7` 宿主时请安装
|
|
75
|
+
`@deepseek-ai/cordis@4.0.3`。矩阵脚本已按版本固化这一点。
|
|
76
|
+
|
|
77
|
+
同样支持:`@deepseek-ai/dsh-web`、`dsh-settings`(可选)、`dsh-launch-environment`
|
|
78
|
+
覆盖上述整个范围;Node.js `>=22.19.0`(与 harness 自身下限一致)。
|
|
79
|
+
|
|
80
|
+
### profile 安装注意事项
|
|
81
|
+
|
|
82
|
+
dsh profile 默认 `autoInstallPeers: false`,且 harness 自身的服务由 dsh 宿主在运行时
|
|
83
|
+
提供、并不经 pnpm 解析。把下面这段加进 profile 的 `pnpm-workspace.yaml`,
|
|
84
|
+
`dsh plugin add` 就不会再报警告:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
peerDependencyRules:
|
|
88
|
+
ignoreMissing:
|
|
89
|
+
- '@deepseek-ai/cordis'
|
|
90
|
+
- '@deepseek-ai/dsh-*'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 降级与回退
|
|
94
|
+
|
|
95
|
+
免 key 通道是共享的尽力而为端点。本 provider 会如实汇报自身健康状况,而不是假装永远可用:
|
|
96
|
+
|
|
97
|
+
- 连续 3 次瞬时失败(5xx、429、网络错误、响应体无法解析)会打开熔断器并冷却 5 分钟,
|
|
98
|
+
期间 `available()` 返回 `false`;任意一次成功搜索即关闭熔断器。
|
|
99
|
+
- 429 以外的 4xx **不会**触发熔断——这类失败会永远重复,把它藏进冷却期只是推迟同一个错误。
|
|
100
|
+
- 匿名通道的 429 抛出 `WEB_RATE_LIMITED`(而非笼统的 `WEB_PROVIDER_ERROR`),
|
|
101
|
+
错误信息里直接点名 `EXA_API_KEY`。
|
|
102
|
+
- 配了 key 的 REST 路径不受熔断影响:付费端点的失败应当让你看到。
|
|
103
|
+
|
|
104
|
+
**但"是否真的自动回退"取决于 harness 侧。** seam 只会选中唯一一个可用 provider,
|
|
105
|
+
并不存在优先级链——同时存在多个可用 provider 时会抛 `WEB_PROVIDER_AMBIGUOUS`。因此:
|
|
106
|
+
|
|
107
|
+
- 写死 `searchProvider: exa`:选择确定,但**没有**回退;熔断打开时得到
|
|
108
|
+
`WEB_PROVIDER_CONFIGURED_UNAVAILABLE`。
|
|
109
|
+
- 不写 `searchProvider`:牺牲确定性。Exa 降级后确实不再是候选,但如果另一个
|
|
110
|
+
provider(比如带有效 `DEEPSEEK_API_KEY` 的 `deepseek-official`)也可用,
|
|
111
|
+
seam 会报"多个可用"而不是替你挑一个。
|
|
112
|
+
|
|
113
|
+
两种失败模式各有利弊,插件无法替你做这个决定。
|
|
114
|
+
|
|
115
|
+
### 从源码构建
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
pnpm install
|
|
119
|
+
pnpm run build # tsdown -> lib/index.js + lib/index.d.ts
|
|
120
|
+
pnpm run typecheck # tsc --noEmit
|
|
121
|
+
pnpm test # 先构建,再对 lib/ 跑 node:test 套件
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`src/` 是唯一事实来源;`lib/` 仍然提交进仓库,因为 npm 发布包与基于 git 的安装都依赖它。
|
|
125
|
+
|
|
17
126
|
## 特性
|
|
18
127
|
|
|
19
128
|
- 🆓 **零配置、默认免 key** —— 搜索经由 Exa 官方托管的 MCP 服务器(`mcp.exa.ai/mcp`),**完全不携带凭据**(Exa 官方提供的免认证公共 MCP,有限流)。
|
|
@@ -34,7 +143,7 @@ DeepSeek Harness 自带官方 Exa 提供方 [`@deepseek-ai/dsh-web-search-exa`](
|
|
|
34
143
|
| 零配置安装 | ❌ | ✅ |
|
|
35
144
|
| Provider id | `exa`(固定) | 默认 `exa`,**可用 `providerId` 配置** |
|
|
36
145
|
| Cordis 插件名 | `web-search-exa` | `web-search-exa` |
|
|
37
|
-
| 配置键 | `apiKey`、`baseURL`、`searchType`、`numResults`、`highlightsPerResult` | `apiKey`、`apiKeyEnv`、`apiURL
|
|
146
|
+
| 配置键 | `apiKey`、`baseURL`、`searchType`、`numResults`、`highlightsPerResult` | `apiKey`、`apiKeyEnv`、`baseURL`、`apiURL`(旧版)、`mcpURL`、`searchType`、`numResults`、`highlightsPerResult`、`providerId` |
|
|
38
147
|
|
|
39
148
|
## 我该用哪个?
|
|
40
149
|
|
|
@@ -46,20 +155,43 @@ DeepSeek Harness 自带官方 Exa 提供方 [`@deepseek-ai/dsh-web-search-exa`](
|
|
|
46
155
|
|
|
47
156
|
| 条件 | 路径 | 端点 |
|
|
48
157
|
|---|---|---|
|
|
49
|
-
| 配置了 `apiKey` / `EXA_API_KEY` | REST `POST /search`,`Authorization: Bearer` | `https://api.exa.ai/search
|
|
158
|
+
| 配置了 `apiKey` / `EXA_API_KEY` | REST `POST /search`,`Authorization: Bearer` | `https://api.exa.ai/search`(可用 `baseURL` 配置) |
|
|
50
159
|
| 未配置任何 key | 匿名 MCP `tools/call web_search_exa`(JSON-RPC 2.0,无凭据) | `https://mcp.exa.ai/mcp`(可配置) |
|
|
51
160
|
|
|
52
|
-
匿名 MCP 路径不发送任何凭据,来源标识通过 `x-exa-source: dsh-anything` 头携带。结果按 seam 的 `WebSearchSource` 形状规范化(`url`、`title`、`snippet`、`publishedAt`),`maxResults` 由 seam 在返回路径上强制执行。匿名使用受 Exa 限流:HTTP 429
|
|
161
|
+
匿名 MCP 路径不发送任何凭据,来源标识通过 `x-exa-source: dsh-anything` 头携带。结果按 seam 的 `WebSearchSource` 形状规范化(`url`、`title`、`snippet`、`publishedAt`),`maxResults` 由 seam 在返回路径上强制执行。匿名使用受 Exa 限流:HTTP 429 会以独立的 `WEB_RATE_LIMITED` 码呈现(而非笼统的 provider 失败),并提示配置 API key(配置后自动切换到 REST 路径)。
|
|
53
162
|
|
|
54
163
|
## 安装(装入 dsh profile)
|
|
55
164
|
|
|
56
|
-
|
|
165
|
+
> **同一个产物,两个入口。** CI 打包本版本的 tarball、在**每一个**受支持的 dsh 版本上验证、把它挂到 GitHub Release,并把**这个产物本身**发布到 npm——因此 Release 附件与 npm 上的 tarball 是同一个文件,而不是两次恰好一致的构建。
|
|
166
|
+
|
|
167
|
+
**从 npm 安装**(v0.1.4+ 自带 `dsh.bundle` manifest,bundle patch 会自动插入 provider 行,无需手动改 patch):
|
|
57
168
|
|
|
58
169
|
```powershell
|
|
59
170
|
dsh plugin --profile web add @tonydua/dsh-web-search-exa
|
|
60
171
|
```
|
|
61
172
|
|
|
62
|
-
|
|
173
|
+
**从 GitHub Release 安装**——同一份 tarball,npm 不可达时用:
|
|
174
|
+
|
|
175
|
+
```powershell
|
|
176
|
+
dsh plugin --profile web add https://github.com/TonyDua/dsh-web-search-exa/releases/latest/download/dsh-web-search-exa.tgz
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**从仓库安装**(跟随 `main`,包含尚未发布的改动):
|
|
180
|
+
|
|
181
|
+
```powershell
|
|
182
|
+
dsh plugin --profile web add github:TonyDua/dsh-web-search-exa
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
重启 `dsh web` 生效。**无 API key 时**官方 DeepSeek 搜索提供方不可用,seam 会自动选中本插件——完全零配置。**配了 key 时**,需在你的 `$DSH_HOME/profiles/web/cordis.patch.yml`(在 bundle patch 之后应用)里显式选中 Exa:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
- id: web
|
|
189
|
+
name: '@deepseek-ai/dsh-web'
|
|
190
|
+
config:
|
|
191
|
+
searchProvider: exa
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
…或用环境变量 `$DSH_WEB_SEARCH_PROVIDER=exa` 在运行时选中。
|
|
63
195
|
|
|
64
196
|
**本地开发目录:**
|
|
65
197
|
|
|
@@ -95,7 +227,8 @@ dsh plugin --profile web add ../plugins/dsh-web-search-exa
|
|
|
95
227
|
| `providerId` | `exa` | 注册进 `ctx.web` 的提供方 id。仅当本包与官方包同时安装时才需要改(见下一节)。 |
|
|
96
228
|
| `apiKey` | 未设置 | Exa API 密钥字面值。为空/缺失时启用匿名 MCP 路径。 |
|
|
97
229
|
| `apiKeyEnv` | `EXA_API_KEY` | 未设置字面 `apiKey` 时读取的环境变量名。 |
|
|
98
|
-
| `
|
|
230
|
+
| `baseURL` | `https://api.exa.ai` | Exa API 基础 URL;带 key 的 REST 路径会追加 `/search`,与官方 dsh 提供方一致。 |
|
|
231
|
+
| `apiURL` | 未设置 | 已弃用的完整 REST 端点别名;设置后优先于 `baseURL`。 |
|
|
99
232
|
| `mcpURL` | `https://mcp.exa.ai/mcp` | Exa 托管 MCP 端点(匿名路径使用)。 |
|
|
100
233
|
| `searchType` | `auto` | REST 检索模式:`auto` / `keyword` / `neural`。 |
|
|
101
234
|
| `numResults` | 未设置 | 请求未携带 `maxResults` 时的默认结果数。 |
|
|
@@ -130,7 +263,7 @@ dsh plugin --profile web add ../plugins/dsh-web-search-exa
|
|
|
130
263
|
**状态:本版本的配置入口在 profile 补丁层,不在 Web UI —— 没有可编辑的界面入口。** Settings UI 只渲染客户端插件为固定命名空间(`shell`、`agent-loop`、`web-search-deepseek`)手工注册的卡片,对任意插件命名空间没有通用表单。当前实际情况:
|
|
131
264
|
|
|
132
265
|
- **插件清单**(Settings → Plugins):启用后自动出现 `web-search-exa`(`@tonydua/dsh-web-search-exa`)条目 —— 清单直接读取 Cordis loader 的实时条目,无需额外代码。
|
|
133
|
-
-
|
|
266
|
+
- **设置命名空间**(服务端):插件通过当前的 `ctx.settings.installSection` API 注册了 `web-search-exa` 段,数据层可写——但**没有任何客户端卡片绑定它**,所以界面上不显示。内置的 "Web search" 卡片编辑的是官方 `web-search-deepseek` 命名空间,与本插件无关。
|
|
134
267
|
- **现在怎么改配置**:编辑 `$DSH_HOME/profiles/web/cordis.patch.yml` 里本插件的 `config`(字段与默认值见上方配置表),重启 `dsh web`;或用环境变量 `EXA_API_KEY` / `$DSH_WEB_SEARCH_PROVIDER`。`apiKey` 标记了 `role('secret')`,任何 `describe()` 响应都不会暴露其值。
|
|
135
268
|
- **搜索结果卡片**:`web_search` 调用经 `dsh-tool-web` 照常渲染 `web` 结果卡片(来源、摘要、日期),与提供方无关 —— 匿名 Exa 的结果与 DeepSeek 搜索显示完全一致。
|
|
136
269
|
|
|
@@ -150,6 +283,9 @@ dsh plugin --profile web add ../plugins/dsh-web-search-exa
|
|
|
150
283
|
**Q: 为什么 Web UI 里没有设置入口?**
|
|
151
284
|
本版本只在服务端注册了 `web-search-exa` 设置命名空间;UI 卡片计划在下一版本提供。现阶段通过 `cordis.patch.yml` 或环境变量配置(见[在 Web 面板中的呈现](#在-web-面板中的呈现))。
|
|
152
285
|
|
|
286
|
+
**Q: 支持哪些 dsh 版本?**
|
|
287
|
+
`0.1.2-alpha.2` 到 `0.1.7-alpha.1` 之间**每一个**已发布的 dsh 版本,以及未来的 `0.1.8+` 稳定版。每个版本都会在 CI 里被独立安装、用该版本自己的类型声明做类型检查,并跑一遍本包的测试套件——见[为什么 peer 范围长这样](#为什么-peer-范围长这样)。
|
|
288
|
+
|
|
153
289
|
## 致谢(Acknowledgements)
|
|
154
290
|
|
|
155
291
|
匿名 MCP 接入方式参考了 [can1357/oh-my-pi](https://github.com/can1357/oh-my-pi) 的 `web_search` 实现(`packages/coding-agent/src/web/search/providers/exa.ts` 与 `src/exa/mcp-client.ts`)以及 [`@oh-my-pi/exa`](https://www.npmjs.com/package/@oh-my-pi/exa) 插件:同样的"有 key 走 REST、无 key 走免凭据 `mcp.exa.ai/mcp`"策略、同样的 `x-exa-source` 来源头、同样的 `Title:` 分节响应解析。感谢 oh-my-pi(omp)项目率先打通了零配置的 Exa 接入。
|
package/cordis.patch.yml
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# The dsh-web-search-exa bundle patch.
|
|
2
2
|
#
|
|
3
|
-
# Inserts the zero-config Exa search provider into the profile
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
3
|
+
# Inserts the zero-config Exa search provider into the profile. It must NOT
|
|
4
|
+
# re-define rows owned by official bundle patches (e.g. dsh-base's `- id: web`
|
|
5
|
+
# row): duplicate ids inside the bundle group fail the loader, while the
|
|
6
|
+
# user's own profile cordis.patch.yml (applied last) may override rows by id.
|
|
7
|
+
#
|
|
8
|
+
# Selection: with no API key the official deepseek provider is unavailable, so
|
|
9
|
+
# the seam auto-selects this provider (zero-config). Keyed users who prefer Exa
|
|
10
|
+
# select it explicitly in their own profile patch or via
|
|
11
|
+
# DSH_WEB_SEARCH_PROVIDER=exa (see README).
|
|
8
12
|
- insert:
|
|
9
13
|
- id: web-search-exa
|
|
10
14
|
name: '@tonydua/dsh-web-search-exa'
|
|
11
15
|
config:
|
|
12
16
|
apiKeyEnv: EXA_API_KEY
|
|
13
17
|
# providerId: exa-anon # uncomment to coexist with the official package
|
|
14
|
-
|
|
15
|
-
- id: web
|
|
16
|
-
name: '@deepseek-ai/dsh-web'
|
|
17
|
-
config:
|
|
18
|
-
searchProvider: exa
|