@deepseek-ai/dsh-session-persistence-jsonl 0.1.5-rc.1 → 0.1.6-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +4 -2
- package/README.zh.md +9 -7
- package/lib/index.js +2 -1
- package/lib/worker.cjs +438 -283
- package/package.json +11 -11
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/session/session-persistence-jsonl/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: fc431ff27bcd236c4d44b8d3e7b20d4291b936d4
|
|
6
|
+
README.zh.md: 3b265bf43f058d4c0711568df156825febed4d24
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
|
|
|
53
53
|
|
|
54
54
|
### On-disk layout
|
|
55
55
|
|
|
56
|
-
Each session gets a session-owned directory under a readable project directory. Every canonical generation starts with a physical header whose version equals its filename. The current format stores one physical row per durable event; the frozen v0 and v1 readers also understand their historical packed Assistant-delta rows. The current format stores `isSeeded` in the header and derives the inherited cut from the last tagged `session/end-seed` marker, while historical codecs translate their numeric `seedLength`. The format catalog completes that translation before a handle exposes current logical values. Current storage records use the lossless
|
|
56
|
+
Each session gets a session-owned directory under a readable project directory. Every canonical generation starts with a physical header whose version equals its filename. The current format stores one physical row per durable event; the frozen v0 and v1 readers also understand their historical packed Assistant-delta rows. The current format stores `isSeeded` in the header and derives the inherited cut from the last tagged `session/end-seed` marker, while historical codecs translate their numeric `seedLength`. The format catalog completes that translation before a handle exposes current logical values. Current storage records use the lossless source-event representation described below:
|
|
57
57
|
|
|
58
58
|
```text
|
|
59
59
|
<root>/
|
|
@@ -62,9 +62,11 @@ Each session gets a session-owned directory under a readable project directory.
|
|
|
62
62
|
session.jsonl.zstd # released v0, compressed root
|
|
63
63
|
session.v1.jsonl.zstd # released v1, compressed root
|
|
64
64
|
session.v2.jsonl.zstd # released v2, compressed root
|
|
65
|
+
session.v3.jsonl.zstd # released v3/current, compressed root
|
|
65
66
|
session.jsonl # released v0, raw root
|
|
66
67
|
session.v1.jsonl # released v1, raw root
|
|
67
|
-
session.v2.jsonl # released v2, raw root
|
|
68
|
+
session.v2.jsonl # released v2, raw root
|
|
69
|
+
session.v3.jsonl # released v3/current, raw root; later versions use vN
|
|
68
70
|
```
|
|
69
71
|
|
|
70
72
|
Session ids are injectively escaped to one safe path segment before use (no traversal, no collision). The normalized cwd keeps the project directory readable for navigation; cwd strings that normalize alike share a project directory while session ids still select distinct session directories. Runtime operations select the numerically highest canonical generation, and format-refusal diagnostics name that absolute path so an operator can find the raw log a build refused to interpret.
|
package/README.zh.md
CHANGED
|
@@ -29,7 +29,7 @@ kind: "package-reference"
|
|
|
29
29
|
|
|
30
30
|
### 何时选择
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
当消费方受益于每会话一份产物——导航、外部工具或可逐行读取的原始日志——时选择此后端。它是唯一的第一方会话持久化提供方。后端把会话保存在部署控制的根下:项目本地、共享、临时或集中式。
|
|
33
33
|
|
|
34
34
|
### 最小配置
|
|
35
35
|
|
|
@@ -62,12 +62,14 @@ kind: "package-reference"
|
|
|
62
62
|
session.jsonl.zstd # released v0, compressed root
|
|
63
63
|
session.v1.jsonl.zstd # released v1, compressed root
|
|
64
64
|
session.v2.jsonl.zstd # released v2, compressed root
|
|
65
|
+
session.v3.jsonl.zstd # released v3/current, compressed root
|
|
65
66
|
session.jsonl # released v0, raw root
|
|
66
67
|
session.v1.jsonl # released v1, raw root
|
|
67
|
-
session.v2.jsonl # released v2, raw root
|
|
68
|
+
session.v2.jsonl # released v2, raw root
|
|
69
|
+
session.v3.jsonl # released v3/current, raw root; later versions use vN
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
会话 id 在使用前被单射转义为一个安全路径段(无遍历、无冲突)。规范化 cwd 让项目目录保持可读、便于导航;规范化相同的 cwd 字符串共享项目目录,而会话 id
|
|
72
|
+
会话 id 在使用前被单射转义为一个安全路径段(无遍历、无冲突)。规范化 cwd 让项目目录保持可读、便于导航;规范化相同的 cwd 字符串共享项目目录,而会话 id 仍选择不同会话目录。运行时操作选择数值最高的规范 generation,格式拒绝诊断会点名该绝对路径,让操作者能找到构建拒绝解读的原始日志。
|
|
71
73
|
|
|
72
74
|
### 持久性与崩溃语义
|
|
73
75
|
|
|
@@ -77,7 +79,7 @@ kind: "package-reference"
|
|
|
77
79
|
|
|
78
80
|
### 读取日志
|
|
79
81
|
|
|
80
|
-
`open(id, 'read'|'write')` 选择最高规范 generation。当前格式输入走普通快速路径。对于历史输入,只读 open 会单遍解码并迁移源、校验当前逻辑结果,然后在不发布后继的情况下返回。写 open 会在可用时复用按 revision 为键的 preparation,否则执行同一套 preparation,再按有界分片编码同目录临时文件、在 Worker Thread 中校验、复查源修订,并在返回前以不覆盖方式发布当前后继。源保持逐字节不变。如果源在 preparation 后发生变化,该次写 open 会失败,已经返回给读方的逻辑历史不会被替换;后续写 open 会针对新的 revision 重新执行 preparation
|
|
82
|
+
`open(id, 'read'|'write')` 选择最高规范 generation。当前格式输入走普通快速路径。对于历史输入,只读 open 会单遍解码并迁移源、校验当前逻辑结果,然后在不发布后继的情况下返回。写 open 会在可用时复用按 revision 为键的 preparation,否则执行同一套 preparation,再按有界分片编码同目录临时文件、在 Worker Thread 中校验、复查源修订,并在返回前以不覆盖方式发布当前后继。源保持逐字节不变。如果源在 preparation 后发生变化,该次写 open 会失败,已经返回给读方的逻辑历史不会被替换;后续写 open 会针对新的 revision 重新执行 preparation。后端在 memo 化前冻结已解码的 event graph,并在此时将其标记为 `shared-frozen`;句柄读取和 slice 即使为空也保留该状态。只有尚未实体化的 pending 空日志报告 `detached`。`stat(id)` 与 `list()` 只选择并转换最高 generation 的 header,不读取事件行,也不启动迁移;快照携带所选文件的 `sizeBytes` 与尽力而为的 stat 派生修订号。选择 `compression: 'none'` 后,日志是外部读取方可直接消费的换行分隔文本;压缩默认值必须经后端读取。
|
|
81
83
|
|
|
82
84
|
-----
|
|
83
85
|
|
|
@@ -108,7 +110,7 @@ kind: "package-reference"
|
|
|
108
110
|
| [`src/migration-verifier.ts`](src/migration-verifier.ts) | stage 与竞争 generation 校验的 Worker 生命周期 |
|
|
109
111
|
| [`src/zstd.ts`](src/zstd.ts) | Zstandard 帧压缩、解码与帧扫描 |
|
|
110
112
|
| [`src/win32.ts`](src/win32.ts) | Windows write-through 发布与目录创建 |
|
|
111
|
-
| — |
|
|
113
|
+
| — | 不发布运行时不变式伴生入口;身份在存储层强制;持久化正确性依赖后端往返与崩溃尾部测试,本包不公开可持续观察的进程内关系。 |
|
|
112
114
|
|
|
113
115
|
</details>
|
|
114
116
|
|
|
@@ -151,11 +153,11 @@ JSONL 存储不修改实时请求前缀。只有重建历史、当前 envelope
|
|
|
151
153
|
|
|
152
154
|
这些限制说明本后端何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
|
153
155
|
|
|
154
|
-
- **格式迁移保留已配置编码,且只支持 catalog 中的链**——本 build
|
|
156
|
+
- **格式迁移保留已配置编码,且只支持 catalog 中的链**——本 build 把受支持的历史代迁移到当前格式;更改压缩需要独立根,保留的旧版本不提供自动 fallback 或 downgrade 支持。
|
|
155
157
|
- **平铺文件存储布局不加载**——加载前使用独立根,或将预发布产物移入项目/会话目录布局。
|
|
156
158
|
- **压缩文件不能直接按行读取**——使用后端加载;或在写入新根前选择 `compression: 'none'`,供外部行读取方使用。
|
|
157
159
|
- **不删除会话文件**——日志在 `root` 下累积,直到外部移除;seam 无删除接口。
|
|
158
|
-
- **每会话一个活动写入方**——写句柄认领在所属后端实例内排除第二个写入方,内核锁(`session.lock` 上的非阻塞 `flock(2)`;Windows
|
|
160
|
+
- **每会话一个活动写入方**——写句柄认领在所属后端实例内排除第二个写入方,内核锁(`session.lock` 上的非阻塞 `flock(2)`;Windows 上为由该路径派生的命名内核信号量,零文件系统足迹)排除其他所有实例与进程;锁在以写模式打开既有产物时立即获取,新建会话则仅在首次实体化写入之前获取,因此未实体化的会话不留任何文件系统足迹。崩溃持有者的锁随其进程消亡,会话立即可再写入,而活着但卡死的持有者会阻塞写入方直到其进程退出(POSIX 上删除锁文件即放弃该排他;释放本身从不删除它)。咨询式 `flock` 在部分网络文件系统(NFSv3)上不可靠,Windows 信号量名按登录会话隔离。
|
|
159
161
|
- **POSIX 实体化需要硬链接支持**——第一次 append 使用 `link()`,使同 id 竞态失败而不覆盖已提交日志;Windows 使用无替换 write-through rename。
|
|
160
162
|
- **POSIX 写入需要匹配的预编译系统 addon**——[`node-addon-system`](../../../native/system/README.zh.md) 提供异步 flock,无须在用户侧编译。addon 缺失时拒绝写入所有权;Windows 保留其信号量实现。
|
|
161
163
|
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { constants, createZstdCompress, createZstdDecompress, zstdCompress, zstd
|
|
|
16
16
|
import { isDeepStrictEqual, promisify } from "node:util";
|
|
17
17
|
import { constants as constants$1 } from "node:buffer";
|
|
18
18
|
import { Worker } from "node:worker_threads";
|
|
19
|
+
import { currentSessionMessageProjections } from "@deepseek-ai/dsh-session-format-catalog/message-projections";
|
|
19
20
|
import { Readable, pipeline } from "node:stream";
|
|
20
21
|
/**
|
|
21
22
|
* The JSONL session handle. Mutations serialize on a per-handle promise
|
|
@@ -1819,7 +1820,7 @@ async function verifyCurrentGeneration(path, compression, expectedId, expectedEv
|
|
|
1819
1820
|
});
|
|
1820
1821
|
if (generation.meta.id !== expectedId) throw new Error(`current session generation contains id "${generation.meta.id}", expected "${expectedId}"`);
|
|
1821
1822
|
if (generation.events.length !== expectedEventCount) throw new Error(`current session generation contains ${generation.events.length} events, expected ${expectedEventCount}`);
|
|
1822
|
-
Session.fromRestore(generation.meta.id, generation.events, generation.meta, generation.inheritedEventCount, "detached");
|
|
1823
|
+
Session.fromRestore(generation.meta.id, generation.events, generation.meta, generation.inheritedEventCount, "detached", currentSessionMessageProjections);
|
|
1823
1824
|
assertCurrentAssistantStreams(generation.events);
|
|
1824
1825
|
return {
|
|
1825
1826
|
identity: snapshot.identity,
|
package/lib/worker.cjs
CHANGED
|
@@ -9,6 +9,320 @@ let node_util = require("node:util");
|
|
|
9
9
|
let node_zlib = require("node:zlib");
|
|
10
10
|
let node_module = require("node:module");
|
|
11
11
|
let node_buffer = require("node:buffer");
|
|
12
|
+
//#region ../../util/values/src/index.ts
|
|
13
|
+
/**
|
|
14
|
+
* Mark an unreachable closed-union branch.
|
|
15
|
+
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
16
|
+
* @param context - optional switch-site label included in the failure message.
|
|
17
|
+
* @returns never; a runtime value that escaped its type always throws.
|
|
18
|
+
*/
|
|
19
|
+
function assertNever(value, context) {
|
|
20
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
21
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
22
|
+
}
|
|
23
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
24
|
+
function hasIntrinsicConstructor(prototype, name) {
|
|
25
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
26
|
+
if (typeof constructor !== "function") return false;
|
|
27
|
+
try {
|
|
28
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
34
|
+
function isIntrinsicObjectPrototype(value) {
|
|
35
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
36
|
+
}
|
|
37
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
38
|
+
function hasPlainArrayPrototype(value) {
|
|
39
|
+
const prototype = Object.getPrototypeOf(value);
|
|
40
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
41
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
42
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
43
|
+
}
|
|
44
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
45
|
+
function hasPlainObjectPrototype(value) {
|
|
46
|
+
const prototype = Object.getPrototypeOf(value);
|
|
47
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
48
|
+
}
|
|
49
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
50
|
+
function enumerableStringKeys(value) {
|
|
51
|
+
const keys = Reflect.ownKeys(value);
|
|
52
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
53
|
+
return keys;
|
|
54
|
+
}
|
|
55
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
56
|
+
function walkJsonValue(value, detach) {
|
|
57
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
58
|
+
let root;
|
|
59
|
+
const assign = (destination, item) => {
|
|
60
|
+
if (destination === void 0) return;
|
|
61
|
+
if (destination.kind === "root") root = item;
|
|
62
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
63
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
64
|
+
value: item,
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
writable: true
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const tasks = [{
|
|
71
|
+
kind: "visit",
|
|
72
|
+
value,
|
|
73
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
74
|
+
}];
|
|
75
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
76
|
+
if (task.kind === "leave") {
|
|
77
|
+
ancestors.delete(task.source);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (task.kind === "array-item") {
|
|
81
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
82
|
+
tasks.push({
|
|
83
|
+
kind: "visit",
|
|
84
|
+
value: task.source[task.index],
|
|
85
|
+
...task.target === void 0 ? {} : { destination: {
|
|
86
|
+
kind: "array",
|
|
87
|
+
target: task.target,
|
|
88
|
+
index: task.index
|
|
89
|
+
} }
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (task.kind === "object-property") {
|
|
94
|
+
tasks.push({
|
|
95
|
+
kind: "visit",
|
|
96
|
+
value: task.source[task.key],
|
|
97
|
+
...task.target === void 0 ? {} : { destination: {
|
|
98
|
+
kind: "object",
|
|
99
|
+
target: task.target,
|
|
100
|
+
key: task.key
|
|
101
|
+
} }
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const current = task.value;
|
|
106
|
+
if (current === null) {
|
|
107
|
+
assign(task.destination, null);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
111
|
+
assign(task.destination, current);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (typeof current === "number") {
|
|
115
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
116
|
+
assign(task.destination, current);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (typeof current !== "object") return void 0;
|
|
120
|
+
if (ancestors.has(current)) return void 0;
|
|
121
|
+
if (Array.isArray(current)) {
|
|
122
|
+
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
123
|
+
const length = current.length;
|
|
124
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
125
|
+
const target = detach ? [] : void 0;
|
|
126
|
+
if (target !== void 0) assign(task.destination, target);
|
|
127
|
+
ancestors.add(current);
|
|
128
|
+
tasks.push({
|
|
129
|
+
kind: "leave",
|
|
130
|
+
source: current
|
|
131
|
+
});
|
|
132
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
133
|
+
kind: "array-item",
|
|
134
|
+
source: current,
|
|
135
|
+
index,
|
|
136
|
+
...target === void 0 ? {} : { target }
|
|
137
|
+
});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
141
|
+
const keys = enumerableStringKeys(current);
|
|
142
|
+
if (keys === void 0) return void 0;
|
|
143
|
+
const target = detach ? {} : void 0;
|
|
144
|
+
if (target !== void 0) assign(task.destination, target);
|
|
145
|
+
ancestors.add(current);
|
|
146
|
+
tasks.push({
|
|
147
|
+
kind: "leave",
|
|
148
|
+
source: current
|
|
149
|
+
});
|
|
150
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
151
|
+
const key = keys[index];
|
|
152
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
153
|
+
if (key === void 0) return void 0;
|
|
154
|
+
tasks.push({
|
|
155
|
+
kind: "object-property",
|
|
156
|
+
source: current,
|
|
157
|
+
key,
|
|
158
|
+
...target === void 0 ? {} : { target }
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return detach ? root : true;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Validate and detach lossless JSON in one read per property.
|
|
166
|
+
* @param value - candidate value to validate and detach.
|
|
167
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
168
|
+
*/
|
|
169
|
+
function snapshotJsonValue(value) {
|
|
170
|
+
return walkJsonValue(value, true);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
|
|
174
|
+
* @param value - candidate value to test.
|
|
175
|
+
* @returns whether the value survives a JSON round trip without loss.
|
|
176
|
+
*/
|
|
177
|
+
function isJsonValue(value) {
|
|
178
|
+
return walkJsonValue(value, false) === true;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Compare JSON-compatible values structurally.
|
|
182
|
+
* @param a - one JSON-compatible value.
|
|
183
|
+
* @param b - the other JSON-compatible value.
|
|
184
|
+
* @returns whether both values contain the same JSON data.
|
|
185
|
+
*/
|
|
186
|
+
function deepEqualJson(a, b) {
|
|
187
|
+
if (a === b) return true;
|
|
188
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
189
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
190
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
191
|
+
return a.every((entry, index) => deepEqualJson(entry, b[index]));
|
|
192
|
+
}
|
|
193
|
+
const left = a;
|
|
194
|
+
const right = b;
|
|
195
|
+
const keys = Object.keys(left);
|
|
196
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
197
|
+
return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
201
|
+
* @param value - value to freeze.
|
|
202
|
+
* @returns the same value after every reachable enumerable child is frozen.
|
|
203
|
+
*/
|
|
204
|
+
function deepFreeze(value) {
|
|
205
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
206
|
+
const pending = [{
|
|
207
|
+
kind: "visit",
|
|
208
|
+
node: value
|
|
209
|
+
}];
|
|
210
|
+
while (pending.length > 0) {
|
|
211
|
+
const task = pending.pop();
|
|
212
|
+
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
213
|
+
if (task === void 0) continue;
|
|
214
|
+
if (task.kind === "property") {
|
|
215
|
+
pending.push({
|
|
216
|
+
kind: "visit",
|
|
217
|
+
node: task.source[task.key]
|
|
218
|
+
});
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const node = task.node;
|
|
222
|
+
if (node === null || typeof node !== "object") continue;
|
|
223
|
+
if (node instanceof AbortSignal) continue;
|
|
224
|
+
if (seen.has(node)) continue;
|
|
225
|
+
seen.add(node);
|
|
226
|
+
Object.freeze(node);
|
|
227
|
+
const keys = Object.keys(node);
|
|
228
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
229
|
+
const key = keys[index];
|
|
230
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
231
|
+
if (key === void 0) continue;
|
|
232
|
+
pending.push({
|
|
233
|
+
kind: "property",
|
|
234
|
+
source: node,
|
|
235
|
+
key
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region ../../compaction/compaction-image-offload/src/project-message.ts
|
|
243
|
+
/**
|
|
244
|
+
* Project selected image occurrences to immutable offloaded blocks.
|
|
245
|
+
* @param message - message projected before this decision.
|
|
246
|
+
* @param indexes - nonempty, strictly increasing depth-first image indexes.
|
|
247
|
+
* @returns an immutable message with the same identity and selected images marked.
|
|
248
|
+
* @throws when a selected occurrence is missing or already offloaded.
|
|
249
|
+
*/
|
|
250
|
+
function offloadMessageImages(message, indexes) {
|
|
251
|
+
let imageIndex = 0;
|
|
252
|
+
let selected = 0;
|
|
253
|
+
const visit = (blocks) => {
|
|
254
|
+
let next;
|
|
255
|
+
for (const [index, block] of blocks.entries()) {
|
|
256
|
+
let projected = block;
|
|
257
|
+
if (block.type === "image") {
|
|
258
|
+
if (imageIndex === indexes[selected]) {
|
|
259
|
+
if (block.offloaded === true) throw new Error(`image/offload: image index ${imageIndex} is already offloaded`);
|
|
260
|
+
projected = {
|
|
261
|
+
...block,
|
|
262
|
+
offloaded: true
|
|
263
|
+
};
|
|
264
|
+
selected += 1;
|
|
265
|
+
}
|
|
266
|
+
imageIndex += 1;
|
|
267
|
+
} else if (block.type === "tool-result") {
|
|
268
|
+
const content = visit(block.content);
|
|
269
|
+
if (content !== block.content) projected = {
|
|
270
|
+
...block,
|
|
271
|
+
content
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (projected !== block) next ??= blocks.slice(0, index);
|
|
275
|
+
next?.push(projected);
|
|
276
|
+
}
|
|
277
|
+
return next ?? blocks;
|
|
278
|
+
};
|
|
279
|
+
const content = visit(message.content);
|
|
280
|
+
if (selected !== indexes.length) throw new Error(`image/offload: image index ${indexes[selected]} does not exist`);
|
|
281
|
+
return deepFreeze({
|
|
282
|
+
...message,
|
|
283
|
+
content
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region ../../compaction/compaction-image-offload/src/projection.ts
|
|
288
|
+
/** Whether a durable value is a JSON object. */
|
|
289
|
+
function isRecord$1(value) {
|
|
290
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
291
|
+
}
|
|
292
|
+
/** Whether a durable occurrence index or sequence is canonical. */
|
|
293
|
+
function isIndex(value) {
|
|
294
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0);
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region ../session-format-catalog/src/message-projections.ts
|
|
298
|
+
/** Pure first-party message interpreters for detached current-format replay, including browser readers. */
|
|
299
|
+
/** Installed interpretation definitions; recovery listeners are mounted separately by their owning plugins. */
|
|
300
|
+
const currentSessionMessageProjections = [{
|
|
301
|
+
type: "image/offload",
|
|
302
|
+
project(event, context) {
|
|
303
|
+
const data = event.data;
|
|
304
|
+
if (!isRecord$1(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");
|
|
305
|
+
const messages = /* @__PURE__ */ new Map();
|
|
306
|
+
const nodes = new Set(context.nodes);
|
|
307
|
+
for (const target of data["targets"]) {
|
|
308
|
+
if (!isRecord$1(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");
|
|
309
|
+
const seq = target["seq"];
|
|
310
|
+
if (messages.has(seq)) throw new Error(`image/offload: duplicate target seq ${seq}`);
|
|
311
|
+
if (!nodes.has(seq)) throw new Error(`image/offload: target seq ${seq} is not a current surface node`);
|
|
312
|
+
const source = context.events[seq - context.baseSeq];
|
|
313
|
+
if (source?.type !== "user/message" && source?.type !== "tool/result") throw new Error(`image/offload: target seq ${seq} must be user/message or tool/result`);
|
|
314
|
+
let previous = -1;
|
|
315
|
+
for (const index of target["imageIndexes"]) {
|
|
316
|
+
if (!isIndex(index) || index <= previous) throw new Error("image/offload: imageIndexes must be strictly increasing non-negative safe integers");
|
|
317
|
+
previous = index;
|
|
318
|
+
}
|
|
319
|
+
const message = context.messages.get(seq) ?? (source.type === "user/message" ? source.data : source.data.message);
|
|
320
|
+
messages.set(seq, offloadMessageImages(message, target["imageIndexes"]));
|
|
321
|
+
}
|
|
322
|
+
return messages;
|
|
323
|
+
}
|
|
324
|
+
}];
|
|
325
|
+
//#endregion
|
|
12
326
|
//#region ../../../vendor/cosmokit/src/misc.ts
|
|
13
327
|
/** Return true when a value is `null` or `undefined`. */
|
|
14
328
|
function isNullable(value) {
|
|
@@ -1390,8 +1704,8 @@ var Fiber = class {
|
|
|
1390
1704
|
*
|
|
1391
1705
|
* @param config — the new raw config; validated before anything restarts.
|
|
1392
1706
|
* @param noSave — hint for persistence hooks not to write the change back.
|
|
1393
|
-
* @returns the
|
|
1394
|
-
* @throws when
|
|
1707
|
+
* @returns nothing; the restart runs behind the `internal/update` waterfall.
|
|
1708
|
+
* @throws {ValidationError} when the new config fails validation.
|
|
1395
1709
|
*/
|
|
1396
1710
|
update(config, noSave = false) {
|
|
1397
1711
|
this.assertActive();
|
|
@@ -1403,7 +1717,7 @@ var Fiber = class {
|
|
|
1403
1717
|
return;
|
|
1404
1718
|
}
|
|
1405
1719
|
config = this._resolveConfig(config);
|
|
1406
|
-
|
|
1720
|
+
this.context.waterfall(this, "internal/update", config, noSave, () => {
|
|
1407
1721
|
this.config = config;
|
|
1408
1722
|
this._error = void 0;
|
|
1409
1723
|
return this.restart();
|
|
@@ -2083,236 +2397,6 @@ function brandNumber(value) {
|
|
|
2083
2397
|
return value;
|
|
2084
2398
|
}
|
|
2085
2399
|
//#endregion
|
|
2086
|
-
//#region ../../util/values/src/index.ts
|
|
2087
|
-
/**
|
|
2088
|
-
* Mark an unreachable closed-union branch.
|
|
2089
|
-
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
2090
|
-
* @param context - optional switch-site label included in the failure message.
|
|
2091
|
-
* @returns never; a runtime value that escaped its type always throws.
|
|
2092
|
-
*/
|
|
2093
|
-
function assertNever(value, context) {
|
|
2094
|
-
const rendered = JSON.stringify(value) ?? String(value);
|
|
2095
|
-
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
2096
|
-
}
|
|
2097
|
-
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
2098
|
-
function hasIntrinsicConstructor(prototype, name) {
|
|
2099
|
-
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
2100
|
-
if (typeof constructor !== "function") return false;
|
|
2101
|
-
try {
|
|
2102
|
-
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
2103
|
-
} catch {
|
|
2104
|
-
return false;
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
2108
|
-
function isIntrinsicObjectPrototype(value) {
|
|
2109
|
-
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
2110
|
-
}
|
|
2111
|
-
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
2112
|
-
function hasPlainArrayPrototype(value) {
|
|
2113
|
-
const prototype = Object.getPrototypeOf(value);
|
|
2114
|
-
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
2115
|
-
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
2116
|
-
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
2117
|
-
}
|
|
2118
|
-
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
2119
|
-
function hasPlainObjectPrototype(value) {
|
|
2120
|
-
const prototype = Object.getPrototypeOf(value);
|
|
2121
|
-
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
2122
|
-
}
|
|
2123
|
-
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
2124
|
-
function enumerableStringKeys(value) {
|
|
2125
|
-
const keys = Reflect.ownKeys(value);
|
|
2126
|
-
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
2127
|
-
return keys;
|
|
2128
|
-
}
|
|
2129
|
-
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
2130
|
-
function walkJsonValue(value, detach) {
|
|
2131
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
2132
|
-
let root;
|
|
2133
|
-
const assign = (destination, item) => {
|
|
2134
|
-
if (destination === void 0) return;
|
|
2135
|
-
if (destination.kind === "root") root = item;
|
|
2136
|
-
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
2137
|
-
else Object.defineProperty(destination.target, destination.key, {
|
|
2138
|
-
value: item,
|
|
2139
|
-
enumerable: true,
|
|
2140
|
-
configurable: true,
|
|
2141
|
-
writable: true
|
|
2142
|
-
});
|
|
2143
|
-
};
|
|
2144
|
-
const tasks = [{
|
|
2145
|
-
kind: "visit",
|
|
2146
|
-
value,
|
|
2147
|
-
...detach ? { destination: { kind: "root" } } : {}
|
|
2148
|
-
}];
|
|
2149
|
-
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
2150
|
-
if (task.kind === "leave") {
|
|
2151
|
-
ancestors.delete(task.source);
|
|
2152
|
-
continue;
|
|
2153
|
-
}
|
|
2154
|
-
if (task.kind === "array-item") {
|
|
2155
|
-
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
2156
|
-
tasks.push({
|
|
2157
|
-
kind: "visit",
|
|
2158
|
-
value: task.source[task.index],
|
|
2159
|
-
...task.target === void 0 ? {} : { destination: {
|
|
2160
|
-
kind: "array",
|
|
2161
|
-
target: task.target,
|
|
2162
|
-
index: task.index
|
|
2163
|
-
} }
|
|
2164
|
-
});
|
|
2165
|
-
continue;
|
|
2166
|
-
}
|
|
2167
|
-
if (task.kind === "object-property") {
|
|
2168
|
-
tasks.push({
|
|
2169
|
-
kind: "visit",
|
|
2170
|
-
value: task.source[task.key],
|
|
2171
|
-
...task.target === void 0 ? {} : { destination: {
|
|
2172
|
-
kind: "object",
|
|
2173
|
-
target: task.target,
|
|
2174
|
-
key: task.key
|
|
2175
|
-
} }
|
|
2176
|
-
});
|
|
2177
|
-
continue;
|
|
2178
|
-
}
|
|
2179
|
-
const current = task.value;
|
|
2180
|
-
if (current === null) {
|
|
2181
|
-
assign(task.destination, null);
|
|
2182
|
-
continue;
|
|
2183
|
-
}
|
|
2184
|
-
if (typeof current === "boolean" || typeof current === "string") {
|
|
2185
|
-
assign(task.destination, current);
|
|
2186
|
-
continue;
|
|
2187
|
-
}
|
|
2188
|
-
if (typeof current === "number") {
|
|
2189
|
-
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
2190
|
-
assign(task.destination, current);
|
|
2191
|
-
continue;
|
|
2192
|
-
}
|
|
2193
|
-
if (typeof current !== "object") return void 0;
|
|
2194
|
-
if (ancestors.has(current)) return void 0;
|
|
2195
|
-
if (Array.isArray(current)) {
|
|
2196
|
-
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
2197
|
-
const length = current.length;
|
|
2198
|
-
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
2199
|
-
const target = detach ? [] : void 0;
|
|
2200
|
-
if (target !== void 0) assign(task.destination, target);
|
|
2201
|
-
ancestors.add(current);
|
|
2202
|
-
tasks.push({
|
|
2203
|
-
kind: "leave",
|
|
2204
|
-
source: current
|
|
2205
|
-
});
|
|
2206
|
-
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
2207
|
-
kind: "array-item",
|
|
2208
|
-
source: current,
|
|
2209
|
-
index,
|
|
2210
|
-
...target === void 0 ? {} : { target }
|
|
2211
|
-
});
|
|
2212
|
-
continue;
|
|
2213
|
-
}
|
|
2214
|
-
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
2215
|
-
const keys = enumerableStringKeys(current);
|
|
2216
|
-
if (keys === void 0) return void 0;
|
|
2217
|
-
const target = detach ? {} : void 0;
|
|
2218
|
-
if (target !== void 0) assign(task.destination, target);
|
|
2219
|
-
ancestors.add(current);
|
|
2220
|
-
tasks.push({
|
|
2221
|
-
kind: "leave",
|
|
2222
|
-
source: current
|
|
2223
|
-
});
|
|
2224
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
2225
|
-
const key = keys[index];
|
|
2226
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
2227
|
-
if (key === void 0) return void 0;
|
|
2228
|
-
tasks.push({
|
|
2229
|
-
kind: "object-property",
|
|
2230
|
-
source: current,
|
|
2231
|
-
key,
|
|
2232
|
-
...target === void 0 ? {} : { target }
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
return detach ? root : true;
|
|
2237
|
-
}
|
|
2238
|
-
/**
|
|
2239
|
-
* Validate and detach lossless JSON in one read per property.
|
|
2240
|
-
* @param value - candidate value to validate and detach.
|
|
2241
|
-
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
2242
|
-
*/
|
|
2243
|
-
function snapshotJsonValue(value) {
|
|
2244
|
-
return walkJsonValue(value, true);
|
|
2245
|
-
}
|
|
2246
|
-
/**
|
|
2247
|
-
* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
|
|
2248
|
-
* @param value - candidate value to test.
|
|
2249
|
-
* @returns whether the value survives a JSON round trip without loss.
|
|
2250
|
-
*/
|
|
2251
|
-
function isJsonValue(value) {
|
|
2252
|
-
return walkJsonValue(value, false) === true;
|
|
2253
|
-
}
|
|
2254
|
-
/**
|
|
2255
|
-
* Compare JSON-compatible values structurally.
|
|
2256
|
-
* @param a - one JSON-compatible value.
|
|
2257
|
-
* @param b - the other JSON-compatible value.
|
|
2258
|
-
* @returns whether both values contain the same JSON data.
|
|
2259
|
-
*/
|
|
2260
|
-
function deepEqualJson(a, b) {
|
|
2261
|
-
if (a === b) return true;
|
|
2262
|
-
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
2263
|
-
if (Array.isArray(a) || Array.isArray(b)) {
|
|
2264
|
-
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
2265
|
-
return a.every((entry, index) => deepEqualJson(entry, b[index]));
|
|
2266
|
-
}
|
|
2267
|
-
const left = a;
|
|
2268
|
-
const right = b;
|
|
2269
|
-
const keys = Object.keys(left);
|
|
2270
|
-
if (keys.length !== Object.keys(right).length) return false;
|
|
2271
|
-
return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
|
|
2272
|
-
}
|
|
2273
|
-
/**
|
|
2274
|
-
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
2275
|
-
* @param value - value to freeze.
|
|
2276
|
-
* @returns the same value after every reachable enumerable child is frozen.
|
|
2277
|
-
*/
|
|
2278
|
-
function deepFreeze(value) {
|
|
2279
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
2280
|
-
const pending = [{
|
|
2281
|
-
kind: "visit",
|
|
2282
|
-
node: value
|
|
2283
|
-
}];
|
|
2284
|
-
while (pending.length > 0) {
|
|
2285
|
-
const task = pending.pop();
|
|
2286
|
-
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
2287
|
-
if (task === void 0) continue;
|
|
2288
|
-
if (task.kind === "property") {
|
|
2289
|
-
pending.push({
|
|
2290
|
-
kind: "visit",
|
|
2291
|
-
node: task.source[task.key]
|
|
2292
|
-
});
|
|
2293
|
-
continue;
|
|
2294
|
-
}
|
|
2295
|
-
const node = task.node;
|
|
2296
|
-
if (node === null || typeof node !== "object") continue;
|
|
2297
|
-
if (node instanceof AbortSignal) continue;
|
|
2298
|
-
if (seen.has(node)) continue;
|
|
2299
|
-
seen.add(node);
|
|
2300
|
-
Object.freeze(node);
|
|
2301
|
-
const keys = Object.keys(node);
|
|
2302
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
2303
|
-
const key = keys[index];
|
|
2304
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
2305
|
-
if (key === void 0) continue;
|
|
2306
|
-
pending.push({
|
|
2307
|
-
kind: "property",
|
|
2308
|
-
source: node,
|
|
2309
|
-
key
|
|
2310
|
-
});
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
return value;
|
|
2314
|
-
}
|
|
2315
|
-
//#endregion
|
|
2316
2400
|
//#region ../../core/session/src/types.ts
|
|
2317
2401
|
/**
|
|
2318
2402
|
* Brand a string as a {@link SessionId}.
|
|
@@ -2383,6 +2467,7 @@ const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
|
2383
2467
|
"goal/change",
|
|
2384
2468
|
"hook/invoked",
|
|
2385
2469
|
"hook/result",
|
|
2470
|
+
"image/offload",
|
|
2386
2471
|
"llm/retry",
|
|
2387
2472
|
"llm/retry-started",
|
|
2388
2473
|
"model/selection",
|
|
@@ -2420,6 +2505,8 @@ const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
|
2420
2505
|
"user/message",
|
|
2421
2506
|
"web/deepseek-search-llm-request"
|
|
2422
2507
|
]);
|
|
2508
|
+
/** Event types whose model-visible effects require an explicit pure interpreter. */
|
|
2509
|
+
const MESSAGE_PROJECTION_EVENT_TYPES = new Set(["image/offload"]);
|
|
2423
2510
|
//#endregion
|
|
2424
2511
|
//#region ../../core/session/src/surface.ts
|
|
2425
2512
|
/** Runtime counterpart of the message-producing event union. */
|
|
@@ -2440,17 +2527,18 @@ function isSurfaceEligibleType(type) {
|
|
|
2440
2527
|
/**
|
|
2441
2528
|
* Project a single event into the LLM message it derives to, or null when it
|
|
2442
2529
|
* produces none — a non-surface event (attempt, boundary, log-only record) or an
|
|
2443
|
-
* empty-content assistant/message (which exists only to host usage).
|
|
2444
|
-
*
|
|
2445
|
-
*
|
|
2446
|
-
*
|
|
2447
|
-
*
|
|
2448
|
-
* nested in the event wrapper and shared by delivery, durable history, and
|
|
2449
|
-
* model requests.
|
|
2530
|
+
* empty-content assistant/message (which exists only to host usage). A caller
|
|
2531
|
+
* reconstructing model input supplies the same prefix's `projectedMessages`
|
|
2532
|
+
* from {@link foldSurface}; without that map this function reads original
|
|
2533
|
+
* event content. Session instance methods apply the live projection. Messages
|
|
2534
|
+
* are immutable and unchanged content retains its durable identity.
|
|
2450
2535
|
* @param event - the event to project.
|
|
2536
|
+
* @param projectedMessages - message projections from the same log prefix's surface fold.
|
|
2451
2537
|
* @returns the derived message, or null when the event produces none.
|
|
2452
2538
|
*/
|
|
2453
|
-
function deriveEventMessage(event) {
|
|
2539
|
+
function deriveEventMessage(event, projectedMessages) {
|
|
2540
|
+
const projected = projectedMessages?.get(event.seq);
|
|
2541
|
+
if (projected !== void 0) return projected;
|
|
2454
2542
|
switch (event.type) {
|
|
2455
2543
|
case "user/message": return event.data;
|
|
2456
2544
|
case "system/message":
|
|
@@ -2495,7 +2583,10 @@ function validateSessionEventData(event, subject) {
|
|
|
2495
2583
|
function createFoldState() {
|
|
2496
2584
|
return {
|
|
2497
2585
|
nodes: [],
|
|
2498
|
-
replaceGeneration: 0
|
|
2586
|
+
replaceGeneration: 0,
|
|
2587
|
+
contentGeneration: 0,
|
|
2588
|
+
projectedMessages: /* @__PURE__ */ new Map(),
|
|
2589
|
+
projections: /* @__PURE__ */ new Set()
|
|
2499
2590
|
};
|
|
2500
2591
|
}
|
|
2501
2592
|
/** Whether a runtime value is a non-negative safe event sequence. */
|
|
@@ -2524,7 +2615,7 @@ function surfaceOpOf(event) {
|
|
|
2524
2615
|
return op;
|
|
2525
2616
|
}
|
|
2526
2617
|
/** Validate cited source-event seqs against prior log entries and the replacement range. */
|
|
2527
|
-
function
|
|
2618
|
+
function assertSourceEventReferences(event, shadowedSeqs) {
|
|
2528
2619
|
const raw = event.sourceEventSeqs;
|
|
2529
2620
|
if (event.type === "assistant/message" && raw !== void 0) throw new Error("assistant/message embeds its source stream and cannot carry sourceEventSeqs");
|
|
2530
2621
|
const sources = /* @__PURE__ */ new Set();
|
|
@@ -2553,7 +2644,7 @@ function assertProvenance(event, shadowedSeqs) {
|
|
|
2553
2644
|
function validateSurfaceMetadata(event) {
|
|
2554
2645
|
const op = surfaceOpOf(event);
|
|
2555
2646
|
if (op !== void 0 && op !== "append" && (op.startSeq >= event.seq || op.endSeq >= event.seq)) throw new Error(`surface replace at seq ${event.seq}: startSeq and endSeq must reference earlier events`);
|
|
2556
|
-
if (op !== void 0)
|
|
2647
|
+
if (op !== void 0) assertSourceEventReferences(event, []);
|
|
2557
2648
|
return op;
|
|
2558
2649
|
}
|
|
2559
2650
|
/** Locate one replacement range without mutating the current fold state. */
|
|
@@ -2626,16 +2717,28 @@ function assertSystemHeadRewrite(event, state, startIdx, shadowedSeqs, events, b
|
|
|
2626
2717
|
if (event.type !== "system/message" || shadowedSeqs.length !== 1) throw new Error("surface replace: node 0 holds the system prompt and may be rewritten only by a system/message over exactly that node");
|
|
2627
2718
|
}
|
|
2628
2719
|
/** Validate one event at its replay boundary and prepare its atomic fold transition. */
|
|
2629
|
-
function planSurfaceEvent(state, event, expectedSeq, events, baseSeq) {
|
|
2720
|
+
function planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projections) {
|
|
2630
2721
|
if (event.seq !== expectedSeq) throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`);
|
|
2631
2722
|
const surfaceOp = validateSurfaceMetadata(event);
|
|
2723
|
+
const projection = projections.find((item) => item.type === event.type);
|
|
2724
|
+
if (projection !== void 0) return {
|
|
2725
|
+
kind: "project",
|
|
2726
|
+
projection,
|
|
2727
|
+
messages: projection.project(event, {
|
|
2728
|
+
nodes: state.nodes,
|
|
2729
|
+
events,
|
|
2730
|
+
baseSeq,
|
|
2731
|
+
messages: state.projectedMessages
|
|
2732
|
+
})
|
|
2733
|
+
};
|
|
2734
|
+
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`);
|
|
2632
2735
|
if (surfaceOp === void 0) return;
|
|
2633
2736
|
if (surfaceOp === "append") return {
|
|
2634
2737
|
kind: "append",
|
|
2635
2738
|
seq: event.seq
|
|
2636
2739
|
};
|
|
2637
2740
|
const range = replacementRange(state, surfaceOp);
|
|
2638
|
-
|
|
2741
|
+
assertSourceEventReferences(event, range.shadowedSeqs);
|
|
2639
2742
|
assertToolResultRewrite(event, range.shadowedSeqs, events, baseSeq);
|
|
2640
2743
|
assertSystemHeadRewrite(event, state, range.startIdx, range.shadowedSeqs, events, baseSeq);
|
|
2641
2744
|
return {
|
|
@@ -2647,8 +2750,8 @@ function planSurfaceEvent(state, event, expectedSeq, events, baseSeq) {
|
|
|
2647
2750
|
};
|
|
2648
2751
|
}
|
|
2649
2752
|
/** Apply one event and return replacement metadata only when one occurred. */
|
|
2650
|
-
function applySurfaceEvent(state, event, expectedSeq, events, baseSeq) {
|
|
2651
|
-
return applySurfacePlan(state, planSurfaceEvent(state, event, expectedSeq, events, baseSeq));
|
|
2753
|
+
function applySurfaceEvent(state, event, expectedSeq, events, baseSeq, projections) {
|
|
2754
|
+
return applySurfacePlan(state, planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projections));
|
|
2652
2755
|
}
|
|
2653
2756
|
/** Commit one previously validated surface transition. */
|
|
2654
2757
|
function applySurfacePlan(state, plan) {
|
|
@@ -2656,6 +2759,11 @@ function applySurfacePlan(state, plan) {
|
|
|
2656
2759
|
else if (plan?.kind === "replace") {
|
|
2657
2760
|
state.nodes.splice(plan.startIdx, plan.endIdx - plan.startIdx + 1, plan.seq);
|
|
2658
2761
|
state.replaceGeneration += 1;
|
|
2762
|
+
state.contentGeneration += 1;
|
|
2763
|
+
} else if (plan?.kind === "project") {
|
|
2764
|
+
for (const [seq, message] of plan.messages) state.projectedMessages.set(seq, message);
|
|
2765
|
+
state.projections.add(plan.projection);
|
|
2766
|
+
state.contentGeneration += 1;
|
|
2659
2767
|
}
|
|
2660
2768
|
if (plan?.kind !== "replace") return;
|
|
2661
2769
|
return {
|
|
@@ -2669,6 +2777,7 @@ function applySurfacePlan(state, plan) {
|
|
|
2669
2777
|
var SurfaceManager = class {
|
|
2670
2778
|
log;
|
|
2671
2779
|
baseSeq;
|
|
2780
|
+
projections;
|
|
2672
2781
|
/** Shared transition state; replacement history is not retained. */
|
|
2673
2782
|
_state = createFoldState();
|
|
2674
2783
|
/** Last processed absolute seq. */
|
|
@@ -2678,10 +2787,12 @@ var SurfaceManager = class {
|
|
|
2678
2787
|
/**
|
|
2679
2788
|
* @param log - Contiguous complete log or loaded event window.
|
|
2680
2789
|
* @param baseSeq - Absolute sequence of the window's first event.
|
|
2790
|
+
* @param projections - live borrowed definitions; removing a used definition invalidates further reads.
|
|
2681
2791
|
*/
|
|
2682
|
-
constructor(log, baseSeq = SessionLogOffset(0)) {
|
|
2792
|
+
constructor(log, baseSeq = SessionLogOffset(0), projections = []) {
|
|
2683
2793
|
this.log = log;
|
|
2684
2794
|
this.baseSeq = baseSeq;
|
|
2795
|
+
this.projections = projections;
|
|
2685
2796
|
this._lastProcessedSeq = baseSeq === 0 ? -1 : SessionSeq(baseSeq - 1);
|
|
2686
2797
|
}
|
|
2687
2798
|
/**
|
|
@@ -2689,21 +2800,40 @@ var SurfaceManager = class {
|
|
|
2689
2800
|
* @param event - candidate event that has not entered the log yet.
|
|
2690
2801
|
*/
|
|
2691
2802
|
validateNext(event) {
|
|
2803
|
+
this._assertProjections();
|
|
2692
2804
|
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2693
2805
|
const expectedSeq = SessionSeq(this.baseSeq + this.log.length);
|
|
2694
2806
|
this._pendingPlan = {
|
|
2695
2807
|
event,
|
|
2696
2808
|
expectedSeq,
|
|
2697
|
-
plan: planSurfaceEvent(this._state, event, expectedSeq, this.log, this.baseSeq)
|
|
2809
|
+
plan: planSurfaceEvent(this._state, event, expectedSeq, this.log, this.baseSeq, this.projections)
|
|
2698
2810
|
};
|
|
2699
2811
|
}
|
|
2700
2812
|
/** Monotonic count of folded positional replacements. */
|
|
2701
2813
|
get replaceGeneration() {
|
|
2814
|
+
this._assertProjections();
|
|
2702
2815
|
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2703
2816
|
return this._state.replaceGeneration;
|
|
2704
2817
|
}
|
|
2818
|
+
/** Monotonic count of committed changes to existing model-visible content. */
|
|
2819
|
+
get contentGeneration() {
|
|
2820
|
+
this._assertProjections();
|
|
2821
|
+
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2822
|
+
return this._state.contentGeneration;
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Project one message with every committed message projection applied.
|
|
2826
|
+
* @param event - message-producing or log-only event.
|
|
2827
|
+
* @returns its immutable projected message, or null when it produces none.
|
|
2828
|
+
*/
|
|
2829
|
+
deriveEventMessage(event) {
|
|
2830
|
+
this._assertProjections();
|
|
2831
|
+
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2832
|
+
return deriveEventMessage(event, this._state.projectedMessages);
|
|
2833
|
+
}
|
|
2705
2834
|
/** Surface event sequences in model-visible order. */
|
|
2706
2835
|
get nodes() {
|
|
2836
|
+
this._assertProjections();
|
|
2707
2837
|
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2708
2838
|
return this._state.nodes;
|
|
2709
2839
|
}
|
|
@@ -2715,11 +2845,18 @@ var SurfaceManager = class {
|
|
|
2715
2845
|
const event = this.log[index];
|
|
2716
2846
|
const pending = this._pendingPlan;
|
|
2717
2847
|
if (pending?.event === event && pending.expectedSeq === seq) applySurfacePlan(this._state, pending.plan);
|
|
2718
|
-
else applySurfaceEvent(this._state, event, SessionSeq(seq), this.log, this.baseSeq);
|
|
2848
|
+
else applySurfaceEvent(this._state, event, SessionSeq(seq), this.log, this.baseSeq, this.projections);
|
|
2719
2849
|
if (pending !== void 0 && pending.expectedSeq <= seq) this._pendingPlan = void 0;
|
|
2720
2850
|
this._lastProcessedSeq = SessionSeq(seq);
|
|
2721
2851
|
}
|
|
2722
2852
|
}
|
|
2853
|
+
/** Cached messages cannot outlive the definitions that interpreted their log. */
|
|
2854
|
+
_assertProjections() {
|
|
2855
|
+
const candidate = this._pendingPlan;
|
|
2856
|
+
const pending = candidate !== void 0 && this.log[candidate.expectedSeq - this.baseSeq] === candidate.event ? candidate.plan : void 0;
|
|
2857
|
+
const required = pending?.kind === "project" ? [...this._state.projections, pending.projection] : this._state.projections;
|
|
2858
|
+
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`);
|
|
2859
|
+
}
|
|
2723
2860
|
};
|
|
2724
2861
|
//#endregion
|
|
2725
2862
|
//#region ../../typert/protocol/src/remote-error.ts
|
|
@@ -3719,13 +3856,15 @@ function failureSnapshot(value) {
|
|
|
3719
3856
|
const status = candidate.status;
|
|
3720
3857
|
const providerRetryAfterMs = candidate.providerRetryAfterMs;
|
|
3721
3858
|
const requestId = candidate.requestId;
|
|
3722
|
-
|
|
3859
|
+
const offloadImages = candidate.offloadImages;
|
|
3860
|
+
if (typeof message !== "string" || message.length === 0 || typeof code !== "string" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== "string" || requestId.length === 0) || offloadImages !== void 0 && (!Number.isSafeInteger(offloadImages) || offloadImages <= 0)) return void 0;
|
|
3723
3861
|
return Object.freeze({
|
|
3724
3862
|
message,
|
|
3725
3863
|
code,
|
|
3726
3864
|
...status === void 0 ? {} : { status },
|
|
3727
3865
|
...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },
|
|
3728
|
-
...requestId === void 0 ? {} : { requestId }
|
|
3866
|
+
...requestId === void 0 ? {} : { requestId },
|
|
3867
|
+
...offloadImages === void 0 ? {} : { offloadImages }
|
|
3729
3868
|
});
|
|
3730
3869
|
} catch (_sdkFailureGetter) {
|
|
3731
3870
|
return;
|
|
@@ -4414,7 +4553,8 @@ var LlmError = class extends HarnessError {
|
|
|
4414
4553
|
code,
|
|
4415
4554
|
...options?.status === void 0 ? {} : { status: options.status },
|
|
4416
4555
|
...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },
|
|
4417
|
-
...options?.requestId === void 0 ? {} : { requestId: options.requestId }
|
|
4556
|
+
...options?.requestId === void 0 ? {} : { requestId: options.requestId },
|
|
4557
|
+
...options?.offloadImages === void 0 ? {} : { offloadImages: options.offloadImages }
|
|
4418
4558
|
});
|
|
4419
4559
|
}
|
|
4420
4560
|
};
|
|
@@ -5291,7 +5431,7 @@ const attachments = /* @__PURE__ */ new WeakMap();
|
|
|
5291
5431
|
var Session = class Session {
|
|
5292
5432
|
log = [];
|
|
5293
5433
|
/** Single incremental owner of surface acceptance and projection state. */
|
|
5294
|
-
surfaceManager
|
|
5434
|
+
surfaceManager;
|
|
5295
5435
|
/** The ordered surface over this session's event log. */
|
|
5296
5436
|
get surface() {
|
|
5297
5437
|
return this.surfaceManager;
|
|
@@ -5342,10 +5482,12 @@ var Session = class Session {
|
|
|
5342
5482
|
* @param seed - optional borrowed replay or fork events.
|
|
5343
5483
|
* @param header - optional borrowed storage metadata.
|
|
5344
5484
|
* @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
|
|
5485
|
+
* @param projections - pure interpreters for plugin-owned message changes.
|
|
5345
5486
|
* @returns a detached session.
|
|
5487
|
+
* @throws when a seed event requires a missing message interpreter or fails validation.
|
|
5346
5488
|
*/
|
|
5347
|
-
static create(id, seed, header, inheritedEventCount) {
|
|
5348
|
-
return new Session(id, seed, header, "snapshot", inheritedEventCount);
|
|
5489
|
+
static create(id, seed, header, inheritedEventCount, projections) {
|
|
5490
|
+
return new Session(id, seed, header, "snapshot", inheritedEventCount, projections);
|
|
5349
5491
|
}
|
|
5350
5492
|
/**
|
|
5351
5493
|
* Restore a detached session by adopting an independently owned or deeply frozen seed.
|
|
@@ -5358,12 +5500,15 @@ var Session = class Session {
|
|
|
5358
5500
|
* @param header - independently owned storage metadata.
|
|
5359
5501
|
* @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
|
|
5360
5502
|
* @param eventState - aliasing state carried from the operation that produced the seed.
|
|
5503
|
+
* @param projections - pure interpreters for plugin-owned message changes.
|
|
5361
5504
|
* @returns a restored detached session.
|
|
5505
|
+
* @throws when a seed event requires a missing message interpreter or fails validation.
|
|
5362
5506
|
*/
|
|
5363
|
-
static fromRestore(id, seed, header, inheritedEventCount, eventState) {
|
|
5364
|
-
return new Session(id, seed, header, eventState, inheritedEventCount);
|
|
5507
|
+
static fromRestore(id, seed, header, inheritedEventCount, eventState, projections) {
|
|
5508
|
+
return new Session(id, seed, header, eventState, inheritedEventCount, projections);
|
|
5365
5509
|
}
|
|
5366
|
-
constructor(id, seed, header, mode = "snapshot", suppliedInheritedEventCount) {
|
|
5510
|
+
constructor(id, seed, header, mode = "snapshot", suppliedInheritedEventCount, projections = []) {
|
|
5511
|
+
this.surfaceManager = new SurfaceManager(this.log, SessionLogOffset(0), projections);
|
|
5367
5512
|
const restoredHeader = mode === "snapshot" ? void 0 : validateRestoredSessionHeader(id, header);
|
|
5368
5513
|
if (seed !== void 0) for (const [index, source] of seed.entries()) {
|
|
5369
5514
|
const snapshot = mode === "snapshot" ? snapshotJsonValue(source) : source;
|
|
@@ -5393,6 +5538,8 @@ var Session = class Session {
|
|
|
5393
5538
|
eventsSnapshot;
|
|
5394
5539
|
/**
|
|
5395
5540
|
* Return the immutable event stored at one exact sequence number.
|
|
5541
|
+
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
|
5542
|
+
* See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
|
|
5396
5543
|
* @param seq - event sequence number.
|
|
5397
5544
|
* @returns the accepted event, or undefined when the log does not contain it.
|
|
5398
5545
|
*/
|
|
@@ -5403,6 +5550,8 @@ var Session = class Session {
|
|
|
5403
5550
|
* Materialize an immutable snapshot of a half-open event sequence range.
|
|
5404
5551
|
* A full current snapshot is reused until the next append; every previously
|
|
5405
5552
|
* returned snapshot remains stable after later appends.
|
|
5553
|
+
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
|
5554
|
+
* See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
|
|
5406
5555
|
* @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
|
|
5407
5556
|
* @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
|
|
5408
5557
|
* @returns a frozen array of the selected deeply frozen events.
|
|
@@ -5416,6 +5565,8 @@ var Session = class Session {
|
|
|
5416
5565
|
}
|
|
5417
5566
|
/**
|
|
5418
5567
|
* Return this Session's events after its fork-inherited prefix.
|
|
5568
|
+
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
|
5569
|
+
* See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
|
|
5419
5570
|
* @returns a fresh array containing child-owned events in log order.
|
|
5420
5571
|
*/
|
|
5421
5572
|
ownEvents() {
|
|
@@ -5549,7 +5700,7 @@ var Session = class Session {
|
|
|
5549
5700
|
derived = [];
|
|
5550
5701
|
/** Surface position (nodes projected) the cache has reached. */
|
|
5551
5702
|
derivedNodes = 0;
|
|
5552
|
-
/** {@link SurfaceManager.
|
|
5703
|
+
/** {@link SurfaceManager.contentGeneration} the cache was built under. */
|
|
5553
5704
|
derivedGeneration = 0;
|
|
5554
5705
|
/**
|
|
5555
5706
|
* Derive the LLM message history by walking the ordered sequences of
|
|
@@ -5558,21 +5709,21 @@ var Session = class Session {
|
|
|
5558
5709
|
* append records its `surfaceOp`, so a raw event with no marker (a chunk, a
|
|
5559
5710
|
* turn boundary) is correctly absent, and a compaction `replace` deletes the
|
|
5560
5711
|
* shadowed nodes from the derivation. The projection rules are
|
|
5561
|
-
* {@link deriveEventMessage},
|
|
5712
|
+
* {@link deriveEventMessage}, with logged message projections applied
|
|
5713
|
+
* without changing node membership or message identity.
|
|
5562
5714
|
*
|
|
5563
|
-
* CACHED:
|
|
5564
|
-
*
|
|
5565
|
-
* {@link SessionSurface.replaceGeneration}) rebuilds. The returned array is
|
|
5715
|
+
* CACHED: pure tail growth costs O(new nodes); a replacement or message projection
|
|
5716
|
+
* ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
|
|
5566
5717
|
* a fresh snapshot per call (later appends never grow an array a caller
|
|
5567
5718
|
* already holds); the `Message` objects in it are SHARED and **deep-frozen**.
|
|
5568
|
-
*
|
|
5569
|
-
*
|
|
5719
|
+
* Unchanged content reuses frozen event data; projected blocks are frozen
|
|
5720
|
+
* derived copies. Consumers cannot mutate the log through either form.
|
|
5570
5721
|
* @returns a fresh array of the shared, frozen derived history.
|
|
5571
5722
|
*/
|
|
5572
5723
|
deriveMessages() {
|
|
5573
5724
|
const surface = this.surface;
|
|
5574
5725
|
const nodes = surface.nodes;
|
|
5575
|
-
const generation = surface.
|
|
5726
|
+
const generation = surface.contentGeneration;
|
|
5576
5727
|
if (generation !== this.derivedGeneration) {
|
|
5577
5728
|
this.derived = [];
|
|
5578
5729
|
this.derivedNodes = 0;
|
|
@@ -5586,13 +5737,13 @@ var Session = class Session {
|
|
|
5586
5737
|
return [...this.derived];
|
|
5587
5738
|
}
|
|
5588
5739
|
/**
|
|
5589
|
-
*
|
|
5590
|
-
*
|
|
5740
|
+
* Project one event with all committed message projections applied.
|
|
5741
|
+
* The original durable event remains unchanged.
|
|
5591
5742
|
* @param event - the event to project.
|
|
5592
5743
|
* @returns the derived message, or null when the event produces none.
|
|
5593
5744
|
*/
|
|
5594
5745
|
deriveEventMessage(event) {
|
|
5595
|
-
return deriveEventMessage(event);
|
|
5746
|
+
return this.surfaceManager.deriveEventMessage(event);
|
|
5596
5747
|
}
|
|
5597
5748
|
};
|
|
5598
5749
|
//#endregion
|
|
@@ -6183,7 +6334,7 @@ function validateInstalledCurrentSessionHeader(header) {
|
|
|
6183
6334
|
*/
|
|
6184
6335
|
function validateInstalledCurrentSessionArtifact(artifact) {
|
|
6185
6336
|
if (artifact.header.version !== 3) throw new Error(`installed Session format is v3, got v${artifact.header.version}`);
|
|
6186
|
-
Session.fromRestore(SessionId(artifact.header.id), artifact.events, artifact.header, SessionLogOffset(artifact.inheritedEventCount), "detached");
|
|
6337
|
+
Session.fromRestore(SessionId(artifact.header.id), artifact.events, artifact.header, SessionLogOffset(artifact.inheritedEventCount), "detached", currentSessionMessageProjections);
|
|
6187
6338
|
}
|
|
6188
6339
|
//#endregion
|
|
6189
6340
|
//#region ../session-format-v0-to-v1/src/dispositions.ts
|
|
@@ -7657,11 +7808,11 @@ function assertReleasedV1Header(header) {
|
|
|
7657
7808
|
* @param record - exact event envelope.
|
|
7658
7809
|
* @param seq - event position used for earlier-reference checks.
|
|
7659
7810
|
* @param type - surface event type used in diagnostics.
|
|
7660
|
-
* @param assistantSources - whether this generation admits empty Assistant chunk
|
|
7811
|
+
* @param assistantSources - whether this generation admits empty Assistant chunk references.
|
|
7661
7812
|
*/
|
|
7662
7813
|
function assertReleasedSurfaceMetadata(record, seq, type, assistantSources) {
|
|
7663
7814
|
const sources = record["sourceEventSeqs"];
|
|
7664
|
-
if (type === "assistant/message" && sources !== void 0 && assistantSources === "forbid-assistant") throw new SessionFormatError(`assistant/message ${seq} retains obsolete chunk
|
|
7815
|
+
if (type === "assistant/message" && sources !== void 0 && assistantSources === "forbid-assistant") throw new SessionFormatError(`assistant/message ${seq} retains obsolete chunk references`);
|
|
7665
7816
|
if (sources !== void 0) {
|
|
7666
7817
|
if (!Array.isArray(sources)) throw new SessionFormatError(`${type} ${seq} sourceEventSeqs must be an array`);
|
|
7667
7818
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7977,6 +8128,7 @@ function decodeSeqRanges$1(value, maxEntries) {
|
|
|
7977
8128
|
}
|
|
7978
8129
|
//#endregion
|
|
7979
8130
|
//#region ../session-format-v0-to-v1/src/migration.ts
|
|
8131
|
+
const LEGACY_ASSISTANT_SOURCE_KEY = ["pro", "venance"].join("");
|
|
7980
8132
|
/** Identity format edge that promotes released v0 into released v1. */
|
|
7981
8133
|
const sessionFormatV0ToV1 = defineSessionFormatMigration({
|
|
7982
8134
|
name: "@deepseek-ai/dsh-session-format-v0-to-v1",
|
|
@@ -8300,9 +8452,12 @@ function normalizeLegacyMessage(event, sessionId, messageIds) {
|
|
|
8300
8452
|
}
|
|
8301
8453
|
};
|
|
8302
8454
|
case "assistant/message": {
|
|
8303
|
-
if (Object.hasOwn(data, "message") || !Object.hasOwn(data, "content") || !Object.hasOwn(data,
|
|
8304
|
-
const
|
|
8305
|
-
const
|
|
8455
|
+
if (Object.hasOwn(data, "message") || !Object.hasOwn(data, "content") || !Object.hasOwn(data, LEGACY_ASSISTANT_SOURCE_KEY)) return event;
|
|
8456
|
+
const content = data["content"];
|
|
8457
|
+
const eventData = { ...data };
|
|
8458
|
+
delete eventData["content"];
|
|
8459
|
+
const source = releasedV0Record(eventData[LEGACY_ASSISTANT_SOURCE_KEY], `assistant/message ${event.seq} legacy source`);
|
|
8460
|
+
Reflect.deleteProperty(eventData, LEGACY_ASSISTANT_SOURCE_KEY);
|
|
8306
8461
|
return {
|
|
8307
8462
|
...event,
|
|
8308
8463
|
data: {
|
|
@@ -8878,7 +9033,7 @@ const releasedV2SessionFormatCodec = Object.freeze({
|
|
|
8878
9033
|
return encodeHeader(header, inheritedEventCount);
|
|
8879
9034
|
},
|
|
8880
9035
|
encodeEvent(event) {
|
|
8881
|
-
return
|
|
9036
|
+
return encodeSourceEventRanges(event);
|
|
8882
9037
|
}
|
|
8883
9038
|
});
|
|
8884
9039
|
function decodePhysicalHeader(value) {
|
|
@@ -8986,7 +9141,7 @@ function encodeHeader(header, inheritedEventCount) {
|
|
|
8986
9141
|
...header.agentPreset === void 0 ? {} : { agentPreset: header.agentPreset }
|
|
8987
9142
|
};
|
|
8988
9143
|
}
|
|
8989
|
-
function
|
|
9144
|
+
function encodeSourceEventRanges(event) {
|
|
8990
9145
|
if (event.sourceEventSeqs === void 0) return event;
|
|
8991
9146
|
return {
|
|
8992
9147
|
...event,
|
|
@@ -9229,7 +9384,7 @@ function transformMessage(state, event, context) {
|
|
|
9229
9384
|
emitSource(state, messageEvent(event, attemptGroup(turn, step)), context);
|
|
9230
9385
|
return;
|
|
9231
9386
|
}
|
|
9232
|
-
if (pending === void 0 || !matchesChunkSources(pending.group, sources)) throw refusal(`assistant/message ${event.seq} chunk
|
|
9387
|
+
if (pending === void 0 || !matchesChunkSources(pending.group, sources)) throw refusal(`assistant/message ${event.seq} chunk references are not one complete ordered attempt`);
|
|
9233
9388
|
assertAttemptCut(state, pending.group, event.seq);
|
|
9234
9389
|
pending.group.terminal = true;
|
|
9235
9390
|
flushBuffered(state, pending, context);
|
|
@@ -10053,7 +10208,7 @@ function relationshipEvent(event) {
|
|
|
10053
10208
|
//#endregion
|
|
10054
10209
|
//#region ../session-format-v2-to-v3/src/codec.ts
|
|
10055
10210
|
/** V3 framing with hard structural admission and recoverable canonical event validation. */
|
|
10056
|
-
/** V3 codec validates structural rows before recovery and logical envelopes after
|
|
10211
|
+
/** V3 codec validates structural rows before recovery and logical envelopes after source-event range decoding. */
|
|
10057
10212
|
const releasedV3SessionFormatCodec = Object.freeze({
|
|
10058
10213
|
version: 3,
|
|
10059
10214
|
decodeHeader(value) {
|
|
@@ -10119,7 +10274,7 @@ const releasedV3SessionFormatCodec = Object.freeze({
|
|
|
10119
10274
|
});
|
|
10120
10275
|
/**
|
|
10121
10276
|
* Validate owned V3 admission rules before a scanner or codec can discard a recoverable tail.
|
|
10122
|
-
* This checks only identified structural payloads; physical
|
|
10277
|
+
* This checks only identified structural payloads; physical source-event ranges still belong to decoding.
|
|
10123
10278
|
* @param row - parsed physical row, before envelope or compressed-range decoding.
|
|
10124
10279
|
*/
|
|
10125
10280
|
function assertV3RowAdmission(row) {
|
|
@@ -11289,7 +11444,7 @@ async function verifyCurrentGeneration(path, compression, expectedId, expectedEv
|
|
|
11289
11444
|
});
|
|
11290
11445
|
if (generation.meta.id !== expectedId) throw new Error(`current session generation contains id "${generation.meta.id}", expected "${expectedId}"`);
|
|
11291
11446
|
if (generation.events.length !== expectedEventCount) throw new Error(`current session generation contains ${generation.events.length} events, expected ${expectedEventCount}`);
|
|
11292
|
-
Session.fromRestore(generation.meta.id, generation.events, generation.meta, generation.inheritedEventCount, "detached");
|
|
11447
|
+
Session.fromRestore(generation.meta.id, generation.events, generation.meta, generation.inheritedEventCount, "detached", currentSessionMessageProjections);
|
|
11293
11448
|
assertCurrentAssistantStreams(generation.events);
|
|
11294
11449
|
return {
|
|
11295
11450
|
identity: snapshot.identity,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-session-persistence-jsonl",
|
|
3
3
|
"description": "JSONL durable session persistence backend for the DeepSeek Harness",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -28,23 +28,23 @@
|
|
|
28
28
|
],
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
32
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.
|
|
31
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
|
|
32
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.6-alpha.1",
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.2"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"koffi": "^3.1.0",
|
|
37
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
38
|
-
"@deepseek-ai/dsh-session-format": "^0.1.
|
|
39
|
-
"@deepseek-ai/dsh-session-format-catalog": "^0.1.
|
|
37
|
+
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.1",
|
|
38
|
+
"@deepseek-ai/dsh-session-format": "^0.1.6-alpha.1",
|
|
39
|
+
"@deepseek-ai/dsh-session-format-catalog": "^0.1.6-alpha.1",
|
|
40
|
+
"@deepseek-ai/dsh-session-format-v2-to-v3": "^0.1.6-alpha.1",
|
|
40
41
|
"@deepseek-ai/node-addon-system": "^0.1.2",
|
|
41
|
-
"@deepseek-ai/schemastery": "^3.18.2"
|
|
42
|
-
"@deepseek-ai/dsh-session-format-v2-to-v3": "^0.1.5-rc.1"
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
46
|
-
"@deepseek-ai/dsh-session-format-v0-to-v1": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.
|
|
45
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-session-format-v0-to-v1": "^0.1.6-alpha.1",
|
|
47
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.6-alpha.1",
|
|
48
48
|
"@deepseek-ai/cordis": "^4.0.2"
|
|
49
49
|
}
|
|
50
50
|
}
|