deepseek-web-retrieval 0.9.0 → 0.10.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 +9 -1
- package/README.md +25 -16
- package/dist/cli.js +33 -14
- package/docs/adr/0001-refuse-drift-between-plan-and-apply.md +7 -3
- package/docs/adr/0002-shared-config-base-layer.md +5 -1
- package/docs/adr/0003-need-content-per-call-boolean.md +49 -0
- package/docs/agents/claude-code.md +8 -4
- package/docs/agents/codex.md +7 -7
- package/docs/agents/domain.md +2 -2
- package/docs/agents/issue-tracker.md +5 -5
- package/docs/agents/opencode-v2.md +21 -15
- package/docs/agents/pi.md +35 -11
- package/docs/configuration.md +19 -9
- package/docs/design/config-layering-design.md +10 -5
- package/docs/design/installer-design.md +53 -21
- package/docs/design/search-content-design.md +133 -0
- package/docs/integration.md +19 -16
- package/docs/research/control-matrix.md +39 -34
- package/docs/research/mcp-compatibility.md +3 -1
- package/docs/research/ori-question-sourcing.md +19 -19
- package/docs/research/result-quality-passthrough.md +9 -7
- package/docs/research/search-api-defaults.md +4 -2
- package/docs/research/search-inline-content.md +49 -9
- package/package.json +1 -1
- package/src/core/client.ts +9 -2
- package/src/core/config-summary.ts +2 -0
- package/src/core/constants.ts +13 -0
- package/src/core/descriptions.ts +7 -1
- package/src/core/env.ts +4 -0
- package/src/core/format.ts +10 -1
- package/src/core/options.ts +9 -0
- package/src/core/search.ts +12 -4
- package/src/core/types.ts +12 -0
- package/src/mcp/server.ts +4 -1
- package/src/pi/extension.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here, one sentence per item.
|
|
4
4
|
|
|
5
|
+
## 0.10.0
|
|
6
|
+
|
|
7
|
+
- Add `need_content` as an optional per-call boolean on the MCP and Pi search tools: `true` attaches each result's page body as Markdown (structure-preserving, indented into the result list) in place of its summary, and a missing body falls back to the summary; OpenCode's fixed provider input keeps that path summary-only.
|
|
8
|
+
- Add the `searchContentMaxChars` (1,500 characters) and `searchContentTimeoutMs` (60,000 ms) deployment knobs — the content envelope every `need_content` call runs inside, never tool parameters, recorded in ADR-0003 and the search content design note.
|
|
9
|
+
- State the `need_content` facts — excerpt length, latency, price, and the fetch alternative — in the tool and parameter descriptions, with both full-page routes named side by side, and no usage policy: when content is worth it stays the model's per-call judgment.
|
|
10
|
+
- Extend the inline-content research note with the vendor reading that no surveyed architecture couples a content flag to another parameter's value range: budgets are static-range, conditional-effect.
|
|
11
|
+
- Add a keyless `pi-mock` e2e leg that drives a real Pi session search with `need_content: true` against the mock upstream and asserts the flag reached the wire and the page body reached the model, with the digest legs now also asserting `need_content: false` by default.
|
|
12
|
+
|
|
5
13
|
## 0.9.0
|
|
6
14
|
|
|
7
15
|
- Accept an optional per-call `count` (1–50) on the MCP and Pi search tools and an optional per-call `maxChars` (0–1,000,000) on every adapter's fetch, each overriding the configured value for that one call.
|
|
@@ -13,7 +21,7 @@ All notable changes to this project are documented here, one sentence per item.
|
|
|
13
21
|
|
|
14
22
|
## 0.8.0
|
|
15
23
|
|
|
16
|
-
- Add Claude Code as a supported host: an MCP-only integration registered in `.mcp.json` (project scope) or `~/.claude.json` (user scope), with a full installer host module (plan/apply/uninstall, both scopes), a `docs/agents/claude-code.md` guide, a version probe with drift note, and managed-policy detection that reports a `managed-settings.json` layer outranking the install.
|
|
24
|
+
- Add Claude Code as a supported host: an MCP-only integration registered in `.mcp.json` (project scope) or `~/.claude.json` (Claude Code's user scope — global scope in this repo's vocabulary), with a full installer host module (plan/apply/uninstall, both scopes), a `docs/agents/claude-code.md` guide, a version probe with drift note, and managed-policy detection that reports a `managed-settings.json` layer outranking the install.
|
|
17
25
|
- Route Claude Code tiers through its own settings files: forced capabilities deny the built-in `WebSearch`/`WebFetch` via `permissions.deny` in `.claude/settings.json` or `~/.claude/settings.json` (merged with the user's entries), off capabilities deny the full `mcp__deepseek__*` tool ids, and preferred writes a rules block into `CLAUDE.md`; web-capable competitor servers are excluded under forced via server-level `mcp__<server>` deny rules instead of refusing the tier.
|
|
18
26
|
- Add a billed `claude-code` Docker e2e scenario that runs headless `claude -p` prompts with DeepSeek as the LLM over its Anthropic-compatible endpoint (single key for both legs) and asserts the deny-based kill switch keeps the built-in tools out of the session, plus installer/reinit coverage of the new host in the keyless scenarios.
|
|
19
27
|
- Guard Pi's global config files with the shared-file ownership sidecar, and release claims before deleting the install record on uninstall, so two installs under different config homes can no longer claim the same `~/.pi/agent` file.
|
package/README.md
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
Search the web and fetch pages as Markdown through
|
|
6
6
|
[DeepSeek's APIs](https://api-docs.deepseek.com/guides/search), using a **native OpenCode
|
|
7
|
-
plugin**, a **native Pi extension
|
|
8
|
-
the same retrieval core, with results
|
|
7
|
+
plugin**, a **native [Pi](https://pi.dev/) extension** (Pi, the open-source coding agent),
|
|
8
|
+
or **MCP for Codex and Claude Code**. All four share the same retrieval core, with results
|
|
9
|
+
formatted for each host.
|
|
9
10
|
|
|
10
11
|
This is an unofficial community project, not affiliated with DeepSeek or the supported
|
|
11
12
|
host projects.
|
|
@@ -16,8 +17,9 @@ Jump to: [Features](#features) · [Quickstart](#quickstart) · [Usage](#usage)
|
|
|
16
17
|
|
|
17
18
|
## Features
|
|
18
19
|
|
|
19
|
-
- **One package, four hosts** — a native OpenCode v2
|
|
20
|
-
and a stdio MCP server for Codex and Claude Code, all sharing one
|
|
20
|
+
- **One package, four hosts** — a native plugin for OpenCode v2 (the 2.x series), a
|
|
21
|
+
native Pi extension, and a stdio MCP server for Codex and Claude Code, all sharing one
|
|
22
|
+
retrieval core
|
|
21
23
|
- **Routing control per tool** — search and fetch are set independently to optional,
|
|
22
24
|
preferred, forced, or off; the tier table in Usage explains each
|
|
23
25
|
- **Review before anything is written** — `init` prints the full plan and writes only
|
|
@@ -59,8 +61,9 @@ Choose your host:
|
|
|
59
61
|
| Claude Code | MCP server over stdio in `.mcp.json` or `~/.claude.json` | [Claude Code guide](docs/agents/claude-code.md) |
|
|
60
62
|
|
|
61
63
|
Set `DEEPSEEK_API_KEY` in the environment that launches your host; OpenCode can also
|
|
62
|
-
use its stored DeepSeek key. Run `init` for your host to preview a configuration —
|
|
63
|
-
command
|
|
64
|
+
use its stored DeepSeek key. Run `init` for your host to preview a configuration — take
|
|
65
|
+
the quickstart command and swap `--host` and `--scope` for your host's values; each host
|
|
66
|
+
guide shows its recommended tiers.
|
|
64
67
|
|
|
65
68
|
`--search` and `--fetch` each pick a tier — how strongly the host steers toward the
|
|
66
69
|
DeepSeek tool, chosen independently per capability:
|
|
@@ -87,7 +90,8 @@ Files:
|
|
|
87
90
|
upsert marked rules block
|
|
88
91
|
|
|
89
92
|
Notes:
|
|
90
|
-
- Codex stdio children do not inherit the parent environment, so the entry forwards DEEPSEEK_API_KEY and every DEEPSEEK_* option variable explicitly via env_vars; shell-set knobs reach the server without hand-editing config.toml
|
|
93
|
+
- Codex stdio children do not inherit the parent environment, so the entry forwards DEEPSEEK_API_KEY and every DEEPSEEK_* option variable explicitly via env_vars; shell-set knobs reach the server without hand-editing config.toml.
|
|
94
|
+
- The shared config file needs no forwarding at all: the server reads it itself.
|
|
91
95
|
- Rules block upserted in ~/.codex/AGENTS.md; Codex reads ~/.codex/AGENTS.md plus every AGENTS.md from the project root to the cwd.
|
|
92
96
|
- Shared config ownership is recorded in ~/.codex/.config.toml.deepseek-web-retrieval.codex.owner.json; uninstall releases it. Keep this local metadata with the config.
|
|
93
97
|
|
|
@@ -116,19 +120,21 @@ What this adapter layer adds on top of the
|
|
|
116
120
|
own capabilities (Markdown-only output, no JavaScript rendering, no private networks or
|
|
117
121
|
binary files, per-request billing) are documented there.
|
|
118
122
|
|
|
119
|
-
|
|
|
123
|
+
| Setting | Default | Knob |
|
|
120
124
|
| --- | --- | --- |
|
|
121
125
|
| Results per search | 10 | `count` |
|
|
122
126
|
| Fetch output cap | 16,000 characters | `fetchMaxChars` |
|
|
123
127
|
| Search summary cap | 1,200 characters | `maxSummaryChars` |
|
|
124
|
-
|
|
|
128
|
+
| Inline content cap, per result (when requested) | 1,500 characters | `searchContentMaxChars` |
|
|
129
|
+
| Request timeout, body read included | 30 s (60 s for `need_content` calls) | `searchTimeoutMs` / `fetchTimeoutMs` / `searchContentTimeoutMs` |
|
|
125
130
|
| Transient retries | 2 (range 0–5) | `maxRetries` |
|
|
126
131
|
|
|
127
132
|
### Search
|
|
128
133
|
|
|
129
|
-
- Requests summaries
|
|
130
|
-
`snippet`);
|
|
131
|
-
|
|
134
|
+
- Requests summaries by default (`need_content: false`, falling back from `summary` to
|
|
135
|
+
`snippet`); a summary the cap shortens ends with an ellipsis. When a call sets
|
|
136
|
+
`need_content: true`, each result instead carries its page body as Markdown, clamped
|
|
137
|
+
to `searchContentMaxChars` per result — a missing body falls back to the summary.
|
|
132
138
|
- Deduplicates URLs; an empty result set is normal, not an error.
|
|
133
139
|
- Builds `blockedDomains`, `includeDomains`, and `language` into query operators (part of
|
|
134
140
|
DeepSeek's documented search syntax). A combined query over the API's 128-character limit
|
|
@@ -144,8 +150,11 @@ binary files, per-request billing) are documented there.
|
|
|
144
150
|
the Pi extension), the tool accepts an optional `original_question`: the user's
|
|
145
151
|
question in their own words. It is forwarded as DeepSeek's `ori_question`, trimmed
|
|
146
152
|
and truncated to 128 characters; on the Pi extension, a blank input falls back to the
|
|
147
|
-
most recent user message in the session.
|
|
148
|
-
|
|
153
|
+
most recent user message in the session.
|
|
154
|
+
- The same paths also take an optional per-call `count` (1–50), overriding the
|
|
155
|
+
configured result count for that one call, and an optional per-call `need_content`
|
|
156
|
+
boolean requesting page bodies in the results (see
|
|
157
|
+
[search content design](docs/design/search-content-design.md)).
|
|
149
158
|
|
|
150
159
|
### Fetch
|
|
151
160
|
|
|
@@ -228,8 +237,8 @@ bun run check
|
|
|
228
237
|
```
|
|
229
238
|
|
|
230
239
|
Use `bun run lint:fix` to apply Biome's safe lint fixes and formatting. See
|
|
231
|
-
[CONTRIBUTING.md](CONTRIBUTING.md) for development checks
|
|
232
|
-
[automated tag releases](CONTRIBUTING.md#automated-releases).
|
|
240
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for development checks and e2e scenarios, and
|
|
241
|
+
[automated tag releases](CONTRIBUTING.md#automated-releases-maintainers).
|
|
233
242
|
|
|
234
243
|
The test suite mocks the DeepSeek API and the OpenCode and Pi host APIs; real API calls
|
|
235
244
|
require a funded DeepSeek account. Shared retrieval logic lives in `src/core/`; adapters
|
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
18
|
// src/core/constants.ts
|
|
19
|
-
var DEFAULT_API_BASE_URL = "https://api.deepseek.com", SEARCH_ENDPOINT_PATH = "/v1/tools/search", FETCH_ENDPOINT_PATH = "/v1/tools/fetch", SEARCH_ENDPOINT, FETCH_ENDPOINT, DEFAULT_SEARCH_COUNT = 10, DEFAULT_MAX_RESULTS = 10, DEFAULT_MIN_RERANK = 0, DEFAULT_MIN_AUTHORITY = 0, DEFAULT_MAX_SUMMARY_CHARS = 1200, DEFAULT_FETCH_MAX_CHARS = 16000, MAX_FETCH_CHARS = 1e6, MAX_SEARCH_COUNT = 50, DEFAULT_TIMEOUT_MS = 30000, MIN_TIMEOUT_MS = 1000, MAX_TIMEOUT_MS = 120000, MAX_QUERY_CHARS = 128, MAX_ORI_QUESTION_CHARS = 128, MAX_URL_CHARS = 8192, MAX_ERROR_BODY_CHARS = 2000, MAX_RESPONSE_BYTES, FETCH_TRUNCATION_MARKER = `
|
|
19
|
+
var DEFAULT_API_BASE_URL = "https://api.deepseek.com", SEARCH_ENDPOINT_PATH = "/v1/tools/search", FETCH_ENDPOINT_PATH = "/v1/tools/fetch", SEARCH_ENDPOINT, FETCH_ENDPOINT, DEFAULT_SEARCH_COUNT = 10, DEFAULT_MAX_RESULTS = 10, DEFAULT_MIN_RERANK = 0, DEFAULT_MIN_AUTHORITY = 0, DEFAULT_MAX_SUMMARY_CHARS = 1200, DEFAULT_SEARCH_CONTENT_MAX_CHARS = 1500, DEFAULT_SEARCH_CONTENT_TIMEOUT_MS = 60000, DEFAULT_FETCH_MAX_CHARS = 16000, MAX_FETCH_CHARS = 1e6, MAX_SEARCH_COUNT = 50, DEFAULT_TIMEOUT_MS = 30000, MIN_TIMEOUT_MS = 1000, MAX_TIMEOUT_MS = 120000, MAX_QUERY_CHARS = 128, MAX_ORI_QUESTION_CHARS = 128, MAX_URL_CHARS = 8192, MAX_ERROR_BODY_CHARS = 2000, MAX_RESPONSE_BYTES, FETCH_TRUNCATION_MARKER = `
|
|
20
20
|
|
|
21
21
|
[content truncated]`, DEFAULT_API_KEY_SOURCE = "auto", DEFAULT_MAX_RETRIES = 2, MAX_RETRIES = 5, DEFAULT_RETRY_BASE_DELAY_MS = 250, MAX_RETRY_DELAY_MS = 1e4, API_KEY_ENV = "DEEPSEEK_API_KEY", BASE_URL_ENV = "DEEPSEEK_BASE_URL", ENABLE_SEARCH_ENV = "DEEPSEEK_ENABLE_SEARCH", ENABLE_FETCH_ENV = "DEEPSEEK_ENABLE_FETCH", SEARCH_LOCALIZATION_ENV = "DEEPSEEK_SEARCH_LOCALIZATION";
|
|
22
22
|
var init_constants = __esm(() => {
|
|
@@ -162,6 +162,8 @@ function resolveSearchOptions(input) {
|
|
|
162
162
|
language: optionalText(input?.language),
|
|
163
163
|
localization: localizationValue(input?.searchLocalization),
|
|
164
164
|
maxSummaryChars: clampInt(input?.maxSummaryChars, DEFAULT_MAX_SUMMARY_CHARS, 0, 1e6),
|
|
165
|
+
contentMaxChars: clampInt(input?.searchContentMaxChars, DEFAULT_SEARCH_CONTENT_MAX_CHARS, 0, 1e6),
|
|
166
|
+
contentTimeoutMs: clampInt(input?.searchContentTimeoutMs, DEFAULT_SEARCH_CONTENT_TIMEOUT_MS, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS),
|
|
165
167
|
timeoutMs: clampInt(input?.searchTimeoutMs, DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS),
|
|
166
168
|
enabled: input?.searchEnabled !== false
|
|
167
169
|
};
|
|
@@ -279,6 +281,8 @@ var init_config_summary = __esm(() => {
|
|
|
279
281
|
{ key: "language", read: (o) => o.search.language },
|
|
280
282
|
{ key: "searchLocalization", read: (o) => o.search.localization },
|
|
281
283
|
{ key: "maxSummaryChars", read: (o) => o.search.maxSummaryChars },
|
|
284
|
+
{ key: "searchContentMaxChars", read: (o) => o.search.contentMaxChars },
|
|
285
|
+
{ key: "searchContentTimeoutMs", read: (o) => o.search.contentTimeoutMs },
|
|
282
286
|
{ key: "searchTimeoutMs", read: (o) => o.search.timeoutMs },
|
|
283
287
|
{ key: "fetchMaxChars", read: (o) => o.fetch.maxChars },
|
|
284
288
|
{ key: "fetchAllowedDomains", read: (o) => o.fetch.allowedDomains },
|
|
@@ -4170,7 +4174,9 @@ No results found.` : "No results found.";
|
|
|
4170
4174
|
const marker = formatQualityMarker(result);
|
|
4171
4175
|
const lines = [`${index + 1}. [${escapeBrackets(title)}](${result.url})${marker ? ` ${marker}` : ""}`];
|
|
4172
4176
|
if (result.content) {
|
|
4173
|
-
lines.push(
|
|
4177
|
+
lines.push(result.contentKind === "page" ? result.content.split(`
|
|
4178
|
+
`).map((line) => ` ${line}`).join(`
|
|
4179
|
+
`) : ` ${result.content.replaceAll(`
|
|
4174
4180
|
`, " ")}`);
|
|
4175
4181
|
}
|
|
4176
4182
|
if (result.published !== undefined) {
|
|
@@ -4354,9 +4360,14 @@ function processResults(raw, options) {
|
|
|
4354
4360
|
continue;
|
|
4355
4361
|
}
|
|
4356
4362
|
seen.add(url);
|
|
4357
|
-
const
|
|
4358
|
-
const
|
|
4359
|
-
const
|
|
4363
|
+
const body = optionalText2(item.content);
|
|
4364
|
+
const digest = optionalText2(item.summary) ?? optionalText2(item.snippet) ?? "";
|
|
4365
|
+
const fromPage = body !== undefined && options.contentMaxChars > 0;
|
|
4366
|
+
const rawContent = fromPage && body !== undefined ? body : digest;
|
|
4367
|
+
const contentBudget = fromPage ? options.contentMaxChars : options.maxSummaryChars;
|
|
4368
|
+
const cutContent = truncate(rawContent, contentBudget);
|
|
4369
|
+
const content = cutContent && charCount(rawContent) > contentBudget ? `${cutContent}…` : cutContent;
|
|
4370
|
+
const contentKind = fromPage ? "page" : "digest";
|
|
4360
4371
|
const published = parsePublished(item.date);
|
|
4361
4372
|
const title = optionalText2(item.title);
|
|
4362
4373
|
const rerankScore = typeof rerank === "number" && Number.isFinite(rerank) ? rerank : undefined;
|
|
@@ -4365,7 +4376,7 @@ function processResults(raw, options) {
|
|
|
4365
4376
|
results.push({
|
|
4366
4377
|
url,
|
|
4367
4378
|
...title ? { title } : {},
|
|
4368
|
-
...content ? { content } : {},
|
|
4379
|
+
...content ? { content, contentKind } : {},
|
|
4369
4380
|
...published !== undefined ? { published } : {},
|
|
4370
4381
|
...rerankScore !== undefined ? { rerankScore } : {},
|
|
4371
4382
|
...authorityScore !== undefined ? { authority: authorityScore } : {},
|
|
@@ -4620,12 +4631,13 @@ async function searchWeb(query, options, request) {
|
|
|
4620
4631
|
}
|
|
4621
4632
|
const composed = composeSearchQuery(trimmed, options);
|
|
4622
4633
|
const builtQuery = composed.query;
|
|
4623
|
-
const
|
|
4634
|
+
const needContent = request.needContent === true;
|
|
4635
|
+
const timeout = effectiveTimeout(needContent ? options.contentTimeoutMs : options.timeoutMs, request.timeoutMs);
|
|
4624
4636
|
const oriQuestion = oriQuestionValue(request.oriQuestion);
|
|
4625
4637
|
const payload = await postJson(apiUrl(request.baseUrl, SEARCH_ENDPOINT_PATH), {
|
|
4626
4638
|
query: builtQuery,
|
|
4627
4639
|
count: options.count,
|
|
4628
|
-
need_content:
|
|
4640
|
+
need_content: needContent,
|
|
4629
4641
|
...oriQuestion === undefined ? {} : { ori_question: oriQuestion },
|
|
4630
4642
|
...options.localization === undefined ? {} : { localization: options.localization }
|
|
4631
4643
|
}, "DeepSeek Search", { ...request, timeoutMs: timeout });
|
|
@@ -4705,6 +4717,8 @@ function envOptionsReport(env = process.env) {
|
|
|
4705
4717
|
putText(options, "searchLocalization", env);
|
|
4706
4718
|
putNumber(options, "maxSummaryChars", env, warnings);
|
|
4707
4719
|
putNumber(options, "searchTimeoutMs", env, warnings);
|
|
4720
|
+
putNumber(options, "searchContentMaxChars", env, warnings);
|
|
4721
|
+
putNumber(options, "searchContentTimeoutMs", env, warnings);
|
|
4708
4722
|
putNumber(options, "fetchMaxChars", env, warnings);
|
|
4709
4723
|
putDomains(options, "fetchAllowedDomains", env, warnings);
|
|
4710
4724
|
putDomains(options, "fetchBlockedDomains", env, warnings);
|
|
@@ -4792,6 +4806,8 @@ var init_env = __esm(() => {
|
|
|
4792
4806
|
minAuthority: "DEEPSEEK_SEARCH_MIN_AUTHORITY",
|
|
4793
4807
|
maxSummaryChars: "DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS",
|
|
4794
4808
|
searchTimeoutMs: "DEEPSEEK_SEARCH_TIMEOUT_MS",
|
|
4809
|
+
searchContentMaxChars: "DEEPSEEK_SEARCH_CONTENT_MAX_CHARS",
|
|
4810
|
+
searchContentTimeoutMs: "DEEPSEEK_SEARCH_CONTENT_TIMEOUT_MS",
|
|
4795
4811
|
fetchMaxChars: "DEEPSEEK_FETCH_MAX_CHARS",
|
|
4796
4812
|
fetchTimeoutMs: "DEEPSEEK_FETCH_TIMEOUT_MS",
|
|
4797
4813
|
maxRetries: "DEEPSEEK_MAX_RETRIES",
|
|
@@ -6222,7 +6238,7 @@ var init_package = __esm(() => {
|
|
|
6222
6238
|
package_default = {
|
|
6223
6239
|
$schema: "https://json.schemastore.org/package.json",
|
|
6224
6240
|
name: "deepseek-web-retrieval",
|
|
6225
|
-
version: "0.
|
|
6241
|
+
version: "0.10.0",
|
|
6226
6242
|
description: "Token-efficient DeepSeek Search and Fetch for coding agents, starting with OpenCode v2.",
|
|
6227
6243
|
type: "module",
|
|
6228
6244
|
license: "MIT",
|
|
@@ -6326,13 +6342,14 @@ var init_package = __esm(() => {
|
|
|
6326
6342
|
});
|
|
6327
6343
|
|
|
6328
6344
|
// src/core/descriptions.ts
|
|
6329
|
-
var FETCH_SERVICE_LIMITS = "DeepSeek does not render JavaScript and cannot reach private networks; PDFs, images, and other binary files are unsupported.", FETCH_OUTPUT_TRUNCATED = "Output is truncated to the configured size; the optional maxChars parameter raises or lowers that size for one call.", SEARCH_RANKED_FILTERED = "Results are ranked by relevance and source authority, deduplicated by URL, and capped at the configured maximum.", SEARCH_EMPTY_IS_NORMAL = "An empty result list is normal, not an error.", SEARCH_QUERY_OPERATORS = "Use site:/-site: operators in the query to include or exclude domains, and after:/before: with a year or date (e.g. after:2025-06-01) to filter by publish time; the full query must stay within 128 characters.", SEARCH_TOOL_DESCRIPTION, FETCH_TOOL_DESCRIPTION, SEARCH_QUERY_PARAMETER = "Search query, 128 characters or fewer including operators (site:, -site:, quoted phrases, lang:, after:, before:).", SEARCH_ORI_QUESTION_PARAMETER, FETCH_URL_PARAMETER = "Absolute http:// or https:// URL to fetch. Credentials are rejected.", FETCH_MAX_CHARS_PARAMETER, SEARCH_COUNT_PARAMETER;
|
|
6345
|
+
var FETCH_SERVICE_LIMITS = "DeepSeek does not render JavaScript and cannot reach private networks; PDFs, images, and other binary files are unsupported.", FETCH_OUTPUT_TRUNCATED = "Output is truncated to the configured size; the optional maxChars parameter raises or lowers that size for one call.", SEARCH_RANKED_FILTERED = "Results are ranked by relevance and source authority, deduplicated by URL, and capped at the configured maximum.", SEARCH_EMPTY_IS_NORMAL = "An empty result list is normal, not an error.", SEARCH_QUERY_OPERATORS = "Use site:/-site: operators in the query to include or exclude domains, and after:/before: with a year or date (e.g. after:2025-06-01) to filter by publish time; the full query must stay within 128 characters.", SEARCH_TOOL_DESCRIPTION, FETCH_TOOL_DESCRIPTION, SEARCH_QUERY_PARAMETER = "Search query, 128 characters or fewer including operators (site:, -site:, quoted phrases, lang:, after:, before:).", SEARCH_ORI_QUESTION_PARAMETER, FETCH_URL_PARAMETER = "Absolute http:// or https:// URL to fetch. Credentials are rejected.", FETCH_MAX_CHARS_PARAMETER, SEARCH_COUNT_PARAMETER, SEARCH_NEED_CONTENT_PARAMETER;
|
|
6330
6346
|
var init_descriptions = __esm(() => {
|
|
6331
|
-
SEARCH_TOOL_DESCRIPTION = "Search the web with DeepSeek's in-house search engine and return a short, filtered result " + "list (title, URL, summary). " + `${SEARCH_RANKED_FILTERED} ` + `${SEARCH_EMPTY_IS_NORMAL} ` + `${SEARCH_QUERY_OPERATORS} ` + "To read
|
|
6347
|
+
SEARCH_TOOL_DESCRIPTION = "Search the web with DeepSeek's in-house search engine and return a short, filtered result " + "list (title, URL, summary). " + `${SEARCH_RANKED_FILTERED} ` + `${SEARCH_EMPTY_IS_NORMAL} ` + `${SEARCH_QUERY_OPERATORS} ` + "To read one full page in depth, pass a result URL to the deepseek_fetch tool; to carry each result's " + "page body in this response, set need_content to true.";
|
|
6332
6348
|
FETCH_TOOL_DESCRIPTION = "Fetch one web page as clean Markdown through DeepSeek's Fetch API. " + `${FETCH_SERVICE_LIMITS} ` + `${FETCH_OUTPUT_TRUNCATED}`;
|
|
6333
6349
|
SEARCH_ORI_QUESTION_PARAMETER = "The user's original question or task that triggered this search, in the user's own words rather than a " + "reformulated query. Optional; 1-128 characters, and longer values are truncated.";
|
|
6334
6350
|
FETCH_MAX_CHARS_PARAMETER = "Optional per-call character budget for this fetch, 0-1,000,000, title included. Overrides the configured cap " + "for this call; a [content truncated] marker is appended when the output is cut.";
|
|
6335
6351
|
SEARCH_COUNT_PARAMETER = "Optional number of results for this call, 1-50. Overrides the configured count and result cap for this call; " + "billing is per request, not per result.";
|
|
6352
|
+
SEARCH_NEED_CONTENT_PARAMETER = "Optional; set true to attach each result's page body as Markdown — an excerpt clamped to a configured per-result " + "budget, not a full page — in place of its short summary. Slower per call (DeepSeek fetches the result pages) and " + "priced the same per request; for a full page, call deepseek_fetch.";
|
|
6336
6353
|
});
|
|
6337
6354
|
|
|
6338
6355
|
// src/mcp/server.ts
|
|
@@ -6346,12 +6363,13 @@ function createDeepSeekServer(config, env = process.env, client = retrievalClien
|
|
|
6346
6363
|
description: SEARCH_TOOL_DESCRIPTION,
|
|
6347
6364
|
inputSchema: SEARCH_INPUT,
|
|
6348
6365
|
annotations: { readOnlyHint: true }
|
|
6349
|
-
}, async ({ query, original_question, count }, ctx) => {
|
|
6366
|
+
}, async ({ query, original_question, count, need_content }, ctx) => {
|
|
6350
6367
|
try {
|
|
6351
6368
|
const request = requestContext(config, requireApiKey(env), ctx.mcpReq.signal);
|
|
6352
6369
|
const outcome = await client.searchWeb(query, withPerCallCount(config.search, count), {
|
|
6353
6370
|
...request,
|
|
6354
|
-
oriQuestion: original_question
|
|
6371
|
+
oriQuestion: original_question,
|
|
6372
|
+
needContent: need_content === true
|
|
6355
6373
|
});
|
|
6356
6374
|
return callResult(server, ctx, formatSearchResults(outcome.results, outcome), {
|
|
6357
6375
|
results: outcome.results.map(toStructuredResult)
|
|
@@ -6409,7 +6427,8 @@ var init_server = __esm(() => {
|
|
|
6409
6427
|
SEARCH_INPUT = z2.object({
|
|
6410
6428
|
query: z2.string().min(1).describe(SEARCH_QUERY_PARAMETER),
|
|
6411
6429
|
original_question: z2.string().optional().describe(SEARCH_ORI_QUESTION_PARAMETER),
|
|
6412
|
-
count: z2.number().int().min(1).max(MAX_SEARCH_COUNT).optional().describe(SEARCH_COUNT_PARAMETER)
|
|
6430
|
+
count: z2.number().int().min(1).max(MAX_SEARCH_COUNT).optional().describe(SEARCH_COUNT_PARAMETER),
|
|
6431
|
+
need_content: z2.boolean().optional().describe(SEARCH_NEED_CONTENT_PARAMETER)
|
|
6413
6432
|
});
|
|
6414
6433
|
FETCH_INPUT = z2.object({
|
|
6415
6434
|
url: z2.string().min(1).describe(FETCH_URL_PARAMETER),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Refuse drift between plan and apply
|
|
2
2
|
|
|
3
|
+
Status: Accepted — shipped in 0.3.0 (see [CHANGELOG](../../CHANGELOG.md)).
|
|
4
|
+
|
|
3
5
|
The installer's plan previews the previous value each `set` op replaces, and the
|
|
4
6
|
install record must snapshot exactly that value for uninstall to restore. We
|
|
5
|
-
decided that apply re-verifies the current value at every `set` path still
|
|
7
|
+
decided that apply re-verifies that the current value at every `set` path still
|
|
6
8
|
equals the plan-time `previous`, refuses with guidance when it does not
|
|
7
9
|
("config changed since the plan was rendered — re-run"), and records the
|
|
8
10
|
plan-time snapshot — so the preview, the secret guard, and the record all
|
|
@@ -12,9 +14,11 @@ describe one snapshot taken once, at plan time.
|
|
|
12
14
|
|
|
13
15
|
- **Inherit the plan-time snapshot silently.** Single source, but apply would
|
|
14
16
|
overwrite an edit made between plan review and `--apply` without the user's
|
|
15
|
-
knowledge — violating the installer's first
|
|
17
|
+
knowledge — violating the [installer's first
|
|
18
|
+
principle](../design/installer-design.md#principles).
|
|
16
19
|
- **Keep re-deriving `previous` at write time** (previous status quo). The
|
|
17
|
-
preview and the record can disagree, and the secret guard
|
|
20
|
+
preview and the record can disagree, and the secret guard (the
|
|
21
|
+
`withSecretGuard` wrapper in `src/installer/hosts/index.ts`), which checks the
|
|
18
22
|
plan-time value, no longer covers what is actually recorded.
|
|
19
23
|
|
|
20
24
|
## Consequences
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Share one config base layer across hosts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Status: Accepted — shipped in 0.7.0 (see [CHANGELOG](../../CHANGELOG.md)).
|
|
4
|
+
|
|
5
|
+
The option pipeline was single-sourced — names, defaults, and clamping resolved through
|
|
6
|
+
one chain (`src/core/constants.ts` → `src/core/env.ts` → `src/core/options.ts`) — but the
|
|
7
|
+
channels feeding it were per
|
|
4
8
|
host: `DEEPSEEK_*` shell variables on Pi, only `ctx.options` on OpenCode, and
|
|
5
9
|
on Codex only the names the installer's `env_vars` entry forwards — just
|
|
6
10
|
`DEEPSEEK_API_KEY`, so every other knob set in the shell silently never
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Expose need_content as the agent's per-call boolean
|
|
2
|
+
|
|
3
|
+
Status: Accepted — shipped in 0.10.0 (see [CHANGELOG](../../CHANGELOG.md)).
|
|
4
|
+
|
|
5
|
+
DeepSeek's Search API accepts a `need_content` boolean — return each result's page body —
|
|
6
|
+
that this repo had hardcoded `false` since the first release: the wire field arrived, the
|
|
7
|
+
pipeline dropped it, and no search result ever carried a page body
|
|
8
|
+
([search-inline-content.md](../research/search-inline-content.md)). The same research
|
|
9
|
+
pinned why the value cannot travel alone: DeepSeek documents no size bound for the
|
|
10
|
+
returned content, host output caps run as tight as Pi's 20 KB MCP-path truncation, and
|
|
11
|
+
the 30 s default search timeout does not budget for a server-side page crawl. We decided
|
|
12
|
+
that the boolean itself becomes a per-call tool parameter on the model-facing surfaces
|
|
13
|
+
(the MCP server and the Pi extension), defaulting to `false`, while the deployment owns
|
|
14
|
+
two envelope-only knobs that apply whenever content flows: `searchContentMaxChars` (the
|
|
15
|
+
per-result body budget, default 1,500) and `searchContentTimeoutMs` (the timeout a
|
|
16
|
+
content call runs on, default 60 s). Neither knob is ever a tool parameter — the model
|
|
17
|
+
cannot see which host caps its output, so it cannot own a budget.
|
|
18
|
+
|
|
19
|
+
## Considered Options
|
|
20
|
+
|
|
21
|
+
- **A tristate `searchContent: off | on | auto` config mode** gating whether the
|
|
22
|
+
parameter is exposed. The wire value is a boolean; `auto` is invented vocabulary, and
|
|
23
|
+
no per-call parameter in this repo (`count`, fetch `maxChars`) is mode-gated. The
|
|
24
|
+
`on` state's only real beneficiary is OpenCode, whose `websearch` provider input the
|
|
25
|
+
host fixes to `query` — the same upstream gap that already blocks per-call `count`
|
|
26
|
+
([opencode-v2.md](../agents/opencode-v2.md)).
|
|
27
|
+
- **Default-on inline content** (Jina's architecture). DeepSeek documents no size bound
|
|
28
|
+
for `content`; a default `count=10` response at typical page sizes runs 30–110 KB
|
|
29
|
+
against host caps that truncate near 20 KB, and per-request billing makes the call
|
|
30
|
+
free, not the context bloat.
|
|
31
|
+
- **The previous status quo** (hardcoded `false`). An agent that needs page bodies must
|
|
32
|
+
guess from summaries which URLs to fetch; a wrong guess is a silent miss.
|
|
33
|
+
|
|
34
|
+
## Consequences
|
|
35
|
+
|
|
36
|
+
The model decides per call whether a search carries page bodies — one round-trip
|
|
37
|
+
replaces search-plus-fetch when recall matters, and summaries stay the default when
|
|
38
|
+
they suffice. A page body replaces its result's digest, clamped to
|
|
39
|
+
`searchContentMaxChars` with an ellipsis marker, so a response's content volume is
|
|
40
|
+
bounded by budget × count and can never blow a host cap by an undocumented server-side
|
|
41
|
+
size. The text channel renders bodies as indented Markdown that keeps heading and list
|
|
42
|
+
structure; digests stay one-line. `searchContentMaxChars: 0` drops bodies entirely (the
|
|
43
|
+
digest renders) — a deployment kill switch that still pays the slower call, so leaving
|
|
44
|
+
the parameter false is the better tool for the model. A result whose content comes back
|
|
45
|
+
empty — the API documents per-result absence — falls back to its summary, so a content
|
|
46
|
+
call degrades to today's output rather than to nothing. OpenCode stays summary-only
|
|
47
|
+
until its provider input widens upstream. Design detail (budget arithmetic, rendering,
|
|
48
|
+
per-adapter composition) lives in the
|
|
49
|
+
[search content design note](../design/search-content-design.md).
|
|
@@ -38,20 +38,23 @@ The quickest option is the installer CLI:
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
npx deepseek-web-retrieval init --host claude-code --scope project \
|
|
41
|
-
--search <
|
|
41
|
+
--search <optional|preferred|forced|off> --fetch <optional|preferred|forced|off>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
It prints the full plan — every file, old value → new value, and notes about existing MCP
|
|
45
45
|
servers — and writes nothing until you pass `--apply`. Add `--pin` to register the exact
|
|
46
46
|
version of the CLI you are running instead of the always-latest package name. `uninstall`
|
|
47
47
|
removes recorded additions and restores snapshotted values that have not since been edited.
|
|
48
|
+
|
|
48
49
|
`--scope project` registers the server in `./.mcp.json` (shareable via VCS) and writes tier
|
|
49
50
|
artifacts to `./.claude/settings.json` and `./CLAUDE.md`; `--scope global` uses
|
|
50
51
|
`~/.claude.json`, `~/.claude/settings.json`, and `~/.claude/CLAUDE.md` instead — the two
|
|
51
52
|
scopes use disjoint files, so both can coexist. An `off` capability is written as the
|
|
52
53
|
tool's full id in the `permissions.deny` array — Claude Code's own mechanism, which removes
|
|
53
54
|
the tool from the model's context while the server keeps running — merging with any entries
|
|
54
|
-
you put there yourself.
|
|
55
|
+
you put there yourself.
|
|
56
|
+
|
|
57
|
+
When `claude` is on PATH, `init` also probes `claude --version` and
|
|
55
58
|
adds a plan note when the installed release differs from the tested lock (see
|
|
56
59
|
[hosts.jsonc](../hosts.jsonc)) — version-anchored behavior such as the deny rules may have
|
|
57
60
|
drifted between the two.
|
|
@@ -176,7 +179,8 @@ operators change the query actually sent, the result text opens with
|
|
|
176
179
|
`Searched for: <effective query>` (plus any degradation applied to fit the API's
|
|
177
180
|
128-character limit), so the model can attribute results to the configuration in effect.
|
|
178
181
|
|
|
179
|
-
Tool output is capped at 25k tokens by default (`MAX_MCP_OUTPUT_TOKENS
|
|
182
|
+
Tool output is capped at 25k tokens by default (`MAX_MCP_OUTPUT_TOKENS`, a Claude Code
|
|
183
|
+
environment setting), and successful
|
|
180
184
|
text results longer than 50,000 characters are saved to a file and referenced — not
|
|
181
185
|
dropped silently.
|
|
182
186
|
|
|
@@ -255,7 +259,7 @@ one vocabulary pair, one pipeline. `—` marks host-integration entries the file
|
|
|
255
259
|
| Claude Code | locked in [`docs/hosts.jsonc`](../hosts.jsonc) |
|
|
256
260
|
| MCP revision | `2026-07-28` negotiated; server accepts `2024-11-05`–`2026-07-28` |
|
|
257
261
|
| MCP SDK | `@modelcontextprotocol/server` 2.3.1 |
|
|
258
|
-
| Runtime | Node.js 20
|
|
262
|
+
| Runtime | Node.js 20 or newer (stdio) |
|
|
259
263
|
| DeepSeek API | `/v1/tools/search`, `/v1/tools/fetch` |
|
|
260
264
|
|
|
261
265
|
Tested with Claude Code at the `docs/hosts.jsonc` lock on Linux over stdio: the keyless
|
package/docs/agents/codex.md
CHANGED
|
@@ -17,10 +17,10 @@ The tools appear in Codex as `mcp__deepseek__deepseek_web_search` and
|
|
|
17
17
|
[Control tiers](#control-tiers) for routing it away.
|
|
18
18
|
|
|
19
19
|
The server recognizes Codex's MCP client identity (`codex-mcp-client`, declared in the
|
|
20
|
-
initialize handshake) and returns text-only tool results
|
|
21
|
-
blocks with the serialized `structuredContent` JSON when it is present, so for
|
|
22
|
-
structured mirror is omitted and the model reads clean Markdown. Other
|
|
23
|
-
both the text and `structuredContent`.
|
|
20
|
+
initialize handshake) and returns text-only tool results for it. Codex replaces the
|
|
21
|
+
content blocks with the serialized `structuredContent` JSON when it is present, so for
|
|
22
|
+
Codex the structured mirror is omitted and the model reads clean Markdown. Other
|
|
23
|
+
clients receive 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).
|
|
@@ -42,7 +42,7 @@ The quickest option is the installer CLI:
|
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
npx deepseek-web-retrieval init --host codex --scope global \
|
|
45
|
-
--search <
|
|
45
|
+
--search <optional|preferred|forced|off> --fetch <optional|preferred|forced|off>
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
It prints the full plan — every file, old value → new value, and notes about existing
|
|
@@ -117,7 +117,7 @@ built-in fetch tool, so the tiers differ per capability:
|
|
|
117
117
|
|
|
118
118
|
| Capability | Optional | Preferred | Forced |
|
|
119
119
|
| --- | --- | --- | --- |
|
|
120
|
-
| Search | Tools registered; the built-in `web_search` stays available | Plus an AGENTS.md rules block steering the model to `mcp__deepseek__deepseek_web_search` | Exclusion: `web_search = "disabled"` at the top level of config.toml |
|
|
120
|
+
| Search | Tools registered; the built-in `web_search` stays available | Plus an AGENTS.md rules block steering the model to `mcp__deepseek__deepseek_web_search` | Exclusion: `web_search = "disabled"` at the top level of `config.toml` |
|
|
121
121
|
| Fetch | Tools registered | Plus the rules block | De-facto: no built-in fetch tool exists |
|
|
122
122
|
|
|
123
123
|
The installer maps each tier to these artifacts. `AGENTS.md` rules land in
|
|
@@ -231,7 +231,7 @@ one vocabulary pair, one pipeline. `—` marks host-integration entries the file
|
|
|
231
231
|
| Codex | locked in [`docs/hosts.jsonc`](../hosts.jsonc) |
|
|
232
232
|
| MCP revision | `2025-06-18` negotiated (`mcp_2026_07_28` behind a flag); server accepts `2024-11-05`–`2026-07-28` |
|
|
233
233
|
| MCP SDK | `@modelcontextprotocol/server` 2.3.1 |
|
|
234
|
-
| Runtime | Node.js 20
|
|
234
|
+
| Runtime | Node.js 20 or newer (stdio) |
|
|
235
235
|
| DeepSeek API | `/v1/tools/search`, `/v1/tools/fetch` |
|
|
236
236
|
|
|
237
237
|
Tested with Codex 0.163.0 on Linux over stdio.
|
package/docs/agents/domain.md
CHANGED
|
@@ -14,7 +14,7 @@ If any of these files don't exist, **proceed silently**. Don't flag their absenc
|
|
|
14
14
|
|
|
15
15
|
Single-context repo (most repos):
|
|
16
16
|
|
|
17
|
-
```
|
|
17
|
+
```text
|
|
18
18
|
/
|
|
19
19
|
├── CONTEXT.md
|
|
20
20
|
├── docs/adr/
|
|
@@ -25,7 +25,7 @@ Single-context repo (most repos):
|
|
|
25
25
|
|
|
26
26
|
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
|
|
27
27
|
|
|
28
|
-
```
|
|
28
|
+
```text
|
|
29
29
|
/
|
|
30
30
|
├── CONTEXT-MAP.md
|
|
31
31
|
├── docs/adr/ ← system-wide decisions
|
|
@@ -5,7 +5,7 @@ Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all o
|
|
|
5
5
|
## Conventions
|
|
6
6
|
|
|
7
7
|
- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
|
|
8
|
-
- **Read an issue**: `gh issue view <number> --comments
|
|
8
|
+
- **Read an issue**: `gh issue view <number> --comments` for reading; add `--json body,labels,comments` (with `--jq` to project fields) when scripting.
|
|
9
9
|
- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
|
|
10
10
|
- **Comment on an issue**: `gh issue comment <number> --body "..."`
|
|
11
11
|
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
|
|
@@ -15,7 +15,7 @@ Infer the repo from `git remote -v`; `gh` does this automatically when run insid
|
|
|
15
15
|
|
|
16
16
|
## Pull requests as a triage surface
|
|
17
17
|
|
|
18
|
-
**PRs as a request surface: no.** _(
|
|
18
|
+
**PRs as a request surface: no.** _(Maintainers only: set to `yes` if this repo treats external PRs as feature requests; the `/triage` skill reads this flag.)_
|
|
19
19
|
|
|
20
20
|
When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents:
|
|
21
21
|
|
|
@@ -39,7 +39,7 @@ Used by `/wayfinder`. The **map** is a single issue with **child** issues as tic
|
|
|
39
39
|
|
|
40
40
|
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
|
|
41
41
|
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
|
42
|
-
- **Blocking**: GitHub's **native issue dependencies**, the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`).
|
|
43
|
-
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0
|
|
42
|
+
- **Blocking**: GitHub's **native issue dependencies**, the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). Read the resulting block count from the issue's REST representation — `gh api repos/<owner>/<repo>/issues/<n> --jq .issue_dependencies_summary.blocked_by` (open blockers only, the live gate; `gh issue view --json` does not expose this field). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
|
|
43
|
+
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0` read as above, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
|
|
44
44
|
- **Claim**: `gh issue edit <n> --add-assignee @me`, the session's first write.
|
|
45
|
-
- **Resolve**: `gh issue comment <n> --body "<answer>"`, then `gh issue close <n>`, then append a context pointer
|
|
45
|
+
- **Resolve**: `gh issue comment <n> --body "<answer>"`, then `gh issue close <n>`, then append a context pointer to the map's Decisions-so-far (link the resolved child; include the gist URL if the resolving session created one).
|
|
@@ -22,7 +22,7 @@ For configuration layers, the full knob mapping, and defaults, see the
|
|
|
22
22
|
|
|
23
23
|
- OpenCode v2 with `@opencode/plugin` in the `^2.0.32` peer range; the tested version
|
|
24
24
|
is recorded in [`docs/hosts.jsonc`](../hosts.jsonc)
|
|
25
|
-
- Node.js 20
|
|
25
|
+
- Node.js 20 or newer and npm when using the installer CLI
|
|
26
26
|
- A DeepSeek account with access to `/v1/tools/search` and `/v1/tools/fetch`
|
|
27
27
|
- A funded DeepSeek account; Search and Fetch are billed per successful request
|
|
28
28
|
- A DeepSeek credential stored in OpenCode, or `DEEPSEEK_API_KEY`
|
|
@@ -41,11 +41,14 @@ npx deepseek-web-retrieval init --host opencode --scope <project|global> \
|
|
|
41
41
|
|
|
42
42
|
It prints the full plan — every file, old value → new value — and writes nothing until
|
|
43
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`
|
|
45
|
-
replaces it, merges `permissions` without touching yours,
|
|
46
|
-
`opencode.jsonc`, and `uninstall` restores recorded values that
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
instead of the always-latest package name. It snapshots the current `websearch.provider`
|
|
45
|
+
before the forced tier replaces it, merges `permissions` without touching yours,
|
|
46
|
+
preserves comments in `opencode.jsonc`, and `uninstall` restores recorded values that
|
|
47
|
+
have not since been edited.
|
|
48
|
+
|
|
49
|
+
> [!NOTE]
|
|
50
|
+
> Search has no `preferred` tier on OpenCode: the provider is host-level config, never
|
|
51
|
+
> a model choice, so the CLI rejects it.
|
|
49
52
|
|
|
50
53
|
### OpenCode CLI install
|
|
51
54
|
|
|
@@ -177,8 +180,10 @@ The model calls `websearch` (provider `deepseek`) or `deepseek_fetch`. When
|
|
|
177
180
|
`fetchOverrideWebfetch` is enabled, Markdown fetches go through `webfetch` instead.
|
|
178
181
|
|
|
179
182
|
DeepSeek's optional `ori_question` is not sent on this path, and the model cannot request a
|
|
180
|
-
per-call result count: OpenCode's `websearch` tool
|
|
181
|
-
at 2.1.4
|
|
183
|
+
per-call result count or inline page content (`need_content`): OpenCode's `websearch` tool
|
|
184
|
+
input is fixed to `query` (verified unchanged at `@opencode/schema` 2.1.4, the drift check
|
|
185
|
+
above the 2.0.32 lock), so the provider has no channel for either — search stays
|
|
186
|
+
summary-only here while the MCP server and the Pi extension expose both parameters. Upstream requests:
|
|
182
187
|
[anomalyco/opencode#58635](https://github.com/anomalyco/opencode/issues/58635) (session linkage)
|
|
183
188
|
and [#58636](https://github.com/anomalyco/opencode/issues/58636) (per-call `count`); see the
|
|
184
189
|
[research note](../research/ori-question-sourcing.md). The fetch tools are not constrained:
|
|
@@ -186,7 +191,8 @@ they accept an optional per-call `maxChars` (0–1,000,000) that overrides the c
|
|
|
186
191
|
`fetchMaxChars` for one call, so the model can widen the budget after seeing a
|
|
187
192
|
`[content truncated]` marker. The effective-query header and query degradation notes have no
|
|
188
193
|
channel on this path either — OpenCode's result shape is closed — but degradation still runs,
|
|
189
|
-
and
|
|
194
|
+
and a query that exceeds the length limit and cannot be degraded further surfaces as an
|
|
195
|
+
error.
|
|
190
196
|
|
|
191
197
|
## Configuration
|
|
192
198
|
|
|
@@ -210,14 +216,14 @@ environment or file channel.
|
|
|
210
216
|
|
|
211
217
|
| Option | Environment | Default | Description |
|
|
212
218
|
| --- | --- | --- | --- |
|
|
213
|
-
| `searchEnabled` | `DEEPSEEK_ENABLE_SEARCH` | `true` | Register the DeepSeek search provider at all.
|
|
219
|
+
| `searchEnabled` | `DEEPSEEK_ENABLE_SEARCH` | `true` | Register the DeepSeek search provider at all. In `options`, only an explicit `false` disables it; the environment twin also accepts `0`/`no`/`off`. |
|
|
214
220
|
| `count` | `DEEPSEEK_SEARCH_COUNT` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
|
|
215
221
|
| `maxResults` | `DEEPSEEK_SEARCH_MAX_RESULTS` | `10` | Results returned to the model after filtering, 0–50; defaults to `count` when unset. `0` returns no results. |
|
|
216
222
|
| `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. |
|
|
217
223
|
| `minAuthority` | `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `0` | Drop known authorities below this tier. Authority is 0–4. |
|
|
218
|
-
| `allowUnknownAuthority` | `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `true` | Keep results whose authority is missing or unknown.
|
|
219
|
-
| `blockedDomains` | `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` |
|
|
220
|
-
| `includeDomains` | `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` |
|
|
224
|
+
| `allowUnknownAuthority` | `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `true` | Keep results whose authority is missing or unknown. In `options`, only an explicit `false` disables it; the environment twin also accepts `0`/`no`/`off`. |
|
|
225
|
+
| `blockedDomains` | `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | unset | Add `-site:` operators to the search query. |
|
|
226
|
+
| `includeDomains` | `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | unset | Restrict results with `site:` operators. |
|
|
221
227
|
| `language` | `DEEPSEEK_SEARCH_LANGUAGE` | unset | Add a `lang:` operator, for example `zh,en`. |
|
|
222
228
|
| `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. |
|
|
223
229
|
| `maxSummaryChars` | `DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS` | `1200` | Per-result summary cap, 0–1,000,000. |
|
|
@@ -233,8 +239,8 @@ environment or file channel.
|
|
|
233
239
|
| `fetchTimeoutMs` | `DEEPSEEK_FETCH_TIMEOUT_MS` | `30000` | Fetch timeout, 1,000–120,000 ms. |
|
|
234
240
|
| `fetchOverrideWebfetch` | — | `false` | Replace the built-in `webfetch`. |
|
|
235
241
|
| `fetchRegisterTool` | — | `true` | Register the separate `deepseek_fetch` tool. |
|
|
236
|
-
| `fetchAllowedDomains` | `DEEPSEEK_FETCH_ALLOWED_DOMAINS` |
|
|
237
|
-
| `fetchBlockedDomains` | `DEEPSEEK_FETCH_BLOCKED_DOMAINS` |
|
|
242
|
+
| `fetchAllowedDomains` | `DEEPSEEK_FETCH_ALLOWED_DOMAINS` | unset | If non-empty, only these hosts and subdomains may be fetched. |
|
|
243
|
+
| `fetchBlockedDomains` | `DEEPSEEK_FETCH_BLOCKED_DOMAINS` | unset | Always block these hosts and subdomains. Blocked wins over allowed. |
|
|
238
244
|
| `fetchBlockPrivateHosts` | `DEEPSEEK_FETCH_BLOCK_PRIVATE_HOSTS` | `true` | Reject localhost, private IPs, `.local`, `.internal`, and similar hosts. |
|
|
239
245
|
|
|
240
246
|
### Credentials and retries
|