@x1a0f3n9/dsh-llm 0.1.7-rc.3 → 0.2.1-rc.3

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
@@ -5,60 +5,60 @@
5
5
  /:
6
6
  en: 860509fb08c860f4
7
7
  zh: b2c4448e8afc1f12
8
- /x1a0f3n9-dsh-llm:
9
- en: a5bc4cf7bfe516fe
10
- zh: b6eb8b63a69621cc
11
- /x1a0f3n9-dsh-llm/summary:
12
- en: 174bcde9c848c4be
13
- zh: bf9329d52c9cf9ce
14
- /x1a0f3n9-dsh-llm/table-of-contents:
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
15
  en: d152484eb41ac6b4
16
16
  zh: 09388d293f9be9cb
17
- /x1a0f3n9-dsh-llm/use-this-package:
17
+ /deepseek-ai-dsh-llm/use-this-package:
18
18
  en: 33738e61ce410ac0
19
19
  zh: 363760460f4d4bed
20
- /x1a0f3n9-dsh-llm/use-this-package/when-to-choose-it:
20
+ /deepseek-ai-dsh-llm/use-this-package/when-to-choose-it:
21
21
  en: c3ecb5a1421f27db
22
22
  zh: 4bbd3c0e513845e1
23
- /x1a0f3n9-dsh-llm/use-this-package/minimal-composition:
24
- en: 7550d716b90fcb09
25
- zh: 77891a44ee623c7a
26
- /x1a0f3n9-dsh-llm/use-this-package/what-you-can-do:
27
- en: b45d7a414141734e
28
- zh: a641c8ea3827cee6
29
- /x1a0f3n9-dsh-llm/use-this-package/failures-and-recovery:
23
+ /deepseek-ai-dsh-llm/use-this-package/minimal-composition:
24
+ en: 9d481d19dc52e404
25
+ zh: aa09cf4462e93ba6
26
+ /deepseek-ai-dsh-llm/use-this-package/what-you-can-do:
27
+ en: dc61eb86585f6e0d
28
+ zh: 9c0d6b240dfd2cda
29
+ /deepseek-ai-dsh-llm/use-this-package/failures-and-recovery:
30
30
  en: 311d8c92447b7094
31
31
  zh: 68cf5cb1c3853cc1
32
- /x1a0f3n9-dsh-llm/understand-the-implementation:
32
+ /deepseek-ai-dsh-llm/understand-the-implementation:
33
33
  en: 9630d7e26193a512
34
34
  zh: 94222c2a487c4b8c
35
- /x1a0f3n9-dsh-llm/understand-the-implementation/design-philosophy:
35
+ /deepseek-ai-dsh-llm/understand-the-implementation/design-philosophy:
36
36
  en: 12d0444c18a397c7
37
37
  zh: 9d4a5468b4bb7fcd
38
- /x1a0f3n9-dsh-llm/understand-the-implementation/source-map:
38
+ /deepseek-ai-dsh-llm/understand-the-implementation/source-map:
39
39
  en: ae1618577f4e8364
40
40
  zh: a6e8ab1563c08dfb
41
- /x1a0f3n9-dsh-llm/understand-the-implementation/main-flow:
42
- en: 6dc955a0d80f6ca0
43
- zh: f81dbcaeacd8b269
44
- /x1a0f3n9-dsh-llm/understand-the-implementation/invariants:
41
+ /deepseek-ai-dsh-llm/understand-the-implementation/main-flow:
42
+ en: c95d4947745e39bb
43
+ zh: ec37b969f27629e2
44
+ /deepseek-ai-dsh-llm/understand-the-implementation/invariants:
45
45
  en: 0163a050239248e5
46
46
  zh: 885c95bedcbe2ff4
47
- /x1a0f3n9-dsh-llm/further-exploration:
47
+ /deepseek-ai-dsh-llm/further-exploration:
48
48
  en: 37e1d696afef8a88
49
49
  zh: 81f99c4507663cad
50
- /x1a0f3n9-dsh-llm/model-experience:
50
+ /deepseek-ai-dsh-llm/model-experience:
51
51
  en: fa5677003ad6bc0a
52
52
  zh: ba33cfc860d9c37c
53
- /x1a0f3n9-dsh-llm/model-experience/kv-cache-effect:
53
+ /deepseek-ai-dsh-llm/model-experience/kv-cache-effect:
54
54
  en: a81634136baef72f
55
55
  zh: b92dda883986b0e1
56
- /x1a0f3n9-dsh-llm/known-limitations-and-deferred-work:
57
- en: 1a96d843acf13feb
58
- zh: 3067b1bfe4e1d219
59
- /x1a0f3n9-dsh-llm/known-limitations-and-deferred-work/dev-note:
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
60
  en: ecbc333b88e14d88
61
61
  zh: 77c725d92efd8646
62
- /x1a0f3n9-dsh-llm/known-limitations-and-deferred-work/dev-note/open-items:
62
+ /deepseek-ai-dsh-llm/known-limitations-and-deferred-work/dev-note/open-items:
63
63
  en: 6b1a7e903e13ee21
64
64
  zh: bb62f2b1cc484bd5
package/README.md CHANGED
@@ -60,11 +60,30 @@ After a successful mount, `ctx.llm.listProviders()` reports the registered route
60
60
 
61
61
  `GenerateOptions.messages` accepts durable `Message` values and request-only `RequestUserInput` values. Request-only inputs carry user-role content with no `id` or `source`; Session writes and Agent delivery still require durable messages. Callers keep auxiliary inputs unchanged until the stream settles. A caller that records its exact request, such as session-title generation, must use durable messages.
62
62
 
63
+ `prepareCall(config, signal, configure?)` accepts concrete `LlmCallConfig` values. The optional synchronous, pure `ConfigureCall` function receives detached, deeply frozen `LlmCallControls` and model metadata from the captured adapter generation. Callers can compose ordinary functions to choose concrete controls; later writes replace earlier ones. Preparation retains the captured route, applies defaults to omitted controls, validates the result, and returns a frozen configuration for recording and dispatch. Callback failures or cancellation reject before dispatch. Preparation requires a registered adapter.
64
+
65
+ Configuration functions select controls before adapter defaults are materialized. For example, combine least-effort selection with an explicit output cap, then pass `configure(maxTokens)` as the third argument:
66
+
67
+ ```ts
68
+ import type { ConfigureCall } from '@x1a0f3n9/dsh-llm'
69
+
70
+ const leastReasoning: ConfigureCall = (controls, model) => {
71
+ const first = model.reasoning?.efforts[0]
72
+ return first === undefined ? controls : { ...controls, reasoningEffort: first.id }
73
+ }
74
+
75
+ const outputLimit = (maxTokens: number): ConfigureCall =>
76
+ controls => ({ ...controls, maxTokens })
77
+
78
+ const configure = (maxTokens: number): ConfigureCall =>
79
+ (controls, model) => outputLimit(maxTokens)(leastReasoning(controls, model), model)
80
+ ```
81
+
63
82
  ### What you can do
64
83
 
65
84
  - **Stream one model call** — `ctx.llm.stream(options)` yields raw chunks (token-level deltas) for any registered provider and model; consumers assemble them with `BlockAssembler`.
66
85
  - **Register provider adapters** — an adapter owns one or more provider routes, and its registration captures that route's retry policy; registering the same route twice fails with `DUPLICATE_ADAPTER`.
67
- - **Expose and activate providers through configuration** — adapters declare configurable-provider routes plus a settings namespace, so configuration surfaces can activate dormant providers and edit connection facts without a restart. `LlmConfigurableProvider.error` reports a configuration diagnostic for repair; unaffected models can remain serviceable.
86
+ - **Expose and activate providers through configuration** — adapters declare configurable-provider routes plus a settings namespace, so configuration surfaces can activate dormant providers and edit connection facts without a restart. `LlmConfigurableProvider.error` reports a configuration diagnostic for repair; unaffected models can remain serviceable. An entry can also carry `defaultBaseURL`, the endpoint the adapter uses when the profile sets none, and `nativeAuth`, the environment variables and OAuth sign-in that the adapter's authentication reads when the profile names no credential reference; `baseURLEnv` lists environment variables used to configure the endpoint. Configuration UIs read these fields from the directory without calling the adapter.
68
87
  - **Discover and resolve models** — List the models an adapter advertises, interrogate an endpoint for the models it serves (candidates carry optional `inputModalities`; absence means unknown), and resolve one exact model's context window, output default, reasoning efforts, input modalities, and system prompt update mode: `LlmResolvedModelInfo.systemPromptUpdate` is `'in-history'` when the model reads the latest `system` message at any position as the effective system prompt and absent when only a leading system message is read; `normalizeModelInfo` rejects any other value with `INVALID_MODEL_INFO`.
69
88
  - **Validate call config** — an explicit or configured reasoning effort is checked against the exact model before any provider I/O, and an adapter-configured output cap is materialized when the request omits one.
70
89
  - **Read an embedded Assistant stream without expanding it** — `assistantStreamFirstTokenTime` (first token), `assistantStreamHasVisibleContent` (any visible content), and `assistantStreamHasVisibleText` (any visible text) answer their questions from the compact records with early exit; `lastAssistantStreamChunk` scans backward to the last raw chunk of one type, `assistantStreamChunks` and `joinAssistantStreamText` scan the whole stream, and `assembleAssistantStream` feeds a `BlockAssembler` one joined delta per run with the same blocks, usage, and replay state as the per-member expansion. `runFirstTokenTime` and `runFirstVisibleTime` do the early-exit scan for one packed run, and `isTokenDelta`, `isVisibleChunk`, and `chunkHasVisibleText` define the token and visibility rules for a single chunk. `expandAssistantStream` remains the validating path for records read at a durable boundary; it is not memoized, because a retained expansion costs roughly ten times the compact stream for as long as the event lives.
@@ -105,9 +124,9 @@ The service is built on one separation: **the logical contract is provider-neutr
105
124
 
106
125
  ### Main flow
107
126
 
108
- A request is validated against its exact model's capability — context window, output default, reasoning efforts, input modalities, and `systemPromptUpdate` mode — and any adapter-configured defaults are materialized. The runtime preserves freezing for already-frozen input; hand-built callers own input immutability. `prepareCall()` binds those facts, detached context, and retry policy to the exact adapter generation that performs terminal dispatch, so HMR or dynamic settings cannot combine one generation's image capability with another generation's endpoint. An image-capable adapter projects durable references into route-specific request versions; `resolveImageAttachmentAccess()` separately maps an attachment provider's optional host object into the current tool execution world without changing the request image or its `variantId`. A text-only route receives deterministic per-image placeholders, including tool-role result images, without rewriting append-only session history. Durable `FileBlock` references never reach any adapter: request assembly replaces each one, including tool-role result occurrences, with deterministic handle text naming the file and its saved read-only path, resolved through the mounted attachment and filesystem providers. `ctx.llm.fileRequestText(ref)` exposes that exact synchronous projection to request measurement. An image occurrence derived with `offloaded: true` reaches every route as placeholder text through `projectOffloadedImages()`. An image-capable route whose retained occurrences exceed its `LlmImageRequestBudget` at their exact bytes fails with `IMAGE_OFFLOAD_REQUIRED` naming the additional oldest occurrences (`requiredImageOffload()`), never with an unlogged projection; `dsh-compaction-image-offload` logs the selected occurrences in one `image/offload` event and retries. Adapters that charge visual tokens declare per-route `imageRequestPricing`, which `ctx.llm.imageRequestPricing(provider, model)` resolves synchronously for the token meter. Dispatch goes through the `llm/stream` waterfall, then chunks return as token-level deltas and every adapter outcome reaches the consumer as one terminal `finish` chunk.
127
+ A request is validated against its exact model's capability — context window, output default, reasoning efforts, input modalities, and `systemPromptUpdate` mode — and any adapter-configured defaults are materialized. The runtime preserves freezing for already-frozen input; hand-built callers own input immutability. `prepareCall()` binds those facts, detached context, and retry policy to the exact adapter generation that performs terminal dispatch, so HMR or dynamic settings cannot combine one generation's image capability with another generation's endpoint. An image-capable adapter projects durable references into route-specific request versions; `resolveImageAttachmentAccess()` separately maps an attachment provider's optional host object into the current tool execution world without changing the request image or its `variantId`. A text-only route receives deterministic per-image placeholders, including tool-role result images, without rewriting append-only session history. Durable `FileBlock` references never reach any adapter: request assembly replaces each one, including tool-role result occurrences, with deterministic handle text naming the file and its saved read-only path, resolved through the mounted attachment and filesystem providers. `ctx.llm.fileRequestText(ref)` exposes that exact synchronous projection to request measurement. An image occurrence derived with `offloaded: true` reaches every route as placeholder text through `projectOffloadedImages()`. An image-capable route whose retained occurrences exceed its `LlmImageRequestBudget` at their exact bytes fails with `IMAGE_OFFLOAD_REQUIRED` naming the additional oldest occurrences (`requiredImageOffload()`), never with an unlogged projection; `dsh-compaction-image-offload` logs the selected occurrences in one `image/offload` event and retries. Adapters that charge visual tokens declare per-route `imageRequestPricing`, which `ctx.llm.imageRequestPricing(provider, model)` resolves synchronously for the token meter. Dispatch goes through the `llm/stream` waterfall, then chunks return as token-level deltas and every adapter outcome reaches the consumer as one terminal `finish` chunk. `imageRequestTarget(provider, model, image)` exposes the same request-image dimensions and byte target to screenshot consumers; adapters without image resizing preserve the normalized source.
109
128
 
110
- File detection reads current content, including tool-role result content, on every request without caching message identities or freeze state. The [file-scan decision](../../../.agents/notes/implemented/simplification/2026-09-07-file-content-scan.md) records the measured traversal cost.
129
+ File detection reads current content, including tool-role result content, on every request without caching message identities or freeze state. The [archived file-scan decision](../../../.agents/notes/archived/simplification/2026-09-07-file-content-scan.md) records the measured traversal cost.
111
130
 
112
131
  ### Invariants
113
132
 
package/README.zh.md CHANGED
@@ -60,11 +60,30 @@ for await (const chunk of ctx.llm.stream({
60
60
 
61
61
  `GenerateOptions.messages` 接受持久 `Message` 值和仅供请求使用的 `RequestUserInput` 值。仅供请求使用的输入包含 user-role 内容,不含 `id` 或 `source`;Session 写入和 Agent 投递仍然要求持久消息。调用方必须在流结束前保持辅助输入不变。会记录完整请求的调用方(例如会话标题生成)必须使用持久消息。
62
62
 
63
+ `prepareCall(config, signal, configure?)` 接受具体的 `LlmCallConfig` 值。可选的同步纯函数 `ConfigureCall` 接收已分离且深度冻结的 `LlmCallControls`,以及捕获的适配器代次所提供的模型元数据。调用方可组合普通函数来选择具体控制项;后执行的写入替换先前的值。准备操作保留捕获的路由,为省略的控制项应用默认值,校验结果,再返回用于记录与分发的冻结配置。回调失败或取消会在分发前拒绝。准备操作要求注册适配器。
64
+
65
+ 配置函数在适配器默认值物化之前选择控制项。例如,将最低强度选择与显式输出上限组合,再将 `configure(maxTokens)` 作为第三个参数传入:
66
+
67
+ ```ts
68
+ import type { ConfigureCall } from '@x1a0f3n9/dsh-llm'
69
+
70
+ const leastReasoning: ConfigureCall = (controls, model) => {
71
+ const first = model.reasoning?.efforts[0]
72
+ return first === undefined ? controls : { ...controls, reasoningEffort: first.id }
73
+ }
74
+
75
+ const outputLimit = (maxTokens: number): ConfigureCall =>
76
+ controls => ({ ...controls, maxTokens })
77
+
78
+ const configure = (maxTokens: number): ConfigureCall =>
79
+ (controls, model) => outputLimit(maxTokens)(leastReasoning(controls, model), model)
80
+ ```
81
+
63
82
  ### 你可以做什么
64
83
 
65
84
  - **流式发起一次模型调用**——`ctx.llm.stream(options)` 为任何已注册提供方与模型产出原始分片(token 级增量);消费方用 `BlockAssembler` 组装。
66
85
  - **注册提供方适配器**——一个适配器拥有一个或多个提供方路由,其注册会捕获该路由的重试策略;重复注册同一路由会以 `DUPLICATE_ADAPTER` 失败。
67
- - **通过配置暴露并激活提供方**——适配器声明可配置提供方路由与 settings namespace,配置界面因此可以激活休眠提供方并编辑连接信息,无需重启。`LlmConfigurableProvider.error` 报告供修复的配置诊断;未受影响的模型仍可提供服务。
86
+ - **通过配置暴露并激活提供方**——适配器声明可配置提供方路由与 settings namespace,配置界面因此可以激活休眠提供方并编辑连接信息,无需重启。`LlmConfigurableProvider.error` 报告供修复的配置诊断;未受影响的模型仍可提供服务。条目还可携带 `defaultBaseURL`,即配置未设置端点时适配器使用的端点;以及 `nativeAuth`,即配置未指定凭据引用时适配器认证读取的环境变量与 OAuth 登录;`baseURLEnv` 列出配置端点所用的环境变量。配置界面直接从目录读取这些字段,无需调用适配器。
68
87
  - **发现与解析模型**——列出适配器公布的模型、询问端点它提供哪些模型(候选携带可选的 `inputModalities`;缺省表示未知),并解析某个精确模型的上下文窗口、输出默认值、推理(reasoning)强度、输入模态与系统提示词更新模式:当模型把任意位置最新的 `system` 消息读作有效系统提示词时,`LlmResolvedModelInfo.systemPromptUpdate` 为 `'in-history'`;只读取开头 system 消息时该字段缺失;`normalizeModelInfo` 以 `INVALID_MODEL_INFO` 拒绝任何其他值。
69
88
  - **校验调用配置**——显式或配置的推理强度会在任何提供方 I/O 之前对照精确模型校验;请求省略输出上限时,会填入适配器配置的输出上限。
70
89
  - **不展开即读取内嵌 Assistant 流**——`assistantStreamFirstTokenTime`(首 token)、`assistantStreamHasVisibleContent`(任一可见内容)与 `assistantStreamHasVisibleText`(任一可见文本)通过可提前退出的扫描直接从紧凑记录得出结果;`lastAssistantStreamChunk` 反向扫描到某一类型的最后一个原始 chunk,`assistantStreamChunks` 与 `joinAssistantStreamText` 扫描整个流,`assembleAssistantStream` 向 `BlockAssembler` 每个 run 喂一段拼接 delta,blocks/usage/replayState 与逐成员展开相同。`runFirstTokenTime` 与 `runFirstVisibleTime` 对单个打包 run 做提前退出扫描,`isTokenDelta`、`isVisibleChunk` 与 `chunkHasVisibleText` 定义单个 chunk 的 token 与可见性规则。`expandAssistantStream` 仍是持久边界读取记录的校验路径;它不被记忆化,因为保留的展开在事件生命周期内约花费紧凑流的十倍内存。
@@ -105,9 +124,9 @@ for await (const chunk of ctx.llm.stream({
105
124
 
106
125
  ### 主流程
107
126
 
108
- 请求会对照其精确模型的能力校验,包括上下文窗口、输出默认值、推理强度、输入模态与 `systemPromptUpdate` 模式,并填入任何适配器配置的默认值。运行时保留已冻结输入的冻结状态;手动构建请求的调用方负责保证输入不可变。`prepareCall()` 把这些事实、分离的上下文与重试策略绑定到执行最终分发的精确适配器代次,因此 HMR(热模块替换)或动态设置无法把一个代次的图片能力与另一代次的端点混用。支持图片的适配器把持久引用投影为路由专用请求版本;`resolveImageAttachmentAccess()` 会单独把附件提供方的可选宿主对象映射进当前工具执行世界,而不改变请求图片或其 `variantId`。纯文本路由接收确定性的逐图片占位符,包括 tool-role 结果图片,而不会改写仅追加会话历史。持久 `FileBlock` 引用永远不会到达任何适配器:请求组装把每个引用(包括 tool-role 结果中的出现)替换为确定性句柄文本,指出文件与其只读保存路径,路径经由挂载的附件与文件系统提供方解析。`ctx.llm.fileRequestText(ref)` 向请求计量公开相同的同步投影。派生后带有 `offloaded: true` 的图片出现位置,经 `projectOffloadedImages()` 以占位文本到达每条路由。支持图片的路由在保留的出现位置按精确字节超过其 `LlmImageRequestBudget` 时,以 `IMAGE_OFFLOAD_REQUIRED` 失败并说明还需省略多少最老的出现位置(`requiredImageOffload()`),绝不发送未记录的投影;`dsh-compaction-image-offload` 用一条 `image/offload` 事件记录所选位置并重试。对视觉 token 收费的适配器声明按路由的 `imageRequestPricing`,`ctx.llm.imageRequestPricing(provider, model)` 为 token meter 同步解析它。分发经过 `llm/stream` waterfall(瀑布式事件),随后分片以 token 级增量返回,每个适配器结果都以唯一一个终止 `finish` 分片到达消费方。
127
+ 请求会对照其精确模型的能力校验,包括上下文窗口、输出默认值、推理强度、输入模态与 `systemPromptUpdate` 模式,并填入任何适配器配置的默认值。运行时保留已冻结输入的冻结状态;手动构建请求的调用方负责保证输入不可变。`prepareCall()` 把这些事实、分离的上下文与重试策略绑定到执行最终分发的精确适配器代次,因此 HMR(热模块替换)或动态设置无法把一个代次的图片能力与另一代次的端点混用。支持图片的适配器把持久引用投影为路由专用请求版本;`resolveImageAttachmentAccess()` 会单独把附件提供方的可选宿主对象映射进当前工具执行世界,而不改变请求图片或其 `variantId`。纯文本路由接收确定性的逐图片占位符,包括 tool-role 结果图片,而不会改写仅追加会话历史。持久 `FileBlock` 引用永远不会到达任何适配器:请求组装把每个引用(包括 tool-role 结果中的出现)替换为确定性句柄文本,指出文件与其只读保存路径,路径经由挂载的附件与文件系统提供方解析。`ctx.llm.fileRequestText(ref)` 向请求计量公开相同的同步投影。派生后带有 `offloaded: true` 的图片出现位置,经 `projectOffloadedImages()` 以占位文本到达每条路由。支持图片的路由在保留的出现位置按精确字节超过其 `LlmImageRequestBudget` 时,以 `IMAGE_OFFLOAD_REQUIRED` 失败并说明还需省略多少最老的出现位置(`requiredImageOffload()`),绝不发送未记录的投影;`dsh-compaction-image-offload` 用一条 `image/offload` 事件记录所选位置并重试。对视觉 token 收费的适配器声明按路由的 `imageRequestPricing`,`ctx.llm.imageRequestPricing(provider, model)` 为 token meter 同步解析它。分发经过 `llm/stream` waterfall(瀑布式事件),随后分片以 token 级增量返回,每个适配器结果都以唯一一个终止 `finish` 分片到达消费方。 `imageRequestTarget(provider, model, image)` 向截图调用方提供与模型请求一致的图片尺寸和字节目标;不缩放图片的适配器保留归一化后的原图。
109
128
 
110
- 文件检测在每次请求时读取当前内容,包括 tool-role 结果内容,不缓存消息身份或冻结状态。[文件扫描决策](../../../.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md)记录了实测遍历成本。
129
+ 文件检测在每次请求时读取当前内容,包括 tool-role 结果内容,不缓存消息身份或冻结状态。[已归档的文件扫描决策](../../../.agents/notes/archived/simplification/2026-09-07-file-content-scan.md)记录了实测遍历成本。
111
130
 
112
131
  ### 不变式
113
132
 
package/lib/index.js CHANGED
@@ -119,7 +119,7 @@ function createToolResultMessage(input) {
119
119
  */
120
120
  /**
121
121
  * Base class for all harness errors. Carries a `code` (stable, programmatic —
122
- * e.g. `NO_ADAPTER`, `INVALID_ARGS`, `INVARIANT`) distinct from the
122
+ * e.g. `NO_ADAPTER`, `INVALID_ARGS`) distinct from the
123
123
  * human-readable `message`, and supports `cause` chaining via the standard
124
124
  * `ErrorOptions`. `name` defaults to the subclass constructor name.
125
125
  */
@@ -846,7 +846,7 @@ function projectToolUpdates(messages, tools, toolUpdate, history) {
846
846
  /**
847
847
  * Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
848
848
  * adapters from drifting. See
849
- * `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
849
+ * `docs/subsystems/llm-streaming.md#appidentity--app-attribution`.
850
850
  *
851
851
  * App-attribution vocabulary for provider requests.
852
852
  * @module @x1a0f3n9/dsh-llm/attribution
@@ -1702,6 +1702,21 @@ var LlmAdapter = class {
1702
1702
  */
1703
1703
  imageRequestPricing(_provider, _model) {}
1704
1704
  /**
1705
+ * Resolve the image dimensions and byte target this adapter sends to the model.
1706
+ * The default preserves normalized images; adapters that resize must override it.
1707
+ * @param _provider - registered provider route.
1708
+ * @param _model - exact model id.
1709
+ * @param image - normalized image whose request projection is being resolved.
1710
+ * @returns the same target used by request serialization, before provider-internal processing.
1711
+ */
1712
+ imageRequestTarget(_provider, _model, image) {
1713
+ return {
1714
+ width: image.width,
1715
+ height: image.height,
1716
+ maxBytes: image.bytes
1717
+ };
1718
+ }
1719
+ /**
1705
1720
  * List models this adapter can currently advertise for one owned provider.
1706
1721
  * Core routing accepts unlisted model ids; catalog-driven entry points such
1707
1722
  * as the GUI may require membership. Adapters used there must advertise
@@ -1808,20 +1823,14 @@ let LlmRuntime = (() => {
1808
1823
  }
1809
1824
  /** Notify topology observers without letting one broken listener veto the commit. */
1810
1825
  emitAdaptersUpdated() {
1811
- let invariantFailure;
1812
1826
  for (const listener of this.ctx.events.dispatch("emit", ["llm/adapters-updated"])) try {
1813
1827
  const returned = listener();
1814
1828
  if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
1815
1829
  this.warnAdaptersListenerFailure(error);
1816
1830
  });
1817
1831
  } catch (error) {
1818
- if (error?.code === "INVARIANT") {
1819
- invariantFailure ??= error;
1820
- continue;
1821
- }
1822
1832
  this.warnAdaptersListenerFailure(error);
1823
1833
  }
1824
- if (invariantFailure !== void 0) throw invariantFailure;
1825
1834
  }
1826
1835
  /** Contained-listener diagnostic shared by the sync and async failure paths. */
1827
1836
  warnAdaptersListenerFailure(error) {
@@ -1930,10 +1939,7 @@ let LlmRuntime = (() => {
1930
1939
  if (entry.provider.length === 0 || entry.displayName.length === 0 || entry.settingsNs.length === 0) throw new LlmError("configurable providers need a non-empty provider, displayName, and settingsNs", "INVALID_DIRECTORY");
1931
1940
  if (entry.settingsPath.some((segment) => segment.length === 0)) throw new LlmError(`configurable provider "${entry.provider}" has an empty settingsPath segment`, "INVALID_DIRECTORY");
1932
1941
  if (this.directory.has(entry.provider) && !own.has(entry.provider) || detached.some((seen) => seen.provider === entry.provider)) throw new LlmError(`configurable provider "${entry.provider}" is already declared`, "DUPLICATE_DIRECTORY");
1933
- detached.push({
1934
- ...entry,
1935
- settingsPath: [...entry.settingsPath]
1936
- });
1942
+ detached.push(detachDirectoryEntry(entry));
1937
1943
  }
1938
1944
  for (const entry of held) this.directory.delete(entry.provider);
1939
1945
  for (const entry of detached) this.directory.set(entry.provider, entry);
@@ -1962,10 +1968,7 @@ let LlmRuntime = (() => {
1962
1968
  * @returns detached directory entries in declaration order.
1963
1969
  */
1964
1970
  listConfigurableProviders() {
1965
- return [...this.directory.values()].map((entry) => ({
1966
- ...entry,
1967
- settingsPath: [...entry.settingsPath]
1968
- }));
1971
+ return [...this.directory.values()].map(detachDirectoryEntry);
1969
1972
  }
1970
1973
  /**
1971
1974
  * Offer to interrogate provider endpoints on behalf of the settings
@@ -2057,6 +2060,16 @@ let LlmRuntime = (() => {
2057
2060
  return this.adapters.get(provider)?.adapter.imageRequestPricing(provider, model);
2058
2061
  }
2059
2062
  /**
2063
+ * Resolve the image projection for an exact registered model route.
2064
+ * @param provider - registered provider route.
2065
+ * @param model - exact model id.
2066
+ * @param image - normalized image reference.
2067
+ * @returns dimensions and byte target used by the adapter's request serializer.
2068
+ */
2069
+ imageRequestTarget(provider, model, image) {
2070
+ return this.registration(provider).adapter.imageRequestTarget(provider, model, image);
2071
+ }
2072
+ /**
2060
2073
  * Resolve the exact text one durable file occurrence contributes to every
2061
2074
  * provider request in the current execution environment.
2062
2075
  * @param ref - durable verbatim file reference from model history.
@@ -2202,20 +2215,33 @@ let LlmRuntime = (() => {
2202
2215
  * Resolve one call under its current adapter registration. The returned
2203
2216
  * one-shot handle keeps that registration across header logging and dispatch,
2204
2217
  * so HMR cannot combine one adapter's capability result with another adapter.
2205
- * @param config - provider/model route and optional request controls.
2218
+ * An optional synchronous callback selects concrete controls using captured
2219
+ * model metadata. Defaults and validation apply to its result. Callback
2220
+ * failures and cancellation reject preparation before dispatch.
2221
+ * @param config - provider/model route and optional concrete request controls.
2206
2222
  * @param signal - optional cancellation for adapter-owned capability lookup.
2223
+ * @param configure - pure control selection, called once before defaults and validation.
2207
2224
  * @returns a prepared config and its registration-bound stream entry point.
2208
2225
  */
2209
- async prepareCall(config, signal) {
2210
- const registration = this.registration(config.provider);
2211
- const adapterCall = await registration.adapter.prepareCall(config.provider, config.model, signal);
2212
- const modelInfo = this.normalizeModelInfo(registration, config.model, adapterCall.model);
2213
- const resolved = this.resolveCallWithInfo(config, modelInfo);
2226
+ async prepareCall(config, signal, configure) {
2227
+ signal?.throwIfAborted();
2228
+ const { provider, model, ...controls } = config;
2229
+ const registration = this.registration(provider);
2230
+ const adapterCall = await registration.adapter.prepareCall(provider, model, signal);
2231
+ signal?.throwIfAborted();
2232
+ const modelInfo = this.normalizeModelInfo(registration, model, adapterCall.model);
2233
+ const proposed = configure === void 0 ? config : {
2234
+ ...configure(deepFreeze(structuredClone(controls)), deepFreeze(modelInfo)),
2235
+ provider,
2236
+ model
2237
+ };
2238
+ signal?.throwIfAborted();
2239
+ const resolved = this.resolveCallWithInfo(proposed, modelInfo);
2214
2240
  const resolvedConfig = deepFreeze(structuredClone(resolved.config));
2215
2241
  const context = resolved.context === void 0 ? void 0 : deepFreeze(structuredClone(resolved.context));
2216
2242
  const adapterDefaults = deepFreeze({
2217
- ...config.reasoningEffort === void 0 && resolvedConfig.reasoningEffort !== void 0 ? { reasoningEffort: true } : {},
2218
- ...config.maxTokens === void 0 && resolvedConfig.maxTokens !== void 0 ? { maxTokens: true } : {}
2243
+ ...proposed.reasoningEffort === void 0 && resolvedConfig.reasoningEffort !== void 0 ? { reasoningEffort: true } : {},
2244
+ ...proposed.maxTokens === void 0 && resolvedConfig.maxTokens !== void 0 ? { maxTokens: true } : {}
2219
2245
  });
2220
2246
  let dispatched = false;
2221
2247
  return Object.freeze({
@@ -2392,5 +2418,18 @@ function adapterFailureChunk(error, signal) {
2392
2418
  }
2393
2419
  };
2394
2420
  }
2421
+ /** Copy a directory entry, including its arrays, so neither side can mutate the other's copy. */
2422
+ function detachDirectoryEntry(entry) {
2423
+ const { nativeAuth } = entry;
2424
+ return {
2425
+ ...entry,
2426
+ settingsPath: [...entry.settingsPath],
2427
+ ...entry.baseURLEnv === void 0 ? {} : { baseURLEnv: [...entry.baseURLEnv] },
2428
+ ...nativeAuth === void 0 ? {} : { nativeAuth: {
2429
+ ...nativeAuth,
2430
+ env: [...nativeAuth.env]
2431
+ } }
2432
+ };
2433
+ }
2395
2434
  //#endregion
2396
2435
  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 };