@deepseek-ai/dsh-web-search-deepseek 0.1.7-rc.2 → 0.2.0-rc.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 CHANGED
@@ -9,8 +9,8 @@
9
9
  en: d2c50aae9d869902
10
10
  zh: ee9f3f0ff575cbf9
11
11
  /deepseek-ai-dsh-web-search-deepseek/summary:
12
- en: b5aa65b385b55fac
13
- zh: 8ca37926bec97052
12
+ en: 1810224f58c8d39b
13
+ zh: 2e253cb447ee49c4
14
14
  /deepseek-ai-dsh-web-search-deepseek/table-of-contents:
15
15
  en: d152484eb41ac6b4
16
16
  zh: 09388d293f9be9cb
@@ -18,11 +18,14 @@
18
18
  en: e0d352d6402d9d3b
19
19
  zh: a449234557ef8bd6
20
20
  /deepseek-ai-dsh-web-search-deepseek/use-this-package/when-to-choose-it:
21
- en: 942798c9aa6ba90f
22
- zh: 800ddd760ff4ae0b
21
+ en: 0187c8af8d2d98c5
22
+ zh: 1c9607b3a662bcaa
23
23
  /deepseek-ai-dsh-web-search-deepseek/use-this-package/minimal-configuration:
24
- en: edd0d2c2f18d0277
25
- zh: e3921e7e080a16a5
24
+ en: 0553b6c926c44cf4
25
+ zh: 5cd3221d3e8eebd9
26
+ /deepseek-ai-dsh-web-search-deepseek/use-this-package/authentication:
27
+ en: 3e49592f15e94825
28
+ zh: c1bcf6dbb946149a
26
29
  /deepseek-ai-dsh-web-search-deepseek/use-this-package/what-a-search-returns:
27
30
  en: e30442c90365357b
28
31
  zh: 9c9e2f1182963a8a
@@ -36,14 +39,14 @@
36
39
  en: 9842c69ad3636bb3
37
40
  zh: 3cdc3f816cbdd0cb
38
41
  /deepseek-ai-dsh-web-search-deepseek/understand-the-implementation/design-philosophy:
39
- en: 39d77f6c18cc791c
40
- zh: d03a8d0a4e4addb2
42
+ en: 1b1760efbf4568c1
43
+ zh: fca06c959e51ce16
41
44
  /deepseek-ai-dsh-web-search-deepseek/understand-the-implementation/source-map:
42
45
  en: d354ae3f0f21f7c6
43
46
  zh: 95eb16d30d9c7a9c
44
47
  /deepseek-ai-dsh-web-search-deepseek/understand-the-implementation/request-flow:
45
- en: e2462cb33430dc4c
46
- zh: c31579cf224dcda2
48
+ en: d6469ab9ac915762
49
+ zh: 07ddf7b12c748eaf
47
50
  /deepseek-ai-dsh-web-search-deepseek/further-exploration:
48
51
  en: 59a97f197c044508
49
52
  zh: de191dd060c31801
@@ -66,8 +69,8 @@
66
69
  en: 666f5753c99783ab
67
70
  zh: b44328e99bcc45c9
68
71
  /deepseek-ai-dsh-web-search-deepseek/model-experience/conversation-tool-result-indirectly/what-the-model-sees:
69
- en: d438b6d9bc0dcc3c
70
- zh: 93a311af5a178113
72
+ en: 0764d19c9b82aa98
73
+ zh: 88b0ba22ef64225b
71
74
  /deepseek-ai-dsh-web-search-deepseek/model-experience/conversation-tool-result-indirectly/token-effect:
72
75
  en: 01ec43a9b616139d
73
76
  zh: 0a5b7c28fde34bcf
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
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`.
12
+ With `dsh-web-search-deepseek`, the harness searches the web through DeepSeek's native search using the DeepSeek account sign-in or 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`.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -29,7 +29,7 @@ Mount the provider in a composition that already loads the web service; it regis
29
29
 
30
30
  ### When to choose it
31
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.
32
+ Choose this backend when a deployment wants DeepSeek's native server-side web search and its users either sign in to a DeepSeek account or hold a `DEEPSEEK_API_KEY` — the provider reuses those credentials as [Authentication](#authentication) describes. 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
33
 
34
34
  ### Minimal configuration
35
35
 
@@ -45,8 +45,8 @@ Load the web service and the provider; the key resolves from `ctx.credentials` w
45
45
 
46
46
  | Field | Default | Meaning |
47
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` |
48
+ | `apiKey` | omitted | Literal DeepSeek API key; prefer `apiKeyEnv` so no secret enters configuration. A non-empty literal wins over `apiKeyEnv`; an account token wins over both |
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 search that needs an API key and finds none fails as `WEB_PROVIDER_CREDENTIAL_MISSING` |
50
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
51
  | `model` | `deepseek-v4-flash` | Anthropic-format model name |
52
52
  | `apiVersion` | `2023-06-01` | `anthropic-version` header value |
@@ -55,6 +55,11 @@ Load the web service and the provider; the key resolves from `ctx.credentials` w
55
55
 
56
56
  The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-web-search-deepseek) lists every accepted field. Each search captures options from the live Config references.
57
57
 
58
+ <a id="authentication"></a>
59
+ ### Authentication
60
+
61
+ A search authenticates with the DeepSeek account when the latest `request/context` event of the initiating Session names the `deepseek-account` provider route and `ctx.deepseekAccount` resolves a token for the search endpoint. The account service resolves one only while signed in and only for its deployment-configured inference origin, `https://api.deepseek.com` by default. That search sends only `x-dsh-auth-token`, even when an API key is configured. Every other search, including a call without an initiating Session and a search whose endpoint has another origin, sends the API key as both `x-api-key` and `Authorization: Bearer`. An HTTP 401 response to an account-authenticated search fails as `WEB_PROVIDER_ERROR` with sign-in guidance instead of endpoint guidance, and leaves the account signed in.
62
+
58
63
  ### What a search returns
59
64
 
60
65
  `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` and `title` directly, 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.
@@ -82,7 +87,7 @@ This section explains the design decisions behind the provider; the observable b
82
87
  The provider is built on two commitments:
83
88
 
84
89
  - **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 keeps its auxiliary request endpoint independent through `$DEEPSEEK_SEARCH_BASE_URL`. 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.
90
+ - **Conversation credentials, resolved per search.** The provider adds no secret: a search from a Session on the account route uses that account's token, and every other search reuses the `DEEPSEEK_API_KEY` reference. The auxiliary request endpoint stays independent through `$DEEPSEEK_SEARCH_BASE_URL`. 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, or an account sign-in, reaches the next search without a restart.
86
91
 
87
92
  ### Source map
88
93
 
@@ -95,7 +100,7 @@ The provider is built on two commitments:
95
100
 
96
101
  ### Request flow
97
102
 
98
- Each search captures the current Config values 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.
103
+ Each search captures the current Config values into provider options — endpoint, model, key reference, limits — then asks `ctx.deepseekAccount` for a token when the initiating Session uses the account route, otherwise 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
104
 
100
105
  </details>
101
106
 
@@ -136,7 +141,7 @@ Independent of the conversation request cache. The auxiliary instruction and nat
136
141
 
137
142
  #### What the model sees
138
143
 
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.
144
+ 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, which also names DeepSeek Account sign-in, `DeepSeek search credential resolution failed: <error>`, and `DeepSeek search aborted`. An HTTP 401 to an account-authenticated search appends an instruction to guide the user to sign in to DeepSeek again. Other 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.
140
145
 
141
146
  #### Token effect
142
147
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- 有了 `dsh-web-search-deepseek`,harness 可以通过 DeepSeek 原生搜索检索 web,使用部署已有的 `DEEPSEEK_API_KEY`。当部署希望使用 DeepSeek 原生搜索、并接受一次搜索在延迟与 token 上消耗一个完整模型轮次时选择它,因为 DeepSeek 不提供专用搜索端点。结果来自 DeepSeek 返回的结构化搜索块,绝不会从回复文本中抓取。凭据缺失时调用以结构化错误失败;响应缺少搜索结果块时会明确报错,而非降级。面向模型的 `web_search` 工具位于 `dsh-tool-web`。
12
+ 有了 `dsh-web-search-deepseek`,harness 可以通过 DeepSeek 原生搜索检索 web,使用 DeepSeek 账号登录或部署已有的 `DEEPSEEK_API_KEY`。当部署希望使用 DeepSeek 原生搜索、并接受一次搜索在延迟与 token 上消耗一个完整模型轮次时选择它,因为 DeepSeek 不提供专用搜索端点。结果来自 DeepSeek 返回的结构化搜索块,绝不会从回复文本中抓取。凭据缺失时调用以结构化错误失败;响应缺少搜索结果块时会明确报错,而非降级。面向模型的 `web_search` 工具位于 `dsh-tool-web`。
13
13
 
14
14
  ## 目录
15
15
 
@@ -29,7 +29,7 @@ kind: "package-reference"
29
29
 
30
30
  ### 何时选择
31
31
 
32
- 当部署希望使用 DeepSeek 原生服务端 web 搜索、且已持有 `DEEPSEEK_API_KEY` 时选择此后端——提供方复用该凭据引用。一次搜索比专用检索端点更重:DeepSeek 在完整模型轮次内执行搜索,因此每次搜索都要预期一次 Messages 调用的延迟与生成 token,每次请求最多 `maxUses` 次服务端搜索。当单次搜索的成本或延迟占主导时避免使用它。
32
+ 当部署希望使用 DeepSeek 原生服务端 web 搜索、且其用户登录 DeepSeek 账号或持有 `DEEPSEEK_API_KEY` 时选择此后端——提供方按[鉴权](#authentication)所述复用这些凭据。一次搜索比专用检索端点更重:DeepSeek 在完整模型轮次内执行搜索,因此每次搜索都要预期一次 Messages 调用的延迟与生成 token,每次请求最多 `maxUses` 次服务端搜索。当单次搜索的成本或延迟占主导时避免使用它。
33
33
 
34
34
  ### 最小配置
35
35
 
@@ -45,8 +45,8 @@ kind: "package-reference"
45
45
 
46
46
  | 字段 | 默认值 | 含义 |
47
47
  |---|---|---|
48
- | `apiKey` | 未设置 | DeepSeek API 密钥字面值;优先使用 `apiKeyEnv`,避免密钥进入配置。非空字面值优先 |
49
- | `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次搜索通过 `ctx.credentials` 解析的凭据引用;没有该服务时从进程环境解析。值缺失时调用以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败 |
48
+ | `apiKey` | 未设置 | DeepSeek API 密钥字面值;优先使用 `apiKeyEnv`,避免密钥进入配置。非空字面值优先于 `apiKeyEnv`;账号 token 优先于两者 |
49
+ | `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次搜索通过 `ctx.credentials` 解析的凭据引用;没有该服务时从进程环境解析。需要 API 密钥却解析不到时,搜索以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败 |
50
50
  | `baseURL` | `https://api.deepseek.com/anthropic/v1` | Anthropic 兼容端点基址;追加 `/messages`。缺省时回退到 `$DEEPSEEK_SEARCH_BASE_URL`;无法解析时提供方不可用 |
51
51
  | `model` | `deepseek-v4-flash` | Anthropic 格式模型名称 |
52
52
  | `apiVersion` | `2023-06-01` | `anthropic-version` 标头值 |
@@ -55,6 +55,11 @@ kind: "package-reference"
55
55
 
56
56
  生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-web-search-deepseek) 列出所有接受的字段。每次搜索从即时 Config 引用捕获选项。
57
57
 
58
+ <a id="authentication"></a>
59
+ ### 鉴权
60
+
61
+ 当发起会话最近的 `request/context` 事件指明 `deepseek-account` 提供方路由,且 `ctx.deepseekAccount` 为搜索端点解析出 token 时,搜索使用 DeepSeek 账号鉴权。账号服务仅在已登录时、且仅对部署配置的推理 origin(默认 `https://api.deepseek.com`)解析 token。这类搜索只发送 `x-dsh-auth-token`,即使配置了 API 密钥也是如此。其余所有搜索,包括没有发起会话的调用以及端点属于其他 origin 的搜索,都把 API 密钥同时作为 `x-api-key` 与 `Authorization: Bearer` 发送。账号鉴权的搜索收到 HTTP 401 时以 `WEB_PROVIDER_ERROR` 失败,附带登录指引而非端点指引,账号保持登录。
62
+
58
63
  ### 搜索返回什么
59
64
 
60
65
  `content` 始终省略:DeepSeek 的提供方文本不作为答案受到信任。`sources[]` 来自 `web_search_tool_result` 块内的 `web_search_result` 条目——`url` 和 `title` 直接取自同名字段,`publishedAt` 取自 `page_age`——snippet 在存在摘录时按 URL 关联的 `cited_text` 条目拼接。结果按 URL 去重,且由于 DeepSeek 不公开结果数量旋钮,服务通过截断并标记来强制执行 `maxResults`。
@@ -82,7 +87,7 @@ kind: "package-reference"
82
87
  本提供方建立在两项承诺之上:
83
88
 
84
89
  - **只取结构化块。** DeepSeek 在服务端执行搜索并返回结构化的 `web_search_tool_result` 块;提供方解析这些块,绝不从模型文本中抓取 URL。严格模式下,没有此类块的响应会抛出 `WEB_PROVIDER_ERROR`,而非降级。
85
- - **一个凭据,逐次解析。** 提供方复用 `DEEPSEEK_API_KEY` 引用(不新增密钥),但通过 `$DEEPSEEK_SEARCH_BASE_URL` 保持辅助请求端点独立。已挂载的凭据服务具有权威性;没有该服务时回退到启动进程的环境。按次解析意味着在 Web 的 Models 页中存储或轮换的密钥无需重启,即可用于下一次搜索。
90
+ - **会话凭据,逐次解析。** 提供方不新增密钥:来自账号路由会话的搜索使用该账号的 token,其余搜索复用 `DEEPSEEK_API_KEY` 引用。辅助请求端点通过 `$DEEPSEEK_SEARCH_BASE_URL` 保持独立。已挂载的凭据服务具有权威性;没有该服务时回退到启动进程的环境。按次解析意味着在 Web 的 Models 页中存储或轮换的密钥,或一次账号登录,无需重启即可用于下一次搜索。
86
91
 
87
92
  ### 源码地图
88
93
 
@@ -95,7 +100,7 @@ kind: "package-reference"
95
100
 
96
101
  ### 请求流程
97
102
 
98
- 每次搜索先把当前 Config 段投影为提供方选项——端点、模型、密钥引用、上限——然后通过 `ctx.credentials`(或环境)解析凭据引用,追加仅用于日志的会话事件,并以原生 `web_search` 服务器工具分发 Messages 请求。响应中的 `web_search_tool_result` 块变为 `sources[]`;文本块中的 `cited_text` 条目按其 URL 拼接为 snippet;结果按 URL 去重;服务在返回路径上强制执行请求的来源上限。
103
+ 每次搜索先把当前 Config 段投影为提供方选项——端点、模型、密钥引用、上限——然后在发起会话使用账号路由时向 `ctx.deepseekAccount` 请求 token,否则通过 `ctx.credentials`(或环境)解析凭据引用,追加仅用于日志的会话事件,并以原生 `web_search` 服务器工具分发 Messages 请求。响应中的 `web_search_tool_result` 块变为 `sources[]`;文本块中的 `cited_text` 条目按其 URL 拼接为 snippet;结果按 URL 去重;服务在返回路径上强制执行请求的来源上限。
99
104
 
100
105
  </details>
101
106
 
@@ -136,7 +141,7 @@ kind: "package-reference"
136
141
 
137
142
  #### 模型看到的内容
138
143
 
139
- 通过 `dsh-tool-web`,会话模型会看到结构化搜索块中去重后的 URL、标题、日期与引用 snippet;提供方文本不会作为答案受到信任。该提供方的具体失败消息包括带有处理指引的凭据缺失消息、`DeepSeek search credential resolution failed: <error>` 和 `DeepSeek search aborted`。请求、HTTP、原生搜索和响应正文失败会追加已解析端点及前述条件式配置指引。错误包装属于消费方。
144
+ 通过 `dsh-tool-web`,会话模型会看到结构化搜索块中去重后的 URL、标题、日期与引用 snippet;提供方文本不会作为答案受到信任。该提供方的具体失败消息包括带有处理指引的凭据缺失消息(其中也提到 DeepSeek 账号登录)、`DeepSeek search credential resolution failed: <error>` 和 `DeepSeek search aborted`。账号鉴权的搜索收到 HTTP 401 时,会追加引导用户重新登录 DeepSeek 的指引。其他请求、HTTP、原生搜索和响应正文失败会追加已解析端点及前述条件式配置指引。错误包装属于消费方。
140
145
 
141
146
  #### Token 影响
142
147
 
package/lib/index.js CHANGED
@@ -98,13 +98,13 @@ var DeepSeekSearchProvider = class {
98
98
  }
99
99
  available() {
100
100
  const options = this.resolveOptions();
101
- return ((options.apiKey?.length ?? 0) > 0 || options.resolveApiKey !== void 0) && URL.canParse(options.baseURL) && isPositiveInteger(options.maxTokens) && isPositiveInteger(options.maxUses);
101
+ return ((options.apiKey?.length ?? 0) > 0 || options.resolveApiKey !== void 0 || options.resolveAccountToken !== void 0) && URL.canParse(options.baseURL) && isPositiveInteger(options.maxTokens) && isPositiveInteger(options.maxUses);
102
102
  }
103
103
  async search(request, signal) {
104
104
  const options = this.resolveOptions();
105
- const apiKey = await this.apiKey(options, signal);
106
- throwIfSearchAborted(signal);
107
105
  const endpoint = `${options.baseURL}/messages`;
106
+ const auth = await this.authHeaders(options, endpoint, signal);
107
+ throwIfSearchAborted(signal);
108
108
  const body = {
109
109
  model: options.model,
110
110
  max_tokens: options.maxTokens,
@@ -133,8 +133,7 @@ var DeepSeekSearchProvider = class {
133
133
  method: "POST",
134
134
  redirect: "error",
135
135
  headers: {
136
- "x-api-key": apiKey,
137
- "authorization": `Bearer ${apiKey}`,
136
+ ...auth.headers,
138
137
  "anthropic-version": options.apiVersion,
139
138
  "content-type": "application/json",
140
139
  "accept": "application/json",
@@ -148,7 +147,8 @@ var DeepSeekSearchProvider = class {
148
147
  throw searchEndpointError(endpoint, `DeepSeek search request failed: ${String(error)}`, error);
149
148
  }
150
149
  if (!response.ok) {
151
- let message = `DeepSeek API error (HTTP ${response.status})`;
150
+ const status = response.status;
151
+ let message = `DeepSeek API error (HTTP ${status})`;
152
152
  try {
153
153
  const parsed = await response.json();
154
154
  const detail = typeof parsed.error === "string" ? parsed.error : parsed.error?.message ?? parsed.message;
@@ -156,6 +156,7 @@ var DeepSeekSearchProvider = class {
156
156
  } catch (error) {
157
157
  if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
158
158
  }
159
+ if (status === 401 && auth.kind === "account") throw accountRejectedError(message);
159
160
  throw searchEndpointError(endpoint, message);
160
161
  }
161
162
  try {
@@ -166,7 +167,30 @@ var DeepSeekSearchProvider = class {
166
167
  }
167
168
  }
168
169
  /**
169
- * Resolve one operation's credential without retaining it on the provider.
170
+ * Resolve one operation's authentication headers without retaining a credential on the provider.
171
+ * @param options - the caller's snapshot, so the credential and the endpoint it is sent to come from one section.
172
+ * @param endpoint - the Messages endpoint this operation dispatches to.
173
+ * @param signal - abort signal for the surrounding search.
174
+ * @returns the account-token header when one resolves, otherwise the API-key headers, tagged by credential kind.
175
+ */
176
+ async authHeaders(options, endpoint, signal) {
177
+ const { resolveAccountToken } = options;
178
+ const token = resolveAccountToken === void 0 ? void 0 : await resolveCredential(() => resolveAccountToken(endpoint), signal);
179
+ if (token !== void 0 && token.length > 0) return {
180
+ kind: "account",
181
+ headers: { "x-dsh-auth-token": token }
182
+ };
183
+ const apiKey = await this.apiKey(options, signal);
184
+ return {
185
+ kind: "api-key",
186
+ headers: {
187
+ "x-api-key": apiKey,
188
+ "authorization": `Bearer ${apiKey}`
189
+ }
190
+ };
191
+ }
192
+ /**
193
+ * Resolve one operation's API key without retaining it on the provider.
170
194
  * @param options - the caller's snapshot, so the key and the endpoint it is sent to come from one section.
171
195
  * @param signal - abort signal for the surrounding search.
172
196
  * @returns the resolved key.
@@ -174,17 +198,33 @@ var DeepSeekSearchProvider = class {
174
198
  async apiKey(options, signal) {
175
199
  throwIfSearchAborted(signal);
176
200
  if (options.apiKey !== void 0 && options.apiKey.length > 0) return options.apiKey;
177
- let resolved;
178
- try {
179
- resolved = await abortable(options.resolveApiKey?.() ?? Promise.resolve(void 0), signal);
180
- } catch (error) {
181
- if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
182
- throw new WebError(`DeepSeek search credential resolution failed: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
183
- }
201
+ const { resolveApiKey } = options;
202
+ const resolved = resolveApiKey === void 0 ? void 0 : await resolveCredential(resolveApiKey, signal);
184
203
  if (resolved !== void 0 && resolved.length > 0) return resolved;
185
- 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");
204
+ 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; a conversation using a DeepSeek Account model searches with the account sign-in instead`, "WEB_PROVIDER_CREDENTIAL_MISSING");
186
205
  }
187
206
  };
207
+ /**
208
+ * Run one credential resolver under the search's cancellation signal. An
209
+ * already-cancelled search never starts the resolver.
210
+ * @param resolve - the resolver; a synchronous throw is mapped like a rejection.
211
+ * @param signal - abort signal for the surrounding search.
212
+ * @returns the resolved credential, or undefined when the resolver supplied none.
213
+ * @throws {@link WebError} `WEB_ABORTED` on cancellation, `WEB_PROVIDER_ERROR` when the resolver fails.
214
+ */
215
+ async function resolveCredential(resolve, signal) {
216
+ throwIfSearchAborted(signal);
217
+ try {
218
+ return await abortable(resolve(), signal);
219
+ } catch (error) {
220
+ if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
221
+ throw new WebError(`DeepSeek search credential resolution failed: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
222
+ }
223
+ }
224
+ /** Replace endpoint guidance with sign-in guidance when DeepSeek rejects the account token. */
225
+ function accountRejectedError(message) {
226
+ return new WebError(`${message}\n\nDeepSeek rejected the account sign-in used for this web search. Guide the user to sign in to DeepSeek again; the search endpoint does not need changing.`, "WEB_PROVIDER_ERROR");
227
+ }
188
228
  /** Add endpoint recovery instructions to failures that occur after request dispatch begins. */
189
229
  function searchEndpointError(endpoint, message, cause) {
190
230
  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 });
@@ -247,6 +287,8 @@ const Config = z.object({
247
287
  * `$DEEPSEEK_BASE_URL` and selected protocol.
248
288
  */
249
289
  const SEARCH_BASE_URL_ENV = "DEEPSEEK_SEARCH_BASE_URL";
290
+ /** Provider route id `dsh-llm-deepseek-account` registers; `request/context` events record it per Session. */
291
+ const ACCOUNT_PROVIDER = "deepseek-account";
250
292
  /** Settings namespace carrying this provider's endpoint, model, and key reference. */
251
293
  const WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE = "web-search-deepseek";
252
294
  /**
@@ -262,6 +304,10 @@ function resolveOptions(ctx, config) {
262
304
  const literalApiKey = config.apiKey !== void 0 && config.apiKey.length > 0 ? config.apiKey : void 0;
263
305
  return {
264
306
  ...literalApiKey === void 0 ? {} : { apiKey: literalApiKey },
307
+ resolveAccountToken: async (endpoint) => {
308
+ if (ctx.get("agents")?.currentInitiator()?.session.requestContext()?.provider !== ACCOUNT_PROVIDER) return void 0;
309
+ return await ctx.get("deepseekAccount")?.resolveToken(endpoint);
310
+ },
265
311
  resolveApiKey: async () => {
266
312
  const credentials = ctx.get("credentials");
267
313
  if (credentials !== void 0) return (await credentials.resolve(apiKeyEnv))?.value;
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Register a DeepSeek-backed provider in `ctx.web`. It calls the Anthropic-compatible Messages API
3
- * with native `web_search_20250305`. The provider reuses `DEEPSEEK_API_KEY` but not
4
- * `DEEPSEEK_BASE_URL`; auxiliary search has its own endpoint configuration.
3
+ * with native `web_search_20250305`. A search initiated by a Session on the DeepSeek account route
4
+ * authenticates with the account token when the account service allows the search endpoint;
5
+ * every other search reuses `DEEPSEEK_API_KEY`. The provider does not reuse `DEEPSEEK_BASE_URL`;
6
+ * auxiliary search has its own endpoint configuration.
5
7
  * @module @deepseek-ai/dsh-web-search-deepseek
6
8
  */
7
9
  import type { Volatile } from '@deepseek-ai/cordis';
@@ -65,6 +65,12 @@ declare module '@deepseek-ai/dsh-session/types' {
65
65
  }
66
66
  /** Resolved provider options (the plugin's `apply` supplies credential and constant defaults). */
67
67
  export interface DeepSeekSearchProviderOptions {
68
+ /**
69
+ * Resolve the DeepSeek account token for one search endpoint. A token takes
70
+ * precedence over every API key and is sent only as `x-dsh-auth-token`;
71
+ * `undefined` selects API-key authentication.
72
+ */
73
+ resolveAccountToken?: (endpoint: string) => Promise<string | undefined>;
68
74
  /** Literal DeepSeek API key; when present it wins over {@link resolveApiKey}. */
69
75
  apiKey?: string;
70
76
  /** Resolve the current DeepSeek API key for one search operation. */
@@ -127,7 +133,15 @@ export declare class DeepSeekSearchProvider implements WebSearchProvider {
127
133
  available(): boolean;
128
134
  search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
129
135
  /**
130
- * Resolve one operation's credential without retaining it on the provider.
136
+ * Resolve one operation's authentication headers without retaining a credential on the provider.
137
+ * @param options - the caller's snapshot, so the credential and the endpoint it is sent to come from one section.
138
+ * @param endpoint - the Messages endpoint this operation dispatches to.
139
+ * @param signal - abort signal for the surrounding search.
140
+ * @returns the account-token header when one resolves, otherwise the API-key headers, tagged by credential kind.
141
+ */
142
+ private authHeaders;
143
+ /**
144
+ * Resolve one operation's API key without retaining it on the provider.
131
145
  * @param options - the caller's snapshot, so the key and the endpoint it is sent to come from one section.
132
146
  * @param signal - abort signal for the surrounding search.
133
147
  * @returns the resolved key.
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.7-rc.2",
4
+ "version": "0.2.0-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,24 +27,31 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
31
- "@deepseek-ai/dsh-credentials": "0.1.7-rc.2",
32
- "@deepseek-ai/dsh-launch-environment": "0.1.7-rc.2",
33
- "@deepseek-ai/dsh-web": "0.1.7-rc.2",
30
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
31
+ "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
32
+ "@deepseek-ai/dsh-deepseek-account": "0.2.0-rc.2",
33
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
34
+ "@deepseek-ai/dsh-web": "0.2.0-rc.2",
34
35
  "@deepseek-ai/cordis": "~4.0.4",
35
- "@deepseek-ai/dsh-session": "0.1.7-rc.2"
36
+ "@deepseek-ai/dsh-launch-environment": "0.2.0-rc.2"
36
37
  },
37
38
  "dependencies": {
38
39
  "@deepseek-ai/schemastery": "~3.18.4"
39
40
  },
40
41
  "devDependencies": {
41
- "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
42
- "@deepseek-ai/dsh-credentials": "0.1.7-rc.2",
43
- "@deepseek-ai/dsh-credentials-local": "0.1.7-rc.2",
44
- "@deepseek-ai/dsh-launch-environment": "0.1.7-rc.2",
45
- "@deepseek-ai/dsh-session": "0.1.7-rc.2",
46
- "@deepseek-ai/dsh-web": "0.1.7-rc.2",
42
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
43
+ "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
44
+ "@deepseek-ai/dsh-credentials-local": "0.2.0-rc.2",
45
+ "@deepseek-ai/dsh-deepseek-account": "0.2.0-rc.2",
46
+ "@deepseek-ai/dsh-launch-environment": "0.2.0-rc.2",
47
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
48
+ "@deepseek-ai/dsh-http-proxy": "0.2.0-rc.2",
47
49
  "@deepseek-ai/cordis": "~4.0.4",
48
- "@deepseek-ai/dsh-http-proxy": "0.1.7-rc.2"
50
+ "@deepseek-ai/dsh-web": "0.2.0-rc.2"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "@deepseek-ai/dsh-deepseek-account": {
54
+ "optional": true
55
+ }
49
56
  }
50
57
  }