@x1a0f3n9/dsh-llm 0.1.2-alpha.6

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -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/llm/llm/README.md
5
+ README.md: fe4503518564e2780bfa5a412a013b7ec2cacdcb
6
+ README.zh.md: a4416f92c83682c92964df1fe8278e5d05c49eaa
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ ---
2
+ description: "The provider-neutral model-call service for users and maintainers streaming requests, registering provider adapters, or resolving model metadata."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-llm
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ `@x1a0f3n9/dsh-llm` is the provider-neutral model-call service at the center of the harness's LLM capability. Every composition that streams a request to a model provider goes through it, and it owns the shared vocabulary — messages, content blocks, and raw stream chunks — that the agent loop, session log, and every plugin speak. With it you can register provider adapters, stream one model call, list and discover models, resolve exact-model metadata and call defaults, and capture each provider's retry policy; every request is logged so it stays reconstructable from the session log. It executes no retries and owns no provider wire logic: adapters translate their provider's format, and the optional `dsh-llm-retry` package re-runs failed requests at durable step boundaries. Requests are deep-frozen before dispatch, so middleware and adapters can read them but never rewrite them.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ 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
+
30
+ ### When to choose it
31
+
32
+ Choose this package whenever a plugin or composition needs to call a model: it is the only supported path into provider adapters, and it keeps one vocabulary across the loop, the session log, and every consumer. Do not reach for it when you need provider-specific wire behavior (that belongs in an adapter such as `dsh-llm-deepseek` or `dsh-llm-pi-ai`) or retry execution (that belongs in `dsh-llm-retry`).
33
+
34
+ ### Minimal composition
35
+
36
+ Mount the service and at least one adapter, then select the provider by name in every request:
37
+
38
+ ```yaml
39
+ - name: '@x1a0f3n9/dsh-llm'
40
+ - name: '@x1a0f3n9/dsh-llm-deepseek'
41
+ config:
42
+ apiKeyEnv: DEEPSEEK_API_KEY
43
+ ```
44
+
45
+ A stream returns token-level chunks and always ends with one terminal `finish` chunk; `BlockAssembler` turns the chunks into content blocks and messages, and the loop logs each chunk for replay:
46
+
47
+ ```text
48
+ for await (const chunk of ctx.llm.stream({
49
+ provider: 'deepseek-official',
50
+ model: 'deepseek-v4-flash',
51
+ messages: [createUserMessage({ content: [{ type: 'text', text: 'Hello' }] })],
52
+ })) {
53
+ // chunks: block-start, text-delta, ..., usage, finish
54
+ }
55
+ ```
56
+
57
+ After a successful mount, `ctx.llm.listProviders()` reports the registered routes in registration order.
58
+
59
+ ### What you can do
60
+
61
+ - **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`.
62
+ - **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`.
63
+ - **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.
64
+ - **Discover and resolve models** — list the models an adapter advertises, interrogate an endpoint for the models it serves, and resolve one exact model's context window, output default, reasoning efforts, and input modalities.
65
+ - **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.
66
+
67
+ ### Failures and recovery
68
+
69
+ 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.
70
+
71
+ -----
72
+
73
+ <a id="understand-the-implementation"></a>
74
+ ## Understand the implementation
75
+
76
+ <details>
77
+ <summary>Implementation internals — click to expand</summary>
78
+
79
+ This section explains the design behind the service; the observable behavior is fully covered in [Use this package](#use-this-package).
80
+
81
+ ### Design philosophy
82
+
83
+ The service is built on one separation: **the logical contract is provider-neutral, adapters own the wire.** It defines the canonical message, content-block, and stream-chunk vocabulary once, and every provider adapter translates only its own wire format into that vocabulary. The registry is the topology owner — adapter routes, configurable-provider entries, and discovery offers all register here and are disposed with their fiber — while a request stays a pure function of the session log: loop-built requests arrive deep-frozen, so listeners and adapters read them and never rewrite them.
84
+
85
+ ### Source map
86
+
87
+ | File | Role |
88
+ |---|---|
89
+ | [`src/index.ts`](src/index.ts) | The `LlmRuntime` service: adapter registry, configurable-provider directory, model discovery, call preparation, and the streaming boundary |
90
+ | [`src/types.ts`](src/types.ts) | The `StreamChunk` protocol, content-block map, finish reasons, and shared vocabulary |
91
+ | [`src/message.ts`](src/message.ts) | Immutable message constructors shared by delivery, history, and requests |
92
+ | [`src/assembler.ts`](src/assembler.ts) | `BlockAssembler`: incremental chunk-to-block assembly |
93
+ | [`src/call-config.ts`](src/call-config.ts) | Call-config validation, adapter-default materialization, and request freezing |
94
+ | [`src/retry-policy.ts`](src/retry-policy.ts) | Provider-owned retry policy resolution (normal and always modes) |
95
+ | [`src/error.ts`](src/error.ts) | `HarnessError`/`LlmError` taxonomy and provider-neutral failure codes |
96
+ | [`src/content.ts`](src/content.ts) | Shared image-content helpers, including request-image offloading |
97
+ | [`src/api-key.ts`](src/api-key.ts) | Credential format check shared by every adapter |
98
+ | [`src/adapter-failure.ts`](src/adapter-failure.ts) | Failure normalization into terminal finish chunks |
99
+
100
+ ### Main flow
101
+
102
+ A request is validated against its exact model's capability — context window, output default, reasoning efforts, and input modalities — and any adapter-configured defaults are materialized, then the whole request is deep-frozen. `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 nested tool-result images, without rewriting append-only session history. `offloadRequestImagesWithPolicy()` removes oldest images deterministically by raw or base64 size and count or byte quanta; the pure `offloadedImagePrefixCount()` exposes that decision so route-owned request pricing can reproduce it without building the projection. 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.
103
+
104
+ ### Invariants
105
+
106
+ - **Model-visible ⟺ logged** — anything that reaches a provider request is reconstructable from the session log; loop-built requests are deep-frozen and never rewritten.
107
+ - **Replay state travels only within one adapter** — assistant replay state rides along only when the same adapter instance owns the historical and target routes; otherwise it is dropped before dispatch.
108
+ - **Prepared calls are one-shot** — a prepared call can be dispatched exactly once, and its call-config fields must match the prepared config.
109
+ - **Image projection follows the captured route** — durable `ImageBlock` references become route-specific request versions only for image-capable models; text-only models receive stable placeholders.
110
+ - **Protocol ordering** — `usage` precedes `finish`, tool arguments stay raw JSON strings, and nothing follows the terminal `finish`.
111
+ - **Registry mutations are atomic** — route and directory registration validates the whole candidate set before anything moves, so a refused change leaves the previous state serving.
112
+
113
+ </details>
114
+
115
+ -----
116
+
117
+ <a id="further-exploration"></a>
118
+ ## Further Exploration
119
+
120
+ Read these pages when the package-level contract is not enough. They move from the shared types to the concrete adapters, the retry executor, and the measurement service.
121
+
122
+ - [LLM streaming subsystem](../../../docs/subsystems/llm-streaming.md) — the message and block types, the assembled model request, the `StreamChunk` protocol, and the adapter contract.
123
+ - [llm-deepseek adapter](../llm-deepseek/README.md) — the direct DeepSeek chat-completions implementation.
124
+ - [llm-pi-ai adapter](../llm-pi-ai/README.md) — the pi-ai-backed multi-provider implementation.
125
+ - [llm-retry](../llm-retry/README.md) — the retry executor that re-runs failed model requests.
126
+ - [Token meter](../token-meter/README.md) — replay-aware request and context pressure measurement.
127
+ - [Twin LLM adapters](../../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md) — why the DeepSeek route ships two structurally different adapters.
128
+ - [Terminal LLM stream failures](../../../.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md) — the service boundary between model-request outcomes and plugin failures.
129
+
130
+ -----
131
+
132
+ <a id="model-experience"></a>
133
+ ## Model Experience
134
+
135
+ None, as the LLM service adds no content; adapters choose when to add the shared image descriptors and per-image placeholders exported by this package.
136
+
137
+ #### KV Cache effect
138
+
139
+ Reasoning-effort materialization preserves the assembled request prefix. Image identity and request-preview text are deterministic, while an optional execution-world path is resolved for each request; a changed path or image-offload boundary can prevent reuse from that image.
140
+
141
+ ## Known Limitations and Deferred Work
142
+
143
+ <a id="known-limitations-and-deferred-work"></a>
144
+
145
+
146
+ These limits define where this service stops and other packages or future work begin. They are current package constraints, not a task backlog.
147
+
148
+ - **No retry execution, caching, or rate limiting ships in this service** — provider registration stores the retry policy, but a stream remains a single provider attempt; `@x1a0f3n9/dsh-llm-retry` executes the policy at durable agent-step boundaries.
149
+ - **`GenerateOptions` sampling is `temperature`/`maxTokens`/`stop` only** — no `tool_choice`, `top_p`, or penalty fields; the vocabulary grows when a producer lands ([dropped inert knobs](../../../.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md)).
150
+ - **Producer-gated variants stay out until produced** — `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)).
151
+ - **`BlockAssembler` handles core block kinds only** — a plugin-added block type whose stream is never closed by `block-end` makes `blocks()` throw.
152
+ - **`GenerateOptions.sessionId` is a locally-declared brand** — importing dsh-session's `SessionId` would create a dependency cycle.
153
+
154
+ <a id="dev-note"></a>
155
+ ### Dev Note
156
+
157
+ <details>
158
+ <summary>Working context for maintainers — click to expand</summary>
159
+
160
+ This Dev Note is non-authoritative working context: open questions and undecided directions. Shipped behavior and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
161
+
162
+ #### Open items
163
+
164
+ - `GenerateOptions.sessionId` is a locally-declared brand because importing dsh-session's `SessionId` would create a dependency cycle; a future ids-owning package could dissolve the workaround.
165
+ - Reasoning-effort identifiers are adapter-owned opaque strings resolved only against each adapter's advertised set; a shared cross-adapter effort vocabulary is not decided.
166
+ - The `llm/adapters-updated` event is payload-free by design; consumers re-read the registries instead of receiving the new topology in the event.
167
+
168
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,168 @@
1
+ ---
2
+ description: "面向用户与维护者的提供方无关模型调用服务说明:流式发起请求、注册提供方适配器或解析模型元数据。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-llm
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ `@x1a0f3n9/dsh-llm` 是位于 harness LLM 能力核心的提供方无关模型调用服务。任何向模型提供方发起流式请求的组合都会经过它,它拥有 agent loop(智能体循环)、会话日志和所有插件共同使用的共享词汇——消息、内容块与原始流式分片。借助它,你可以注册提供方适配器、流式发起一次模型调用、列出与发现模型、解析精确模型元数据与调用默认值,并捕获每个提供方的重试策略;每个请求都会被记录,因此始终可以从会话日志重建。它不执行重试,也不拥有任何提供方协议逻辑:适配器翻译各自提供方的格式,可选包 `dsh-llm-retry` 在持久步骤边界上重跑失败的请求。请求在分发前会被深度冻结,因此 middleware 与适配器只能读取,绝不能改写。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 任何调用模型提供方的组合——agent loop、会话标题生成器、压缩(compaction)摘要器——都会通过本服务流式发起请求。与至少一个提供方适配器一起挂载它;服务本身没有任何配置,也不包含提供方协议代码。
29
+
30
+ ### 何时选择
31
+
32
+ 当插件或组合需要调用模型时选择本包:它是进入提供方适配器的唯一受支持路径,并在 loop、会话日志与每个消费方之间保持同一套词汇。当需要提供方特定的协议行为(那属于 `dsh-llm-deepseek` 或 `dsh-llm-pi-ai` 之类的适配器)或重试执行(那属于 `dsh-llm-retry`)时,不要选择它。
33
+
34
+ ### 最小组合
35
+
36
+ 挂载服务与至少一个适配器,然后在每个请求中按名称选择提供方:
37
+
38
+ ```yaml
39
+ - name: '@x1a0f3n9/dsh-llm'
40
+ - name: '@x1a0f3n9/dsh-llm-deepseek'
41
+ config:
42
+ apiKeyEnv: DEEPSEEK_API_KEY
43
+ ```
44
+
45
+ 流会返回 token 级分片,并始终以一个终止 `finish` 分片结束;`BlockAssembler` 把分片组装为内容块与消息,loop 记录每个分片以供回放:
46
+
47
+ ```text
48
+ for await (const chunk of ctx.llm.stream({
49
+ provider: 'deepseek-official',
50
+ model: 'deepseek-v4-flash',
51
+ messages: [createUserMessage({ content: [{ type: 'text', text: 'Hello' }] })],
52
+ })) {
53
+ // chunks: block-start, text-delta, ..., usage, finish
54
+ }
55
+ ```
56
+
57
+ 挂载成功后,`ctx.llm.listProviders()` 会按注册顺序报告已注册路由。
58
+
59
+ ### 你可以做什么
60
+
61
+ - **流式发起一次模型调用**——`ctx.llm.stream(options)` 为任何已注册提供方与模型产出原始分片(token 级增量);消费方用 `BlockAssembler` 组装。
62
+ - **注册提供方适配器**——一个适配器拥有一个或多个提供方路由,其注册会捕获该路由的重试策略;重复注册同一路由会以 `DUPLICATE_ADAPTER` 失败。
63
+ - **通过配置暴露并激活提供方**——适配器声明可配置提供方路由与 settings namespace,配置界面因此可以激活休眠提供方并编辑连接事实,无需重启。
64
+ - **发现与解析模型**——列出适配器公布的模型、询问端点它提供哪些模型,并解析某个精确模型的上下文窗口、输出默认值、推理(reasoning)强度与输入模态。
65
+ - **校验调用配置**——显式或配置的推理强度会在任何提供方 I/O 之前对照精确模型校验;请求省略输出上限时,会填入适配器配置的输出上限。
66
+
67
+ ### 失败与恢复
68
+
69
+ 每个流都恰好以一个终止 `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 失败步骤扩展点上的职责。
70
+
71
+ -----
72
+
73
+ <a id="understand-the-implementation"></a>
74
+ ## 理解实现
75
+
76
+ <details>
77
+ <summary>实现细节——点击展开</summary>
78
+
79
+ 本节解释服务背后的设计;可观察行为已在[使用本包](#use-this-package)中完整说明。
80
+
81
+ ### 设计理念
82
+
83
+ 服务建立在一个分离之上:**逻辑约定是提供方无关的,适配器拥有协议。** 它一次性地定义规范消息、内容块与流式分片词汇,每个提供方适配器只把自己的协议格式翻译为该词汇。注册表是拓扑的拥有者——适配器路由、可配置提供方条目与发现 offer 都在这里注册,并随其 fiber 一起 dispose(资源释放)——而请求始终是会话日志的纯函数:loop 构建的请求以深度冻结状态到达,因此监听器与适配器只能读取,绝不能改写。
84
+
85
+ ### 源码地图
86
+
87
+ | 文件 | 职责 |
88
+ |---|---|
89
+ | [`src/index.ts`](src/index.ts) | `LlmRuntime` 服务:适配器注册表、可配置提供方目录、模型发现、调用准备与流式边界 |
90
+ | [`src/types.ts`](src/types.ts) | `StreamChunk` 协议、内容块映射、结束原因与共享词汇 |
91
+ | [`src/message.ts`](src/message.ts) | 投递、历史与请求共享的不可变消息构造函数 |
92
+ | [`src/assembler.ts`](src/assembler.ts) | `BlockAssembler`:分片到块的增量组装 |
93
+ | [`src/call-config.ts`](src/call-config.ts) | 调用配置校验、适配器默认值填入与请求冻结 |
94
+ | [`src/retry-policy.ts`](src/retry-policy.ts) | 提供方自有重试策略解析(normal 与 always 模式) |
95
+ | [`src/error.ts`](src/error.ts) | `HarnessError`/`LlmError` 分类体系与提供方无关失败 code |
96
+ | [`src/content.ts`](src/content.ts) | 共享图片内容辅助函数,包括请求图片卸载 |
97
+ | [`src/api-key.ts`](src/api-key.ts) | 每个适配器共享的凭据格式校验 |
98
+ | [`src/adapter-failure.ts`](src/adapter-failure.ts) | 把失败归一化为终止 finish 分片 |
99
+
100
+ ### 主流程
101
+
102
+ 请求会对照其精确模型的能力——上下文窗口、输出默认值、推理强度与输入模态——校验,填入任何适配器配置的默认值,然后整个请求被深度冻结。`prepareCall()` 把这些事实、分离的上下文与重试策略绑定到执行最终分发的精确适配器代次,因此 HMR 或动态设置无法把一个代次的图片能力与另一代次的端点混用。支持图片的适配器把持久引用投影为路由专用请求版本;`resolveImageAttachmentAccess()` 会单独把附件提供方的可选宿主对象映射进当前工具执行世界,而不改变请求图片或其 `variantId`。纯文本路由接收确定性的逐图片占位符,包括嵌套工具结果图片,而不会改写仅追加会话历史。`offloadRequestImagesWithPolicy()` 按原始字节或 base64 大小以及图片数或字节步长,确定性地从最旧图片开始移除;纯函数 `offloadedImagePrefixCount()` 公开同一决策,使路由所属的请求定价无需构建投影即可复现它。对视觉 token 收费的适配器声明按路由的 `imageRequestPricing`,`ctx.llm.imageRequestPricing(provider, model)` 为 token meter 同步解析它。分发经过 `llm/stream` waterfall,随后分片以 token 级增量返回,每个适配器结果都以唯一一个终止 `finish` 分片到达消费方。
103
+
104
+ ### 不变式
105
+
106
+ - **模型可见 ⟺ 已记录**——到达提供方请求的任何内容都可以从会话日志重建;loop 构建的请求被深度冻结,绝不改写。
107
+ - **回放状态只在同一适配器内流动**——仅当同一适配器实例同时拥有历史路由与目标路由时,assistant 回放状态才会随行;否则在分发前被丢弃。
108
+ - **已准备调用是一次性的**——已准备调用只能分发一次,且其调用配置字段必须与准备好的配置一致。
109
+ - **图片投影遵循捕获的路由**——只有支持图片的模型会把持久 `ImageBlock` 引用转换为路由专用请求版本;纯文本模型接收稳定占位符。
110
+ - **协议顺序**——`usage` 先于 `finish`,工具参数保持原始 JSON 字符串,终止 `finish` 之后不再有任何内容。
111
+ - **注册表变更具有原子性**——路由与目录注册会在任何变动前整体校验候选集合,因此被拒绝的变更会让此前状态继续服务。
112
+
113
+ </details>
114
+
115
+ -----
116
+
117
+ <a id="further-exploration"></a>
118
+ ## 进一步探索
119
+
120
+ 当包级约定不够用时阅读以下页面。它们从共享类型逐步进入具体适配器、重试执行器与计量服务。
121
+
122
+ - [LLM 流式子系统](../../../docs/subsystems/llm-streaming.zh.md)——消息与块类型、组装后的模型请求、`StreamChunk` 协议与适配器约定。
123
+ - [llm-deepseek 适配器](../llm-deepseek/README.zh.md)——DeepSeek chat-completions 直连实现。
124
+ - [llm-pi-ai 适配器](../llm-pi-ai/README.zh.md)——基于 pi-ai 的多提供方实现。
125
+ - [llm-retry](../llm-retry/README.zh.md)——重跑失败模型请求的重试执行器。
126
+ - [Token 计量](../token-meter/README.zh.md)——具备回放感知的请求与上下文压力测量。
127
+ - [孪生 LLM 适配器](../../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md)——为什么 DeepSeek 路由交付两个结构不同的适配器。
128
+ - [LLM 流终止失败](../../../.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md)——模型请求结果与插件失败之间的服务边界。
129
+
130
+ -----
131
+
132
+ <a id="model-experience"></a>
133
+ ## 模型体验
134
+
135
+ 没有直接影响,因为 LLM 服务不添加内容;适配器决定何时添加本包导出的共享图片描述符与逐图片占位符。
136
+
137
+ #### KV Cache 影响
138
+
139
+ 推理强度的具体化会保留已组装请求前缀。图片身份与请求预览文本是确定性的,可选执行世界路径则按请求解析;路径变化或图片卸载边界变化可能从该图片起阻止复用。
140
+
141
+ ## 已知限制与延期工作
142
+
143
+ <a id="known-limitations-and-deferred-work"></a>
144
+
145
+
146
+ 这些限制说明本服务在哪里停止、由其他包或未来工作接续。它们是当前包约束,不是任务积压。
147
+
148
+ - **本服务不提供重试执行、缓存或速率限制**——提供方注册会存储重试策略,但一次流仍是一次提供方尝试;`@x1a0f3n9/dsh-llm-retry` 在持久 agent 步骤边界上执行该策略。
149
+ - **`GenerateOptions` 采样只包含 `temperature`/`maxTokens`/`stop`**——没有 `tool_choice`、`top_p` 或 penalty 字段;有产生方落地时词汇才会增长(见[已删除惰性旋钮](../../../.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md))。
150
+ - **只有出现实际产生方后,相应变体才会加入**——`prefill`、逐工具 `strict`、内容块 `cache` 提示和 `agent` 消息来源变体都没有产生方(见 [Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md))。
151
+ - **`BlockAssembler` 只处理核心块类型**——插件添加块类型的流若从未由 `block-end` 关闭,`blocks()` 会抛出异常。
152
+ - **`GenerateOptions.sessionId` 是本地声明的品牌类型**——导入 dsh-session 的 `SessionId` 会产生依赖循环。
153
+
154
+ <a id="dev-note"></a>
155
+ ### 开发备注
156
+
157
+ <details>
158
+ <summary>维护者的工作上下文——点击展开</summary>
159
+
160
+ 本开发备注是不具权威性的工作上下文:开放问题与尚未决定的探索方向。已交付的行为与既定理由以上文、包代码和相关 Agent Note 为准。
161
+
162
+ #### 开放事项
163
+
164
+ - `GenerateOptions.sessionId` 是本地声明的品牌类型,因为导入 dsh-session 的 `SessionId` 会造成依赖循环;未来拥有 id 的包可以消除该权宜之计。
165
+ - 推理强度标识符是由适配器定义的不透明字符串,只对照各适配器公布集合解析;跨适配器共享强度词汇尚未决定。
166
+ - `llm/adapters-updated` 事件按设计不携带载荷;消费方重新读取注册表,而不是在事件中接收新拓扑。
167
+
168
+ </details>