@deepseek-ai/dsh-llm 0.1.7-rc.1 → 0.2.0-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/README.i18n.yaml CHANGED
@@ -1,6 +1,64 @@
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:
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/llm/llm/README.md
5
- README.md: 04de24592379dcb0d815f3b033ca2f9f22e2ff6a
6
- README.zh.md: bee35d4a0b62dd61a61b435dfc4eb27b62cdce69
5
+ /:
6
+ en: 860509fb08c860f4
7
+ zh: b2c4448e8afc1f12
8
+ /deepseek-ai-dsh-llm:
9
+ en: 919467ab4b4ef583
10
+ zh: cb5752f140e58813
11
+ /deepseek-ai-dsh-llm/summary:
12
+ en: 9b9ea6fea3088244
13
+ zh: 09f2eb6606a030da
14
+ /deepseek-ai-dsh-llm/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /deepseek-ai-dsh-llm/use-this-package:
18
+ en: 33738e61ce410ac0
19
+ zh: 363760460f4d4bed
20
+ /deepseek-ai-dsh-llm/use-this-package/when-to-choose-it:
21
+ en: c3ecb5a1421f27db
22
+ zh: 4bbd3c0e513845e1
23
+ /deepseek-ai-dsh-llm/use-this-package/minimal-composition:
24
+ en: 7550d716b90fcb09
25
+ zh: 77891a44ee623c7a
26
+ /deepseek-ai-dsh-llm/use-this-package/what-you-can-do:
27
+ en: b45d7a414141734e
28
+ zh: a641c8ea3827cee6
29
+ /deepseek-ai-dsh-llm/use-this-package/failures-and-recovery:
30
+ en: 311d8c92447b7094
31
+ zh: 68cf5cb1c3853cc1
32
+ /deepseek-ai-dsh-llm/understand-the-implementation:
33
+ en: 9630d7e26193a512
34
+ zh: 94222c2a487c4b8c
35
+ /deepseek-ai-dsh-llm/understand-the-implementation/design-philosophy:
36
+ en: 12d0444c18a397c7
37
+ zh: 9d4a5468b4bb7fcd
38
+ /deepseek-ai-dsh-llm/understand-the-implementation/source-map:
39
+ en: ae1618577f4e8364
40
+ zh: a6e8ab1563c08dfb
41
+ /deepseek-ai-dsh-llm/understand-the-implementation/main-flow:
42
+ en: 6dc955a0d80f6ca0
43
+ zh: f81dbcaeacd8b269
44
+ /deepseek-ai-dsh-llm/understand-the-implementation/invariants:
45
+ en: 0163a050239248e5
46
+ zh: 885c95bedcbe2ff4
47
+ /deepseek-ai-dsh-llm/further-exploration:
48
+ en: 37e1d696afef8a88
49
+ zh: 81f99c4507663cad
50
+ /deepseek-ai-dsh-llm/model-experience:
51
+ en: fa5677003ad6bc0a
52
+ zh: ba33cfc860d9c37c
53
+ /deepseek-ai-dsh-llm/model-experience/kv-cache-effect:
54
+ en: a81634136baef72f
55
+ zh: b92dda883986b0e1
56
+ /deepseek-ai-dsh-llm/known-limitations-and-deferred-work:
57
+ en: d60b9121545c99ec
58
+ zh: 3d4434a0e427470b
59
+ /deepseek-ai-dsh-llm/known-limitations-and-deferred-work/dev-note:
60
+ en: ecbc333b88e14d88
61
+ zh: 77c725d92efd8646
62
+ /deepseek-ai-dsh-llm/known-limitations-and-deferred-work/dev-note/open-items:
63
+ en: 6b1a7e903e13ee21
64
+ zh: bb62f2b1cc484bd5
package/README.md CHANGED
@@ -25,6 +25,8 @@ Use `@deepseek-ai/dsh-llm` to stream model calls through configured provider ada
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
+ `listModels` describes models offered by catalog-driven interfaces. Core resolution and streaming can still accept unlisted ids. The GUI requires catalog membership for selection and submission; adapters intended for GUI use must implement `listModels` and advertise their available models. The base implementation returns an empty list and therefore offers no GUI models.
29
+
28
30
  Any composition that calls a model provider — an agent loop, a session-title generator, a compaction summarizer — streams its requests through this service. Mount it together with at least one provider adapter; the service itself has no configuration and no provider wire code.
29
31
 
30
32
  ### When to choose it
@@ -37,7 +39,7 @@ Mount the service and at least one adapter, then select the provider by name in
37
39
 
38
40
  ```yaml
39
41
  - name: '@deepseek-ai/dsh-llm'
40
- - name: '@deepseek-ai/dsh-llm-deepseek'
42
+ - name: '@deepseek-ai/dsh-llm-deepseek-api-key'
41
43
  config:
42
44
  apiKeyEnv: DEEPSEEK_API_KEY
43
45
  ```
@@ -69,7 +71,7 @@ After a successful mount, `ctx.llm.listProviders()` reports the registered route
69
71
 
70
72
  ### Failures and recovery
71
73
 
72
- Every stream ends in exactly one terminal `finish` chunk: `{ kind: 'error', failure }` on failure, `{ kind: 'aborted', failure }` on cancellation. Failures carry stable codes such as `NO_ADAPTER`, `MISSING_CREDENTIAL`, `AUTH`, `RATE_LIMIT`, and `CONTEXT_WINDOW_EXCEEDED`; consumers route on the code, never on message text. A request naming an unregistered provider fails with `NO_ADAPTER`, and a malformed credential fails with `INVALID_CREDENTIAL` instead of surfacing as an opaque fetch error. This service never re-runs a request: retrying is the job of `dsh-llm-retry` at the agent's failed-step extension point.
74
+ Every stream ends in exactly one terminal `finish` chunk: `{ kind: 'error', failure }` on failure, `{ kind: 'aborted', failure }` on cancellation. Failures carry stable codes such as `NO_ADAPTER`, `MISSING_CREDENTIAL`, `AUTH`, `RATE_LIMIT`, and `CONTEXT_WINDOW_EXCEEDED`; consumers route on the code, never on message text. `QUOTA` is provider-neutral exhaustion, while `ACCOUNT_QUOTA` is reserved for a first-party account balance that the current product can replenish. A request naming an unregistered provider fails with `NO_ADAPTER`, and a malformed credential fails with `INVALID_CREDENTIAL` instead of surfacing as an opaque fetch error. This service never re-runs a request: retrying is the job of `dsh-llm-retry` at the agent's failed-step extension point.
73
75
 
74
76
  -----
75
77
 
@@ -157,7 +159,7 @@ These limits define where this service stops and other packages or future work b
157
159
  - **Variants normally require a producer** — `prefill`, per-tool `strict`, block `cache` hints, and the `agent` message-source variant have no producer ([Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md)).
158
160
  - **`BlockAssembler` handles core block kinds only** — a plugin-added block type whose stream is never closed by `block-end` makes `blocks()` throw.
159
161
  - **`GenerateOptions.sessionId` is a locally-declared brand** — importing dsh-session's `SessionId` would create a dependency cycle.
160
- - **Session-change types are a V4 persistence exception** — `DeveloperMessage` carries incremental session changes. Additions and removals name tools; the containing Session event binds additions to a historical request header that owns their definitions. See [Session references](../../core/session/README.md) for admission and restoration. Provider serialization, deferred loading, and UI presentation remain deferred. Both DeepSeek protocols and pi-ai reject developer history and `deferLoading` requests; Chat and Trajectory reject developer events. Ordinary requests retain their existing behavior.
162
+ - **Tool updates require session history** — `GenerateOptions.tools` contains active definitions. `toolHistory` supplies the initial declarations and historically resolved additions from `Session.toolHistory()`. At adapter dispatch, `projectToolUpdates` constructs deferred declarations and retains removed definitions for `in-history`; `addition-only` omits removed definitions and removal messages. Unsupported routes receive active tools without developer messages or `deferLoading`. Missing history or a request prefix omitting recorded updates falls back to current declarations without developer messages. Explicitly deferred baseline tools remain deferred until their first retained addition block; declaring a deferred tool does not activate it.
161
163
 
162
164
  <a id="dev-note"></a>
163
165
  ### Dev Note
package/README.zh.md CHANGED
@@ -25,6 +25,8 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
+ `listModels` 描述目录驱动界面提供的模型。核心解析与流式调用仍可接受未列出的 ID。GUI 的模型选择与提交要求模型出现在目录中;供 GUI 使用的适配器必须实现 `listModels`,公布其可用模型。基类实现返回空列表,因此不向 GUI 提供模型。
29
+
28
30
  任何调用模型提供方的组合——agent loop(智能体循环)、会话标题生成器、压缩(compaction)摘要器——都会通过本服务流式发起请求。与至少一个提供方适配器一起挂载它;服务本身没有任何配置,也不包含提供方协议代码。
29
31
 
30
32
  ### 何时选择
@@ -37,7 +39,7 @@ kind: "package-reference"
37
39
 
38
40
  ```yaml
39
41
  - name: '@deepseek-ai/dsh-llm'
40
- - name: '@deepseek-ai/dsh-llm-deepseek'
42
+ - name: '@deepseek-ai/dsh-llm-deepseek-api-key'
41
43
  config:
42
44
  apiKeyEnv: DEEPSEEK_API_KEY
43
45
  ```
@@ -69,7 +71,7 @@ for await (const chunk of ctx.llm.stream({
69
71
 
70
72
  ### 失败与恢复
71
73
 
72
- 每个流都恰好以一个终止 `finish` 分片结束:失败为 `{ kind: 'error', failure }`,取消为 `{ kind: 'aborted', failure }`。失败携带稳定 code,如 `NO_ADAPTER`、`MISSING_CREDENTIAL`、`AUTH`、`RATE_LIMIT` 与 `CONTEXT_WINDOW_EXCEEDED`;消费方依据 code 路由,绝不解析消息文本。点名未注册提供方的请求会以 `NO_ADAPTER` 失败,格式错误的凭据会以 `INVALID_CREDENTIAL` 失败,而不是表现为不透明的 fetch 错误。本服务从不自行重跑请求:重试是 `dsh-llm-retry` 在 agent 失败步骤扩展点上的职责。
74
+ 每个流都恰好以一个终止 `finish` 分片结束:失败为 `{ kind: 'error', failure }`,取消为 `{ kind: 'aborted', failure }`。失败携带稳定 code,如 `NO_ADAPTER`、`MISSING_CREDENTIAL`、`AUTH`、`RATE_LIMIT` 与 `CONTEXT_WINDOW_EXCEEDED`;消费方依据 code 路由,绝不解析消息文本。`QUOTA` 表示提供方中立的额度耗尽,`ACCOUNT_QUOTA` 专用于当前产品能够充值的第一方账号余额不足。点名未注册提供方的请求会以 `NO_ADAPTER` 失败,格式错误的凭据会以 `INVALID_CREDENTIAL` 失败,而不是表现为不透明的 fetch 错误。本服务从不自行重跑请求:重试是 `dsh-llm-retry` 在 agent 失败步骤扩展点上的职责。
73
75
 
74
76
  -----
75
77
 
@@ -157,7 +159,7 @@ for await (const chunk of ctx.llm.stream({
157
159
  - **变体通常要求实际产生方**——`prefill`、逐工具 `strict`、内容块 `cache` 提示和 `agent` 消息来源变体都没有产生方(见 [Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md))。
158
160
  - **`BlockAssembler` 只处理核心块类型**——插件添加块类型的流若从未由 `block-end` 关闭,`blocks()` 会抛出异常。
159
161
  - **`GenerateOptions.sessionId` 是本地声明的品牌类型**——导入 dsh-session 的 `SessionId` 会产生依赖循环。
160
- - **Session 变更类型是 V4 持久化的例外** — `DeveloperMessage` 承载增量 Session 变更。添加与移除块记录工具名称;所在的 Session 事件将添加绑定到拥有其定义的历史请求头。接纳和恢复规则见 [Session 引用](../../core/session/README.zh.md)。提供方序列化、延迟加载与 UI 展示仍留待后续实现。DeepSeek 两种协议与 pi-ai 均拒绝 developer 历史及 `deferLoading` 请求;Chat 与 Trajectory 拒绝 developer 事件。普通请求的行为不变。
162
+ - **工具更新需要会话历史** — `GenerateOptions.tools` 包含当前有效定义。`toolHistory` 提供 `Session.toolHistory()` 派生的初始声明及已解析历史定义的添加记录。适配器分发时,`projectToolUpdates` 构造延迟声明,并在 `in-history` 模式保留已移除定义;`addition-only` 省略已移除定义和移除消息。不支持更新的路由接收有效工具,不携带 developer 消息或 `deferLoading`。缺少历史或请求前缀遗漏已记录更新时,回退为当前声明且不发送 developer 消息。 显式延迟加载的初始工具在首个保留的添加块出现前保持延迟状态;声明延迟加载工具不会使其激活。
161
163
 
162
164
  <a id="dev-note"></a>
163
165
  ### 开发备注
package/lib/index.js CHANGED
@@ -136,6 +136,8 @@ var HarnessError = class extends Error {
136
136
  const CONTEXT_WINDOW_EXCEEDED_CODE = "CONTEXT_WINDOW_EXCEEDED";
137
137
  /** Canonical provider-neutral code for an exhausted account quota or balance. */
138
138
  const QUOTA_EXCEEDED_CODE = "QUOTA";
139
+ /** Account-token quota that can be replenished through the first-party billing page. */
140
+ const ACCOUNT_QUOTA_EXCEEDED_CODE = "ACCOUNT_QUOTA";
139
141
  /**
140
142
  * Canonical provider-neutral code for a response that completed normally but
141
143
  * carried no content blocks at all. Providers occasionally emit a degenerate
@@ -750,6 +752,89 @@ function projectImagesForTextModel(messages) {
750
752
  };
751
753
  });
752
754
  }
755
+ function withoutDeveloperMessages(messages) {
756
+ const retained = messages.filter((message) => message.role !== "developer");
757
+ return retained.length === messages.length ? messages : retained;
758
+ }
759
+ function toolDeclarations(tools, mode, history) {
760
+ const declarations = new Map(history.tools.map((tool) => [tool.name, tool]));
761
+ for (const update of history.updates) for (const tool of update.additions) if (!declarations.has(tool.name)) declarations.set(tool.name, {
762
+ ...tool,
763
+ deferLoading: true
764
+ });
765
+ switch (mode) {
766
+ case "in-history": return declarations;
767
+ case "addition-only": {
768
+ const activeNames = new Set(tools?.map((tool) => tool.name));
769
+ for (const name of declarations.keys()) if (!activeNames.has(name)) declarations.delete(name);
770
+ return declarations;
771
+ }
772
+ /* v8 ignore next 2 -- closed-union exhaustiveness guard */
773
+ default: return assertNever(mode);
774
+ }
775
+ }
776
+ /**
777
+ * Construct provider declarations from session-folded history without changing logged active tools.
778
+ * Unsupported routes and incomplete history use current declarations without developer updates.
779
+ * Explicitly deferred baseline tools become available only after their first retained addition.
780
+ * @param messages - complete request inputs, or the prefix selected for an auxiliary call.
781
+ * @param tools - currently active tool schemas.
782
+ * @param toolUpdate - the resolved route's update mode.
783
+ * @param history - immutable state folded from committed headers and developer messages.
784
+ * @returns provider declarations and the corresponding filtered history.
785
+ */
786
+ function projectToolUpdates(messages, tools, toolUpdate, history) {
787
+ if (toolUpdate === void 0) {
788
+ let immediateTools = tools;
789
+ if (tools?.some((tool) => tool.deferLoading === true)) immediateTools = tools.map(({ deferLoading: _loading, ...tool }) => tool);
790
+ return {
791
+ messages: withoutDeveloperMessages(messages),
792
+ tools: immediateTools
793
+ };
794
+ }
795
+ if (history === void 0) return {
796
+ messages: withoutDeveloperMessages(messages),
797
+ tools
798
+ };
799
+ const messageIds = new Set(messages.flatMap((message) => message.role === "developer" ? [message.id] : []));
800
+ if (history.updates.some((update) => !messageIds.has(update.messageId))) return {
801
+ messages: withoutDeveloperMessages(messages),
802
+ tools
803
+ };
804
+ const declarations = toolDeclarations(tools, toolUpdate, history);
805
+ const updateIds = new Set(history.updates.map((update) => update.messageId));
806
+ const offered = new Set(history.tools.filter((tool) => !tool.deferLoading).map((tool) => tool.name));
807
+ const projectedMessages = [];
808
+ for (const message of messages) {
809
+ if (message.role !== "developer") {
810
+ projectedMessages.push(message);
811
+ continue;
812
+ }
813
+ if (!updateIds.has(message.id)) continue;
814
+ const content = message.content.filter((block) => {
815
+ switch (block.type) {
816
+ case "tool-addition":
817
+ if (!declarations.has(block.toolName) || offered.has(block.toolName)) return false;
818
+ offered.add(block.toolName);
819
+ return true;
820
+ case "tool-removal":
821
+ if (toolUpdate !== "in-history") return false;
822
+ return offered.delete(block.toolName);
823
+ default: return true;
824
+ }
825
+ });
826
+ if (content.length === 0) continue;
827
+ if (content.length === message.content.length) projectedMessages.push(message);
828
+ else projectedMessages.push({
829
+ ...message,
830
+ content
831
+ });
832
+ }
833
+ return {
834
+ messages: projectedMessages.length === messages.length && projectedMessages.every((message, index) => message === messages[index]) ? messages : projectedMessages,
835
+ tools: [...declarations.values()]
836
+ };
837
+ }
753
838
  //#endregion
754
839
  //#region lib/types/attribution.js
755
840
  /**
@@ -1612,8 +1697,9 @@ var LlmAdapter = class {
1612
1697
  imageRequestPricing(_provider, _model) {}
1613
1698
  /**
1614
1699
  * List models this adapter can currently advertise for one owned provider.
1615
- * The result is advisory: an adapter may accept unlisted model ids, and
1616
- * consumers must not turn absence into request rejection.
1700
+ * Core routing accepts unlisted model ids; catalog-driven entry points such
1701
+ * as the GUI may require membership. Adapters used there must advertise
1702
+ * their available models; the base empty catalog offers no GUI selection.
1617
1703
  * @param _provider - one provider route owned by this adapter.
1618
1704
  * @returns discoverable models in adapter-preferred order.
1619
1705
  */
@@ -1979,7 +2065,8 @@ let LlmRuntime = (() => {
1979
2065
  }
1980
2066
  /**
1981
2067
  * Discover models advertised by one registered provider. Catalog membership
1982
- * is advisory and never changes routing or request validation.
2068
+ * does not constrain core routing. Catalog-driven entry points may restrict
2069
+ * selection and submission to the advertised models.
1983
2070
  * @param provider - registered provider route to inspect.
1984
2071
  * @returns detached model metadata in adapter-preferred order.
1985
2072
  */
@@ -2024,6 +2111,8 @@ let LlmRuntime = (() => {
2024
2111
  const inputModalities = this.detachedModalities(resolved.inputModalities);
2025
2112
  const systemPromptUpdate = resolved.systemPromptUpdate;
2026
2113
  if (systemPromptUpdate !== void 0 && systemPromptUpdate !== "in-history") throw new LlmError(`adapter returned invalid system prompt update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
2114
+ const toolUpdate = resolved.toolUpdate;
2115
+ if (toolUpdate !== void 0 && toolUpdate !== "in-history" && toolUpdate !== "addition-only") throw new LlmError(`adapter returned invalid tool update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
2027
2116
  const defaultMaxTokens = resolved.defaultMaxTokens;
2028
2117
  if (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider "${provider}" model "${model}"`, "INVALID_MODEL_MAX_TOKENS");
2029
2118
  const info = {
@@ -2034,7 +2123,8 @@ let LlmRuntime = (() => {
2034
2123
  ...inputModalities === void 0 ? {} : { inputModalities },
2035
2124
  ...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
2036
2125
  ...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens },
2037
- ...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate }
2126
+ ...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate },
2127
+ ...resolved.toolUpdate === void 0 ? {} : { toolUpdate: resolved.toolUpdate }
2038
2128
  };
2039
2129
  const reasoning = resolved.reasoning;
2040
2130
  if (reasoning === void 0) return info;
@@ -2129,6 +2219,7 @@ let LlmRuntime = (() => {
2129
2219
  ...context === void 0 ? {} : { context },
2130
2220
  ...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
2131
2221
  ...modelInfo.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: modelInfo.systemPromptUpdate },
2222
+ ...modelInfo.toolUpdate === void 0 ? {} : { toolUpdate: modelInfo.toolUpdate },
2132
2223
  stream: (options) => {
2133
2224
  if (dispatched) throw new LlmError("a prepared LLM call can only be dispatched once", "INVALID_PREPARED_CALL");
2134
2225
  if (!callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
@@ -2218,13 +2309,17 @@ let LlmRuntime = (() => {
2218
2309
  let projectedMessages = resolvedOptions.messages;
2219
2310
  if (projectedMessages.some((message) => contentHasFile(message.content))) projectedMessages = projectFilesToText(projectedMessages, (ref) => this.fileReadPath(ref));
2220
2311
  if (modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && projectedMessages.some((message) => contentHasImage(message.content))) projectedMessages = projectImagesForTextModel(projectedMessages);
2221
- const projectedOptions = projectedMessages === resolvedOptions.messages ? resolvedOptions : Object.isFrozen(resolvedOptions) ? deepFreeze({
2222
- ...resolvedOptions,
2223
- messages: projectedMessages
2224
- }) : {
2225
- ...resolvedOptions,
2226
- messages: projectedMessages
2227
- };
2312
+ const projectedTools = projectToolUpdates(projectedMessages, resolvedOptions.tools, modelInfo.toolUpdate, resolvedOptions.toolHistory);
2313
+ projectedMessages = projectedTools.messages;
2314
+ let projectedOptions = resolvedOptions;
2315
+ if (projectedMessages !== resolvedOptions.messages || projectedTools.tools !== resolvedOptions.tools) {
2316
+ projectedOptions = {
2317
+ ...resolvedOptions,
2318
+ messages: projectedMessages,
2319
+ ...projectedTools.tools === void 0 ? {} : { tools: projectedTools.tools }
2320
+ };
2321
+ if (Object.isFrozen(resolvedOptions)) deepFreeze(projectedOptions);
2322
+ }
2228
2323
  iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
2229
2324
  } catch (error) {
2230
2325
  yield adapterFailureChunk(error, options.signal);
@@ -2292,4 +2387,4 @@ function adapterFailureChunk(error, signal) {
2292
2387
  };
2293
2388
  }
2294
2389
  //#endregion
2295
- export { APP_IDENTITY, AssistantStreamAccumulator, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, HarnessError, IMAGE_OFFLOAD_REQUIRED_CODE, INVALID_CREDENTIAL_CODE, LlmAdapter, LlmAttemptId, LlmError, LlmRuntime, LlmRuntime as default, MessageId, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, ToolCallId, assembleAssistantStream, assertUsableApiKey, assistantStreamChunks, assistantStreamFirstTokenTime, assistantStreamHasVisibleContent, assistantStreamHasVisibleText, attributionHeaders, boundContextSummary, callConfigEquals, chunkHasVisibleText, contentHasFile, contentHasImage, createAssistantMessage, createDeveloperMessage, createMessage, createSystemMessage, createToolResultMessage, createUserMessage, errorChain, expandAssistantStream, fileHandleText, freezeMessage, isAgentLoopRequest, isContextWindowExceededError, isHarnessError, isQuotaExceededError, isTokenDelta, isVisibleChunk, joinAssistantStreamText, lastAssistantStreamChunk, markAgentLoopRequest, normalizeApiKey, offloadedImageText, projectFilesToText, projectImagesForTextModel, projectOffloadedImages, requestImageHandleText, requiredImageOffload, resolveImageAttachmentAccess, resolveRetryPolicy, runFirstTokenTime, runFirstVisibleTime, textOnlyImageText, userAgent };
2390
+ export { ACCOUNT_QUOTA_EXCEEDED_CODE, APP_IDENTITY, AssistantStreamAccumulator, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, HarnessError, IMAGE_OFFLOAD_REQUIRED_CODE, INVALID_CREDENTIAL_CODE, LlmAdapter, LlmAttemptId, LlmError, LlmRuntime, LlmRuntime as default, MessageId, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, ToolCallId, assembleAssistantStream, assertUsableApiKey, assistantStreamChunks, assistantStreamFirstTokenTime, assistantStreamHasVisibleContent, assistantStreamHasVisibleText, attributionHeaders, boundContextSummary, callConfigEquals, chunkHasVisibleText, contentHasFile, contentHasImage, createAssistantMessage, createDeveloperMessage, createMessage, createSystemMessage, createToolResultMessage, createUserMessage, errorChain, expandAssistantStream, fileHandleText, freezeMessage, isAgentLoopRequest, isContextWindowExceededError, isHarnessError, isQuotaExceededError, isTokenDelta, isVisibleChunk, joinAssistantStreamText, lastAssistantStreamChunk, markAgentLoopRequest, normalizeApiKey, offloadedImageText, projectFilesToText, projectImagesForTextModel, projectOffloadedImages, projectToolUpdates, requestImageHandleText, requiredImageOffload, resolveImageAttachmentAccess, resolveRetryPolicy, runFirstTokenTime, runFirstVisibleTime, textOnlyImageText, userAgent };
@@ -74,7 +74,7 @@ export const TYPERT = {
74
74
  typeSymbol: '@deepseek-ai/dsh-llm#llm/discoverModels:result',
75
75
  create: _deepseek_ai_dsh_llm_llm_discoverModels_result$schema,
76
76
  },
77
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":633,"column":9},
77
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":638,"column":9},
78
78
  },
79
79
  {
80
80
  id: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders',
@@ -89,7 +89,7 @@ export const TYPERT = {
89
89
  typeSymbol: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders:result',
90
90
  create: _deepseek_ai_dsh_llm_llm_listConfigurableProviders_result$schema,
91
91
  },
92
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":545,"column":3},
92
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":550,"column":3},
93
93
  },
94
94
  {
95
95
  id: '@deepseek-ai/dsh-llm#llm/listProviders',
@@ -104,7 +104,7 @@ export const TYPERT = {
104
104
  typeSymbol: '@deepseek-ai/dsh-llm#llm/listProviders:result',
105
105
  create: _deepseek_ai_dsh_llm_llm_listProviders_result$schema,
106
106
  },
107
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":473,"column":3},
107
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":478,"column":3},
108
108
  },
109
109
  ],
110
110
  model: {
@@ -192,7 +192,7 @@ export const TYPERT = {
192
192
  "name": "listModels",
193
193
  "signature": "async listModels(provider: string): Promise<LlmModelInfo[]>",
194
194
  "summary": "Discover models advertised by one registered provider.",
195
- "jsDoc": "/**\n * Discover models advertised by one registered provider. Catalog membership\n * is advisory and never changes routing or request validation.\n * @param provider - registered provider route to inspect.\n * @returns detached model metadata in adapter-preferred order.\n */"
195
+ "jsDoc": "/**\n * Discover models advertised by one registered provider. Catalog membership\n * does not constrain core routing. Catalog-driven entry points may restrict\n * selection and submission to the advertised models.\n * @param provider - registered provider route to inspect.\n * @returns detached model metadata in adapter-preferred order.\n */"
196
196
  },
197
197
  {
198
198
  "kind": "method",
@@ -310,7 +310,7 @@ export const TYPERT = {
310
310
  },
311
311
  {
312
312
  "name": "GenerateOptions",
313
- "declaration": "export interface GenerateOptions {\n provider: string;\n model: string;\n reasoningEffort?: ReasoningEffortId;\n messages: RequestMessage[];\n system?: string;\n tools?: ToolSchema[];\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n signal?: AbortSignal;\n sessionId?: Branded<'SessionId'>;\n purpose?: 'compaction' | 'session-title';\n}"
313
+ "declaration": "export interface GenerateOptions {\n provider: string;\n model: string;\n reasoningEffort?: ReasoningEffortId;\n messages: RequestMessage[];\n system?: string;\n tools?: ToolSchema[];\n toolHistory?: ToolHistory;\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n signal?: AbortSignal;\n sessionId?: Branded<'SessionId'>;\n purpose?: 'compaction' | 'session-title';\n}"
314
314
  },
315
315
  {
316
316
  "name": "GoalId",
@@ -390,7 +390,7 @@ export const TYPERT = {
390
390
  },
391
391
  {
392
392
  "name": "LlmResolvedModelInfo",
393
- "declaration": "export interface LlmResolvedModelInfo extends LlmModelInfo {\n context?: LlmModelContext;\n defaultMaxTokens?: number;\n reasoning?: LlmModelReasoningInfo;\n systemPromptUpdate?: SystemPromptUpdate;\n}"
393
+ "declaration": "export interface LlmResolvedModelInfo extends LlmModelInfo {\n context?: LlmModelContext;\n defaultMaxTokens?: number;\n reasoning?: LlmModelReasoningInfo;\n systemPromptUpdate?: SystemPromptUpdate;\n toolUpdate?: ToolUpdate;\n}"
394
394
  },
395
395
  {
396
396
  "name": "Message",
@@ -414,7 +414,7 @@ export const TYPERT = {
414
414
  },
415
415
  {
416
416
  "name": "MessageSourceMap",
417
- "declaration": "export interface MessageSourceMap {\n user: { kind: 'user'; };\n model: ModelMessageSource;\n tool: ToolMessageSource;\n 'system-prompt': SystemPromptMessageSource;\n 'model-selection': { kind: 'model-selection'; } & ContextFormed;\n 'user-approval': { kind: 'user-approval'; } & ContextFormed;\n 'ptc-mode': { kind: 'ptc-mode'; };\n 'tool-registry': { kind: 'tool-registry'; };\n 'user-rpc': { kind: 'user'; rpcId: SessionRequestId; clientTimeZone?: string; };\n 'agent-message': AgentMessageSource;\n 'subagent-settled': SubagentSettledMessageSource;\n 'skill-invocation': SkillInvocationSource;\n 'cordis-host-runner': { kind: 'cordis-host-runner'; };\n goal: GoalMessageSource;\n 'compact-checkpoint': CompactionCheckpointSource;\n 'session-reference': SessionReferenceSource;\n}"
417
+ "declaration": "export interface MessageSourceMap {\n user: { kind: 'user'; };\n model: ModelMessageSource;\n tool: ToolMessageSource;\n 'system-prompt': SystemPromptMessageSource;\n 'model-selection': { kind: 'model-selection'; } & ContextFormed;\n 'user-approval': { kind: 'user-approval'; } & ContextFormed;\n 'ptc-mode': { kind: 'ptc-mode'; };\n 'tool-registry': { kind: 'tool-registry'; };\n 'user-rpc': { kind: 'user'; rpcId: SessionRequestId; clientTimeZone?: string; };\n 'agent-message': AgentMessageSource;\n 'subagent-settled': SubagentSettledMessageSource;\n 'skill-invocation': SkillInvocationSource;\n 'cordis-host-runner': { kind: 'cordis-host-runner'; };\n goal: GoalMessageSource;\n schedule: { kind: 'schedule'; } & ContextFormed;\n 'compact-checkpoint': CompactionCheckpointSource;\n 'session-reference': SessionReferenceSource;\n}"
418
418
  },
419
419
  {
420
420
  "name": "ModelMessageSource",
@@ -438,7 +438,7 @@ export const TYPERT = {
438
438
  },
439
439
  {
440
440
  "name": "PreparedLlmCall",
441
- "declaration": "export interface PreparedLlmCall {\n readonly config: LlmCallConfig;\n readonly retryPolicy: ResolvedRetryPolicy;\n readonly context?: LlmModelContext;\n readonly inputModalities?: readonly ModelModality[];\n readonly systemPromptUpdate?: SystemPromptUpdate;\n readonly adapterDefaults: LlmCallConfigAdapterDefaults;\n stream(options: GenerateOptions): AsyncIterable<StreamChunk>;\n}"
441
+ "declaration": "export interface PreparedLlmCall {\n readonly config: LlmCallConfig;\n readonly retryPolicy: ResolvedRetryPolicy;\n readonly context?: LlmModelContext;\n readonly inputModalities?: readonly ModelModality[];\n readonly systemPromptUpdate?: SystemPromptUpdate;\n readonly toolUpdate?: ToolUpdate;\n readonly adapterDefaults: LlmCallConfigAdapterDefaults;\n stream(options: GenerateOptions): AsyncIterable<StreamChunk>;\n}"
442
442
  },
443
443
  {
444
444
  "name": "ProviderRequestId",
@@ -540,6 +540,10 @@ export const TYPERT = {
540
540
  "name": "ToolCallId",
541
541
  "declaration": "export type ToolCallId = Branded<'ToolCallId'>;"
542
542
  },
543
+ {
544
+ "name": "ToolHistory",
545
+ "declaration": "export interface ToolHistory {\n readonly tools: readonly ToolSchema[];\n readonly updates: readonly { readonly messageId: MessageId; readonly additions: readonly ToolSchema[]; }[];\n}"
546
+ },
543
547
  {
544
548
  "name": "ToolMessageSource",
545
549
  "declaration": "export interface ToolMessageSource {\n kind: 'tool';\n callId: ToolCallId;\n}"
@@ -556,6 +560,10 @@ export const TYPERT = {
556
560
  "name": "ToolSchema",
557
561
  "declaration": "export interface ToolSchema {\n deferLoading?: true;\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n}"
558
562
  },
563
+ {
564
+ "name": "ToolUpdate",
565
+ "declaration": "export type ToolUpdate = 'in-history' | 'addition-only';"
566
+ },
559
567
  {
560
568
  "name": "UserMessage",
561
569
  "declaration": "export interface UserMessage extends MessageBase {\n readonly role: 'user';\n}"
@@ -71,7 +71,7 @@ export const TYPERT_REMOTE = {
71
71
  typeSymbol: '@deepseek-ai/dsh-llm#llm/discoverModels:result',
72
72
  create: _deepseek_ai_dsh_llm_llm_discoverModels_result$schema,
73
73
  },
74
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":633,"column":9},
74
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":638,"column":9},
75
75
  },
76
76
  {
77
77
  id: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders',
@@ -86,7 +86,7 @@ export const TYPERT_REMOTE = {
86
86
  typeSymbol: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders:result',
87
87
  create: _deepseek_ai_dsh_llm_llm_listConfigurableProviders_result$schema,
88
88
  },
89
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":545,"column":3},
89
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":550,"column":3},
90
90
  },
91
91
  {
92
92
  id: '@deepseek-ai/dsh-llm#llm/listProviders',
@@ -101,7 +101,7 @@ export const TYPERT_REMOTE = {
101
101
  typeSymbol: '@deepseek-ai/dsh-llm#llm/listProviders:result',
102
102
  create: _deepseek_ai_dsh_llm_llm_listProviders_result$schema,
103
103
  },
104
- sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":473,"column":3},
104
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":478,"column":3},
105
105
  },
106
106
  ],
107
107
  }
@@ -1,5 +1,5 @@
1
1
  /** Content-block structure helpers. @module @deepseek-ai/dsh-llm/content */
2
- import type { ContentBlock, ImageBlock, LlmImageRequestBudget } from './types.ts';
2
+ import type { ContentBlock, ImageBlock, LlmImageRequestBudget, ToolSchema, ToolUpdate, ToolHistory } from './types.ts';
3
3
  import type { RequestMessage } from './types.ts';
4
4
  import type { Message } from './message.ts';
5
5
  import type { AttachmentStore, FileAttachmentRef, ImageAttachmentRef, RequestImageAttachment } from '@deepseek-ai/dsh-attachment';
@@ -128,4 +128,22 @@ export declare function projectImagesForTextModel(messages: readonly Message[]):
128
128
  * @returns original inputs without images, otherwise copies with stable placeholders.
129
129
  */
130
130
  export declare function projectImagesForTextModel(messages: readonly RequestMessage[]): readonly RequestMessage[];
131
+ /** Request messages and tools after one route's tool update projection. */
132
+ export interface ProjectedToolUpdates {
133
+ /** History with only the developer updates supported by this route and declaration series. */
134
+ readonly messages: readonly RequestMessage[];
135
+ /** Provider declarations, including deferred and retained definitions when supported. */
136
+ readonly tools: readonly ToolSchema[] | undefined;
137
+ }
138
+ /**
139
+ * Construct provider declarations from session-folded history without changing logged active tools.
140
+ * Unsupported routes and incomplete history use current declarations without developer updates.
141
+ * Explicitly deferred baseline tools become available only after their first retained addition.
142
+ * @param messages - complete request inputs, or the prefix selected for an auxiliary call.
143
+ * @param tools - currently active tool schemas.
144
+ * @param toolUpdate - the resolved route's update mode.
145
+ * @param history - immutable state folded from committed headers and developer messages.
146
+ * @returns provider declarations and the corresponding filtered history.
147
+ */
148
+ export declare function projectToolUpdates(messages: readonly RequestMessage[], tools: readonly ToolSchema[] | undefined, toolUpdate: ToolUpdate | undefined, history?: ToolHistory): ProjectedToolUpdates;
131
149
  //# sourceMappingURL=content.d.ts.map
@@ -245,4 +245,111 @@ export function projectImagesForTextModel(messages) {
245
245
  return content === message.content ? message : { ...message, content };
246
246
  });
247
247
  }
248
+ function withoutDeveloperMessages(messages) {
249
+ const retained = messages.filter(message => message.role !== 'developer');
250
+ return retained.length === messages.length ? messages : retained;
251
+ }
252
+ function toolDeclarations(tools, mode, history) {
253
+ const declarations = new Map(history.tools.map(tool => [tool.name, tool]));
254
+ for (const update of history.updates) {
255
+ for (const tool of update.additions) {
256
+ if (!declarations.has(tool.name)) {
257
+ // Later additions activate these definitions at their recorded position.
258
+ declarations.set(tool.name, { ...tool, deferLoading: true });
259
+ }
260
+ }
261
+ }
262
+ switch (mode) {
263
+ case 'in-history':
264
+ // Removal blocks disable tools without discarding their historical definitions.
265
+ return declarations;
266
+ case 'addition-only': {
267
+ // Without removal support, the declaration list must omit inactive tools.
268
+ const activeNames = new Set(tools?.map(tool => tool.name));
269
+ for (const name of declarations.keys()) {
270
+ if (!activeNames.has(name))
271
+ declarations.delete(name);
272
+ }
273
+ return declarations;
274
+ }
275
+ /* v8 ignore next 2 -- closed-union exhaustiveness guard */
276
+ default:
277
+ return assertNever(mode);
278
+ }
279
+ }
280
+ /**
281
+ * Construct provider declarations from session-folded history without changing logged active tools.
282
+ * Unsupported routes and incomplete history use current declarations without developer updates.
283
+ * Explicitly deferred baseline tools become available only after their first retained addition.
284
+ * @param messages - complete request inputs, or the prefix selected for an auxiliary call.
285
+ * @param tools - currently active tool schemas.
286
+ * @param toolUpdate - the resolved route's update mode.
287
+ * @param history - immutable state folded from committed headers and developer messages.
288
+ * @returns provider declarations and the corresponding filtered history.
289
+ */
290
+ export function projectToolUpdates(messages, tools, toolUpdate, history) {
291
+ if (toolUpdate === undefined) {
292
+ // Unsupported routes need immediately available tools and no update messages.
293
+ let immediateTools = tools;
294
+ if (tools?.some(tool => tool.deferLoading === true)) {
295
+ immediateTools = tools.map(({ deferLoading: _loading, ...tool }) => tool);
296
+ }
297
+ return { messages: withoutDeveloperMessages(messages), tools: immediateTools };
298
+ }
299
+ if (history === undefined) {
300
+ // Current schemas alone cannot resolve definitions referenced by past updates.
301
+ return { messages: withoutDeveloperMessages(messages), tools };
302
+ }
303
+ const messageIds = new Set(messages.flatMap(message => message.role === 'developer' ? [message.id] : []));
304
+ if (history.updates.some(update => !messageIds.has(update.messageId))) {
305
+ // An auxiliary prefix may omit updates needed to activate historical declarations.
306
+ return { messages: withoutDeveloperMessages(messages), tools };
307
+ }
308
+ const declarations = toolDeclarations(tools, toolUpdate, history);
309
+ const updateIds = new Set(history.updates.map(update => update.messageId));
310
+ // Deferred baseline tools still need their first addition to become available.
311
+ const offered = new Set(history.tools.filter(tool => !tool.deferLoading).map(tool => tool.name));
312
+ const projectedMessages = [];
313
+ for (const message of messages) {
314
+ if (message.role !== 'developer') {
315
+ projectedMessages.push(message);
316
+ continue;
317
+ }
318
+ // Earlier declaration series do not govern the current tool set.
319
+ if (!updateIds.has(message.id))
320
+ continue;
321
+ const content = message.content.filter((block) => {
322
+ switch (block.type) {
323
+ case 'tool-addition':
324
+ // Only declared tools that are not already available need activation.
325
+ if (!declarations.has(block.toolName) || offered.has(block.toolName))
326
+ return false;
327
+ offered.add(block.toolName);
328
+ return true;
329
+ case 'tool-removal':
330
+ // Addition-only routes cannot deactivate a tool through history.
331
+ if (toolUpdate !== 'in-history')
332
+ return false;
333
+ return offered.delete(block.toolName);
334
+ default:
335
+ // Other core and plugin-defined blocks retain their content and order.
336
+ return true;
337
+ }
338
+ });
339
+ if (content.length === 0)
340
+ continue;
341
+ if (content.length === message.content.length) {
342
+ projectedMessages.push(message);
343
+ }
344
+ else {
345
+ projectedMessages.push({ ...message, content });
346
+ }
347
+ }
348
+ const unchanged = projectedMessages.length === messages.length
349
+ && projectedMessages.every((message, index) => message === messages[index]);
350
+ return {
351
+ messages: unchanged ? messages : projectedMessages,
352
+ tools: [...declarations.values()],
353
+ };
354
+ }
248
355
  //# sourceMappingURL=content.js.map
@@ -18,6 +18,8 @@ export declare class HarnessError extends Error {
18
18
  export declare const CONTEXT_WINDOW_EXCEEDED_CODE = "CONTEXT_WINDOW_EXCEEDED";
19
19
  /** Canonical provider-neutral code for an exhausted account quota or balance. */
20
20
  export declare const QUOTA_EXCEEDED_CODE = "QUOTA";
21
+ /** Account-token quota that can be replenished through the first-party billing page. */
22
+ export declare const ACCOUNT_QUOTA_EXCEEDED_CODE = "ACCOUNT_QUOTA";
21
23
  /**
22
24
  * Canonical provider-neutral code for a response that completed normally but
23
25
  * carried no content blocks at all. Providers occasionally emit a degenerate
@@ -22,6 +22,8 @@ export class HarnessError extends Error {
22
22
  export const CONTEXT_WINDOW_EXCEEDED_CODE = 'CONTEXT_WINDOW_EXCEEDED';
23
23
  /** Canonical provider-neutral code for an exhausted account quota or balance. */
24
24
  export const QUOTA_EXCEEDED_CODE = 'QUOTA';
25
+ /** Account-token quota that can be replenished through the first-party billing page. */
26
+ export const ACCOUNT_QUOTA_EXCEEDED_CODE = 'ACCOUNT_QUOTA';
25
27
  /**
26
28
  * Canonical provider-neutral code for a response that completed normally but
27
29
  * carried no content blocks at all. Providers occasionally emit a degenerate
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { Context } from '@deepseek-ai/cordis';
9
9
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
10
- import type { GenerateOptions, LlmConfigurableProvider, LlmDiscoveredModel, LlmFailure, LlmImageRequestPricing, LlmModelContext, LlmModelDiscoveryRequest, LlmModelInfo, LlmResolvedModelInfo, LlmProviderInfo, ModelModality, StreamChunk, SystemPromptUpdate } from './types.ts';
10
+ import type { GenerateOptions, LlmConfigurableProvider, LlmDiscoveredModel, LlmFailure, LlmImageRequestPricing, LlmModelContext, LlmModelDiscoveryRequest, LlmModelInfo, LlmResolvedModelInfo, LlmProviderInfo, ModelModality, StreamChunk, SystemPromptUpdate, ToolUpdate } from './types.ts';
11
11
  import type { ResolvedRetryPolicy } from './retry-policy.ts';
12
12
  import type { ProviderRequestId } from './brand.ts';
13
13
  import type { LlmCallConfig, LlmCallConfigAdapterDefaults } from './call-config.ts';
@@ -101,6 +101,8 @@ export interface PreparedLlmCall {
101
101
  readonly inputModalities?: readonly ModelModality[];
102
102
  /** Exact model system prompt update mode captured with the adapter dispatch generation. */
103
103
  readonly systemPromptUpdate?: SystemPromptUpdate;
104
+ /** Exact model tool update mode captured with the adapter dispatch generation. */
105
+ readonly toolUpdate?: ToolUpdate;
104
106
  /** Config fields materialized by the captured adapter rather than proposed by the caller. */
105
107
  readonly adapterDefaults: LlmCallConfigAdapterDefaults;
106
108
  /**
@@ -150,8 +152,9 @@ export declare abstract class LlmAdapter {
150
152
  imageRequestPricing(_provider: string, _model: string): LlmImageRequestPricing | undefined;
151
153
  /**
152
154
  * List models this adapter can currently advertise for one owned provider.
153
- * The result is advisory: an adapter may accept unlisted model ids, and
154
- * consumers must not turn absence into request rejection.
155
+ * Core routing accepts unlisted model ids; catalog-driven entry points such
156
+ * as the GUI may require membership. Adapters used there must advertise
157
+ * their available models; the base empty catalog offers no GUI selection.
155
158
  * @param _provider - one provider route owned by this adapter.
156
159
  * @returns discoverable models in adapter-preferred order.
157
160
  */
@@ -339,7 +342,8 @@ export declare class LlmRuntime extends TypertRemoteService {
339
342
  private detachedModalities;
340
343
  /**
341
344
  * Discover models advertised by one registered provider. Catalog membership
342
- * is advisory and never changes routing or request validation.
345
+ * does not constrain core routing. Catalog-driven entry points may restrict
346
+ * selection and submission to the advertised models.
343
347
  * @param provider - registered provider route to inspect.
344
348
  * @returns detached model metadata in adapter-preferred order.
345
349
  */
@@ -47,7 +47,7 @@ import { callConfigEquals } from "./call-config.js";
47
47
  import { HarnessError, INVALID_CREDENTIAL_CODE } from "./error.js";
48
48
  import { normalizeLlmFailure } from "./adapter-failure.js";
49
49
  import { normalizeApiKey } from "./api-key.js";
50
- import { contentHasFile, contentHasImage, fileHandleText, projectFilesToText, projectImagesForTextModel, } from "./content.js";
50
+ import { contentHasFile, contentHasImage, fileHandleText, projectFilesToText, projectImagesForTextModel, projectToolUpdates, } from "./content.js";
51
51
  export * from "./attribution.js";
52
52
  export * from "./brand.js";
53
53
  export * from "./error.js";
@@ -169,8 +169,9 @@ export class LlmAdapter {
169
169
  }
170
170
  /**
171
171
  * List models this adapter can currently advertise for one owned provider.
172
- * The result is advisory: an adapter may accept unlisted model ids, and
173
- * consumers must not turn absence into request rejection.
172
+ * Core routing accepts unlisted model ids; catalog-driven entry points such
173
+ * as the GUI may require membership. Adapters used there must advertise
174
+ * their available models; the base empty catalog offers no GUI selection.
174
175
  * @param _provider - one provider route owned by this adapter.
175
176
  * @returns discoverable models in adapter-preferred order.
176
177
  */
@@ -547,7 +548,8 @@ let LlmRuntime = (() => {
547
548
  }
548
549
  /**
549
550
  * Discover models advertised by one registered provider. Catalog membership
550
- * is advisory and never changes routing or request validation.
551
+ * does not constrain core routing. Catalog-driven entry points may restrict
552
+ * selection and submission to the advertised models.
551
553
  * @param provider - registered provider route to inspect.
552
554
  * @returns detached model metadata in adapter-preferred order.
553
555
  */
@@ -617,6 +619,11 @@ let LlmRuntime = (() => {
617
619
  if (systemPromptUpdate !== undefined && systemPromptUpdate !== 'in-history') {
618
620
  throw new LlmError(`adapter returned invalid system prompt update mode for provider "${provider}" model "${model}"`, 'INVALID_MODEL_INFO');
619
621
  }
622
+ // Widened for the same reason: catalog config supplies the tool update mode as text.
623
+ const toolUpdate = resolved.toolUpdate;
624
+ if (toolUpdate !== undefined && toolUpdate !== 'in-history' && toolUpdate !== 'addition-only') {
625
+ throw new LlmError(`adapter returned invalid tool update mode for provider "${provider}" model "${model}"`, 'INVALID_MODEL_INFO');
626
+ }
620
627
  const defaultMaxTokens = resolved.defaultMaxTokens;
621
628
  if (defaultMaxTokens !== undefined
622
629
  && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) {
@@ -631,6 +638,7 @@ let LlmRuntime = (() => {
631
638
  ...context === undefined ? {} : { context: { contextWindow: context.contextWindow } },
632
639
  ...defaultMaxTokens === undefined ? {} : { defaultMaxTokens },
633
640
  ...resolved.systemPromptUpdate === undefined ? {} : { systemPromptUpdate: resolved.systemPromptUpdate },
641
+ ...resolved.toolUpdate === undefined ? {} : { toolUpdate: resolved.toolUpdate },
634
642
  };
635
643
  const reasoning = resolved.reasoning;
636
644
  if (reasoning === undefined)
@@ -747,6 +755,7 @@ let LlmRuntime = (() => {
747
755
  ? {}
748
756
  : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
749
757
  ...modelInfo.systemPromptUpdate === undefined ? {} : { systemPromptUpdate: modelInfo.systemPromptUpdate },
758
+ ...modelInfo.toolUpdate === undefined ? {} : { toolUpdate: modelInfo.toolUpdate },
750
759
  stream: (options) => {
751
760
  if (dispatched) {
752
761
  throw new LlmError('a prepared LLM call can only be dispatched once', 'INVALID_PREPARED_CALL');
@@ -853,11 +862,19 @@ let LlmRuntime = (() => {
853
862
  && projectedMessages.some(message => contentHasImage(message.content))) {
854
863
  projectedMessages = projectImagesForTextModel(projectedMessages);
855
864
  }
856
- const projectedOptions = projectedMessages === resolvedOptions.messages
857
- ? resolvedOptions
858
- : Object.isFrozen(resolvedOptions)
859
- ? deepFreeze({ ...resolvedOptions, messages: projectedMessages })
860
- : { ...resolvedOptions, messages: projectedMessages };
865
+ // Tool changes are logged on every route; the route's declared mode selects what it receives.
866
+ const projectedTools = projectToolUpdates(projectedMessages, resolvedOptions.tools, modelInfo.toolUpdate, resolvedOptions.toolHistory);
867
+ projectedMessages = projectedTools.messages;
868
+ let projectedOptions = resolvedOptions;
869
+ if (projectedMessages !== resolvedOptions.messages || projectedTools.tools !== resolvedOptions.tools) {
870
+ projectedOptions = {
871
+ ...resolvedOptions,
872
+ messages: projectedMessages,
873
+ ...projectedTools.tools === undefined ? {} : { tools: projectedTools.tools },
874
+ };
875
+ if (Object.isFrozen(resolvedOptions))
876
+ deepFreeze(projectedOptions);
877
+ }
861
878
  const stream = dispatch(this.forAdapter(projectedOptions, adapter));
862
879
  iterator = stream[Symbol.asyncIterator]();
863
880
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { Branded } from '@deepseek-ai/dsh-brand';
7
7
  import type { FileAttachmentRef, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
8
- import type { ToolCallId, ProviderRequestId, ReasoningEffortId } from './brand.ts';
8
+ import type { MessageId, ToolCallId, ProviderRequestId, ReasoningEffortId } from './brand.ts';
9
9
  import type { Message, UserMessage } from './message.ts';
10
10
  declare module '@deepseek-ai/cordis' {
11
11
  interface Events {
@@ -108,9 +108,8 @@ export interface ToolRemovalBlock {
108
108
  }
109
109
  /**
110
110
  * Merge-extensible content blocks keyed by `type`. New core blocks must land
111
- * with adapter, UI, and compaction support. Developer tool-change blocks are
112
- * reserved for Session V4 persistence; providers and UI reject them until
113
- * their producers and consumers are implemented together.
111
+ * with adapter, UI, and compaction support. Tool-change blocks belong to
112
+ * developer messages; `projectToolUpdates` selects what each route receives.
114
113
  */
115
114
  export interface ContentBlockMap {
116
115
  'text': TextBlock;
@@ -364,6 +363,16 @@ export interface LlmModelReasoningInfo {
364
363
  * can follow the cached history instead of rewriting message 0.
365
364
  */
366
365
  export type SystemPromptUpdate = 'in-history';
366
+ /**
367
+ * How a model accepts native tool declarations that change mid-conversation.
368
+ * `'addition-only'`: the model reads a `tool-addition` block in a later
369
+ * developer message as activating a tool declared with `deferLoading`, so an
370
+ * added tool follows the cached history instead of rewriting the declaration
371
+ * list. `'in-history'`: the model additionally reads `tool-removal` blocks,
372
+ * so a removed tool keeps its declaration and the removal follows the history.
373
+ * Absent means every request declares the complete current tool list.
374
+ */
375
+ export type ToolUpdate = 'in-history' | 'addition-only';
367
376
  /** Exact-route model metadata resolved by its owning adapter. */
368
377
  export interface LlmResolvedModelInfo extends LlmModelInfo {
369
378
  /** Provider-owned context capacity when known. */
@@ -374,6 +383,8 @@ export interface LlmResolvedModelInfo extends LlmModelInfo {
374
383
  reasoning?: LlmModelReasoningInfo;
375
384
  /** Declared mid-conversation system prompt handling; absent means only a leading system message is read. */
376
385
  systemPromptUpdate?: SystemPromptUpdate;
386
+ /** Declared mid-conversation tool declaration handling; absent means every request declares the complete tool list. */
387
+ toolUpdate?: ToolUpdate;
377
388
  }
378
389
  /**
379
390
  * Adapter-private lossless-JSON state for replaying a successful response,
@@ -462,6 +473,18 @@ export interface RequestUserInput {
462
473
  }
463
474
  /** A durable conversation message or a user input used only for one request. */
464
475
  export type RequestMessage = Message | RequestUserInput;
476
+ /** Logged tool declarations and update identities since the last declaration reset. */
477
+ export interface ToolHistory {
478
+ /** Complete active declarations at the start of this history. */
479
+ readonly tools: readonly ToolSchema[];
480
+ /** Ordered developer messages, with additions resolved from their historical headers. */
481
+ readonly updates: readonly {
482
+ /** Identity used to locate this update in the derived request history. */
483
+ readonly messageId: MessageId;
484
+ /** Added definitions resolved from the event's referenced request header. */
485
+ readonly additions: readonly ToolSchema[];
486
+ }[];
487
+ }
465
488
  /** A single model request, fully assembled. */
466
489
  export interface GenerateOptions {
467
490
  /** Registered provider route selecting the adapter instance. */
@@ -483,6 +506,8 @@ export interface GenerateOptions {
483
506
  system?: string;
484
507
  /** Tool schemas (adapters map to the provider's `tools` field). */
485
508
  tools?: ToolSchema[];
509
+ /** Session-folded tool history used for route projection; omission sends complete declarations without tool updates. */
510
+ toolHistory?: ToolHistory;
486
511
  temperature?: number;
487
512
  maxTokens?: number;
488
513
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-llm",
3
3
  "description": "Provider-neutral LLM service interface for the DeepSeek Harness",
4
- "version": "0.1.7-rc.1",
4
+ "version": "0.2.0-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -65,16 +65,16 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "zod": "^4.4.3",
68
- "@deepseek-ai/dsh-util-crypto": "0.1.7-rc.1",
69
- "@deepseek-ai/dsh-brand": "0.1.7-rc.1",
70
- "@deepseek-ai/dsh-util-values": "0.1.7-rc.1",
71
- "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.1",
72
- "@deepseek-ai/dsh-timeout": "0.1.7-rc.1",
73
- "@deepseek-ai/schemastery": "~3.18.4"
68
+ "@deepseek-ai/dsh-util-crypto": "0.2.0-rc.1",
69
+ "@deepseek-ai/schemastery": "~3.18.4",
70
+ "@deepseek-ai/dsh-brand": "0.2.0-rc.1",
71
+ "@deepseek-ai/dsh-timeout": "0.2.0-rc.1",
72
+ "@deepseek-ai/dsh-util-values": "0.2.0-rc.1",
73
+ "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1"
74
74
  },
75
75
  "devDependencies": {
76
- "@deepseek-ai/dsh-attachment": "0.1.7-rc.1",
77
- "@deepseek-ai/dsh-invariants": "0.1.7-rc.1",
76
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
77
+ "@deepseek-ai/dsh-invariants": "0.2.0-rc.1",
78
78
  "@deepseek-ai/cordis": "~4.0.4"
79
79
  }
80
80
  }