@deepseek-ai/dsh-llm 0.1.6-alpha.2 → 0.1.7-alpha.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 +2 -2
- package/README.md +11 -8
- package/README.zh.md +11 -8
- package/lib/index.js +41 -97
- package/lib/typert.host.js +69 -13
- package/lib/typert.remote-client.js +3 -3
- package/lib/types/assembler.d.ts +3 -3
- package/lib/types/assembler.js +4 -4
- package/lib/types/content.d.ts +30 -11
- package/lib/types/content.js +12 -64
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.js +3 -1
- package/lib/types/message.d.ts +69 -37
- package/lib/types/message.js +21 -16
- package/lib/types/types.d.ts +39 -12
- package/package.json +11 -11
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
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:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 04de24592379dcb0d815f3b033ca2f9f22e2ff6a
|
|
6
|
+
README.zh.md: bee35d4a0b62dd61a61b435dfc4eb27b62cdce69
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
Use `@deepseek-ai/dsh-llm` to stream model calls through configured provider adapters, discover models, and resolve model capabilities and call defaults.
|
|
12
|
+
Use `@deepseek-ai/dsh-llm` to stream model calls through configured provider adapters, discover models, and resolve model capabilities and call defaults. Callers must keep all model-visible input reconstructable from the session log. Loop-built requests arrive deep-frozen, so extensions and adapters cannot rewrite them. Each stream is one provider attempt: provider-specific translation stays with its adapter, while the optional `@deepseek-ai/dsh-llm-retry` package re-runs failed requests. Streams always end with a terminal result, so callers can handle success, failure, and cancellation consistently.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -48,7 +48,7 @@ A stream returns token-level chunks and always ends with one terminal `finish` c
|
|
|
48
48
|
for await (const chunk of ctx.llm.stream({
|
|
49
49
|
provider: 'deepseek-official',
|
|
50
50
|
model: 'deepseek-v4-flash',
|
|
51
|
-
messages: [
|
|
51
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }],
|
|
52
52
|
})) {
|
|
53
53
|
// chunks: block-start, text-delta, ..., usage, finish
|
|
54
54
|
}
|
|
@@ -56,6 +56,8 @@ for await (const chunk of ctx.llm.stream({
|
|
|
56
56
|
|
|
57
57
|
After a successful mount, `ctx.llm.listProviders()` reports the registered routes in registration order.
|
|
58
58
|
|
|
59
|
+
`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.
|
|
60
|
+
|
|
59
61
|
### What you can do
|
|
60
62
|
|
|
61
63
|
- **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`.
|
|
@@ -81,7 +83,7 @@ This section explains the design behind the service; the observable behavior is
|
|
|
81
83
|
|
|
82
84
|
### Design philosophy
|
|
83
85
|
|
|
84
|
-
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
|
|
86
|
+
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 an agent-loop 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.
|
|
85
87
|
|
|
86
88
|
### Source map
|
|
87
89
|
|
|
@@ -101,13 +103,13 @@ The service is built on one separation: **the logical contract is provider-neutr
|
|
|
101
103
|
|
|
102
104
|
### Main flow
|
|
103
105
|
|
|
104
|
-
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
|
|
106
|
+
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.
|
|
105
107
|
|
|
106
|
-
File detection reads current content, including
|
|
108
|
+
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.
|
|
107
109
|
|
|
108
110
|
### Invariants
|
|
109
111
|
|
|
110
|
-
- **Model-visible ⟺ logged** —
|
|
112
|
+
- **Model-visible ⟺ logged** — callers must keep every provider request's model-visible input reconstructable from the session log; loop-built requests arrive deep-frozen and cannot be rewritten.
|
|
111
113
|
- **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.
|
|
112
114
|
- **Prepared calls are one-shot** — a prepared call can be dispatched exactly once, and its call-config fields must match the prepared config.
|
|
113
115
|
- **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.
|
|
@@ -125,7 +127,7 @@ File detection reads current content, including nested tool results, on every re
|
|
|
125
127
|
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.
|
|
126
128
|
|
|
127
129
|
- [LLM streaming subsystem](../../../docs/subsystems/llm-streaming.md) — the message and block types, compact Assistant stream records, the `StreamChunk` protocol, and the adapter contract.
|
|
128
|
-
- [llm-deepseek adapter](../llm-deepseek/README.md) — the direct DeepSeek
|
|
130
|
+
- [llm-deepseek adapter](../llm-deepseek/README.md) — the direct DeepSeek Messages implementation.
|
|
129
131
|
- [llm-pi-ai adapter](../llm-pi-ai/README.md) — the pi-ai-backed multi-provider implementation.
|
|
130
132
|
- [llm-retry](../llm-retry/README.md) — the retry executor that re-runs failed model requests.
|
|
131
133
|
- [Token meter](../token-meter/README.md) — replay-aware request and context pressure measurement.
|
|
@@ -152,9 +154,10 @@ These limits define where this service stops and other packages or future work b
|
|
|
152
154
|
|
|
153
155
|
- **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; `@deepseek-ai/dsh-llm-retry` executes the policy at durable agent-step boundaries.
|
|
154
156
|
- **`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)).
|
|
155
|
-
- **
|
|
157
|
+
- **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)).
|
|
156
158
|
- **`BlockAssembler` handles core block kinds only** — a plugin-added block type whose stream is never closed by `block-end` makes `blocks()` throw.
|
|
157
159
|
- **`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.
|
|
158
161
|
|
|
159
162
|
<a id="dev-note"></a>
|
|
160
163
|
### Dev Note
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
使用 `@deepseek-ai/dsh-llm`
|
|
12
|
+
使用 `@deepseek-ai/dsh-llm` 可通过已配置的提供方适配器流式调用模型、发现模型,并解析模型能力与调用默认值。调用方必须确保所有模型可见输入都可以从会话日志重建。Loop 构建的请求以深度冻结状态到达,因此扩展与适配器不能改写。每个流只尝试调用提供方一次:提供方特定的转换由对应适配器完成,可选包 `@deepseek-ai/dsh-llm-retry` 负责重跑失败的请求。流始终以终止结果结束,因此调用方可以一致地处理成功、失败与取消。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -48,7 +48,7 @@ kind: "package-reference"
|
|
|
48
48
|
for await (const chunk of ctx.llm.stream({
|
|
49
49
|
provider: 'deepseek-official',
|
|
50
50
|
model: 'deepseek-v4-flash',
|
|
51
|
-
messages: [
|
|
51
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }],
|
|
52
52
|
})) {
|
|
53
53
|
// chunks: block-start, text-delta, ..., usage, finish
|
|
54
54
|
}
|
|
@@ -56,6 +56,8 @@ for await (const chunk of ctx.llm.stream({
|
|
|
56
56
|
|
|
57
57
|
挂载成功后,`ctx.llm.listProviders()` 会按注册顺序报告已注册路由。
|
|
58
58
|
|
|
59
|
+
`GenerateOptions.messages` 接受持久 `Message` 值和仅供请求使用的 `RequestUserInput` 值。仅供请求使用的输入包含 user-role 内容,不含 `id` 或 `source`;Session 写入和 Agent 投递仍然要求持久消息。调用方必须在流结束前保持辅助输入不变。会记录完整请求的调用方(例如会话标题生成)必须使用持久消息。
|
|
60
|
+
|
|
59
61
|
### 你可以做什么
|
|
60
62
|
|
|
61
63
|
- **流式发起一次模型调用**——`ctx.llm.stream(options)` 为任何已注册提供方与模型产出原始分片(token 级增量);消费方用 `BlockAssembler` 组装。
|
|
@@ -81,7 +83,7 @@ for await (const chunk of ctx.llm.stream({
|
|
|
81
83
|
|
|
82
84
|
### 设计理念
|
|
83
85
|
|
|
84
|
-
本服务基于一项职责分离原则:**逻辑约定是提供方无关的,适配器拥有协议。** 它一次性地定义规范消息、内容块与流式分片词汇,每个提供方适配器只把自己的协议格式翻译为该词汇。注册表是拓扑的拥有者——适配器路由、可配置提供方条目与发现 offer 都在这里注册,并随其 fiber 一起 dispose
|
|
86
|
+
本服务基于一项职责分离原则:**逻辑约定是提供方无关的,适配器拥有协议。** 它一次性地定义规范消息、内容块与流式分片词汇,每个提供方适配器只把自己的协议格式翻译为该词汇。注册表是拓扑的拥有者——适配器路由、可配置提供方条目与发现 offer 都在这里注册,并随其 fiber 一起 dispose(资源释放)——而 agent loop 的请求始终是会话日志的纯函数:loop 构建的请求以深度冻结状态到达,因此监听器与适配器只能读取,绝不能改写。
|
|
85
87
|
|
|
86
88
|
### 源码地图
|
|
87
89
|
|
|
@@ -101,13 +103,13 @@ for await (const chunk of ctx.llm.stream({
|
|
|
101
103
|
|
|
102
104
|
### 主流程
|
|
103
105
|
|
|
104
|
-
请求会对照其精确模型的能力校验,包括上下文窗口、输出默认值、推理强度、输入模态与 `systemPromptUpdate`
|
|
106
|
+
请求会对照其精确模型的能力校验,包括上下文窗口、输出默认值、推理强度、输入模态与 `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` 分片到达消费方。
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
文件检测在每次请求时读取当前内容,包括 tool-role 结果内容,不缓存消息身份或冻结状态。[文件扫描决策](../../../.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md)记录了实测遍历成本。
|
|
107
109
|
|
|
108
110
|
### 不变式
|
|
109
111
|
|
|
110
|
-
- **模型可见 ⟺
|
|
112
|
+
- **模型可见 ⟺ 已记录**——调用方必须确保每个提供方请求中的模型可见输入都可以从会话日志重建;loop 构建的请求以深度冻结状态到达,不可改写。
|
|
111
113
|
- **回放状态只在同一适配器内流动**——仅当同一适配器实例同时拥有历史路由与目标路由时,assistant 回放状态才会随行;否则在分发前被丢弃。
|
|
112
114
|
- **已准备调用是一次性的**——已准备调用只能分发一次,且其调用配置字段必须与准备好的配置一致。
|
|
113
115
|
- **图片投影遵循捕获的路由**——只有支持图片的模型会把持久 `ImageBlock` 引用转换为路由专用请求版本;纯文本模型接收稳定占位符。
|
|
@@ -125,7 +127,7 @@ for await (const chunk of ctx.llm.stream({
|
|
|
125
127
|
当包级约定不够用时阅读以下页面。它们从共享类型逐步进入具体适配器、重试执行器与计量服务。
|
|
126
128
|
|
|
127
129
|
- [LLM(大语言模型)流式子系统](../../../docs/subsystems/llm-streaming.zh.md)——消息与块类型、紧凑的 Assistant 流记录、`StreamChunk` 协议与适配器约定。
|
|
128
|
-
- [llm-deepseek 适配器](../llm-deepseek/README.zh.md)——DeepSeek
|
|
130
|
+
- [llm-deepseek 适配器](../llm-deepseek/README.zh.md)——DeepSeek Messages 直连实现。
|
|
129
131
|
- [llm-pi-ai 适配器](../llm-pi-ai/README.zh.md)——基于 pi-ai 的多提供方实现。
|
|
130
132
|
- [llm-retry](../llm-retry/README.zh.md)——重跑失败模型请求的重试执行器。
|
|
131
133
|
- [Token 计量](../token-meter/README.zh.md)——具备回放感知的请求与上下文压力测量。
|
|
@@ -152,9 +154,10 @@ for await (const chunk of ctx.llm.stream({
|
|
|
152
154
|
|
|
153
155
|
- **本服务不提供重试执行、缓存或速率限制**——提供方注册会存储重试策略,但一次流仍是一次提供方尝试;`@deepseek-ai/dsh-llm-retry` 在持久 agent 步骤边界上执行该策略。
|
|
154
156
|
- **`GenerateOptions` 采样只包含 `temperature`/`maxTokens`/`stop`**——没有 `tool_choice`、`top_p` 或 penalty 字段;有产生方落地时词汇才会增长(见[已删除惰性旋钮](../../../.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md))。
|
|
155
|
-
-
|
|
157
|
+
- **变体通常要求实际产生方**——`prefill`、逐工具 `strict`、内容块 `cache` 提示和 `agent` 消息来源变体都没有产生方(见 [Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md))。
|
|
156
158
|
- **`BlockAssembler` 只处理核心块类型**——插件添加块类型的流若从未由 `block-end` 关闭,`blocks()` 会抛出异常。
|
|
157
159
|
- **`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 事件。普通请求的行为不变。
|
|
158
161
|
|
|
159
162
|
<a id="dev-note"></a>
|
|
160
163
|
### 开发备注
|
package/lib/index.js
CHANGED
|
@@ -8,9 +8,9 @@ import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
|
|
|
8
8
|
//#region lib/types/message.js
|
|
9
9
|
/** Message value types, identity, and immutable construction helpers. */
|
|
10
10
|
/**
|
|
11
|
-
* Bound for a `notice` summary.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Bound for a `notice` summary. Producers commit the one-line account to the
|
|
12
|
+
* durable log; its inputs — task labels, goal objectives, tool arguments —
|
|
13
|
+
* are caller text with no length of their own.
|
|
14
14
|
*/
|
|
15
15
|
const CONTEXT_SUMMARY_MAX_CHARS = 120;
|
|
16
16
|
/**
|
|
@@ -35,9 +35,20 @@ function freezeMessage(message) {
|
|
|
35
35
|
* @returns an immutable message with a fresh stable identity.
|
|
36
36
|
*/
|
|
37
37
|
function createMessage(input) {
|
|
38
|
-
return
|
|
38
|
+
return deepFreeze(structuredClone({
|
|
39
39
|
...input,
|
|
40
40
|
id: brandString(randomUUID())
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create an identified, immutable developer message.
|
|
45
|
+
* @param input - content and producer source for the new message.
|
|
46
|
+
* @returns a detached developer message with a fresh identity.
|
|
47
|
+
*/
|
|
48
|
+
function createDeveloperMessage(input) {
|
|
49
|
+
return createMessage({
|
|
50
|
+
...input,
|
|
51
|
+
role: "developer"
|
|
41
52
|
});
|
|
42
53
|
}
|
|
43
54
|
/**
|
|
@@ -70,39 +81,33 @@ function createAssistantMessage(input) {
|
|
|
70
81
|
* Create and freeze one identified system-role message holding a rendered
|
|
71
82
|
* system prompt.
|
|
72
83
|
* @param text - the complete rendered prompt; `''` records "no system prompt".
|
|
73
|
-
* @param plugin - the plugin that assembled the prompt.
|
|
74
84
|
* @returns an immutable system message with a fresh stable identity.
|
|
75
85
|
*/
|
|
76
|
-
function createSystemMessage(text
|
|
86
|
+
function createSystemMessage(text) {
|
|
77
87
|
return createMessage({
|
|
78
88
|
role: "system",
|
|
79
89
|
content: text.length === 0 ? [] : [{
|
|
80
90
|
type: "text",
|
|
81
91
|
text
|
|
82
92
|
}],
|
|
83
|
-
source: {
|
|
84
|
-
kind: "plugin",
|
|
85
|
-
plugin
|
|
86
|
-
}
|
|
93
|
+
source: { kind: "system-prompt" }
|
|
87
94
|
});
|
|
88
95
|
}
|
|
89
96
|
/**
|
|
90
97
|
* Create and freeze one identified tool-result message.
|
|
91
98
|
* @param input - call identity, raw result blocks, and outcome.
|
|
92
|
-
* @returns an immutable
|
|
99
|
+
* @returns an immutable tool-role message that answers the tool call.
|
|
93
100
|
*/
|
|
94
101
|
function createToolResultMessage(input) {
|
|
95
|
-
return
|
|
102
|
+
return createMessage({
|
|
103
|
+
role: "tool",
|
|
96
104
|
source: {
|
|
97
105
|
kind: "tool",
|
|
98
106
|
callId: input.callId
|
|
99
107
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
content: input.content,
|
|
104
|
-
isError: input.isError
|
|
105
|
-
}]
|
|
108
|
+
toolCallId: input.callId,
|
|
109
|
+
content: input.content,
|
|
110
|
+
isError: input.isError
|
|
106
111
|
});
|
|
107
112
|
}
|
|
108
113
|
//#endregion
|
|
@@ -577,25 +582,23 @@ function offloadedImageText(ref, access) {
|
|
|
577
582
|
return `[${identity}${normalizedAccessText(ref, access)}]`;
|
|
578
583
|
}
|
|
579
584
|
/**
|
|
580
|
-
* True when typed model content contains an image block
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
* survey), so a consumer cannot silently diverge on nesting depth.
|
|
585
|
+
* True when typed model content contains an image block. This is the one image
|
|
586
|
+
* walk shared by every image policy (capability gating, text-only
|
|
587
|
+
* serialization, compaction survey), so a consumer cannot silently diverge.
|
|
584
588
|
* @param content - typed model content blocks.
|
|
585
|
-
* @returns whether any
|
|
589
|
+
* @returns whether any block is an image.
|
|
586
590
|
*/
|
|
587
591
|
function contentHasImage(content) {
|
|
588
|
-
return content.some((block) => block.type === "image"
|
|
592
|
+
return content.some((block) => block.type === "image");
|
|
589
593
|
}
|
|
590
594
|
/**
|
|
591
|
-
* True when typed model content contains a file block
|
|
592
|
-
* tool-result content on the same recursion every file policy shares.
|
|
595
|
+
* True when typed model content contains a file block.
|
|
593
596
|
* Reads current content on every call without retaining scan results.
|
|
594
597
|
* @param content - typed model content blocks.
|
|
595
|
-
* @returns whether any
|
|
598
|
+
* @returns whether any block is a file.
|
|
596
599
|
*/
|
|
597
600
|
function contentHasFile(content) {
|
|
598
|
-
for (const block of content) if (block.type === "file"
|
|
601
|
+
for (const block of content) if (block.type === "file") return true;
|
|
599
602
|
return false;
|
|
600
603
|
}
|
|
601
604
|
/**
|
|
@@ -612,7 +615,7 @@ function fileHandleText(ref, readonlyPath) {
|
|
|
612
615
|
if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
|
|
613
616
|
return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
|
|
614
617
|
}
|
|
615
|
-
/** Replace every file occurrence
|
|
618
|
+
/** Replace every file occurrence with handle text. */
|
|
616
619
|
function replaceFilesWithHandles(blocks, resolvePath) {
|
|
617
620
|
let next;
|
|
618
621
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -624,29 +627,10 @@ function replaceFilesWithHandles(blocks, resolvePath) {
|
|
|
624
627
|
});
|
|
625
628
|
continue;
|
|
626
629
|
}
|
|
627
|
-
if (block.type === "tool-result") {
|
|
628
|
-
const content = replaceFilesWithHandles(block.content, resolvePath);
|
|
629
|
-
if (content !== block.content) {
|
|
630
|
-
next ??= blocks.slice(0, index);
|
|
631
|
-
next.push({
|
|
632
|
-
...block,
|
|
633
|
-
content
|
|
634
|
-
});
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
630
|
next?.push(block);
|
|
639
631
|
}
|
|
640
632
|
return next ?? blocks;
|
|
641
633
|
}
|
|
642
|
-
/**
|
|
643
|
-
* Project durable file history into deterministic handle text for every model
|
|
644
|
-
* route. Unlike images, no provider receives file blocks natively, so this
|
|
645
|
-
* projection is unconditional in request assembly.
|
|
646
|
-
* @param messages - complete request history.
|
|
647
|
-
* @param resolvePath - resolve one reference's current execution-world read path.
|
|
648
|
-
* @returns the original list without files, otherwise shallow message copies with handle text.
|
|
649
|
-
*/
|
|
650
634
|
function projectFilesToText(messages, resolvePath) {
|
|
651
635
|
if (!messages.some((message) => contentHasFile(message.content))) return messages;
|
|
652
636
|
return messages.map((message) => {
|
|
@@ -662,16 +646,14 @@ function base64Length(bytes) {
|
|
|
662
646
|
return Math.ceil(bytes / 3) * 4;
|
|
663
647
|
}
|
|
664
648
|
/**
|
|
665
|
-
* Visit every image occurrence of typed content in message order
|
|
666
|
-
* nested tool-result content.
|
|
649
|
+
* Visit every image occurrence of typed content in message order.
|
|
667
650
|
* @param content - typed model content blocks.
|
|
668
651
|
* @param visit - called once per occurrence.
|
|
669
652
|
*/
|
|
670
653
|
function visitImageBlocks(content, visit) {
|
|
671
654
|
for (const block of content) if (block.type === "image") visit(block);
|
|
672
|
-
else if (block.type === "tool-result") visitImageBlocks(block.content, visit);
|
|
673
655
|
}
|
|
674
|
-
/** Replace every offloaded occurrence
|
|
656
|
+
/** Replace every offloaded occurrence with its placeholder. */
|
|
675
657
|
function replaceOffloadedImages(blocks, placeholder) {
|
|
676
658
|
let next;
|
|
677
659
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -683,29 +665,10 @@ function replaceOffloadedImages(blocks, placeholder) {
|
|
|
683
665
|
});
|
|
684
666
|
continue;
|
|
685
667
|
}
|
|
686
|
-
if (block.type === "tool-result") {
|
|
687
|
-
const content = replaceOffloadedImages(block.content, placeholder);
|
|
688
|
-
if (content !== block.content) {
|
|
689
|
-
next ??= blocks.slice(0, index);
|
|
690
|
-
next.push({
|
|
691
|
-
...block,
|
|
692
|
-
content
|
|
693
|
-
});
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
668
|
next?.push(block);
|
|
698
669
|
}
|
|
699
670
|
return next ?? blocks;
|
|
700
671
|
}
|
|
701
|
-
/**
|
|
702
|
-
* Project the surface's offloaded occurrences into deterministic text for one
|
|
703
|
-
* request. The offloaded set is a durable surface fact, so every route sends
|
|
704
|
-
* the same set; only the placeholder text is route-owned.
|
|
705
|
-
* @param messages - derived request history.
|
|
706
|
-
* @param placeholder - build the model-visible replacement for one offloaded attachment.
|
|
707
|
-
* @returns the original list when nothing is offloaded, otherwise shallow message copies with placeholders.
|
|
708
|
-
*/
|
|
709
672
|
function projectOffloadedImages(messages, placeholder) {
|
|
710
673
|
return messages.map((message) => {
|
|
711
674
|
const content = replaceOffloadedImages(message.content, placeholder);
|
|
@@ -761,7 +724,7 @@ function requiredImageOffload(messages, budget, versionBytes) {
|
|
|
761
724
|
});
|
|
762
725
|
return offloadedImagePrefixCount(lengths, budget);
|
|
763
726
|
}
|
|
764
|
-
/** Replace every image occurrence
|
|
727
|
+
/** Replace every image occurrence for a text-only model. */
|
|
765
728
|
function replaceImagesForTextModel(blocks) {
|
|
766
729
|
let next;
|
|
767
730
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -773,26 +736,10 @@ function replaceImagesForTextModel(blocks) {
|
|
|
773
736
|
});
|
|
774
737
|
continue;
|
|
775
738
|
}
|
|
776
|
-
if (block.type === "tool-result") {
|
|
777
|
-
const content = replaceImagesForTextModel(block.content);
|
|
778
|
-
if (content !== block.content) {
|
|
779
|
-
next ??= blocks.slice(0, index);
|
|
780
|
-
next.push({
|
|
781
|
-
...block,
|
|
782
|
-
content
|
|
783
|
-
});
|
|
784
|
-
continue;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
739
|
next?.push(block);
|
|
788
740
|
}
|
|
789
741
|
return next ?? blocks;
|
|
790
742
|
}
|
|
791
|
-
/**
|
|
792
|
-
* Project durable image history into deterministic text for an exact text-only model.
|
|
793
|
-
* @param messages - complete request history.
|
|
794
|
-
* @returns the original list without images, otherwise shallow message copies with stable placeholders.
|
|
795
|
-
*/
|
|
796
743
|
function projectImagesForTextModel(messages) {
|
|
797
744
|
if (!messages.some((message) => contentHasImage(message.content))) return messages;
|
|
798
745
|
return messages.map((message) => {
|
|
@@ -1079,15 +1026,11 @@ var BlockAssembler = class {
|
|
|
1079
1026
|
}
|
|
1080
1027
|
/**
|
|
1081
1028
|
* The assembled assistant message.
|
|
1082
|
-
* @param source -
|
|
1029
|
+
* @param source - provider/model attribution (without the `kind` tag) for the assembled message.
|
|
1083
1030
|
* @returns a frozen assistant-role message over `blocks()` (same open-block assembly rules).
|
|
1084
1031
|
*/
|
|
1085
|
-
message(source
|
|
1086
|
-
|
|
1087
|
-
plugin: "dsh-llm/assembler"
|
|
1088
|
-
}) {
|
|
1089
|
-
return createMessage({
|
|
1090
|
-
role: "assistant",
|
|
1032
|
+
message(source) {
|
|
1033
|
+
return createAssistantMessage({
|
|
1091
1034
|
content: this.blocks(),
|
|
1092
1035
|
source
|
|
1093
1036
|
});
|
|
@@ -2207,8 +2150,9 @@ let LlmRuntime = (() => {
|
|
|
2207
2150
|
/** Remove replay state whose historical route is owned by another adapter. */
|
|
2208
2151
|
forAdapter(options, adapter) {
|
|
2209
2152
|
const messages = options.messages.map((message) => {
|
|
2153
|
+
if (message.role !== "assistant") return message;
|
|
2210
2154
|
const source = message.source;
|
|
2211
|
-
if (
|
|
2155
|
+
if (source.replayState === void 0) return message;
|
|
2212
2156
|
if (this.adapters.get(source.provider)?.adapter === adapter) return message;
|
|
2213
2157
|
return freezeMessage({
|
|
2214
2158
|
...message,
|
|
@@ -2348,4 +2292,4 @@ function adapterFailureChunk(error, signal) {
|
|
|
2348
2292
|
};
|
|
2349
2293
|
}
|
|
2350
2294
|
//#endregion
|
|
2351
|
-
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, 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 };
|
|
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 };
|