deepseek-web-retrieval 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +95 -42
- package/dist/cli.js +397 -307
- package/docs/agents/codex.md +15 -8
- package/docs/agents/opencode-v2.md +12 -6
- package/docs/agents/pi.md +14 -6
- package/docs/design/installer-design.md +25 -20
- package/docs/integration.md +38 -7
- package/docs/research/control-matrix.md +2 -2
- package/docs/research/mcp-compatibility.md +5 -1
- package/package.json +1 -1
- package/src/installer/apply.ts +5 -3
- package/src/installer/cli.ts +43 -10
- package/src/installer/state.ts +1 -0
- package/src/installer/types.ts +2 -0
- package/src/mcp/cli.ts +8 -51
- package/src/mcp/serve.ts +51 -0
- package/src/version.ts +12 -0
package/docs/agents/codex.md
CHANGED
|
@@ -27,7 +27,8 @@ For shared search/fetch limits, retries, and privacy behavior, see
|
|
|
27
27
|
|
|
28
28
|
## Requirements
|
|
29
29
|
|
|
30
|
-
- Codex
|
|
30
|
+
- Codex at the tested version in [`docs/hosts.jsonc`](../hosts.jsonc); init reports
|
|
31
|
+
version drift, and newer versions may require rechecking routing behavior
|
|
31
32
|
- Node.js 20 or newer (`npx` runs the server)
|
|
32
33
|
- A DeepSeek account with access to `/v1/tools/search` and `/v1/tools/fetch`
|
|
33
34
|
- A funded DeepSeek account; Search and Fetch are billed per successful request
|
|
@@ -43,9 +44,9 @@ npx deepseek-web-retrieval init --host codex --scope global \
|
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
It prints the full plan — every file, old value → new value, and notes about existing
|
|
46
|
-
MCP servers — and writes nothing until you pass `--apply`. `uninstall`
|
|
47
|
-
|
|
48
|
-
`~/.codex/config.toml` at the user level only, so the server registration and kill switch
|
|
47
|
+
MCP servers — and writes nothing until you pass `--apply`. `uninstall` removes recorded
|
|
48
|
+
additions and restores snapshotted values that have not since been edited. Codex's config
|
|
49
|
+
lives in `~/.codex/config.toml` at the user level only, so the server registration and kill switch
|
|
49
50
|
are written there even for `--scope project`; the rules file follows the scope. An `off`
|
|
50
51
|
capability is written as the tool's name in the server entry's `disabled_tools` array —
|
|
51
52
|
Codex's own mechanism — merging with any entries you put there yourself. When `codex` is
|
|
@@ -54,6 +55,10 @@ release differs from the tested lock (see
|
|
|
54
55
|
[hosts.jsonc](../hosts.jsonc)) — version-anchored behavior such as the kill switch may
|
|
55
56
|
have drifted between the two.
|
|
56
57
|
|
|
58
|
+
For an existing installer-managed setup, follow the
|
|
59
|
+
[upgrade instructions](../integration.md#upgrading-existing-installs) from the original
|
|
60
|
+
project directory and scope.
|
|
61
|
+
|
|
57
62
|
### Manual install
|
|
58
63
|
|
|
59
64
|
Add the server to `~/.codex/config.toml`:
|
|
@@ -149,9 +154,9 @@ defaults and clamping match the other adapters. `DEEPSEEK_API_KEY` goes in `env_
|
|
|
149
154
|
| `DEEPSEEK_ENABLE_SEARCH` | `true` | Register the search tool. Only an explicit `false`/`0`/`no`/`off` disables it; at least one tool must stay enabled. |
|
|
150
155
|
| `DEEPSEEK_SEARCH_COUNT` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
|
|
151
156
|
| `DEEPSEEK_SEARCH_MAX_RESULTS` | `10` | Results returned to the model after filtering, 0–50. `0` returns no results. |
|
|
152
|
-
| `DEEPSEEK_SEARCH_MIN_RERANK` | `0` | Drop results below this relevance score
|
|
157
|
+
| `DEEPSEEK_SEARCH_MIN_RERANK` | `0` | Drop results below this relevance score (0–3); results without a finite score pass at zero and are dropped at a positive threshold. |
|
|
153
158
|
| `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `0` | Drop known authorities below this tier. Authority is 0–4. |
|
|
154
|
-
| `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `true` | Keep results whose authority is missing or unknown. Set `false`/`0` to drop them. |
|
|
159
|
+
| `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `true` | Keep results whose authority is missing or unknown. Set `false`/`0`/`no`/`off` to drop them. |
|
|
155
160
|
| `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | unset | Comma-separated; adds `-site:` operators to the query. |
|
|
156
161
|
| `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | unset | Comma-separated; restricts results with `site:` operators. |
|
|
157
162
|
| `DEEPSEEK_SEARCH_LANGUAGE` | unset | Adds a `lang:` operator, for example `zh,en`. |
|
|
@@ -208,7 +213,9 @@ Tested with Codex 0.163.0 on Linux over stdio.
|
|
|
208
213
|
npx deepseek-web-retrieval uninstall --host codex --scope global
|
|
209
214
|
```
|
|
210
215
|
|
|
211
|
-
This restores
|
|
212
|
-
|
|
216
|
+
This restores snapshotted `web_search` and `mcp_servers.deepseek` values only when their
|
|
217
|
+
current values still match what the installer wrote, removes the managed rules block,
|
|
218
|
+
and deletes installer-created config files only when left empty. Later user edits to
|
|
219
|
+
configuration values are kept and reported. For a manual installation, remove the
|
|
213
220
|
`[mcp_servers.deepseek]` table, any `deepseek-web-retrieval` rules block in your
|
|
214
221
|
`AGENTS.md`, and the top-level `web_search` key if it was only set for the forced tier.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
OpenCode v2 adapter for [DeepSeek Web Retrieval](../../README.md).
|
|
4
4
|
|
|
5
|
-
Integration mechanism: OpenCode's native plugin API (`@opencode/plugin`
|
|
5
|
+
Integration mechanism: OpenCode's native plugin API (`@opencode/plugin`), not MCP.
|
|
6
6
|
|
|
7
7
|
When loaded, it adds:
|
|
8
8
|
|
|
@@ -18,7 +18,9 @@ For shared search/fetch limits, retries, and privacy behavior, see
|
|
|
18
18
|
|
|
19
19
|
## Requirements
|
|
20
20
|
|
|
21
|
-
- OpenCode v2 with `@opencode/plugin` 2.0.32
|
|
21
|
+
- OpenCode v2 with `@opencode/plugin` in the `^2.0.32` peer range; the tested version
|
|
22
|
+
is recorded in [`docs/hosts.jsonc`](../hosts.jsonc)
|
|
23
|
+
- Node.js 20+ and npm when using the installer CLI
|
|
22
24
|
- A DeepSeek account with access to `/v1/tools/search` and `/v1/tools/fetch`
|
|
23
25
|
- A funded DeepSeek account; Search and Fetch are billed per successful request
|
|
24
26
|
- A DeepSeek credential stored in OpenCode, or `DEEPSEEK_API_KEY`
|
|
@@ -38,8 +40,9 @@ npx deepseek-web-retrieval init --host opencode --scope <project|global> \
|
|
|
38
40
|
It prints the full plan — every file, old value → new value — and writes nothing until
|
|
39
41
|
you pass `--apply`. It snapshots the current `websearch.provider` before the forced tier
|
|
40
42
|
replaces it, merges `permissions` without touching yours, preserves comments in
|
|
41
|
-
`opencode.jsonc`, and `uninstall` restores
|
|
42
|
-
OpenCode (the provider is host-level config, never a
|
|
43
|
+
`opencode.jsonc`, and `uninstall` restores recorded values that have not since been edited.
|
|
44
|
+
Search has no `preferred` tier on OpenCode (the provider is host-level config, never a
|
|
45
|
+
model choice); the CLI rejects it.
|
|
43
46
|
|
|
44
47
|
### OpenCode CLI install
|
|
45
48
|
|
|
@@ -100,9 +103,12 @@ A complete global config:
|
|
|
100
103
|
Use the bare package name (optionally pinned) as the `package` value:
|
|
101
104
|
|
|
102
105
|
```jsonc
|
|
103
|
-
"plugins": [{ "package": "deepseek-web-retrieval
|
|
106
|
+
"plugins": [{ "package": "deepseek-web-retrieval" }]
|
|
104
107
|
```
|
|
105
108
|
|
|
109
|
+
Append `@X.Y.Z` to pin a published version. For an existing installer-managed setup,
|
|
110
|
+
follow the [upgrade instructions](../integration.md#upgrading-existing-installs).
|
|
111
|
+
|
|
106
112
|
**Global checkout**
|
|
107
113
|
|
|
108
114
|
```bash
|
|
@@ -234,7 +240,7 @@ using both an OpenCode-stored DeepSeek credential and `DEEPSEEK_API_KEY`.
|
|
|
234
240
|
## Uninstall
|
|
235
241
|
|
|
236
242
|
With the installer CLI (restores the snapshotted `websearch.provider`, removes only the
|
|
237
|
-
appended permissions, and deletes files
|
|
243
|
+
appended permissions, and deletes installer-created files only when they are left empty):
|
|
238
244
|
|
|
239
245
|
```bash
|
|
240
246
|
npx deepseek-web-retrieval uninstall --host opencode --scope <project|global>
|
package/docs/agents/pi.md
CHANGED
|
@@ -25,7 +25,9 @@ shared search/fetch limits, retries, and privacy behavior, see
|
|
|
25
25
|
|
|
26
26
|
## Requirements
|
|
27
27
|
|
|
28
|
-
- Pi 1.3.0
|
|
28
|
+
- Pi with host SDKs in the `^1.3.0` peer range (`>=1.3.0 <2.0.0`); the tested
|
|
29
|
+
version is recorded in [`docs/hosts.jsonc`](../hosts.jsonc)
|
|
30
|
+
- Node.js 20+ and npm when using the installer CLI or MCP fallback
|
|
29
31
|
- A DeepSeek account with access to `/v1/tools/search` and `/v1/tools/fetch`
|
|
30
32
|
- A funded DeepSeek account; Search and Fetch are billed per successful request
|
|
31
33
|
- `DEEPSEEK_API_KEY` set in the shell that launches Pi
|
|
@@ -46,8 +48,12 @@ the extension: the plan appends `npm:deepseek-web-retrieval` to the `packages` a
|
|
|
46
48
|
source at the next session start (the first launch fetches the package from npm;
|
|
47
49
|
`pi update --extensions` refreshes it later). An off capability is written into the
|
|
48
50
|
`deepseekWebRetrieval` settings namespace the extension reads at load. Pass `--path mcp`
|
|
49
|
-
to install the [MCP fallback path](#the-mcp-fallback-path) instead. `uninstall`
|
|
50
|
-
|
|
51
|
+
to install the [MCP fallback path](#the-mcp-fallback-path) instead. `uninstall` removes
|
|
52
|
+
recorded additions and restores unchanged configuration values from their snapshots.
|
|
53
|
+
|
|
54
|
+
For upgrades from 0.4.0, select `--path mcp` explicitly to retain the old path, or use
|
|
55
|
+
the extension default to migrate; follow the shared
|
|
56
|
+
[upgrade instructions](../integration.md#upgrading-existing-installs).
|
|
51
57
|
|
|
52
58
|
### Manual install
|
|
53
59
|
|
|
@@ -110,9 +116,9 @@ it outranks the environment and is preserved by Pi's own settings rewrites).
|
|
|
110
116
|
| `DEEPSEEK_ENABLE_SEARCH` | `true` | Register the search tool. Only an explicit `false`/`0`/`no`/`off` disables it; at least one tool must stay enabled. |
|
|
111
117
|
| `DEEPSEEK_SEARCH_COUNT` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
|
|
112
118
|
| `DEEPSEEK_SEARCH_MAX_RESULTS` | `10` | Results returned to the model after filtering, 0–50. `0` returns no results. |
|
|
113
|
-
| `DEEPSEEK_SEARCH_MIN_RERANK` | `0` | Drop results below this relevance score
|
|
119
|
+
| `DEEPSEEK_SEARCH_MIN_RERANK` | `0` | Drop results below this relevance score (0–3); results without a finite score pass at zero and are dropped at a positive threshold. |
|
|
114
120
|
| `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `0` | Drop known authorities below this tier. Authority is 0–4. |
|
|
115
|
-
| `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `true` | Keep results whose authority is missing or unknown. Set `false`/`0` to drop them. |
|
|
121
|
+
| `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `true` | Keep results whose authority is missing or unknown. Set `false`/`0`/`no`/`off` to drop them. |
|
|
116
122
|
| `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | unset | Comma-separated; adds `-site:` operators to the query. |
|
|
117
123
|
| `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | unset | Comma-separated; restricts results with `site:` operators. |
|
|
118
124
|
| `DEEPSEEK_SEARCH_LANGUAGE` | unset | Adds a `lang:` operator, for example `zh,en`. |
|
|
@@ -175,7 +181,9 @@ With the installer CLI:
|
|
|
175
181
|
npx deepseek-web-retrieval uninstall --host pi --scope <project|global>
|
|
176
182
|
```
|
|
177
183
|
|
|
178
|
-
|
|
184
|
+
For a manual installation, remove the source with Pi itself; use `--local` for a
|
|
185
|
+
project-local install. Installer-managed installs should use `uninstall` above so
|
|
186
|
+
rules, snapshots, and install records are cleaned up as well:
|
|
179
187
|
|
|
180
188
|
```bash
|
|
181
189
|
pi remove npm:deepseek-web-retrieval
|
|
@@ -6,6 +6,8 @@ Design note for the install-time flow, shipped as the `deepseek-web-retrieval in
|
|
|
6
6
|
the design survives between conversations. Research backing lives in
|
|
7
7
|
[control-matrix.md](../research/control-matrix.md); host facts in [hosts.jsonc](../hosts.jsonc).
|
|
8
8
|
Remaining hosts get host modules here as their guides ship.
|
|
9
|
+
Examples involving other hosts describe design requirements for future modules;
|
|
10
|
+
the current CLI accepts only the three shipped hosts above.
|
|
9
11
|
|
|
10
12
|
## Principles
|
|
11
13
|
|
|
@@ -27,17 +29,16 @@ of the whole install.
|
|
|
27
29
|
- **Optional** — registered, the model picks freely.
|
|
28
30
|
- **Preferred** — registered plus rules steering the model toward them.
|
|
29
31
|
- **Forced** — deterministic routing (replacement, exclusion, or de-facto).
|
|
30
|
-
- **Off** — the capability
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
refuses to install anything.
|
|
32
|
+
- **Off** — the DeepSeek capability is hidden from the model through the adapter's
|
|
33
|
+
registration switch or Codex's host-side `disabled_tools` filter; the mapping below
|
|
34
|
+
describes each path. Both off refuses to install anything.
|
|
34
35
|
- **Scope** — project-level (this checkout, shareable via VCS) or global (every project
|
|
35
36
|
for that host on the machine).
|
|
36
37
|
|
|
37
38
|
## Stock configuration
|
|
38
39
|
|
|
39
40
|
The tier definitions assume a clean host. Real installs land on configuration that
|
|
40
|
-
already exists, and the installer
|
|
41
|
+
already exists, and the installer uses three phases:
|
|
41
42
|
|
|
42
43
|
1. **Detect.** Read the host config, the rules files, and the registered MCP servers.
|
|
43
44
|
Report the conflict surface: existing rules steering toward another web tool; other
|
|
@@ -50,14 +51,15 @@ already exists, and the installer runs three phases before anything is written:
|
|
|
50
51
|
which entries the installer appended; single-value keys (`websearch.provider`, Pi's
|
|
51
52
|
`exposure`) snapshot the prior value before overwriting; files the installer creates
|
|
52
53
|
are tracked as created. Nothing is written until the user approves the full plan.
|
|
53
|
-
3. **Verify.** After applying,
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
3. **Verify.** After applying, re-read the files and confirm the planned values and rules
|
|
55
|
+
were written. Runtime tool visibility and model routing are checked by host-specific
|
|
56
|
+
end-to-end scenarios, not by the installer itself.
|
|
56
57
|
|
|
57
58
|
## Tier semantics against stock configuration
|
|
58
59
|
|
|
59
|
-
- **Optional
|
|
60
|
-
without
|
|
60
|
+
- **Optional keeps built-in routing unchanged**: it registers the DeepSeek capability
|
|
61
|
+
without preference rules or competitor exclusion. An existing DeepSeek registration
|
|
62
|
+
may still need replacement, which the plan shows and snapshots; inspect the preview.
|
|
61
63
|
- **Preferred** can conflict without any config being touched: existing rules pointing
|
|
62
64
|
at another web tool produce contradictory instructions whose outcome depends on each
|
|
63
65
|
host's load order (Claude Code concatenates every level; Devin ranks team above
|
|
@@ -101,16 +103,17 @@ settings hierarchy, OpenCode's global versus project config) under three rules:
|
|
|
101
103
|
alternative — the global fallback for a kill switch, or a different tier that works
|
|
102
104
|
at project scope — instead of quietly writing to the other scope.
|
|
103
105
|
- **Blast radius is part of the plan.** Global scope changes every project the user
|
|
104
|
-
opens with that host; project scope
|
|
105
|
-
|
|
106
|
+
opens with that host; project scope normally writes into one checkout, while shared
|
|
107
|
+
config fallbacks such as Codex's retain user-wide effects. Project-local artifacts are
|
|
108
|
+
often committed, which shares the config and rules with the whole team — including the
|
|
106
109
|
`${DEEPSEEK_API_KEY}` expansion that relies on each member's environment. The impact
|
|
107
110
|
report states which applies.
|
|
108
111
|
- **Layers above the user still outrank the choice.** Managed, enterprise, and system
|
|
109
112
|
layers override anything the installer writes at either scope; the detect phase
|
|
110
113
|
reports them instead of failing silently.
|
|
111
114
|
|
|
112
|
-
Uninstall
|
|
113
|
-
|
|
115
|
+
Uninstall follows the files recorded for the chosen scope, including Codex's user-level
|
|
116
|
+
config even for a project-scope install; list shows those actual paths. The invariant is
|
|
114
117
|
guarded, not just assumed: two records for one host never claim the same file. When a
|
|
115
118
|
scope choice would overlap the other scope's record — Codex's project-scope fallback
|
|
116
119
|
onto `~/.codex/config.toml` beside a global install — the plan refuses with guidance,
|
|
@@ -133,10 +136,13 @@ Do not delete an ownership sidecar while its install is still active.
|
|
|
133
136
|
|
|
134
137
|
## Rollback
|
|
135
138
|
|
|
136
|
-
Uninstall
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
Uninstall restores overwritten values from their snapshots only while the current value
|
|
140
|
+
still matches what the installer wrote; later user edits are kept and reported. Merged
|
|
141
|
+
arrays lose only matching entries the installer appended, and managed rules blocks are
|
|
142
|
+
removed while surrounding user content is retained. Installer-created config files are
|
|
143
|
+
deleted only when left empty; an empty rules file is also removed. TOML comments are not
|
|
144
|
+
restored because the codec reserializes the document. The
|
|
145
|
+
[OpenCode v2 guide](../agents/opencode-v2.md) documents the failure mode this
|
|
140
146
|
avoids: `plugin remove` deletes the plugin entry but leaves `websearch.provider`
|
|
141
147
|
pointing at the removed plugin, and the user must clean up by hand.
|
|
142
148
|
|
|
@@ -215,8 +221,7 @@ host module; their artifacts (from the tier-to-artifact mapping) remain hand-edi
|
|
|
215
221
|
config until a module lands here. OpenCode's `opencode plugin add` remains available but
|
|
216
222
|
cannot pass options or manage snapshots; `init` supersedes it for tiered installs.
|
|
217
223
|
|
|
218
|
-
|
|
219
|
-
### Persistence and concurrent operations
|
|
224
|
+
## Persistence and concurrent operations
|
|
220
225
|
|
|
221
226
|
Install records distinguish absent files from invalid or unreadable files. The
|
|
222
227
|
reader validates version, host/scope identity, tiers, file metadata, and recorded
|
package/docs/integration.md
CHANGED
|
@@ -48,11 +48,11 @@ Codex are Apache-2.0; OpenCode and Pi are MIT. Claude Code and
|
|
|
48
48
|
Cursor are proprietary. GitHub Copilot CLI and Devin CLI publish installers and docs, not
|
|
49
49
|
source code, under proprietary terms.
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`No adapter yet` means
|
|
55
|
-
|
|
51
|
+
OpenCode, Pi, and Codex have supported setup guides and installer host modules. The
|
|
52
|
+
MCP server in `src/mcp/` provides a possible stdio path for the remaining hosts, but
|
|
53
|
+
protocol compatibility alone does not establish a tested host setup. For those rows,
|
|
54
|
+
`No adapter yet` means no supported host-specific adapter or setup guide is shipped.
|
|
55
|
+
All version locks live in [hosts.jsonc](hosts.jsonc), the single
|
|
56
56
|
source of truth for this table; OpenCode is pinned to the adapter's tested target, not
|
|
57
57
|
necessarily the latest OpenCode release. Control tiers per host and capability — optional,
|
|
58
58
|
preferred, or forced routing — are researched in [control-matrix.md](research/control-matrix.md),
|
|
@@ -63,7 +63,7 @@ whose per-host reference also carries MCP registration and rules files.
|
|
|
63
63
|
One line per host. The [per-host reference](research/control-matrix.md#per-host-reference) carries
|
|
64
64
|
the details: built-in web tools and their kill switches, MCP registration, and rules files.
|
|
65
65
|
|
|
66
|
-
- **OpenCode v2** —
|
|
66
|
+
- **OpenCode v2** — Uses OpenCode's native plugin API and
|
|
67
67
|
exposes both a websearch provider and a fetch tool/override. See
|
|
68
68
|
[OpenCode v2](agents/opencode-v2.md).
|
|
69
69
|
- **Claude Code** — Ships built-in `WebSearch` and `WebFetch` tools. See
|
|
@@ -82,7 +82,38 @@ the details: built-in web tools and their kill switches, MCP registration, and r
|
|
|
82
82
|
the native extension adapter (the recommended path), with the MCP server as the fallback;
|
|
83
83
|
see [Pi](agents/pi.md) and [Pi in the per-host reference](research/control-matrix.md#pi).
|
|
84
84
|
|
|
85
|
-
##
|
|
85
|
+
## Upgrading existing installs
|
|
86
|
+
|
|
87
|
+
For an installation managed by `init`:
|
|
88
|
+
|
|
89
|
+
1. Run `npx deepseek-web-retrieval@latest list` from the original project directory,
|
|
90
|
+
using the same home and `XDG_CONFIG_HOME` as the original installation.
|
|
91
|
+
2. Run `npx deepseek-web-retrieval@latest init` with the original `--host`, `--scope`,
|
|
92
|
+
`--search`, and `--fetch` values, changing only the settings you intend to change.
|
|
93
|
+
Review the plan and repeat with `--apply`; re-init preserves the original snapshots
|
|
94
|
+
needed for uninstall. A global install can still claim shared configuration from a
|
|
95
|
+
particular project, so keep the original working directory for upgrades.
|
|
96
|
+
3. Refresh the package in the host and start or reload a session as described in its
|
|
97
|
+
guide. For Pi's extension path, use `pi update --extensions`. If you pin the host's
|
|
98
|
+
package version, pass the desired `--package deepseek-web-retrieval@X.Y.Z` to init;
|
|
99
|
+
updating the CLI alone does not update an independently pinned host package.
|
|
100
|
+
|
|
101
|
+
When upgrading from 0.4.0, Pi's default changes from MCP to the native extension.
|
|
102
|
+
Pass `--path mcp` explicitly to retain MCP, or `--path extension` to migrate; the plan
|
|
103
|
+
shows the old path's removals and retains the original uninstall snapshots. OpenCode's
|
|
104
|
+
native plugin and Codex's MCP path remain the same. Standalone MCP installs no longer
|
|
105
|
+
require OpenCode or Pi SDKs; native adapters use the SDKs provided by their hosts.
|
|
106
|
+
|
|
107
|
+
Keep install records and ownership sidecars: deleting them loses recovery information
|
|
108
|
+
and can cause a later init to refuse an existing registration. If an operation was
|
|
109
|
+
interrupted, follow the [recovery guidance](design/installer-design.md#persistence-and-concurrent-operations)
|
|
110
|
+
before retrying. For manual installations, update the package reference or checkout and
|
|
111
|
+
follow the host guide rather than adopting the config through init.
|
|
112
|
+
|
|
113
|
+
Code importing the core's removed `formatFetchContent` helper must migrate to
|
|
114
|
+
`formatFetchedContent`, which returns both `content` and `truncated`.
|
|
115
|
+
|
|
116
|
+
## Available paths and future hosts
|
|
86
117
|
|
|
87
118
|
- **MCP server** — shipped in `src/mcp/` (the fallback path in the [Pi](agents/pi.md) guide
|
|
88
119
|
and the path in the [Codex](agents/codex.md) guide); the broadest route for hosts that
|
|
@@ -181,8 +181,8 @@ paths imply the available scopes; keys locked to a single scope are enumerated i
|
|
|
181
181
|
prerequisite for every tier. SSE is rejected. Text results over 20 KB are
|
|
182
182
|
middle-truncated with the full text saved to a file, and the per-request default
|
|
183
183
|
timeout is 60 s. Pi is content-first for the model, with a `structuredContent`
|
|
184
|
-
fallback used only when a result has no content blocks
|
|
185
|
-
|
|
184
|
+
fallback used only when a result has no content blocks, unlike Codex's preference for
|
|
185
|
+
`structuredContent`, and a re-declared (not validated) tool
|
|
186
186
|
`outputSchema`.
|
|
187
187
|
- **Rules** — `AGENTS.md`/`CLAUDE.md` context files from the agent dir, the working
|
|
188
188
|
directory, and its parents; `SYSTEM.md` replaces the system prompt and `APPEND_SYSTEM.md`
|
|
@@ -129,7 +129,7 @@ differences:
|
|
|
129
129
|
1.0.81+). A v1.x-built server still works today — modern clients fall back to the legacy
|
|
130
130
|
handshake — but it can never offer the stateless era; v2 serves both from one entry
|
|
131
131
|
point.
|
|
132
|
-
4. **Streamable HTTP only for a hosted deployment.** All
|
|
132
|
+
4. **Streamable HTTP only for a hosted deployment.** All seven MCP hosts surveyed here support it (with
|
|
133
133
|
different `type` strings: `http`, `streamable-http`, `streamableHttp`), and OAuth 2.1
|
|
134
134
|
resource-server behavior (RFC 9728 metadata) then applies; prefer pre-registered clients
|
|
135
135
|
over the now-deprecated Dynamic Client Registration.
|
|
@@ -139,6 +139,10 @@ differences:
|
|
|
139
139
|
truncation, environment passing, output caps, and per-host approval surfaces; consult
|
|
140
140
|
it per host rather than this summary.
|
|
141
141
|
|
|
142
|
+
These protocol findings describe compatibility at the research baseline, not completed
|
|
143
|
+
end-to-end support for every host. The shipped server exposes stdio; supported setup
|
|
144
|
+
guides and installer coverage are listed in the [integration model](../integration.md).
|
|
145
|
+
|
|
142
146
|
## Sources
|
|
143
147
|
|
|
144
148
|
### Specification and SDKs
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "deepseek-web-retrieval",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description": "Token-efficient DeepSeek Search and Fetch for coding agents, starting with OpenCode v2.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
package/src/installer/apply.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { TOOL_VERSION } from "../version";
|
|
3
4
|
import { codecFor, writeConfig } from "./formats";
|
|
4
5
|
import { deepEqual, valueAt } from "./formats/value";
|
|
5
6
|
import { withOperationLock } from "./operation-lock";
|
|
@@ -8,9 +9,9 @@ import { pruneEmptyDir, revertRecordedFile } from "./revert";
|
|
|
8
9
|
import { deleteRecord, readRecord, statePathFor, writeRecord } from "./state";
|
|
9
10
|
import type { ConfigOp, FileEdit, InstallerInput, InstallPlan, InstallRecord, RecordedFile } from "./types";
|
|
10
11
|
|
|
12
|
+
/** What a successful apply reports; every failure mode throws instead. */
|
|
11
13
|
export interface ApplyResult {
|
|
12
|
-
readonly lines: string[];
|
|
13
|
-
readonly ok: boolean;
|
|
14
|
+
readonly lines: readonly string[];
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/** A failed apply tells callers whether retrying is safe or recovery is still needed. */
|
|
@@ -94,6 +95,7 @@ function applyLocked(plan: InstallPlan, input: InstallerInput): ApplyResult {
|
|
|
94
95
|
lines.push(...verification.lines);
|
|
95
96
|
const record: InstallRecord = {
|
|
96
97
|
version: 1,
|
|
98
|
+
installerVersion: TOOL_VERSION,
|
|
97
99
|
host: plan.host,
|
|
98
100
|
scope: plan.scope,
|
|
99
101
|
tiers: plan.tiers,
|
|
@@ -135,7 +137,7 @@ function applyLocked(plan: InstallPlan, input: InstallerInput): ApplyResult {
|
|
|
135
137
|
error,
|
|
136
138
|
);
|
|
137
139
|
}
|
|
138
|
-
return { lines
|
|
140
|
+
return { lines };
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
function errorMessage(error: unknown): string {
|
package/src/installer/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { TOOL_VERSION } from "../version";
|
|
4
|
+
import { ApplyError, applyPlan } from "./apply";
|
|
4
5
|
import { HOST_PLANNERS, HOST_VERSION_PROBES, planForHost } from "./hosts";
|
|
5
6
|
import { renderPlan } from "./render";
|
|
6
7
|
import { listRecords } from "./state";
|
|
@@ -12,12 +13,17 @@ const HOST_CHOICES = `<${HOSTS.join("|")}>`;
|
|
|
12
13
|
const SCOPES: readonly Scope[] = ["project", "global"];
|
|
13
14
|
const TIERS: readonly Tier[] = ["optional", "preferred", "forced", "off"];
|
|
14
15
|
|
|
16
|
+
const EXIT_FAILURE = 1;
|
|
17
|
+
const EXIT_USAGE = 2;
|
|
18
|
+
const EXIT_RECOVERY = 3;
|
|
19
|
+
|
|
15
20
|
const USAGE = `Usage:
|
|
16
21
|
deepseek-web-retrieval init --host ${HOST_CHOICES} --scope <project|global> \
|
|
17
22
|
--search <optional|preferred|forced|off> --fetch <optional|preferred|forced|off> \
|
|
18
23
|
[--path <extension|mcp>] [--package <npm ref>] [--cwd <dir>] [--apply]
|
|
19
24
|
deepseek-web-retrieval uninstall --host ${HOST_CHOICES} --scope <project|global> [--cwd <dir>]
|
|
20
25
|
deepseek-web-retrieval list [--cwd <dir>]
|
|
26
|
+
deepseek-web-retrieval version
|
|
21
27
|
|
|
22
28
|
init prints the full plan (every file, old value → new value) and writes nothing
|
|
23
29
|
until --apply is passed. uninstall restores what the plan applied. --path picks
|
|
@@ -25,7 +31,12 @@ the integration mechanism for hosts with more than one (Pi: extension by
|
|
|
25
31
|
default, mcp for the stdio MCP server). --package overrides the package ref
|
|
26
32
|
written into host config; the Pi extension path turns it into the packages
|
|
27
33
|
source (a bare name is prefixed with npm:, a ref that already starts with npm:
|
|
28
|
-
is written as-is)
|
|
34
|
+
is written as-is).
|
|
35
|
+
|
|
36
|
+
Exit codes: 0 success; 1 refused or failed with nothing written (a failed apply
|
|
37
|
+
is fully rolled back); 2 invalid command line; 3 recovery required — a failed
|
|
38
|
+
apply could not roll back completely, follow the reported guidance before
|
|
39
|
+
re-running.`;
|
|
29
40
|
|
|
30
41
|
interface Flags {
|
|
31
42
|
readonly values: Map<string, string>;
|
|
@@ -54,6 +65,10 @@ function parseFlags(args: readonly string[]): Flags {
|
|
|
54
65
|
problems.push(`--${name} does not accept a value; use --${name} to enable it or omit it`);
|
|
55
66
|
continue;
|
|
56
67
|
}
|
|
68
|
+
if (booleans.has(name)) {
|
|
69
|
+
problems.push(`--${name} was given more than once`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
57
72
|
booleans.add(name);
|
|
58
73
|
continue;
|
|
59
74
|
}
|
|
@@ -61,6 +76,10 @@ function parseFlags(args: readonly string[]): Flags {
|
|
|
61
76
|
problems.push(`unknown flag --${name}`);
|
|
62
77
|
continue;
|
|
63
78
|
}
|
|
79
|
+
if (values.has(name)) {
|
|
80
|
+
problems.push(`--${name} was given more than once`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
64
83
|
if (eq !== -1) {
|
|
65
84
|
values.set(name, arg.slice(eq + 1));
|
|
66
85
|
continue;
|
|
@@ -87,15 +106,23 @@ export async function runInstallerCli(argv: readonly string[], io: CliIo = {}):
|
|
|
87
106
|
const out = io.out ?? ((line: string) => console.log(line));
|
|
88
107
|
const err = io.err ?? ((line: string) => console.error(line));
|
|
89
108
|
const [command, ...rest] = argv;
|
|
90
|
-
if (command ===
|
|
109
|
+
if (command === "version" || command === "--version") {
|
|
110
|
+
out(TOOL_VERSION);
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
if (command === undefined) {
|
|
114
|
+
err(USAGE);
|
|
115
|
+
return EXIT_USAGE;
|
|
116
|
+
}
|
|
117
|
+
if (command === "help" || command === "--help") {
|
|
91
118
|
out(USAGE);
|
|
92
|
-
return
|
|
119
|
+
return 0;
|
|
93
120
|
}
|
|
94
121
|
const flags = parseFlags(rest);
|
|
95
122
|
if (flags.problems.length > 0) {
|
|
96
123
|
for (const problem of flags.problems) err(`error: ${problem}`);
|
|
97
124
|
err(`\n${USAGE}`);
|
|
98
|
-
return
|
|
125
|
+
return EXIT_USAGE;
|
|
99
126
|
}
|
|
100
127
|
const cwd = resolve(flags.values.get("cwd") ?? process.cwd());
|
|
101
128
|
const home = process.env.HOME ?? homedir();
|
|
@@ -114,8 +141,9 @@ export async function runInstallerCli(argv: readonly string[], io: CliIo = {}):
|
|
|
114
141
|
return 0;
|
|
115
142
|
}
|
|
116
143
|
for (const record of records) {
|
|
144
|
+
const by = record.installerVersion ? ` by v${record.installerVersion}` : "";
|
|
117
145
|
out(
|
|
118
|
-
`${record.host} (${record.scope}) — search=${record.tiers.search}, fetch=${record.tiers.fetch}, installed ${record.createdAt}`,
|
|
146
|
+
`${record.host} (${record.scope}) — search=${record.tiers.search}, fetch=${record.tiers.fetch}, installed ${record.createdAt}${by}`,
|
|
119
147
|
);
|
|
120
148
|
for (const file of record.files) out(` ${file.created ? "created" : "modified"} ${file.file}`);
|
|
121
149
|
}
|
|
@@ -136,7 +164,7 @@ export async function runInstallerCli(argv: readonly string[], io: CliIo = {}):
|
|
|
136
164
|
|
|
137
165
|
if (command !== "init") {
|
|
138
166
|
err(`Unknown command "${command}".\n\n${USAGE}`);
|
|
139
|
-
return
|
|
167
|
+
return EXIT_USAGE;
|
|
140
168
|
}
|
|
141
169
|
|
|
142
170
|
const required = requireInput(flags, cwd, home, configHome, true);
|
|
@@ -158,17 +186,22 @@ export async function runInstallerCli(argv: readonly string[], io: CliIo = {}):
|
|
|
158
186
|
try {
|
|
159
187
|
const result = applyPlan(plan, input);
|
|
160
188
|
for (const line of result.lines) out(line);
|
|
161
|
-
return
|
|
189
|
+
return 0;
|
|
162
190
|
} catch (error) {
|
|
163
191
|
err(error instanceof Error ? error.message : String(error));
|
|
164
|
-
return
|
|
192
|
+
return exitCodeForError(error);
|
|
165
193
|
}
|
|
166
194
|
}
|
|
167
195
|
|
|
196
|
+
/** Maps a thrown error to its exit code; recovery is the case a script must not retry blindly. */
|
|
197
|
+
export function exitCodeForError(error: unknown): number {
|
|
198
|
+
return error instanceof ApplyError && error.status === "recovery_required" ? EXIT_RECOVERY : EXIT_FAILURE;
|
|
199
|
+
}
|
|
200
|
+
|
|
168
201
|
function usageError(problems: readonly string[], err: (line: string) => void): number {
|
|
169
202
|
for (const problem of problems) err(`error: ${problem}`);
|
|
170
203
|
err(`\n${USAGE}`);
|
|
171
|
-
return
|
|
204
|
+
return EXIT_USAGE;
|
|
172
205
|
}
|
|
173
206
|
|
|
174
207
|
/**
|
package/src/installer/state.ts
CHANGED
|
@@ -22,6 +22,7 @@ const operation = z.discriminatedUnion("op", [
|
|
|
22
22
|
]);
|
|
23
23
|
const recordSchema = z.object({
|
|
24
24
|
version: z.literal(1),
|
|
25
|
+
installerVersion: z.string().optional(),
|
|
25
26
|
host: z.enum(["opencode", "pi", "codex"]),
|
|
26
27
|
scope: z.enum(["global", "project"]),
|
|
27
28
|
tiers: z.object({ search: tier, fetch: tier }),
|
package/src/installer/types.ts
CHANGED
|
@@ -136,6 +136,8 @@ export interface RecordedFile {
|
|
|
136
136
|
/** Persisted install state; one record per host and scope. */
|
|
137
137
|
export interface InstallRecord {
|
|
138
138
|
readonly version: 1;
|
|
139
|
+
/** Version of the installer that wrote this record; absent on records from older releases. */
|
|
140
|
+
readonly installerVersion?: string;
|
|
139
141
|
readonly host: HostName;
|
|
140
142
|
readonly scope: Scope;
|
|
141
143
|
readonly tiers: { readonly search: Tier; readonly fetch: Tier };
|
package/src/mcp/cli.ts
CHANGED
|
@@ -1,57 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { createDeepSeekServer, servePrecheck } from "./server";
|
|
8
|
-
|
|
9
|
-
// Installer subcommands run before the server path; MCP clients spawn this bin
|
|
10
|
-
// with no arguments, so the default stdio behavior is unchanged.
|
|
11
|
-
const subcommand = process.argv[2];
|
|
12
|
-
if (subcommand === "init" || subcommand === "uninstall" || subcommand === "list") {
|
|
2
|
+
// Any argument means a human ran the CLI; MCP clients spawn this bin with no
|
|
3
|
+
// arguments for the stdio server. The installer is imported statically and the
|
|
4
|
+
// MCP server dynamically, so init/uninstall never load — or depend on — the
|
|
5
|
+
// server's dependency graph, including when recovering from a bad server setup.
|
|
6
|
+
if (process.argv.length > 2) {
|
|
13
7
|
const { runInstallerCli } = await import("../installer/cli");
|
|
14
8
|
process.exit(await runInstallerCli(process.argv.slice(2)));
|
|
15
9
|
}
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!apiKeyFromEnv(env)) {
|
|
20
|
-
console.error(MISSING_API_KEY_MESSAGE);
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const config = resolveEnvOptions(env);
|
|
25
|
-
const precheck = servePrecheck(config);
|
|
26
|
-
if (precheck) {
|
|
27
|
-
console.error(precheck);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
const handle = serveStdio(() => {
|
|
31
|
-
const server = createDeepSeekServer(config, env);
|
|
32
|
-
// Fires after the handshake-era initialize exchange (the stateless era opens
|
|
33
|
-
// differently and simply never logs). One stderr line so a human run shows
|
|
34
|
-
// which client connected and whether text-only shaping kicked in; if a host
|
|
35
|
-
// renames its clientInfo, this line is where the regression becomes visible.
|
|
36
|
-
server.server.oninitialized = () => {
|
|
37
|
-
const info = server.server.getClientVersion();
|
|
38
|
-
if (info === undefined) return;
|
|
39
|
-
console.error(
|
|
40
|
-
`mcp client: ${info.name} ${info.version ?? "unknown"} — ` +
|
|
41
|
-
(isJsonReplacingClient(info.name)
|
|
42
|
-
? "text-only results (this client replaces content with serialized structuredContent)"
|
|
43
|
-
: "text + structuredContent results"),
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
return server;
|
|
47
|
-
});
|
|
48
|
-
const tools = [
|
|
49
|
-
config.search.enabled ? `search (max ${config.search.maxResults} results)` : null,
|
|
50
|
-
config.fetch.enabled ? `fetch (max ${config.fetch.maxChars} chars)` : null,
|
|
51
|
-
]
|
|
52
|
-
.filter(Boolean)
|
|
53
|
-
.join(", ");
|
|
54
|
-
console.error(`deepseek-web-retrieval MCP server on stdio (${tools})`);
|
|
11
|
+
const { serveMcp } = await import("./serve");
|
|
12
|
+
serveMcp();
|
|
55
13
|
|
|
56
|
-
|
|
57
|
-
process.on("SIGTERM", () => void handle.close());
|
|
14
|
+
export {};
|