deepseek-web-retrieval 0.3.0 → 0.5.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +96 -230
  2. package/README.md +53 -25
  3. package/dist/cli.js +1358 -617
  4. package/docs/agents/codex.md +32 -11
  5. package/docs/agents/opencode-v2.md +5 -5
  6. package/docs/agents/pi.md +92 -90
  7. package/docs/design/installer-design.md +72 -9
  8. package/docs/hosts.jsonc +2 -2
  9. package/docs/integration.md +14 -9
  10. package/docs/research/control-matrix.md +48 -13
  11. package/docs/research/mcp-compatibility.md +9 -4
  12. package/docs/research/search-api-defaults.md +309 -0
  13. package/examples/pi.settings.jsonc +17 -0
  14. package/index.ts +2 -2
  15. package/package.json +35 -4
  16. package/src/core/auth.ts +4 -6
  17. package/src/core/client.ts +65 -53
  18. package/src/core/constants.ts +30 -24
  19. package/src/core/descriptions.ts +30 -6
  20. package/src/core/domains.ts +56 -67
  21. package/src/core/env.ts +112 -0
  22. package/src/core/format.ts +53 -50
  23. package/src/core/index.ts +9 -9
  24. package/src/core/jsonc.ts +6 -6
  25. package/src/core/object.ts +4 -0
  26. package/src/core/options.ts +21 -26
  27. package/src/core/search.ts +45 -44
  28. package/src/core/transport.ts +109 -113
  29. package/src/core/types.ts +47 -47
  30. package/src/core/url.ts +20 -20
  31. package/src/installer/apply.ts +256 -81
  32. package/src/installer/cli.ts +135 -104
  33. package/src/installer/detect.ts +46 -31
  34. package/src/installer/formats/index.ts +26 -28
  35. package/src/installer/formats/json.ts +81 -77
  36. package/src/installer/formats/rules.ts +55 -28
  37. package/src/installer/formats/toml.ts +88 -81
  38. package/src/installer/formats/value.ts +34 -30
  39. package/src/installer/hosts/codex.ts +100 -54
  40. package/src/installer/hosts/index.ts +126 -41
  41. package/src/installer/hosts/opencode.ts +70 -51
  42. package/src/installer/hosts/pi.ts +269 -75
  43. package/src/installer/hosts/shared.ts +19 -14
  44. package/src/installer/operation-lock.ts +54 -0
  45. package/src/installer/ownership.ts +129 -0
  46. package/src/installer/paths.ts +7 -5
  47. package/src/installer/persistence.ts +66 -0
  48. package/src/installer/render.ts +29 -29
  49. package/src/installer/revert.ts +33 -29
  50. package/src/installer/rulesContent.ts +24 -13
  51. package/src/installer/state.ts +116 -31
  52. package/src/installer/types.ts +74 -56
  53. package/src/installer/uninstall.ts +40 -17
  54. package/src/mcp/auth.ts +7 -8
  55. package/src/mcp/cli.ts +38 -20
  56. package/src/mcp/client.ts +35 -0
  57. package/src/mcp/server.ts +68 -97
  58. package/src/opencode/auth.ts +24 -26
  59. package/src/opencode/plugin.ts +99 -87
  60. package/src/pi/extension.ts +164 -0
  61. package/src/pi/settings.ts +49 -0
  62. package/src/mcp/config.ts +0 -124
@@ -16,6 +16,12 @@ The tools appear in Codex as `mcp__deepseek__deepseek_web_search` and
16
16
  `mcp__deepseek__deepseek_fetch`. Codex's built-in `web_search` tool is not replaced; see
17
17
  [Control tiers](#control-tiers) for routing it away.
18
18
 
19
+ The server recognizes Codex's MCP client identity (`codex-mcp-client`, declared in the
20
+ initialize handshake) and returns text-only tool results: Codex replaces the content
21
+ blocks with the serialized `structuredContent` JSON when it is present, so for Codex the
22
+ structured mirror is omitted and the model reads clean Markdown. Other clients receive
23
+ both the text and `structuredContent`.
24
+
19
25
  For shared search/fetch limits, retries, and privacy behavior, see
20
26
  [General behavior](../../README.md#general-behavior).
21
27
 
@@ -40,7 +46,13 @@ It prints the full plan — every file, old value → new value, and notes about
40
46
  MCP servers — and writes nothing until you pass `--apply`. `uninstall` restores exactly
41
47
  what the plan applied (snapshots, merged entries, created files). Codex's config lives in
42
48
  `~/.codex/config.toml` at the user level only, so the server registration and kill switch
43
- are written there even for `--scope project`; the rules file follows the scope.
49
+ are written there even for `--scope project`; the rules file follows the scope. An `off`
50
+ capability is written as the tool's name in the server entry's `disabled_tools` array —
51
+ Codex's own mechanism — merging with any entries you put there yourself. When `codex` is
52
+ on PATH, `init` also probes `codex --version` and adds a plan note when the installed
53
+ release differs from the tested lock (see
54
+ [hosts.jsonc](../hosts.jsonc)) — version-anchored behavior such as the kill switch may
55
+ have drifted between the two.
44
56
 
45
57
  ### Manual install
46
58
 
@@ -59,13 +71,22 @@ env_vars = ["DEEPSEEK_API_KEY"]
59
71
  > forwarded explicitly and set in the shell that launches Codex. Literal values can go
60
72
  > in an `env` table instead if you prefer them in the file.
61
73
 
62
- To disable one capability, set it in the `env` table:
74
+ To disable one capability, add its raw tool name to the server's `disabled_tools` —
75
+ Codex removes the tool from the model's context while the server keeps running:
63
76
 
64
77
  ```toml
65
- [mcp_servers.deepseek.env]
66
- DEEPSEEK_ENABLE_SEARCH = "false" # or DEEPSEEK_ENABLE_FETCH; at least one must stay on
78
+ [mcp_servers.deepseek]
79
+ command = "npx"
80
+ args = ["-y", "deepseek-web-retrieval"]
81
+ env_vars = ["DEEPSEEK_API_KEY"]
82
+ disabled_tools = ["deepseek_web_search"] # or deepseek_fetch; at least one must stay on
67
83
  ```
68
84
 
85
+ The names match verbatim what the server declares (`deepseek_web_search`,
86
+ `deepseek_fetch`). The server also honors `DEEPSEEK_ENABLE_SEARCH`/`DEEPSEEK_ENABLE_FETCH`
87
+ in the `env` table if you prefer switching the tools off at the source; the installer uses
88
+ `disabled_tools`.
89
+
69
90
  ### Local checkout
70
91
 
71
92
  ```bash
@@ -111,7 +132,7 @@ Ask naturally:
111
132
 
112
133
  ```text
113
134
  Search the web for the latest Codex MCP config changes and cite the sources.
114
- Fetch https://developers.openai.com/codex/mcp and summarize the config keys.
135
+ Fetch https://learn.chatgpt.com/docs/extend/mcp and summarize the config keys.
115
136
  ```
116
137
 
117
138
  The model calls `mcp__deepseek__deepseek_web_search` or `mcp__deepseek__deepseek_fetch`.
@@ -127,10 +148,10 @@ defaults and clamping match the other adapters. `DEEPSEEK_API_KEY` goes in `env_
127
148
  | --- | --- | --- |
128
149
  | `DEEPSEEK_ENABLE_SEARCH` | `true` | Register the search tool. Only an explicit `false`/`0`/`no`/`off` disables it; at least one tool must stay enabled. |
129
150
  | `DEEPSEEK_SEARCH_COUNT` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
130
- | `DEEPSEEK_SEARCH_MAX_RESULTS` | `5` | Results returned to the model after filtering, 0–50. `0` returns no results. |
131
- | `DEEPSEEK_SEARCH_MIN_RERANK` | `1.5` | Drop results below this relevance score. DeepSeek scores are 0–3. |
132
- | `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `2` | Drop known authorities below this tier. Authority is 0–4. |
133
- | `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `false` | Keep results whose authority is missing or unknown. |
151
+ | `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. DeepSeek scores are 0–3. |
153
+ | `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. |
134
155
  | `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | unset | Comma-separated; adds `-site:` operators to the query. |
135
156
  | `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | unset | Comma-separated; restricts results with `site:` operators. |
136
157
  | `DEEPSEEK_SEARCH_LANGUAGE` | unset | Adds a `lang:` operator, for example `zh,en`. |
@@ -163,8 +184,8 @@ defaults and clamping match the other adapters. `DEEPSEEK_API_KEY` goes in `env_
163
184
  | Server fails to start or `HTTP 401` from the tools | `DEEPSEEK_API_KEY` must be set in the shell that launches Codex and forwarded via `env_vars`; stdio children do not inherit the environment. |
164
185
  | Both tools missing after startup | At least one of `DEEPSEEK_ENABLE_SEARCH`/`DEEPSEEK_ENABLE_FETCH` must stay enabled; the server exits when both are false. |
165
186
  | Search still uses the built-in tool under the forced tier | The kill switch is the top-level `web_search = "disabled"` key; the `[tools] web_search` form is a no-op at 0.163.0. |
166
- | Tool results look like raw JSON | When a result has `structuredContent`, Codex replaces the content blocks with the serialized JSON — expected behavior. |
167
- | Tools time out around 60 s | Raise `tool_timeout_sec` for the server entry if you raise `DEEPSEEK_*_TIMEOUT_MS` beyond it. |
187
+ | Tool results look like raw JSON | The server omits `structuredContent` for Codex's `codex-mcp-client` client identity so the model reads Markdown; if JSON still appears, a newer Codex renamed the identity — report it. |
188
+ | Tools time out before the configured DeepSeek timeout | Codex's per-server `tool_timeout_sec` defaults to 300 s — above the 120 s `DEEPSEEK_*_TIMEOUT_MS` maximum — so this only happens if you lowered it; raise it back above the DeepSeek timeout. |
168
189
  | Routing not exclusive | Other `[mcp_servers.*]` entries stay registered and are noted in the plan. Under a forced tier, an entry that looks web-capable makes `init` refuse — Codex has no per-server kill switch, so remove the server or restrict its tools via `disabled_tools` by hand. |
169
190
  | `HTTP 402`, `HTTP 429` | Top up the DeepSeek account; wait, reduce concurrency, or set `DEEPSEEK_MAX_RETRIES`. |
170
191
  | Fetch fails on localhost or an internal URL | Use a local fetch tool; DeepSeek cannot reach private networks. |
@@ -100,7 +100,7 @@ A complete global config:
100
100
  Use the bare package name (optionally pinned) as the `package` value:
101
101
 
102
102
  ```jsonc
103
- "plugins": [{ "package": "deepseek-web-retrieval@0.3.0" }]
103
+ "plugins": [{ "package": "deepseek-web-retrieval@0.4.0" }]
104
104
  ```
105
105
 
106
106
  **Global checkout**
@@ -173,10 +173,10 @@ Set these in the plugin's `options` object.
173
173
  | --- | --- | --- |
174
174
  | `searchEnabled` | `true` | Register the DeepSeek search provider at all. Only an explicit `false` disables it. |
175
175
  | `count` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
176
- | `maxResults` | `5` | Results returned to the model after filtering, 0–50. `0` returns no results. |
177
- | `minRerank` | `1.5` | Drop results below this relevance score. DeepSeek scores are 0–3. |
178
- | `minAuthority` | `2` | Drop known authorities below this tier. Authority is 0–4. |
179
- | `allowUnknownAuthority` | `false` | Keep results whose authority is missing or unknown. |
176
+ | `maxResults` | `10` | Results returned to the model after filtering, 0–50. `0` returns no results. |
177
+ | `minRerank` | `0` | Drop results below this relevance score. DeepSeek scores are 0–3. A result without a finite score passes while the threshold is `0` and is dropped once it is positive. |
178
+ | `minAuthority` | `0` | Drop known authorities below this tier. Authority is 0–4. |
179
+ | `allowUnknownAuthority` | `true` | Keep results whose authority is missing or unknown. Only an explicit `false` disables it. |
180
180
  | `blockedDomains` | `[]` | Add `-site:` operators to the search query. |
181
181
  | `includeDomains` | `[]` | Restrict results with `site:` operators. |
182
182
  | `language` | unset | Add a `lang:` operator, for example `zh,en`. |
package/docs/agents/pi.md CHANGED
@@ -2,29 +2,33 @@
2
2
 
3
3
  Pi adapter for [DeepSeek Web Retrieval](../../README.md).
4
4
 
5
- Integration mechanism: MCP server over stdio, launched from Pi's `mcp.json`. Pi 1.3.0
6
- negotiates MCP revision `2025-11-25`; the server also accepts clients from `2024-11-05`
7
- through `2026-07-28`, so the same build works for other MCP hosts (see
8
- [the compatibility research](../research/mcp-compatibility.md)).
5
+ Integration mechanism: Pi's native extension API — the package's `pi` manifest names
6
+ `src/pi/extension.ts`, and Pi loads it in-process (via jiti, TypeScript directly) at session
7
+ start. The same build also ships an MCP server over stdio as the fallback path
8
+ ([below](#the-mcp-fallback-path)); the extension is the recommended one:
9
9
 
10
- When registered, it adds:
10
+ - The tools are declared to the model like built-ins (`exposure: "direct"` is Pi's own
11
+ default for extension tools — the MCP path's codemode trap does not exist here).
12
+ - Results bypass Pi's MCP layer entirely: no 20 KB middle truncation and no 60 s
13
+ per-request timeout; the adapter's own output cap is the only limit.
14
+ - The key is read from the Pi process environment — no `env` forwarding, no `${VAR}`
15
+ indirection in any config file.
16
+
17
+ When loaded, it adds:
11
18
 
12
19
  - A `deepseek_web_search` tool backed by DeepSeek Search
13
20
  - A `deepseek_fetch` tool backed by DeepSeek Fetch
14
21
 
15
- The tools appear in Pi as `mcp__deepseek__deepseek_web_search` and
16
- `mcp__deepseek__deepseek_fetch`. Pi's built-in tools are not replaced.
17
-
18
- For shared search/fetch limits, retries, and privacy behavior, see
22
+ Pi 1.3.0 ships no built-in web tool, so the registered tools are the only web path. For
23
+ shared search/fetch limits, retries, and privacy behavior, see
19
24
  [General behavior](../../README.md#general-behavior).
20
25
 
21
26
  ## Requirements
22
27
 
23
28
  - Pi 1.3.0 or newer
24
- - Node.js 20 or newer (`npx` runs the server)
25
29
  - A DeepSeek account with access to `/v1/tools/search` and `/v1/tools/fetch`
26
30
  - A funded DeepSeek account; Search and Fetch are billed per successful request
27
- - `DEEPSEEK_API_KEY` set in the server's `env` block
31
+ - `DEEPSEEK_API_KEY` set in the shell that launches Pi
28
32
 
29
33
  ## Install
30
34
 
@@ -36,79 +40,49 @@ npx deepseek-web-retrieval init --host pi --scope <project|global> \
36
40
  ```
37
41
 
38
42
  It prints the full plan — every file, old value → new value, and notes about existing
39
- MCP servers and the `exposure` requirement — and writes nothing until you pass `--apply`.
40
- JSON edits preserve comments. `uninstall` restores exactly what the plan applied. Under a
41
- forced tier, other registered MCP servers that look web-capable are disabled
42
- (`enabled: false`, prior value snapshotted) so DeepSeek routing is exclusive, and
43
- `uninstall` restores them. The
44
- tiers follow the [control matrix](../research/control-matrix.md): Pi ships no built-in web
45
- tool, so forced is de-facto for both capabilities and `off` writes
46
- `DEEPSEEK_ENABLE_SEARCH`/`DEEPSEEK_ENABLE_FETCH` into the server's env block.
43
+ packages and MCP servers — and writes nothing until you pass `--apply`. The default path is
44
+ the extension: the plan appends `npm:deepseek-web-retrieval` to the `packages` array in
45
+ `~/.pi/agent/settings.json` (global) or `.pi/settings.json` (project), and Pi resolves the
46
+ source at the next session start (the first launch fetches the package from npm;
47
+ `pi update --extensions` refreshes it later). An off capability is written into the
48
+ `deepseekWebRetrieval` settings namespace the extension reads at load. Pass `--path mcp`
49
+ to install the [MCP fallback path](#the-mcp-fallback-path) instead. `uninstall` restores
50
+ exactly what the plan applied.
47
51
 
48
52
  ### Manual install
49
53
 
50
- Add the server to `~/.pi/agent/mcp.json` (user) or `.pi/mcp.json` (project):
51
-
52
- ```jsonc
53
- {
54
- "mcpServers": {
55
- "deepseek": {
56
- "command": "npx",
57
- "args": ["-y", "deepseek-web-retrieval"],
58
- "env": { "DEEPSEEK_API_KEY": "${DEEPSEEK_API_KEY}" },
59
- "exposure": "direct"
60
- }
61
- }
62
- }
63
- ```
64
-
65
- `${DEEPSEEK_API_KEY}` expands from the shell that launches Pi, so the key itself stays out
66
- of the config file. Set it in your shell profile:
54
+ Use Pi's own installer:
67
55
 
68
56
  ```bash
69
- export DEEPSEEK_API_KEY="sk-***"
57
+ pi install npm:deepseek-web-retrieval # ~/.pi/agent/settings.json (user)
58
+ pi install --local npm:deepseek-web-retrieval # .pi/settings.json (project)
70
59
  ```
71
60
 
72
- > [!NOTE]
73
- > `exposure` matters. Pi 1.3.0 defaults MCP tools to `codemode`, which does not declare
74
- > them to the model. `"exposure": "direct"` registers the two tools like built-ins so the
75
- > model can call them. Without it the server connects but the tools are never used.
76
-
77
- Verify the registration with `pi mcp list` (or the `/mcp` panel inside a session); Pi
78
- shows the server's tools when the handshake succeeds. Config changes load with `/reload`
79
- or a new session.
61
+ Pi reads the package's `pi` manifest and loads `src/pi/extension.ts` at session start. No
62
+ build step and no config entry beyond the `packages` source are needed.
80
63
 
81
64
  ### Local checkout
82
65
 
83
- To run the server from a checkout instead of the published package (for
84
- development):
85
-
86
66
  ```bash
87
67
  git clone https://github.com/gwokhou/deepseek-web-retrieval.git
88
- cd deepseek-web-retrieval
89
- bun install && bun run build
90
68
  ```
91
69
 
92
- Then point Pi at the built entry:
70
+ Then point Pi at the checkout (it is itself a valid package):
93
71
 
94
- ```jsonc
95
- {
96
- "mcpServers": {
97
- "deepseek": {
98
- "command": "node",
99
- "args": ["/absolute/path/to/deepseek-web-retrieval/dist/cli.js"],
100
- "env": { "DEEPSEEK_API_KEY": "${DEEPSEEK_API_KEY}" },
101
- "exposure": "direct"
102
- }
103
- }
104
- }
72
+ ```bash
73
+ pi install /absolute/path/to/deepseek-web-retrieval
105
74
  ```
106
75
 
76
+ The extension entry imports only host-supplied modules (`@earendil-works/pi-coding-agent`,
77
+ `@earendil-works/pi-ai`) and the package's own `src/core/`, so a plain clone works.
78
+
107
79
  ## Authentication
108
80
 
109
- `DEEPSEEK_API_KEY` is the only source; MCP has no host credential store. The key is read
110
- per request and sent only to `api.deepseek.com` as a Bearer token. It is not written to
111
- tool output or logs. Without a key the server exits at startup with a message on stderr.
81
+ `DEEPSEEK_API_KEY` is the only source: the extension runs inside the Pi process and reads
82
+ the environment directly, so the key must be set in the shell that launches Pi (MCP's env
83
+ forwarding does not apply). It is read per request and sent only to `api.deepseek.com` as a
84
+ Bearer token. It is not written to tool output or logs. Without it, the first tool call
85
+ returns an error naming the missing variable.
112
86
 
113
87
  ## Usage
114
88
 
@@ -119,12 +93,15 @@ Search the web for the latest Pi extension API changes and cite the sources.
119
93
  Fetch https://pi.dev/docs/latest/extensions and summarize the extension lifecycle.
120
94
  ```
121
95
 
122
- The model calls `mcp__deepseek__deepseek_web_search` or `mcp__deepseek__deepseek_fetch`.
96
+ The model calls `deepseek_web_search` or `deepseek_fetch` — the same names over both paths;
97
+ only the MCP fallback prefixes them with `mcp__deepseek__`.
123
98
 
124
99
  ## Configuration
125
100
 
126
- Set these in the server's `env` block. Values map onto the shared option pipeline, so
127
- defaults and clamping match the OpenCode adapter.
101
+ The runtime config surface is the same `DEEPSEEK_*` environment the MCP server reads
102
+ (identical defaults and clamping), set in the shell that launches Pi, plus the
103
+ `deepseekWebRetrieval` settings namespace for the off switches (written by the installer;
104
+ it outranks the environment and is preserved by Pi's own settings rewrites).
128
105
 
129
106
  ### Search
130
107
 
@@ -132,23 +109,23 @@ defaults and clamping match the OpenCode adapter.
132
109
  | --- | --- | --- |
133
110
  | `DEEPSEEK_ENABLE_SEARCH` | `true` | Register the search tool. Only an explicit `false`/`0`/`no`/`off` disables it; at least one tool must stay enabled. |
134
111
  | `DEEPSEEK_SEARCH_COUNT` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
135
- | `DEEPSEEK_SEARCH_MAX_RESULTS` | `5` | Results returned to the model after filtering, 0–50. `0` returns no results. |
136
- | `DEEPSEEK_SEARCH_MIN_RERANK` | `1.5` | Drop results below this relevance score. DeepSeek scores are 0–3. |
137
- | `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `2` | Drop known authorities below this tier. Authority is 0–4. |
138
- | `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `false` | Keep results whose authority is missing or unknown. Set `true`/`1`. |
112
+ | `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. DeepSeek scores are 0–3. |
114
+ | `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. |
139
116
  | `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | unset | Comma-separated; adds `-site:` operators to the query. |
140
117
  | `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | unset | Comma-separated; restricts results with `site:` operators. |
141
118
  | `DEEPSEEK_SEARCH_LANGUAGE` | unset | Adds a `lang:` operator, for example `zh,en`. |
142
119
  | `DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS` | `1200` | Per-result summary cap, 0–1,000,000. |
143
- | `DEEPSEEK_SEARCH_TIMEOUT_MS` | `30000` | Search timeout, 1,000–120,000 ms. |
120
+ | `DEEPSEEK_SEARCH_TIMEOUT_MS` | `30000` | Search timeout, 1,000–120,000 ms. No host-side cap applies on the extension path. |
144
121
 
145
122
  ### Fetch
146
123
 
147
124
  | Env variable | Default | Description |
148
- | --- | --- |
125
+ | --- | --- | --- |
149
126
  | `DEEPSEEK_ENABLE_FETCH` | `true` | Register the fetch tool. Same false-only semantics as `DEEPSEEK_ENABLE_SEARCH`. |
150
- | `DEEPSEEK_FETCH_MAX_CHARS` | `16000` | Maximum characters returned, including the title, 0–1,000,000. |
151
- | `DEEPSEEK_FETCH_TIMEOUT_MS` | `30000` | Fetch timeout, 1,000–120,000 ms. |
127
+ | `DEEPSEEK_FETCH_MAX_CHARS` | `16000` | Maximum characters returned, including the title, 0–1,000,000. The only output cap on this path. |
128
+ | `DEEPSEEK_FETCH_TIMEOUT_MS` | `30000` | Fetch timeout, 1,000–120,000 ms. No host-side cap applies on the extension path. |
152
129
  | `DEEPSEEK_FETCH_ALLOWED_DOMAINS` | unset | If set, only these hosts and subdomains may be fetched. |
153
130
  | `DEEPSEEK_FETCH_BLOCKED_DOMAINS` | unset | Always block these hosts and subdomains. Blocked wins over allowed. |
154
131
  | `DEEPSEEK_FETCH_BLOCK_PRIVATE_HOSTS` | `true` | Reject localhost, private IPs, `.local`, `.internal`, and similar hosts. Set `false`/`0` to disable. |
@@ -157,7 +134,7 @@ defaults and clamping match the OpenCode adapter.
157
134
 
158
135
  | Env variable | Default | Description |
159
136
  | --- | --- | --- |
160
- | `DEEPSEEK_API_KEY` | required | DeepSeek API key. The server refuses to start without it. |
137
+ | `DEEPSEEK_API_KEY` | required | DeepSeek API key, read from the Pi process environment. |
161
138
  | `DEEPSEEK_MAX_RETRIES` | `0` | Retries for 408, 425, 429, and 5xx responses, 0–5. |
162
139
  | `DEEPSEEK_RETRY_BASE_DELAY_MS` | `250` | Base delay for exponential backoff. `Retry-After` is honored up to 10 seconds. |
163
140
 
@@ -167,30 +144,28 @@ Invalid values fall back to the shared default.
167
144
 
168
145
  | Error or symptom | What to do |
169
146
  | --- | --- |
170
- | Server connects but the model never calls the tools | Set `"exposure": "direct"`; Pi 1.3.0 defaults to `codemode`, which hides MCP tools from the model. |
171
- | Server fails to start (`pi mcp list`) | Check `DEEPSEEK_API_KEY`; without it the process exits with a stderr message. |
147
+ | `Failed to load extension ... at least one must stay enabled` | Both capabilities are off (env or the `deepseekWebRetrieval` namespace); keep at least one on. |
148
+ | Tools never appear | Check that the `packages` entry is present (`pi list`) and that the first launch after install could reach npm; `pi update --extensions` re-fetches. |
149
+ | First tool call errors on the key | `DEEPSEEK_API_KEY` must be set in the shell that launches Pi; the extension reads only the process environment. |
172
150
  | `HTTP 401, invalid API key` | Check the key and its access to the tools endpoints. |
173
151
  | `HTTP 402, insufficient balance` | Top up the DeepSeek account. |
174
152
  | `HTTP 429, rate limited` | Wait, reduce concurrency, or set `DEEPSEEK_MAX_RETRIES`. |
175
153
  | Search query too long | Shorten the query or reduce configured domain/language operators. |
176
154
  | Fetch fails on localhost or an internal URL | Use a local fetch tool; DeepSeek cannot reach private networks. |
177
- | Long fetch results look cut in the middle | Pi truncates text results over 20 KB (full text is saved to a file). Keep `DEEPSEEK_FETCH_MAX_CHARS` at or below 20000 to truncate deterministically. |
178
- | Request fails around 60 seconds | Pi's per-request default timeout is 60 s; raise the server's `timeout` if you raise `DEEPSEEK_*_TIMEOUT_MS` beyond it. |
179
- | Config change does not take effect | Run `/reload` in the session or start a new one. |
155
+ | Project-scope install does not load | Pi gates project-local files behind project trust; approve the project when prompted (or launch with `--approve`). |
180
156
 
181
157
  ## Compatibility
182
158
 
183
159
  | Component | Version |
184
160
  | --- | --- |
185
161
  | Pi | locked in [`docs/hosts.jsonc`](../hosts.jsonc) |
186
- | MCP revision | `2025-11-25` negotiated; server accepts `2024-11-05`–`2026-07-28` |
187
- | MCP SDK | `@modelcontextprotocol/server` 2.3.1 |
188
- | Runtime | Node.js 20+ (stdio) |
162
+ | Extension entry | `src/pi/extension.ts` via the package's `pi` manifest (jiti, no build step) |
163
+ | Runtime | Inside the Pi process (Node, host-provided) |
189
164
  | DeepSeek API | `/v1/tools/search`, `/v1/tools/fetch` |
190
165
 
191
- Pi 1.3.0 supports stdio and Streamable HTTP servers; this guide uses stdio. Tested with
192
- Pi 1.3.0 on Linux over stdio; the dual-era protocol behavior is covered by the SDK client
193
- test personas (`2024-11-05`, `2025-06-18`, `2025-11-25`, `2026-07-28`).
166
+ Tested with Pi 1.3.0 on Linux. The extension API is version-locked research surface: the
167
+ `ToolDefinition` shape and the settings `packages` key are verified at the locked version
168
+ (see [the per-host reference](../research/control-matrix.md#pi)).
194
169
 
195
170
  ## Uninstall
196
171
 
@@ -200,10 +175,37 @@ With the installer CLI:
200
175
  npx deepseek-web-retrieval uninstall --host pi --scope <project|global>
201
176
  ```
202
177
 
203
- Or remove the `deepseek` entry from `~/.pi/agent/mcp.json` or `.pi/mcp.json`, or run:
178
+ Or remove the source with Pi itself:
204
179
 
205
180
  ```bash
206
- pi mcp remove deepseek
181
+ pi remove npm:deepseek-web-retrieval
207
182
  ```
208
183
 
209
184
  For a local-checkout install, also delete the cloned directory.
185
+
186
+ ## The MCP fallback path
187
+
188
+ The same package ships a stdio MCP server (`npx deepseek-web-retrieval`). It works on Pi
189
+ through `~/.pi/agent/mcp.json` or `.pi/mcp.json`:
190
+
191
+ ```jsonc
192
+ {
193
+ "mcpServers": {
194
+ "deepseek": {
195
+ "command": "npx",
196
+ "args": ["-y", "deepseek-web-retrieval"],
197
+ "env": { "DEEPSEEK_API_KEY": "${DEEPSEEK_API_KEY}" },
198
+ "exposure": "direct"
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ The tools appear as `mcp__deepseek__deepseek_web_search` and `mcp__deepseek__deepseek_fetch`,
205
+ and the MCP layer's limits apply instead: text results over 20 KB are middle-truncated with
206
+ the full text saved to a file (keep `DEEPSEEK_FETCH_MAX_CHARS` at or below 20000 to truncate
207
+ deterministically), and the per-request default timeout is 60 s (raise the entry's `timeout`
208
+ if you raise `DEEPSEEK_*_TIMEOUT_MS` beyond it). `pi mcp list` verifies the handshake, and
209
+ config changes load with `/reload` or a new session. `npx deepseek-web-retrieval init
210
+ --host pi --path mcp ...` installs this shape with plan-then-apply semantics; see
211
+ [`examples/pi.mcp.jsonc`](../../examples/pi.mcp.jsonc).
@@ -116,6 +116,21 @@ scope choice would overlap the other scope's record — Codex's project-scope fa
116
116
  onto `~/.codex/config.toml` beside a global install — the plan refuses with guidance,
117
117
  and uninstall runs the same check defensively before touching anything.
118
118
 
119
+ Shared config files also carry an owner-only sidecar beside the actual file
120
+ (`.config.toml.deepseek-web-retrieval.codex.owner.json` for Codex). It points to the
121
+ owning install record and project directory, so two different project directories
122
+ cannot both claim the same global config, even with different `XDG_CONFIG_HOME`
123
+ values. Planning reports this metadata write; apply acquires the claim before
124
+ writing config and rechecks ownership, and uninstall releases it. A failed first
125
+ apply releases its new claim; a failed re-init retains the previous owner's claim.
126
+ Project-local configs and shared rules blocks do not need these sidecars.
127
+
128
+ For installs created before this ownership metadata existed, re-run init from the
129
+ original project and scope to establish the claim. An installer-style registration
130
+ without a record at the requested location is refused rather than adopted as a new
131
+ snapshot. If an identical registration was created manually, remove it before init.
132
+ Do not delete an ownership sidecar while its install is still active.
133
+
119
134
  ## Rollback
120
135
 
121
136
  Uninstall reverses exactly what the plan applied: overwritten values restore their
@@ -127,14 +142,17 @@ pointing at the removed plugin, and the user must clean up by hand.
127
142
 
128
143
  ## Tier-to-artifact mapping
129
144
 
145
+ Pi has two integration paths (`--path extension` by default, `--path mcp` as the fallback);
146
+ the artifacts below name the path where they differ.
147
+
130
148
  | Choice | Artifacts |
131
149
  | --- | --- |
132
- | Optional | Host config registering the server, plus host-side per-tool filtering where supported (Copilot `tools` whitelist, Codex `enabled_tools`); Pi needs `"exposure": "direct"` |
133
- | Preferred | The above plus a rules file for the host (file names per host in the [per-host reference](../research/control-matrix.md#per-host-reference)) |
150
+ | Optional | Host config registering the integration: Pi's extension path appends an `npm:<package>` source to `packages` in settings.json; the MCP paths register the server (Pi's MCP entry needs `"exposure": "direct"`; the extension path needs nothing — extension tools are `direct` by Pi's own default) |
151
+ | Preferred | The above plus a rules file for the host (file names per host in the [per-host reference](../research/control-matrix.md#per-host-reference)); the rules name the path's tool ids (`deepseek_web_search` for the extension, `mcp__deepseek__deepseek_web_search` for MCP) |
134
152
  | Forced (replacement) | OpenCode plugin options (`setDefaultWebsearch` / `fetchOverrideWebfetch`) |
135
- | Forced (exclusion) | The built-in competitor's kill switch (per-host keys in the per-host reference), plus the exclusion of any other web-capable MCP servers the detect phase found — Pi sets their `enabled` key to `false`; Codex, lacking a per-server kill switch, refuses the tier instead |
153
+ | Forced (exclusion) | The built-in competitor's kill switch (per-host keys in the per-host reference), plus the exclusion of any other web-capable MCP servers the detect phase found — Pi sets their `enabled` key to `false`; Codex, lacking a per-server kill switch, refuses the tier instead; Pi's extension path additionally refuses when a web-capable `packages`/`extensions` source is listed in settings.json (no per-package kill switch exists) |
136
154
  | Forced (de-facto) | Nothing beyond the Optional artifacts — no competitor exists |
137
- | Off | The MCP entry with the capability's false-only server-side switch instead of that tool (`DEEPSEEK_ENABLE_SEARCH` / `DEEPSEEK_ENABLE_FETCH`); both capabilities off refuses to install |
155
+ | Off | Codex writes the capability's raw tool name into the server entry's `disabled_tools` (host-native: the tool leaves the model's context; the user's own entries in that array are kept); the other MCP entry writes the capability's false-only server-side switch instead (`DEEPSEEK_ENABLE_SEARCH` / `DEEPSEEK_ENABLE_FETCH`); Pi's extension path writes the same switches into the `deepseekWebRetrieval` settings namespace the extension reads at load; both capabilities off refuses to install |
138
156
 
139
157
  OpenCode additionally appends `permissions` allow entries for exactly the tools it
140
158
  registers (`websearch`, `deepseek_fetch`, and `webfetch` when fetch is forced), merged
@@ -146,10 +164,13 @@ into any existing `permissions` array rather than replacing it.
146
164
  request; deterministic routing removes both the model's discretion and its restraint as
147
165
  a cost brake. Every forced-tier plan carries a note naming the README's "Privacy and
148
166
  cost" section.
149
- - **Rules content.** Name the full tool ids (`mcp__deepseek__deepseek_web_search`), state
150
- trigger conditions rather than unconditional preference, and list exclusions
151
- (localhost/private-network URLs go to local fetch tools; the server blocks them by
152
- default).
167
+ - **Rules content.** Name the full tool ids the host will see (`mcp__deepseek__deepseek_web_search`
168
+ over MCP, `deepseek_web_search` over Pi's extension), state trigger conditions rather than
169
+ unconditional preference, and list exclusions (localhost/private-network URLs go to local
170
+ fetch tools; the server blocks them by default). Emit routing guidance only for the
171
+ capability selected as preferred; an optional capability stays optional even when the
172
+ other capability is preferred. Missing, reversed, or duplicate block markers refuse
173
+ planning, apply, and uninstall without deleting the ambiguous surrounding text.
153
174
 
154
175
  ## Implementation notes
155
176
 
@@ -162,11 +183,22 @@ into any existing `permissions` array rather than replacing it.
162
183
  records snapshots, appended entries, created files, and created path prefixes — never
163
184
  full file contents, so other servers' secrets stay out of the state file.
164
185
  - Re-applying over a previous init inherits the original snapshots instead of recording
165
- the installer's own output as the "previous" value.
186
+ the installer's own output as the "previous" value. Pi forced re-inits keep their
187
+ competitor-disable ops even when those servers are already disabled, so orphan
188
+ reverts cannot re-enable a still-excluded competitor.
166
189
  - Verify is file-level: after apply it re-reads each written file and asserts every op
167
190
  holds (set values in place, appended entries present, rules block present). Higher
168
191
  layers would be a plan-time detect concern, and none of the three shipped hosts
169
192
  documents a layer above user config, so there is nothing to check there yet.
193
+ - Only a bare `--apply` enables writes; assigning a value such as `--apply=false`
194
+ is a usage error and writes nothing.
195
+ - File verification precedes the install-record commit. A write or verification
196
+ failure restores the pre-apply files and record and checks the restored values.
197
+ `ApplyError.status` is `rolled_back` only after recovery and new-claim cleanup
198
+ succeed. Otherwise it is `recovery_required`, with the original cause and a
199
+ recovery report; new claims remain while file or record recovery is incomplete.
200
+ Obsolete claims are released after commit. If that cleanup fails, the verified
201
+ install and new record are retained and reported explicitly for recovery.
170
202
 
171
203
  ## Server-side gap (resolved)
172
204
 
@@ -182,3 +214,34 @@ Claude Code, Cursor, Devin, GitHub Copilot, and Cline still have no
182
214
  host module; their artifacts (from the tier-to-artifact mapping) remain hand-edited
183
215
  config until a module lands here. OpenCode's `opencode plugin add` remains available but
184
216
  cannot pass options or manage snapshots; `init` supersedes it for tiered installs.
217
+
218
+
219
+ ### Persistence and concurrent operations
220
+
221
+ Install records distinguish absent files from invalid or unreadable files. The
222
+ reader validates version, host/scope identity, tiers, file metadata, and recorded
223
+ operations before an install or uninstall can use undo state. `list` reports a
224
+ bad record instead of silently omitting it. Restore the record from backup or
225
+ repair access before retrying; deleting it can discard the original snapshots.
226
+
227
+ Config and record writes use private temporary files in the target directory,
228
+ verify the temporary contents, flush the file, then rename and flush the directory
229
+ (on POSIX). Existing config permissions and valid symbolic links are preserved;
230
+ records are always mode 0600. A failed write before rename leaves the old file
231
+ intact. This makes each file replacement atomic, not the entire multi-file install.
232
+ The apply rollback remains responsible for caught errors; power-loss recovery is
233
+ not an automatic transaction replay.
234
+
235
+ Apply and uninstall acquire a record operation lock before reading undo state,
236
+ then locks for every config/rules path they may mutate, including orphan reverts.
237
+ Locks follow canonical paths, so another project or symlink alias cannot bypass a
238
+ shared-file lock. A plan whose prior record changed is refused, even if its only
239
+ edits append arrays. Preview generation is read-only and does not reserve locks.
240
+
241
+ Operation locks are directories ending in `.deepseek-web-retrieval.lock`, with
242
+ owner PID and start time inside. Ordinary success or failure releases them. A
243
+ killed process may leave a lock: after confirming no installer is running, inspect
244
+ config, install record, and persistent ownership claims, restore any inconsistent
245
+ state, then remove the reported lock directory and generate a new plan. Locks are
246
+ never stolen based only on age or PID. Persistent `.owner.json` claims remain a
247
+ separate safeguard against two installations owning one shared config.
package/docs/hosts.jsonc CHANGED
@@ -82,13 +82,13 @@
82
82
  "npm": "@earendil-works/pi-coding-agent",
83
83
  "nativeSearchProvider": false,
84
84
  "nativeFetchOverride": false,
85
- "paths": ["mcp", "extension"],
85
+ "paths": ["extension", "mcp"],
86
86
  "status": "supported",
87
87
  "control": {
88
88
  "search": { "optional": "yes", "preferred": "yes", "forced": "de-facto", "key": "no built-in web tool at 1.3.0 (source-verified)" },
89
89
  "fetch": { "optional": "yes", "preferred": "yes", "forced": "de-facto", "key": "no built-in web tool at 1.3.0 (source-verified)" }
90
90
  },
91
- "adapter": "src/mcp",
91
+ "adapter": "src/pi",
92
92
  "installer": "src/installer/hosts/pi",
93
93
  "guide": "docs/agents/pi.md",
94
94
  "e2eScenario": "pi"
@@ -16,8 +16,8 @@ ship built-in web tools. MCP tools can add equivalent tools, but do not replace
16
16
  | Native websearch provider | Yes | No | Host plugin API | OpenCode v2 adapter |
17
17
  | Native tool / fetch override | No | Yes | Host tool API | OpenCode v2 adapter |
18
18
  | Custom tool plugin | Yes | Yes | Host plugin API | Cline CLI/SDK; no adapter yet |
19
- | Extension API | Yes | Yes | Host extension API | Pi; no adapter yet |
20
- | MCP server | Yes | Yes | Host MCP client | Shipped (`src/mcp/`; Pi guide first) |
19
+ | Extension API | Yes | Yes | Host extension API | Pi adapter (`src/pi/`) |
20
+ | MCP server | Yes | Yes | Host MCP client | Shipped (`src/mcp/`; Pi and Codex guides) |
21
21
  | CLI/HTTP wrapper | Yes | Yes | Host can call external tools | Not shipped |
22
22
 
23
23
  MCP is a protocol, not an agent. It is one possible transport for hosts that support MCP
@@ -34,7 +34,7 @@ Rows are sorted with open-source hosts first, then by host name alphabetically.
34
34
  | Cline | 0.0.13 | Yes (Apache-2.0) | No | No | MCP server or custom tool plugin | No adapter yet |
35
35
  | Codex | 0.163.0 | Yes (Apache-2.0) | No | No | MCP server | Supported |
36
36
  | OpenCode | 2.0.32 | Yes (MIT) | Yes | Yes | native websearch provider or native tool/fetch override | Supported |
37
- | Pi | 1.3.0 | Yes (MIT) | No | No | MCP server or extension API | Supported |
37
+ | Pi | 1.3.0 | Yes (MIT) | No | No | extension API or MCP server | Supported |
38
38
  | Claude Code | 2.1.303 | No | No | No | MCP server | No adapter yet |
39
39
  | Cursor | 2026.10.08 | No | No | No | MCP server | No adapter yet |
40
40
  | Devin | 3000.11.92 | No | No | No | MCP server | No adapter yet |
@@ -78,14 +78,18 @@ the details: built-in web tools and their kill switches, MCP registration, and r
78
78
  [Cline](research/control-matrix.md#cline).
79
79
  - **Devin** — Ships built-in `web_search` and `webfetch` tools. See
80
80
  [Devin](research/control-matrix.md#devin).
81
- - **Pi** — No built-in web tools; MCP plus a TypeScript extension API. Supported through
82
- the MCP server; see [Pi](agents/pi.md) and [Pi in the per-host reference](research/control-matrix.md#pi).
81
+ - **Pi** — No built-in web tools; a TypeScript extension API plus MCP. Supported through
82
+ the native extension adapter (the recommended path), with the MCP server as the fallback;
83
+ see [Pi](agents/pi.md) and [Pi in the per-host reference](research/control-matrix.md#pi).
83
84
 
84
85
  ## Roadmap
85
86
 
86
- - **MCP server** — shipped in `src/mcp/` with the [Pi](agents/pi.md) guide; the broadest path for
87
- hosts that support MCP tools. Protocol compatibility across these hosts is researched in
87
+ - **MCP server** — shipped in `src/mcp/` (the fallback path in the [Pi](agents/pi.md) guide
88
+ and the path in the [Codex](agents/codex.md) guide); the broadest route for hosts that
89
+ support MCP tools. Protocol compatibility across these hosts is researched in
88
90
  [mcp-compatibility.md](research/mcp-compatibility.md).
91
+ - **Native extension adapters** — the Pi extension adapter ships in `src/pi/`; a host whose
92
+ extension point can register tools directly gets a native path alongside MCP.
89
93
  - **Host-specific guides** — added as requests and testing land; the server itself is
90
94
  host-agnostic.
91
95
 
@@ -94,8 +98,9 @@ the details: built-in web tools and their kill switches, MCP registration, and r
94
98
  - OpenCode: [plugin API](https://opencode.ai/v2/docs/build/plugins)
95
99
  - Claude Code: [MCP](https://code.claude.com/docs/en/mcp),
96
100
  [tools reference](https://code.claude.com/docs/en/tools-reference)
97
- - Codex: [MCP](https://developers.openai.com/codex/mcp),
98
- [CLI](https://developers.openai.com/codex/cli)
101
+ - Codex: [MCP](https://learn.chatgpt.com/docs/extend/mcp),
102
+ [CLI](https://learn.chatgpt.com/docs/codex/cli),
103
+ [config reference](https://learn.chatgpt.com/docs/config-file/config-reference)
99
104
  - Cursor: [MCP](https://cursor.com/docs/mcp),
100
105
  [CLI changelog](https://cursor.com/docs/cli/changelog)
101
106
  - Copilot CLI: [repository](https://github.com/github/copilot-cli),