deepseek-web-retrieval 0.6.0 → 0.8.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 (50) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +113 -53
  3. package/dist/cli.js +2427 -1813
  4. package/docs/adr/0002-shared-config-base-layer.md +43 -0
  5. package/docs/agents/claude-code.md +273 -0
  6. package/docs/agents/codex.md +50 -27
  7. package/docs/agents/opencode-v2.md +59 -33
  8. package/docs/agents/pi.md +50 -27
  9. package/docs/configuration.md +109 -0
  10. package/docs/design/config-layering-design.md +180 -0
  11. package/docs/design/installer-design.md +24 -13
  12. package/docs/hosts.jsonc +5 -1
  13. package/docs/integration.md +15 -9
  14. package/docs/research/control-matrix.md +49 -11
  15. package/docs/research/mcp-compatibility.md +6 -3
  16. package/docs/research/ori-question-sourcing.md +336 -0
  17. package/docs/research/result-quality-passthrough.md +159 -0
  18. package/docs/research/search-api-defaults.md +18 -2
  19. package/docs/research/search-inline-content.md +244 -0
  20. package/examples/opencode.global.jsonc +3 -2
  21. package/package.json +2 -1
  22. package/src/core/auth.ts +3 -0
  23. package/src/core/client.ts +26 -4
  24. package/src/core/config-file.ts +168 -0
  25. package/src/core/config-summary.ts +82 -0
  26. package/src/core/constants.ts +15 -1
  27. package/src/core/descriptions.ts +6 -2
  28. package/src/core/env.ts +102 -32
  29. package/src/core/format.ts +19 -1
  30. package/src/core/options.ts +22 -2
  31. package/src/core/search.ts +6 -0
  32. package/src/core/types.ts +11 -0
  33. package/src/installer/cli.ts +50 -16
  34. package/src/installer/config-command.ts +116 -0
  35. package/src/installer/hosts/claude-code.ts +325 -0
  36. package/src/installer/hosts/codex.ts +9 -4
  37. package/src/installer/hosts/index.ts +3 -0
  38. package/src/installer/hosts/opencode.ts +9 -5
  39. package/src/installer/hosts/pi.ts +34 -9
  40. package/src/installer/ownership.ts +54 -1
  41. package/src/installer/paths.ts +7 -1
  42. package/src/installer/state.ts +73 -20
  43. package/src/installer/types.ts +11 -1
  44. package/src/installer/uninstall.ts +4 -1
  45. package/src/mcp/auth.ts +2 -3
  46. package/src/mcp/cli.ts +4 -3
  47. package/src/mcp/serve.ts +10 -1
  48. package/src/mcp/server.ts +7 -2
  49. package/src/opencode/plugin.ts +22 -2
  50. package/src/pi/extension.ts +78 -9
@@ -0,0 +1,43 @@
1
+ # Share one config base layer across hosts
2
+
3
+ The option pipeline was single-sourced, but the channels feeding it were per
4
+ host: `DEEPSEEK_*` shell variables on Pi, only `ctx.options` on OpenCode, and
5
+ on Codex only the names the installer's `env_vars` entry forwards — just
6
+ `DEEPSEEK_API_KEY`, so every other knob set in the shell silently never
7
+ reached the server. One intent ("20 results, block pinterest.com") took up to
8
+ three configuration acts in two vocabularies. We decided that a shared config
9
+ file becomes the base layer under every existing channel, with the precedence
10
+ `built-in defaults < shared config file < DEEPSEEK_* environment < host-native
11
+ config`, a three-class parameter taxonomy that isolates host integration in
12
+ the host-native layer and rejects it from the file, and no API key in the
13
+ file — auth keeps its existing channels.
14
+
15
+ ## Considered Options
16
+
17
+ - **Unify on the file alone** (retire the environment and host-option
18
+ vocabularies so there is one configuration act). But host integration —
19
+ `searchEnabled`/`fetchEnabled`, `setDefaultWebsearch`,
20
+ `fetchOverrideWebfetch`, `fetchRegisterTool`, `apiKeySource` — describes the
21
+ host's tool landscape, not the user↔DeepSeek relationship, so the host layer
22
+ must keep owning it, and retiring `DEEPSEEK_*` would break every existing
23
+ shell profile.
24
+ - **Keep the per-host channels as the only surface** (previous status quo).
25
+ The Codex `env_vars` trap stays — knobs set in the shell reach the server
26
+ only by hand-editing `config.toml` — and the same knob keeps two
27
+ vocabularies across hosts.
28
+
29
+ ## Consequences
30
+
31
+ One file — `$XDG_CONFIG_HOME/deepseek-web-retrieval/config.json`, default
32
+ `~/.config/deepseek-web-retrieval/config.json`, camelCase keys — configures
33
+ every host, and host layers keep winning: installer-written intent and
34
+ explicit per-host overrides always outrank the base, bounding the blast
35
+ radius of a bad shared value. Unknown keys and host-integration keys in the
36
+ file draw a one-line stderr warning and are otherwise ignored. Because a bad
37
+ shared value now fails everywhere at once, every adapter prints a
38
+ deviations-from-defaults summary of its effective configuration.
39
+
40
+ The installer's Codex `env_vars` entry forwards every `DEEPSEEK_*` name, so
41
+ environment overrides work on Codex without hand-editing `config.toml`.
42
+ Design detail (per-adapter composition, validation, visibility, rollout)
43
+ lives in the [layering design note](../design/config-layering-design.md).
@@ -0,0 +1,273 @@
1
+ # Claude Code
2
+
3
+ Claude Code adapter for [DeepSeek Web Retrieval](../../README.md).
4
+
5
+ Integration mechanism: MCP server over stdio, registered in Claude Code's own MCP config
6
+ files — `.mcp.json` at the project root (project scope) or the top-level `mcpServers` key
7
+ of `~/.claude.json` (user scope, where `claude mcp add --scope user` writes). Claude Code
8
+ negotiates MCP revision `2026-07-28`; the server accepts clients from `2024-11-05` through
9
+ `2026-07-28`, so the same build works for other MCP hosts (see
10
+ [the compatibility research](../research/mcp-compatibility.md)).
11
+
12
+ When registered, it adds:
13
+
14
+ - A `deepseek_web_search` tool backed by DeepSeek Search
15
+ - A `deepseek_fetch` tool backed by DeepSeek Fetch
16
+
17
+ The tools appear in Claude Code as `mcp__deepseek__deepseek_web_search` and
18
+ `mcp__deepseek__deepseek_fetch`. Claude Code's built-in `WebSearch` and `WebFetch` tools
19
+ are not replaced; see [Control tiers](#control-tiers) for routing them away.
20
+
21
+ For shared search/fetch limits, retries, and privacy behavior, see
22
+ [General behavior](../../README.md#general-behavior).
23
+ For configuration layers, the full knob mapping, and defaults, see the
24
+ [configuration guide](../configuration.md).
25
+
26
+ ## Requirements
27
+
28
+ - Claude Code at the tested version in [`docs/hosts.jsonc`](../hosts.jsonc); init reports
29
+ version drift, and newer versions may require rechecking routing behavior
30
+ - Node.js 20 or newer (`npx` runs the server)
31
+ - A DeepSeek account with access to `/v1/tools/search` and `/v1/tools/fetch`
32
+ - A funded DeepSeek account; Search and Fetch are billed per successful request
33
+ - `DEEPSEEK_API_KEY` set in the shell that launches Claude Code
34
+
35
+ ## Install
36
+
37
+ The quickest option is the installer CLI:
38
+
39
+ ```bash
40
+ npx deepseek-web-retrieval init --host claude-code --scope project \
41
+ --search <tier> --fetch <tier>
42
+ ```
43
+
44
+ It prints the full plan — every file, old value → new value, and notes about existing MCP
45
+ servers — and writes nothing until you pass `--apply`. Add `--pin` to register the exact
46
+ version of the CLI you are running instead of the always-latest package name. `uninstall`
47
+ removes recorded additions and restores snapshotted values that have not since been edited.
48
+ `--scope project` registers the server in `./.mcp.json` (shareable via VCS) and writes tier
49
+ artifacts to `./.claude/settings.json` and `./CLAUDE.md`; `--scope global` uses
50
+ `~/.claude.json`, `~/.claude/settings.json`, and `~/.claude/CLAUDE.md` instead — the two
51
+ scopes use disjoint files, so both can coexist. An `off` capability is written as the
52
+ tool's full id in the `permissions.deny` array — Claude Code's own mechanism, which removes
53
+ the tool from the model's context while the server keeps running — merging with any entries
54
+ you put there yourself. When `claude` is on PATH, `init` also probes `claude --version` and
55
+ adds a plan note when the installed release differs from the tested lock (see
56
+ [hosts.jsonc](../hosts.jsonc)) — version-anchored behavior such as the deny rules may have
57
+ drifted between the two.
58
+
59
+ For an existing installer-managed setup, follow the
60
+ [upgrade instructions](../integration.md#upgrading-existing-installs) from the original
61
+ project directory and scope.
62
+
63
+ ### Manual install
64
+
65
+ Add the server to `.mcp.json` at the project root:
66
+
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "deepseek": {
71
+ "command": "npx",
72
+ "args": ["-y", "deepseek-web-retrieval"],
73
+ "env": { "DEEPSEEK_API_KEY": "${DEEPSEEK_API_KEY}" }
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ > [!NOTE]
80
+ > `"${DEEPSEEK_API_KEY}"` is Claude Code's own `${VAR}` expansion: the key is read from
81
+ > the environment that launches Claude Code and never sits in the file. A literal value
82
+ > works too if you prefer it in the file. For a user-scope install, the same entry goes
83
+ > under the top-level `mcpServers` key of `~/.claude.json`, or use
84
+ > `claude mcp add --scope user --env 'DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}' deepseek -- npx -y deepseek-web-retrieval`.
85
+
86
+ Claude Code asks once per project whether to use servers from `.mcp.json`; approve the
87
+ `deepseek` server when prompted (or via the `/mcp` menu), or its tools will not load in
88
+ that project.
89
+
90
+ To disable one capability, add the tool's full id to `permissions.deny` in
91
+ `.claude/settings.json` — Claude Code removes the tool from the model's context while the
92
+ server keeps running:
93
+
94
+ ```json
95
+ {
96
+ "permissions": {
97
+ "deny": ["mcp__deepseek__deepseek_web_search"]
98
+ }
99
+ }
100
+ ```
101
+
102
+ The ids match the tool names verbatim (`mcp__deepseek__deepseek_web_search`,
103
+ `mcp__deepseek__deepseek_fetch`). The server also honors
104
+ `DEEPSEEK_ENABLE_SEARCH`/`DEEPSEEK_ENABLE_FETCH` in the server's `env` block if you prefer
105
+ switching the tools off at the source; the installer uses `permissions.deny`.
106
+
107
+ ### Local checkout
108
+
109
+ ```bash
110
+ git clone https://github.com/gwokhou/deepseek-web-retrieval.git
111
+ cd deepseek-web-retrieval && bun install && bun run build
112
+ ```
113
+
114
+ ```json
115
+ {
116
+ "mcpServers": {
117
+ "deepseek": {
118
+ "command": "node",
119
+ "args": ["/absolute/path/to/deepseek-web-retrieval/dist/cli.js"],
120
+ "env": { "DEEPSEEK_API_KEY": "${DEEPSEEK_API_KEY}" }
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ ## Control tiers
127
+
128
+ Claude Code ships built-in `WebSearch` and `WebFetch` tools; neither is replaceable, so
129
+ both capabilities use exclusion for the forced tier:
130
+
131
+ | Capability | Optional | Preferred | Forced |
132
+ | --- | --- | --- | --- |
133
+ | Search | Tools registered; the built-in `WebSearch` stays available | Plus a CLAUDE.md rules block steering the model to `mcp__deepseek__deepseek_web_search` | Exclusion: `permissions.deny: ["WebSearch"]` |
134
+ | Fetch | Tools registered; the built-in `WebFetch` stays available | Plus the rules block | Exclusion: `permissions.deny: ["WebFetch"]` |
135
+
136
+ The installer maps each tier to these artifacts. Deny entries land in
137
+ `~/.claude/settings.json` (user) or `./.claude/settings.json` (project) and merge with
138
+ your existing `permissions.deny` entries. The CLAUDE.md rules block lands in
139
+ `~/.claude/CLAUDE.md` (user) or `./CLAUDE.md` (project); Claude Code loads the user file
140
+ plus every CLAUDE.md from the cwd up the directory tree, concatenating the levels — so
141
+ the block steers wherever it is installed. Under a forced tier, other web-capable MCP
142
+ servers detected in the config are denied by server-level rules (`mcp__<server>` denies
143
+ every tool of that server), so routing stays exclusive.
144
+
145
+ > [!WARNING]
146
+ > Deny rules outrank allow rules at every settings level and **cannot be carved out** —
147
+ > denying `WebSearch` or `WebFetch` removes the tool for every workflow in the install's
148
+ > scope (every project on the machine for `--scope global`). A managed policy layer
149
+ > (`managed-settings.json`) outranks both files; the installer reports when one exists.
150
+
151
+ ## Authentication
152
+
153
+ `DEEPSEEK_API_KEY` is the only source; MCP has no host credential store. It is forwarded
154
+ through the server's `env` block as `${DEEPSEEK_API_KEY}` and read per request, sent only
155
+ to `api.deepseek.com` as a Bearer token. Without it the server exits at startup with a
156
+ message on stderr.
157
+
158
+ ## Usage
159
+
160
+ Ask naturally:
161
+
162
+ ```text
163
+ Search the web for the latest Claude Code MCP config changes and cite the sources.
164
+ Fetch https://code.claude.com/docs/en/mcp and summarize the config keys.
165
+ ```
166
+
167
+ The model calls `mcp__deepseek__deepseek_web_search` or `mcp__deepseek__deepseek_fetch`.
168
+
169
+ The search tool accepts an optional `original_question` argument — the user's question in
170
+ their own words — forwarded as DeepSeek's `ori_question` to sharpen search intent; omit it
171
+ when the query already stands alone.
172
+
173
+ Tool output is capped at 25k tokens by default (`MAX_MCP_OUTPUT_TOKENS`), and successful
174
+ text results longer than 50,000 characters are saved to a file and referenced — not
175
+ dropped silently.
176
+
177
+ ## Configuration
178
+
179
+ Set these in the server's `env` block in `.mcp.json` (or `~/.claude.json`). Values map onto
180
+ the shared option pipeline, so defaults and clamping match the other adapters.
181
+ `DEEPSEEK_API_KEY` goes in the same block as the `${VAR}` expansion.
182
+
183
+ > [!NOTE]
184
+ > A shared config file — `~/.config/deepseek-web-retrieval/config.json`
185
+ > (Windows: `%APPDATA%\deepseek-web-retrieval\config.json`), camelCase keys —
186
+ > sits under the environment layer and works with no env forwarding at all: the server
187
+ > reads the file itself, making it the way to set knobs without editing the MCP entry. The
188
+ > file carries neither the API key nor the host-integration switches. Run
189
+ > `npx deepseek-web-retrieval config` to see every knob's effective value and source
190
+ > (it replays file warnings visibly and exits 1 on file problems); `config --search "<query>"`
191
+ > verifies key, config, and network with one billed search.
192
+
193
+ ### Search
194
+
195
+ The "Config file" column names each key's camelCase twin in the shared config file —
196
+ one vocabulary pair, one pipeline. `—` marks host-integration entries the file rejects.
197
+
198
+ | Env variable | Config file | Default | Description |
199
+ | --- | --- | --- | --- |
200
+ | `DEEPSEEK_ENABLE_SEARCH` | — | `true` | Register the search tool. Only an explicit `false`/`0`/`no`/`off` disables it; at least one tool must stay enabled. |
201
+ | `DEEPSEEK_SEARCH_COUNT` | `count` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
202
+ | `DEEPSEEK_SEARCH_MAX_RESULTS` | `maxResults` | `10` | Results returned to the model after filtering, 0–50; defaults to `DEEPSEEK_SEARCH_COUNT` when unset. `0` returns no results. |
203
+ | `DEEPSEEK_SEARCH_MIN_RERANK` | `minRerank` | `0` | Drop results below this relevance score (0–3); results without a finite score pass at zero and are dropped at a positive threshold. |
204
+ | `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `minAuthority` | `0` | Drop known authorities below this tier. Authority is 0–4. |
205
+ | `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `allowUnknownAuthority` | `true` | Keep results whose authority is missing or unknown. Set `false`/`0`/`no`/`off` to drop them. |
206
+ | `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | `blockedDomains` | unset | Comma-separated; adds `-site:` operators to the query. |
207
+ | `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | `includeDomains` | unset | Comma-separated; restricts results with `site:` operators. |
208
+ | `DEEPSEEK_SEARCH_LANGUAGE` | `language` | unset | Adds a `lang:` operator, for example `zh,en`. |
209
+ | `DEEPSEEK_SEARCH_LOCALIZATION` | `searchLocalization` | unset | Valid IPv4 or IPv6 address sent as DeepSeek's `localization` to prefer geo-localized results; invalid values are ignored. Deployment-level only, never a per-call parameter. |
210
+ | `DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS` | `maxSummaryChars` | `1200` | Per-result summary cap, 0–1,000,000. |
211
+ | `DEEPSEEK_SEARCH_TIMEOUT_MS` | `searchTimeoutMs` | `30000` | Search timeout, 1,000–120,000 ms. |
212
+
213
+ ### Fetch
214
+
215
+ | Env variable | Config file | Default | Description |
216
+ | --- | --- | --- | --- |
217
+ | `DEEPSEEK_ENABLE_FETCH` | — | `true` | Register the fetch tool. Same false-only semantics as `DEEPSEEK_ENABLE_SEARCH`. |
218
+ | `DEEPSEEK_FETCH_MAX_CHARS` | `fetchMaxChars` | `16000` | Maximum characters returned, including the title, 0–1,000,000. |
219
+ | `DEEPSEEK_FETCH_TIMEOUT_MS` | `fetchTimeoutMs` | `30000` | Fetch timeout, 1,000–120,000 ms. |
220
+ | `DEEPSEEK_FETCH_ALLOWED_DOMAINS` | `fetchAllowedDomains` | unset | If set, only these hosts and subdomains may be fetched. |
221
+ | `DEEPSEEK_FETCH_BLOCKED_DOMAINS` | `fetchBlockedDomains` | unset | Always block these hosts and subdomains. Blocked wins over allowed. |
222
+ | `DEEPSEEK_FETCH_BLOCK_PRIVATE_HOSTS` | `fetchBlockPrivateHosts` | `true` | Reject localhost, private IPs, `.local`, `.internal`, and similar hosts. |
223
+
224
+ ### Credentials and retries
225
+
226
+ | Env variable | Config file | Default | Description |
227
+ | --- | --- | --- | --- |
228
+ | `DEEPSEEK_API_KEY` | — | required | DeepSeek API key, expanded from the launching environment via `${DEEPSEEK_API_KEY}`. The server refuses to start without it. |
229
+ | `DEEPSEEK_MAX_RETRIES` | `maxRetries` | `2` | Retries for 408, 425, 429, and 5xx responses, 0–5. |
230
+ | `DEEPSEEK_RETRY_BASE_DELAY_MS` | `retryBaseDelayMs` | `250` | Base delay for exponential backoff. `Retry-After` is honored up to 10 seconds. |
231
+
232
+ ## Troubleshooting
233
+
234
+ | Error or symptom | What to do |
235
+ | --- | --- |
236
+ | Server fails to start or `HTTP 401` from the tools | `DEEPSEEK_API_KEY` must be set in the environment that launches Claude Code; the `${DEEPSEEK_API_KEY}` expansion in the server's `env` block reads it at launch. |
237
+ | Project tools missing after install | Claude Code asks once per project whether to use `.mcp.json` servers; approve the `deepseek` server when prompted or via the `/mcp` menu. |
238
+ | 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. |
239
+ | Forced tier still shows the built-in tool | The kill switch is the bare deny rule in `permissions.deny` at the install's scope; a managed policy layer (`managed-settings.json`) outranks it — the plan reports when one exists. |
240
+ | Routing not exclusive | Other MCP servers stay registered and are noted in the plan. Under a forced tier, web-capable servers detected in the config (and the other scope's file) are denied by server-level `mcp__<server>` rules; a server added after install is not covered — re-run init. |
241
+ | Fetch results appear as a file reference instead of content | Tool output is capped at 25k tokens by default; raise `MAX_MCP_OUTPUT_TOKENS` or lower `DEEPSEEK_FETCH_MAX_CHARS`. |
242
+ | `HTTP 402`, `HTTP 429` | Top up the DeepSeek account; wait, reduce concurrency, or set `DEEPSEEK_MAX_RETRIES`. |
243
+ | Fetch fails on localhost or an internal URL | Use a local fetch tool; DeepSeek cannot reach private networks. |
244
+
245
+ ## Compatibility
246
+
247
+ | Component | Version |
248
+ | --- | --- |
249
+ | Claude Code | locked in [`docs/hosts.jsonc`](../hosts.jsonc) |
250
+ | MCP revision | `2026-07-28` negotiated; server accepts `2024-11-05`–`2026-07-28` |
251
+ | MCP SDK | `@modelcontextprotocol/server` 2.3.1 |
252
+ | Runtime | Node.js 20+ (stdio) |
253
+ | DeepSeek API | `/v1/tools/search`, `/v1/tools/fetch` |
254
+
255
+ Tested with Claude Code at the `docs/hosts.jsonc` lock on Linux over stdio: the keyless
256
+ Docker e2e verifies the installer's registration, tiers, and uninstall with a real
257
+ `claude mcp list` handshake, and the billed scenario (`bun run test:e2e claude-code`,
258
+ requires `DEEPSEEK_API_KEY`) runs headless prompts end to end with the deny kill switch
259
+ asserted for both capabilities.
260
+
261
+ ## Uninstall
262
+
263
+ ```bash
264
+ npx deepseek-web-retrieval uninstall --host claude-code --scope project
265
+ ```
266
+
267
+ This removes the recorded `mcpServers.deepseek` entry and the installer-appended
268
+ `permissions.deny` entries only when their current values still match what the installer
269
+ wrote, removes the managed rules block, and deletes installer-created config files only
270
+ when left empty. Later user edits to configuration values are kept and reported. For a
271
+ manual installation, remove the `deepseek` entry from `.mcp.json` (or `~/.claude.json`),
272
+ any `deepseek-web-retrieval` rules block in your `CLAUDE.md`, and the deny entries the
273
+ setup added to `settings.json` if they were only for this integration.
@@ -24,6 +24,8 @@ both the text and `structuredContent`.
24
24
 
25
25
  For shared search/fetch limits, retries, and privacy behavior, see
26
26
  [General behavior](../../README.md#general-behavior).
27
+ For configuration layers, the full knob mapping, and defaults, see the
28
+ [configuration guide](../configuration.md).
27
29
 
28
30
  ## Requirements
29
31
 
@@ -44,7 +46,9 @@ npx deepseek-web-retrieval init --host codex --scope global \
44
46
  ```
45
47
 
46
48
  It prints the full plan — every file, old value → new value, and notes about existing
47
- MCP servers — and writes nothing until you pass `--apply`. `uninstall` removes recorded
49
+ MCP servers — and writes nothing until you pass `--apply`. Add `--pin` to register the
50
+ exact version of the CLI you are running instead of the always-latest package name.
51
+ `uninstall` removes recorded
48
52
  additions and restores snapshotted values that have not since been edited. Codex's config
49
53
  lives in `~/.codex/config.toml` at the user level only, so the server registration and kill switch
50
54
  are written there even for `--scope project`; the rules file follows the scope. An `off`
@@ -142,45 +146,64 @@ Fetch https://learn.chatgpt.com/docs/extend/mcp and summarize the config keys.
142
146
 
143
147
  The model calls `mcp__deepseek__deepseek_web_search` or `mcp__deepseek__deepseek_fetch`.
144
148
 
149
+ The search tool accepts an optional `original_question` argument — the user's question in
150
+ their own words — forwarded as DeepSeek's `ori_question` to sharpen search intent; omit it
151
+ when the query already stands alone.
152
+
145
153
  ## Configuration
146
154
 
147
155
  Set these in the server's `env` table. Values map onto the shared option pipeline, so
148
156
  defaults and clamping match the other adapters. `DEEPSEEK_API_KEY` goes in `env_vars`.
149
157
 
158
+ > [!NOTE]
159
+ > A shared config file — `~/.config/deepseek-web-retrieval/config.json`
160
+ > (Windows: `%APPDATA%\deepseek-web-retrieval\config.json`), camelCase keys —
161
+ > sits under the environment layer and works with no env forwarding at all: the server
162
+ > reads the file itself, making it the way to set knobs without editing `env_vars` (the
163
+ > installer's `env_vars` entry also forwards every `DEEPSEEK_*` name). The file carries
164
+ > neither the API key nor the host-integration switches. Run
165
+ > `npx deepseek-web-retrieval config` to see every knob's effective value and source
166
+ > (it replays file warnings visibly and exits 1 on file problems); `config --search "<query>"`
167
+ > verifies key, config, and network with one billed search.
168
+
150
169
  ### Search
151
170
 
152
- | Env variable | Default | Description |
153
- | --- | --- | --- |
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. |
155
- | `DEEPSEEK_SEARCH_COUNT` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
156
- | `DEEPSEEK_SEARCH_MAX_RESULTS` | `10` | Results returned to the model after filtering, 0–50. `0` returns no results. |
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. |
158
- | `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `0` | Drop known authorities below this tier. Authority is 0–4. |
159
- | `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `true` | Keep results whose authority is missing or unknown. Set `false`/`0`/`no`/`off` to drop them. |
160
- | `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | unset | Comma-separated; adds `-site:` operators to the query. |
161
- | `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | unset | Comma-separated; restricts results with `site:` operators. |
162
- | `DEEPSEEK_SEARCH_LANGUAGE` | unset | Adds a `lang:` operator, for example `zh,en`. |
163
- | `DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS` | `1200` | Per-result summary cap, 0–1,000,000. |
164
- | `DEEPSEEK_SEARCH_TIMEOUT_MS` | `30000` | Search timeout, 1,000–120,000 ms. |
171
+ The "Config file" column names each key's camelCase twin in the shared config file —
172
+ one vocabulary pair, one pipeline. `—` marks host-integration entries the file rejects.
173
+
174
+ | Env variable | Config file | Default | Description |
175
+ | --- | --- | --- | --- |
176
+ | `DEEPSEEK_ENABLE_SEARCH` | — | `true` | Register the search tool. Only an explicit `false`/`0`/`no`/`off` disables it; at least one tool must stay enabled. |
177
+ | `DEEPSEEK_SEARCH_COUNT` | `count` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
178
+ | `DEEPSEEK_SEARCH_MAX_RESULTS` | `maxResults` | `10` | Results returned to the model after filtering, 0–50; defaults to `DEEPSEEK_SEARCH_COUNT` when unset. `0` returns no results. |
179
+ | `DEEPSEEK_SEARCH_MIN_RERANK` | `minRerank` | `0` | Drop results below this relevance score (0–3); results without a finite score pass at zero and are dropped at a positive threshold. |
180
+ | `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `minAuthority` | `0` | Drop known authorities below this tier. Authority is 0–4. |
181
+ | `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `allowUnknownAuthority` | `true` | Keep results whose authority is missing or unknown. Set `false`/`0`/`no`/`off` to drop them. |
182
+ | `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | `blockedDomains` | unset | Comma-separated; adds `-site:` operators to the query. |
183
+ | `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | `includeDomains` | unset | Comma-separated; restricts results with `site:` operators. |
184
+ | `DEEPSEEK_SEARCH_LANGUAGE` | `language` | unset | Adds a `lang:` operator, for example `zh,en`. |
185
+ | `DEEPSEEK_SEARCH_LOCALIZATION` | `searchLocalization` | unset | Valid IPv4 or IPv6 address sent as DeepSeek's `localization` to prefer geo-localized results; invalid values are ignored. Deployment-level only, never a per-call parameter. |
186
+ | `DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS` | `maxSummaryChars` | `1200` | Per-result summary cap, 0–1,000,000. |
187
+ | `DEEPSEEK_SEARCH_TIMEOUT_MS` | `searchTimeoutMs` | `30000` | Search timeout, 1,000–120,000 ms. |
165
188
 
166
189
  ### Fetch
167
190
 
168
- | Env variable | Default | Description |
169
- | --- | --- | --- |
170
- | `DEEPSEEK_ENABLE_FETCH` | `true` | Register the fetch tool. Same false-only semantics as `DEEPSEEK_ENABLE_SEARCH`. |
171
- | `DEEPSEEK_FETCH_MAX_CHARS` | `16000` | Maximum characters returned, including the title, 0–1,000,000. |
172
- | `DEEPSEEK_FETCH_TIMEOUT_MS` | `30000` | Fetch timeout, 1,000–120,000 ms. |
173
- | `DEEPSEEK_FETCH_ALLOWED_DOMAINS` | unset | If set, only these hosts and subdomains may be fetched. |
174
- | `DEEPSEEK_FETCH_BLOCKED_DOMAINS` | unset | Always block these hosts and subdomains. Blocked wins over allowed. |
175
- | `DEEPSEEK_FETCH_BLOCK_PRIVATE_HOSTS` | `true` | Reject localhost, private IPs, `.local`, `.internal`, and similar hosts. |
191
+ | Env variable | Config file | Default | Description |
192
+ | --- | --- | --- | --- |
193
+ | `DEEPSEEK_ENABLE_FETCH` | — | `true` | Register the fetch tool. Same false-only semantics as `DEEPSEEK_ENABLE_SEARCH`. |
194
+ | `DEEPSEEK_FETCH_MAX_CHARS` | `fetchMaxChars` | `16000` | Maximum characters returned, including the title, 0–1,000,000. |
195
+ | `DEEPSEEK_FETCH_TIMEOUT_MS` | `fetchTimeoutMs` | `30000` | Fetch timeout, 1,000–120,000 ms. |
196
+ | `DEEPSEEK_FETCH_ALLOWED_DOMAINS` | `fetchAllowedDomains` | unset | If set, only these hosts and subdomains may be fetched. |
197
+ | `DEEPSEEK_FETCH_BLOCKED_DOMAINS` | `fetchBlockedDomains` | unset | Always block these hosts and subdomains. Blocked wins over allowed. |
198
+ | `DEEPSEEK_FETCH_BLOCK_PRIVATE_HOSTS` | `fetchBlockPrivateHosts` | `true` | Reject localhost, private IPs, `.local`, `.internal`, and similar hosts. |
176
199
 
177
200
  ### Credentials and retries
178
201
 
179
- | Env variable | Default | Description |
180
- | --- | --- | --- |
181
- | `DEEPSEEK_API_KEY` | required | DeepSeek API key, forwarded via `env_vars`. The server refuses to start without it. |
182
- | `DEEPSEEK_MAX_RETRIES` | `0` | Retries for 408, 425, 429, and 5xx responses, 0–5. |
183
- | `DEEPSEEK_RETRY_BASE_DELAY_MS` | `250` | Base delay for exponential backoff. `Retry-After` is honored up to 10 seconds. |
202
+ | Env variable | Config file | Default | Description |
203
+ | --- | --- | --- | --- |
204
+ | `DEEPSEEK_API_KEY` | — | required | DeepSeek API key, forwarded via `env_vars`. The server refuses to start without it. |
205
+ | `DEEPSEEK_MAX_RETRIES` | `maxRetries` | `2` | Retries for 408, 425, 429, and 5xx responses, 0–5. |
206
+ | `DEEPSEEK_RETRY_BASE_DELAY_MS` | `retryBaseDelayMs` | `250` | Base delay for exponential backoff. `Retry-After` is honored up to 10 seconds. |
184
207
 
185
208
  ## Troubleshooting
186
209
 
@@ -15,6 +15,8 @@ if you want all Markdown fetches to go through DeepSeek.
15
15
 
16
16
  For shared search/fetch limits, retries, and privacy behavior, see
17
17
  [General behavior](../../README.md#general-behavior).
18
+ For configuration layers, the full knob mapping, and defaults, see the
19
+ [configuration guide](../configuration.md).
18
20
 
19
21
  ## Requirements
20
22
 
@@ -38,7 +40,8 @@ npx deepseek-web-retrieval init --host opencode --scope <project|global> \
38
40
  ```
39
41
 
40
42
  It prints the full plan — every file, old value → new value — and writes nothing until
41
- you pass `--apply`. It snapshots the current `websearch.provider` before the forced tier
43
+ you pass `--apply`. Add `--pin` to register the exact version of the CLI you are running
44
+ instead of the always-latest package name. It snapshots the current `websearch.provider` before the forced tier
42
45
  replaces it, merges `permissions` without touching yours, preserves comments in
43
46
  `opencode.jsonc`, and `uninstall` restores recorded values that have not since been edited.
44
47
  Search has no `preferred` tier on OpenCode (the provider is host-level config, never a
@@ -112,11 +115,14 @@ follow the [upgrade instructions](../integration.md#upgrading-existing-installs)
112
115
  **Global checkout**
113
116
 
114
117
  ```bash
115
- git clone https://github.com/gwokhou/deepseek-web-retrieval.git \
118
+ git clone https://github.com/guokhou/deepseek-web-retrieval.git \
116
119
  ~/.config/opencode/vendor/deepseek-web-retrieval
117
120
  ```
118
121
 
119
- Use `"package": "./vendor/deepseek-web-retrieval"`.
122
+ Use `"package": "./vendor/deepseek-web-retrieval"`. On Windows the global config root is
123
+ `%APPDATA%\opencode\` (unless `XDG_CONFIG_HOME` is explicitly set), so clone into
124
+ `%APPDATA%\opencode\vendor\deepseek-web-retrieval` there — the same root the installer
125
+ writes to.
120
126
 
121
127
  **Project checkout**
122
128
 
@@ -131,7 +137,8 @@ Use `"package": "./.opencode/vendor/deepseek-web-retrieval"`.
131
137
  **Auto-discovery**
132
138
 
133
139
  Clone or copy the repository into `~/.config/opencode/plugins/deepseek-web-retrieval` or
134
- `.opencode/plugins/deepseek-web-retrieval`. Auto-discovered plugins do not receive options,
140
+ `.opencode/plugins/deepseek-web-retrieval` (on Windows, `%APPDATA%\opencode\plugins\…`).
141
+ Auto-discovered plugins do not receive options,
135
142
  so `webfetch` stays in place; the `deepseek` search provider and `deepseek_fetch` are still
136
143
  added.
137
144
 
@@ -169,47 +176,66 @@ Fetch https://opencode.ai/v2/docs/build/plugins and summarize the plugin lifecyc
169
176
  The model calls `websearch` (provider `deepseek`) or `deepseek_fetch`. When
170
177
  `fetchOverrideWebfetch` is enabled, Markdown fetches go through `webfetch` instead.
171
178
 
179
+ DeepSeek's optional `ori_question` is not sent on this path: OpenCode's `websearch` tool
180
+ input is fixed to `query` at 2.0.32, so the provider has no channel for it (see the
181
+ [research note](../research/ori-question-sourcing.md)).
182
+
172
183
  ## Configuration
173
184
 
174
185
  Set these in the plugin's `options` object.
175
186
 
187
+ > [!NOTE]
188
+ > These options use the same camelCase vocabulary as the shared config file
189
+ > (`~/.config/deepseek-web-retrieval/config.json`; on Windows,
190
+ > `%APPDATA%\deepseek-web-retrieval\config.json`), with the `DEEPSEEK_*` environment in
191
+ > between: `shared file < environment < options`. Configure once in the file — it works on
192
+ > every host — and override per host here in `options`; existing `options` keep winning
193
+ > exactly as before. `npx deepseek-web-retrieval config` shows the file/env/defaults view
194
+ > of every knob (these `options` sit above it, so check them here when it differs).
195
+
176
196
  ### Search
177
197
 
178
- | Option | Default | Description |
179
- | --- | --- | --- |
180
- | `searchEnabled` | `true` | Register the DeepSeek search provider at all. Only an explicit `false` disables it. |
181
- | `count` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
182
- | `maxResults` | `10` | Results returned to the model after filtering, 0–50. `0` returns no results. |
183
- | `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. |
184
- | `minAuthority` | `0` | Drop known authorities below this tier. Authority is 0–4. |
185
- | `allowUnknownAuthority` | `true` | Keep results whose authority is missing or unknown. Only an explicit `false` disables it. |
186
- | `blockedDomains` | `[]` | Add `-site:` operators to the search query. |
187
- | `includeDomains` | `[]` | Restrict results with `site:` operators. |
188
- | `language` | unset | Add a `lang:` operator, for example `zh,en`. |
189
- | `maxSummaryChars` | `1200` | Per-result summary cap, 0–1,000,000. |
190
- | `searchTimeoutMs` | `30000` | Search timeout, 1,000–120,000 ms. |
191
- | `setDefaultWebsearch` | `false` | Make `deepseek` the default websearch provider when the plugin loads. |
198
+ The "Environment" column names each option's `DEEPSEEK_*` twin — the plugin reads the
199
+ environment as the layer beneath these options, and the same camelCase keys also work
200
+ in the shared config file beneath both. `—` marks host-integration options with no
201
+ environment or file channel.
202
+
203
+ | Option | Environment | Default | Description |
204
+ | --- | --- | --- | --- |
205
+ | `searchEnabled` | `DEEPSEEK_ENABLE_SEARCH` | `true` | Register the DeepSeek search provider at all. Only an explicit `false` disables it. |
206
+ | `count` | `DEEPSEEK_SEARCH_COUNT` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
207
+ | `maxResults` | `DEEPSEEK_SEARCH_MAX_RESULTS` | `10` | Results returned to the model after filtering, 0–50; defaults to `count` when unset. `0` returns no results. |
208
+ | `minRerank` | `DEEPSEEK_SEARCH_MIN_RERANK` | `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. |
209
+ | `minAuthority` | `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `0` | Drop known authorities below this tier. Authority is 0–4. |
210
+ | `allowUnknownAuthority` | `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `true` | Keep results whose authority is missing or unknown. Only an explicit `false` disables it. |
211
+ | `blockedDomains` | `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | `[]` | Add `-site:` operators to the search query. |
212
+ | `includeDomains` | `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | `[]` | Restrict results with `site:` operators. |
213
+ | `language` | `DEEPSEEK_SEARCH_LANGUAGE` | unset | Add a `lang:` operator, for example `zh,en`. |
214
+ | `searchLocalization` | `DEEPSEEK_SEARCH_LOCALIZATION` | unset | Valid IPv4 or IPv6 address sent as DeepSeek's `localization` to prefer geo-localized results; invalid values are ignored. Deployment-level only, never a per-call parameter. |
215
+ | `maxSummaryChars` | `DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS` | `1200` | Per-result summary cap, 0–1,000,000. |
216
+ | `searchTimeoutMs` | `DEEPSEEK_SEARCH_TIMEOUT_MS` | `30000` | Search timeout, 1,000–120,000 ms. |
217
+ | `setDefaultWebsearch` | — | `false` | Make `deepseek` the default websearch provider when the plugin loads. |
192
218
 
193
219
  ### Fetch
194
220
 
195
- | Option | Default | Description |
196
- | --- | --- | --- |
197
- | `fetchEnabled` | `true` | Register any DeepSeek fetch tooling (the separate tool and the webfetch override). |
198
- | `fetchMaxChars` | `16000` | Maximum characters returned, including the title, 0–1,000,000. |
199
- | `fetchTimeoutMs` | `30000` | Fetch timeout, 1,000–120,000 ms. |
200
- | `fetchOverrideWebfetch` | `false` | Replace the built-in `webfetch`. |
201
- | `fetchRegisterTool` | `true` | Register the separate `deepseek_fetch` tool. |
202
- | `fetchAllowedDomains` | `[]` | If non-empty, only these hosts and subdomains may be fetched. |
203
- | `fetchBlockedDomains` | `[]` | Always block these hosts and subdomains. Blocked wins over allowed. |
204
- | `fetchBlockPrivateHosts` | `true` | Reject localhost, private IPs, `.local`, `.internal`, and similar hosts. |
221
+ | Option | Environment | Default | Description |
222
+ | --- | --- | --- | --- |
223
+ | `fetchEnabled` | `DEEPSEEK_ENABLE_FETCH` | `true` | Register any DeepSeek fetch tooling (the separate tool and the webfetch override). |
224
+ | `fetchMaxChars` | `DEEPSEEK_FETCH_MAX_CHARS` | `16000` | Maximum characters returned, including the title, 0–1,000,000. |
225
+ | `fetchTimeoutMs` | `DEEPSEEK_FETCH_TIMEOUT_MS` | `30000` | Fetch timeout, 1,000–120,000 ms. |
226
+ | `fetchOverrideWebfetch` | — | `false` | Replace the built-in `webfetch`. |
227
+ | `fetchRegisterTool` | — | `true` | Register the separate `deepseek_fetch` tool. |
228
+ | `fetchAllowedDomains` | `DEEPSEEK_FETCH_ALLOWED_DOMAINS` | `[]` | If non-empty, only these hosts and subdomains may be fetched. |
229
+ | `fetchBlockedDomains` | `DEEPSEEK_FETCH_BLOCKED_DOMAINS` | `[]` | Always block these hosts and subdomains. Blocked wins over allowed. |
230
+ | `fetchBlockPrivateHosts` | `DEEPSEEK_FETCH_BLOCK_PRIVATE_HOSTS` | `true` | Reject localhost, private IPs, `.local`, `.internal`, and similar hosts. |
205
231
 
206
232
  ### Credentials and retries
207
233
 
208
- | Option | Default | Description |
209
- | --- | --- | --- |
210
- | `apiKeySource` | `"auto"` | Credential strategy: `"auto"`, `"opencode"`, or `"env"`. |
211
- | `maxRetries` | `0` | Retries for 408, 425, 429, and 5xx responses, 0–5. |
212
- | `retryBaseDelayMs` | `250` | Base delay for exponential backoff. `Retry-After` is honored up to 10 seconds. |
234
+ | Option | Environment | Default | Description |
235
+ | --- | --- | --- | --- |
236
+ | `apiKeySource` | — | `"auto"` | Credential strategy: `"auto"`, `"opencode"`, or `"env"`. |
237
+ | `maxRetries` | `DEEPSEEK_MAX_RETRIES` | `2` | Retries for 408, 425, 429, and 5xx responses, 0–5. |
238
+ | `retryBaseDelayMs` | `DEEPSEEK_RETRY_BASE_DELAY_MS` | `250` | Base delay for exponential backoff. `Retry-After` is honored up to 10 seconds. |
213
239
 
214
240
  ## Troubleshooting
215
241