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.
- package/CHANGELOG.md +23 -1
- package/README.md +113 -53
- package/dist/cli.js +2427 -1813
- package/docs/adr/0002-shared-config-base-layer.md +43 -0
- package/docs/agents/claude-code.md +273 -0
- package/docs/agents/codex.md +50 -27
- package/docs/agents/opencode-v2.md +59 -33
- package/docs/agents/pi.md +50 -27
- package/docs/configuration.md +109 -0
- package/docs/design/config-layering-design.md +180 -0
- package/docs/design/installer-design.md +24 -13
- package/docs/hosts.jsonc +5 -1
- package/docs/integration.md +15 -9
- package/docs/research/control-matrix.md +49 -11
- package/docs/research/mcp-compatibility.md +6 -3
- package/docs/research/ori-question-sourcing.md +336 -0
- package/docs/research/result-quality-passthrough.md +159 -0
- package/docs/research/search-api-defaults.md +18 -2
- package/docs/research/search-inline-content.md +244 -0
- package/examples/opencode.global.jsonc +3 -2
- package/package.json +2 -1
- package/src/core/auth.ts +3 -0
- package/src/core/client.ts +26 -4
- package/src/core/config-file.ts +168 -0
- package/src/core/config-summary.ts +82 -0
- package/src/core/constants.ts +15 -1
- package/src/core/descriptions.ts +6 -2
- package/src/core/env.ts +102 -32
- package/src/core/format.ts +19 -1
- package/src/core/options.ts +22 -2
- package/src/core/search.ts +6 -0
- package/src/core/types.ts +11 -0
- package/src/installer/cli.ts +50 -16
- package/src/installer/config-command.ts +116 -0
- package/src/installer/hosts/claude-code.ts +325 -0
- package/src/installer/hosts/codex.ts +9 -4
- package/src/installer/hosts/index.ts +3 -0
- package/src/installer/hosts/opencode.ts +9 -5
- package/src/installer/hosts/pi.ts +34 -9
- package/src/installer/ownership.ts +54 -1
- package/src/installer/paths.ts +7 -1
- package/src/installer/state.ts +73 -20
- package/src/installer/types.ts +11 -1
- package/src/installer/uninstall.ts +4 -1
- package/src/mcp/auth.ts +2 -3
- package/src/mcp/cli.ts +4 -3
- package/src/mcp/serve.ts +10 -1
- package/src/mcp/server.ts +7 -2
- package/src/opencode/plugin.ts +22 -2
- package/src/pi/extension.ts +78 -9
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Passing result quality fields to the model
|
|
2
|
+
|
|
3
|
+
This note answers one question: through which channel can DeepSeek's per-result quality fields —
|
|
4
|
+
`rerank_score` (0–3, "≥1.5 优质"), `authority` (0–4, -1 unknown, "≥3 极权威"), `site_name`
|
|
5
|
+
(string|null) — reach the **model** (not the human UI) on each host at its locked version? The
|
|
6
|
+
repo filters on the first two in `src/core/search.ts` (thresholds default to off); at the
|
|
7
|
+
research baseline it dropped all three before returning — the adapters now pass them through
|
|
8
|
+
as a text marker plus a structured mirror (item 1 below is implemented as
|
|
9
|
+
`formatQualityMarker`).
|
|
10
|
+
Related: [ori-question-sourcing.md](ori-question-sourcing.md) (input side),
|
|
11
|
+
[search-inline-content.md](search-inline-content.md) (content volume).
|
|
12
|
+
|
|
13
|
+
**Research baseline:** 2026-10-23, using the same version locks as
|
|
14
|
+
[hosts.jsonc](../hosts.jsonc) (Cline 0.0.13, Codex 0.163.0, OpenCode 2.0.32, Pi 1.3.0,
|
|
15
|
+
Claude Code 2.1.303, Cursor 2026.10.08, Devin 3000.11.92, GitHub Copilot 1.0.98). Claims are
|
|
16
|
+
cited to primary sources: the MCP specification, the official TypeScript SDK as installed in
|
|
17
|
+
this repo (`@modelcontextprotocol/server` 2.3.1 — the shipped artifact), host source at the
|
|
18
|
+
exact tags or as installed in `node_modules` (the published npm packages are themselves primary
|
|
19
|
+
sources), and official docs/changelogs. Prior findings in
|
|
20
|
+
[mcp-compatibility.md](mcp-compatibility.md) and [control-matrix.md](control-matrix.md) are
|
|
21
|
+
cited rather than re-derived; this note re-verified the load-bearing ones and extends them.
|
|
22
|
+
|
|
23
|
+
## What the specification says
|
|
24
|
+
|
|
25
|
+
MCP [`2025-06-18` tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
|
|
26
|
+
introduced structured output:
|
|
27
|
+
|
|
28
|
+
- Unstructured content rides in `content` (text/image/audio/resource blocks); structured
|
|
29
|
+
content is "a JSON object in the `structuredContent` field."
|
|
30
|
+
- Backwards compatibility is a server-side SHOULD, verbatim: *"For backwards compatibility, a
|
|
31
|
+
tool that returns structured content **SHOULD** also return the serialized JSON in a
|
|
32
|
+
TextContent block."*
|
|
33
|
+
- With a declared `outputSchema`: *"Servers **MUST** provide structured results that conform to
|
|
34
|
+
this schema. Clients **SHOULD** validate structured results against this schema."*
|
|
35
|
+
- Content blocks carry optional `annotations.audience` (`"user"` / `"assistant"`) — a spec-level
|
|
36
|
+
way to mark a block as model-visible or human-visible. No host in the matrix is documented to
|
|
37
|
+
honor it; treated as unpinned below.
|
|
38
|
+
|
|
39
|
+
The `2024-11-05` revision (Cline's floor) has neither `structuredContent` nor `outputSchema`.
|
|
40
|
+
|
|
41
|
+
## What the official TypeScript SDK enforces (v2.3.1, as shipped in this repo)
|
|
42
|
+
|
|
43
|
+
The server-side validator (`@modelcontextprotocol/server` 2.3.1,
|
|
44
|
+
`dist/mcp-*.mjs`, `validateToolOutput`) makes the outputSchema obligation concrete:
|
|
45
|
+
|
|
46
|
+
- A tool with an `outputSchema` whose result has no `structuredContent` is **rejected**:
|
|
47
|
+
`Output validation error: Tool <name> has an output schema but no structured content was
|
|
48
|
+
provided` (JSON-RPC `InvalidParams`). Text-only results are not allowed for schema'd tools.
|
|
49
|
+
- `structuredContent` is then validated against the schema; a mismatch is rejected the same way.
|
|
50
|
+
- The v2 codec's SEP-2106 §4.3 auto-append (`appendTextFallbackForNonObject`) adds a
|
|
51
|
+
`JSON.stringify(structuredContent)` TextContent block **only when `structuredContent` is a
|
|
52
|
+
non-object** (primitive/array/null) and no text block exists. Object-shaped
|
|
53
|
+
`structuredContent` gets **no** automatic text mirror — the server must mirror it itself to
|
|
54
|
+
satisfy the spec's SHOULD.
|
|
55
|
+
|
|
56
|
+
Consequence for this repo: the shipped server declares no `outputSchema` and emits both a text
|
|
57
|
+
summary and (except for Codex, below) a mirrored `structuredContent` — that combination remains
|
|
58
|
+
the spec- and SDK-safe shape. Declaring an `outputSchema` would obligate every successful call
|
|
59
|
+
to return schema-conforming structured content.
|
|
60
|
+
|
|
61
|
+
## Per host: what actually reaches the model
|
|
62
|
+
|
|
63
|
+
| Host (lock) | Text `content` reaches model | `structuredContent` reaches model | Output caps at the lock |
|
|
64
|
+
| --- | --- | --- | --- |
|
|
65
|
+
| Codex 0.163.0 | Replaced when `structuredContent` present | Yes — content is **replaced** by serialized JSON | Per-tool `output_token_limit` + "standard 20% serialization allowance" ([mcp_types.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/config/src/mcp_types.rs)); default value not pinned there |
|
|
66
|
+
| Pi 1.3.0 (MCP path) | Yes (content-first) | Only when no content blocks | 20 KB text middle-truncate, full text saved to file; 60 s per-request ([control-matrix](control-matrix.md#pi)) |
|
|
67
|
+
| Pi 1.3.0 (extension path) | Yes — **the only model channel** | **No** — event-layer only (below) | Extension tools bypass the MCP 20 KB truncation ([control-matrix](control-matrix.md#pi)) |
|
|
68
|
+
| OpenCode 2.0.32 (plugin path) | Yes (content wins) | **No** — serialized `output` is a fallback only (below) | 2000 lines / 50 KiB tool-output truncation, full output retained to file for 7 days |
|
|
69
|
+
| Claude Code 2.1.303 | Yes | Spec-level yes (2026-07-28 + TS SDK 2.0 runtime); client-side rendering not pinned by a primary source | 25k tokens default (`MAX_MCP_OUTPUT_TOKENS`), oversized saved to file ([control-matrix](control-matrix.md#claude-code)) |
|
|
70
|
+
| Cline 0.0.13 | Yes | **No** — `2024-11-05` has no `structuredContent` | None documented |
|
|
71
|
+
| Devin 3000.11.92 / Copilot 1.0.98 | Yes | Spec-level yes (both negotiate 2026-07-28); rendering not pinned | Not pinned |
|
|
72
|
+
| Cursor 2026.10.08 | Yes | Revision unpinnable (docs show ≥2025-06-18 features, so the field exists); rendering not pinned | Not pinned |
|
|
73
|
+
|
|
74
|
+
Codex's replacement behavior and Cline's text-only floor were established at these locks in
|
|
75
|
+
[mcp-compatibility.md](mcp-compatibility.md#what-each-host-supports) and re-checked; the two
|
|
76
|
+
native-adapter findings below are new.
|
|
77
|
+
|
|
78
|
+
### Pi 1.3.0: the extension `structuredContent` never reaches the model
|
|
79
|
+
|
|
80
|
+
From the installed `@earendil-works/pi-coding-agent` 1.3.0 (the shipped artifact):
|
|
81
|
+
|
|
82
|
+
- An extension tool's `execute` returns `{ content, structuredContent?, details? }`. The
|
|
83
|
+
`structuredContent` field exists only on the extension **event** layer, documented as
|
|
84
|
+
*"Machine-readable result for tools that declare an `outputSchema`"*
|
|
85
|
+
(`dist/core/extensions/types.d.ts`, `ToolResultEventBase`) — consumers are other extensions
|
|
86
|
+
(hooks, nested tool calls), not the model.
|
|
87
|
+
- The model-facing message type (`@earendil-works/pi-ai` `ToolResultMessage`,
|
|
88
|
+
`dist/types.d.ts`) carries `content` blocks, `isError`, ids — no `structuredContent`, and
|
|
89
|
+
`details` is session-record only. The Anthropic serializer sends exactly
|
|
90
|
+
`tool_use_id` + content blocks + `is_error`
|
|
91
|
+
(`dist/api/anthropic-messages.js`, `convertToolResult`).
|
|
92
|
+
- So this repo's Pi adapter already does the right thing by putting the full result in the text
|
|
93
|
+
block; its `structuredContent` payload is event-layer data for machine consumers.
|
|
94
|
+
|
|
95
|
+
### OpenCode 2.0.32: text `content` wins; typed `output` is a JSON-stringified fallback; `metadata` is not model-facing
|
|
96
|
+
|
|
97
|
+
From the official repo at tag `v2.0.32` (github.com/anomalyco/opencode, where `@opencode/cli`
|
|
98
|
+
2.0.32's npm manifest points) and the installed `@opencode/schema`/`@opencode/plugin`:
|
|
99
|
+
|
|
100
|
+
- `Tool.Result` is `{ output?, content?, metadata? }` — `output` typed by an optional declared
|
|
101
|
+
output schema, `content` a string or text/file blocks, `metadata` a free record
|
|
102
|
+
(`@opencode/schema/dist/tool.d.ts`).
|
|
103
|
+
- The model channel is decided by
|
|
104
|
+
[`normalizeContent(content, output)`](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool/runtime.ts):
|
|
105
|
+
a string or non-empty `content` wins; only when `content` is absent/empty is `output`
|
|
106
|
+
serialized via `JSON.stringify` into a text block. `metadata` never enters this path (it
|
|
107
|
+
feeds `tool.execute.after` hooks and UI).
|
|
108
|
+
- Every tool result then passes through `ToolOutput.truncate`:
|
|
109
|
+
**`MAX_LINES = 2000`, `MAX_BYTES = 50 KiB`** (full output retained to a `tool-output/`
|
|
110
|
+
file for 7 days) — [`packages/core/src/tool-output.ts`](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool-output.ts).
|
|
111
|
+
These caps were not previously recorded in [control-matrix.md](control-matrix.md).
|
|
112
|
+
|
|
113
|
+
## What this means for this repo
|
|
114
|
+
|
|
115
|
+
1. **Text is the only channel verified to reach the model on every host.** Codex is the sole
|
|
116
|
+
host where `structuredContent` demonstrably reaches the model (as a *replacement*); on the
|
|
117
|
+
two native adapters it provably does not (Pi: event-layer only; OpenCode: fallback only).
|
|
118
|
+
Quality fields the model should reason about therefore belong **in the text** — e.g. a
|
|
119
|
+
compact per-result marker like `[rerank 2.1 · authority 3]` (plus `site_name` where present).
|
|
120
|
+
At 10 results that is a few hundred characters, negligible against every cap above.
|
|
121
|
+
2. **Mirroring in `structuredContent` stays worth it** for machine consumers (Pi extension
|
|
122
|
+
events, future hosts, tests) and costs nothing: the shipped dual-emission — text plus
|
|
123
|
+
structured, with the mirror omitted for the detected `codex-mcp-client` identity — already
|
|
124
|
+
matches the spec's SHOULD and Codex's replacement. Adding the new fields to both shapes
|
|
125
|
+
changes no host behavior.
|
|
126
|
+
3. **Keep `outputSchema` undeclared** on the MCP server unless the team is prepared to guarantee
|
|
127
|
+
schema-conforming `structuredContent` on every successful call (the SDK rejects otherwise).
|
|
128
|
+
The Pi adapter's extension `outputSchema` is safe as-is — Pi re-declares but does not
|
|
129
|
+
validate it, and its `structuredContent` is event data.
|
|
130
|
+
4. **New cap facts for the design margin:** OpenCode plugin results truncate at 2000 lines /
|
|
131
|
+
50 KiB (new), Pi extension tools bypass the MCP 20 KB truncation (already known), Claude
|
|
132
|
+
Code 25k tokens, Codex per-tool `output_token_limit` (default unpublished). None of these
|
|
133
|
+
bind at search-result volumes; they matter if [inline content](search-inline-content.md) is
|
|
134
|
+
ever enabled.
|
|
135
|
+
|
|
136
|
+
## Sources
|
|
137
|
+
|
|
138
|
+
- MCP spec: [2025-06-18 tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
|
|
139
|
+
(structured content, outputSchema MUST/SHOULD, audience annotations); revision table in
|
|
140
|
+
[mcp-compatibility.md](mcp-compatibility.md#protocol-revisions).
|
|
141
|
+
- Official TypeScript SDK, **as installed** (`@modelcontextprotocol/server` 2.3.1,
|
|
142
|
+
`@modelcontextprotocol/client` 2.3.1): `dist/mcp-*.mjs` `validateToolOutput` /
|
|
143
|
+
`validateToolInput`; `dist/src-*.mjs` `appendTextFallbackForNonObject` /
|
|
144
|
+
`projectCallToolResult` (SEP-2106 §4.3).
|
|
145
|
+
- Pi, **as installed** (`@earendil-works/pi-coding-agent` 1.3.0, `@earendil-works/pi-ai`):
|
|
146
|
+
`dist/core/extensions/types.d.ts` (`ToolResultEventBase.structuredContent` doc,
|
|
147
|
+
`ToolDefinition.outputSchema`), `pi-ai/dist/types.d.ts` (`ToolResultMessage`),
|
|
148
|
+
`pi-ai/dist/api/anthropic-messages.js` (`convertToolResult`, `convertMessages`).
|
|
149
|
+
- OpenCode at the lock: [`v2.0.32` tool/runtime.ts (`normalizeContent`)](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool/runtime.ts),
|
|
150
|
+
[`v2.0.32` tool-output.ts (MAX_LINES/MAX_BYTES)](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool-output.ts),
|
|
151
|
+
[`v2.0.32` core/src/tool.ts (execute.after result shape)](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool.ts),
|
|
152
|
+
installed `@opencode/schema/dist/tool.d.ts` and `@opencode/plugin/dist/promise/tool.d.ts`.
|
|
153
|
+
- Codex: [`mcp_types.rs` at rust-v0.163.0 (`output_token_limit`, 20% serialization allowance)](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/config/src/mcp_types.rs);
|
|
154
|
+
replacement behavior per [mcp-compatibility.md](mcp-compatibility.md#what-each-host-supports).
|
|
155
|
+
- Host caps and protocol facts previously verified at these locks:
|
|
156
|
+
[control-matrix.md](control-matrix.md#per-host-reference) (Pi 20 KB / 60 s, Claude Code
|
|
157
|
+
`MAX_MCP_OUTPUT_TOKENS` 25k, Cline text-only floor),
|
|
158
|
+
[mcp-compatibility.md](mcp-compatibility.md) (per-host negotiated revisions; Cursor
|
|
159
|
+
unpinnable).
|
|
@@ -255,9 +255,25 @@ quality filtering**. What they do publish, verbatim
|
|
|
255
255
|
- `rerank_score`: "排序分数,0~3 分,综合考虑相关性、权威性、时效性等维度,**大于等于 1.5 分为优质结果**。"
|
|
256
256
|
- `authority`: "网页权威性,0~4(**-1 表示未知**),**大于等于 3 为极权威结果**。"
|
|
257
257
|
- Request defaults: `count` "否 / 默认值 10 / 返回条数,1~50"; `need_content` "否 / false";
|
|
258
|
-
`
|
|
258
|
+
`ori_question` optional ("触发搜索的用户原始问题,1~128 个字符").
|
|
259
|
+
- `localization` (checked 2026-10-23): documented as "用于优先返回本地化搜索结果的 IP
|
|
260
|
+
地址" — an IP address used to prefer geo-localized results, optional, default empty. The
|
|
261
|
+
docs pin nothing beyond "IP 地址"; this repo accepts exactly what `net.isIP` validates
|
|
262
|
+
(IPv4 or IPv6) and drops anything else at option resolution.
|
|
263
|
+
- Query operators (checked 2026-10-23): the `query` "会原样传递给搜索服务" and supports
|
|
264
|
+
`site:` / `-site:` / `lang:` (e.g. `lang:zh,en`) plus the time filters `after:` ("将结果限制在指定日期之后发布的网页")
|
|
265
|
+
and `before:` ("将结果限制在指定日期之前发布的网页") — each taking a year or a date, with the
|
|
266
|
+
doc examples "DeepSeek after:2025 或 DeepSeek after:2025-01-01" and
|
|
267
|
+
"DeepSeek before:2026 或 DeepSeek before:2026-06-01".
|
|
259
268
|
- Response sizing: `snippet` "短摘要 … 通常为 100~200 字符"; `summary` "长摘要 … 通常为 600~800 字符".
|
|
260
269
|
|
|
270
|
+
Two follow-up notes examine the request parameters this section lists only as defaults:
|
|
271
|
+
[search-inline-content.md](search-inline-content.md) covers `need_content: true` — what
|
|
272
|
+
provably returns, what it costs, how large it can get, and where that collides with
|
|
273
|
+
per-host output caps — and [ori-question-sourcing.md](ori-question-sourcing.md) covers
|
|
274
|
+
`ori_question` — the per-host channels that could supply a value (the MCP and Pi adapters
|
|
275
|
+
now send it; OpenCode's websearch provider path has no input channel at its lock).
|
|
276
|
+
|
|
261
277
|
## What this means for this repo's defaults
|
|
262
278
|
|
|
263
279
|
Fact-by-fact alignment of the repo's client-side defaults
|
|
@@ -268,7 +284,7 @@ here; the table only lines up what the industry ships.
|
|
|
268
284
|
| --- | --- |
|
|
269
285
|
| API `count=10`, client `maxResults=10` (post-dedup cap; `5` before 2026-10-21) | API-level count defaults: Tavily 10, Exa 10, Perplexity Search API 10, Firecrawl 10, You.com 10 (per section), Brave 20. Post-filter caps are uncommon; a fixed 5 exists at Jina (`s.jina.ai` top 5, not configurable) and Perplexity's `image_search` tool (default 5). Linkup's schema example is 5 but no default is published. OpenAI and Anthropic expose no count at all |
|
|
270
286
|
| `minRerank=0`, `minAuthority=0`, `allowUnknownAuthority=true` — no client-side filtering (previously `1.5`/`2`/`false`) | **No surveyed product filters search results client-side by relevance score or authority.** The only published thresholds for DeepSeek's own fields are the docs' quality labels (≥1.5 "优质", ≥3 "极权威") — labels, not filters. The closest shipped analogues are domain allow/block lists (Tavily, Exa, Perplexity, You.com, Anthropic, Brave) and You.com's `safesearch: moderate` default, which is content moderation, not authority |
|
|
271
|
-
| `maxRetries=0` | Official model-API SDKs: OpenAI 2, Anthropic 2 (both languages). Search-API SDKs: firecrawl-js 3 (502 only), tavily-python 0, Linkup SDK 0 |
|
|
287
|
+
| `maxRetries=2` — changed from `0` on 2026-10-23: this package runs as an agent tool, where a transient failure left un-retried falls to the model re-issuing the whole billed tool call, the failure profile of the model-API SDKs in this row (2, both languages) rather than of the split search-API SDKs; the default followed the model-SDK column | Official model-API SDKs: OpenAI 2, Anthropic 2 (both languages). Search-API SDKs: firecrawl-js 3 (502 only), tavily-python 0, Linkup SDK 0 |
|
|
272
288
|
| `timeout=30s` (search and fetch, 1–120 s range, covers body read) | Search/scrape API timeouts: Firecrawl 60 s (scrape max 300 s), Tavily SDK search 60 s (cap 120 s) / extract 30 s, You.com `crawl_timeout` 10 s, Linkup SDK no timeout, Jina `x-timeout` unset by default (max 180 s). Model-API SDKs: OpenAI and Anthropic 10 minutes |
|
|
273
289
|
| Fetch truncation 16,000 chars | Exa: page text **off** by default (`contents.text: false`), opt-in `maxCharacters` with no published default; DeepSeek itself: `need_content: false` by default; Anthropic `web_fetch`: token-based `max_content_tokens`, no published default (example 100000), no char/MiB figure in current docs; You.com highlights budget 8192 tokens; Firecrawl and Jina publish no truncation default |
|
|
274
290
|
| Summary truncation 1,200 chars | DeepSeek's own field doc says `summary` is "通常为 600~800 字符"; no surveyed vendor ships a client-side summary cap (they return snippets/highlights sized server-side: Exa verbosity `compact`, You.com highlights budget) |
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Search with inline content: `need_content` facts
|
|
2
|
+
|
|
3
|
+
This note answers one question: if DeepSeek's Search API were called with
|
|
4
|
+
`need_content: true` (today the repo hardcodes `false`), what would the response provably
|
|
5
|
+
contain, what would it cost, how large could it get, and where does that collide with
|
|
6
|
+
per-host output caps? It extends
|
|
7
|
+
[search-api-defaults.md](search-api-defaults.md) — which surveyed factory defaults — with the
|
|
8
|
+
one question that survey did not ask: which vendors ship search-with-inline-content as the
|
|
9
|
+
default architecture versus opt-in, and with what per-result content budget. It records facts
|
|
10
|
+
only; the closing section lines the facts up against the current two-tool design (search
|
|
11
|
+
summary-only plus a separate fetch tool) without recommending for or against it.
|
|
12
|
+
|
|
13
|
+
**Research baseline: 2026-10-23.** Claims are cited to primary sources only: official
|
|
14
|
+
DeepSeek documentation (api-docs.deepseek.com, English and 中文 pages, plus the official FAQ
|
|
15
|
+
bundle), other vendors' official documentation and SDK/API references, and OpenAI Codex
|
|
16
|
+
source at the repo's locked tag. Secondhand blogs are excluded. Where no primary source pins
|
|
17
|
+
a fact — most importantly the size of `content` — that is stated as such: an undocumented
|
|
18
|
+
size is itself a decision-relevant fact.
|
|
19
|
+
|
|
20
|
+
## What `need_content=true` provably returns
|
|
21
|
+
|
|
22
|
+
From the official Search & Fetch guide, the parameter and its response field, verbatim:
|
|
23
|
+
|
|
24
|
+
| Item | 中文 | English |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `need_content` (request) | “是否在 content 中返回网页正文,为 false 时 content 恒为空字符串。” | "Whether to return page content in content, which is always an empty string when false." |
|
|
27
|
+
| `content` (search response) | “网页正文,need_content 为 false 或正文缺失时为空字符串。” | "The page content, which is empty when need_content is false or when the content is unavailable." |
|
|
28
|
+
|
|
29
|
+
([指南](https://api-docs.deepseek.com/zh-cn/guides/search) ·
|
|
30
|
+
[guide](https://api-docs.deepseek.com/guides/search), checked 2026-10-23.)
|
|
31
|
+
|
|
32
|
+
Three facts follow, all of them absences:
|
|
33
|
+
|
|
34
|
+
1. **No size bound of any kind is documented for `content`.** The same guide gives "通常"
|
|
35
|
+
sizing for the other two text fields — `snippet` "通常为 100~200 字符" ("typically 100 to
|
|
36
|
+
200 characters"), `summary` "通常为 600~800 字符" ("typically 600 to 800 characters") —
|
|
37
|
+
but has **no analogous statement** for `content`: no "通常" range, no maximum, no
|
|
38
|
+
truncation rule, no server-side character cap. An undocumented content size is the
|
|
39
|
+
single most decision-relevant fact in this note.
|
|
40
|
+
2. **No format statement for search `content`.** The Fetch API's `content` field is
|
|
41
|
+
explicitly Markdown — "The fetched page content as Markdown text." /
|
|
42
|
+
“抓取后的 Markdown 正文” — but the Search API's `content` field carries no format
|
|
43
|
+
statement. Whether search-side `content` is the same Markdown rendering the Fetch
|
|
44
|
+
endpoint returns, or something else, is not documented. “正文缺失时为空字符串”
|
|
45
|
+
("or when the content is unavailable") implies per-result content can be missing
|
|
46
|
+
independently of `need_content`, which matches the Fetch API's own empty-content failure
|
|
47
|
+
mode; no recovery behavior is specified for search.
|
|
48
|
+
3. **No `need_content` × `count` interaction is documented.** The only sentence linking a
|
|
49
|
+
request parameter to result shape is about count and price (below); nothing says content
|
|
50
|
+
is returned for only the top N results, or that `count` behaves differently under
|
|
51
|
+
`need_content`.
|
|
52
|
+
|
|
53
|
+
## Billing and rate limits
|
|
54
|
+
|
|
55
|
+
**Billing is per successful request, and `need_content` is never mentioned in connection
|
|
56
|
+
with price.** Verbatim from the guide and pricing page:
|
|
57
|
+
|
|
58
|
+
- “Search 和 Fetch 按请求次数计费,不按 token 或返回结果条数计费” — Search and Fetch are
|
|
59
|
+
billed per request, **not by token or by number of results returned**
|
|
60
|
+
([指南](https://api-docs.deepseek.com/zh-cn/guides/search)).
|
|
61
|
+
- "count only controls how many results are returned and does not affect the per-request
|
|
62
|
+
price." / “count 只控制返回的结果条数,不影响单次请求的价格。” (same guide, both languages.)
|
|
63
|
+
- Price: Search and Fetch are each "Per 1,000 successful requests $0.75"
|
|
64
|
+
([pricing](https://api-docs.deepseek.com/quick_start/pricing); the 中文 pages state
|
|
65
|
+
每 1,000 次成功请求 5 元). A request is billable "only when it returns HTTP 200 and
|
|
66
|
+
produces a result" (Search must return non-empty `results`; Fetch must return page
|
|
67
|
+
content); failures, empty results, and timeouts are free.
|
|
68
|
+
- Neither the guide, the pricing page, nor the official FAQ
|
|
69
|
+
(`static.deepseek.com/faq`, whose embedded question set was inspected — it contains zero
|
|
70
|
+
entries about the Search/Fetch API) mentions `need_content` as a price modifier. Under
|
|
71
|
+
the documented model, a `need_content: true` search is one request at the Search
|
|
72
|
+
per-request price; there is no token-metered billing tier for Search/Fetch to move into.
|
|
73
|
+
|
|
74
|
+
One dated note on the pricing page (verbatim, EN): "when you use the DeepSeek API through
|
|
75
|
+
DeepSeek Harness, Claude Code, or Codex," server-side tool calls already hit the Search and
|
|
76
|
+
Fetch APIs; "These calls are currently free of charge;" — “这部分 Search/Fetch 接口调用目前未计费,
|
|
77
|
+
并将于北京时间 2026 年 11 月 1 日 00:00 起按次收取调用费用” (per-request billing for those
|
|
78
|
+
server-side-tool calls starts 00:00 Beijing time on 2026-11-01 — nine days after this note's
|
|
79
|
+
baseline). This note concerns the repo's direct `/v1/tools/search` calls, which follow the
|
|
80
|
+
per-request price above, not those chat-completions-internal tool calls.
|
|
81
|
+
|
|
82
|
+
**Rate limits are per endpoint and independent; `need_content` is not mentioned.** Search
|
|
83
|
+
100 QPS, Fetch 25 QPS per account; “Search 和 Fetch 分别独立限速,互不占用额度” ("traffic to
|
|
84
|
+
one does not consume the other's quota"); “除了账号级限制,Fetch 还针对目标域名有 QPS 限制”
|
|
85
|
+
(Fetch additionally has an unspecified per-domain QPS limit)
|
|
86
|
+
([guide](https://api-docs.deepseek.com/guides/search)). A `need_content: true` call is a
|
|
87
|
+
Search-endpoint call; no page documents it counting against anything but the Search quota.
|
|
88
|
+
|
|
89
|
+
## Official guidance on `need_content` vs the Fetch endpoint
|
|
90
|
+
|
|
91
|
+
None exists. The guide gives only the two positioning sentences — Search “按 query 搜索网页,
|
|
92
|
+
返回结构化结果”, Fetch “抓取单个网页,以 Markdown 文本返回页面内容” — with no use-case,
|
|
93
|
+
latency, or cost trade-off statement, in either language. (Checked 2026-10-23; also absent
|
|
94
|
+
from the FAQ.)
|
|
95
|
+
|
|
96
|
+
## Vendor architectures: who ships content-bearing search by default
|
|
97
|
+
|
|
98
|
+
Extending [search-api-defaults.md](search-api-defaults.md) (which established the defaults)
|
|
99
|
+
with the architecture reading. "Inline by default" means the caller gets page bodies in the
|
|
100
|
+
search response without setting any parameter.
|
|
101
|
+
|
|
102
|
+
| Vendor | Content inline by default? | Opt-in mechanism | Per-result content budget knob | Separate fetch tool? |
|
|
103
|
+
| --- | --- | --- | --- | --- |
|
|
104
|
+
| **Tavily** | Partial — default response carries per-result `content` of **relevance chunks**: `chunks_per_source` **3** chunks of ~500 chars (~1,500 chars/result) | Full body via `include_raw_content` (default **false**; accepts `markdown`/`text`/`true`) | Chunk count only (`chunks_per_source` 1–3, sizes `content`, **not** `raw_content`); no knob limits `raw_content` size in `/search` | Yes — `/extract` (up to 20 URLs; `extract_depth` basic/advanced; no per-URL cap; ~800-char chunks only when `query` is given) |
|
|
105
|
+
| **Exa** | No — `contents.text` defaults **false** | `contents.text: true` in the **same** `POST /search` body | **`maxCharacters`** (1–1,000,000, no published default) plus `verbosity` `compact`/`standard`/`full` | Yes — `/contents` endpoint by result ID/URL |
|
|
106
|
+
| **You.com** | No — “Omit `extraction` for a plain search that returns snippets only” | `extraction.full_page` (“crawls each result and returns the page content”) | **None for `full_page`** (only `crawl_timeout`); the `highlights` mode has a response-wide `total_max_tokens` budget of **8192** (1,024–8,192) | No dedicated fetch endpoint documented on `/v1/search` |
|
|
107
|
+
| **Perplexity** | No — Search API results are `title`/`url`/`snippet`/`date` only | Token budgets: `max_tokens` ("Maximum total webpage content tokens to return across all search results") and **`max_tokens_per_page`** ("Maximum webpage content tokens to extract from each result page"), on both the Search API and the Agent API `web_search` tool; **no default published** for either ("the default budgets apply"); on the Search API the extracted text surfaces through `snippet` | **`max_tokens_per_page`** (min 1, max 1,000,000, no default) | Yes — Agent API "Fetch URL Content" tool ("Fetch full content from known URLs") |
|
|
108
|
+
| **Anthropic** | Not caller-visible — `web_search` results carry `url`/`title`/`page_age` plus `encrypted_content` (“Encrypted content that you must pass back in multi-turn conversations”); the model reads content in its context, the caller cannot | `response_inclusion` (`"full"` default / `"excluded"`) only controls whether consumed search blocks are echoed in the response — it is not a content-size knob | **None** (dynamic filtering on `web_search_20260209+` has Claude write code to filter results before they reach context, but no caller-set budget) | Yes — `web_fetch` with token-based `max_content_tokens` (no published default) |
|
|
109
|
+
| **Firecrawl** | No — search returns `title`/`description`/`url` (+ `highlights`, default true) | `scrapeOptions` (default `{}`): "Include `scrapeOptions` with `formats: [{"type": "markdown"}] to get complete markdown content for each search result" | **None** for content size (`onlyMainContent` default true trims boilerplate, not length) | Collapsed into search via `scrapeOptions`; standalone `/scrape` endpoint also exists |
|
|
110
|
+
| **Jina** (`s.jina.ai`) | **Yes — the only default-content architecture surveyed**: "Reader searches the web, fetches the top 5 results, visits each URL, and applies `r.jina.ai` to it"; result count is fixed at 5, not a parameter | None needed (content is the product) | **`x-token-budget`** header, no default (parsed only when sent) — a global budget, not per-result | Same product (`r.jina.ai`) serves plain fetch |
|
|
111
|
+
| **DeepSeek (subject of this note)** | No — `need_content` defaults **false** | `need_content: true` | **None documented** (see above) | Yes — Fetch API, Markdown, no documented size cap |
|
|
112
|
+
|
|
113
|
+
The split, stated plainly: of the eight architectures surveyed, exactly one (Jina
|
|
114
|
+
`s.jina.ai`) ships content-bearing search as the default, and it pins the result count at 5.
|
|
115
|
+
Every other vendor makes inline content opt-in (Tavily `raw_content`, Exa `contents.text`,
|
|
116
|
+
You.com `extraction`, Firecrawl `scrapeOptions`, Perplexity token budgets) or keeps page
|
|
117
|
+
content out of the caller's hands entirely (Anthropic `encrypted_content`). Where budgets
|
|
118
|
+
exist they are per-result character caps (Exa `maxCharacters`) or per-result token caps
|
|
119
|
+
(Perplexity `max_tokens_per_page`); Tavily and You.com ship full-content modes with **no**
|
|
120
|
+
per-result size knob at all — the two closest analogues to DeepSeek's `need_content`.
|
|
121
|
+
|
|
122
|
+
Sources: [Tavily /search](https://docs.tavily.com/documentation/api-reference/endpoint/search) ·
|
|
123
|
+
[Tavily /extract](https://docs.tavily.com/documentation/api-reference/endpoint/extract) ·
|
|
124
|
+
[Exa /search](https://exa.ai/docs/reference/search) ·
|
|
125
|
+
[You.com /v1/search](https://you.com/docs/api-reference/search/v1-search) ·
|
|
126
|
+
[Perplexity /search](https://docs.perplexity.ai/api-reference/search-post) ·
|
|
127
|
+
[Perplexity Agent web_search](https://docs.perplexity.ai/docs/agent-api/tools/web-search) ·
|
|
128
|
+
[Anthropic web search tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool) ·
|
|
129
|
+
[Anthropic web fetch tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool) ·
|
|
130
|
+
[Firecrawl /search](https://docs.firecrawl.dev/api-reference/endpoint/search) ·
|
|
131
|
+
[Jina reader README](https://github.com/jina-ai/reader/blob/main/README.md) — all checked
|
|
132
|
+
2026-10-23; Tavily/Exa/You.com/Jina defaults cross-checked against
|
|
133
|
+
[search-api-defaults.md](search-api-defaults.md).
|
|
134
|
+
|
|
135
|
+
## Volume math against host caps
|
|
136
|
+
|
|
137
|
+
**Assumptions, labeled as such (not source-backed):** a typical article page body rendered
|
|
138
|
+
to Markdown is ~2,000–10,000 characters (assumption; it is consistent with this repo's own
|
|
139
|
+
calibration that 16,000 chars is a sensible budget for one *fetched* page, but no vendor
|
|
140
|
+
publishes a typical body size); English text ≈ 1 byte and CJK ≈ 3 bytes per UTF-8 char;
|
|
141
|
+
English ≈ 4 chars per token and CJK ≈ 1–1.5 chars per token (rough tokenizer conventions,
|
|
142
|
+
assumptions).
|
|
143
|
+
|
|
144
|
+
With `count=10` (the repo's default, and DeepSeek's) and `need_content: true`:
|
|
145
|
+
|
|
146
|
+
- **Response size, order of magnitude:** 10 × 2k–10k chars of body ≈ **20k–100k chars**,
|
|
147
|
+
plus ~1k chars of metadata per result (title/url/snippet/summary/scores) → roughly
|
|
148
|
+
**30k–110k chars**, i.e. **~30–110 KB** for English pages and up to **~330 KB** if pages
|
|
149
|
+
are CJK-heavy. A single malformed huge page (a pasted log, a link farm) can exceed this
|
|
150
|
+
unboundedly — there is no documented server-side cap (the key fact above).
|
|
151
|
+
- **Pi, MCP path: collides at the low end of the assumption.** Text results over **20 KB**
|
|
152
|
+
are middle-truncated (full text saved to a file) and the per-request timeout is **60 s**
|
|
153
|
+
([control-matrix.md](control-matrix.md)). Even 10 × 2k-char bodies plus metadata (~30 KB)
|
|
154
|
+
exceeds 20 KB. Staying under 20 KB at count=10 requires ≲**1.8k chars of content per
|
|
155
|
+
result** after metadata. Caveat from the same note: the *shipped* Pi adapter is the native
|
|
156
|
+
extension, whose results bypass the MCP layer — "neither the 20 KB truncation nor the 60 s
|
|
157
|
+
request timeout applies"; the cap binds the MCP fallback path only.
|
|
158
|
+
- **Claude Code: collides at the high end.** Tool output is capped at **25k tokens** by
|
|
159
|
+
default (`MAX_MCP_OUTPUT_TOKENS`), oversized text saved to a file
|
|
160
|
+
([control-matrix.md](control-matrix.md)). 30k–110k English chars ≈ 8k–28k tokens —
|
|
161
|
+
brushing or overflowing the cap at the top of the assumed range; CJK at 1–1.5 chars/token
|
|
162
|
+
≈ 20k–110k tokens — overflowing from the middle of the range up. Staying under 25k tokens
|
|
163
|
+
at count=10 requires ≲**2.5k tokens (~10k English chars) per result**.
|
|
164
|
+
- **Codex: collides by policy, magnitude unpublished.** Codex truncates tool output by a
|
|
165
|
+
per-model, server-provided truncation policy (bytes or tokens — "Server-provided
|
|
166
|
+
truncation policy metadata for a model"), using **middle truncation**, with a 20%
|
|
167
|
+
serialization allowance added (`with_serialization_allowance` = policy × 1.2); no default
|
|
168
|
+
number is published in the repo or config reference (the only literal in source,
|
|
169
|
+
`bytes(10_000)`, is a `#[cfg(test)]` fixture). The per-tool override
|
|
170
|
+
`mcp_servers.<id>.tools.<tool>.output_token_limit` exists ("Token budget for one MCP
|
|
171
|
+
tool's output, before the standard 20% serialization allowance")
|
|
172
|
+
([config reference](https://learn.chatgpt.com/docs/config-file/config-reference);
|
|
173
|
+
[mcp_types.rs at rust-v0.163.0](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/config/src/mcp_types.rs)).
|
|
174
|
+
[control-matrix.md](control-matrix.md) records Codex's 300 s MCP tool timeout — latency,
|
|
175
|
+
not size, is that host's documented ceiling.
|
|
176
|
+
- **The binding constraint is Pi's 20 KB**, roughly an order of magnitude tighter than
|
|
177
|
+
Claude Code's 25k-token default: a per-result content budget in the **~1k–2k char**
|
|
178
|
+
range keeps count=10 under every published cap; reusing the repo's fetch budget (16,000
|
|
179
|
+
chars) *per search result* would reach 160k chars per response — 8× Pi's cap and ~40k
|
|
180
|
+
tokens on Claude Code.
|
|
181
|
+
- **The repo's own 5 MiB `MAX_RESPONSE_BYTES` is not the binding constraint** at these
|
|
182
|
+
assumptions (10 results would need to average ~500 KB each to reach it), but note it
|
|
183
|
+
**rejects** oversized bodies with an error rather than truncating
|
|
184
|
+
([transport.ts](../../src/core/transport.ts) `readResponseText`), and with no documented
|
|
185
|
+
server-side cap, a pathological page could trip it.
|
|
186
|
+
|
|
187
|
+
## What this means for this repo
|
|
188
|
+
|
|
189
|
+
Fact-by-fact alignment with the current code. No recommendation is made here; the
|
|
190
|
+
maintainer decides.
|
|
191
|
+
|
|
192
|
+
- **The toggle point is one hardcoded literal.** `searchWeb` posts `need_content: false`
|
|
193
|
+
([client.ts](../../src/core/client.ts)); everything else about a content-bearing mode
|
|
194
|
+
would be new surface.
|
|
195
|
+
- **Today's pipeline would silently drop the content.** `DeepSeekSearchResult`
|
|
196
|
+
([types.ts](../../src/core/types.ts)) has no `content` field, and `processResults`
|
|
197
|
+
([search.ts](../../src/core/search.ts)) maps `SearchResult.content` from
|
|
198
|
+
`summary ?? snippet` only — a `need_content` response's page bodies would be received and
|
|
199
|
+
discarded, unbilled-for-nothing but also unrendered.
|
|
200
|
+
- **The only per-result clamp in the search path is `maxSummaryChars` (1,200)** — a
|
|
201
|
+
summary/snippet budget. `fetchMaxChars` (16,000) and `formatFetchedContent`'s
|
|
202
|
+
truncation-marker logic apply exclusively to the fetch tool path. There is **no per-result
|
|
203
|
+
page-content budget knob of any kind**; the vendor table above shows the two shapes such
|
|
204
|
+
a knob takes elsewhere (Exa's per-result `maxCharacters`, Perplexity's per-result
|
|
205
|
+
`max_tokens_per_page`).
|
|
206
|
+
- **`MAX_RESPONSE_BYTES` (5 MiB) is the only existing guard a need_content response meets**,
|
|
207
|
+
and it errors rather than truncates — the one host cap it protects against is the
|
|
208
|
+
undocumented-content-size scenario at its pathological extreme.
|
|
209
|
+
- **Text rendering flattens structure.** `formatSearchResults` ([format.ts](../../src/core/format.ts))
|
|
210
|
+
renders each result's `content` with newlines replaced by single spaces — sized for a
|
|
211
|
+
1,200-char summary. Multi-thousand-character Markdown bodies in the text content block
|
|
212
|
+
would lose heading/list structure; the `structuredContent` mirror preserves it as-is.
|
|
213
|
+
- **The two facts that make this decision unusual:** DeepSeek documents **no size bound and
|
|
214
|
+
no per-result budget knob** for `need_content` content (unlike Exa/Perplexity, whose
|
|
215
|
+
opt-in modes ship with budget parameters), and **no vendor but Jina ships
|
|
216
|
+
content-bearing search by default** — with Jina pinning count at 5, exactly the
|
|
217
|
+
count-shrinking direction the Pi 20 KB arithmetic above pushes toward.
|
|
218
|
+
|
|
219
|
+
## Sources
|
|
220
|
+
|
|
221
|
+
- DeepSeek: [Search & Fetch guide (EN)](https://api-docs.deepseek.com/guides/search) ·
|
|
222
|
+
[网页搜索与抓取 (ZH)](https://api-docs.deepseek.com/zh-cn/guides/search) ·
|
|
223
|
+
[Pricing](https://api-docs.deepseek.com/quick_start/pricing) ·
|
|
224
|
+
[FAQ (static.deepseek.com/faq)](https://static.deepseek.com/faq/index.html?lang=en) —
|
|
225
|
+
question set inspected from the app bundle, 2026-10-23
|
|
226
|
+
- Tavily: [search endpoint](https://docs.tavily.com/documentation/api-reference/endpoint/search) ·
|
|
227
|
+
[extract endpoint](https://docs.tavily.com/documentation/api-reference/endpoint/extract)
|
|
228
|
+
- Exa: [search reference](https://exa.ai/docs/reference/search)
|
|
229
|
+
- You.com: [POST /v1/search reference](https://you.com/docs/api-reference/search/v1-search)
|
|
230
|
+
- Perplexity: [Search API reference](https://docs.perplexity.ai/api-reference/search-post) ·
|
|
231
|
+
[Agent API web_search tool](https://docs.perplexity.ai/docs/agent-api/tools/web-search)
|
|
232
|
+
- Anthropic: [web search tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool) ·
|
|
233
|
+
[web fetch tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool)
|
|
234
|
+
- Firecrawl: [search endpoint](https://docs.firecrawl.dev/api-reference/endpoint/search)
|
|
235
|
+
- Jina: [reader README](https://github.com/jina-ai/reader/blob/main/README.md)
|
|
236
|
+
- Codex: [config reference](https://learn.chatgpt.com/docs/config-file/config-reference) ·
|
|
237
|
+
[mcp_types.rs at rust-v0.163.0](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/config/src/mcp_types.rs) ·
|
|
238
|
+
[openai_models.rs at rust-v0.163.0](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/protocol/src/openai_models.rs) ·
|
|
239
|
+
[output truncation lib at rust-v0.163.0](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/utils/output-truncation/src/lib.rs)
|
|
240
|
+
- Repo: [search-api-defaults.md](search-api-defaults.md) ·
|
|
241
|
+
[control-matrix.md](control-matrix.md) ·
|
|
242
|
+
[constants.ts](../../src/core/constants.ts) · [client.ts](../../src/core/client.ts) ·
|
|
243
|
+
[search.ts](../../src/core/search.ts) · [format.ts](../../src/core/format.ts) ·
|
|
244
|
+
[transport.ts](../../src/core/transport.ts)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
3
|
|
|
4
|
-
// Global config: ~/.config/opencode/opencode.jsonc
|
|
5
|
-
// Assumes the repo is cloned to
|
|
4
|
+
// Global config: ~/.config/opencode/opencode.jsonc (%APPDATA%\opencode\opencode.jsonc on Windows)
|
|
5
|
+
// Assumes the repo is cloned to the vendor/ directory beside the config
|
|
6
|
+
// (~/.config/opencode/vendor/deepseek-web-retrieval; %APPDATA%\opencode\vendor\... on Windows).
|
|
6
7
|
"plugins": [
|
|
7
8
|
{
|
|
8
9
|
"package": "./vendor/deepseek-web-retrieval",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "deepseek-web-retrieval",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"description": "Token-efficient DeepSeek Search and Fetch for coding agents, starting with OpenCode v2.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
],
|
|
49
49
|
"scripts": {
|
|
50
50
|
"lint": "biome check --error-on-warnings .",
|
|
51
|
+
"clean": "bun run scripts/clean.ts",
|
|
51
52
|
"lint:ci": "biome ci --error-on-warnings .",
|
|
52
53
|
"lint:fix": "biome check --write --error-on-warnings .",
|
|
53
54
|
"format": "biome format --write .",
|
package/src/core/auth.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { API_KEY_ENV } from "./constants";
|
|
2
2
|
|
|
3
|
+
/** Shared missing-key guidance; every surface that needs a key prints this one message. */
|
|
4
|
+
export const MISSING_API_KEY_MESSAGE = `DeepSeek API key not found. Set ${API_KEY_ENV} in the MCP server's env configuration.`;
|
|
5
|
+
|
|
3
6
|
export function apiKeyFromEnv(env: Readonly<Record<string, string | undefined>> = process.env): string | undefined {
|
|
4
7
|
const key = env[API_KEY_ENV]?.trim();
|
|
5
8
|
return key ? key : undefined;
|
package/src/core/client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FETCH_ENDPOINT, MAX_QUERY_CHARS, SEARCH_ENDPOINT } from "./constants";
|
|
1
|
+
import { FETCH_ENDPOINT, MAX_ORI_QUESTION_CHARS, MAX_QUERY_CHARS, SEARCH_ENDPOINT } from "./constants";
|
|
2
2
|
import { assertFetchHostPolicy } from "./domains";
|
|
3
|
-
import { formatFetchedContent } from "./format";
|
|
3
|
+
import { formatFetchedContent, truncate } from "./format";
|
|
4
4
|
import { buildQuery, processResults } from "./search";
|
|
5
5
|
import { postJson } from "./transport";
|
|
6
6
|
import type {
|
|
@@ -22,6 +22,16 @@ function effectiveTimeout(configuredMs: number, perCallMs: number | undefined):
|
|
|
22
22
|
return perCallMs === undefined ? configuredMs : Math.min(configuredMs, perCallMs);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* The wire value for ori_question: absent/whitespace-only means "not
|
|
27
|
+
* provided"; longer values are clamped rather than rejected — the field is
|
|
28
|
+
* auxiliary and a hard error would punish the model for verbosity.
|
|
29
|
+
*/
|
|
30
|
+
function oriQuestionValue(oriQuestion: string | undefined): string | undefined {
|
|
31
|
+
const trimmed = oriQuestion?.trim();
|
|
32
|
+
return trimmed ? truncate(trimmed, MAX_ORI_QUESTION_CHARS) : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
interface SearchResponse {
|
|
26
36
|
readonly results?: unknown;
|
|
27
37
|
}
|
|
@@ -41,10 +51,19 @@ export interface RequestOptions {
|
|
|
41
51
|
readonly timeoutMs?: number;
|
|
42
52
|
}
|
|
43
53
|
|
|
54
|
+
export interface SearchRequest extends RequestOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Per-call only — the option-resolution pipeline never populates this. The
|
|
57
|
+
* user's original question behind the search, sent as DeepSeek's optional
|
|
58
|
+
* ori_question to sharpen intent; adapters fill it from tool input.
|
|
59
|
+
*/
|
|
60
|
+
readonly oriQuestion?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
export async function searchWeb(
|
|
45
64
|
query: string,
|
|
46
65
|
options: SearchOptions,
|
|
47
|
-
request:
|
|
66
|
+
request: SearchRequest,
|
|
48
67
|
): Promise<SearchResult[]> {
|
|
49
68
|
const trimmed = query.trim();
|
|
50
69
|
if (!trimmed) throw new Error("DeepSeek search query must not be empty");
|
|
@@ -63,12 +82,15 @@ export async function searchWeb(
|
|
|
63
82
|
}
|
|
64
83
|
|
|
65
84
|
const timeout = effectiveTimeout(options.timeoutMs, request.timeoutMs);
|
|
85
|
+
const oriQuestion = oriQuestionValue(request.oriQuestion);
|
|
66
86
|
const payload = await postJson<SearchResponse>(
|
|
67
87
|
SEARCH_ENDPOINT,
|
|
68
88
|
{
|
|
69
89
|
query: builtQuery,
|
|
70
90
|
count: options.count,
|
|
71
91
|
need_content: false,
|
|
92
|
+
...(oriQuestion === undefined ? {} : { ori_question: oriQuestion }),
|
|
93
|
+
...(options.localization === undefined ? {} : { localization: options.localization }),
|
|
72
94
|
},
|
|
73
95
|
"DeepSeek Search",
|
|
74
96
|
{ ...request, timeoutMs: timeout },
|
|
@@ -129,7 +151,7 @@ export async function fetchDocument(
|
|
|
129
151
|
export function createRetrievalClient(dependencies: { readonly fetchImpl?: typeof fetch } = {}) {
|
|
130
152
|
const bind = (request: RequestOptions): RequestOptions => ({ ...request, ...dependencies });
|
|
131
153
|
return {
|
|
132
|
-
searchWeb: (query: string, options: SearchOptions, request:
|
|
154
|
+
searchWeb: (query: string, options: SearchOptions, request: SearchRequest) =>
|
|
133
155
|
searchWeb(query, options, bind(request)),
|
|
134
156
|
fetchPage: (url: URL, options: FetchOptions, request: RequestOptions) => fetchPage(url, options, bind(request)),
|
|
135
157
|
fetchDocument: (url: string | URL, options: FetchOptions, request: RequestOptions) =>
|