refactor: move image offload projection into its plugin
This commit is contained in:
1 parent
bc49b2a15c
commit
413ac14b16
73 files changed
+908
-339
No files matched your search
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-10-image-offload-events.md
|
||||
2026-09-10-image-offload-events.md: 0e2dbac4629232c578711c96fa24d8c4d8765f07
|
||||
2026-09-10-image-offload-events.zh.md: 7e8d104b58a9714abd30b5ceb143e54fb6af8f35
|
||||
2026-09-10-image-offload-events.md: f540dc98cee8daf3e360b14234a2ac21d0f53aab
|
||||
2026-09-10-image-offload-events.zh.md: 868fca59ba5fc1941de752127f61b091aedb234f
|
||||
@@ -16,9 +16,9 @@ Summary requests bypass the agent error waterfall. `dsh-compaction-basic` preser
|
||||
|
||||
The event payload is `{ targets: [{ seq, imageIndexes }] }`. Each target identifies a current `user/message` or `tool/result` node. Its strictly increasing, zero-based indexes enumerate every image in depth-first content order, including nested tool results and previously omitted images. Equal attachment ids remain distinct occurrences. Exact selections remain unambiguous after positional replacements reorder the surface; an attachment id or a raw-log prefix cannot identify the selected set.
|
||||
|
||||
The event has no `surfaceOp`. It neither creates a message nor replaces a message node. Session validates all targets before commit and rejects missing or shadowed nodes, output images, duplicate targets, invalid indexes, and already omitted occurrences. Reconstruction derives immutable message copies with `ImageBlock.offloaded: true` at the recorded positions. Original events, message ids, sources, and unaffected blocks remain unchanged. Resume, fork, and replay apply the same selections from the log.
|
||||
The event has no `surfaceOp`. It neither creates a message nor replaces a message node. The plugin's pure projection validates all targets before Session commits the event and rejects missing or shadowed nodes, output images, duplicate targets, invalid indexes, and already omitted occurrences. Reconstruction derives immutable message copies with `ImageBlock.offloaded: true` at the recorded positions. Original events, message ids, sources, and unaffected blocks remain unchanged. Resume, fork, and replay apply the same selections from the log.
|
||||
|
||||
Selection, budgets, and retry policy stay in the compaction plugin. Session owns only durable validation and reconstruction. The instance `deriveEventMessage()` and `deriveMessages()` return projected messages; pure reconstructors pass `foldSurface(events).offloadedMessages` to the exported `deriveEventMessage()`. Rewriters preserving images must consume projected messages, including tool-result pruning and summarizer input.
|
||||
The compaction plugin owns selection, event declaration, image validation and projection, and retry policy. [Plugin-owned message projections](2026-09-11-plugin-owned-message-projections.md) owns the generic Session integration and explicit detached assembly, superseding core-owned interpretation. The instance `deriveEventMessage()` and `deriveMessages()` return projected messages; pure reconstructors pass `foldSurface(events, projections).projectedMessages` to the exported `deriveEventMessage()`. Rewriters preserving images must consume projected messages, including tool-result pruning and summarizer input.
|
||||
|
||||
`contentGeneration` advances for message replacements and image-offload events so cached history and request series are refreshed. `replaceGeneration` advances only for replacements; image omission cannot masquerade as successful text compaction. An unchanged request envelope gets a `request/header` with reason `series`; a simultaneous envelope change carries `startsSeries: true`.
|
||||
|
||||
@@ -42,8 +42,8 @@ Token accounting applies selections to the affected nodes' image occurrences wit
|
||||
|
||||
Omission remains effective when a route budget grows, an inline fallback ends, or compaction reduces context. It does not delete attachment bytes. Reading the placeholder's access path creates a new image occurrence that can be retained independently. The execution-world path is still resolved during serialization, as it is for retained image handles; this event records the selected image set, not filesystem mappings.
|
||||
|
||||
Session adds a required event fold and one content-generation signal. Consumers reconstructing model input must use projected messages. Human views can still show original images from immutable events. The plugin no longer requires token-meter or compaction services merely to record omission.
|
||||
Session invokes the registered pure projection and maintains one content-generation signal. Consumers reconstructing model input must use projected messages. Human views can still show original images from immutable events. The plugin requires neither token-meter nor compaction services merely to record omission.
|
||||
|
||||
## Testing
|
||||
|
||||
Session tests cover nested and repeated occurrences, atomic rejection, cache invalidation, pure folding, restore, fork, and untouched source events. Plugin tests cover consecutive failures, current surface order, exhausted recovery, fresh request headers, and disposal. Token-meter tests preserve node identity and prior usage anchors while repricing only selected occurrences. Tool-result pruning and compaction tests ensure later rewrites consume the projected history. TypeScript and Python SDK expected outputs exercise the required event through shipped profiles.
|
||||
Plugin projection tests cover nested and repeated occurrences, atomic rejection, cache invalidation, pure folding, restore, fork, and untouched source events. Recovery tests cover consecutive failures, current surface order, exhausted recovery, fresh request headers, and disposal. Token-meter tests preserve node identity and prior usage anchors while repricing only selected occurrences. Tool-result pruning and compaction tests ensure later rewrites consume the projected history. TypeScript and Python SDK expected outputs exercise the required event through shipped profiles.
|
||||
@@ -16,9 +16,9 @@ Status: implemented
|
||||
|
||||
事件载荷为 `{ targets: [{ seq, imageIndexes }] }`。每个目标指向当前的 `user/message` 或 `tool/result` 节点。索引从零开始且严格递增,按内容深度优先顺序枚举所有图片,包括嵌套工具结果和先前省略的图片。相同附件 ID 的多次出现分别计数。位置替换改变 surface 顺序后,明确选择仍没有歧义,附件 ID 或原始日志前缀无法标识这个集合。
|
||||
|
||||
事件不带 `surfaceOp`,不创建消息,也不替换消息节点。Session 在提交前校验全部目标,拒绝缺失或已被遮蔽的节点、输出图片、重复目标、无效索引和已被省略的位置。重建过程派生不可变消息副本,将记录的位置标为 `ImageBlock.offloaded: true`。原始事件、消息 ID、来源和未受影响的内容块保持不变。恢复、分叉和回放从日志应用相同的选择。
|
||||
事件不带 `surfaceOp`,不创建消息,也不替换消息节点。插件的纯投影在 Session 提交事件前校验全部目标,拒绝缺失或已被遮蔽的节点、输出图片、重复目标、无效索引和已被省略的位置。重建过程派生不可变消息副本,将记录的位置标为 `ImageBlock.offloaded: true`。原始事件、消息 ID、来源和未受影响的内容块保持不变。恢复、分叉和回放从日志应用相同的选择。
|
||||
|
||||
选择策略、预算和重试策略保留在 compaction 插件中。Session 只负责持久数据校验和重建。实例方法 `deriveEventMessage()` 与 `deriveMessages()` 返回派生消息,纯重建函数将 `foldSurface(events).offloadedMessages` 传给导出的 `deriveEventMessage()`。需要保留图片的改写必须读取派生消息,包括工具结果裁剪和摘要输入。
|
||||
compaction 插件拥有选图、事件声明、图片校验与投影,以及重试策略。[插件拥有消息投影](2026-09-11-plugin-owned-message-projections.zh.md)负责通用 Session 集成和显式独立装配,取代由核心负责解释的职责划分。实例方法 `deriveEventMessage()` 与 `deriveMessages()` 返回派生消息,纯重建函数将 `foldSurface(events, projections).projectedMessages` 传给导出的 `deriveEventMessage()`。需要保留图片的改写必须读取派生消息,包括工具结果裁剪和摘要输入。
|
||||
|
||||
消息替换和图片省略事件都会推进 `contentGeneration`,使历史缓存和请求系列刷新。`replaceGeneration` 只随替换推进,图片省略不能被判断为文本压缩成功。请求配置未变时写入原因是 `series` 的 `request/header`,配置同时变化时携带 `startsSeries: true`。
|
||||
|
||||
@@ -42,8 +42,8 @@ Token 计量将选择应用到受影响节点的图片出现位置,不替换
|
||||
|
||||
路由预算增加、内联回退结束或压缩减少上下文时,省略仍然有效。省略不删除附件字节。读取占位文本中的访问路径会创建新的图片出现位置,可以独立保留。执行环境路径仍在序列化时解析,与保留图片的标识文本一致。这个事件记录选择的图片集合,不记录文件系统映射。
|
||||
|
||||
Session 增加必需事件的折叠和一个内容版本信号。重建模型输入的消费者必须使用派生消息。面向人的视图仍可从不可变事件显示原始图片。插件记录省略时不再需要 token-meter 或 compaction 服务。
|
||||
Session 调用已注册的纯投影,并维护一个内容版本信号。重建模型输入的消费方必须使用派生消息。面向人的视图仍可从不可变事件显示原始图片。插件记录省略时不需要 token-meter 或 compaction 服务。
|
||||
|
||||
## 测试
|
||||
|
||||
Session 测试覆盖嵌套和重复图片、原子拒绝、缓存失效、纯折叠、恢复、分叉及原始事件不变。插件测试覆盖连续失败、当前 surface 顺序、无图可省略时的恢复、新请求头和释放。Token-meter 测试在仅重计所选位置价格时保留节点身份和已有用量锚点。工具结果裁剪和压缩测试保证后续改写读取派生历史。TypeScript 和 Python SDK 预期输出通过发布的 profile 覆盖必需事件。
|
||||
插件的投影测试覆盖嵌套和重复图片、原子拒绝、缓存失效、纯折叠、恢复、分叉及原始事件不变。恢复测试覆盖连续失败、当前 surface 顺序、无图可省略时的恢复、新请求头和释放。Token-meter 测试在仅重计所选位置价格时保留节点身份和已有用量锚点。工具结果裁剪和压缩测试保证后续改写读取派生历史。TypeScript 和 Python SDK 预期输出通过发布的 profile 覆盖必需事件。
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md
|
||||
2026-09-11-plugin-owned-message-projections.md: 2c58904e229bedcd44ba738506a3a98f34740d1c
|
||||
2026-09-11-plugin-owned-message-projections.zh.md: 337308bcc43d80e76653d996143a28b917d93a42
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Note: Plugin-owned message projections
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-11-plugin-owned-message-projections.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The dedicated [image-offload event](2026-09-10-image-offload-events.md) changes derived message content without replacing nodes. Implementing its image traversal and target validation inside Session makes each feature-specific message transformation a core change. The ordinary session-projection registry derives domain state but does not participate in canonical model-history derivation.
|
||||
|
||||
## Decision
|
||||
|
||||
The event-owning plugin supplies a pure `SessionMessageProjection`: one event type and a synchronous interpreter of the preceding history. It validates the complete durable payload and returns immutable updates for existing messages, preserving their identities. Session owns atomic acceptance, shared live and detached folding, and content-generation cache invalidation. It contains no image selection or image projection implementation.
|
||||
|
||||
The `@messageProjection` tag on the owning `SessionEventMap` member generates the required-interpreter inventory. Missing definitions reject append, seeded creation, restore, and pure folding. This inventory does not make unknown third-party event names readable or change `ignorable` compatibility. A projection event cannot also declare a surface operation.
|
||||
|
||||
The compaction-image-offload plugin registers its definition through a fiber-owned `ctx.sessions.registerMessageProjection()` effect. Each event has one registered owner. Removing a definition invalidates pending committed decisions and cached reads that used it; a replacement definition requires restoring the session. Rejected, uncommitted candidates retain no interpreter dependency.
|
||||
|
||||
Detached readers pass definitions explicitly. The current Session format catalog assembles the same browser-safe plugin exports for persistence validation, offline queries, and browser fixture replay. This static assembly does not mount recovery listeners. Live sessions use the registered composition, while compaction invariants borrow that composition's definitions. No process-global registry or import-time registration is involved.
|
||||
|
||||
The image event payload and required-on-read semantics remain owned by the image-offload note. Its indexes, selection policy, retry behavior, and SDK recordings are unchanged; this decision partially supersedes that note's core-owned interpretation.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep image interpretation in Session.** That gives detached callers an implicit built-in, but turns a plugin-owned transformation into core event-specific code. Explicit assembly preserves deterministic replay without assigning the algorithm to core.
|
||||
|
||||
**Use the ordinary session-projection registry.** Its folds expose domain state after commit. They cannot veto an invalid append or supply canonical `deriveMessages()` content without another integration point.
|
||||
|
||||
**Transform only the outgoing request.** Compaction, fork, offline replay, and request invariants also derive messages. A send-time transformation does not cover these consumers.
|
||||
|
||||
## Consequences
|
||||
|
||||
Adding a content-changing event requires its declaration, pure interpreter, live registration, and detached assembly entry. Ordinary log-only events need none of this. Consumers of the pure fold use the generic `projectedMessages` result. The event envelope and stored image decisions remain unchanged, so no structural Session format version is added.
|
||||
|
||||
Tests cover missing interpreters, generic atomic projection, loaded windows, restore and fork, duplicate registration, fiber disposal, cached and pending reads, and rejected candidates. Image-specific tests remain with their plugin. The catalog checks that every generated required type has a detached interpreter and rejects invalid image references. Existing TypeScript and Python image SDK recordings exercise the same durable event through shipped profiles.
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Agent Note: 插件拥有消息投影
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-11-plugin-owned-message-projections.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
独立的[图片省略事件](2026-09-10-image-offload-events.zh.md)修改派生消息内容,不替换节点。如果图片遍历和目标校验在 Session 内实现,每种功能专用的消息转换都需要修改核心代码。普通的 session-projection 注册表派生领域状态,不参与模型历史的标准派生过程。
|
||||
|
||||
## 决策
|
||||
|
||||
事件所属的插件提供纯 `SessionMessageProjection`,包含一个事件类型和同步解释此前历史的处理器。它校验完整的持久载荷,返回现有消息的不可变更新,并保留消息身份。Session 负责原子接受、实时与独立回放共用的折叠,以及内容版本驱动的缓存失效。它不包含选图或图片投影实现。
|
||||
|
||||
所属 `SessionEventMap` 成员上的 `@messageProjection` 标记生成必需处理器清单。缺少处理器时,追加、带历史的创建、恢复和纯折叠都会被拒绝。这份清单不会让未知的第三方事件名变得可读,也不改变 `ignorable` 的兼容规则。投影事件不能同时声明 surface 操作。
|
||||
|
||||
compaction-image-offload 插件通过 fiber 拥有的 `ctx.sessions.registerMessageProjection()` effect 注册处理器。每种事件只有一个注册者。移除处理器后,尚未应用的已提交决策和使用过它的缓存读取都会失效。替换处理器需要重新恢复会话。被拒绝且未提交的候选事件不会保留对处理器的依赖。
|
||||
|
||||
独立读取器显式传入处理器。当前 Session 格式目录装配相同的浏览器安全插件导出,供持久数据校验、离线查询和浏览器 fixture 回放使用。这项静态装配不挂载恢复监听器。实时会话使用已注册的组合,压缩 invariant 借用该组合的处理器。不使用进程全局注册表,也不在导入模块时注册。
|
||||
|
||||
图片事件载荷和读取时必须识别的语义仍由图片省略记录负责。索引、选图策略、重试行为和 SDK 录制保持不变。本决策部分取代该记录中由核心负责解释事件的职责划分。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留 Session 内的图片解释逻辑。** 独立调用者可以隐式获得内置处理器,但插件拥有的转换会变成核心中特定事件的代码。显式装配能够保留确定性回放,无需让核心拥有算法。
|
||||
|
||||
**使用普通的 session-projection 注册表。** 它的折叠在提交后公开领域状态。没有额外集成点,它无法否决无效追加,也无法提供标准 `deriveMessages()` 内容。
|
||||
|
||||
**只转换发出的请求。** 压缩、分叉、离线回放和请求 invariant 也会派生消息。发送时转换无法覆盖这些消费方。
|
||||
|
||||
## 影响
|
||||
|
||||
增加修改内容的事件,需要事件声明、纯处理器、实时注册和独立装配项。普通的仅记录日志事件不需要这些机制。纯折叠的消费方使用通用的 `projectedMessages` 结果。事件信封和已经存储的图片决策保持不变,因此不增加结构性的 Session 格式版本。
|
||||
|
||||
测试覆盖缺少处理器、通用原子投影、已加载窗口、恢复与分叉、重复注册、fiber 释放、缓存与待应用读取,以及被拒绝的候选事件。图片专用测试保留在插件包。目录检查每种生成的必需类型都有独立处理器,并拒绝无效图片引用。现有 TypeScript 和 Python 图片 SDK 录制通过发布的 profile 覆盖相同的持久事件。
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/architecture.md
|
||||
architecture.md: e2fa9f3e92b2ffca8d2ae252eb396314f874d375
|
||||
architecture.zh.md: 5ef3e0a834fc7efc3b9018457572470d3b989c73
|
||||
architecture.md: d6187c120f925059be3e094fc55e78d6dec04a5a
|
||||
architecture.zh.md: 078d99dc06cf49233d0ca4c036bab89a3f664d57
|
||||
@@ -118,7 +118,7 @@ The session log is the source of the context the model sees. `deriveMessages()`
|
||||
|
||||
Session consumers know only the current logical format. Header-only `stat` and `list` rescan each Session directory, select its numerically highest canonical generation, and translate a supported historical header without loading events or publishing a successor. A stored-session `open` selects that same generation, refuses a future version, or decodes and composes the static adjacent migration chain once before returning validated current logical events. A read open uses that in-memory result without publishing a successor; a write open first encodes, verifies, and exclusively publishes the final version-named successor beside the unchanged source. Ordinary repair of an unsealed interrupted tail remains a handle consumer responsibility; migration inserts a missing interrupted `turn/end` only for the bounded released restart already sealed by a later `turn/start`. JSONL v0 uses `session.jsonl[.zstd]`, v1 and later use lowercase `session.vN.jsonl[.zstd]`, and committed generation paths are never renamed, replaced, or deleted. The JSONL provider owns physical framing, compression, generation selection, and exclusive publication, while each adjacent migration package owns exactly one `vN -> vN+1` step ([decision](../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)).
|
||||
|
||||
**Model-visible means logged.** Anything that reaches a model request must be reconstructable from the log, and a runtime invariant asserts it. This is why a new model-visible input requires a new session event: extend `SessionEventMap` and render from the log.
|
||||
**Model-visible means logged.** Anything that reaches a model request must be reconstructable from the log, and a runtime invariant asserts it. A new model-visible input requires a session event. Plugins that change existing message content register [pure message projections](subsystems/session.md#plugin-owned-message-projections); detached readers supply the same definitions explicitly.
|
||||
|
||||
**Projection seam.** `dsh-session-projection` owns `ctx.sessionProjections`: registered units fold committed events incrementally, host consumers read one typed state with `stateOf()`, and carriers batch cropped client views with `snapshot()`. A host reader either requires this service during activation or fails explicitly when the registry or required key is absent. Contributors may retain `ctx.inject(['sessionProjections'], ...)` registration without silently defaulting a missing host value. The agent loop registers shared `turnBoundary` state for its readers ([decision](../.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md)).
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ turn/end
|
||||
|
||||
Session 消费方只了解当前逻辑格式。仅 header 的 `stat` 与 `list` 会重新扫描每个 Session 目录,选择数值最高的规范 generation,并在不加载事件或发布后继的情况下转换受支持的历史 header。已存储 Session 的 `open` 选择同一 generation,拒绝未来版本,或只 Decode 并组合一次构建时静态确定的相邻迁移链,再返回经过校验的当前逻辑事件。只读 open 直接使用这份内存结果,不发布后继;写 open 则先编码、校验并在未改变源的旁边排他发布最终版本命名的后继。未被后续事件封住的普通中断尾部仍由句柄消费方修复;只有在后续 `turn/start` 已经封住一种有限的已发布 restart 时,migration 才会插入缺失的 interrupted `turn/end`。JSONL v0 使用 `session.jsonl[.zstd]`,v1 及后续版本使用小写 `session.vN.jsonl[.zstd]`;已提交 generation 路径绝不重命名、替换或删除。JSONL provider 负责物理 framing、压缩、generation 选择与排他发布,每个相邻迁移包只负责一个 `vN -> vN+1` 步骤([决策](../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。
|
||||
|
||||
**模型可见即已记录。** 抵达模型请求的一切都必须能从日志重建,并由一项运行时不变量断言这一点。因此,新增一项模型可见输入就需要新增一个会话事件:扩展 `SessionEventMap` 并从日志渲染。
|
||||
**模型可见即已记录。** 抵达模型请求的一切都必须能从日志重建,并由一项运行时不变量断言这一点。新增模型可见输入需要一个会话事件。修改现有消息内容的插件注册[纯消息投影](subsystems/session.zh.md#plugin-owned-message-projections),独立读取器显式传入相同的处理器。
|
||||
|
||||
**投影 seam。** `dsh-session-projection` 提供 `ctx.sessionProjections`:已注册单元增量折叠已提交事件,host 消费方通过 `stateOf()` 读取单个类型化状态,载体通过 `snapshot()` 批量取得裁剪后的客户端视图。host 读取方要么在激活时要求该服务,要么在注册表或必需 key 缺席时明确失败。贡献方可以保留 `ctx.inject(['sessionProjections'], ...)` 注册,但不能为缺失的 host 值静默提供默认值。agent loop 为读取方注册共享的 `turnBoundary` 状态([决策](../.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.zh.md))。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/module-graph.md
|
||||
module-graph.md: 464614c2f00002cd66b1acde8bb6c9f41175c72e
|
||||
module-graph.zh.md: c6a401f534700f22deca036bc0df29af670bf957
|
||||
module-graph.md: 51732a536a5ff6f063e27144d22a0d8e8a66f6bd
|
||||
module-graph.zh.md: 72edfa6c295a59e4f078b818ceac399d93c8bdb6
|
||||
+29
-28
@@ -698,11 +698,6 @@ flowchart TD
|
||||
pkg_terminal_bash --> pkg_session_projection
|
||||
pkg_terminal_bash --> pkg_subprocess
|
||||
pkg_terminal_bash --> pkg_terminal
|
||||
pkg_token_meter --> pkg_compaction
|
||||
pkg_token_meter --> pkg_llm
|
||||
pkg_token_meter --> pkg_llm_retry
|
||||
pkg_token_meter --> pkg_session
|
||||
pkg_token_meter --> pkg_session_projection
|
||||
pkg_agent_loop --> pkg_agent
|
||||
pkg_agent_loop --> pkg_invariants
|
||||
pkg_agent_loop --> pkg_llm
|
||||
@@ -901,6 +896,12 @@ flowchart TD
|
||||
pkg_plugin_package_inventory_deepseek --> pkg_agent_presets
|
||||
pkg_plugin_package_inventory_deepseek --> pkg_deepseek_llm_api_extensions
|
||||
pkg_plugin_package_inventory_deepseek --> pkg_session
|
||||
pkg_token_meter --> pkg_compaction
|
||||
pkg_token_meter --> pkg_compaction_image_offload
|
||||
pkg_token_meter --> pkg_llm
|
||||
pkg_token_meter --> pkg_llm_retry
|
||||
pkg_token_meter --> pkg_session
|
||||
pkg_token_meter --> pkg_session_projection
|
||||
pkg_session_query --> pkg_brand
|
||||
pkg_session_query --> pkg_llm
|
||||
pkg_session_query --> pkg_session
|
||||
@@ -909,14 +910,6 @@ flowchart TD
|
||||
pkg_session_query --> pkg_session_projection_cache
|
||||
pkg_session_query --> pkg_session_title
|
||||
pkg_session_query --> pkg_tool_todo
|
||||
pkg_acp --> pkg_agent
|
||||
pkg_acp --> pkg_attachment
|
||||
pkg_acp --> pkg_llm
|
||||
pkg_acp --> pkg_mcp_client
|
||||
pkg_acp --> pkg_session
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_token_meter
|
||||
pkg_acp --> pkg_user_approval
|
||||
pkg_api_settings_controller --> pkg_agent_presets
|
||||
pkg_api_settings_controller --> pkg_credentials
|
||||
pkg_api_settings_controller --> pkg_native_command
|
||||
@@ -925,10 +918,6 @@ flowchart TD
|
||||
pkg_api_settings_controller --> pkg_typert_protocol
|
||||
pkg_web_app --> pkg_shell_env
|
||||
pkg_web_app --> pkg_system_prompt
|
||||
pkg_compaction_tool_result_pruner --> pkg_compaction
|
||||
pkg_compaction_tool_result_pruner --> pkg_llm
|
||||
pkg_compaction_tool_result_pruner --> pkg_session
|
||||
pkg_compaction_tool_result_pruner --> pkg_token_meter
|
||||
pkg_tool_cordis --> pkg_agent
|
||||
pkg_tool_cordis --> pkg_cordis_host_runner
|
||||
pkg_tool_cordis --> pkg_llm
|
||||
@@ -1011,13 +1000,18 @@ flowchart TD
|
||||
pkg_tool_session_query --> pkg_system_prompt
|
||||
pkg_tool_session_query --> pkg_timeout
|
||||
pkg_tool_session_query --> pkg_tools
|
||||
pkg_compaction_basic --> pkg_agent
|
||||
pkg_compaction_basic --> pkg_commands
|
||||
pkg_compaction_basic --> pkg_compaction
|
||||
pkg_compaction_basic --> pkg_compaction_tool_result_pruner
|
||||
pkg_compaction_basic --> pkg_llm
|
||||
pkg_compaction_basic --> pkg_session
|
||||
pkg_compaction_basic --> pkg_token_meter
|
||||
pkg_acp --> pkg_agent
|
||||
pkg_acp --> pkg_attachment
|
||||
pkg_acp --> pkg_llm
|
||||
pkg_acp --> pkg_mcp_client
|
||||
pkg_acp --> pkg_session
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_token_meter
|
||||
pkg_acp --> pkg_user_approval
|
||||
pkg_compaction_tool_result_pruner --> pkg_compaction
|
||||
pkg_compaction_tool_result_pruner --> pkg_llm
|
||||
pkg_compaction_tool_result_pruner --> pkg_session
|
||||
pkg_compaction_tool_result_pruner --> pkg_token_meter
|
||||
pkg_session_reference --> pkg_agent
|
||||
pkg_session_reference --> pkg_compaction
|
||||
pkg_session_reference --> pkg_llm
|
||||
@@ -1106,6 +1100,13 @@ flowchart TD
|
||||
pkg_api_session_controller --> pkg_util_values
|
||||
pkg_api_session_controller --> pkg_util_workspace_path
|
||||
pkg_api_session_controller --> pkg_workspace
|
||||
pkg_compaction_basic --> pkg_agent
|
||||
pkg_compaction_basic --> pkg_commands
|
||||
pkg_compaction_basic --> pkg_compaction
|
||||
pkg_compaction_basic --> pkg_compaction_tool_result_pruner
|
||||
pkg_compaction_basic --> pkg_llm
|
||||
pkg_compaction_basic --> pkg_session
|
||||
pkg_compaction_basic --> pkg_token_meter
|
||||
pkg_experimental_agent_team --> pkg_agent
|
||||
pkg_experimental_agent_team --> pkg_invariants
|
||||
pkg_experimental_agent_team --> pkg_llm
|
||||
@@ -1384,7 +1385,6 @@ flowchart TD
|
||||
| [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
|
||||
| [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
|
||||
| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) |
|
||||
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
|
||||
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`agent-tool-presentation`](../packages/core/agent-tool-presentation) | `core` | [`tools`](../packages/core/tools) |
|
||||
| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
@@ -1422,11 +1422,10 @@ flowchart TD
|
||||
| [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) |
|
||||
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
|
||||
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo) |
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`api-settings-controller`](../packages/api/settings-controller) | `api` | [`agent-presets`](../packages/preset/agent-presets), [`credentials`](../packages/credentials/credentials), [`native-command`](../packages/util/native-command), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`web-app`](../packages/bundle/web-app) | `bundle` | [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`host-plugin-inventory`](../packages/host/plugin-inventory) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`brand`](../packages/util/brand), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
|
||||
@@ -1437,7 +1436,8 @@ flowchart TD
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time) |
|
||||
| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) |
|
||||
| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
| [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`webhook-github`](../packages/webhook/webhook-github) | `webhook` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`session`](../packages/core/session), [`webhook`](../packages/webhook/webhook) |
|
||||
| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
|
||||
@@ -1448,6 +1448,7 @@ flowchart TD
|
||||
| [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) |
|
||||
| [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
|
||||
| [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
|
||||
+29
-28
@@ -700,11 +700,6 @@ flowchart TD
|
||||
pkg_terminal_bash --> pkg_session_projection
|
||||
pkg_terminal_bash --> pkg_subprocess
|
||||
pkg_terminal_bash --> pkg_terminal
|
||||
pkg_token_meter --> pkg_compaction
|
||||
pkg_token_meter --> pkg_llm
|
||||
pkg_token_meter --> pkg_llm_retry
|
||||
pkg_token_meter --> pkg_session
|
||||
pkg_token_meter --> pkg_session_projection
|
||||
pkg_agent_loop --> pkg_agent
|
||||
pkg_agent_loop --> pkg_invariants
|
||||
pkg_agent_loop --> pkg_llm
|
||||
@@ -903,6 +898,12 @@ flowchart TD
|
||||
pkg_plugin_package_inventory_deepseek --> pkg_agent_presets
|
||||
pkg_plugin_package_inventory_deepseek --> pkg_deepseek_llm_api_extensions
|
||||
pkg_plugin_package_inventory_deepseek --> pkg_session
|
||||
pkg_token_meter --> pkg_compaction
|
||||
pkg_token_meter --> pkg_compaction_image_offload
|
||||
pkg_token_meter --> pkg_llm
|
||||
pkg_token_meter --> pkg_llm_retry
|
||||
pkg_token_meter --> pkg_session
|
||||
pkg_token_meter --> pkg_session_projection
|
||||
pkg_session_query --> pkg_brand
|
||||
pkg_session_query --> pkg_llm
|
||||
pkg_session_query --> pkg_session
|
||||
@@ -911,14 +912,6 @@ flowchart TD
|
||||
pkg_session_query --> pkg_session_projection_cache
|
||||
pkg_session_query --> pkg_session_title
|
||||
pkg_session_query --> pkg_tool_todo
|
||||
pkg_acp --> pkg_agent
|
||||
pkg_acp --> pkg_attachment
|
||||
pkg_acp --> pkg_llm
|
||||
pkg_acp --> pkg_mcp_client
|
||||
pkg_acp --> pkg_session
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_token_meter
|
||||
pkg_acp --> pkg_user_approval
|
||||
pkg_api_settings_controller --> pkg_agent_presets
|
||||
pkg_api_settings_controller --> pkg_credentials
|
||||
pkg_api_settings_controller --> pkg_native_command
|
||||
@@ -927,10 +920,6 @@ flowchart TD
|
||||
pkg_api_settings_controller --> pkg_typert_protocol
|
||||
pkg_web_app --> pkg_shell_env
|
||||
pkg_web_app --> pkg_system_prompt
|
||||
pkg_compaction_tool_result_pruner --> pkg_compaction
|
||||
pkg_compaction_tool_result_pruner --> pkg_llm
|
||||
pkg_compaction_tool_result_pruner --> pkg_session
|
||||
pkg_compaction_tool_result_pruner --> pkg_token_meter
|
||||
pkg_tool_cordis --> pkg_agent
|
||||
pkg_tool_cordis --> pkg_cordis_host_runner
|
||||
pkg_tool_cordis --> pkg_llm
|
||||
@@ -1013,13 +1002,18 @@ flowchart TD
|
||||
pkg_tool_session_query --> pkg_system_prompt
|
||||
pkg_tool_session_query --> pkg_timeout
|
||||
pkg_tool_session_query --> pkg_tools
|
||||
pkg_compaction_basic --> pkg_agent
|
||||
pkg_compaction_basic --> pkg_commands
|
||||
pkg_compaction_basic --> pkg_compaction
|
||||
pkg_compaction_basic --> pkg_compaction_tool_result_pruner
|
||||
pkg_compaction_basic --> pkg_llm
|
||||
pkg_compaction_basic --> pkg_session
|
||||
pkg_compaction_basic --> pkg_token_meter
|
||||
pkg_acp --> pkg_agent
|
||||
pkg_acp --> pkg_attachment
|
||||
pkg_acp --> pkg_llm
|
||||
pkg_acp --> pkg_mcp_client
|
||||
pkg_acp --> pkg_session
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_token_meter
|
||||
pkg_acp --> pkg_user_approval
|
||||
pkg_compaction_tool_result_pruner --> pkg_compaction
|
||||
pkg_compaction_tool_result_pruner --> pkg_llm
|
||||
pkg_compaction_tool_result_pruner --> pkg_session
|
||||
pkg_compaction_tool_result_pruner --> pkg_token_meter
|
||||
pkg_session_reference --> pkg_agent
|
||||
pkg_session_reference --> pkg_compaction
|
||||
pkg_session_reference --> pkg_llm
|
||||
@@ -1108,6 +1102,13 @@ flowchart TD
|
||||
pkg_api_session_controller --> pkg_util_values
|
||||
pkg_api_session_controller --> pkg_util_workspace_path
|
||||
pkg_api_session_controller --> pkg_workspace
|
||||
pkg_compaction_basic --> pkg_agent
|
||||
pkg_compaction_basic --> pkg_commands
|
||||
pkg_compaction_basic --> pkg_compaction
|
||||
pkg_compaction_basic --> pkg_compaction_tool_result_pruner
|
||||
pkg_compaction_basic --> pkg_llm
|
||||
pkg_compaction_basic --> pkg_session
|
||||
pkg_compaction_basic --> pkg_token_meter
|
||||
pkg_experimental_agent_team --> pkg_agent
|
||||
pkg_experimental_agent_team --> pkg_invariants
|
||||
pkg_experimental_agent_team --> pkg_llm
|
||||
@@ -1386,7 +1387,6 @@ flowchart TD
|
||||
| [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
|
||||
| [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
|
||||
| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) |
|
||||
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
|
||||
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`agent-tool-presentation`](../packages/core/agent-tool-presentation) | `core` | [`tools`](../packages/core/tools) |
|
||||
| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
@@ -1424,11 +1424,10 @@ flowchart TD
|
||||
| [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) |
|
||||
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
|
||||
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo) |
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`api-settings-controller`](../packages/api/settings-controller) | `api` | [`agent-presets`](../packages/preset/agent-presets), [`credentials`](../packages/credentials/credentials), [`native-command`](../packages/util/native-command), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`web-app`](../packages/bundle/web-app) | `bundle` | [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`host-plugin-inventory`](../packages/host/plugin-inventory) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`brand`](../packages/util/brand), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
|
||||
@@ -1439,7 +1438,8 @@ flowchart TD
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time) |
|
||||
| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) |
|
||||
| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
| [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`webhook-github`](../packages/webhook/webhook-github) | `webhook` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`session`](../packages/core/session), [`webhook`](../packages/webhook/webhook) |
|
||||
| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
|
||||
@@ -1450,6 +1450,7 @@ flowchart TD
|
||||
| [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) |
|
||||
| [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
|
||||
| [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md
|
||||
persistence-catalog.md: 69982dcf3cb162bc95d7b9f1c3bc2375977ca33d
|
||||
persistence-catalog.zh.md: efb8b5b53b6d2c8ffdd96b3bc2e9c2f797926d7d
|
||||
persistence-catalog.md: 331e55e7a1568c722b090dabfc48f858b42e4f62
|
||||
persistence-catalog.zh.md: c7840d1b404102f9a3a40fae76daf4a9f1d456a3
|
||||
+20
-21
@@ -83,7 +83,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}[T]
|
||||
```
|
||||
|
||||
Sources: [`packages/core/session/src/types.ts:421`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:429`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:451`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:482`](../packages/core/session/src/types.ts)
|
||||
Sources: [`packages/core/session/src/types.ts:404`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:412`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:434`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:465`](../packages/core/session/src/types.ts)
|
||||
|
||||
## Events
|
||||
|
||||
@@ -210,7 +210,7 @@ Source: [`packages/interaction/user-approval/src/index.ts:33`](../packages/inter
|
||||
'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:344`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:335`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="assistantmessage--surface"></a>
|
||||
|
||||
@@ -240,7 +240,7 @@ Source: [`packages/core/session/src/types.ts:344`](../packages/core/session/src/
|
||||
|
||||
Types: [TokenUsage](subsystems/llm-streaming.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:330`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:321`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `command/*`
|
||||
|
||||
@@ -522,17 +522,16 @@ Source: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-
|
||||
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Permanently omit the selected input-image occurrences from subsequent
|
||||
* model requests. Each target names a current user/message or tool/result
|
||||
* node; image indexes are zero-based depth-first positions within that
|
||||
* message, including nested tool results and already offloaded images.
|
||||
* Targets are unique and each index list is nonempty and strictly increasing.
|
||||
* This event changes derived content without replacing message nodes.
|
||||
* Permanently omit selected input-image occurrences from subsequent model requests.
|
||||
* Targets name unique current user/message or tool/result nodes. Nonempty, strictly
|
||||
* increasing indexes count all images in depth-first order, including nested tool
|
||||
* results and already omitted images. Message nodes and identities remain unchanged.
|
||||
* @messageProjection
|
||||
*/
|
||||
'image/offload': { targets: ImageOffloadTarget[] }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:278`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/compaction/compaction-image-offload/src/projection.ts:25`](../packages/compaction/compaction-image-offload/src/projection.ts)
|
||||
|
||||
### `llm/*`
|
||||
|
||||
@@ -625,7 +624,7 @@ Source: [`packages/plan/plan-mode/src/index.ts:47`](../packages/plan/plan-mode/s
|
||||
'request/context': RequestContext
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:386`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:377`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="requestheader--log-only"></a>
|
||||
|
||||
@@ -644,7 +643,7 @@ Source: [`packages/core/session/src/types.ts:386`](../packages/core/session/src/
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:374`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:365`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `sandbox/*`
|
||||
|
||||
@@ -719,7 +718,7 @@ Source: [`packages/schedule/schedule/src/types.ts:219`](../packages/schedule/sch
|
||||
'session/end-seed': { inherited?: true }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:409`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:400`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="sessiontitle--log-only"></a>
|
||||
|
||||
@@ -781,7 +780,7 @@ Source: [`packages/session/session-log-deepseek/src/types.ts:81`](../packages/se
|
||||
'step/end': { turn: number; step: number }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:298`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:289`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="stepstart--log-only"></a>
|
||||
|
||||
@@ -792,7 +791,7 @@ Source: [`packages/core/session/src/types.ts:298`](../packages/core/session/src/
|
||||
'step/start': { turn: number; step: number }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:296`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:287`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `subagent/*`
|
||||
|
||||
@@ -868,7 +867,7 @@ Source: [`packages/subagent/tool-subagent/src/model-selection-state.ts:17`](../p
|
||||
'system/message': { turn: number; step: number; message: SystemMessage }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:319`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:310`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `team/*`
|
||||
|
||||
@@ -961,7 +960,7 @@ Source: [`packages/todo/tool-todo/src/types.ts:31`](../packages/todo/tool-todo/s
|
||||
|
||||
Types: [ToolCallId](subsystems/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:350`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:341`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="toolptc-dispatch--log-only"></a>
|
||||
|
||||
@@ -1037,7 +1036,7 @@ Source: [`packages/core/tools/src/types.ts:40`](../packages/core/tools/src/types
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:362`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:353`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `tool-workflow/*`
|
||||
|
||||
@@ -1117,7 +1116,7 @@ Source: [`packages/workflow/tool-workflow/src/types.ts:47`](../packages/workflow
|
||||
|
||||
Types: [TurnEndReason](subsystems/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:294`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:285`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="turnstart--log-only"></a>
|
||||
|
||||
@@ -1133,7 +1132,7 @@ Source: [`packages/core/session/src/types.ts:294`](../packages/core/session/src/
|
||||
'turn/start': { turn: number }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:285`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:276`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `user/*`
|
||||
|
||||
@@ -1152,7 +1151,7 @@ Source: [`packages/core/session/src/types.ts:285`](../packages/core/session/src/
|
||||
'user/message': UserMessage
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:306`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:297`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `web/*`
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:344`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:335`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="assistantmessage--surface"></a>
|
||||
|
||||
@@ -242,7 +242,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[TokenUsage](subsystems/llm-streaming.zh.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:330`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:321`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `command/*`
|
||||
|
||||
@@ -524,17 +524,16 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Permanently omit the selected input-image occurrences from subsequent
|
||||
* model requests. Each target names a current user/message or tool/result
|
||||
* node; image indexes are zero-based depth-first positions within that
|
||||
* message, including nested tool results and already offloaded images.
|
||||
* Targets are unique and each index list is nonempty and strictly increasing.
|
||||
* This event changes derived content without replacing message nodes.
|
||||
* Permanently omit selected input-image occurrences from subsequent model requests.
|
||||
* Targets name unique current user/message or tool/result nodes. Nonempty, strictly
|
||||
* increasing indexes count all images in depth-first order, including nested tool
|
||||
* results and already omitted images. Message nodes and identities remain unchanged.
|
||||
* @messageProjection
|
||||
*/
|
||||
'image/offload': { targets: ImageOffloadTarget[] }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:278`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/compaction/compaction-image-offload/src/projection.ts:25`](../packages/compaction/compaction-image-offload/src/projection.ts)
|
||||
|
||||
### `llm/*`
|
||||
|
||||
@@ -627,7 +626,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'request/context': RequestContext
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:386`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:377`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="requestheader--log-only"></a>
|
||||
|
||||
@@ -646,7 +645,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:374`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:365`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `sandbox/*`
|
||||
|
||||
@@ -721,7 +720,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'session/end-seed': { inherited?: true }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:409`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:400`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="sessiontitle--log-only"></a>
|
||||
|
||||
@@ -783,7 +782,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'step/end': { turn: number; step: number }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:298`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:289`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="stepstart--log-only"></a>
|
||||
|
||||
@@ -794,7 +793,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'step/start': { turn: number; step: number }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:296`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:287`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `subagent/*`
|
||||
|
||||
@@ -870,7 +869,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'system/message': { turn: number; step: number; message: SystemMessage }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:319`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:310`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `team/*`
|
||||
|
||||
@@ -963,7 +962,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[ToolCallId](subsystems/core.zh.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:350`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:341`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="toolptc-dispatch--log-only"></a>
|
||||
|
||||
@@ -1039,7 +1038,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:362`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:353`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `tool-workflow/*`
|
||||
|
||||
@@ -1119,7 +1118,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[TurnEndReason](subsystems/session.zh.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:294`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:276`](../packages/core/session/src/types.ts)
|
||||
|
||||
<a id="turnstart--log-only"></a>
|
||||
|
||||
@@ -1154,7 +1153,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'user/message': UserMessage
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:306`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:297`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `web/*`
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/compaction.md
|
||||
compaction.md: 33af5e6a4a4da4f18c93e49c03ef65764efc5c57
|
||||
compaction.zh.md: 2dffc032b116fd2c7c6963a9009e02cb612c763b
|
||||
compaction.md: 6e135025ab6095e2365e172847bc493d41c50774
|
||||
compaction.zh.md: 9b82fb7a7b9e55f2aefa6d561922948fd328c8e9
|
||||
@@ -22,6 +22,21 @@ The markers are lock time points, not an exclusive container. An unrelated idle
|
||||
|
||||
These variants are merged inside a `declare module '@deepseek-ai/dsh-session/types'` block, so — unlike the top-level types on the other subsystem pages — they are not pasted as a drift-checked ` ```ts type-equiv ` block (the `verify-type-equiv` extractor matches only top-level declarations by name). The payload table above is the catalog entry; follow the source link for the authoritative fields.
|
||||
|
||||
<a id="image-offload"></a>
|
||||
## Image offload
|
||||
|
||||
`compaction-image-offload` owns the `image/offload` declaration and its pure message projection. Each target identifies a current input node and exact depth-first image occurrences. The event preserves node and message identities and carries no `surfaceOp`. The [package README](../../packages/compaction/compaction-image-offload/README.md) owns recovery policy, registration, and detached replay.
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact input-image occurrences selected by one durable offload decision. */
|
||||
interface ImageOffloadTarget {
|
||||
/** Current message-producing event containing these occurrences. */
|
||||
seq: SessionSeq
|
||||
/** Zero-based depth-first image indexes within the immutable message. */
|
||||
imageIndexes: number[]
|
||||
}
|
||||
```
|
||||
|
||||
## `CompactionResult`
|
||||
|
||||
What a successful compaction returns to its caller: the bookkeeping-event seqs, safe summary projection, shadowed range and seqs, and estimated token count.
|
||||
|
||||
@@ -22,6 +22,21 @@
|
||||
|
||||
这些变体在 `declare module '@deepseek-ai/dsh-session/types'` 块内合并,因此——与其他子系统页面上的顶层类型不同——它们不以漂移检查的 ` ```ts type-equiv ` 块粘贴(`verify-type-equiv` 提取器只按名称匹配顶层声明)。上方的载荷表即为目录条目;权威字段请循源码链接查看。
|
||||
|
||||
<a id="image-offload"></a>
|
||||
## 图片省略
|
||||
|
||||
`compaction-image-offload` 拥有 `image/offload` 声明及其纯消息投影。每个目标指定当前输入节点和按深度优先计数的确切图片位置。事件保留节点和消息身份,不携带 `surfaceOp`。[包 README](../../packages/compaction/compaction-image-offload/README.zh.md)负责恢复策略、注册和独立回放说明。
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact input-image occurrences selected by one durable offload decision. */
|
||||
interface ImageOffloadTarget {
|
||||
/** Current message-producing event containing these occurrences. */
|
||||
seq: SessionSeq
|
||||
/** Zero-based depth-first image indexes within the immutable message. */
|
||||
imageIndexes: number[]
|
||||
}
|
||||
```
|
||||
|
||||
## `CompactionResult`
|
||||
|
||||
成功压缩向调用方返回:记账事件 seq、安全摘要投影、被遮蔽的范围与 seq,以及估算 token 数。
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/session.md
|
||||
session.md: 7a44f3661b848a03a842c4c9d22b8e4861daf61a
|
||||
session.zh.md: e051b668b2715786f3e99d553e46234e2fd51d16
|
||||
session.md: 4058d432d7d4a2bd0b7811b88af5f4b5d3c30271
|
||||
session.zh.md: f037b9b8a66f7221949d9a887a561aed2ba5b6f5
|
||||
+60
-30
@@ -25,15 +25,6 @@ interface UserMessage extends Message {
|
||||
* compact raw streams so persistence stores one durable settlement per attempt.
|
||||
*/
|
||||
interface SessionEventMap {
|
||||
/**
|
||||
* Permanently omit the selected input-image occurrences from subsequent
|
||||
* model requests. Each target names a current user/message or tool/result
|
||||
* node; image indexes are zero-based depth-first positions within that
|
||||
* message, including nested tool results and already offloaded images.
|
||||
* Targets are unique and each index list is nonempty and strictly increasing.
|
||||
* This event changes derived content without replacing message nodes.
|
||||
*/
|
||||
'image/offload': { targets: ImageOffloadTarget[] }
|
||||
/**
|
||||
* Opens turn `turn` before the loop claims queued input or runs pre-step.
|
||||
* Rejection, empty input, cancellation, or failure may close it with no
|
||||
@@ -348,15 +339,39 @@ Required for `SurfaceEventType` events — every message-producing event must de
|
||||
|
||||
`assistant/message` cannot carry `sourceEventSeqs`; its `stream` owns exact provider evidence. Other surface events omit the field when they cite no earlier event and use a complete non-empty list when they do.
|
||||
|
||||
`image/offload` uses exact per-node indexes. It changes derived input content without adding a surface node; `contentGeneration` advances so request series and cached derivation are refreshed. Reconstructors apply `foldSurface(events).offloadedMessages` through `deriveEventMessage(event, offloadedMessages)`.
|
||||
<a id="plugin-owned-message-projections"></a>
|
||||
### Plugin-owned message projections
|
||||
|
||||
Content-changing plugins mark their event declaration with `@messageProjection` and register a pure definition through `ctx.sessions.registerMessageProjection()`. Session validates the complete decision through that definition before commit, applies its immutable message updates, and advances `contentGeneration`. It rejects missing interpreters, including on restore and detached folding; unloading a used definition blocks cached reads. Detached readers pass explicit definitions to `foldSurface(events, projections)` and apply `projectedMessages` through `deriveEventMessage()`. The installed format catalog assembles first-party definitions for offline readers. The [image-offload plugin](compaction.md#image-offload) owns its image-specific event and interpretation.
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact input-image occurrences selected by one durable offload decision. */
|
||||
interface ImageOffloadTarget {
|
||||
/** Current message-producing event containing these occurrences. */
|
||||
seq: SessionSeq
|
||||
/** Zero-based depth-first image indexes within the immutable message. */
|
||||
imageIndexes: number[]
|
||||
/** Readonly history immediately before a message-projection event. */
|
||||
interface SessionMessageProjectionContext {
|
||||
/** Current message-producing event sequences in model-visible order. */
|
||||
nodes: readonly SessionSeq[]
|
||||
/** Contiguous event window; entries at or beyond the candidate seq are not committed inputs. */
|
||||
events: readonly SessionEvent[]
|
||||
/** Absolute sequence of the window's first event. */
|
||||
baseSeq: SessionLogOffset
|
||||
/** Previously projected messages keyed by their original event sequences. */
|
||||
messages: ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Pure interpretation of one plugin-owned event that changes existing message content. */
|
||||
interface SessionMessageProjection<T extends SessionEventType = SessionEventType> {
|
||||
/** Event interpreted by this definition; declare it with `@messageProjection` in SessionEventMap. */
|
||||
type: T
|
||||
/**
|
||||
* Validate the complete durable decision before returning any updates. Preserve
|
||||
* message identities and publish immutable copies without mutating the input.
|
||||
* @param event - candidate event, not yet applied to the supplied history.
|
||||
* @param context - history preceding this decision.
|
||||
* @returns changed current messages keyed by their original sequences.
|
||||
* @throws when the durable decision cannot be applied to this history.
|
||||
*/
|
||||
project(event: SessionEvent<T>, context: SessionMessageProjectionContext): ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -364,7 +379,7 @@ interface ImageOffloadTarget {
|
||||
|
||||
`Session.surface` returns the session's stable `SessionSurface` view. The same incremental manager validates append candidates before commit and advances this projection from committed events; callers can observe membership and replacement generation but cannot invoke validation.
|
||||
|
||||
`SurfaceManager(log, baseSeq?)` can instead fold a contiguous loaded window whose first event has the absolute sequence `baseSeq`. Every event remains contiguous in that absolute sequence space, and a replacement that crosses the window head fails because its declared range is absent.
|
||||
`SurfaceManager(log, baseSeq?, projections?)` can instead fold a contiguous loaded window whose first event has the absolute sequence `baseSeq`. Every event remains contiguous in that absolute sequence space, and a replacement that crosses the window head fails because its declared range is absent.
|
||||
|
||||
```ts type-equiv
|
||||
/** Readonly live projection of the message-producing session events. */
|
||||
@@ -373,14 +388,14 @@ interface SessionSurface {
|
||||
readonly nodes: readonly SessionSeq[]
|
||||
/** Monotonic count of committed positional replacements. */
|
||||
readonly replaceGeneration: number
|
||||
/** Monotonic count of committed replacements and image-offload decisions. */
|
||||
/** Monotonic count of committed replacements and plugin-owned message changes. */
|
||||
readonly contentGeneration: number
|
||||
}
|
||||
```
|
||||
|
||||
### `SurfaceFoldReplacement` and `SurfaceFoldResult` — a complete surface replay
|
||||
|
||||
`foldSurface(events)` returns detached current event sequences together with the actual sequences shadowed by each declared replacement range. The live manager uses the same transitions without retaining replacement history. Its `replaceGeneration` increments for each committed replacement so incremental consumers can distinguish pure tail growth from a rewrite.
|
||||
`foldSurface(events, projections)` returns detached current event sequences together with the actual sequences shadowed by each declared replacement range. The live manager uses the same transitions without retaining replacement history. Its `replaceGeneration` increments for each committed replacement so incremental consumers can distinguish pure tail growth from a rewrite.
|
||||
|
||||
```ts type-equiv
|
||||
/** One replacement operation observed while folding a session surface. */
|
||||
@@ -403,8 +418,8 @@ interface SurfaceFoldResult {
|
||||
nodes: SessionSeq[]
|
||||
/** Replacement operations in event order. */
|
||||
replacements: SurfaceFoldReplacement[]
|
||||
/** Immutable image-offloaded messages, keyed by their original event sequences. */
|
||||
offloadedMessages: ReadonlyMap<SessionSeq, Message>
|
||||
/** Immutable projected messages, keyed by their original event sequences. */
|
||||
projectedMessages: ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -459,7 +474,7 @@ declare class Session {
|
||||
* When this lifecycle appends the marker, it occupies this seq before the
|
||||
* store attaches and therefore does not publish either. Otherwise this seq
|
||||
* holds an ordinary published write.
|
||||
*/
|
||||
*/
|
||||
readonly firstLiveSeq: SessionLogOffset;
|
||||
/**
|
||||
* Create a detached session by validating and snapshotting borrowed seed
|
||||
@@ -468,14 +483,17 @@ declare class Session {
|
||||
* @param seed - optional borrowed replay or fork events.
|
||||
* @param header - optional borrowed storage metadata.
|
||||
* @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
|
||||
* @param projections - pure interpreters for plugin-owned message changes.
|
||||
* @returns a detached session.
|
||||
* @throws when a seed event requires a missing message interpreter or fails validation.
|
||||
*/
|
||||
static create(
|
||||
id: SessionId,
|
||||
seed?: readonly SessionEvent[],
|
||||
header?: SessionHeader,
|
||||
inheritedEventCount?: SessionLogOffset,
|
||||
): Session;
|
||||
projections?: readonly SessionMessageProjection[],
|
||||
): Session;
|
||||
/**
|
||||
* Restore a detached session by adopting an independently owned or deeply frozen seed.
|
||||
* Runtime-required event fields, event envelopes, sequence continuity, surface
|
||||
@@ -487,7 +505,9 @@ declare class Session {
|
||||
* @param header - independently owned storage metadata.
|
||||
* @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
|
||||
* @param eventState - aliasing state carried from the operation that produced the seed.
|
||||
* @param projections - pure interpreters for plugin-owned message changes.
|
||||
* @returns a restored detached session.
|
||||
* @throws when a seed event requires a missing message interpreter or fails validation.
|
||||
*/
|
||||
static fromRestore(
|
||||
id: SessionId,
|
||||
@@ -495,7 +515,8 @@ declare class Session {
|
||||
header: SessionHeader,
|
||||
inheritedEventCount: SessionLogOffset,
|
||||
eventState: SessionSeedEventState,
|
||||
): Session;
|
||||
projections?: readonly SessionMessageProjection[],
|
||||
): Session;
|
||||
/**
|
||||
* Return the immutable event stored at one exact sequence number.
|
||||
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
||||
@@ -517,7 +538,7 @@ declare class Session {
|
||||
snapshotEvents(
|
||||
fromSeq: SessionLogOffset = SessionLogOffset(0),
|
||||
toSeqExclusive: SessionLogOffset = this.seq,
|
||||
): readonly SessionEvent[];
|
||||
): readonly SessionEvent[];
|
||||
/**
|
||||
* Return this Session's events after its fork-inherited prefix.
|
||||
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
||||
@@ -574,7 +595,7 @@ declare class Session {
|
||||
type: T,
|
||||
data: SessionEventMap[T],
|
||||
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
|
||||
): SessionEvent<T>;
|
||||
): SessionEvent<T>;
|
||||
/**
|
||||
* The {@link EpochHeader} in force after the log's last header event — the
|
||||
* header the NEXT request will be compared against — or undefined before
|
||||
@@ -597,20 +618,20 @@ declare class Session {
|
||||
* append records its `surfaceOp`, so a raw event with no marker (a chunk, a
|
||||
* turn boundary) is correctly absent, and a compaction `replace` deletes the
|
||||
* shadowed nodes from the derivation. The projection rules are
|
||||
* {@link deriveEventMessage}, with logged image-offload selections applied
|
||||
* {@link deriveEventMessage}, with logged message projections applied
|
||||
* without changing node membership or message identity.
|
||||
*
|
||||
* CACHED: pure tail growth costs O(new nodes); a replacement or image offload
|
||||
* CACHED: pure tail growth costs O(new nodes); a replacement or message projection
|
||||
* ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
|
||||
* a fresh snapshot per call (later appends never grow an array a caller
|
||||
* already holds); the `Message` objects in it are SHARED and **deep-frozen**.
|
||||
* Unchanged content reuses frozen event data; offloaded blocks are frozen
|
||||
* Unchanged content reuses frozen event data; projected blocks are frozen
|
||||
* derived copies. Consumers cannot mutate the log through either form.
|
||||
* @returns a fresh array of the shared, frozen derived history.
|
||||
*/
|
||||
deriveMessages(): Message[];
|
||||
/**
|
||||
* Project one event with all committed image-offload selections applied.
|
||||
* Project one event with all committed message projections applied.
|
||||
* The original durable event remains unchanged.
|
||||
* @param event - the event to project.
|
||||
* @returns the derived message, or null when the event produces none.
|
||||
@@ -881,6 +902,15 @@ In-memory session store (`ctx.sessions`).
|
||||
Persistence is intentionally not implemented here — the agent lifecycle attaches a session-log writer to each published session's write handle; a session published outside that lifecycle persists nothing.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Register one event interpreter for live creation, restore, and fork.
|
||||
* Disposing the contribution makes sessions that used it refuse further derivation.
|
||||
* @param projection - pure definition owned by the event's plugin.
|
||||
* @returns the fiber-owned disposer.
|
||||
* @throws when another definition already owns this event type.
|
||||
*/
|
||||
registerMessageProjection(projection: SessionMessageProjection): () => void
|
||||
|
||||
/**
|
||||
* Create a session owned by the calling fiber: disposing that fiber stops
|
||||
* event notification and removes the session from the store. `options.seed`
|
||||
|
||||
@@ -25,15 +25,6 @@ interface UserMessage extends Message {
|
||||
* compact raw streams so persistence stores one durable settlement per attempt.
|
||||
*/
|
||||
interface SessionEventMap {
|
||||
/**
|
||||
* Permanently omit the selected input-image occurrences from subsequent
|
||||
* model requests. Each target names a current user/message or tool/result
|
||||
* node; image indexes are zero-based depth-first positions within that
|
||||
* message, including nested tool results and already offloaded images.
|
||||
* Targets are unique and each index list is nonempty and strictly increasing.
|
||||
* This event changes derived content without replacing message nodes.
|
||||
*/
|
||||
'image/offload': { targets: ImageOffloadTarget[] }
|
||||
/**
|
||||
* Opens turn `turn` before the loop claims queued input or runs pre-step.
|
||||
* Rejection, empty input, cancellation, or failure may close it with no
|
||||
@@ -350,15 +341,39 @@ type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
|
||||
|
||||
`assistant/message` 不能携带 `sourceEventSeqs`;它的 `stream` 拥有精确 provider 证据。其他 surface event 不引用较早 event 时省略该字段,需要引用时使用完整非空 list。
|
||||
|
||||
`image/offload` 使用逐节点的明确索引,不增加 surface 节点,但改变派生输入内容。`contentGeneration` 推进,使请求系列和派生缓存刷新。重建函数通过 `deriveEventMessage(event, offloadedMessages)` 应用 `foldSurface(events).offloadedMessages`。
|
||||
<a id="plugin-owned-message-projections"></a>
|
||||
### 插件拥有的消息投影
|
||||
|
||||
修改内容的插件用 `@messageProjection` 标记事件声明,并通过 `ctx.sessions.registerMessageProjection()` 注册纯处理器。Session 在提交前通过处理器校验完整决策,应用其不可变消息更新,并推进 `contentGeneration`。缺少处理器时拒绝操作,包括恢复和独立折叠,卸载已经使用的处理器后也会拒绝读取缓存。独立读取器将处理器显式传给 `foldSurface(events, projections)`,并通过 `deriveEventMessage()` 应用 `projectedMessages`。当前格式目录为离线读取器装配第一方处理器。[图片省略插件](compaction.zh.md#image-offload)拥有图片专用的事件及解释逻辑。
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact input-image occurrences selected by one durable offload decision. */
|
||||
interface ImageOffloadTarget {
|
||||
/** Current message-producing event containing these occurrences. */
|
||||
seq: SessionSeq
|
||||
/** Zero-based depth-first image indexes within the immutable message. */
|
||||
imageIndexes: number[]
|
||||
/** Readonly history immediately before a message-projection event. */
|
||||
interface SessionMessageProjectionContext {
|
||||
/** Current message-producing event sequences in model-visible order. */
|
||||
nodes: readonly SessionSeq[]
|
||||
/** Contiguous event window; entries at or beyond the candidate seq are not committed inputs. */
|
||||
events: readonly SessionEvent[]
|
||||
/** Absolute sequence of the window's first event. */
|
||||
baseSeq: SessionLogOffset
|
||||
/** Previously projected messages keyed by their original event sequences. */
|
||||
messages: ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Pure interpretation of one plugin-owned event that changes existing message content. */
|
||||
interface SessionMessageProjection<T extends SessionEventType = SessionEventType> {
|
||||
/** Event interpreted by this definition; declare it with `@messageProjection` in SessionEventMap. */
|
||||
type: T
|
||||
/**
|
||||
* Validate the complete durable decision before returning any updates. Preserve
|
||||
* message identities and publish immutable copies without mutating the input.
|
||||
* @param event - candidate event, not yet applied to the supplied history.
|
||||
* @param context - history preceding this decision.
|
||||
* @returns changed current messages keyed by their original sequences.
|
||||
* @throws when the durable decision cannot be applied to this history.
|
||||
*/
|
||||
project(event: SessionEvent<T>, context: SessionMessageProjectionContext): ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -366,7 +381,7 @@ interface ImageOffloadTarget {
|
||||
|
||||
`Session.surface` 返回会话稳定的 `SessionSurface` 视图。同一个增量管理器在提交前校验追加候选事件,并根据已提交事件推进该投影;调用方可以观察成员关系和替换代次,但不能调用校验。
|
||||
|
||||
`SurfaceManager(log, baseSeq?)` 也可以折叠一个连续的已加载窗口,其第一个事件的绝对序号为 `baseSeq`。每个事件在该绝对序号空间中仍保持连续;如果替换跨过窗口头部,由于其声明的范围并不存在,该替换会失败。
|
||||
`SurfaceManager(log, baseSeq?, projections?)` 也可以折叠一个连续的已加载窗口,其第一个事件的绝对序号为 `baseSeq`。每个事件在该绝对序号空间中仍保持连续;如果替换跨过窗口头部,由于其声明的范围并不存在,该替换会失败。
|
||||
|
||||
```ts type-equiv
|
||||
/** Readonly live projection of the message-producing session events. */
|
||||
@@ -375,14 +390,14 @@ interface SessionSurface {
|
||||
readonly nodes: readonly SessionSeq[]
|
||||
/** Monotonic count of committed positional replacements. */
|
||||
readonly replaceGeneration: number
|
||||
/** Monotonic count of committed replacements and image-offload decisions. */
|
||||
/** Monotonic count of committed replacements and plugin-owned message changes. */
|
||||
readonly contentGeneration: number
|
||||
}
|
||||
```
|
||||
|
||||
### `SurfaceFoldReplacement` 与 `SurfaceFoldResult`:完整的 surface 回放
|
||||
|
||||
`foldSurface(events)` 返回一份独立的当前事件 seq 列表,以及每个声明的替换范围实际遮蔽的 seq。实时管理器复用同一套状态转换,但不保留替换历史。每提交一次替换,其 `replaceGeneration` 就递增一次,使增量消费方能够区分纯尾部增长与重写。
|
||||
`foldSurface(events, projections)` 返回一份独立的当前事件 seq 列表,以及每个声明的替换范围实际遮蔽的 seq。实时管理器复用同一套状态转换,但不保留替换历史。每提交一次替换,其 `replaceGeneration` 就递增一次,使增量消费方能够区分纯尾部增长与重写。
|
||||
|
||||
```ts type-equiv
|
||||
/** One replacement operation observed while folding a session surface. */
|
||||
@@ -405,8 +420,8 @@ interface SurfaceFoldResult {
|
||||
nodes: SessionSeq[]
|
||||
/** Replacement operations in event order. */
|
||||
replacements: SurfaceFoldReplacement[]
|
||||
/** Immutable image-offloaded messages, keyed by their original event sequences. */
|
||||
offloadedMessages: ReadonlyMap<SessionSeq, Message>
|
||||
/** Immutable projected messages, keyed by their original event sequences. */
|
||||
projectedMessages: ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -461,7 +476,7 @@ declare class Session {
|
||||
* When this lifecycle appends the marker, it occupies this seq before the
|
||||
* store attaches and therefore does not publish either. Otherwise this seq
|
||||
* holds an ordinary published write.
|
||||
*/
|
||||
*/
|
||||
readonly firstLiveSeq: SessionLogOffset;
|
||||
/**
|
||||
* Create a detached session by validating and snapshotting borrowed seed
|
||||
@@ -470,14 +485,17 @@ declare class Session {
|
||||
* @param seed - optional borrowed replay or fork events.
|
||||
* @param header - optional borrowed storage metadata.
|
||||
* @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
|
||||
* @param projections - pure interpreters for plugin-owned message changes.
|
||||
* @returns a detached session.
|
||||
* @throws when a seed event requires a missing message interpreter or fails validation.
|
||||
*/
|
||||
static create(
|
||||
id: SessionId,
|
||||
seed?: readonly SessionEvent[],
|
||||
header?: SessionHeader,
|
||||
inheritedEventCount?: SessionLogOffset,
|
||||
): Session;
|
||||
projections?: readonly SessionMessageProjection[],
|
||||
): Session;
|
||||
/**
|
||||
* Restore a detached session by adopting an independently owned or deeply frozen seed.
|
||||
* Runtime-required event fields, event envelopes, sequence continuity, surface
|
||||
@@ -489,7 +507,9 @@ declare class Session {
|
||||
* @param header - independently owned storage metadata.
|
||||
* @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
|
||||
* @param eventState - aliasing state carried from the operation that produced the seed.
|
||||
* @param projections - pure interpreters for plugin-owned message changes.
|
||||
* @returns a restored detached session.
|
||||
* @throws when a seed event requires a missing message interpreter or fails validation.
|
||||
*/
|
||||
static fromRestore(
|
||||
id: SessionId,
|
||||
@@ -497,7 +517,8 @@ declare class Session {
|
||||
header: SessionHeader,
|
||||
inheritedEventCount: SessionLogOffset,
|
||||
eventState: SessionSeedEventState,
|
||||
): Session;
|
||||
projections?: readonly SessionMessageProjection[],
|
||||
): Session;
|
||||
/**
|
||||
* Return the immutable event stored at one exact sequence number.
|
||||
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
||||
@@ -519,7 +540,7 @@ declare class Session {
|
||||
snapshotEvents(
|
||||
fromSeq: SessionLogOffset = SessionLogOffset(0),
|
||||
toSeqExclusive: SessionLogOffset = this.seq,
|
||||
): readonly SessionEvent[];
|
||||
): readonly SessionEvent[];
|
||||
/**
|
||||
* Return this Session's events after its fork-inherited prefix.
|
||||
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
||||
@@ -576,7 +597,7 @@ declare class Session {
|
||||
type: T,
|
||||
data: SessionEventMap[T],
|
||||
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
|
||||
): SessionEvent<T>;
|
||||
): SessionEvent<T>;
|
||||
/**
|
||||
* The {@link EpochHeader} in force after the log's last header event — the
|
||||
* header the NEXT request will be compared against — or undefined before
|
||||
@@ -599,20 +620,20 @@ declare class Session {
|
||||
* append records its `surfaceOp`, so a raw event with no marker (a chunk, a
|
||||
* turn boundary) is correctly absent, and a compaction `replace` deletes the
|
||||
* shadowed nodes from the derivation. The projection rules are
|
||||
* {@link deriveEventMessage}, with logged image-offload selections applied
|
||||
* {@link deriveEventMessage}, with logged message projections applied
|
||||
* without changing node membership or message identity.
|
||||
*
|
||||
* CACHED: pure tail growth costs O(new nodes); a replacement or image offload
|
||||
* CACHED: pure tail growth costs O(new nodes); a replacement or message projection
|
||||
* ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
|
||||
* a fresh snapshot per call (later appends never grow an array a caller
|
||||
* already holds); the `Message` objects in it are SHARED and **deep-frozen**.
|
||||
* Unchanged content reuses frozen event data; offloaded blocks are frozen
|
||||
* Unchanged content reuses frozen event data; projected blocks are frozen
|
||||
* derived copies. Consumers cannot mutate the log through either form.
|
||||
* @returns a fresh array of the shared, frozen derived history.
|
||||
*/
|
||||
deriveMessages(): Message[];
|
||||
/**
|
||||
* Project one event with all committed image-offload selections applied.
|
||||
* Project one event with all committed message projections applied.
|
||||
* The original durable event remains unchanged.
|
||||
* @param event - the event to project.
|
||||
* @returns the derived message, or null when the event produces none.
|
||||
@@ -885,6 +906,15 @@ In-memory session store (`ctx.sessions`).
|
||||
Persistence is intentionally not implemented here — the agent lifecycle attaches a session-log writer to each published session's write handle; a session published outside that lifecycle persists nothing.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Register one event interpreter for live creation, restore, and fork.
|
||||
* Disposing the contribution makes sessions that used it refuse further derivation.
|
||||
* @param projection - pure definition owned by the event's plugin.
|
||||
* @returns the fiber-owned disposer.
|
||||
* @throws when another definition already owns this event type.
|
||||
*/
|
||||
registerMessageProjection(projection: SessionMessageProjection): () => void
|
||||
|
||||
/**
|
||||
* Create a session owned by the calling fiber: disposing that fiber stops
|
||||
* event notification and removes the session from the store. `options.seed`
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
"dependencies": {
|
||||
"@deepseek-ai/dsh-credentials": "workspace:^",
|
||||
"@deepseek-ai/schemastery": "workspace:^",
|
||||
"zod": "^4.4.3"
|
||||
"zod": "^4.4.3",
|
||||
"@deepseek-ai/dsh-session-format-catalog": "workspace:^"
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Standalone browser fixture for UI development without a server.
|
||||
|
||||
import { currentSessionMessageProjections } from '@deepseek-ai/dsh-session-format-catalog/message-projections'
|
||||
import {
|
||||
createAssistantMessage,
|
||||
createSystemMessage,
|
||||
@@ -1278,7 +1279,7 @@ function contextBreakdownOf(log: readonly SessionEvent[]): FixtureContextBreakdo
|
||||
: headerEvent.data.header
|
||||
let systemTokens = 0
|
||||
let messageTokens = 0
|
||||
for (const seq of foldSurface(log).nodes) {
|
||||
for (const seq of foldSurface(log, currentSessionMessageProjections).nodes) {
|
||||
const event = log[seq]
|
||||
if (event === undefined) continue
|
||||
const message = deriveEventMessage(event)
|
||||
@@ -3105,7 +3106,7 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
|
||||
const query = searchTokenSpans(request.query).tokens.map(token => token.value)
|
||||
const matches = sessions.flatMap((summary) => {
|
||||
const log = logs.get(summary.sessionId) ?? []
|
||||
const current = new Set(foldSurface(log).nodes)
|
||||
const current = new Set(foldSurface(log, currentSessionMessageProjections).nodes)
|
||||
const best = log.flatMap((event): FixtureSearchCandidate[] => {
|
||||
if (!current.has(event.seq)) return []
|
||||
const eventText = searchEventText(event)
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../util/brand"
|
||||
},
|
||||
{
|
||||
"path": "../../session/session-format-catalog"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -61,6 +61,7 @@
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
||||
"@deepseek-ai/dsh-tools": "workspace:^"
|
||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||
"@deepseek-ai/dsh-compaction-image-offload": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { imageOffloadProjection } from '@deepseek-ai/dsh-compaction-image-offload/projection'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
@@ -99,6 +100,7 @@ interface LoopHarness {
|
||||
async function loopHarness(): Promise<LoopHarness> {
|
||||
const ctx = new Context()
|
||||
await mountAgentLoopTestDependencies(ctx)
|
||||
ctx.sessions.registerMessageProjection(imageOffloadProjection)
|
||||
await ctx.plugin(InvariantRegistry)
|
||||
await ctx.plugin(SessionInvariant)
|
||||
await ctx.plugin(AgentInvariant)
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/compaction/compaction-image-offload/README.md
|
||||
README.md: ed22ccfe8817a70cf05d2f3ff0d54bc779702f4f
|
||||
README.zh.md: c5830133533a44ec3c3cd36cab1593cda476177a
|
||||
README.md: 8e4d812c043234e07207c7326882856b445b0950
|
||||
README.zh.md: 06ed168e750cffbaa6594454ac4459f0ceb99788
|
||||
@@ -49,11 +49,11 @@ The plugin acts only on `IMAGE_OFFLOAD_REQUIRED` failures that carry `offloadIma
|
||||
<details>
|
||||
<summary>Implementation internals — click to expand</summary>
|
||||
|
||||
The recovery listener owns selection and retry policy. It reads Session's projected messages, selects the oldest retained input images in request order, and commits the complete selection in one event. Session validates and applies those exact references during append and replay; its shared derivation supplies requests, compaction, and later message rewrites. Token measurement folds the same selections without replacement shadow prices.
|
||||
This package owns the event declaration, reference validation, immutable image projection, selection, and retry policy. It registers `imageOffloadProjection` with Session before accepting image-offload events. The same browser-safe definition is exported from `./projection` for detached replay; the installed format catalog assembles it for offline readers. Missing registration rejects live restore, and unloading a used definition blocks further message derivation. Token measurement folds the same selections without replacement shadow prices.
|
||||
|
||||
Summary failures use the synchronous `compaction/summary-error` waterfall. The plugin selects only the supplied summary region and returns true after recording an omission. The compaction backend re-derives and re-prices that region before retrying; each retry omits additional retained occurrences, so recovery ends when none remain. Cancellation and unrelated selection changes reject the summary. Recorded omissions survive later failure or cancellation.
|
||||
|
||||
No runtime invariant companion is published: Session rejects invalid or repeated image references before commit, and this executor retains no separate mutable offload state.
|
||||
No runtime invariant companion is published: the pure projection rejects invalid or repeated image references before Session commits the event, and this executor retains no separate mutable offload state.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -49,11 +49,11 @@ kind: "package-reference"
|
||||
<details>
|
||||
<summary>实现内部——点击展开</summary>
|
||||
|
||||
恢复监听器负责选图和重试策略。它读取 Session 派生的消息,按请求顺序选取最早的保留输入图片,并用一条事件提交本次选择。Session 在追加和回放时校验并应用这些确切引用,共享派生结果供请求、压缩(compaction)和后续消息改写使用。token 测量应用同一份选择,不依赖替换影子价格。
|
||||
本包拥有事件声明、引用校验、不可变图片投影、选图和重试策略。它在接受图片省略事件前向 Session 注册 `imageOffloadProjection`。相同的浏览器安全处理器通过 `./projection` 导出,供独立回放使用,当前格式目录为离线读取器装配该处理器。缺少注册时拒绝实时恢复,卸载已经使用的处理器后拒绝继续派生消息。token 测量应用同一份选择,不依赖替换影子价格。
|
||||
|
||||
摘要失败使用同步的 `compaction/summary-error` waterfall。插件只在传入的摘要选区内选图,记录省略后返回 true。压缩后端重新派生该选区并计价,然后重试。每次重试都会继续省略保留的图片,没有可省略的位置时结束恢复。取消或无关的选区变更会使摘要失败。已经记录的省略在后续失败或取消后仍然有效。
|
||||
|
||||
本包不发布运行时 invariant 伴生插件:Session 在提交前拒绝无效或重复省略的图片引用,执行器不维护独立可变的省略状态。
|
||||
本包不发布运行时 invariant 伴生插件:纯投影在 Session 提交事件前拒绝无效或重复省略的图片引用,执行器不维护独立可变的省略状态。
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -18,11 +18,16 @@
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
"./package.json": "./package.json",
|
||||
"./projection": {
|
||||
"types": "./lib/types/projection.d.ts",
|
||||
"default": "./lib/types/projection.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
"lib/types/**/*.d.ts"
|
||||
"lib/types/**/*.d.ts",
|
||||
"lib/types/**/*.js"
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
@@ -45,5 +50,8 @@
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-token-meter": "workspace:^"
|
||||
},
|
||||
"dependencies": {
|
||||
"@deepseek-ai/dsh-util-values": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/** Select and log permanent image omissions in current model-request order. */
|
||||
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import type { ImageOffloadTarget, Session, SessionSeq } from '@deepseek-ai/dsh-session'
|
||||
import type { Session, SessionSeq } from '@deepseek-ai/dsh-session'
|
||||
import type { ImageOffloadTarget } from './projection.ts'
|
||||
|
||||
/**
|
||||
* Record one decision omitting the oldest retained input-image occurrences.
|
||||
|
||||
@@ -13,15 +13,17 @@ import type {} from '@deepseek-ai/dsh-compaction'
|
||||
import type { RequestErrorAction } from '@deepseek-ai/dsh-agent'
|
||||
import { IMAGE_OFFLOAD_REQUIRED_CODE, LlmError } from '@deepseek-ai/dsh-llm'
|
||||
import { offloadOldestImages } from './image-offload.ts'
|
||||
import { imageOffloadProjection } from './projection.ts'
|
||||
|
||||
export const name = 'compaction-image-offload'
|
||||
export const inject = ['agents']
|
||||
export const inject = ['agents', 'sessions']
|
||||
|
||||
/**
|
||||
* Mount agent and summary recovery listeners without configuration.
|
||||
* @param ctx - the plugin context.
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
ctx.sessions.registerMessageProjection(imageOffloadProjection)
|
||||
ctx.on('agent/request-error', ({ agent, failure }, next): Promise<RequestErrorAction> => {
|
||||
if (failure.code !== IMAGE_OFFLOAD_REQUIRED_CODE || failure.offloadImages === undefined) return next()
|
||||
// A durable surface repair, not a provider retry: it spends no retry budget and logs no retry event.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/** Exact application of logged image selections; selection policy belongs to compaction-image-offload. */
|
||||
/** Immutable application of the image occurrences recorded by image/offload. */
|
||||
|
||||
import type { ContentBlock, Message } from '@deepseek-ai/dsh-llm'
|
||||
import { deepFreeze } from '@deepseek-ai/dsh-util-values'
|
||||
@@ -0,0 +1,74 @@
|
||||
/** Browser-safe durable image selection declaration and pure replay definition. */
|
||||
|
||||
import type { Message } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionSeq } from '@deepseek-ai/dsh-session/types'
|
||||
import type { SessionMessageProjection } from '@deepseek-ai/dsh-session/surface'
|
||||
import { offloadMessageImages } from './project-message.ts'
|
||||
|
||||
/** Exact input-image occurrences selected by one durable offload decision. */
|
||||
export interface ImageOffloadTarget {
|
||||
/** Current message-producing event containing these occurrences. */
|
||||
seq: SessionSeq
|
||||
/** Zero-based depth-first image indexes within the immutable message. */
|
||||
imageIndexes: number[]
|
||||
}
|
||||
|
||||
declare module '@deepseek-ai/dsh-session/types' {
|
||||
interface SessionEventMap {
|
||||
/**
|
||||
* Permanently omit selected input-image occurrences from subsequent model requests.
|
||||
* Targets name unique current user/message or tool/result nodes. Nonempty, strictly
|
||||
* increasing indexes count all images in depth-first order, including nested tool
|
||||
* results and already omitted images. Message nodes and identities remain unchanged.
|
||||
* @messageProjection
|
||||
*/
|
||||
'image/offload': { targets: ImageOffloadTarget[] }
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether a durable value is a JSON object. */
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
/** Whether a durable occurrence index or sequence is canonical. */
|
||||
function isIndex(value: unknown): value is SessionSeq {
|
||||
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0)
|
||||
}
|
||||
|
||||
/** Atomic validation and reconstruction shared by live sessions and detached replay. */
|
||||
export const imageOffloadProjection: SessionMessageProjection<'image/offload'> = {
|
||||
type: 'image/offload',
|
||||
project(event, context) {
|
||||
const data: unknown = event.data
|
||||
if (!isRecord(data) || Object.keys(data).length !== 1 || !Array.isArray(data['targets']) || data['targets'].length === 0) {
|
||||
throw new Error('image/offload: data must contain a nonempty targets array')
|
||||
}
|
||||
const messages = new Map<SessionSeq, Message>()
|
||||
const nodes = new Set(context.nodes)
|
||||
for (const target of data['targets'] as unknown[]) {
|
||||
if (!isRecord(target) || Object.keys(target).length !== 2 || !isIndex(target['seq'])
|
||||
|| !Array.isArray(target['imageIndexes']) || target['imageIndexes'].length === 0) {
|
||||
throw new Error('image/offload: each target must contain a seq and nonempty imageIndexes')
|
||||
}
|
||||
const seq = target['seq']
|
||||
if (messages.has(seq)) throw new Error(`image/offload: duplicate target seq ${seq}`)
|
||||
if (!nodes.has(seq)) throw new Error(`image/offload: target seq ${seq} is not a current surface node`)
|
||||
const source = context.events[seq - context.baseSeq]
|
||||
if (source?.type !== 'user/message' && source?.type !== 'tool/result') {
|
||||
throw new Error(`image/offload: target seq ${seq} must be user/message or tool/result`)
|
||||
}
|
||||
let previous = -1
|
||||
for (const index of target['imageIndexes'] as unknown[]) {
|
||||
if (!isIndex(index) || index <= previous) {
|
||||
throw new Error('image/offload: imageIndexes must be strictly increasing non-negative safe integers')
|
||||
}
|
||||
previous = index
|
||||
}
|
||||
const message = context.messages.get(seq)
|
||||
?? (source.type === 'user/message' ? source.data : source.data.message)
|
||||
messages.set(seq, offloadMessageImages(message, target['imageIndexes'] as number[]))
|
||||
}
|
||||
return messages
|
||||
},
|
||||
}
|
||||
+19
-13
@@ -3,6 +3,12 @@ import { createAssistantMessage, createToolResultMessage, createUserMessage, Too
|
||||
import type { ContentBlock, ImageBlock } from '@deepseek-ai/dsh-llm'
|
||||
import { deriveEventMessage, foldSurface, Session, SessionId, SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session'
|
||||
import { imageOffloadProjection } from '../src/projection.ts'
|
||||
|
||||
function createSession(...args: Parameters<typeof Session.create>): Session {
|
||||
args[4] = [imageOffloadProjection]
|
||||
return Session.create(...args)
|
||||
}
|
||||
|
||||
const image: ImageBlock = {
|
||||
type: 'image',
|
||||
@@ -27,7 +33,7 @@ function marked(session: Session): boolean[] {
|
||||
|
||||
describe('durable image selections', () => {
|
||||
it('preserves message identity, original data, and previously derived snapshots', () => {
|
||||
const session = Session.create(SessionId('images'))
|
||||
const session = createSession(SessionId('images'))
|
||||
const source = input(session, [
|
||||
{ type: 'text', text: 'before' }, image,
|
||||
{ type: 'tool-result', toolCallId: ToolCallId('nested'), content: [image, { type: 'text', text: 'after' }] },
|
||||
@@ -57,19 +63,19 @@ describe('durable image selections', () => {
|
||||
})
|
||||
|
||||
it('reconstructs the same selections through pure folding, resume, restore, and fork', () => {
|
||||
const session = Session.create(SessionId('source'))
|
||||
const session = createSession(SessionId('source'))
|
||||
const source = input(session)
|
||||
const before = session.snapshotEvents()
|
||||
session.append('image/offload', { targets: [{ seq: source.seq, imageIndexes: [0] }] })
|
||||
const events = session.snapshotEvents()
|
||||
const fold = foldSurface(events)
|
||||
expect(deriveEventMessage(source, fold.offloadedMessages)).toEqual(session.deriveMessages()[0])
|
||||
expect(marked(Session.create(SessionId('before'), before))).toEqual([false, false])
|
||||
expect(marked(Session.create(SessionId('resume'), events))).toEqual([true, false])
|
||||
const restored = Session.fromRestore(session.id, events, session.header, SessionLogOffset(0), 'shared-frozen')
|
||||
const fold = foldSurface(events, [imageOffloadProjection])
|
||||
expect(deriveEventMessage(source, fold.projectedMessages)).toEqual(session.deriveMessages()[0])
|
||||
expect(marked(createSession(SessionId('before'), before))).toEqual([false, false])
|
||||
expect(marked(createSession(SessionId('resume'), events))).toEqual([true, false])
|
||||
const restored = Session.fromRestore(session.id, events, session.header, SessionLogOffset(0), 'shared-frozen', [imageOffloadProjection])
|
||||
expect(marked(restored)).toEqual([true, false])
|
||||
const childId = SessionId('child')
|
||||
const child = Session.create(childId, events, {
|
||||
const child = createSession(childId, events, {
|
||||
...session.header, id: childId, parentSession: session.id, isSeeded: true,
|
||||
}, SessionLogOffset(events.length))
|
||||
expect(marked(child)).toEqual([true, false])
|
||||
@@ -79,7 +85,7 @@ describe('durable image selections', () => {
|
||||
})
|
||||
|
||||
it('counts all images in a tool result without changing its call identity', () => {
|
||||
const session = Session.create(SessionId('tool'))
|
||||
const session = createSession(SessionId('tool'))
|
||||
const source = session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({ callId: ToolCallId('shot'), isError: false, content: [image, image] }),
|
||||
@@ -104,13 +110,13 @@ describe('durable image selections', () => {
|
||||
{ targets: [{ seq: 1, imageIndexes: [0] }] },
|
||||
{ targets: [{ seq: 0, imageIndexes: [0] }, { seq: 0, imageIndexes: [1] }] },
|
||||
])('rejects malformed selections at append and replay without partial application: %j', (data) => {
|
||||
const session = Session.create(SessionId('invalid'))
|
||||
const session = createSession(SessionId('invalid'))
|
||||
input(session)
|
||||
const events = session.snapshotEvents()
|
||||
const candidate = { type: 'image/offload', seq: SessionSeq(1), time: 0, data } as SessionEvent
|
||||
expect(() => session.append('image/offload', data as SessionEventMap['image/offload'])).toThrow(/image\/offload/)
|
||||
expect(() => Session.create(SessionId('seed'), [...events, candidate])).toThrow(/image\/offload/)
|
||||
expect(() => foldSurface([...events, candidate])).toThrow(/image\/offload/)
|
||||
expect(() => createSession(SessionId('seed'), [...events, candidate])).toThrow(/image\/offload/)
|
||||
expect(() => foldSurface([...events, candidate], [imageOffloadProjection])).toThrow(/image\/offload/)
|
||||
expect(session.snapshotEvents()).toEqual(events)
|
||||
expect(marked(session)).toEqual([false, false])
|
||||
expect(session.surface.contentGeneration).toBe(0)
|
||||
@@ -119,7 +125,7 @@ describe('durable image selections', () => {
|
||||
})
|
||||
|
||||
it('rejects repeated offloads and shadowed or assistant targets', () => {
|
||||
const session = Session.create(SessionId('invalid-targets'))
|
||||
const session = createSession(SessionId('invalid-targets'))
|
||||
const source = input(session)
|
||||
session.append('image/offload', { targets: [{ seq: source.seq, imageIndexes: [0] }] })
|
||||
expect(() => session.append('image/offload', { targets: [{ seq: source.seq, imageIndexes: [0] }] })).toThrow(/already offloaded/)
|
||||
@@ -8,11 +8,26 @@
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../../vendor/cosmokit" },
|
||||
{ "path": "../../../vendor/cordis" },
|
||||
{ "path": "../compaction" },
|
||||
{ "path": "../../llm/llm" },
|
||||
{ "path": "../../core/session" },
|
||||
{ "path": "../../core/agent" }
|
||||
{
|
||||
"path": "../../../vendor/cosmokit"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../compaction"
|
||||
},
|
||||
{
|
||||
"path": "../../llm/llm"
|
||||
},
|
||||
{
|
||||
"path": "../../core/session"
|
||||
},
|
||||
{
|
||||
"path": "../../core/agent"
|
||||
},
|
||||
{
|
||||
"path": "../../util/values"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -46,6 +46,7 @@
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-token-meter": "workspace:^"
|
||||
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
||||
"@deepseek-ai/dsh-compaction-image-offload": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { imageOffloadProjection } from '@deepseek-ai/dsh-compaction-image-offload/projection'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
@@ -80,7 +81,7 @@ function appendToolStep(
|
||||
|
||||
describe('tool-result pruning configuration', () => {
|
||||
it('preserves a logged image offload when pruning the same result later', () => {
|
||||
const session = Session.create(SessionId('prune-offloaded'))
|
||||
const session = Session.create(SessionId('prune-offloaded'), undefined, undefined, undefined, [imageOffloadProjection])
|
||||
const seq = appendToolStep(session, 1, 'shot', [
|
||||
{ type: 'text', text: 'x'.repeat(200) },
|
||||
{ type: 'image', attachment: {
|
||||
@@ -93,7 +94,7 @@ describe('tool-result pruning configuration', () => {
|
||||
const replacement = session.snapshotEvents().at(-1)!
|
||||
expect(replacement.type).toBe('tool/result')
|
||||
expect(JSON.stringify(session.deriveEventMessage(replacement))).toContain('"offloaded":true')
|
||||
expect(JSON.stringify(Session.create(SessionId('restored-offloaded'), session.snapshotEvents()).deriveMessages())).toContain('"offloaded":true')
|
||||
expect(JSON.stringify(Session.create(SessionId('restored-offloaded'), session.snapshotEvents(), undefined, undefined, [imageOffloadProjection]).deriveMessages())).toContain('"offloaded":true')
|
||||
expect(JSON.stringify(session.eventAt(SessionSeq(seq)))).not.toContain('offloaded')
|
||||
})
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant
|
||||
openTurn: null,
|
||||
compaction: undefined,
|
||||
surfaceEvents,
|
||||
surface: new SurfaceManager(surfaceEvents),
|
||||
surface: new SurfaceManager(surfaceEvents, undefined, ctx.sessions.messageProjections),
|
||||
}
|
||||
traces.set(session, trace)
|
||||
// oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/core/session/README.md
|
||||
README.md: 0fbede71e77b1e5ff5e46363112136eaf75f86e0
|
||||
README.zh.md: 32c0702500cb5671eced07ca48601365157b809f
|
||||
README.md: 98e7744aa76a73148e30a56c34b44c4c5d52bd0e
|
||||
README.zh.md: 28b6f1f02838420861a76409046c0e9a12bac1b5
|
||||
@@ -49,7 +49,7 @@ session.deriveMessages() // the derived model history
|
||||
|
||||
Surface events (`system/message`, `user/message`, `assistant/message`, `tool/result`) require `surfaceOp` in both typed events and append input. A replacement uses exactly `{ op: 'replace', startSeq, endSeq }`, with inclusive `SessionSeq` endpoints in current surface order. An Assistant message embeds its exact compact provider stream and forbids `sourceEventSeqs`. Known log-only events forbid both metadata fields and never produce a message.
|
||||
|
||||
`image/offload` records exact input-image occurrences without creating or replacing a message. Session rejects missing, shadowed, output-image, or already offloaded targets before commit. `deriveMessages()` and the instance `deriveEventMessage()` apply the selections; rewrites preserving images must use these projected messages. Pure reconstructors pass `foldSurface(events).offloadedMessages` to the exported `deriveEventMessage()`.
|
||||
Plugins declare content-changing events with `@messageProjection` and register a pure definition through `ctx.sessions.registerMessageProjection()`. Session calls the definition before accepting an event and caches its immutable message updates. Missing definitions reject append and restore; unloading a used definition also blocks cached reads. Detached constructors and `foldSurface(events, projections)` require explicit definitions. Reconstructors pass the fold's `projectedMessages` to `deriveEventMessage()`; live instance methods apply the same projections automatically. [Plugin-owned message projections](../../../.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md) defines ownership and offline assembly.
|
||||
|
||||
Append, seed/restore, and event adoption/snapshot reject any `header.system` and exactly empty optional request-header fields (`tools: []`, `adapterDefaults: {}`) instead of normalizing input. Tool-result `data.error` is allowed only when `message.content[0].isError === true`; failure identity remains optional. Rejected appends do not change the log, derived state, or event feed. Adoption validates event-local metadata but not referenced history or replacement membership.
|
||||
|
||||
@@ -83,11 +83,11 @@ This section explains how the package realizes the behavior above; the observabl
|
||||
|
||||
### Design concept
|
||||
|
||||
The package is built on event sourcing: a `Session` is an append-only log of typed `SessionEvent`s, and everything else — model history, transcripts, telemetry, titles, persistence — derives from that stream. The surface is a derived projection: an incremental manager validates append candidates, advances the ordered view from committed events, and tracks `replaceGeneration` for positional replacements and `contentGeneration` for replacements and image-offload decisions. Model-visible means logged: anything that reaches a model request must be reconstructable from the log. Each model attempt that reaches settlement commits one event: `assistant/message` carries the assembled model-visible message plus its compact timed stream, while `assistant/attempt` retains a failed, retried, cancelled, or stream-error attempt without adding model history. A hard process loss before settlement leaves no durable attempt stream.
|
||||
The package is built on event sourcing: a `Session` is an append-only log of typed `SessionEvent`s, and everything else — model history, transcripts, telemetry, titles, persistence — derives from that stream. The surface is a derived projection: an incremental manager validates append candidates, advances the ordered view from committed events, and tracks `replaceGeneration` for positional replacements and `contentGeneration` for replacements and plugin-owned message changes. Model-visible means logged: anything that reaches a model request must be reconstructable from the log. Each model attempt that reaches settlement commits one event: `assistant/message` carries the assembled model-visible message plus its compact timed stream, while `assistant/attempt` retains a failed, retried, cancelled, or stream-error attempt without adding model history. A hard process loss before settlement leaves no durable attempt stream.
|
||||
|
||||
### Request headers
|
||||
|
||||
`request/header` stores a full canonical snapshot of the non-history request envelope with reason `initial`, `resume`, `change`, or `series`. An explicit message-series start, a surface replacement, or an image-offload decision writes a `series` snapshot when the envelope is unchanged; a simultaneous change uses `startsSeries: true`. Same-series steps, retries, and ordinary later turns inherit the latest snapshot. `adapterDefaults` distinguishes values resolved by the adapter from explicit settings, and `foldRequestHeader()` selects the latest snapshot. This self-contained record supports partial-window rendering and exact reconstruction at the cost of growth per message series; the [reconstructable-requests Agent Note](../../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md) owns the detail.
|
||||
`request/header` stores a full canonical snapshot of the non-history request envelope with reason `initial`, `resume`, `change`, or `series`. An explicit message-series start, a surface replacement, or a plugin-owned message change writes a `series` snapshot when the envelope is unchanged; a simultaneous change uses `startsSeries: true`. Same-series steps, retries, and ordinary later turns inherit the latest snapshot. `adapterDefaults` distinguishes values resolved by the adapter from explicit settings, and `foldRequestHeader()` selects the latest snapshot. This self-contained record supports partial-window rendering and exact reconstruction at the cost of growth per message series; the [reconstructable-requests Agent Note](../../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md) owns the detail.
|
||||
|
||||
### Source map
|
||||
|
||||
@@ -107,7 +107,7 @@ Every append uses the shared iterative `snapshotJsonValue()` pass, which reads,
|
||||
|
||||
### Derived history
|
||||
|
||||
`deriveMessages()` caches deep-frozen projections and returns a fresh array per call. The four surface event types (`system/message`, `user/message`, `assistant/message`, `tool/result`) supply their recorded message identities and content; an empty-content system node projects to no message. Image-offload selections mark derived input-image blocks without mutating recorded messages. Replacements and offload decisions invalidate the cache. Embedded Assistant streams and `assistant/attempt` events remain replay and diagnostic data only.
|
||||
`deriveMessages()` caches deep-frozen projections and returns a fresh array per call. The four surface event types (`system/message`, `user/message`, `assistant/message`, `tool/result`) supply their recorded message identities and content; an empty-content system node projects to no message. Plugin-owned projections change derived content without mutating recorded messages. Replacements and projection decisions invalidate the cache. Embedded Assistant streams and `assistant/attempt` events remain replay and diagnostic data only.
|
||||
|
||||
### The request header
|
||||
|
||||
@@ -137,7 +137,7 @@ The package-level contract is enough for most consumers; read these when you nee
|
||||
|
||||
#### What the model sees
|
||||
|
||||
The model receives the complete messages from `system/message`, `user/message`, `assistant/message`, and `tool/result` surface entries with logged image selections applied, the system prompt first. Identities, roles, sources, and unmodified blocks retain their original values; projections never mint identities. Direct prompts and injected context remain separate `user/message` events whose sources preserve their provenance. Embedded streams, `assistant/attempt`, boundaries, and other log-only facts add no message.
|
||||
The model receives the complete messages from `system/message`, `user/message`, `assistant/message`, and `tool/result` surface entries with logged projections applied, the system prompt first. Identities, roles, sources, and unmodified blocks retain their original values; projections never mint identities. Direct prompts and injected context remain separate `user/message` events whose sources preserve their provenance. Embedded streams, `assistant/attempt`, boundaries, and other log-only facts add no message.
|
||||
|
||||
#### Token effect
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ session.deriveMessages() // the derived model history
|
||||
|
||||
表层事件(`system/message`、`user/message`、`assistant/message`、`tool/result`)在类型化事件与追加输入中都必须带有 `surfaceOp`。替换操作仅接受 `{ op: 'replace', startSeq, endSeq }`,端点为包含边界的 `SessionSeq`,按当前 surface 顺序解释。assistant 消息会嵌入精确、紧凑的提供方流,并禁止 `sourceEventSeqs`。已知仅日志事件禁止这两个元数据字段,且从不产生消息。
|
||||
|
||||
`image/offload` 记录确切的输入图片出现位置,不创建或替换消息。Session 在提交前拒绝缺失、已被遮蔽、属于输出图片或已被省略的目标。`deriveMessages()` 和实例方法 `deriveEventMessage()` 应用这些选择,需要保留图片的改写必须读取这些派生消息。纯重建函数将 `foldSurface(events).offloadedMessages` 传给导出的 `deriveEventMessage()`。
|
||||
插件用 `@messageProjection` 声明修改内容的事件,并通过 `ctx.sessions.registerMessageProjection()` 注册纯处理器。Session 在接受事件前调用处理器,并缓存其不可变消息更新。缺少处理器时拒绝追加和恢复,卸载已经使用的处理器后也会拒绝读取缓存。独立构造函数和 `foldSurface(events, projections)` 必须显式接收处理器。重建函数将折叠结果的 `projectedMessages` 传给 `deriveEventMessage()`,实时实例方法自动应用相同的投影。[插件拥有消息投影](../../../.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md)说明职责划分和离线装配。
|
||||
|
||||
追加、seed/restore 与事件 adoption/snapshot 会拒绝任何 `header.system` 及恰好为空的可选请求头字段(`tools: []`、`adapterDefaults: {}`),而不规范化输入。工具结果的 `data.error` 仅在 `message.content[0].isError === true` 时允许存在;失败标识仍是可选的。被拒绝的追加不会改变日志、派生状态或事件流。Adoption 校验事件局部元数据,但不校验所引用的历史或替换端点是否属于 surface。
|
||||
|
||||
@@ -83,11 +83,11 @@ session.deriveMessages() // the derived model history
|
||||
|
||||
### 设计理念
|
||||
|
||||
该包建立在事件溯源之上:`Session` 是类型化 `SessionEvent` 的仅追加日志,其他一切——模型历史、transcript(文本记录)、遥测、标题、持久化——都从这条流派生。surface 是派生投影:一个增量管理器校验追加候选、根据已提交事件推进有序视图,通过 `replaceGeneration` 跟踪位置替换,通过 `contentGeneration` 跟踪位置替换和图片省略决定。模型可见即已记录:任何到达模型请求的内容都必须能从日志重建。每个完成结算的模型尝试都会提交一个事件:`assistant/message` 携带组装后的模型可见 message 及其紧凑带时间 stream,`assistant/attempt` 则保留失败、重试、取消或 stream error attempt,且不添加模型历史。如果进程在 settlement 前硬中断,则不会留下持久 attempt stream。
|
||||
该包建立在事件溯源之上:`Session` 是类型化 `SessionEvent` 的仅追加日志,其他一切——模型历史、transcript(文本记录)、遥测、标题、持久化——都从这条流派生。surface 是派生投影:一个增量管理器校验追加候选、根据已提交事件推进有序视图,通过 `replaceGeneration` 跟踪位置替换,通过 `contentGeneration` 跟踪位置替换和插件拥有的消息变更。模型可见即已记录:任何到达模型请求的内容都必须能从日志重建。每个完成结算的模型尝试都会提交一个事件:`assistant/message` 携带组装后的模型可见 message 及其紧凑带时间 stream,`assistant/attempt` 则保留失败、重试、取消或 stream error attempt,且不添加模型历史。如果进程在 settlement 前硬中断,则不会留下持久 attempt stream。
|
||||
|
||||
### 请求头
|
||||
|
||||
`request/header` 存储非历史请求封装的完整规范快照,原因为 `initial`、`resume`、`change` 或 `series`。显式消息序列起点、表层替换或图片省略决定会在请求封装不变时写入 `series` 快照;同时发生变化时使用 `startsSeries: true`。同一序列内的步骤、重试与普通后续轮次继承最新快照。`adapterDefaults` 区分由适配器解析的值与显式设置,`foldRequestHeader()` 选择最新快照。这种自包含记录以每个消息序列增加存储为代价,支持局部窗口渲染与精确重建;细节由[可重建请求 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md)负责。
|
||||
`request/header` 存储非历史请求封装的完整规范快照,原因为 `initial`、`resume`、`change` 或 `series`。显式消息序列起点、表层替换或插件拥有的消息变更会在请求封装不变时写入 `series` 快照;同时发生变化时使用 `startsSeries: true`。同一序列内的步骤、重试与普通后续轮次继承最新快照。`adapterDefaults` 区分由适配器解析的值与显式设置,`foldRequestHeader()` 选择最新快照。这种自包含记录以每个消息序列增加存储为代价,支持局部窗口渲染与精确重建;细节由[可重建请求 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md)负责。
|
||||
|
||||
### 源码地图
|
||||
|
||||
@@ -107,7 +107,7 @@ session.deriveMessages() // the derived model history
|
||||
|
||||
### 派生历史
|
||||
|
||||
`deriveMessages()` 缓存深度冻结的派生消息,每次调用返回新数组。四种 surface 事件类型(`system/message`、`user/message`、`assistant/message`、`tool/result`)提供记录的消息身份和内容,空内容的系统节点不派生消息。图片省略选择为派生的输入图片块添加标记,不修改记录的消息。替换和省略决策使缓存失效。嵌入式 Assistant stream 与 `assistant/attempt` 事件只保留回放和诊断数据。
|
||||
`deriveMessages()` 缓存深度冻结的派生消息,每次调用返回新数组。四种 surface 事件类型(`system/message`、`user/message`、`assistant/message`、`tool/result`)提供记录的消息身份和内容,空内容的系统节点不派生消息。插件拥有的投影修改派生内容,不修改记录的消息。替换和投影决策使缓存失效。嵌入式 Assistant stream 与 `assistant/attempt` 事件只保留回放和诊断数据。
|
||||
|
||||
### 请求头
|
||||
|
||||
@@ -137,7 +137,7 @@ session.deriveMessages() // the derived model history
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
模型会接收 `system/message`、`user/message`、`assistant/message` 与 `tool/result` surface 条目中的消息,并应用日志中的图片省略选择,系统提示词在先。消息标识、角色和来源保持不变,投影不生成标识。直接提示词与注入上下文仍是独立的 `user/message` 事件,各事件的来源保留其出处。嵌入式 stream、`assistant/attempt`、边界与其他仅日志事实不添加消息。
|
||||
模型会接收 `system/message`、`user/message`、`assistant/message` 与 `tool/result` surface 条目中的消息,并应用日志中的投影,系统提示词在先。消息标识、角色、来源及未修改的内容块保持不变,投影不生成标识。直接提示词与注入上下文仍是独立的 `user/message` 事件,各事件的来源保留其出处。嵌入式 stream、`assistant/attempt`、边界与其他仅日志事实不添加消息。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { SESSION_FORMAT_VERSION, SessionLogOffset, SessionSeq } from './types.ts
|
||||
import type { TypertLookup } from '@deepseek-ai/dsh-typert-protocol'
|
||||
import type { CreateSessionOptions, EpochHeader, PrepareSessionOptions, RequestContext, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SessionId, SessionSeedEventState, SurfaceIntent, SurfaceEventType } from './types.ts'
|
||||
import { SurfaceManager, validateSessionEventData, validateSurfaceMetadata } from './surface.ts'
|
||||
import type { SessionSurface } from './surface.ts'
|
||||
import type { SessionSurface, SessionMessageProjection } from './surface.ts'
|
||||
import { foldRequestHeader } from './request-header.ts'
|
||||
|
||||
export * from './types.ts'
|
||||
@@ -25,7 +25,7 @@ export { SessionPreparation } from './preparation.ts'
|
||||
export type { SessionPreparationOptions } from './preparation.ts'
|
||||
export type { AssistantMessage, SystemMessage, ToolResultMessage, UserMessage } from '@deepseek-ai/dsh-llm'
|
||||
export { interruptedTurnClosers, TOOL_NOT_STARTED, TOOL_OUTCOME_UNKNOWN } from './repair.ts'
|
||||
export type { SessionSurface, SurfaceFoldReplacement, SurfaceFoldResult } from './surface.ts'
|
||||
export type { SessionSurface, SurfaceFoldReplacement, SurfaceFoldResult, SessionMessageProjection, SessionMessageProjectionContext } from './surface.ts'
|
||||
export { deriveEventMessage, foldSurface, isAppendSurfaceEvent, isReplacementSurfaceEvent, isSurfaceEvent, isSurfaceEligibleType } from './surface.ts'
|
||||
export { canonicalHeader, foldRequestHeader, headerEquals } from './request-header.ts'
|
||||
export { KNOWN_SESSION_EVENT_TYPES } from './known-event-types.ts'
|
||||
@@ -446,7 +446,7 @@ const attachments = new WeakMap<Session, SessionEntry>()
|
||||
export class Session {
|
||||
private log: SessionEvent[] = []
|
||||
/** Single incremental owner of surface acceptance and projection state. */
|
||||
private readonly surfaceManager = new SurfaceManager(this.log)
|
||||
private readonly surfaceManager: SurfaceManager
|
||||
|
||||
/** The ordered surface over this session's event log. */
|
||||
get surface(): SessionSurface {
|
||||
@@ -503,15 +503,18 @@ export class Session {
|
||||
* @param seed - optional borrowed replay or fork events.
|
||||
* @param header - optional borrowed storage metadata.
|
||||
* @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
|
||||
* @param projections - pure interpreters for plugin-owned message changes.
|
||||
* @returns a detached session.
|
||||
* @throws when a seed event requires a missing message interpreter or fails validation.
|
||||
*/
|
||||
static create(
|
||||
id: SessionId,
|
||||
seed?: readonly SessionEvent[],
|
||||
header?: SessionHeader,
|
||||
inheritedEventCount?: SessionLogOffset,
|
||||
projections?: readonly SessionMessageProjection[],
|
||||
): Session {
|
||||
return new Session(id, seed, header, 'snapshot', inheritedEventCount)
|
||||
return new Session(id, seed, header, 'snapshot', inheritedEventCount, projections)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,7 +528,9 @@ export class Session {
|
||||
* @param header - independently owned storage metadata.
|
||||
* @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
|
||||
* @param eventState - aliasing state carried from the operation that produced the seed.
|
||||
* @param projections - pure interpreters for plugin-owned message changes.
|
||||
* @returns a restored detached session.
|
||||
* @throws when a seed event requires a missing message interpreter or fails validation.
|
||||
*/
|
||||
static fromRestore(
|
||||
id: SessionId,
|
||||
@@ -533,6 +538,7 @@ export class Session {
|
||||
header: SessionHeader,
|
||||
inheritedEventCount: SessionLogOffset,
|
||||
eventState: SessionSeedEventState,
|
||||
projections?: readonly SessionMessageProjection[],
|
||||
): Session {
|
||||
return new Session(
|
||||
id,
|
||||
@@ -540,6 +546,7 @@ export class Session {
|
||||
header,
|
||||
eventState,
|
||||
inheritedEventCount,
|
||||
projections,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -549,7 +556,9 @@ export class Session {
|
||||
header?: SessionHeader,
|
||||
mode: 'snapshot' | SessionSeedEventState = 'snapshot',
|
||||
suppliedInheritedEventCount?: SessionLogOffset,
|
||||
projections: readonly SessionMessageProjection[] = [],
|
||||
) {
|
||||
this.surfaceManager = new SurfaceManager(this.log, SessionLogOffset(0), projections)
|
||||
const restoredHeader = mode === 'snapshot' ? undefined : validateRestoredSessionHeader(id, header)
|
||||
if (seed !== undefined) {
|
||||
// Validate the seed to the SAME invariants `append` enforces, so a
|
||||
@@ -818,14 +827,14 @@ export class Session {
|
||||
* append records its `surfaceOp`, so a raw event with no marker (a chunk, a
|
||||
* turn boundary) is correctly absent, and a compaction `replace` deletes the
|
||||
* shadowed nodes from the derivation. The projection rules are
|
||||
* {@link deriveEventMessage}, with logged image-offload selections applied
|
||||
* {@link deriveEventMessage}, with logged message projections applied
|
||||
* without changing node membership or message identity.
|
||||
*
|
||||
* CACHED: pure tail growth costs O(new nodes); a replacement or image offload
|
||||
* CACHED: pure tail growth costs O(new nodes); a replacement or message projection
|
||||
* ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
|
||||
* a fresh snapshot per call (later appends never grow an array a caller
|
||||
* already holds); the `Message` objects in it are SHARED and **deep-frozen**.
|
||||
* Unchanged content reuses frozen event data; offloaded blocks are frozen
|
||||
* Unchanged content reuses frozen event data; projected blocks are frozen
|
||||
* derived copies. Consumers cannot mutate the log through either form.
|
||||
* @returns a fresh array of the shared, frozen derived history.
|
||||
*/
|
||||
@@ -853,7 +862,7 @@ export class Session {
|
||||
}
|
||||
|
||||
/**
|
||||
* Project one event with all committed image-offload selections applied.
|
||||
* Project one event with all committed message projections applied.
|
||||
* The original durable event remains unchanged.
|
||||
* @param event - the event to project.
|
||||
* @returns the derived message, or null when the event produces none.
|
||||
@@ -899,6 +908,29 @@ export class SessionForkError extends Error {
|
||||
export class SessionStore extends Service {
|
||||
private store = new Map<SessionId, SessionEntry>()
|
||||
private counter = 0
|
||||
private readonly projections: SessionMessageProjection[] = []
|
||||
|
||||
/** Borrowed definitions for detached replay; contributions live until their registering fibers unload. */
|
||||
get messageProjections(): readonly SessionMessageProjection[] {
|
||||
return this.projections
|
||||
}
|
||||
|
||||
/**
|
||||
* Register one event interpreter for live creation, restore, and fork.
|
||||
* Disposing the contribution makes sessions that used it refuse further derivation.
|
||||
* @param projection - pure definition owned by the event's plugin.
|
||||
* @returns the fiber-owned disposer.
|
||||
* @throws when another definition already owns this event type.
|
||||
*/
|
||||
registerMessageProjection(projection: SessionMessageProjection): () => void {
|
||||
if (this.projections.some(item => item.type === projection.type)) {
|
||||
throw new Error(`session message projection "${projection.type}" is already registered`)
|
||||
}
|
||||
return this.ctx.effect(() => {
|
||||
this.projections.push(projection)
|
||||
return () => { this.projections.splice(this.projections.indexOf(projection), 1) }
|
||||
}, 'sessions.registerMessageProjection()')
|
||||
}
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'sessions')
|
||||
@@ -986,6 +1018,7 @@ export class SessionStore extends Service {
|
||||
options.meta,
|
||||
options.inheritedEventCount,
|
||||
eventState,
|
||||
this.projections,
|
||||
)
|
||||
case undefined:
|
||||
break
|
||||
@@ -1007,7 +1040,7 @@ export class SessionStore extends Service {
|
||||
...meta?.delegationDepth === undefined ? {} : { delegationDepth: meta.delegationDepth },
|
||||
...meta?.agentPreset === undefined ? {} : { agentPreset: meta.agentPreset },
|
||||
}
|
||||
return Session.create(sessionId, seed, header, options?.inheritedEventCount)
|
||||
return Session.create(sessionId, seed, header, options?.inheritedEventCount, this.projections)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,3 +78,8 @@ export const KNOWN_SESSION_EVENT_TYPES: ReadonlySet<string> = new Set([
|
||||
'user/message',
|
||||
'web/deepseek-search-llm-request',
|
||||
])
|
||||
|
||||
/** Event types whose model-visible effects require an explicit pure interpreter. */
|
||||
export const MESSAGE_PROJECTION_EVENT_TYPES: ReadonlySet<string> = new Set([
|
||||
'image/offload',
|
||||
])
|
||||
@@ -10,15 +10,42 @@
|
||||
|
||||
import type { Message } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionLogOffset, SessionSeq } from './types.ts'
|
||||
import { KNOWN_SESSION_EVENT_TYPES } from './known-event-types.ts'
|
||||
import { offloadMessageImages } from './image-offload.ts'
|
||||
import { KNOWN_SESSION_EVENT_TYPES, MESSAGE_PROJECTION_EVENT_TYPES } from './known-event-types.ts'
|
||||
import type {
|
||||
SessionEvent,
|
||||
SessionEventType,
|
||||
SessionSeqCursor,
|
||||
SurfaceEvent,
|
||||
SurfaceOp,
|
||||
} from './types.ts'
|
||||
|
||||
/** Readonly history immediately before a message-projection event. */
|
||||
export interface SessionMessageProjectionContext {
|
||||
/** Current message-producing event sequences in model-visible order. */
|
||||
nodes: readonly SessionSeq[]
|
||||
/** Contiguous event window; entries at or beyond the candidate seq are not committed inputs. */
|
||||
events: readonly SessionEvent[]
|
||||
/** Absolute sequence of the window's first event. */
|
||||
baseSeq: SessionLogOffset
|
||||
/** Previously projected messages keyed by their original event sequences. */
|
||||
messages: ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
|
||||
/** Pure interpretation of one plugin-owned event that changes existing message content. */
|
||||
export interface SessionMessageProjection<T extends SessionEventType = SessionEventType> {
|
||||
/** Event interpreted by this definition; declare it with `@messageProjection` in SessionEventMap. */
|
||||
type: T
|
||||
/**
|
||||
* Validate the complete durable decision before returning any updates. Preserve
|
||||
* message identities and publish immutable copies without mutating the input.
|
||||
* @param event - candidate event, not yet applied to the supplied history.
|
||||
* @param context - history preceding this decision.
|
||||
* @returns changed current messages keyed by their original sequences.
|
||||
* @throws when the durable decision cannot be applied to this history.
|
||||
*/
|
||||
project(event: SessionEvent<T>, context: SessionMessageProjectionContext): ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
|
||||
/** Runtime counterpart of the message-producing event union. */
|
||||
const SURFACE_EVENT_TYPES = new Set<string>([
|
||||
'system/message',
|
||||
@@ -81,19 +108,19 @@ export function isReplacementSurfaceEvent(
|
||||
* Project a single event into the LLM message it derives to, or null when it
|
||||
* produces none — a non-surface event (attempt, boundary, log-only record) or an
|
||||
* empty-content assistant/message (which exists only to host usage). A caller
|
||||
* reconstructing model input supplies the same prefix's `offloadedMessages`
|
||||
* reconstructing model input supplies the same prefix's `projectedMessages`
|
||||
* from {@link foldSurface}; without that map this function reads original
|
||||
* event content. Session instance methods apply the live projection. Messages
|
||||
* are immutable and unchanged content retains its durable identity.
|
||||
* @param event - the event to project.
|
||||
* @param offloadedMessages - image projections from the same log prefix's surface fold.
|
||||
* @param projectedMessages - message projections from the same log prefix's surface fold.
|
||||
* @returns the derived message, or null when the event produces none.
|
||||
*/
|
||||
export function deriveEventMessage(
|
||||
event: SessionEvent,
|
||||
offloadedMessages?: ReadonlyMap<SessionSeq, Message>,
|
||||
projectedMessages?: ReadonlyMap<SessionSeq, Message>,
|
||||
): Message | null {
|
||||
const projected = offloadedMessages?.get(event.seq)
|
||||
const projected = projectedMessages?.get(event.seq)
|
||||
if (projected !== undefined) return projected
|
||||
// Intentionally non-exhaustive: only message-producing events derive
|
||||
// history; turn/step boundaries, failed attempts, and errors are trace/replay
|
||||
@@ -189,8 +216,8 @@ export interface SurfaceFoldResult {
|
||||
nodes: SessionSeq[]
|
||||
/** Replacement operations in event order. */
|
||||
replacements: SurfaceFoldReplacement[]
|
||||
/** Immutable image-offloaded messages, keyed by their original event sequences. */
|
||||
offloadedMessages: ReadonlyMap<SessionSeq, Message>
|
||||
/** Immutable projected messages, keyed by their original event sequences. */
|
||||
projectedMessages: ReadonlyMap<SessionSeq, Message>
|
||||
}
|
||||
|
||||
/** Readonly live projection of the message-producing session events. */
|
||||
@@ -199,7 +226,7 @@ export interface SessionSurface {
|
||||
readonly nodes: readonly SessionSeq[]
|
||||
/** Monotonic count of committed positional replacements. */
|
||||
readonly replaceGeneration: number
|
||||
/** Monotonic count of committed replacements and image-offload decisions. */
|
||||
/** Monotonic count of committed replacements and plugin-owned message changes. */
|
||||
readonly contentGeneration: number
|
||||
}
|
||||
|
||||
@@ -208,7 +235,8 @@ interface SurfaceFoldState {
|
||||
nodes: SessionSeq[]
|
||||
replaceGeneration: number
|
||||
contentGeneration: number
|
||||
offloadedMessages: Map<SessionSeq, Message>
|
||||
projectedMessages: Map<SessionSeq, Message>
|
||||
projections: Set<SessionMessageProjection>
|
||||
}
|
||||
|
||||
/** A validated replacement transition that has not mutated fold state yet. */
|
||||
@@ -222,50 +250,11 @@ interface SurfaceReplacePlan extends SurfaceFoldReplacement {
|
||||
type SurfacePlan =
|
||||
| { kind: 'append'; seq: SessionSeq }
|
||||
| SurfaceReplacePlan
|
||||
| { kind: 'image-offload'; messages: ReadonlyMap<SessionSeq, Message> }
|
||||
| { kind: 'project'; projection: SessionMessageProjection; messages: ReadonlyMap<SessionSeq, Message> }
|
||||
|
||||
/** Create an empty surface fold state. */
|
||||
function createFoldState(): SurfaceFoldState {
|
||||
return { nodes: [], replaceGeneration: 0, contentGeneration: 0, offloadedMessages: new Map() }
|
||||
}
|
||||
|
||||
/** Validate exact image references before publishing any part of the decision. */
|
||||
function planImageOffload(
|
||||
state: SurfaceFoldState,
|
||||
event: SessionEvent<'image/offload'>,
|
||||
events: readonly SessionEvent[],
|
||||
baseSeq: SessionLogOffset,
|
||||
): Extract<SurfacePlan, { kind: 'image-offload' }> {
|
||||
const data: unknown = event.data
|
||||
if (!isRecord(data) || Object.keys(data).length !== 1 || !Array.isArray(data['targets']) || data['targets'].length === 0) {
|
||||
throw new Error('image/offload: data must contain a nonempty targets array')
|
||||
}
|
||||
const messages = new Map<SessionSeq, Message>()
|
||||
const nodes = new Set(state.nodes)
|
||||
for (const target of data['targets'] as unknown[]) {
|
||||
if (!isRecord(target) || Object.keys(target).length !== 2 || !isEventSeq(target['seq'])
|
||||
|| !Array.isArray(target['imageIndexes']) || target['imageIndexes'].length === 0) {
|
||||
throw new Error('image/offload: each target must contain a seq and nonempty imageIndexes')
|
||||
}
|
||||
const seq = target['seq']
|
||||
if (messages.has(seq)) throw new Error(`image/offload: duplicate target seq ${seq}`)
|
||||
if (!nodes.has(seq)) throw new Error(`image/offload: target seq ${seq} is not a current surface node`)
|
||||
const source = events[seq - baseSeq]
|
||||
if (source?.type !== 'user/message' && source?.type !== 'tool/result') {
|
||||
throw new Error(`image/offload: target seq ${seq} must be user/message or tool/result`)
|
||||
}
|
||||
let previous = -1
|
||||
for (const index of target['imageIndexes'] as unknown[]) {
|
||||
if (!isEventSeq(index) || index <= previous) {
|
||||
throw new Error('image/offload: imageIndexes must be strictly increasing non-negative safe integers')
|
||||
}
|
||||
previous = index
|
||||
}
|
||||
const message = state.offloadedMessages.get(seq)
|
||||
?? (source.type === 'user/message' ? source.data : source.data.message)
|
||||
messages.set(seq, offloadMessageImages(message, target['imageIndexes'] as number[]))
|
||||
}
|
||||
return { kind: 'image-offload', messages }
|
||||
return { nodes: [], replaceGeneration: 0, contentGeneration: 0, projectedMessages: new Map(), projections: new Set() }
|
||||
}
|
||||
|
||||
/** Whether a runtime value is a non-negative safe event sequence. */
|
||||
@@ -475,12 +464,21 @@ function planSurfaceEvent(
|
||||
expectedSeq: SessionSeq,
|
||||
events: readonly SessionEvent[],
|
||||
baseSeq: SessionLogOffset,
|
||||
projections: readonly SessionMessageProjection[],
|
||||
): SurfacePlan | undefined {
|
||||
if (event.seq !== expectedSeq) {
|
||||
throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`)
|
||||
}
|
||||
const surfaceOp = validateSurfaceMetadata(event)
|
||||
if (event.type === 'image/offload') return planImageOffload(state, event, events, baseSeq)
|
||||
const projection = projections.find(item => item.type === event.type)
|
||||
if (projection !== undefined) {
|
||||
return { kind: 'project', projection, messages: projection.project(event, {
|
||||
nodes: state.nodes, events, baseSeq, messages: state.projectedMessages,
|
||||
}) }
|
||||
}
|
||||
if (MESSAGE_PROJECTION_EVENT_TYPES.has(event.type)) {
|
||||
throw new Error(`session event "${event.type}" requires a message projection; load its owning plugin or supply its projection definition`)
|
||||
}
|
||||
if (surfaceOp === undefined) return
|
||||
if (surfaceOp === 'append') {
|
||||
return { kind: 'append', seq: event.seq }
|
||||
@@ -505,8 +503,9 @@ function applySurfaceEvent(
|
||||
expectedSeq: SessionSeq,
|
||||
events: readonly SessionEvent[],
|
||||
baseSeq: SessionLogOffset,
|
||||
projections: readonly SessionMessageProjection[],
|
||||
): SurfaceFoldReplacement | undefined {
|
||||
const plan = planSurfaceEvent(state, event, expectedSeq, events, baseSeq)
|
||||
const plan = planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projections)
|
||||
return applySurfacePlan(state, plan)
|
||||
}
|
||||
|
||||
@@ -521,8 +520,9 @@ function applySurfacePlan(
|
||||
state.nodes.splice(plan.startIdx, plan.endIdx - plan.startIdx + 1, plan.seq)
|
||||
state.replaceGeneration += 1
|
||||
state.contentGeneration += 1
|
||||
} else if (plan?.kind === 'image-offload') {
|
||||
for (const [seq, message] of plan.messages) state.offloadedMessages.set(seq, message)
|
||||
} else if (plan?.kind === 'project') {
|
||||
for (const [seq, message] of plan.messages) state.projectedMessages.set(seq, message)
|
||||
state.projections.add(plan.projection)
|
||||
state.contentGeneration += 1
|
||||
}
|
||||
if (plan?.kind !== 'replace') return
|
||||
@@ -537,10 +537,11 @@ function applySurfacePlan(
|
||||
/**
|
||||
* Replay a complete session log through the canonical surface fold.
|
||||
* @param events - session events in contiguous seq order.
|
||||
* @param projections - pure interpreters for plugin-owned message changes; required definitions must be supplied.
|
||||
* @returns detached current sequences and replacement history.
|
||||
* @throws when an event violates surface metadata, source-event references, range, or tool-result rewrite rules.
|
||||
* @throws when an interpreter is missing or an event violates its projection, surface metadata, provenance, or replacement rules.
|
||||
*/
|
||||
export function foldSurface(events: readonly SessionEvent[]): SurfaceFoldResult {
|
||||
export function foldSurface(events: readonly SessionEvent[], projections: readonly SessionMessageProjection[] = []): SurfaceFoldResult {
|
||||
const state = createFoldState()
|
||||
const replacements: SurfaceFoldReplacement[] = []
|
||||
for (const [index, event] of events.entries()) {
|
||||
@@ -550,10 +551,11 @@ export function foldSurface(events: readonly SessionEvent[]): SurfaceFoldResult
|
||||
SessionSeq(index),
|
||||
events,
|
||||
SessionLogOffset(0),
|
||||
projections,
|
||||
)
|
||||
if (replacement !== undefined) replacements.push(replacement)
|
||||
}
|
||||
return { nodes: [...state.nodes], replacements, offloadedMessages: new Map(state.offloadedMessages) }
|
||||
return { nodes: [...state.nodes], replacements, projectedMessages: new Map(state.projectedMessages) }
|
||||
}
|
||||
|
||||
/** Incremental ordered surface view and append-boundary validator. */
|
||||
@@ -568,10 +570,12 @@ export class SurfaceManager implements SessionSurface {
|
||||
/**
|
||||
* @param log - Contiguous complete log or loaded event window.
|
||||
* @param baseSeq - Absolute sequence of the window's first event.
|
||||
* @param projections - live borrowed definitions; removing a used definition invalidates further reads.
|
||||
*/
|
||||
constructor(
|
||||
private log: readonly SessionEvent[],
|
||||
private readonly baseSeq: SessionLogOffset = SessionLogOffset(0),
|
||||
private readonly projections: readonly SessionMessageProjection[] = [],
|
||||
) {
|
||||
this._lastProcessedSeq = baseSeq === 0 ? -1 : SessionSeq(baseSeq - 1)
|
||||
}
|
||||
@@ -581,39 +585,44 @@ export class SurfaceManager implements SessionSurface {
|
||||
* @param event - candidate event that has not entered the log yet.
|
||||
*/
|
||||
validateNext(event: SessionEvent): void {
|
||||
this._assertProjections()
|
||||
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta()
|
||||
const expectedSeq = SessionSeq(this.baseSeq + this.log.length)
|
||||
this._pendingPlan = {
|
||||
event,
|
||||
expectedSeq,
|
||||
plan: planSurfaceEvent(this._state, event, expectedSeq, this.log, this.baseSeq),
|
||||
plan: planSurfaceEvent(this._state, event, expectedSeq, this.log, this.baseSeq, this.projections),
|
||||
}
|
||||
}
|
||||
|
||||
/** Monotonic count of folded positional replacements. */
|
||||
get replaceGeneration(): number {
|
||||
this._assertProjections()
|
||||
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta()
|
||||
return this._state.replaceGeneration
|
||||
}
|
||||
|
||||
/** Monotonic count of committed changes to existing model-visible content. */
|
||||
get contentGeneration(): number {
|
||||
this._assertProjections()
|
||||
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta()
|
||||
return this._state.contentGeneration
|
||||
}
|
||||
|
||||
/**
|
||||
* Project one message with every committed image-offload decision applied.
|
||||
* Project one message with every committed message projection applied.
|
||||
* @param event - message-producing or log-only event.
|
||||
* @returns its immutable projected message, or null when it produces none.
|
||||
*/
|
||||
deriveEventMessage(event: SessionEvent): Message | null {
|
||||
this._assertProjections()
|
||||
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta()
|
||||
return deriveEventMessage(event, this._state.offloadedMessages)
|
||||
return deriveEventMessage(event, this._state.projectedMessages)
|
||||
}
|
||||
|
||||
/** Surface event sequences in model-visible order. */
|
||||
get nodes(): readonly SessionSeq[] {
|
||||
this._assertProjections()
|
||||
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta()
|
||||
return this._state.nodes
|
||||
}
|
||||
@@ -629,10 +638,25 @@ export class SurfaceManager implements SessionSurface {
|
||||
if (pending?.event === event && pending.expectedSeq === seq) {
|
||||
applySurfacePlan(this._state, pending.plan)
|
||||
} else {
|
||||
applySurfaceEvent(this._state, event, SessionSeq(seq), this.log, this.baseSeq)
|
||||
applySurfaceEvent(this._state, event, SessionSeq(seq), this.log, this.baseSeq, this.projections)
|
||||
}
|
||||
if (pending !== undefined && pending.expectedSeq <= seq) this._pendingPlan = undefined
|
||||
this._lastProcessedSeq = SessionSeq(seq)
|
||||
}
|
||||
}
|
||||
|
||||
/** Cached messages cannot outlive the definitions that interpreted their log. */
|
||||
private _assertProjections(): void {
|
||||
const candidate = this._pendingPlan
|
||||
const pending = candidate !== undefined && this.log[candidate.expectedSeq - this.baseSeq] === candidate.event
|
||||
? candidate.plan : undefined
|
||||
const required = pending?.kind === 'project'
|
||||
? [...this._state.projections, pending.projection]
|
||||
: this._state.projections
|
||||
for (const projection of required) {
|
||||
if (!this.projections.includes(projection)) {
|
||||
throw new Error(`session message projection "${projection.type}" was removed or replaced; restore the session with its owning plugin`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,15 +267,6 @@ export type RequestHeaderReason = 'initial' | 'resume' | 'change' | 'series'
|
||||
* compact raw streams so persistence stores one durable settlement per attempt.
|
||||
*/
|
||||
export interface SessionEventMap {
|
||||
/**
|
||||
* Permanently omit the selected input-image occurrences from subsequent
|
||||
* model requests. Each target names a current user/message or tool/result
|
||||
* node; image indexes are zero-based depth-first positions within that
|
||||
* message, including nested tool results and already offloaded images.
|
||||
* Targets are unique and each index list is nonempty and strictly increasing.
|
||||
* This event changes derived content without replacing message nodes.
|
||||
*/
|
||||
'image/offload': { targets: ImageOffloadTarget[] }
|
||||
/**
|
||||
* Opens turn `turn` before the loop claims queued input or runs pre-step.
|
||||
* Rejection, empty input, cancellation, or failure may close it with no
|
||||
@@ -409,14 +400,6 @@ export interface SessionEventMap {
|
||||
'session/end-seed': { inherited?: true }
|
||||
}
|
||||
|
||||
/** Exact input-image occurrences selected by one durable offload decision. */
|
||||
export interface ImageOffloadTarget {
|
||||
/** Current message-producing event containing these occurrences. */
|
||||
seq: SessionSeq
|
||||
/** Zero-based depth-first image indexes within the immutable message. */
|
||||
imageIndexes: number[]
|
||||
}
|
||||
|
||||
/** The appendable event-type keys of {@link SessionEventMap}, plugin-merged extensions included. */
|
||||
export type SessionEventType = keyof SessionEventMap
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
collectLogEvents,
|
||||
collectSurfaceEventTypes,
|
||||
render,
|
||||
renderKnownEventTypes,
|
||||
} from '../../../../scripts/gen-persistence-catalog.ts'
|
||||
|
||||
/** Create a fixture scan root; `files` maps `packages/…`-relative paths to source. */
|
||||
@@ -45,6 +46,18 @@ afterEach(() => {
|
||||
const OWNER_MANIFEST = '{ "name": "@deepseek-ai/dsh-session" }\n'
|
||||
|
||||
describe('gen-persistence-catalog collectLogEvents', () => {
|
||||
it('generates required interpreter types from plugin-owned event declarations', () => {
|
||||
const events = collectLogEvents(make({
|
||||
'packages/group/fix/src/types.ts': merge(' /**\n * Projects existing messages.\n * @messageProjection\n */\n \'fix/project\': { seq: number }'),
|
||||
}))
|
||||
expect(events[0]?.messageProjection).toBe(true)
|
||||
expect(renderKnownEventTypes(annotateSurface(events, [])))
|
||||
.toContain("MESSAGE_PROJECTION_EVENT_TYPES: ReadonlySet<string> = new Set([\n 'fix/project',\n])")
|
||||
expect(() => annotateSurface(events, ['fix/project'])).toThrow(/cannot declare both/)
|
||||
const ordinary = events.map(({ messageProjection: _projection, ...event }) => event)
|
||||
expect(renderKnownEventTypes(annotateSurface(ordinary, [])))
|
||||
.toContain('MESSAGE_PROJECTION_EVENT_TYPES: ReadonlySet<string> = new Set([\n])')
|
||||
})
|
||||
it('extracts a documented member of the owning top-level interface', () => {
|
||||
const events = collectLogEvents(make({
|
||||
'packages/core/fix/package.json': OWNER_MANIFEST,
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { deepFreeze } from '@deepseek-ai/dsh-util-values'
|
||||
import SessionStore, { Session, SessionId, SessionSeq, SessionLogOffset, foldSurface, deriveEventMessage } from '../src/index.ts'
|
||||
import type { SessionEvent, SessionMessageProjection } from '../src/index.ts'
|
||||
import { MESSAGE_PROJECTION_EVENT_TYPES } from '../src/known-event-types.ts'
|
||||
import { SurfaceManager } from '../src/surface.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-session/types' {
|
||||
interface SessionEventMap {
|
||||
'test/project': { seq: SessionSeq; text: string }
|
||||
}
|
||||
}
|
||||
|
||||
const projection: SessionMessageProjection<'test/project'> = {
|
||||
type: 'test/project',
|
||||
project(event, context) {
|
||||
const source = context.events[event.data.seq - context.baseSeq]!
|
||||
const original = deriveEventMessage(source, context.messages)!
|
||||
if (event.data.text === 'reject') throw new Error('rejected decision')
|
||||
return new Map([[source.seq, deepFreeze({ ...original, content: [{ type: 'text' as const, text: event.data.text }] })]])
|
||||
},
|
||||
}
|
||||
|
||||
const contexts: Context[] = []
|
||||
afterEach(async () => {
|
||||
await Promise.all(contexts.splice(0).map(ctx => ctx.fiber.dispose()))
|
||||
})
|
||||
|
||||
function input(session: Session) {
|
||||
return session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'original' }], source: { kind: 'user' } }), { surfaceOp: 'append' })
|
||||
}
|
||||
|
||||
describe('plugin-owned message projections', () => {
|
||||
it('refuses required events when no interpreter is supplied', () => {
|
||||
const type = [...MESSAGE_PROJECTION_EVENT_TYPES][0]!
|
||||
const event = { type, seq: SessionSeq(0), time: 0, data: {} } as SessionEvent
|
||||
const session = Session.create(SessionId('missing'))
|
||||
expect(() => session.append(event.type as 'test/project', event.data as never)).toThrow(/requires a message projection/)
|
||||
expect(() => foldSurface([event])).toThrow(/requires a message projection/)
|
||||
expect(() => Session.create(session.id, [event])).toThrow(/requires a message projection/)
|
||||
expect(session.seq).toBe(0)
|
||||
})
|
||||
|
||||
it('applies generic decisions atomically and replays them through detached folds', () => {
|
||||
const definitions = [projection]
|
||||
const session = Session.create(SessionId('pure'), undefined, undefined, undefined, definitions)
|
||||
const source = input(session)
|
||||
const original = session.deriveMessages()
|
||||
expect(() => session.append('test/project', { seq: source.seq, text: 'reject' })).toThrow('rejected decision')
|
||||
expect(session.deriveMessages()).toEqual(original)
|
||||
session.append('test/project', { seq: source.seq, text: 'projected' })
|
||||
expect(session.deriveMessages()[0]?.content).toEqual([{ type: 'text', text: 'projected' }])
|
||||
expect(session.surface.replaceGeneration).toBe(0)
|
||||
expect(session.surface.contentGeneration).toBe(1)
|
||||
const folded = foldSurface(session.snapshotEvents(), definitions)
|
||||
expect(deriveEventMessage(source, folded.projectedMessages)).toEqual(session.deriveMessages()[0])
|
||||
expect(source.data.content).toEqual([{ type: 'text', text: 'original' }])
|
||||
})
|
||||
|
||||
it('registers by fiber and refuses cached or pending decisions after disposal', async () => {
|
||||
const ctx = new Context()
|
||||
contexts.push(ctx)
|
||||
await ctx.plugin(SessionStore)
|
||||
const fiber = await ctx.plugin((owner) => { owner.sessions.registerMessageProjection(projection) })
|
||||
expect(() => ctx.sessions.registerMessageProjection(projection)).toThrow(/already registered/)
|
||||
const live = ctx.sessions.create(SessionId('live'))
|
||||
const source = input(live)
|
||||
live.append('test/project', { seq: source.seq, text: 'changed' })
|
||||
const pending = ctx.sessions.create(SessionId('pending'))
|
||||
input(pending)
|
||||
pending.append('test/project', { seq: source.seq, text: 'changed' })
|
||||
const before = live.deriveMessages()
|
||||
const child = ctx.sessions.fork(live)
|
||||
expect(child.deriveMessages()).toEqual(before)
|
||||
const restored = ctx.sessions.prepare(SessionId('restore'), {
|
||||
seed: live.snapshotEvents(), meta: { ...live.header, id: SessionId('restore') },
|
||||
inheritedEventCount: live.inheritedEventCount, eventState: 'shared-frozen',
|
||||
})
|
||||
expect(restored.deriveMessages()).toEqual(before)
|
||||
await fiber.dispose()
|
||||
expect(ctx.sessions.messageProjections).toEqual([])
|
||||
for (const session of [live, pending, child, restored]) {
|
||||
expect(() => session.deriveMessages()).toThrow(/was removed or replaced/)
|
||||
expect(() => session.deriveEventMessage(source)).toThrow(/was removed or replaced/)
|
||||
expect(() => session.surface.replaceGeneration).toThrow(/was removed or replaced/)
|
||||
expect(() => session.surface.contentGeneration).toThrow(/was removed or replaced/)
|
||||
expect(() => session.append('turn/start', { turn: 1 })).toThrow(/was removed or replaced/)
|
||||
}
|
||||
expect(before[0]?.content).toEqual([{ type: 'text', text: 'changed' }])
|
||||
})
|
||||
|
||||
it('applies a supplied interpreter to a loaded window with absolute sequences', () => {
|
||||
const session = Session.create(SessionId('window'), undefined, undefined, undefined, [projection])
|
||||
input(session)
|
||||
const source = input(session)
|
||||
session.append('test/project', { seq: source.seq, text: 'window' })
|
||||
const events = session.snapshotEvents().slice(1)
|
||||
const surface = new SurfaceManager(events, SessionLogOffset(1), [projection])
|
||||
expect(surface.nodes).toEqual([source.seq])
|
||||
expect(surface.deriveEventMessage(source)?.content).toEqual([{ type: 'text', text: 'window' }])
|
||||
})
|
||||
|
||||
it('does not retain an interpreter for a candidate that never committed', () => {
|
||||
const session = Session.create(SessionId('candidate'))
|
||||
const source = input(session)
|
||||
const definitions: SessionMessageProjection[] = [projection]
|
||||
const surface = new SurfaceManager(session.snapshotEvents(), undefined, definitions)
|
||||
surface.validateNext({ type: 'test/project', seq: SessionSeq(1), time: 0, data: { seq: source.seq, text: 'unused' } })
|
||||
definitions.length = 0
|
||||
expect(surface.deriveEventMessage(source)).toBe(source.data)
|
||||
})
|
||||
})
|
||||
@@ -1869,6 +1869,13 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
summary: 'In-memory session store (`ctx.sessions`).',
|
||||
description: 'In-memory session store (`ctx.sessions`).\n\nPersistence is intentionally not implemented here — the agent lifecycle attaches a session-log writer to each published session\'s write handle; a session published outside that lifecycle persists nothing.',
|
||||
methods: [
|
||||
{
|
||||
signature: 'registerMessageProjection(projection: SessionMessageProjection): () => void',
|
||||
description: 'Register one event interpreter for live creation, restore, and fork. Disposing the contribution makes sessions that used it refuse further derivation.',
|
||||
parameters: [{ name: 'projection', description: 'pure definition owned by the event\'s plugin.' }],
|
||||
returns: 'the fiber-owned disposer.',
|
||||
throws: ['when another definition already owns this event type.'],
|
||||
},
|
||||
{
|
||||
signature: 'create(id?: SessionId, options?: CreateSessionOptions): Session',
|
||||
description: 'Create a session owned by the calling fiber: disposing that fiber stops event notification and removes the session from the store. `options.seed` populates the session with a copy of those events (replay/fork); `options.meta` attaches creation metadata (validated absolute `cwd`, seed and parent lineage, and delegation depth) as the immutable SessionHeader (the store fills `version`/`id`/`createdAt`).\n\nFor an agent whose session must be torn down IN ORDER with its loop (so the loop\'s final events are published before the store attachment ends), do NOT use this — fold the session lifecycle into the agent\'s own effect via prepare + enter + announce (see `dsh-agent-loop`\'s creation transaction).',
|
||||
@@ -4370,10 +4377,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'ImageMediaType',
|
||||
declaration: 'export type ImageMediaType = \'image/png\' | \'image/jpeg\' | \'image/webp\' | \'image/gif\';',
|
||||
},
|
||||
{
|
||||
name: 'ImageOffloadTarget',
|
||||
declaration: 'export interface ImageOffloadTarget {\n seq: SessionSeq;\n imageIndexes: number[];\n}',
|
||||
},
|
||||
{
|
||||
name: 'ImageRequestTarget',
|
||||
declaration: 'export interface ImageRequestTarget {\n width: number;\n height: number;\n maxBytes: number;\n}',
|
||||
@@ -5052,7 +5055,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'Session',
|
||||
declaration: 'export class Session {\n get surface(): SessionSurface;\n readonly header: SessionHeader;\n readonly inheritedEventCount: SessionLogOffset;\n get id(): SessionId;\n readonly firstLiveSeq: SessionLogOffset;\n static create(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader, inheritedEventCount?: SessionLogOffset): Session;\n static fromRestore(id: SessionId, seed: readonly SessionEvent[], header: SessionHeader, inheritedEventCount: SessionLogOffset, eventState: SessionSeedEventState): Session;\n eventAt(seq: SessionSeq): SessionEvent | undefined;\n snapshotEvents(fromSeq: SessionLogOffset = SessionLogOffset(0), toSeqExclusive: SessionLogOffset = this.seq): readonly SessionEvent[];\n ownEvents(): readonly SessionEvent[];\n isOwnSeq(seq: SessionSeq): boolean;\n get seq(): SessionLogOffset;\n append<T extends SessionEventType>(type: T, data: SessionEventMap[T], ...opts: T extends SurfaceEventType ? [\n opts: SurfaceIntent<T>\n ] : [\n ]): SessionEvent<T>;\n requestHeader(): EpochHeader | undefined;\n requestContext(): RequestContext | undefined;\n deriveMessages(): Message[];\n deriveEventMessage(event: SessionEvent): Message | null;\n}',
|
||||
declaration: 'export class Session {\n get surface(): SessionSurface;\n readonly header: SessionHeader;\n readonly inheritedEventCount: SessionLogOffset;\n get id(): SessionId;\n readonly firstLiveSeq: SessionLogOffset;\n static create(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader, inheritedEventCount?: SessionLogOffset, projections?: readonly SessionMessageProjection[]): Session;\n static fromRestore(id: SessionId, seed: readonly SessionEvent[], header: SessionHeader, inheritedEventCount: SessionLogOffset, eventState: SessionSeedEventState, projections?: readonly SessionMessageProjection[]): Session;\n eventAt(seq: SessionSeq): SessionEvent | undefined;\n snapshotEvents(fromSeq: SessionLogOffset = SessionLogOffset(0), toSeqExclusive: SessionLogOffset = this.seq): readonly SessionEvent[];\n ownEvents(): readonly SessionEvent[];\n isOwnSeq(seq: SessionSeq): boolean;\n get seq(): SessionLogOffset;\n append<T extends SessionEventType>(type: T, data: SessionEventMap[T], ...opts: T extends SurfaceEventType ? [\n opts: SurfaceIntent<T>\n ] : [\n ]): SessionEvent<T>;\n requestHeader(): EpochHeader | undefined;\n requestContext(): RequestContext | undefined;\n deriveMessages(): Message[];\n deriveEventMessage(event: SessionEvent): Message | null;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionAccess',
|
||||
@@ -5120,7 +5123,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'SessionEventMap',
|
||||
declaration: 'export interface SessionEventMap {\n \'image/offload\': {\n targets: ImageOffloadTarget[];\n };\n \'turn/start\': {\n turn: number;\n };\n \'turn/end\': {\n turn: number;\n reason: TurnEndReason;\n };\n \'step/start\': {\n turn: number;\n step: number;\n };\n \'step/end\': {\n turn: number;\n step: number;\n };\n \'user/message\': UserMessage;\n \'system/message\': {\n turn: number;\n step: number;\n message: SystemMessage;\n };\n \'assistant/message\': {\n turn: number;\n step: number;\n message: AssistantMessage;\n stream: AssistantStreamRecord[];\n usage?: TokenUsage;\n interrupted?: true;\n };\n \'assistant/attempt\': {\n turn: number;\n step: number;\n stream: AssistantStreamRecord[];\n };\n \'tool/call\': {\n turn: number;\n step: number;\n callId: ToolCallId;\n name: string;\n arguments: string;\n };\n \'tool/result\': {\n turn: number;\n step: number;\n message: ToolResultMessage;\n error?: {\n name: string;\n code: string;\n };\n meta?: JsonValue;\n };\n \'request/header\': {\n header: EpochHeader;\n reason: RequestHeaderReason;\n startsSeries?: true;\n };\n \'request/context\': RequestContext;\n \'session/end-seed\': {\n inherited?: true;\n };\n}',
|
||||
declaration: 'export interface SessionEventMap {\n \'turn/start\': {\n turn: number;\n };\n \'turn/end\': {\n turn: number;\n reason: TurnEndReason;\n };\n \'step/start\': {\n turn: number;\n step: number;\n };\n \'step/end\': {\n turn: number;\n step: number;\n };\n \'user/message\': UserMessage;\n \'system/message\': {\n turn: number;\n step: number;\n message: SystemMessage;\n };\n \'assistant/message\': {\n turn: number;\n step: number;\n message: AssistantMessage;\n stream: AssistantStreamRecord[];\n usage?: TokenUsage;\n interrupted?: true;\n };\n \'assistant/attempt\': {\n turn: number;\n step: number;\n stream: AssistantStreamRecord[];\n };\n \'tool/call\': {\n turn: number;\n step: number;\n callId: ToolCallId;\n name: string;\n arguments: string;\n };\n \'tool/result\': {\n turn: number;\n step: number;\n message: ToolResultMessage;\n error?: {\n name: string;\n code: string;\n };\n meta?: JsonValue;\n };\n \'request/header\': {\n header: EpochHeader;\n reason: RequestHeaderReason;\n startsSeries?: true;\n };\n \'request/context\': RequestContext;\n \'session/end-seed\': {\n inherited?: true;\n };\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionEventMetadataFilter',
|
||||
@@ -5278,6 +5281,14 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'SessionLogSnapshot',
|
||||
declaration: 'export interface SessionLogSnapshot {\n session: SessionHeader;\n inheritedEventCount: SessionLogOffset;\n events: SessionEvent[];\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionMessageProjection',
|
||||
declaration: 'export interface SessionMessageProjection<T extends SessionEventType = SessionEventType> {\n type: T;\n project(event: SessionEvent<T>, context: SessionMessageProjectionContext): ReadonlyMap<SessionSeq, Message>;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionMessageProjectionContext',
|
||||
declaration: 'export interface SessionMessageProjectionContext {\n nodes: readonly SessionSeq[];\n events: readonly SessionEvent[];\n baseSeq: SessionLogOffset;\n messages: ReadonlyMap<SessionSeq, Message>;\n}',
|
||||
},
|
||||
{
|
||||
name: 'SessionObservation',
|
||||
declaration: 'export interface SessionObservation extends Disposable {\n readonly source: \'live\' | \'prepared\';\n readonly header: SessionHeader;\n readonly inheritedEventCount: SessionLogOffsetType;\n readonly events: readonly SessionEvent[];\n readonly cursor: SessionSeqCursor;\n readonly revision?: SessionPersistenceRevision;\n readonly projections?: ProjectionSnapshot;\n retain(): SessionObservation;\n}',
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-retry": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^"
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-compaction-image-offload": "workspace:^"
|
||||
},
|
||||
"dependencies": {
|
||||
"@deepseek-ai/dsh-util-values": "workspace:^",
|
||||
@@ -50,6 +51,7 @@
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-retry": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^"
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-compaction-image-offload": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { Context, Service } from '@deepseek-ai/cordis'
|
||||
import type {} from '@deepseek-ai/dsh-compaction-image-offload/projection'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import { assembleAssistantStream } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmImageRequestPricing, LlmRuntime, Message, TokenUsage } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { imageOffloadProjection } from '@deepseek-ai/dsh-compaction-image-offload/projection'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import {
|
||||
@@ -85,7 +86,7 @@ async function harness(pricing: (model: string) => LlmImageRequestPricing | unde
|
||||
const llm = new LlmRuntime(ctx)
|
||||
llm.registerAdapter(['mock'], new PricingAdapter(pricing))
|
||||
const meter = new TokenMeter(ctx)
|
||||
return { ctx, meter, session: Session.create(SessionId('route-priced')) }
|
||||
return { ctx, meter, session: Session.create(SessionId('route-priced'), undefined, undefined, undefined, [imageOffloadProjection]) }
|
||||
}
|
||||
|
||||
/** Route price of one image-bearing message under the fixed pricing double. */
|
||||
@@ -229,7 +230,7 @@ describe('request projection pricing', () => {
|
||||
expect(after.nodes.map(node => node.heuristicTokens)).toEqual(before.nodes.map(node => node.heuristicTokens))
|
||||
const breakdown = ctx.sessionProjections.snapshot(session).values.contextBreakdown
|
||||
expect(breakdown?.messageTokens).toBe(after.nodes.reduce((total, node) => total + node.heuristicTokens, 0))
|
||||
const restored = Session.create(SessionId('offloaded-restored'), session.snapshotEvents())
|
||||
const restored = Session.create(SessionId('offloaded-restored'), session.snapshotEvents(), undefined, undefined, [imageOffloadProjection])
|
||||
expect(meter.measure(restored).surfaceTokens).toBe(after.surfaceTokens)
|
||||
})
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../session/session-projection"
|
||||
},
|
||||
{
|
||||
"path": "../../compaction/compaction-image-offload"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -58,5 +58,8 @@
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection-cache": "workspace:^",
|
||||
"@deepseek-ai/cordis": "workspace:^"
|
||||
},
|
||||
"dependencies": {
|
||||
"@deepseek-ai/dsh-session-format-catalog": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/** Shared event metadata and semantic-document projection. */
|
||||
|
||||
import { currentSessionMessageProjections } from '@deepseek-ai/dsh-session-format-catalog/message-projections'
|
||||
import { foldSurface } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEvent, SessionId, SessionSeq } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEventRecord, SessionEventSearchDocument, SessionEventSurface } from './types.ts'
|
||||
@@ -56,7 +57,7 @@ export function buildSessionEventSearchDocuments(
|
||||
function classifySurface(events: readonly SessionEvent[]): Map<SessionSeq, SessionEventSurface> {
|
||||
let folded: ReturnType<typeof foldSurface>
|
||||
try {
|
||||
folded = foldSurface(events)
|
||||
folded = foldSurface(events, currentSessionMessageProjections)
|
||||
} catch (error: unknown) {
|
||||
throw new SessionQueryError(
|
||||
/* v8 ignore next -- foldSurface throws Error instances */
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @module @deepseek-ai/dsh-session-query
|
||||
*/
|
||||
|
||||
import { currentSessionMessageProjections } from '@deepseek-ai/dsh-session-format-catalog/message-projections'
|
||||
import { Context, Service } from '@deepseek-ai/cordis'
|
||||
import {
|
||||
Session,
|
||||
@@ -187,6 +188,7 @@ export abstract class SessionQueryEngine extends Service {
|
||||
loaded.events,
|
||||
loaded.header,
|
||||
loaded.inheritedEventCount,
|
||||
currentSessionMessageProjections,
|
||||
)
|
||||
return {
|
||||
session: structuredClone(loaded.header),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** One-shot session-lineage and event-relationship tracing helpers. */
|
||||
|
||||
import { currentSessionMessageProjections } from '@deepseek-ai/dsh-session-format-catalog/message-projections'
|
||||
import { foldSurface, isSurfaceEvent, snapshotSessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import type {
|
||||
SessionEvent,
|
||||
@@ -183,7 +184,7 @@ function analyzeEventLog(
|
||||
): EventLogAnalysis {
|
||||
let folded: ReturnType<typeof foldSurface>
|
||||
try {
|
||||
folded = foldSurface(events)
|
||||
folded = foldSurface(events, currentSessionMessageProjections)
|
||||
} catch (error: unknown) {
|
||||
throw new SessionQueryError(
|
||||
/* v8 ignore next -- foldSurface throws Error instances */
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../session/session-projection-cache"
|
||||
},
|
||||
{
|
||||
"path": "../../session/session-format-catalog"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/session/session-format-catalog/README.md
|
||||
README.md: 8cebff66b4d788792075784dfc5c59a6da92a400
|
||||
README.zh.md: 5190c19927dfa917f7855f28a687711b724f6411
|
||||
README.md: 8232a28b2c2c31c0176adb62e07ed16a804791e8
|
||||
README.zh.md: 2c4c86da50640f672842d98e6cc254dca08a3470
|
||||
@@ -44,7 +44,7 @@ Import `sessionFormatCatalog` from the package root. JSONL and fixture readers c
|
||||
|
||||
Production historical reads select `{ recovery: 'recoverable', validation: 'transformed' }`. Worker and fixture verification select `{ recovery: 'strict', validation: 'current' }`. Transformed validation runs the released-current rules after migration but deliberately skips installed semantic validation for input that is already current.
|
||||
|
||||
The catalog contains all supported historical readers directly. A profile cannot add, remove, or reorder an edge by mounting a feature plugin. Its peer dependency on `dsh-session` supplies the installed current event vocabulary and current restoration rules, while historical edge validators remain frozen.
|
||||
The catalog contains all supported historical readers directly. A profile cannot add, remove, or reorder an edge by mounting a feature plugin. Its peer dependency on `dsh-session` supplies the installed current event vocabulary and current restoration rules, while historical edge validators remain frozen. The browser-safe `./message-projections` export assembles current plugin-owned interpreters for detached constructors and surface folds; it does not mount recovery listeners.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ const eventRecords = current.events.map(sessionFormatCatalog.encodeCurrentEvent)
|
||||
|
||||
Production 历史读取使用 `{ recovery: 'recoverable', validation: 'transformed' }`。Worker 与 fixture 校验使用 `{ recovery: 'strict', validation: 'current' }`。Transformed validation 会在迁移后执行已发布 current 规则,但对已经是 current 的输入有意跳过已安装语义校验。
|
||||
|
||||
该目录直接包含所有受支持的历史读取器。Profile 无法通过挂载功能插件来添加、移除或重新排列迁移边。它通过对 `dsh-session` 的对等依赖(peer dependency)获得已安装的当前事件词表与当前还原规则,而历史迁移边校验器保持冻结。
|
||||
该目录直接包含所有受支持的历史读取器。Profile 无法通过挂载功能插件来添加、移除或重新排列迁移边。它通过对 `dsh-session` 的对等依赖(peer dependency)获得已安装的当前事件词表与当前还原规则,而历史迁移边校验器保持冻结。浏览器安全的 `./message-projections` 导出为独立构造函数和 surface 折叠装配当前插件拥有的处理器,不挂载恢复监听器。
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -19,18 +19,24 @@
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./src/*": "./src/*",
|
||||
"./package.json": "./package.json"
|
||||
"./package.json": "./package.json",
|
||||
"./message-projections": {
|
||||
"types": "./lib/types/message-projections.d.ts",
|
||||
"default": "./lib/types/message-projections.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
"lib/types/**/*.d.ts"
|
||||
"lib/types/**/*.d.ts",
|
||||
"lib/types/**/*.js"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@deepseek-ai/dsh-session-format": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-format-v0-to-v1": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-format-v1-to-v2": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-format-v2-to-v3": "workspace:^"
|
||||
"@deepseek-ai/dsh-session-format-v2-to-v3": "workspace:^",
|
||||
"@deepseek-ai/dsh-compaction-image-offload": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:^",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionFormatArtifact, SessionFormatHeader } from '@deepseek-ai/dsh-session-format'
|
||||
import { currentSessionMessageProjections } from './message-projections.ts'
|
||||
|
||||
/**
|
||||
* Validate current logical metadata through the installed Session package.
|
||||
@@ -46,5 +47,6 @@ export function validateInstalledCurrentSessionArtifact(artifact: SessionFormatA
|
||||
artifact.header as unknown as SessionHeader,
|
||||
SessionLogOffset(artifact.inheritedEventCount),
|
||||
'detached',
|
||||
currentSessionMessageProjections,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/** Pure first-party message interpreters for detached current-format replay, including browser readers. */
|
||||
|
||||
import { imageOffloadProjection } from '@deepseek-ai/dsh-compaction-image-offload/projection'
|
||||
import type { SessionMessageProjection } from '@deepseek-ai/dsh-session/surface'
|
||||
|
||||
/** Installed interpretation definitions; recovery listeners are mounted separately by their owning plugins. */
|
||||
export const currentSessionMessageProjections: readonly SessionMessageProjection[] = [imageOffloadProjection]
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { SessionFormatEvent } from '@deepseek-ai/dsh-session-format'
|
||||
import { sessionFormatCatalog } from '../src/index.ts'
|
||||
import { currentSessionMessageProjections } from '../src/message-projections.ts'
|
||||
import { MESSAGE_PROJECTION_EVENT_TYPES } from '@deepseek-ai/dsh-session/src/known-event-types.ts'
|
||||
import { validateInstalledCurrentSessionArtifact } from '../src/current.ts'
|
||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
function deepFreeze<T>(value: T): T {
|
||||
if (value !== null && typeof value === 'object') {
|
||||
@@ -11,6 +16,17 @@ function deepFreeze<T>(value: T): T {
|
||||
}
|
||||
|
||||
describe('first-party Session format catalog', () => {
|
||||
it('supplies every required current message interpreter and validates its durable references', () => {
|
||||
expect(currentSessionMessageProjections.map(projection => projection.type).sort())
|
||||
.toEqual([...MESSAGE_PROJECTION_EVENT_TYPES].sort())
|
||||
const session = Session.create(SessionId('projection-catalog'))
|
||||
session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'no image' }], source: { kind: 'user' } }), { surfaceOp: 'append' })
|
||||
const artifact = {
|
||||
header: session.header, inheritedEventCount: 0,
|
||||
events: [...session.snapshotEvents(), { type: 'image/offload', seq: 1, time: 0, data: { targets: [{ seq: 0, imageIndexes: [0] }] } }],
|
||||
}
|
||||
expect(() => validateInstalledCurrentSessionArtifact(artifact)).toThrow(/image index 0 does not exist/)
|
||||
})
|
||||
it('statically owns the complete adjacent v0 to v3 chain', () => {
|
||||
const header = {
|
||||
type: 'session',
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../session-format-v2-to-v3"
|
||||
},
|
||||
{
|
||||
"path": "../../compaction/compaction-image-offload"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
* @module @deepseek-ai/dsh-session-persistence-jsonl/generation
|
||||
*/
|
||||
|
||||
import { currentSessionMessageProjections } from '@deepseek-ai/dsh-session-format-catalog/message-projections'
|
||||
import { createHash, randomBytes } from 'node:crypto'
|
||||
import {
|
||||
link as fsLink,
|
||||
@@ -536,6 +537,7 @@ async function verifyCurrentGeneration(
|
||||
generation.meta,
|
||||
generation.inheritedEventCount,
|
||||
'detached',
|
||||
currentSessionMessageProjections,
|
||||
)
|
||||
assertCurrentAssistantStreams(generation.events)
|
||||
return {
|
||||
|
||||
@@ -259,7 +259,7 @@ export class OpenTelemetrySessionBackend extends SessionTelemetryBackend {
|
||||
if (committed === undefined) return
|
||||
const session = Session.fromRestore(
|
||||
snapshot.meta.id, snapshot.events, snapshot.meta, snapshot.inheritedEventCount,
|
||||
'detached',
|
||||
'detached', ctx.sessions.messageProjections,
|
||||
)
|
||||
// fromRestore appends a lifecycle marker that this submission did not commit.
|
||||
if (isFeedback(session, committed)) coordinator.captureSession(session, committed.seq)
|
||||
|
||||
Generated
+23
@@ -2016,6 +2016,9 @@ importers:
|
||||
'@deepseek-ai/dsh-credentials':
|
||||
specifier: workspace:^
|
||||
version: link:../../credentials/credentials
|
||||
'@deepseek-ai/dsh-session-format-catalog':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-format-catalog
|
||||
'@deepseek-ai/schemastery':
|
||||
specifier: link:../../../vendor/schemastery
|
||||
version: link:../../../vendor/schemastery
|
||||
@@ -4688,6 +4691,9 @@ importers:
|
||||
'@deepseek-ai/dsh-compaction':
|
||||
specifier: workspace:^
|
||||
version: link:../compaction
|
||||
'@deepseek-ai/dsh-compaction-image-offload':
|
||||
specifier: workspace:^
|
||||
version: link:../compaction-image-offload
|
||||
'@deepseek-ai/dsh-compaction-tool-result-pruner':
|
||||
specifier: workspace:^
|
||||
version: link:../compaction-tool-result-pruner
|
||||
@@ -4714,6 +4720,10 @@ importers:
|
||||
version: link:../../core/tools
|
||||
|
||||
packages/compaction/compaction-image-offload:
|
||||
dependencies:
|
||||
'@deepseek-ai/dsh-util-values':
|
||||
specifier: workspace:^
|
||||
version: link:../../util/values
|
||||
devDependencies:
|
||||
'@deepseek-ai/cordis':
|
||||
specifier: workspace:^
|
||||
@@ -4773,6 +4783,9 @@ importers:
|
||||
'@deepseek-ai/dsh-compaction':
|
||||
specifier: workspace:^
|
||||
version: link:../compaction
|
||||
'@deepseek-ai/dsh-compaction-image-offload':
|
||||
specifier: workspace:^
|
||||
version: link:../compaction-image-offload
|
||||
'@deepseek-ai/dsh-invariants':
|
||||
specifier: workspace:^
|
||||
version: link:../../runtime-diagnostics/invariants
|
||||
@@ -7306,6 +7319,9 @@ importers:
|
||||
'@deepseek-ai/dsh-compaction':
|
||||
specifier: workspace:^
|
||||
version: link:../../compaction/compaction
|
||||
'@deepseek-ai/dsh-compaction-image-offload':
|
||||
specifier: workspace:^
|
||||
version: link:../../compaction/compaction-image-offload
|
||||
'@deepseek-ai/dsh-llm':
|
||||
specifier: workspace:^
|
||||
version: link:../llm
|
||||
@@ -7908,6 +7924,10 @@ importers:
|
||||
version: 18.3.1
|
||||
|
||||
packages/session-query/session-query:
|
||||
dependencies:
|
||||
'@deepseek-ai/dsh-session-format-catalog':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-format-catalog
|
||||
devDependencies:
|
||||
'@deepseek-ai/cordis':
|
||||
specifier: workspace:^
|
||||
@@ -8071,6 +8091,9 @@ importers:
|
||||
|
||||
packages/session/session-format-catalog:
|
||||
dependencies:
|
||||
'@deepseek-ai/dsh-compaction-image-offload':
|
||||
specifier: workspace:^
|
||||
version: link:../../compaction/compaction-image-offload
|
||||
'@deepseek-ai/dsh-session-format':
|
||||
specifier: workspace:^
|
||||
version: link:../session-format
|
||||
|
||||
@@ -353,6 +353,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
|
||||
SessionSelectModelRequest: 'session.md',
|
||||
SessionSelectModelValue: 'session.md',
|
||||
SessionSummary: 'session.md',
|
||||
SessionMessageProjection: 'session.md',
|
||||
SessionUpdateQueueRequest: 'session.md',
|
||||
SessionUpdateQueueValue: 'session.md',
|
||||
EncodedFileUploadRequest: 'attachment.md',
|
||||
|
||||
@@ -61,6 +61,8 @@ const LINK_MAP: Record<string, string> = {
|
||||
|
||||
/** One log event, extracted from a `SessionEventMap` declaration. */
|
||||
export interface LogEventEntry {
|
||||
/** Whether interpretation requires a plugin-owned message projection. */
|
||||
messageProjection?: true
|
||||
/** Scoped name, e.g. `turn/start`. */
|
||||
name: string
|
||||
/** The scope prefix, e.g. `turn` (everything before the first `/`). */
|
||||
@@ -234,7 +236,10 @@ export function collectLogEvents(scanRoot: string = root): LogEventEntry[] {
|
||||
violations.push(`${where} has no description prose. Say what the event records and what its payload means — the JSDoc becomes the catalog entry.`)
|
||||
}
|
||||
const declaration = declarationText(text, sf, member)
|
||||
entries.push({ name, scope: name.split('/')[0] ?? name, payload, declaration, doc, source: src })
|
||||
const messageProjection = ts.getJSDocTags(member).some(tag => tag.tagName.text === 'messageProjection')
|
||||
entries.push({ name, scope: name.split('/')[0] ?? name, payload, declaration, doc, source: src,
|
||||
...messageProjection ? { messageProjection: true as const } : {},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,6 +336,11 @@ export function annotateSurface(events: LogEventEntry[], surfaceTypes: string[])
|
||||
throw new Error(`gen-persistence-catalog: SurfaceEventType member(s) ${stale.map(t => `'${t}'`).join(', ')} name no declared log event (stale union member?).`)
|
||||
}
|
||||
const surface = new Set(surfaceTypes)
|
||||
for (const event of events) {
|
||||
if (event.messageProjection && surface.has(event.name)) {
|
||||
throw new Error(`gen-persistence-catalog: '${event.name}' cannot declare both a message projection and a surface operation`)
|
||||
}
|
||||
}
|
||||
return events.map(e => ({ ...e, surface: surface.has(e.name) }))
|
||||
}
|
||||
|
||||
@@ -424,6 +434,11 @@ export function renderKnownEventTypes(events: AnnotatedLogEventEntry[]): string
|
||||
...names.map(name => ` '${name}',`),
|
||||
'])',
|
||||
'',
|
||||
'/** Event types whose model-visible effects require an explicit pure interpreter. */',
|
||||
'export const MESSAGE_PROJECTION_EVENT_TYPES: ReadonlySet<string> = new Set([',
|
||||
...events.filter(event => event.messageProjection).map(event => ` '${event.name}',`).sort(),
|
||||
'])',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"comment": "Maps each primary ` ```ts type-equiv ` or ` ```ts public-api ` block (by doc + declared symbol + projection) to the source declaration and original JSDoc it must match. Paired `.zh.md` blocks are byte-identical derivatives checked through their unsuffixed sibling and have no duplicate entry. Omit projection for the complete declaration; use public-api with a ` ```ts public-api ` block for a body-stripped public class declaration. verify-type-equiv.ts enforces a 1:1 correspondence between primary blocks and entries. Add an entry when you add a primary source-equivalence block; remove it when you remove the block.",
|
||||
"entries": [
|
||||
{
|
||||
"doc": "docs/subsystems/session.md",
|
||||
"doc": "docs/subsystems/compaction.md",
|
||||
"symbol": "ImageOffloadTarget",
|
||||
"source": "packages/core/session/src/types.ts"
|
||||
"source": "packages/compaction/compaction-image-offload/src/projection.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/core.md",
|
||||
@@ -2115,6 +2115,16 @@
|
||||
"doc": "packages/client/ui-conversation/README.md",
|
||||
"symbol": "ComposerChainProps",
|
||||
"source": "packages/client/ui-conversation/src/client/contract/slots.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/session.md",
|
||||
"symbol": "SessionMessageProjection",
|
||||
"source": "packages/core/session/src/surface.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/session.md",
|
||||
"symbol": "SessionMessageProjectionContext",
|
||||
"source": "packages/core/session/src/surface.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -252,6 +252,8 @@
|
||||
// session-log-export lives under session-query/, so the generated aliases map only its
|
||||
// bare name; the browser half needs this explicit subpath for source-mode dynamic imports.
|
||||
"@deepseek-ai/dsh-session-log-export/client": ["./packages/session-query/session-log-export/src/client"],
|
||||
"@deepseek-ai/dsh-compaction-image-offload/projection": ["./packages/compaction/compaction-image-offload/src/projection.ts"],
|
||||
"@deepseek-ai/dsh-session-format-catalog/message-projections": ["./packages/session/session-format-catalog/src/message-projections.ts"],
|
||||
// BEGIN generated package aliases — pnpm run gen-tsconfig-paths
|
||||
"@deepseek-ai/dsh-acp": ["./packages/acp/acp/src"],
|
||||
"@deepseek-ai/dsh-acp-app": ["./packages/bundle/acp-app/src"],
|
||||
|
||||
Reference in new issue
Block a user