@deepseek-ai/dsh-web-search-deepseek 0.0.1-rc.1

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/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, DeepSeek
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/web/web-search-deepseek/README.md
5
+ README.md: fb9e528633954b8eb9fd0fec8d19e24381a63f12
6
+ README.zh.md: 4e761aa6ef1a67ce17fbc1cabf595af7f24e15e6
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @deepseek-ai/dsh-web-search-deepseek
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ A [DeepSeek](https://deepseek.com)-backed `WebSearchProvider` for the harness [web capability seam](../web/README.md) (`ctx.web`). It calls DeepSeek's **Anthropic-compatible Messages API** (`POST {baseURL}/messages`) with the native `web_search_20250305` server tool enabled, and maps the structured `web_search_tool_result` blocks DeepSeek returns into the seam's normalized `WebSearchResult`.
6
+
7
+ This is an **implementation** package: it registers a provider into `ctx.web`, resolves its credential for each search through the optional `ctx.credentials` seam, records the auxiliary request in the initiating Agent session when one exists, and does not register a model-facing tool. Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`). The Anthropic wire shape is a provider-private detail — it does **not** make this provider depend on `ctx.llm`.
8
+
9
+ ## How it differs from a dedicated search endpoint
10
+
11
+ Exa and Perplexity expose dedicated search endpoints; DeepSeek does not. Instead this provider issues a **full Messages model call** carrying the `web_search` server tool, so one search costs a complete model turn in latency and tokens — heavier than a pure retrieval endpoint. 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**.
12
+
13
+ **Strict mode**: if the response carries no `web_search_tool_result` block (native search did not trigger), the provider throws `WebError` `WEB_PROVIDER_ERROR` rather than degrading to prose-scraping.
14
+
15
+ It reuses the `DEEPSEEK_API_KEY` credential reference (no new secret) but **not** `$DEEPSEEK_BASE_URL`: the search endpoint is the Anthropic-compatible base (`https://api.deepseek.com/anthropic/v1`), distinct from the chat-completions base (`https://api.deepseek.com`) the LLM adapter uses. A mounted credentials service is authoritative; without one, the provider falls back to the launching process environment. The reference is resolved for each search, so a key stored or rotated by the Web Models page reaches the next call without a restart.
16
+
17
+ ## Config
18
+
19
+ | Key | Default | Meaning |
20
+ |---|---|---|
21
+ | `apiKey` | omitted | Literal DeepSeek API key. Prefer `apiKeyEnv` so no secret enters configuration; a non-empty literal wins. |
22
+ | `apiKeyEnv` | `DEEPSEEK_API_KEY` | Credential reference resolved for each search through `ctx.credentials`, or from the process environment when that seam is absent. A missing value fails the call as `WEB_PROVIDER_CREDENTIAL_MISSING`. |
23
+ | `baseURL` | `https://api.deepseek.com/anthropic/v1` | Anthropic-compatible endpoint base; `/messages` is appended. Falls back to `$DEEPSEEK_SEARCH_BASE_URL` from any environment layer; do not reuse `$DEEPSEEK_BASE_URL`, which belongs to the chat-completions LLM adapter. An unparseable value makes the provider unavailable. |
24
+ | `model` | `deepseek-v4-flash` | Anthropic-format model name. |
25
+ | `apiVersion` | `2023-06-01` | `anthropic-version` header value. |
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. |
28
+
29
+ ```yaml
30
+ - id: web-search-deepseek
31
+ name: '@deepseek-ai/dsh-web-search-deepseek'
32
+ config:
33
+ apiKeyEnv: DEEPSEEK_API_KEY
34
+ baseURL: https://gateway.internal/anthropic/v1
35
+ ```
36
+
37
+ ## Mapping
38
+
39
+ DeepSeek returns no provider-generated answer surface this provider trusts as `content`, so `content` is omitted. `sources[]` comes from `web_search_result` items inside `web_search_tool_result` blocks: `url` ← `url`, `title` ← `title`, and `publishedAt` ← `page_age`. Snippets live separately as URL-keyed `cited_text` entries in a text block's `citations[]`; the provider joins them, leaving `snippet` absent when no excerpt exists.
40
+
41
+ Results are deduplicated by URL because one request may surface the same page across searches. DeepSeek exposes `maxUses`, not a result-count knob, so the seam enforces `maxResults` by truncating `sources[]` and setting `truncated`.
42
+
43
+ Provider failures become `WEB_PROVIDER_ERROR`; caller cancellation becomes `WEB_ABORTED`. HTTP redirects are rejected before the `Location` target is contacted and surface as `WEB_PROVIDER_ERROR`.
44
+
45
+ ## Request logging
46
+
47
+ Immediately before dispatch, a search running under an initiating Agent appends the log-only `web/deepseek-search-llm-request` session event. It contains the resolved endpoint, API version, and 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. Direct programmatic provider calls outside an Agent have no initiating session to log.
48
+
49
+ ## Model Experience
50
+
51
+ ### Auxiliary DeepSeek search request
52
+
53
+ #### What the model sees
54
+
55
+ A separate DeepSeek model receives exactly `Perform a web search for the query: <query>` as its user text and one native `web_search` server-tool definition. This request is not part of the conversation model's context.
56
+
57
+ #### Token effect
58
+
59
+ Separate provider input and output tokens are incurred for each search; `maxTokens` caps generated output and `maxUses` caps native search uses.
60
+
61
+ #### KV Cache effect
62
+
63
+ Independent of the conversation request cache. The auxiliary instruction and native tool definition can form a stable prefix, but each changed query or model route prevents reuse from its first difference.
64
+
65
+ ### Conversation tool result, indirectly
66
+
67
+ #### What the model sees
68
+
69
+ Through [`dsh-tool-web`](../tool-web/README.md), 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>`, `DeepSeek search aborted`, `DeepSeek search request failed: <error>`, `DeepSeek returned no web_search_tool_result blocks; the request may not have triggered native web search`, and `DeepSeek returned an unprocessable response body: <error>`; HTTP failures preserve the provider message. The consumer owns the error wrapper.
70
+
71
+ #### Token effect
72
+
73
+ Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the seam enforces the requested source bound.
74
+
75
+ #### KV Cache effect
76
+
77
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
78
+
79
+ ## Known Limitations and Deferred Work
80
+
81
+ - **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.
82
+ - **Dynamic credential availability resolves inside the operation** — the synchronous `available()` contract can establish that a resolver exists but cannot query an asynchronous credential store. A selected keyless provider therefore fails the search with `WEB_PROVIDER_CREDENTIAL_MISSING`; the stable `web_search` schema remains registered. Caller cancellation races this preflight locally, but cannot force an arbitrary credential backend itself to stop work.
83
+ - **Over-returned sources still cost tokens** — with no result-count knob on the wire, `maxResults` is enforced only post-hoc by seam truncation.
84
+ - **Uncited results carry no `snippet`** — a source gains one only when a `text` block citation (`cited_text`) matches its URL.
package/README.zh.md ADDED
@@ -0,0 +1,84 @@
1
+ # @deepseek-ai/dsh-web-search-deepseek
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 由 [DeepSeek](https://deepseek.com) 支持的 `WebSearchProvider`,用于 harness [web 能力 seam](../web/README.md)(`ctx.web`)。它调用 DeepSeek 的 **Anthropic 兼容 Messages API**(`POST {baseURL}/messages`),启用原生 `web_search_20250305` 服务器工具,并把 DeepSeek 返回的结构化 `web_search_tool_result` 块映射为 seam 规范化的 `WebSearchResult`。
6
+
7
+ 这是一个**实现**包:它向 `ctx.web` 注册提供方,通过可选的 `ctx.credentials` seam 为每次搜索解析凭据,若存在发起请求的 agent(智能体)会话,还会在其中记录该辅助请求,且不注册面向模型的工具。与 `@deepseek-ai/dsh-llm-deepseek` 一样,它是函数/命名空间插件(`inject: ['web']`)。Anthropic 协议格式(wire format)是提供方私有细节,并**不**使该提供方依赖 `ctx.llm`。
8
+
9
+ ## 与专用搜索端点的区别
10
+
11
+ Exa 和 Perplexity 提供专用搜索端点,DeepSeek 则没有。该提供方改为发起一次携带 `web_search` 服务器工具的**完整 Messages 模型调用**,因此一次搜索会消耗完整模型轮次的延迟与 token,比纯检索端点更重。DeepSeek 在服务器侧执行搜索,返回**结构化** `web_search_tool_result` 块;提供方解析这些块,**绝不会从模型文本中抓取 URL**。
12
+
13
+ **严格模式**:如果响应不含 `web_search_tool_result` 块(未触发原生搜索),提供方会抛出 `WebError` `WEB_PROVIDER_ERROR`,而非降级为文本抓取。
14
+
15
+ 它复用 `DEEPSEEK_API_KEY` 凭据引用(不增加密钥),但**不会**复用 `$DEEPSEEK_BASE_URL`:搜索端点使用 Anthropic 兼容基址(`https://api.deepseek.com/anthropic/v1`),不同于大语言模型(LLM)适配器使用的 chat-completions 基址(`https://api.deepseek.com`)。已挂载的凭据服务具有权威性;没有该服务时,提供方会回退到启动进程的环境变量。每次搜索都会解析该引用,因此在 Web 的 Models 页中存储或轮换的密钥无需重启,即可用于下一次调用。
16
+
17
+ ## 配置
18
+
19
+ | 配置键 | 默认值 | 含义 |
20
+ |---|---|---|
21
+ | `apiKey` | 未设置 | DeepSeek API 密钥字面值。优先使用 `apiKeyEnv`,避免密钥进入配置;非空字面值优先。 |
22
+ | `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次搜索都会通过 `ctx.credentials` 解析该凭据引用;没有该 seam 时则从进程环境解析。值缺失时,调用以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败。 |
23
+ | `baseURL` | `https://api.deepseek.com/anthropic/v1` | Anthropic 兼容端点基址;追加 `/messages`。缺省时回退到任一环境层中的 `$DEEPSEEK_SEARCH_BASE_URL`;禁止复用属于 chat-completions LLM 适配器的 `$DEEPSEEK_BASE_URL`。无法解析时提供方不可用。 |
24
+ | `model` | `deepseek-v4-flash` | Anthropic 格式模型名称。 |
25
+ | `apiVersion` | `2023-06-01` | `anthropic-version` 标头值。 |
26
+ | `maxTokens` | `4096` | Messages 请求生成 token 的正整数上限。 |
27
+ | `maxUses` | `5` | 每次请求使用 `web_search` 服务器工具的正整数上限。 |
28
+
29
+ ```yaml
30
+ - id: web-search-deepseek
31
+ name: '@deepseek-ai/dsh-web-search-deepseek'
32
+ config:
33
+ apiKeyEnv: DEEPSEEK_API_KEY
34
+ baseURL: https://gateway.internal/anthropic/v1
35
+ ```
36
+
37
+ ## 映射
38
+
39
+ DeepSeek 不返回该提供方可作为 `content` 信任的提供方生成答案表层,因此省略 `content`。`sources[]` 来自 `web_search_result` 配置项,这些配置项位于 `web_search_tool_result` 块内:`url` ← `url`、`title` ← `title`、`publishedAt` ← `page_age`。`cited_text` 配置项按 URL 标识,单独位于文本块的 `citations[]` 中;提供方会将其作为 snippet 连接,没有摘录时省略 `snippet`。
40
+
41
+ 结果按 URL 去重,因为一次请求可能在多次搜索中呈现同一页面。DeepSeek 公开 `maxUses` 而非结果数量旋钮,因此 seam 会强制执行 `maxResults`:截断 `sources[]` 并设置 `truncated`。
42
+
43
+ 提供方失败变为 `WEB_PROVIDER_ERROR`;调用方取消变为 `WEB_ABORTED`。HTTP 重定向会在接触 `Location` 目标前被拒绝,并以 `WEB_PROVIDER_ERROR` 呈现。
44
+
45
+ ## 请求日志
46
+
47
+ 由 agent 发起的搜索会在发出请求前一刻,向相应会话追加仅用于日志的 `web/deepseek-search-llm-request` 会话事件。其中包含已解析端点、API 版本,以及发送给 DeepSeek 且不含密钥的精确 JSON 请求体;不包含标头和凭据。发出请求前发生凭据处理失败或取消时不会创建事件;发出请求后才发生 HTTP 或响应失败时,本次请求尝试仍保留持久记录。在 agent 之外通过程序直接调用提供方时,没有发起会话可供记录。
48
+
49
+ ## 模型体验
50
+
51
+ ### 辅助 DeepSeek 搜索请求
52
+
53
+ #### 模型看到的内容
54
+
55
+ 独立的 DeepSeek 模型会原样接收 `Perform a web search for the query: <query>` 作为用户文本,并收到一个原生 `web_search` 服务器工具定义。该请求不属于会话模型上下文。
56
+
57
+ #### Token 影响
58
+
59
+ 每次搜索都会产生独立的提供方输入与输出 token;`maxTokens` 限制生成输出,`maxUses` 限制原生搜索次数。
60
+
61
+ #### KV Cache 影响
62
+
63
+ 与会话请求缓存相互独立。辅助指令与原生工具定义可以形成稳定前缀,但查询或模型路由的每次变化都会阻止从首个差异起的复用。
64
+
65
+ ### 间接的会话工具结果
66
+
67
+ #### 模型看到的内容
68
+
69
+ 通过 [`dsh-tool-web`](../tool-web/README.md),会话模型会看到结构化搜索块中去重后的 URL、标题、日期与引用 snippet;提供方文本不会作为答案受到信任。该提供方的具体错误消息包括带有处理指引的凭据缺失消息、`DeepSeek search credential resolution failed: <error>`、`DeepSeek search aborted`、`DeepSeek search request failed: <error>`、`DeepSeek returned no web_search_tool_result blocks; the request may not have triggered native web search` 和 `DeepSeek returned an unprocessable response body: <error>`;HTTP 失败保留提供方消息。错误包装属于消费方。
70
+
71
+ #### Token 影响
72
+
73
+ 注册不会直接产生会话 token。结果 token 随返回源与 snippet 增长,随后 seam 会强制执行请求的源数量上限。
74
+
75
+ #### KV Cache 影响
76
+
77
+ 仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
78
+
79
+ ## 已知限制与暂缓事项
80
+
81
+ - **一次搜索需要完整的 Messages 模型轮次**:会产生延迟与生成 token,并且最多执行 `maxUses` 次服务器侧搜索;DeepSeek 不公开专用检索端点。
82
+ - **动态凭据的可用性在操作内部解析**:同步的 `available()` 约定可以确认解析器存在,但无法查询异步凭据存储。因此,选中的无密钥提供方会使搜索以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败;稳定的 `web_search` schema 仍保持注册。调用方取消在本地与该预检存在竞态,但无法强制任意凭据后端自行停止工作。
83
+ - **超量返回的源仍消耗 token**:协议没有结果数量旋钮,`maxResults` 只能由 seam 在事后截断。
84
+ - **未引用的结果没有 `snippet`**:只有 `text` 块中的引用(`cited_text`)匹配其 URL 时,源才会获得 snippet。
package/lib/index.js ADDED
@@ -0,0 +1,266 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
3
+ import { environmentOf } from "@deepseek-ai/dsh-environment";
4
+ import { WebError } from "@deepseek-ai/dsh-web";
5
+ //#region lib/types/provider.js
6
+ /**
7
+ * DeepSeek search through an Anthropic-compatible Messages model call with the native
8
+ * `web_search_20250305` server tool. Each search costs a model turn, but returns structured
9
+ * result blocks; absence of those blocks is an error rather than a prose-scraping fallback.
10
+ * The wire format and native `fetch` client are provider-private and do not use `ctx.llm`.
11
+ * @module @deepseek-ai/dsh-web-search-deepseek/provider
12
+ */
13
+ /** Stable id this provider registers under. */
14
+ const DEEPSEEK_PROVIDER_ID = "deepseek-official";
15
+ /**
16
+ * Default endpoint: DeepSeek's Anthropic-compatible surface, `/v1` included
17
+ * (`/messages` is appended). This is NOT the chat-completions base
18
+ * (`https://api.deepseek.com`) `@deepseek-ai/dsh-llm-deepseek` uses, so this
19
+ * provider does NOT reuse `$DEEPSEEK_BASE_URL` — only the API key is shared.
20
+ */
21
+ const DEEPSEEK_DEFAULT_BASE_URL = "https://api.deepseek.com/anthropic/v1";
22
+ /** Default Anthropic-format model name (aligned with the repo's DeepSeek model vocabulary). */
23
+ const DEEPSEEK_DEFAULT_MODEL = "deepseek-v4-flash";
24
+ /** Default `anthropic-version` header value. */
25
+ const DEEPSEEK_DEFAULT_API_VERSION = "2023-06-01";
26
+ /** Default upper bound on generated tokens for the Messages request. */
27
+ const DEEPSEEK_DEFAULT_MAX_TOKENS = 4096;
28
+ /** Default maximum `web_search` server-tool uses per request. */
29
+ const DEEPSEEK_DEFAULT_MAX_USES = 5;
30
+ /** Attribution header sent on every request. Bump with the package version. */
31
+ const USER_AGENT = "deepseek-harness/0.0.1";
32
+ /**
33
+ * Build a `url → cited_text` map from every `text` block's `citations[]`. This
34
+ * is the snippet surface: Anthropic `web_search_result` items carry
35
+ * `url`/`title`/`page_age` but typically NO inline snippet — the excerpt lives
36
+ * in a separate `text` block's citation, keyed by `url` (first occurrence wins).
37
+ *
38
+ * @param blocks - the response's content blocks; non-`text` blocks are skipped.
39
+ * @returns the `url → cited_text` map (empty when no citations are present).
40
+ */
41
+ function citationSnippets(blocks) {
42
+ const map = /* @__PURE__ */ new Map();
43
+ for (const block of blocks) {
44
+ if (block.type !== "text") continue;
45
+ for (const cite of block.citations ?? []) if (cite.url != null && cite.url.length > 0 && cite.cited_text != null && cite.cited_text.length > 0 && !map.has(cite.url)) map.set(cite.url, cite.cited_text);
46
+ }
47
+ return map;
48
+ }
49
+ /**
50
+ * Map a DeepSeek Anthropic Messages response to a normalized search result. Walks
51
+ * `web_search_tool_result` blocks for citeable `web_search_result` items, joins each to its
52
+ * citation excerpt as `snippet`, and dedupes by `url` (a `max_uses > 1` request can surface
53
+ * the same URL across searches). The web service owns the final `maxResults` truncation, so
54
+ * `truncated` is always `false` here.
55
+ *
56
+ * @param response - the parsed Messages response body.
57
+ * @returns the normalized result with deduped, snippet-joined sources.
58
+ * @throws {@link WebError} when native search produced no result block.
59
+ */
60
+ function mapAnthropicResponse(response) {
61
+ const blocks = response.content ?? [];
62
+ const resultBlocks = blocks.filter((block) => block.type === "web_search_tool_result");
63
+ if (resultBlocks.length === 0) throw new WebError("DeepSeek returned no web_search_tool_result blocks; the request may not have triggered native web search", "WEB_PROVIDER_ERROR");
64
+ const snippets = citationSnippets(blocks);
65
+ const seen = /* @__PURE__ */ new Set();
66
+ const sources = [];
67
+ for (const block of resultBlocks) for (const item of block.content ?? []) {
68
+ if (item.type !== "web_search_result" || item.url.length === 0 || seen.has(item.url)) continue;
69
+ seen.add(item.url);
70
+ const snippet = snippets.get(item.url);
71
+ sources.push({
72
+ url: item.url,
73
+ ...item.title != null && item.title.length > 0 ? { title: item.title } : {},
74
+ ...snippet != null && snippet.length > 0 ? { snippet } : {},
75
+ ...item.page_age != null && item.page_age.length > 0 ? { publishedAt: item.page_age } : {}
76
+ });
77
+ }
78
+ return {
79
+ sources,
80
+ truncated: false
81
+ };
82
+ }
83
+ /** The DeepSeek-backed search provider; HTTP redirects fail as `WEB_PROVIDER_ERROR`. */
84
+ var DeepSeekSearchProvider = class {
85
+ options;
86
+ id = DEEPSEEK_PROVIDER_ID;
87
+ constructor(options) {
88
+ this.options = options;
89
+ }
90
+ available() {
91
+ return ((this.options.apiKey?.length ?? 0) > 0 || this.options.resolveApiKey !== void 0) && URL.canParse(this.options.baseURL) && isPositiveInteger(this.options.maxTokens) && isPositiveInteger(this.options.maxUses);
92
+ }
93
+ async search(request, signal) {
94
+ const apiKey = await this.apiKey(signal);
95
+ throwIfSearchAborted(signal);
96
+ const endpoint = `${this.options.baseURL}/messages`;
97
+ const body = {
98
+ model: this.options.model,
99
+ max_tokens: this.options.maxTokens,
100
+ messages: [{
101
+ role: "user",
102
+ content: [{
103
+ type: "text",
104
+ text: `Perform a web search for the query: ${request.query}`
105
+ }]
106
+ }],
107
+ tools: [{
108
+ type: "web_search_20250305",
109
+ name: "web_search",
110
+ max_uses: this.options.maxUses
111
+ }]
112
+ };
113
+ this.options.recordRequest?.({
114
+ endpoint,
115
+ apiVersion: this.options.apiVersion,
116
+ body
117
+ });
118
+ throwIfSearchAborted(signal);
119
+ let response;
120
+ try {
121
+ response = await fetch(endpoint, {
122
+ method: "POST",
123
+ redirect: "error",
124
+ headers: {
125
+ "x-api-key": apiKey,
126
+ "authorization": `Bearer ${apiKey}`,
127
+ "anthropic-version": this.options.apiVersion,
128
+ "content-type": "application/json",
129
+ "accept": "application/json",
130
+ "user-agent": USER_AGENT
131
+ },
132
+ body: JSON.stringify(body),
133
+ ...signal !== void 0 ? { signal } : {}
134
+ });
135
+ } catch (error) {
136
+ if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
137
+ throw new WebError(`DeepSeek search request failed: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
138
+ }
139
+ if (!response.ok) {
140
+ let message = `DeepSeek API error (HTTP ${response.status})`;
141
+ try {
142
+ const parsed = await response.json();
143
+ const detail = typeof parsed.error === "string" ? parsed.error : parsed.error?.message ?? parsed.message;
144
+ if (detail !== void 0 && detail.length > 0) message = detail;
145
+ } catch (error) {
146
+ if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
147
+ }
148
+ throw new WebError(message, "WEB_PROVIDER_ERROR");
149
+ }
150
+ try {
151
+ return mapAnthropicResponse(await response.json());
152
+ } catch (error) {
153
+ if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
154
+ if (error instanceof WebError) throw error;
155
+ throw new WebError(`DeepSeek returned an unprocessable response body: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
156
+ }
157
+ }
158
+ /** Resolve one operation's credential without retaining it on the provider. */
159
+ async apiKey(signal) {
160
+ throwIfSearchAborted(signal);
161
+ if (this.options.apiKey !== void 0 && this.options.apiKey.length > 0) return this.options.apiKey;
162
+ let resolved;
163
+ try {
164
+ resolved = await abortable(this.options.resolveApiKey?.() ?? Promise.resolve(void 0), signal);
165
+ } catch (error) {
166
+ if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
167
+ throw new WebError(`DeepSeek search credential resolution failed: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
168
+ }
169
+ if (resolved !== void 0 && resolved.length > 0) return resolved;
170
+ throw new WebError(`DeepSeek search has no API key for "${this.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");
171
+ }
172
+ };
173
+ /**
174
+ * Race a same-process asynchronous preflight against caller cancellation. The
175
+ * attached settlement handlers keep observing an uncooperative operation after
176
+ * abort so a later rejection cannot become unhandled.
177
+ */
178
+ function abortable(operation, signal) {
179
+ if (signal === void 0) return operation;
180
+ if (signal.aborted) return Promise.reject(searchAborted(signal));
181
+ return new Promise((resolve, reject) => {
182
+ const onAbort = () => {
183
+ reject(searchAborted(signal));
184
+ };
185
+ signal.addEventListener("abort", onAbort, { once: true });
186
+ operation.then((value) => {
187
+ signal.removeEventListener("abort", onAbort);
188
+ resolve(value);
189
+ }, (error) => {
190
+ signal.removeEventListener("abort", onAbort);
191
+ reject(new Error(String(error).replace(/^Error: /u, ""), { cause: error }));
192
+ });
193
+ });
194
+ }
195
+ /** Throw the provider's stable cancellation error when the caller already aborted. */
196
+ function throwIfSearchAborted(signal) {
197
+ if (signal?.aborted === true) throw searchAborted(signal);
198
+ }
199
+ /** Build the provider's stable cancellation error while retaining the caller's reason. */
200
+ function searchAborted(signal, fallback) {
201
+ return new WebError("DeepSeek search aborted", "WEB_ABORTED", { cause: signal?.aborted === true ? signal.reason : fallback });
202
+ }
203
+ /** True for a fetch/`AbortSignal` abort, surfaced as `WEB_ABORTED`. */
204
+ function isAbortError(error) {
205
+ return error instanceof DOMException && error.name === "AbortError";
206
+ }
207
+ /** True for DeepSeek request limits that can be sent to the Messages API. */
208
+ function isPositiveInteger(value) {
209
+ return Number.isInteger(value) && value > 0;
210
+ }
211
+ //#endregion
212
+ //#region lib/types/index.js
213
+ /**
214
+ * Register a DeepSeek-backed provider in `ctx.web`. It calls the Anthropic-compatible Messages API
215
+ * with native `web_search_20250305`. The provider reuses `DEEPSEEK_API_KEY` but not
216
+ * `DEEPSEEK_BASE_URL`, because search and chat-completions use different bases.
217
+ * @module @deepseek-ai/dsh-web-search-deepseek
218
+ */
219
+ /** Cordis plugin name used by loader diagnostics. */
220
+ const name = "web-search-deepseek";
221
+ /** The web seam this provider registers into. */
222
+ const inject = ["web"];
223
+ const DEFAULT_API_KEY_ENV = "DEEPSEEK_API_KEY";
224
+ const Config = z.object({
225
+ apiKey: z.string().role("secret"),
226
+ apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
227
+ baseURL: z.string(),
228
+ model: z.string(),
229
+ apiVersion: z.string(),
230
+ maxTokens: z.number().step(1).min(1),
231
+ maxUses: z.number().step(1).min(1)
232
+ });
233
+ /**
234
+ * Environment variable naming this provider's endpoint. Deliberately distinct
235
+ * from `$DEEPSEEK_BASE_URL`, which belongs to the chat-completions adapter:
236
+ * search speaks the Anthropic-compatible Messages API, so one variable cannot
237
+ * serve both.
238
+ */
239
+ const SEARCH_BASE_URL_ENV = "DEEPSEEK_SEARCH_BASE_URL";
240
+ /** Register the DeepSeek search provider with `ctx.web`. */
241
+ function apply(ctx, config) {
242
+ const maxTokens = config.maxTokens ?? 4096;
243
+ const maxUses = config.maxUses ?? 5;
244
+ const apiKeyEnv = credentialRef(config.apiKeyEnv ?? DEFAULT_API_KEY_ENV);
245
+ const literalApiKey = config.apiKey !== void 0 && config.apiKey.length > 0 ? config.apiKey : void 0;
246
+ ctx.web.registerSearchProvider(new DeepSeekSearchProvider({
247
+ ...literalApiKey === void 0 ? {} : { apiKey: literalApiKey },
248
+ resolveApiKey: async () => {
249
+ const credentials = ctx.get("credentials");
250
+ if (credentials !== void 0) return (await credentials.resolve(apiKeyEnv))?.value;
251
+ const ambient = environmentOf(ctx).get(apiKeyEnv);
252
+ return ambient !== void 0 && ambient.value.length > 0 ? ambient.value : void 0;
253
+ },
254
+ apiKeyEnv,
255
+ baseURL: config.baseURL ?? environmentOf(ctx).get(SEARCH_BASE_URL_ENV)?.value ?? "https://api.deepseek.com/anthropic/v1",
256
+ model: config.model ?? "deepseek-v4-flash",
257
+ apiVersion: config.apiVersion ?? "2023-06-01",
258
+ maxTokens,
259
+ maxUses,
260
+ recordRequest: (request) => {
261
+ ctx.get("agents")?.currentInitiator()?.session.append("web/deepseek-search-llm-request", request);
262
+ }
263
+ }));
264
+ }
265
+ //#endregion
266
+ export { Config, DEEPSEEK_DEFAULT_API_VERSION, DEEPSEEK_DEFAULT_BASE_URL, DEEPSEEK_DEFAULT_MAX_TOKENS, DEEPSEEK_DEFAULT_MAX_USES, DEEPSEEK_DEFAULT_MODEL, DEEPSEEK_PROVIDER_ID, DeepSeekSearchProvider, apply, inject, name };
@@ -0,0 +1,24 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-web-search-deepseek`.
4
+ * @module @deepseek-ai/dsh-web-search-deepseek/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-web-search-deepseek";
7
+ /** Cordis companion plugin name. */
8
+ const name = "web-search-deepseek-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: the package emits a pre-dispatch log event but owns no
13
+ * later authoritative dispatch event to relate it to. Exact envelope equality
14
+ * is pinned at the provider boundary instead.
15
+ */
16
+ const install = () => {};
17
+ /**
18
+ * Register this package's invariant companion.
19
+ * @param ctx - Cordis context carrying the invariant service.
20
+ * @returns the installed registration's disposer after setup succeeds.
21
+ */
22
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
23
+ //#endregion
24
+ export { apply, inject, name };
@@ -0,0 +1,35 @@
1
+ /**
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`, because search and chat-completions use different bases.
5
+ * @module @deepseek-ai/dsh-web-search-deepseek
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import z from '@deepseek-ai/schemastery';
9
+ export { DeepSeekSearchProvider, DEEPSEEK_DEFAULT_API_VERSION, DEEPSEEK_DEFAULT_BASE_URL, DEEPSEEK_DEFAULT_MAX_TOKENS, DEEPSEEK_DEFAULT_MAX_USES, DEEPSEEK_DEFAULT_MODEL, DEEPSEEK_PROVIDER_ID, } from './provider.ts';
10
+ export type { DeepSeekSearchLlmRequest, DeepSeekSearchProviderOptions } from './provider.ts';
11
+ /** Cordis plugin name used by loader diagnostics. */
12
+ export declare const name = "web-search-deepseek";
13
+ /** The web seam this provider registers into. */
14
+ export declare const inject: string[];
15
+ /** Plugin config (all optional — `apply` fills env-var and constant defaults). */
16
+ export interface Config {
17
+ /** Literal DeepSeek API key; prefer {@link apiKeyEnv} so no secret enters configuration files. */
18
+ apiKey?: string;
19
+ /** Credential reference resolved for each search; defaults to `DEEPSEEK_API_KEY`. */
20
+ apiKeyEnv?: string;
21
+ /** Anthropic-compatible endpoint base; `/messages` is appended. */
22
+ baseURL?: string;
23
+ /** Anthropic-format model name. Defaults to `deepseek-v4-flash`. */
24
+ model?: string;
25
+ /** `anthropic-version` header value. Defaults to `2023-06-01`. */
26
+ apiVersion?: string;
27
+ /** Upper bound on generated tokens for the Messages request. Defaults to 4096. */
28
+ maxTokens?: number;
29
+ /** Maximum `web_search` server-tool uses per request. Defaults to 5. */
30
+ maxUses?: number;
31
+ }
32
+ export declare const Config: z<Config>;
33
+ /** Register the DeepSeek search provider with `ctx.web`. */
34
+ export declare function apply(ctx: Context, config: Config): void;
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-web-search-deepseek`.
3
+ * @module @deepseek-ai/dsh-web-search-deepseek/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "web-search-deepseek-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,123 @@
1
+ /**
2
+ * DeepSeek search through an Anthropic-compatible Messages model call with the native
3
+ * `web_search_20250305` server tool. Each search costs a model turn, but returns structured
4
+ * result blocks; absence of those blocks is an error rather than a prose-scraping fallback.
5
+ * The wire format and native `fetch` client are provider-private and do not use `ctx.llm`.
6
+ * @module @deepseek-ai/dsh-web-search-deepseek/provider
7
+ */
8
+ import type { WebSearchProvider, WebSearchRequest, WebSearchResult } from '@deepseek-ai/dsh-web';
9
+ import type { CredentialRef } from '@deepseek-ai/dsh-credentials';
10
+ import type { AnthropicResponse, ContentBlock } from './types.ts';
11
+ /** Stable id this provider registers under. */
12
+ export declare const DEEPSEEK_PROVIDER_ID = "deepseek-official";
13
+ /**
14
+ * Default endpoint: DeepSeek's Anthropic-compatible surface, `/v1` included
15
+ * (`/messages` is appended). This is NOT the chat-completions base
16
+ * (`https://api.deepseek.com`) `@deepseek-ai/dsh-llm-deepseek` uses, so this
17
+ * provider does NOT reuse `$DEEPSEEK_BASE_URL` — only the API key is shared.
18
+ */
19
+ export declare const DEEPSEEK_DEFAULT_BASE_URL = "https://api.deepseek.com/anthropic/v1";
20
+ /** Default Anthropic-format model name (aligned with the repo's DeepSeek model vocabulary). */
21
+ export declare const DEEPSEEK_DEFAULT_MODEL = "deepseek-v4-flash";
22
+ /** Default `anthropic-version` header value. */
23
+ export declare const DEEPSEEK_DEFAULT_API_VERSION = "2023-06-01";
24
+ /** Default upper bound on generated tokens for the Messages request. */
25
+ export declare const DEEPSEEK_DEFAULT_MAX_TOKENS = 4096;
26
+ /** Default maximum `web_search` server-tool uses per request. */
27
+ export declare const DEEPSEEK_DEFAULT_MAX_USES = 5;
28
+ /**
29
+ * Exact secret-free DeepSeek Messages request recorded immediately before one
30
+ * auxiliary search dispatch.
31
+ */
32
+ export interface DeepSeekSearchLlmRequest {
33
+ /** Fully resolved Messages endpoint. */
34
+ readonly endpoint: string;
35
+ /** `anthropic-version` header value. */
36
+ readonly apiVersion: string;
37
+ /** Exact JSON body sent to the provider. */
38
+ readonly body: {
39
+ readonly model: string;
40
+ readonly max_tokens: number;
41
+ readonly messages: readonly [
42
+ {
43
+ readonly role: 'user';
44
+ readonly content: readonly [
45
+ {
46
+ readonly type: 'text';
47
+ readonly text: string;
48
+ }
49
+ ];
50
+ }
51
+ ];
52
+ readonly tools: readonly [
53
+ {
54
+ readonly type: 'web_search_20250305';
55
+ readonly name: 'web_search';
56
+ readonly max_uses: number;
57
+ }
58
+ ];
59
+ };
60
+ }
61
+ declare module '@deepseek-ai/dsh-session/types' {
62
+ interface SessionEventMap {
63
+ /** Secret-free auxiliary DeepSeek search request recorded before dispatch. */
64
+ 'web/deepseek-search-llm-request': DeepSeekSearchLlmRequest;
65
+ }
66
+ }
67
+ /** Resolved provider options (the plugin's `apply` supplies credential and constant defaults). */
68
+ export interface DeepSeekSearchProviderOptions {
69
+ /** Literal DeepSeek API key; when present it wins over {@link resolveApiKey}. */
70
+ apiKey?: string;
71
+ /** Resolve the current DeepSeek API key for one search operation. */
72
+ resolveApiKey?: () => Promise<string | undefined>;
73
+ /** Credential reference named by missing-credential diagnostics. */
74
+ apiKeyEnv?: CredentialRef;
75
+ /** Endpoint base; `/messages` is appended. */
76
+ baseURL: string;
77
+ /** Anthropic-format model name. */
78
+ model: string;
79
+ /** `anthropic-version` header value. */
80
+ apiVersion: string;
81
+ /** Upper bound on generated tokens for the Messages request. */
82
+ maxTokens: number;
83
+ /** Maximum `web_search` server-tool uses per request. */
84
+ maxUses: number;
85
+ /**
86
+ * Record the exact secret-free request immediately before dispatch. A throw
87
+ * prevents dispatch so model-visible auxiliary input cannot escape logging.
88
+ */
89
+ recordRequest?: (request: DeepSeekSearchLlmRequest) => void;
90
+ }
91
+ /**
92
+ * Build a `url → cited_text` map from every `text` block's `citations[]`. This
93
+ * is the snippet surface: Anthropic `web_search_result` items carry
94
+ * `url`/`title`/`page_age` but typically NO inline snippet — the excerpt lives
95
+ * in a separate `text` block's citation, keyed by `url` (first occurrence wins).
96
+ *
97
+ * @param blocks - the response's content blocks; non-`text` blocks are skipped.
98
+ * @returns the `url → cited_text` map (empty when no citations are present).
99
+ */
100
+ export declare function citationSnippets(blocks: readonly ContentBlock[]): Map<string, string>;
101
+ /**
102
+ * Map a DeepSeek Anthropic Messages response to a normalized search result. Walks
103
+ * `web_search_tool_result` blocks for citeable `web_search_result` items, joins each to its
104
+ * citation excerpt as `snippet`, and dedupes by `url` (a `max_uses > 1` request can surface
105
+ * the same URL across searches). The web service owns the final `maxResults` truncation, so
106
+ * `truncated` is always `false` here.
107
+ *
108
+ * @param response - the parsed Messages response body.
109
+ * @returns the normalized result with deduped, snippet-joined sources.
110
+ * @throws {@link WebError} when native search produced no result block.
111
+ */
112
+ export declare function mapAnthropicResponse(response: AnthropicResponse): WebSearchResult;
113
+ /** The DeepSeek-backed search provider; HTTP redirects fail as `WEB_PROVIDER_ERROR`. */
114
+ export declare class DeepSeekSearchProvider implements WebSearchProvider {
115
+ private readonly options;
116
+ readonly id = "deepseek-official";
117
+ constructor(options: DeepSeekSearchProviderOptions);
118
+ available(): boolean;
119
+ search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
120
+ /** Resolve one operation's credential without retaining it on the provider. */
121
+ private apiKey;
122
+ }
123
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Provider-private wire types for DeepSeek's Anthropic-compatible Messages API. Citeable
3
+ * result items and citation excerpts arrive in separate blocks; the provider joins them by
4
+ * URL. These types do not create a dependency on `ctx.llm`.
5
+ * @module @deepseek-ai/dsh-web-search-deepseek/types
6
+ */
7
+ /** A `web_search_result` item inside a `web_search_tool_result` block. */
8
+ export interface WebSearchResultItem {
9
+ type: string;
10
+ url: string;
11
+ title?: string | null;
12
+ /** Provider-supplied page age/recency string (mapped to `publishedAt`). */
13
+ page_age?: string | null;
14
+ }
15
+ /** A `web_search_tool_result` content block: the citeable result surface. */
16
+ export interface WebSearchToolResultBlock {
17
+ type: 'web_search_tool_result';
18
+ content?: WebSearchResultItem[];
19
+ }
20
+ /** One citation location inside a `text` block (the snippet surface). */
21
+ export interface CitationLocation {
22
+ type?: string;
23
+ url?: string | null;
24
+ cited_text?: string | null;
25
+ }
26
+ /** A `text` content block: the model's prose plus per-URL citations. */
27
+ export interface TextBlock {
28
+ type: 'text';
29
+ text?: string | null;
30
+ citations?: CitationLocation[];
31
+ }
32
+ /** Any content block; only `web_search_tool_result` and `text` are consumed. */
33
+ export type ContentBlock = WebSearchToolResultBlock | TextBlock | {
34
+ type: string;
35
+ };
36
+ /** DeepSeek's Anthropic Messages response envelope. */
37
+ export interface AnthropicResponse {
38
+ content?: ContentBlock[];
39
+ }
40
+ /** DeepSeek's error response envelope (best-effort; fields vary). */
41
+ export interface AnthropicError {
42
+ error?: {
43
+ message?: string;
44
+ } | string;
45
+ message?: string;
46
+ }
47
+ //# sourceMappingURL=types.d.ts.map
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@deepseek-ai/dsh-web-search-deepseek",
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.0.1-rc.1",
5
+ "publishConfig": {
6
+ "access": "restricted"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/web/web-search-deepseek"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/invariant.js",
31
+ "lib/types/**/*.d.ts"
32
+ ],
33
+ "license": "BSD-3-Clause",
34
+ "peerDependencies": {
35
+ "@deepseek-ai/dsh-agent": "^0.0.1-rc.1",
36
+ "@deepseek-ai/dsh-credentials": "^0.0.1-rc.1",
37
+ "@deepseek-ai/dsh-environment": "^0.0.1-rc.1",
38
+ "@deepseek-ai/dsh-session": "^0.0.1-rc.1",
39
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
40
+ "@deepseek-ai/dsh-web": "^0.0.1-rc.1",
41
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
42
+ },
43
+ "dependencies": {
44
+ "@deepseek-ai/schemastery": "^3.18.1-rc.1"
45
+ },
46
+ "devDependencies": {
47
+ "@deepseek-ai/dsh-agent": "^0.0.1-rc.1",
48
+ "@deepseek-ai/dsh-credentials": "^0.0.1-rc.1",
49
+ "@deepseek-ai/dsh-credentials-local": "^0.0.1-rc.1",
50
+ "@deepseek-ai/dsh-environment": "^0.0.1-rc.1",
51
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
52
+ "@deepseek-ai/cordis": "^4.0.1-rc.1",
53
+ "@deepseek-ai/dsh-session": "^0.0.1-rc.1",
54
+ "@deepseek-ai/dsh-web": "^0.0.1-rc.1"
55
+ }
56
+ }