@deepseek-ai/dsh-web-search-deepseek 0.1.1-rc.2 → 0.1.2-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +117 -30
- package/README.zh.md +119 -32
- package/lib/index.js +20 -13
- package/lib/types/index.d.ts +1 -1
- package/lib/types/provider.d.ts +4 -1
- package/package.json +19 -19
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/web/web-search-deepseek/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: a00bdf206acdf09d008b7e3f5604dec087930aed
|
|
6
|
+
README.zh.md: 578b3efac3fb1bc4bd85ccd05ca92ec3b3707c26
|
package/README.md
CHANGED
|
@@ -1,53 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "The DeepSeek-backed search provider for ctx.web: how deployments mount native DeepSeek web search through the Anthropic-compatible Messages API, with per-search credential resolution."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# @deepseek-ai/dsh-web-search-deepseek
|
|
2
7
|
|
|
3
8
|
English | [中文](README.zh.md)
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
## Summary
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
With `dsh-web-search-deepseek`, the harness searches the web through DeepSeek's native search using an existing `DEEPSEEK_API_KEY`. Choose it when a deployment wants DeepSeek native search and accepts that one search costs a full model turn in latency and tokens, because DeepSeek exposes no dedicated search endpoint. Results come from the structured search blocks DeepSeek returns, never from scraping text out of a reply. A missing credential fails the call with a structured error; a response without a search-result block fails loudly rather than degrading. The model-facing `web_search` tool lives in `dsh-tool-web`.
|
|
8
13
|
|
|
9
|
-
##
|
|
14
|
+
## Table of Contents
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
12
22
|
|
|
13
|
-
|
|
23
|
+
-----
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
Mount the provider in a composition that already loads the web service; it registers as the `deepseek-official` search provider, so `ctx.web.search()` resolves it automatically when it is the only usable search backend — or pin it with `searchProvider: deepseek-official`.
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
| `maxTokens` | `4096` | Positive-integer upper bound on generated tokens for the Messages request. |
|
|
27
|
-
| `maxUses` | `5` | Positive-integer maximum `web_search` server-tool uses per request. |
|
|
30
|
+
### When to choose it
|
|
31
|
+
|
|
32
|
+
Choose this backend when a deployment wants DeepSeek's native server-side web search and already holds a `DEEPSEEK_API_KEY` — the provider reuses that credential reference. One search is heavier than a dedicated retrieval endpoint: DeepSeek runs the search inside a full model turn, so expect one Messages call's latency and generated tokens per search, with up to `maxUses` server-side searches per request. Avoid it when per-search cost or latency dominates.
|
|
33
|
+
|
|
34
|
+
### Minimal configuration
|
|
35
|
+
|
|
36
|
+
Load the web service and the provider; the key resolves from `ctx.credentials` when that service is mounted, otherwise from the process environment. The search endpoint uses the Anthropic-compatible base (`https://api.deepseek.com/anthropic/v1`), distinct from the chat-completions base the LLM adapter uses — never reuse `$DEEPSEEK_BASE_URL`.
|
|
28
37
|
|
|
29
38
|
```yaml
|
|
30
|
-
-
|
|
31
|
-
|
|
39
|
+
- name: '@deepseek-ai/dsh-web'
|
|
40
|
+
- name: '@deepseek-ai/dsh-web-search-deepseek'
|
|
32
41
|
config:
|
|
33
42
|
apiKeyEnv: DEEPSEEK_API_KEY
|
|
34
43
|
baseURL: https://gateway.internal/anthropic/v1
|
|
35
44
|
```
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
| Field | Default | Meaning |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `apiKey` | omitted | Literal DeepSeek API key; prefer `apiKeyEnv` so no secret enters configuration. A non-empty literal wins |
|
|
49
|
+
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | Credential reference resolved for each search through `ctx.credentials`, or from the process environment when that service is absent. A missing value fails the call as `WEB_PROVIDER_CREDENTIAL_MISSING` |
|
|
50
|
+
| `baseURL` | `https://api.deepseek.com/anthropic/v1` | Anthropic-compatible endpoint base; `/messages` is appended. Falls back to `$DEEPSEEK_SEARCH_BASE_URL`; an unparseable value makes the provider unavailable |
|
|
51
|
+
| `model` | `deepseek-v4-flash` | Anthropic-format model name |
|
|
52
|
+
| `apiVersion` | `2023-06-01` | `anthropic-version` header value |
|
|
53
|
+
| `maxTokens` | `4096` | Positive-integer upper bound on generated tokens for the Messages request |
|
|
54
|
+
| `maxUses` | `5` | Positive-integer maximum `web_search` server-tool uses per request |
|
|
55
|
+
|
|
56
|
+
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-web-search-deepseek) is the exhaustive source for every accepted field and its JSDoc. The entry above is the base layer of the provider's Settings section; a user layer over it reaches the next search, because the provider projects the section per call rather than capturing it at registration.
|
|
57
|
+
|
|
58
|
+
### What a search returns
|
|
59
|
+
|
|
60
|
+
`content` is always omitted: DeepSeek's provider prose is not trusted as an answer. `sources[]` comes from `web_search_result` items inside `web_search_tool_result` blocks — `url`, `title`, and `publishedAt` from `page_age` — with snippets joined from URL-keyed `cited_text` entries where an excerpt exists. Results are deduplicated by URL, and because DeepSeek exposes no result-count knob, the service enforces `maxResults` by truncating and flagging.
|
|
61
|
+
|
|
62
|
+
### Request logging
|
|
63
|
+
|
|
64
|
+
A search running under an initiating agent appends the log-only `web/deepseek-search-llm-request` session event immediately before dispatch. It carries the resolved endpoint, API version, and the exact secret-free JSON body sent to DeepSeek; headers and credentials are excluded. Credential failures and cancellations before dispatch create no event, while later HTTP or response failures leave the attempted request durable.
|
|
65
|
+
|
|
66
|
+
### Failures and recovery
|
|
67
|
+
|
|
68
|
+
Failures throw `WebError` with a machine-routable code: a missing credential is `WEB_PROVIDER_CREDENTIAL_MISSING`, caller cancellation is `WEB_ABORTED`, and provider or transport failures — including a response with no `web_search_tool_result` block — are `WEB_PROVIDER_ERROR`. HTTP redirects are rejected before the `Location` target is contacted. Every failure after dispatch names the resolved search endpoint and explains that search endpoint configuration is separate from chat. If the endpoint is unintended, the message tells the conversation model to guide the user to the Endpoint field under Settings > Plugins > Plugin configuration > Web search and save the change. When that page is unavailable, it names `DEEPSEEK_SEARCH_BASE_URL` and `web-search-deepseek.baseURL` as deployment configuration alternatives. The model must not choose or change the endpoint. The model-facing `web_search` tool surfaces this text under its own error wrapper.
|
|
69
|
+
|
|
70
|
+
-----
|
|
71
|
+
|
|
72
|
+
<a id="understand-the-implementation"></a>
|
|
73
|
+
## Understand the implementation
|
|
74
|
+
|
|
75
|
+
<details>
|
|
76
|
+
<summary>Implementation internals — click to expand</summary>
|
|
77
|
+
|
|
78
|
+
This section explains the design decisions behind the provider; the observable behavior is fully covered in [Use this package](#use-this-package).
|
|
38
79
|
|
|
39
|
-
|
|
80
|
+
### Design philosophy
|
|
40
81
|
|
|
41
|
-
|
|
82
|
+
The provider is built on two commitments:
|
|
42
83
|
|
|
43
|
-
|
|
84
|
+
- **Structured blocks only.** DeepSeek runs the search server-side and returns structured `web_search_tool_result` blocks; the provider parses those blocks and never scrapes URLs out of model prose. In strict mode, a response with no such block throws `WEB_PROVIDER_ERROR` instead of degrading.
|
|
85
|
+
- **One credential, resolved per search.** The provider reuses the `DEEPSEEK_API_KEY` reference (no new secret) but not `$DEEPSEEK_BASE_URL`, because search speaks the Anthropic-compatible Messages API. A mounted credentials service is authoritative; without one the provider falls back to the launching process environment. Resolving per call means a key stored or rotated in the Web Models page reaches the next search without a restart.
|
|
44
86
|
|
|
45
|
-
|
|
87
|
+
### Source map
|
|
46
88
|
|
|
47
|
-
|
|
89
|
+
| File | Role |
|
|
90
|
+
|---|---|
|
|
91
|
+
| [`src/index.ts`](src/index.ts) | Plugin entry: config schema, Settings section installation, per-search option projection |
|
|
92
|
+
| [`src/provider.ts`](src/provider.ts) | The `DeepSeekSearchProvider`: Messages request dispatch, block parsing, citation joining, credential resolution |
|
|
93
|
+
| [`src/types.ts`](src/types.ts) | Anthropic wire types for the search response |
|
|
94
|
+
| [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; contracts are enforced at the service) |
|
|
48
95
|
|
|
49
|
-
|
|
96
|
+
### Request flow
|
|
50
97
|
|
|
98
|
+
Each search projects the current Settings section into provider options — endpoint, model, key reference, limits — then resolves the credential reference through `ctx.credentials` (or the environment), appends the log-only session event, and dispatches the Messages request with the native `web_search` server tool. The response's `web_search_tool_result` blocks become `sources[]`; `cited_text` entries from text blocks are joined to their URLs as snippets; results are deduplicated by URL; and the service enforces the requested source bound on the way back.
|
|
99
|
+
|
|
100
|
+
</details>
|
|
101
|
+
|
|
102
|
+
-----
|
|
103
|
+
|
|
104
|
+
<a id="further-exploration"></a>
|
|
105
|
+
## Further Exploration
|
|
106
|
+
|
|
107
|
+
Read these pages when the package-level contract is not enough. They move from the shared vocabulary to the service, the model-facing tools, and the design rationale.
|
|
108
|
+
|
|
109
|
+
- [Web subsystem](../../../docs/subsystems/web.md) — the exhaustive search request/result vocabulary and error codes.
|
|
110
|
+
- [Web package map](../README.md) — the six-package family and each role.
|
|
111
|
+
- [dsh-web](../web/README.md) — the web service this provider registers into.
|
|
112
|
+
- [dsh-tool-web](../tool-web/README.md) — the model-facing `web_search` tool that renders this provider's sources.
|
|
113
|
+
- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-web-search-deepseek) — every accepted config field and its source declaration.
|
|
114
|
+
- [Web capability seam decision](../../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md) — why search and fetch share one provider-selection service.
|
|
115
|
+
|
|
116
|
+
-----
|
|
117
|
+
|
|
118
|
+
<a id="model-experience"></a>
|
|
51
119
|
## Model Experience
|
|
52
120
|
|
|
53
121
|
### Auxiliary DeepSeek search request
|
|
@@ -68,11 +136,11 @@ Independent of the conversation request cache. The auxiliary instruction and nat
|
|
|
68
136
|
|
|
69
137
|
#### What the model sees
|
|
70
138
|
|
|
71
|
-
Through
|
|
139
|
+
Through `dsh-tool-web`, the conversation model sees deduplicated URLs, titles, dates, and citation snippets from structured search blocks; provider prose is not trusted as an answer. This provider's exact failures include the actionable missing-credential message, `DeepSeek search credential resolution failed: <error>`, and `DeepSeek search aborted`. Request, HTTP, native-search, and response-body failures append the resolved endpoint and the conditional configuration instruction described above. The consumer owns the error wrapper.
|
|
72
140
|
|
|
73
141
|
#### Token effect
|
|
74
142
|
|
|
75
|
-
Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the
|
|
143
|
+
Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the service enforces the requested source bound.
|
|
76
144
|
|
|
77
145
|
#### KV Cache effect
|
|
78
146
|
|
|
@@ -80,7 +148,26 @@ Append-only; newly visible content follows the reusable request prefix and does
|
|
|
80
148
|
|
|
81
149
|
## Known Limitations and Deferred Work
|
|
82
150
|
|
|
151
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
These limits define when the provider is expensive or incomplete. They are current package constraints.
|
|
155
|
+
|
|
83
156
|
- **One search costs a full Messages model turn** — latency plus generated tokens, with up to `maxUses` server-side searches; DeepSeek exposes no dedicated retrieval endpoint.
|
|
84
|
-
- **Dynamic credential availability resolves inside the operation** — the synchronous
|
|
85
|
-
- **Over-returned sources still cost tokens** — with no result-count knob on the wire, `maxResults` is enforced only post-hoc by
|
|
86
|
-
- **Uncited results carry no `snippet`** — a source gains one only when a
|
|
157
|
+
- **Dynamic credential availability resolves inside the operation** — the synchronous availability check can establish that a resolver exists but cannot query an asynchronous credential store, so a selected keyless provider fails the search with `WEB_PROVIDER_CREDENTIAL_MISSING`; the stable `web_search` schema stays registered.
|
|
158
|
+
- **Over-returned sources still cost tokens** — with no result-count knob on the wire, `maxResults` is enforced only post-hoc by service truncation.
|
|
159
|
+
- **Uncited results carry no `snippet`** — a source gains one only when a text-block citation (`cited_text`) matches its URL.
|
|
160
|
+
|
|
161
|
+
<a id="dev-note"></a>
|
|
162
|
+
### Dev Note
|
|
163
|
+
|
|
164
|
+
<details>
|
|
165
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
166
|
+
|
|
167
|
+
This Dev Note is working context for maintainers: open questions and undecided directions. It is explicitly non-authoritative — shipped behavior, limits, and rationale live in the sections above and the linked Agent Notes.
|
|
168
|
+
|
|
169
|
+
#### Future: dedicated retrieval endpoint
|
|
170
|
+
|
|
171
|
+
A native DeepSeek search endpoint that avoids the full model turn would remove the dominant cost; until DeepSeek exposes one, this provider stays a Messages-call adapter.
|
|
172
|
+
|
|
173
|
+
</details>
|
package/README.zh.md
CHANGED
|
@@ -1,53 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "ctx.web 的 DeepSeek 搜索提供方:部署方如何通过 Anthropic 兼容 Messages API 挂载 DeepSeek 原生 web 搜索,并逐次解析凭据。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# @deepseek-ai/dsh-web-search-deepseek
|
|
2
7
|
|
|
3
8
|
[English](README.md) | 中文
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
## 概述
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
有了 `dsh-web-search-deepseek`,harness 可以通过 DeepSeek 原生搜索检索 web,使用部署已有的 `DEEPSEEK_API_KEY`。当部署希望使用 DeepSeek 原生搜索、并接受一次搜索在延迟与 token 上消耗一个完整模型轮次时选择它,因为 DeepSeek 不提供专用搜索端点。结果来自 DeepSeek 返回的结构化搜索块,绝不会从回复文本中抓取。凭据缺失时调用以结构化错误失败;响应缺少搜索结果块时会响亮地失败,而非降级。面向模型的 `web_search` 工具位于 `dsh-tool-web`。
|
|
8
13
|
|
|
9
|
-
##
|
|
14
|
+
## 目录
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
12
22
|
|
|
13
|
-
|
|
23
|
+
-----
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
在已加载 web 服务的组合中挂载本提供方;它以 `deepseek-official` 搜索提供方身份注册,因此当它是唯一可用的搜索后端时,`ctx.web.search()` 会自动解析到它——也可以用 `searchProvider: deepseek-official` 固定。
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
| `maxTokens` | `4096` | Messages 请求生成 token 的正整数上限。 |
|
|
27
|
-
| `maxUses` | `5` | 每次请求使用 `web_search` 服务器工具的正整数上限。 |
|
|
30
|
+
### 何时选择
|
|
31
|
+
|
|
32
|
+
当部署希望使用 DeepSeek 原生服务端 web 搜索、且已持有 `DEEPSEEK_API_KEY` 时选择此后端——提供方复用该凭据引用。一次搜索比专用检索端点更重:DeepSeek 在完整模型轮次内执行搜索,因此每次搜索都要预期一次 Messages 调用的延迟与生成 token,每次请求最多 `maxUses` 次服务端搜索。当单次搜索的成本或延迟占主导时避免使用它。
|
|
33
|
+
|
|
34
|
+
### 最小配置
|
|
35
|
+
|
|
36
|
+
加载 web 服务与本提供方;密钥在已挂载 `ctx.credentials` 服务时从其解析,否则从进程环境解析。搜索端点使用 Anthropic 兼容基址(`https://api.deepseek.com/anthropic/v1`),不同于 LLM(大语言模型)适配器使用的 chat-completions 基址——绝不复用 `$DEEPSEEK_BASE_URL`。
|
|
28
37
|
|
|
29
38
|
```yaml
|
|
30
|
-
-
|
|
31
|
-
|
|
39
|
+
- name: '@deepseek-ai/dsh-web'
|
|
40
|
+
- name: '@deepseek-ai/dsh-web-search-deepseek'
|
|
32
41
|
config:
|
|
33
42
|
apiKeyEnv: DEEPSEEK_API_KEY
|
|
34
43
|
baseURL: https://gateway.internal/anthropic/v1
|
|
35
44
|
```
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
| 字段 | 默认值 | 含义 |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `apiKey` | 未设置 | DeepSeek API 密钥字面值;优先使用 `apiKeyEnv`,避免密钥进入配置。非空字面值优先 |
|
|
49
|
+
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次搜索通过 `ctx.credentials` 解析的凭据引用;没有该服务时从进程环境解析。值缺失时调用以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败 |
|
|
50
|
+
| `baseURL` | `https://api.deepseek.com/anthropic/v1` | Anthropic 兼容端点基址;追加 `/messages`。缺省时回退到 `$DEEPSEEK_SEARCH_BASE_URL`;无法解析时提供方不可用 |
|
|
51
|
+
| `model` | `deepseek-v4-flash` | Anthropic 格式模型名称 |
|
|
52
|
+
| `apiVersion` | `2023-06-01` | `anthropic-version` 标头值 |
|
|
53
|
+
| `maxTokens` | `4096` | Messages 请求生成 token 的正整数上限 |
|
|
54
|
+
| `maxUses` | `5` | 每次请求使用 `web_search` 服务器工具的正整数上限 |
|
|
55
|
+
|
|
56
|
+
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-web-search-deepseek)是每个受支持字段及其 JSDoc 的穷尽式真源。上面的条目是提供方 Settings 段的 base 层;叠加其上的用户层会作用于下一次搜索,因为提供方是按次投影该段,而不是在注册时固化它。
|
|
57
|
+
|
|
58
|
+
### 搜索返回什么
|
|
59
|
+
|
|
60
|
+
`content` 始终省略:DeepSeek 的提供方文本不作为答案受到信任。`sources[]` 来自 `web_search_tool_result` 块内的 `web_search_result` 条目——`url`、`title`、`publishedAt` 取自 `page_age`——snippet 在存在摘录时按 URL 关联的 `cited_text` 条目拼接。结果按 URL 去重,且由于 DeepSeek 不公开结果数量旋钮,服务通过截断并标记来强制执行 `maxResults`。
|
|
61
|
+
|
|
62
|
+
### 请求日志
|
|
63
|
+
|
|
64
|
+
由发起 agent(智能体)运行的搜索会在发出请求前一刻,追加仅用于日志的 `web/deepseek-search-llm-request` 会话事件。其中包含已解析端点、API 版本,以及发送给 DeepSeek 且不含密钥的精确 JSON 请求体;不包含标头和凭据。发出请求前发生凭据失败或取消时不会创建事件,而发出请求后的 HTTP 或响应失败会保留本次请求尝试的持久记录。
|
|
65
|
+
|
|
66
|
+
### 失败与恢复
|
|
67
|
+
|
|
68
|
+
失败抛出携带可按机器路由 code 的 `WebError`:凭据缺失为 `WEB_PROVIDER_CREDENTIAL_MISSING`,调用方取消为 `WEB_ABORTED`,提供方或传输失败,包括响应中没有 `web_search_tool_result` 块,为 `WEB_PROVIDER_ERROR`。HTTP 重定向会在接触 `Location` 指向的目标之前被拒绝。请求发出后的每项失败都会指出已解析的搜索端点,并说明搜索端点配置独立于聊天端点。如果该端点不符合用户预期,错误消息会要求会话模型指导用户进入 Settings > Plugins > Plugin configuration > Web search,修改 Endpoint 字段并保存。该页面不可用时,消息会把 `DEEPSEEK_SEARCH_BASE_URL` 和 `web-search-deepseek.baseURL` 作为部署配置方式。模型不得替用户选择或修改端点。面向模型的 `web_search` 工具会在自己的错误包装层内呈现这段文本。
|
|
69
|
+
|
|
70
|
+
-----
|
|
71
|
+
|
|
72
|
+
<a id="understand-the-implementation"></a>
|
|
73
|
+
## 理解实现
|
|
74
|
+
|
|
75
|
+
<details>
|
|
76
|
+
<summary>实现细节——点击展开</summary>
|
|
77
|
+
|
|
78
|
+
本节解释提供方背后的设计决策;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
|
38
79
|
|
|
39
|
-
|
|
80
|
+
### 设计理念
|
|
40
81
|
|
|
41
|
-
|
|
82
|
+
本提供方建立在两项承诺之上:
|
|
42
83
|
|
|
43
|
-
|
|
84
|
+
- **只取结构化块。** DeepSeek 在服务端执行搜索并返回结构化的 `web_search_tool_result` 块;提供方解析这些块,绝不从模型文本中抓取 URL。严格模式下,没有此类块的响应会抛出 `WEB_PROVIDER_ERROR`,而非降级。
|
|
85
|
+
- **一个凭据,逐次解析。** 提供方复用 `DEEPSEEK_API_KEY` 引用(不新增密钥),但不复用 `$DEEPSEEK_BASE_URL`,因为搜索使用 Anthropic 兼容 Messages API。已挂载的凭据服务具有权威性;没有该服务时回退到启动进程的环境。按次解析意味着在 Web 的 Models 页中存储或轮换的密钥无需重启,即可用于下一次搜索。
|
|
44
86
|
|
|
45
|
-
|
|
87
|
+
### 源码地图
|
|
46
88
|
|
|
47
|
-
|
|
89
|
+
| 文件 | 职责 |
|
|
90
|
+
|---|---|
|
|
91
|
+
| [`src/index.ts`](src/index.ts) | 插件入口:配置 schema、Settings 段安装、逐次选项投影 |
|
|
92
|
+
| [`src/provider.ts`](src/provider.ts) | `DeepSeekSearchProvider`:Messages 请求分发、块解析、引用拼接、凭据解析 |
|
|
93
|
+
| [`src/types.ts`](src/types.ts) | 搜索响应的 Anthropic 协议类型 |
|
|
94
|
+
| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;约定在服务处强制执行) |
|
|
48
95
|
|
|
49
|
-
|
|
96
|
+
### 请求流程
|
|
50
97
|
|
|
98
|
+
每次搜索先把当前 Settings 段投影为提供方选项——端点、模型、密钥引用、上限——然后通过 `ctx.credentials`(或环境)解析凭据引用,追加仅用于日志的会话事件,并以原生 `web_search` 服务器工具分发 Messages 请求。响应中的 `web_search_tool_result` 块变为 `sources[]`;文本块中的 `cited_text` 条目按其 URL 拼接为 snippet;结果按 URL 去重;服务在返回路径上强制执行请求的来源上限。
|
|
99
|
+
|
|
100
|
+
</details>
|
|
101
|
+
|
|
102
|
+
-----
|
|
103
|
+
|
|
104
|
+
<a id="further-exploration"></a>
|
|
105
|
+
## 进一步探索
|
|
106
|
+
|
|
107
|
+
当包级约定不够用时阅读以下页面。它们从共享词汇逐步进入服务、面向模型的工具与设计依据。
|
|
108
|
+
|
|
109
|
+
- [web 子系统](../../../docs/subsystems/web.zh.md)——穷尽式的搜索请求/结果词汇与错误码。
|
|
110
|
+
- [web 包映射](../README.zh.md)——六包家族与各角色。
|
|
111
|
+
- [dsh-web](../web/README.zh.md)——本提供方注册进入的 web 服务。
|
|
112
|
+
- [dsh-tool-web](../tool-web/README.zh.md)——渲染本提供方来源的面向模型 `web_search` 工具。
|
|
113
|
+
- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-web-search-deepseek)——每个受支持配置字段及其源声明。
|
|
114
|
+
- [web 能力 seam 决策](../../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md)——搜索与抓取为何共用一项提供方选择服务。
|
|
115
|
+
|
|
116
|
+
-----
|
|
117
|
+
|
|
118
|
+
<a id="model-experience"></a>
|
|
51
119
|
## 模型体验
|
|
52
120
|
|
|
53
121
|
### 辅助 DeepSeek 搜索请求
|
|
@@ -68,19 +136,38 @@ DeepSeek 返回的提供方生成答案均不被该提供方信任为 `content`
|
|
|
68
136
|
|
|
69
137
|
#### 模型看到的内容
|
|
70
138
|
|
|
71
|
-
通过
|
|
139
|
+
通过 `dsh-tool-web`,会话模型会看到结构化搜索块中去重后的 URL、标题、日期与引用 snippet;提供方文本不会作为答案受到信任。该提供方的具体失败消息包括带有处理指引的凭据缺失消息、`DeepSeek search credential resolution failed: <error>` 和 `DeepSeek search aborted`。请求、HTTP、原生搜索和响应正文失败会追加已解析端点及前述条件式配置指引。错误包装属于消费方。
|
|
72
140
|
|
|
73
141
|
#### Token 影响
|
|
74
142
|
|
|
75
|
-
注册不会直接产生会话 token。结果 token 随返回源与 snippet
|
|
143
|
+
注册不会直接产生会话 token。结果 token 随返回源与 snippet 增长,随后服务强制执行请求的来源上限。
|
|
76
144
|
|
|
77
145
|
#### KV Cache 影响
|
|
78
146
|
|
|
79
147
|
仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
80
148
|
|
|
81
|
-
##
|
|
149
|
+
## 已知限制与延期工作
|
|
150
|
+
|
|
151
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
这些限制说明提供方在哪些情况下昂贵或不完整。它们是当前包约束。
|
|
155
|
+
|
|
156
|
+
- **一次搜索消耗一个完整的 Messages 模型轮次**——产生延迟与生成 token,最多执行 `maxUses` 次服务端搜索;DeepSeek 不公开专用检索端点。
|
|
157
|
+
- **动态凭据的可用性在操作内部解析**——同步可用性检查可以确认解析器存在,但无法查询异步凭据存储,因此选中的无密钥提供方会使搜索以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败;稳定的 `web_search` schema 仍保持注册。
|
|
158
|
+
- **超量返回的来源仍消耗 token**——协议没有结果数量旋钮,`maxResults` 只能由服务在事后截断。
|
|
159
|
+
- **未引用的结果没有 `snippet`**——只有当文本块引用(`cited_text`)匹配其 URL 时,来源才会获得 snippet。
|
|
160
|
+
|
|
161
|
+
<a id="dev-note"></a>
|
|
162
|
+
### 开发备注
|
|
163
|
+
|
|
164
|
+
<details>
|
|
165
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
166
|
+
|
|
167
|
+
本开发备注是维护者的工作上下文:开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文和相关 Agent Note 为准。
|
|
168
|
+
|
|
169
|
+
#### 未来:专用检索端点
|
|
170
|
+
|
|
171
|
+
能够避免完整模型轮次的 DeepSeek 原生搜索端点将消除主要成本;在 DeepSeek 公开此类端点之前,本提供方仍是 Messages 调用适配器。
|
|
82
172
|
|
|
83
|
-
|
|
84
|
-
- **动态凭据的可用性在操作内部解析**:同步的 `available()` 约定可以确认解析器存在,但无法查询异步凭据存储。因此,选中的无密钥提供方会使搜索以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败;稳定的 `web_search` schema 仍保持注册。调用方取消在本地与该预检存在竞态,但无法强制任意凭据后端自行停止工作。
|
|
85
|
-
- **超量返回的源仍消耗 token**:协议没有结果数量旋钮,`maxResults` 只能由 seam 在事后截断。
|
|
86
|
-
- **未引用的结果没有 `snippet`**:只有 `text` 块中的引用(`cited_text`)匹配其 URL 时,源才会获得 snippet。
|
|
173
|
+
</details>
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
3
|
-
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4
3
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
5
4
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
6
5
|
//#region lib/types/provider.js
|
|
@@ -81,7 +80,10 @@ function mapAnthropicResponse(response) {
|
|
|
81
80
|
truncated: false
|
|
82
81
|
};
|
|
83
82
|
}
|
|
84
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* The DeepSeek-backed search provider. HTTP redirects fail as `WEB_PROVIDER_ERROR`;
|
|
85
|
+
* failures after dispatch name the endpoint and tell the model how the user can configure it.
|
|
86
|
+
*/
|
|
85
87
|
var DeepSeekSearchProvider = class {
|
|
86
88
|
resolveOptions;
|
|
87
89
|
id = DEEPSEEK_PROVIDER_ID;
|
|
@@ -144,25 +146,24 @@ var DeepSeekSearchProvider = class {
|
|
|
144
146
|
});
|
|
145
147
|
} catch (error) {
|
|
146
148
|
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
147
|
-
throw
|
|
149
|
+
throw searchEndpointError(endpoint, `DeepSeek search request failed: ${String(error)}`, error);
|
|
148
150
|
}
|
|
149
151
|
if (!response.ok) {
|
|
150
152
|
let message = `DeepSeek API error (HTTP ${response.status})`;
|
|
151
153
|
try {
|
|
152
154
|
const parsed = await response.json();
|
|
153
155
|
const detail = typeof parsed.error === "string" ? parsed.error : parsed.error?.message ?? parsed.message;
|
|
154
|
-
if (detail !== void 0 && detail.length > 0) message
|
|
156
|
+
if (detail !== void 0 && detail.length > 0) message += `: ${detail}`;
|
|
155
157
|
} catch (error) {
|
|
156
158
|
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
157
159
|
}
|
|
158
|
-
throw
|
|
160
|
+
throw searchEndpointError(endpoint, message);
|
|
159
161
|
}
|
|
160
162
|
try {
|
|
161
163
|
return mapAnthropicResponse(await response.json());
|
|
162
164
|
} catch (error) {
|
|
163
165
|
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
164
|
-
|
|
165
|
-
throw new WebError(`DeepSeek returned an unprocessable response body: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
166
|
+
throw searchEndpointError(endpoint, error instanceof WebError ? error.message : `DeepSeek returned an unprocessable response body: ${String(error)}`, error);
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
/**
|
|
@@ -185,6 +186,10 @@ var DeepSeekSearchProvider = class {
|
|
|
185
186
|
throw new WebError(`DeepSeek search has no API key for "${options.apiKeyEnv ?? "DEEPSEEK_API_KEY"}"; store it through the credentials service (the web Models page writes it), export it in the launching environment, or set a literal "apiKey" in the web-search-deepseek config`, "WEB_PROVIDER_CREDENTIAL_MISSING");
|
|
186
187
|
}
|
|
187
188
|
};
|
|
189
|
+
/** Add endpoint recovery instructions to failures that occur after request dispatch begins. */
|
|
190
|
+
function searchEndpointError(endpoint, message, cause) {
|
|
191
|
+
return new WebError(`${message}\n\nThe web search request used endpoint ${JSON.stringify(endpoint)}. Search endpoint configuration is separate from chat. If that endpoint is not intended, guide the user to Settings > Plugins > Plugin configuration > Web search, where they can change and save Endpoint. If that settings page is unavailable, the user can set DEEPSEEK_SEARCH_BASE_URL or configure web-search-deepseek.baseURL to a trusted Anthropic-compatible Messages API base. Only the user should choose or change the endpoint.`, "WEB_PROVIDER_ERROR", cause === void 0 ? void 0 : { cause });
|
|
192
|
+
}
|
|
188
193
|
/**
|
|
189
194
|
* Race a same-process asynchronous preflight against caller cancellation. The
|
|
190
195
|
* attached settlement handlers keep observing an uncooperative operation after
|
|
@@ -253,7 +258,7 @@ const Config = z.object({
|
|
|
253
258
|
*/
|
|
254
259
|
const SEARCH_BASE_URL_ENV = "DEEPSEEK_SEARCH_BASE_URL";
|
|
255
260
|
/** Settings namespace carrying this provider's endpoint, model, and key reference. */
|
|
256
|
-
const WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE =
|
|
261
|
+
const WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE = "web-search-deepseek";
|
|
257
262
|
/**
|
|
258
263
|
* Project one resolved section into the options the provider serves its next
|
|
259
264
|
* search with. Environment fallbacks stay here rather than in the provider:
|
|
@@ -287,11 +292,13 @@ function resolveOptions(ctx, config) {
|
|
|
287
292
|
/** Register the DeepSeek search provider with `ctx.web`. */
|
|
288
293
|
function apply(ctx, config) {
|
|
289
294
|
let current = () => config;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
296
|
+
settingsCtx.settings.installSection(ctx, WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE, Config, config, {
|
|
297
|
+
setSource: (source) => {
|
|
298
|
+
current = source;
|
|
299
|
+
},
|
|
300
|
+
onChange: () => {}
|
|
301
|
+
});
|
|
295
302
|
});
|
|
296
303
|
ctx.web.registerSearchProvider(new DeepSeekSearchProvider(() => resolveOptions(ctx, current())));
|
|
297
304
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface Config {
|
|
|
31
31
|
}
|
|
32
32
|
export declare const Config: z<Config>;
|
|
33
33
|
/** Settings namespace carrying this provider's endpoint, model, and key reference. */
|
|
34
|
-
export declare const WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE
|
|
34
|
+
export declare const WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE = "web-search-deepseek";
|
|
35
35
|
/** Register the DeepSeek search provider with `ctx.web`. */
|
|
36
36
|
export declare function apply(ctx: Context, config: Config): void;
|
|
37
37
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/types/provider.d.ts
CHANGED
|
@@ -110,7 +110,10 @@ export declare function citationSnippets(blocks: readonly ContentBlock[]): Map<s
|
|
|
110
110
|
* @throws {@link WebError} when native search produced no result block.
|
|
111
111
|
*/
|
|
112
112
|
export declare function mapAnthropicResponse(response: AnthropicResponse): WebSearchResult;
|
|
113
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* The DeepSeek-backed search provider. HTTP redirects fail as `WEB_PROVIDER_ERROR`;
|
|
115
|
+
* failures after dispatch name the endpoint and tell the model how the user can configure it.
|
|
116
|
+
*/
|
|
114
117
|
export declare class DeepSeekSearchProvider implements WebSearchProvider {
|
|
115
118
|
private readonly resolveOptions;
|
|
116
119
|
readonly id = "deepseek-official";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-web-search-deepseek",
|
|
3
3
|
"description": "DeepSeek-backed search provider (native web_search via the Anthropic-compatible API) for the DeepSeek Harness web capability seam (ctx.web)",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -32,27 +32,27 @@
|
|
|
32
32
|
],
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@deepseek-ai/dsh-
|
|
36
|
-
"@deepseek-ai/dsh-
|
|
37
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
38
|
-
"@deepseek-ai/dsh-
|
|
39
|
-
"@deepseek-ai/dsh-
|
|
40
|
-
"@deepseek-ai/
|
|
41
|
-
"@deepseek-ai/
|
|
42
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
35
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
|
|
36
|
+
"@deepseek-ai/dsh-credentials": "^0.1.2-alpha.2",
|
|
37
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
|
|
38
|
+
"@deepseek-ai/dsh-launch-environment": "^0.1.2-alpha.2",
|
|
39
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
|
|
40
|
+
"@deepseek-ai/dsh-web": "^0.1.2-alpha.2",
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
42
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.2"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
45
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@deepseek-ai/dsh-
|
|
49
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/
|
|
54
|
-
"@deepseek-ai/
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
48
|
+
"@deepseek-ai/dsh-launch-environment": "^0.1.2-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-credentials-local": "^0.1.2-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-web": "^0.1.2-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
|
|
53
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
54
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-credentials": "^0.1.2-alpha.2",
|
|
56
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.2"
|
|
57
57
|
}
|
|
58
58
|
}
|