@deepseek-ai/dsh-session-persistence-jsonl 0.2.0-rc.2 → 0.2.1-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 +6 -6
- package/README.md +1 -4
- package/README.zh.md +1 -4
- package/lib/worker.cjs +626 -8
- package/package.json +13 -13
package/README.i18n.yaml
CHANGED
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
en: b69d4565a11b6f02
|
|
40
40
|
zh: c8309690fc931f8d
|
|
41
41
|
/deepseek-ai-dsh-session-persistence-jsonl/understand-the-implementation/physical-encoding:
|
|
42
|
-
en:
|
|
43
|
-
zh:
|
|
42
|
+
en: 5aaf80115840318f
|
|
43
|
+
zh: 0b0fb2b2e085a111
|
|
44
44
|
/deepseek-ai-dsh-session-persistence-jsonl/understand-the-implementation/source-map:
|
|
45
|
-
en:
|
|
46
|
-
zh:
|
|
45
|
+
en: 2a4fd4bde1da0a1b
|
|
46
|
+
zh: d7a8cbdad7beee09
|
|
47
47
|
/deepseek-ai-dsh-session-persistence-jsonl/further-exploration:
|
|
48
|
-
en:
|
|
49
|
-
zh:
|
|
48
|
+
en: 71fbff06a098440a
|
|
49
|
+
zh: d3c07db42668ea18
|
|
50
50
|
/deepseek-ai-dsh-session-persistence-jsonl/model-experience:
|
|
51
51
|
en: 215e7ba838619b7b
|
|
52
52
|
zh: a311da3843709f90
|
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ The backend owns its complete storage runtime (`src/storage.ts`): `JsonlSessionH
|
|
|
101
101
|
|
|
102
102
|
### Physical encoding
|
|
103
103
|
|
|
104
|
-
The default artifact is a standard concatenation of independent
|
|
104
|
+
The default artifact is a standard concatenation of independent Zstandard frames: one checksummed frame containing only the header line, then one checksummed frame per durable append batch, using Node's built-in Zstandard API at its default compression level (no level knob). The current format writes one event per row; `sourceEventSeqs` uses a lossless storage representation in which consecutive runs of at least three sequence numbers become `[start, end]` pairs, any other list stays verbatim, and reading expands the exact in-memory array. Historical migration reuses one Zstandard decoder, passes parsed rows through stateful format stages, and streams current records through one compression context in about 1 MiB main-thread slices while retaining only final current events, bounded decoder state, and the required sequence-remap table. Listing reads and validates only the header frame. `compression: 'none'` keeps the same storage-form logical lines without frame compression. A root belongs to one encoding: startup discovery and targeted lookup reject generations with the other suffix; format migration preserves the configured encoding, while compression conversion, mixed-root fallback, and dual write remain unsupported. Frozen v0 and v1 codecs retain their packed-row decoders solely for historical generations.
|
|
105
105
|
|
|
106
106
|
### Source map
|
|
107
107
|
|
|
@@ -114,7 +114,6 @@ The default artifact is a standard concatenation of independent [Zstandard frame
|
|
|
114
114
|
| [`src/migration-verifier.ts`](src/migration-verifier.ts) | Worker lifecycle for staged and competing-generation verification |
|
|
115
115
|
| [`src/zstd.ts`](src/zstd.ts) | Zstandard frame compression, decoding, and frame scanning |
|
|
116
116
|
| [`src/win32.ts`](src/win32.ts) | Windows write-through publish and directory creation |
|
|
117
|
-
| — | No runtime invariant companion is published; persistence correctness requires backend round-trip and crash-tail tests; this package exposes no continuously observable in-process relation. |
|
|
118
117
|
|
|
119
118
|
</details>
|
|
120
119
|
|
|
@@ -127,8 +126,6 @@ Read these pages when the package-level contract is not enough. They move from t
|
|
|
127
126
|
|
|
128
127
|
- [Session persistence subsystem](../../../docs/subsystems/persistence.md) — backend-neutral service semantics and provider relationships.
|
|
129
128
|
- [Session persistence seam](../session-persistence/README.md) — the service contract this backend implements.
|
|
130
|
-
- [Project-session directory decision](../../../.agents/notes/implemented/architecture/2026-07-24-project-session-directories.md) — the layout tradeoff behind project and session directories.
|
|
131
|
-
- [Zstandard JSONL session logs](../../../.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md) — the checksummed-frame encoding rationale.
|
|
132
129
|
- [Released Session format migrations](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) — immutable generations, adjacent migration edges, and publication rules.
|
|
133
130
|
|
|
134
131
|
-----
|
package/README.zh.md
CHANGED
|
@@ -101,7 +101,7 @@ kind: "package-reference"
|
|
|
101
101
|
|
|
102
102
|
### 物理编码
|
|
103
103
|
|
|
104
|
-
默认产物是独立
|
|
104
|
+
默认产物是独立 Zstandard 帧 的标准拼接:一个仅包含 header 行的带校验和帧,后跟每个持久 append 批次一个带校验和帧,使用 Node 内置 Zstandard API 的默认压缩级别(无级别开关)。当前格式为每个事件写一行;`sourceEventSeqs` 使用无损存储形式:至少包含三个序列号的连续段会变成 `[start, end]` 区间对,其他列表原样保留;读取时会展开回精确的内存数组。历史迁移会复用一个 Zstandard decoder,让已解析行流经有状态格式 Stage,并通过一个压缩 context 以约 1 MiB 主线程分片流式写入当前记录,同时只保留最终当前事件、有界 decoder 状态与必需的序号重映射表。列表只读取并验证 header 帧。`compression: 'none'` 保留相同的存储形式逻辑行,但不使用帧压缩。一个根只属于一种编码:启动发现与定向查找会拒绝使用另一后缀的 generation;格式迁移保留已配置编码,而压缩转换、混合根回退与双写仍不受支持。冻结的 v0 与 v1 codec 仅为历史 generation 保留 packed-row decoder。
|
|
105
105
|
|
|
106
106
|
### 源码地图
|
|
107
107
|
|
|
@@ -114,7 +114,6 @@ kind: "package-reference"
|
|
|
114
114
|
| [`src/migration-verifier.ts`](src/migration-verifier.ts) | stage 与竞争 generation 校验的 Worker 生命周期 |
|
|
115
115
|
| [`src/zstd.ts`](src/zstd.ts) | Zstandard 帧压缩、解码与帧扫描 |
|
|
116
116
|
| [`src/win32.ts`](src/win32.ts) | Windows write-through 发布与目录创建 |
|
|
117
|
-
| — | 不发布运行时不变式伴生入口;身份在存储层强制;持久化正确性依赖后端往返与崩溃尾部测试,本包不公开可持续观察的进程内关系。 |
|
|
118
117
|
|
|
119
118
|
</details>
|
|
120
119
|
|
|
@@ -127,8 +126,6 @@ kind: "package-reference"
|
|
|
127
126
|
|
|
128
127
|
- [会话持久化子系统](../../../docs/subsystems/persistence.zh.md)——后端无关的服务语义与提供方关系。
|
|
129
128
|
- [会话持久化 seam](../session-persistence/README.zh.md)——本后端实现的服务约定。
|
|
130
|
-
- [项目会话目录决策](../../../.agents/notes/implemented/architecture/2026-07-24-project-session-directories.zh.md)——项目与会话目录布局背后的取舍。
|
|
131
|
-
- [Zstandard JSONL 会话日志](../../../.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md)——带校验和帧编码的理由。
|
|
132
129
|
- [已发布 Session 格式迁移](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)——不可变 generation、相邻迁移边与发布规则。
|
|
133
130
|
|
|
134
131
|
-----
|
package/lib/worker.cjs
CHANGED
|
@@ -9,6 +9,630 @@ 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/partial-json.ts
|
|
13
|
+
/**
|
|
14
|
+
* Lazily scanned view of one JSON object's top-level fields, built from text
|
|
15
|
+
* that may still be streaming or from an already parsed object. Nothing is
|
|
16
|
+
* scanned until a reader asks; the view remembers every question it answered
|
|
17
|
+
* and reports changed answers when the owner refreshes for publication.
|
|
18
|
+
* Used for model tool-call arguments: a row reads the fields it
|
|
19
|
+
* cares about at whatever granularity it displays, at every stage of the call.
|
|
20
|
+
* @module @deepseek-ai/dsh-util-values/src/partial-json
|
|
21
|
+
*/
|
|
22
|
+
const SIMPLE_ESCAPES = {
|
|
23
|
+
"\"": "\"",
|
|
24
|
+
"\\": "\\",
|
|
25
|
+
"/": "/",
|
|
26
|
+
b: "\b",
|
|
27
|
+
f: "\f",
|
|
28
|
+
n: "\n",
|
|
29
|
+
r: "\r",
|
|
30
|
+
t: " "
|
|
31
|
+
};
|
|
32
|
+
const CONTENT_ESCAPE = /[\\\u0000-\u001f]/u;
|
|
33
|
+
function isWhitespace(c) {
|
|
34
|
+
return c === " " || c === "\n" || c === "\r" || c === " ";
|
|
35
|
+
}
|
|
36
|
+
function isHex(c) {
|
|
37
|
+
return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
|
|
38
|
+
}
|
|
39
|
+
(class PartialArguments {
|
|
40
|
+
/** The view of a call with no arguments available. */
|
|
41
|
+
static EMPTY = PartialArguments.fromObject({});
|
|
42
|
+
/**
|
|
43
|
+
* View finished argument text without scanning it until a reader asks.
|
|
44
|
+
* @param text - the complete argument JSON text.
|
|
45
|
+
* @returns a sealed view.
|
|
46
|
+
*/
|
|
47
|
+
static fromText(text) {
|
|
48
|
+
const view = new PartialArguments();
|
|
49
|
+
view.append(text);
|
|
50
|
+
view.sealed = true;
|
|
51
|
+
return view;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* View an already parsed argument payload, such as a PTC dispatch object.
|
|
55
|
+
* @param value - the parsed argument value.
|
|
56
|
+
* @returns a sealed view; a non-object payload has no fields.
|
|
57
|
+
*/
|
|
58
|
+
static fromObject(value) {
|
|
59
|
+
const view = new PartialArguments();
|
|
60
|
+
view.object = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
61
|
+
view.sealed = true;
|
|
62
|
+
return view;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The source: text so far or a parsed object, plus whether it can still grow.
|
|
66
|
+
* These are the only enumerable fields, so two views over the same source
|
|
67
|
+
* compare equal structurally however far each has been read.
|
|
68
|
+
*/
|
|
69
|
+
chunks = [];
|
|
70
|
+
object;
|
|
71
|
+
sealed = false;
|
|
72
|
+
#ends = [];
|
|
73
|
+
#size = 0;
|
|
74
|
+
#consumed = 0;
|
|
75
|
+
#mode = "root";
|
|
76
|
+
#escape = false;
|
|
77
|
+
#keyStart = 0;
|
|
78
|
+
#keyEscaped = false;
|
|
79
|
+
#key = "";
|
|
80
|
+
#current = null;
|
|
81
|
+
#nestedEnds = [];
|
|
82
|
+
#nestedInString = false;
|
|
83
|
+
#invalidAt;
|
|
84
|
+
#invalidValue = false;
|
|
85
|
+
#entries = /* @__PURE__ */ new Map();
|
|
86
|
+
#order = [];
|
|
87
|
+
#reads = /* @__PURE__ */ new Map();
|
|
88
|
+
/** Whether this view rejects further appends; does not scan text or register reads. */
|
|
89
|
+
get isSealed() {
|
|
90
|
+
return this.sealed;
|
|
91
|
+
}
|
|
92
|
+
/** Whether indexing or a content read found invalid JSON; unread value contents are not validated. */
|
|
93
|
+
get invalid() {
|
|
94
|
+
this.scan();
|
|
95
|
+
return this.#mode === "invalid" || this.#invalidValue;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Retain streamed argument text without scanning or comparing observed answers.
|
|
99
|
+
* @param fragment - the text following every fragment appended before.
|
|
100
|
+
*/
|
|
101
|
+
append(fragment) {
|
|
102
|
+
if (this.sealed) throw new Error("PartialArguments: cannot append to a sealed view");
|
|
103
|
+
if (fragment.length === 0) return;
|
|
104
|
+
this.chunks.push(fragment);
|
|
105
|
+
this.#size += fragment.length;
|
|
106
|
+
this.#ends.push(this.#size);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Reconcile a streamed prefix with authoritative complete text without joining the fragments.
|
|
110
|
+
* @param text - the final argument text, which replaces missing or conflicting deltas.
|
|
111
|
+
* @returns this view sealed with its caches retained when every character matches; otherwise a new sealed view.
|
|
112
|
+
*/
|
|
113
|
+
settle(text) {
|
|
114
|
+
if (this.object !== void 0 || text.length !== this.#size) return PartialArguments.fromText(text);
|
|
115
|
+
let offset = 0;
|
|
116
|
+
for (const chunk of this.chunks) {
|
|
117
|
+
if (!text.startsWith(chunk, offset)) return PartialArguments.fromText(text);
|
|
118
|
+
offset += chunk.length;
|
|
119
|
+
}
|
|
120
|
+
this.chunks = text.length === 0 ? [] : [text];
|
|
121
|
+
this.#ends = text.length === 0 ? [] : [text.length];
|
|
122
|
+
this.sealed = true;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Compare observed answers and advance their publication baseline. Unread views remain unscanned.
|
|
127
|
+
* @returns whether any observed answer changed since its first read or the preceding refresh.
|
|
128
|
+
*/
|
|
129
|
+
refresh() {
|
|
130
|
+
if (this.#reads.size === 0) return false;
|
|
131
|
+
this.scan();
|
|
132
|
+
let changed = false;
|
|
133
|
+
let completions = false;
|
|
134
|
+
for (const read of this.#reads.values()) {
|
|
135
|
+
if (read.completion) {
|
|
136
|
+
completions = true;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
changed = this.refreshRead(read) || changed;
|
|
140
|
+
}
|
|
141
|
+
if (completions) {
|
|
142
|
+
for (const read of this.#reads.values()) if (read.completion) changed = this.refreshRead(read) || changed;
|
|
143
|
+
}
|
|
144
|
+
if (this.sealed) this.#reads.clear();
|
|
145
|
+
return changed;
|
|
146
|
+
}
|
|
147
|
+
refreshRead(read) {
|
|
148
|
+
const now = read.answer();
|
|
149
|
+
if (Object.is(now, read.last)) return false;
|
|
150
|
+
read.last = now;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Check whether no further fields can arrive.
|
|
155
|
+
* @returns whether the outer object closed, indexing failed, or the view is sealed; unread values are not validated.
|
|
156
|
+
*/
|
|
157
|
+
closed() {
|
|
158
|
+
return this.remember("closed", "", () => this.closedNow());
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* List discovered fields in first-appearance order.
|
|
162
|
+
* @returns top-level keys seen so far, in first-appearance order.
|
|
163
|
+
*/
|
|
164
|
+
keys() {
|
|
165
|
+
return this.remember("keys", "", () => this.keysNow(), (keys) => keys.length);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Check whether a top-level field has appeared.
|
|
169
|
+
* @param key - argument name.
|
|
170
|
+
* @returns whether the field has appeared (a string opened or another value began).
|
|
171
|
+
*/
|
|
172
|
+
has(key) {
|
|
173
|
+
return this.remember("has", key, () => this.hasNow(key));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check whether a field's closing delimiter has arrived, without validating its contents.
|
|
177
|
+
* @param key - argument name.
|
|
178
|
+
* @returns whether its delimiter arrived and no content reader has reported an error for this value.
|
|
179
|
+
*/
|
|
180
|
+
complete(key) {
|
|
181
|
+
return this.remember("complete", key, () => this.completeNow(key));
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Read string length without materializing its text.
|
|
185
|
+
* @param key - argument name.
|
|
186
|
+
* @param options - change granularity for a streaming string.
|
|
187
|
+
* @returns decoded UTF-16 length of the string field so far; undefined when absent or not a string.
|
|
188
|
+
*/
|
|
189
|
+
stringLength(key, options) {
|
|
190
|
+
const step = Math.max(1, Math.floor(options?.step ?? 1));
|
|
191
|
+
const offset = options?.offset ?? 0;
|
|
192
|
+
return this.remember(`length:${step}:${offset}`, key, () => this.lengthNow(key), (length) => length === void 0 ? void 0 : Math.ceil((length + offset) / step));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Check a string against a decoded UTF-16 length limit without materializing it.
|
|
196
|
+
* @param key - argument name.
|
|
197
|
+
* @param maxLength - decoded UTF-16 limit, floored to at least zero.
|
|
198
|
+
* @returns whether the string is longer than the limit; false when absent or not a string.
|
|
199
|
+
*/
|
|
200
|
+
stringExceeds(key, maxLength) {
|
|
201
|
+
const limit = Math.max(0, Math.floor(maxLength));
|
|
202
|
+
return this.remember(`exceeds:${limit}`, key, () => (this.lengthNow(key, limit + 1) ?? 0) > limit);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Read a decoded string, including a streaming prefix.
|
|
206
|
+
* @param key - argument name.
|
|
207
|
+
* @returns the string field's decoded text so far; undefined when absent or not a string.
|
|
208
|
+
*/
|
|
209
|
+
text(key) {
|
|
210
|
+
return this.remember("text", key, () => this.textNow(key));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Read at most the first decoded UTF-16 units of a string.
|
|
214
|
+
* @param key - argument name.
|
|
215
|
+
* @param maxLength - maximum decoded UTF-16 length, floored to at least one.
|
|
216
|
+
* @returns the bounded string prefix; undefined when absent or not a string.
|
|
217
|
+
*/
|
|
218
|
+
textPrefix(key, maxLength) {
|
|
219
|
+
const limit = Math.max(1, Math.floor(maxLength));
|
|
220
|
+
return this.remember(`prefix:${limit}`, key, () => this.textPrefixNow(key, limit));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Read a completed non-string argument.
|
|
224
|
+
* @param key - argument name.
|
|
225
|
+
* @returns the parsed non-string value once it closed; undefined while open, absent, or a string.
|
|
226
|
+
*/
|
|
227
|
+
value(key) {
|
|
228
|
+
return this.remember("value", key, () => this.valueNow(key));
|
|
229
|
+
}
|
|
230
|
+
/** Answer a question and, on a streaming view, remember it for change detection. */
|
|
231
|
+
remember(kind, key, read, comparison) {
|
|
232
|
+
this.scan();
|
|
233
|
+
const result = read();
|
|
234
|
+
if (!this.sealed) {
|
|
235
|
+
const id = `${kind}/${key}`;
|
|
236
|
+
if (!this.#reads.has(id)) this.#reads.set(id, {
|
|
237
|
+
completion: kind === "complete",
|
|
238
|
+
answer: comparison === void 0 ? read : () => comparison(read()),
|
|
239
|
+
last: comparison === void 0 ? result : comparison(result)
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
closedNow() {
|
|
245
|
+
return this.sealed || this.#mode === "closed" || this.#mode === "invalid";
|
|
246
|
+
}
|
|
247
|
+
keysNow() {
|
|
248
|
+
return this.object === void 0 ? this.#order : Object.keys(this.object);
|
|
249
|
+
}
|
|
250
|
+
hasNow(key) {
|
|
251
|
+
return this.object === void 0 ? this.#entries.has(key) : Object.hasOwn(this.object, key);
|
|
252
|
+
}
|
|
253
|
+
completeNow(key) {
|
|
254
|
+
if (this.object !== void 0) return Object.hasOwn(this.object, key);
|
|
255
|
+
const entry = this.#entries.get(key);
|
|
256
|
+
return entry !== void 0 && entry.end >= 0 && (entry.kind === "string" ? entry.invalidAt === void 0 : !entry.invalid);
|
|
257
|
+
}
|
|
258
|
+
lengthNow(key, limit = Number.POSITIVE_INFINITY) {
|
|
259
|
+
if (this.object !== void 0) {
|
|
260
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
261
|
+
return typeof field === "string" ? field.length : void 0;
|
|
262
|
+
}
|
|
263
|
+
const entry = this.#entries.get(key);
|
|
264
|
+
if (entry?.kind !== "string") return void 0;
|
|
265
|
+
if (entry.text !== void 0 && entry.text.at === entry.end) return entry.text.length;
|
|
266
|
+
const read = entry.length ??= {
|
|
267
|
+
at: entry.start,
|
|
268
|
+
length: 0,
|
|
269
|
+
text: ""
|
|
270
|
+
};
|
|
271
|
+
this.readString(entry, read, limit, false);
|
|
272
|
+
return read.length;
|
|
273
|
+
}
|
|
274
|
+
textNow(key) {
|
|
275
|
+
if (this.object !== void 0) {
|
|
276
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
277
|
+
return typeof field === "string" ? field : void 0;
|
|
278
|
+
}
|
|
279
|
+
const entry = this.#entries.get(key);
|
|
280
|
+
if (entry?.kind !== "string") return void 0;
|
|
281
|
+
if (entry.text === void 0 && entry.end >= 0 && entry.needsDecoding && entry.invalidAt === void 0) {
|
|
282
|
+
let text;
|
|
283
|
+
try {
|
|
284
|
+
text = JSON.parse(`"${this.slice(entry.start, entry.end)}"`);
|
|
285
|
+
} catch (_error) {}
|
|
286
|
+
if (text !== void 0) entry.text = {
|
|
287
|
+
at: entry.end,
|
|
288
|
+
length: text.length,
|
|
289
|
+
text
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const read = entry.text ??= {
|
|
293
|
+
at: entry.start,
|
|
294
|
+
length: 0,
|
|
295
|
+
text: ""
|
|
296
|
+
};
|
|
297
|
+
this.readString(entry, read, Number.POSITIVE_INFINITY, true);
|
|
298
|
+
return read.text;
|
|
299
|
+
}
|
|
300
|
+
textPrefixNow(key, maxLength) {
|
|
301
|
+
if (this.object !== void 0) {
|
|
302
|
+
const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
|
|
303
|
+
return typeof field === "string" ? field.slice(0, maxLength) : void 0;
|
|
304
|
+
}
|
|
305
|
+
const entry = this.#entries.get(key);
|
|
306
|
+
if (entry?.kind !== "string") return void 0;
|
|
307
|
+
const prefixes = entry.prefixes ??= /* @__PURE__ */ new Map();
|
|
308
|
+
let read = prefixes.get(maxLength);
|
|
309
|
+
if (read === void 0) {
|
|
310
|
+
read = {
|
|
311
|
+
at: entry.start,
|
|
312
|
+
length: 0,
|
|
313
|
+
text: ""
|
|
314
|
+
};
|
|
315
|
+
prefixes.set(maxLength, read);
|
|
316
|
+
}
|
|
317
|
+
this.readString(entry, read, maxLength, true);
|
|
318
|
+
return read.text;
|
|
319
|
+
}
|
|
320
|
+
valueNow(key) {
|
|
321
|
+
if (this.object !== void 0) {
|
|
322
|
+
if (!Object.hasOwn(this.object, key)) return void 0;
|
|
323
|
+
const field = this.object[key];
|
|
324
|
+
return typeof field === "string" ? void 0 : field;
|
|
325
|
+
}
|
|
326
|
+
const entry = this.#entries.get(key);
|
|
327
|
+
if (entry?.kind !== "value" || entry.end < 0 || entry.invalid) return void 0;
|
|
328
|
+
if (entry.parsed === void 0) try {
|
|
329
|
+
entry.parsed = JSON.parse(this.slice(entry.start, entry.end));
|
|
330
|
+
} catch (_error) {
|
|
331
|
+
entry.invalid = true;
|
|
332
|
+
this.#invalidValue = true;
|
|
333
|
+
}
|
|
334
|
+
return entry.parsed;
|
|
335
|
+
}
|
|
336
|
+
chunkAt(at) {
|
|
337
|
+
let low = 0;
|
|
338
|
+
let high = this.#ends.length;
|
|
339
|
+
while (low < high) {
|
|
340
|
+
const mid = low + high >>> 1;
|
|
341
|
+
if (this.#ends[mid] <= at) low = mid + 1;
|
|
342
|
+
else high = mid;
|
|
343
|
+
}
|
|
344
|
+
return low;
|
|
345
|
+
}
|
|
346
|
+
/** Materialize only a requested range, never the cumulative source. */
|
|
347
|
+
slice(start, end) {
|
|
348
|
+
if (start >= end) return "";
|
|
349
|
+
const first = this.chunkAt(start);
|
|
350
|
+
const last = this.chunkAt(end - 1);
|
|
351
|
+
const base = first === 0 ? 0 : this.#ends[first - 1];
|
|
352
|
+
if (first === last) return this.chunks[first].slice(start - base, end - base);
|
|
353
|
+
const parts = [this.chunks[first].slice(start - base)];
|
|
354
|
+
for (let i = first + 1; i < last; i++) parts.push(this.chunks[i]);
|
|
355
|
+
parts.push(this.chunks[last].slice(0, end - this.#ends[last - 1]));
|
|
356
|
+
return parts.join("");
|
|
357
|
+
}
|
|
358
|
+
readString(entry, read, limit, materialize) {
|
|
359
|
+
const end = Math.min(entry.end < 0 ? this.#consumed : entry.end, entry.invalidAt ?? Number.POSITIVE_INFINITY, this.#invalidAt ?? Number.POSITIVE_INFINITY);
|
|
360
|
+
if (!entry.needsDecoding) {
|
|
361
|
+
const length = Math.min(end - read.at, limit - read.length);
|
|
362
|
+
if (length <= 0) return;
|
|
363
|
+
if (materialize) read.text += this.slice(read.at, read.at + length);
|
|
364
|
+
read.at += length;
|
|
365
|
+
read.length += length;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
let chunkIndex = this.chunkAt(read.at);
|
|
369
|
+
while (read.at < end && read.length < limit) {
|
|
370
|
+
const base = chunkIndex === 0 ? 0 : this.#ends[chunkIndex - 1];
|
|
371
|
+
const chunk = this.chunks[chunkIndex];
|
|
372
|
+
const remaining = chunk.slice(read.at - base, Math.min(chunk.length, end - base));
|
|
373
|
+
const boundary = remaining.search(CONTENT_ESCAPE);
|
|
374
|
+
const length = Math.min(boundary < 0 ? remaining.length : boundary, limit - read.length);
|
|
375
|
+
if (length > 0) {
|
|
376
|
+
if (materialize) read.text += remaining.slice(0, length);
|
|
377
|
+
read.at += length;
|
|
378
|
+
read.length += length;
|
|
379
|
+
if (read.at === base + chunk.length) chunkIndex++;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const type = remaining.length > 1 ? remaining[1] : read.at + 1 < end ? this.chunks[chunkIndex + 1][0] : void 0;
|
|
383
|
+
let decoded;
|
|
384
|
+
let width = 2;
|
|
385
|
+
if (remaining[0] === "\\" && type === void 0 && entry.end < 0) return;
|
|
386
|
+
if (remaining[0] === "\\" && type === "u") {
|
|
387
|
+
const hex = this.slice(read.at + 2, Math.min(end, read.at + 6));
|
|
388
|
+
let valid = true;
|
|
389
|
+
for (let i = 0; i < hex.length; i++) if (!isHex(hex[i])) valid = false;
|
|
390
|
+
if (valid) {
|
|
391
|
+
if (hex.length < 4 && entry.end < 0) return;
|
|
392
|
+
if (hex.length === 4) decoded = String.fromCharCode(Number.parseInt(hex, 16));
|
|
393
|
+
}
|
|
394
|
+
width = 6;
|
|
395
|
+
} else if (remaining[0] === "\\" && type !== void 0) decoded = SIMPLE_ESCAPES[type];
|
|
396
|
+
if (decoded === void 0) {
|
|
397
|
+
entry.invalidAt = read.at;
|
|
398
|
+
this.#invalidValue = true;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (materialize) read.text += decoded;
|
|
402
|
+
read.length++;
|
|
403
|
+
read.at += width;
|
|
404
|
+
while (chunkIndex < this.chunks.length && read.at >= this.#ends[chunkIndex]) chunkIndex++;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/** Locate new field ranges without decoding or parsing their contents. */
|
|
408
|
+
scan() {
|
|
409
|
+
if (this.object !== void 0 || this.#consumed === this.#size) return;
|
|
410
|
+
for (let i = this.chunkAt(this.#consumed); i < this.chunks.length && this.#invalidAt === void 0; i++) {
|
|
411
|
+
const pending = this.chunks[i];
|
|
412
|
+
const base = i === 0 ? 0 : this.#ends[i - 1];
|
|
413
|
+
for (let index = this.#consumed - base; index < pending.length && this.#mode !== "invalid"; index++) {
|
|
414
|
+
if (this.#mode === "string" || this.#mode === "nested" && this.#nestedInString) {
|
|
415
|
+
const end = this.stringBoundary(pending, index);
|
|
416
|
+
this.#consumed += end - index;
|
|
417
|
+
index = end;
|
|
418
|
+
if (index === pending.length) break;
|
|
419
|
+
}
|
|
420
|
+
this.step(pending[index], this.#consumed);
|
|
421
|
+
this.#consumed++;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/** Only raw quotes and their preceding backslash runs can terminate a string. */
|
|
426
|
+
stringBoundary(fragment, start) {
|
|
427
|
+
let at = start;
|
|
428
|
+
while (true) {
|
|
429
|
+
const quote = fragment.indexOf("\"", at);
|
|
430
|
+
const end = quote < 0 ? fragment.length : quote;
|
|
431
|
+
if (this.#mode === "string") {
|
|
432
|
+
const entry = this.#current;
|
|
433
|
+
if (!entry.needsDecoding && CONTENT_ESCAPE.test(fragment.slice(at, end))) entry.needsDecoding = true;
|
|
434
|
+
}
|
|
435
|
+
let slashStart = end;
|
|
436
|
+
while (slashStart > at && fragment[slashStart - 1] === "\\") slashStart--;
|
|
437
|
+
const escaped = (end - slashStart) % 2 === 1 !== (slashStart === at && this.#escape);
|
|
438
|
+
this.#escape = quote < 0 && escaped;
|
|
439
|
+
if (quote < 0 || !escaped) return end;
|
|
440
|
+
at = quote + 1;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
step(c, at) {
|
|
444
|
+
switch (this.#mode) {
|
|
445
|
+
case "root":
|
|
446
|
+
if (isWhitespace(c)) return;
|
|
447
|
+
if (c === "{") {
|
|
448
|
+
this.#mode = "key-or-end";
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
this.fail();
|
|
452
|
+
return;
|
|
453
|
+
case "key-or-end":
|
|
454
|
+
if (isWhitespace(c)) return;
|
|
455
|
+
if (c === "}") {
|
|
456
|
+
this.#mode = "closed";
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (c === "\"") {
|
|
460
|
+
this.beginKey(at);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
this.fail();
|
|
464
|
+
return;
|
|
465
|
+
case "key-only":
|
|
466
|
+
if (isWhitespace(c)) return;
|
|
467
|
+
if (c === "\"") {
|
|
468
|
+
this.beginKey(at);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
this.fail();
|
|
472
|
+
return;
|
|
473
|
+
case "key":
|
|
474
|
+
this.stepKey(c, at);
|
|
475
|
+
return;
|
|
476
|
+
case "colon":
|
|
477
|
+
if (isWhitespace(c)) return;
|
|
478
|
+
if (c === ":") {
|
|
479
|
+
this.#mode = "value";
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
this.fail();
|
|
483
|
+
return;
|
|
484
|
+
case "value":
|
|
485
|
+
this.beginValue(c, at);
|
|
486
|
+
return;
|
|
487
|
+
case "string": {
|
|
488
|
+
const entry = this.#current;
|
|
489
|
+
entry.end = at;
|
|
490
|
+
this.#current = null;
|
|
491
|
+
this.#mode = "comma-or-end";
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
case "scalar":
|
|
495
|
+
this.stepScalar(c, at);
|
|
496
|
+
return;
|
|
497
|
+
case "nested":
|
|
498
|
+
this.stepNested(c, at);
|
|
499
|
+
return;
|
|
500
|
+
case "comma-or-end":
|
|
501
|
+
if (isWhitespace(c)) return;
|
|
502
|
+
if (c === ",") {
|
|
503
|
+
this.#mode = "key-only";
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (c === "}") {
|
|
507
|
+
this.#mode = "closed";
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
this.fail();
|
|
511
|
+
return;
|
|
512
|
+
case "closed":
|
|
513
|
+
if (isWhitespace(c)) return;
|
|
514
|
+
this.fail();
|
|
515
|
+
return;
|
|
516
|
+
/* v8 ignore next 2 -- scan() stops stepping once the view is invalid. */
|
|
517
|
+
case "invalid": return;
|
|
518
|
+
/* v8 ignore next 2 -- Every scanner mode has a handler above. */
|
|
519
|
+
default: assertNever(this.#mode);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
fail() {
|
|
523
|
+
this.#invalidAt = this.#consumed;
|
|
524
|
+
this.#mode = "invalid";
|
|
525
|
+
this.#current = null;
|
|
526
|
+
}
|
|
527
|
+
beginKey(at) {
|
|
528
|
+
this.#mode = "key";
|
|
529
|
+
this.#keyStart = at + 1;
|
|
530
|
+
this.#keyEscaped = false;
|
|
531
|
+
this.#escape = false;
|
|
532
|
+
}
|
|
533
|
+
stepKey(c, at) {
|
|
534
|
+
if (c < " ") {
|
|
535
|
+
this.fail();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (this.#escape) {
|
|
539
|
+
this.#escape = false;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (c === "\\") {
|
|
543
|
+
this.#escape = true;
|
|
544
|
+
this.#keyEscaped = true;
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (c !== "\"") return;
|
|
548
|
+
const raw = this.slice(this.#keyStart, at);
|
|
549
|
+
if (this.#keyEscaped) try {
|
|
550
|
+
this.#key = JSON.parse(`"${raw}"`);
|
|
551
|
+
} catch (_error) {
|
|
552
|
+
this.fail();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
else this.#key = raw;
|
|
556
|
+
this.#mode = "colon";
|
|
557
|
+
}
|
|
558
|
+
open(entry) {
|
|
559
|
+
if (!this.#entries.has(this.#key)) this.#order.push(this.#key);
|
|
560
|
+
this.#entries.set(this.#key, entry);
|
|
561
|
+
this.#current = entry;
|
|
562
|
+
}
|
|
563
|
+
beginValue(c, at) {
|
|
564
|
+
if (isWhitespace(c)) return;
|
|
565
|
+
if (c === "\"") {
|
|
566
|
+
this.open({
|
|
567
|
+
kind: "string",
|
|
568
|
+
start: at + 1,
|
|
569
|
+
end: -1,
|
|
570
|
+
needsDecoding: false,
|
|
571
|
+
invalidAt: void 0,
|
|
572
|
+
length: void 0,
|
|
573
|
+
text: void 0,
|
|
574
|
+
prefixes: void 0
|
|
575
|
+
});
|
|
576
|
+
this.#escape = false;
|
|
577
|
+
this.#mode = "string";
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (c === "}" || c === "," || c === ":" || c === "]") {
|
|
581
|
+
this.fail();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
this.open({
|
|
585
|
+
kind: "value",
|
|
586
|
+
start: at,
|
|
587
|
+
end: -1,
|
|
588
|
+
parsed: void 0,
|
|
589
|
+
invalid: false
|
|
590
|
+
});
|
|
591
|
+
if (c === "{" || c === "[") {
|
|
592
|
+
this.#mode = "nested";
|
|
593
|
+
this.#nestedEnds = [c === "{" ? "}" : "]"];
|
|
594
|
+
this.#nestedInString = false;
|
|
595
|
+
this.#escape = false;
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
this.#mode = "scalar";
|
|
599
|
+
}
|
|
600
|
+
stepScalar(c, at) {
|
|
601
|
+
if (c !== "," && c !== "}" && !isWhitespace(c)) return;
|
|
602
|
+
this.closeValue(at);
|
|
603
|
+
this.#mode = c === "," ? "key-only" : c === "}" ? "closed" : "comma-or-end";
|
|
604
|
+
}
|
|
605
|
+
stepNested(c, at) {
|
|
606
|
+
if (this.#nestedInString) {
|
|
607
|
+
this.#nestedInString = false;
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (c === "\"") {
|
|
611
|
+
this.#nestedInString = true;
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (c === "{" || c === "[") {
|
|
615
|
+
this.#nestedEnds.push(c === "{" ? "}" : "]");
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (c === "}" || c === "]") {
|
|
619
|
+
if (this.#nestedEnds.pop() !== c) {
|
|
620
|
+
this.fail();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (this.#nestedEnds.length === 0) {
|
|
624
|
+
this.closeValue(at + 1);
|
|
625
|
+
this.#mode = "comma-or-end";
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
closeValue(end) {
|
|
630
|
+
const entry = this.#current;
|
|
631
|
+
entry.end = end;
|
|
632
|
+
this.#current = null;
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
//#endregion
|
|
12
636
|
//#region ../../util/values/src/index.ts
|
|
13
637
|
/**
|
|
14
638
|
* Mark an unreachable closed-union branch.
|
|
@@ -3801,7 +4425,7 @@ const MAX_TIMER_DELAY_MS = 2147483647;
|
|
|
3801
4425
|
*/
|
|
3802
4426
|
/**
|
|
3803
4427
|
* Base class for all harness errors. Carries a `code` (stable, programmatic —
|
|
3804
|
-
* e.g. `NO_ADAPTER`, `INVALID_ARGS
|
|
4428
|
+
* e.g. `NO_ADAPTER`, `INVALID_ARGS`) distinct from the
|
|
3805
4429
|
* human-readable `message`, and supports `cause` chaining via the standard
|
|
3806
4430
|
* `ErrorOptions`. `name` defaults to the subclass constructor name.
|
|
3807
4431
|
*/
|
|
@@ -4224,7 +4848,7 @@ function projectToolUpdates(messages, tools, toolUpdate, history) {
|
|
|
4224
4848
|
/**
|
|
4225
4849
|
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
|
|
4226
4850
|
* adapters from drifting. See
|
|
4227
|
-
*
|
|
4851
|
+
* `docs/subsystems/llm-streaming.md#appidentity--app-attribution`.
|
|
4228
4852
|
*
|
|
4229
4853
|
* App-attribution vocabulary for provider requests.
|
|
4230
4854
|
* @module @deepseek-ai/dsh-llm/attribution
|
|
@@ -4815,20 +5439,14 @@ var LlmError = class extends HarnessError {
|
|
|
4815
5439
|
}
|
|
4816
5440
|
/** Notify topology observers without letting one broken listener veto the commit. */
|
|
4817
5441
|
emitAdaptersUpdated() {
|
|
4818
|
-
let invariantFailure;
|
|
4819
5442
|
for (const listener of this.ctx.events.dispatch("emit", ["llm/adapters-updated"])) try {
|
|
4820
5443
|
const returned = listener();
|
|
4821
5444
|
if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
|
|
4822
5445
|
this.warnAdaptersListenerFailure(error);
|
|
4823
5446
|
});
|
|
4824
5447
|
} catch (error) {
|
|
4825
|
-
if (error?.code === "INVARIANT") {
|
|
4826
|
-
invariantFailure ??= error;
|
|
4827
|
-
continue;
|
|
4828
|
-
}
|
|
4829
5448
|
this.warnAdaptersListenerFailure(error);
|
|
4830
5449
|
}
|
|
4831
|
-
if (invariantFailure !== void 0) throw invariantFailure;
|
|
4832
5450
|
}
|
|
4833
5451
|
/** Contained-listener diagnostic shared by the sync and async failure paths. */
|
|
4834
5452
|
warnAdaptersListenerFailure(error) {
|
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.2.
|
|
4
|
+
"version": "0.2.1-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.2.
|
|
32
|
-
"@deepseek-ai/dsh-session-persistence": "0.2.
|
|
33
|
-
"@deepseek-ai/cordis": "~4.0.
|
|
31
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.1",
|
|
32
|
+
"@deepseek-ai/dsh-session-persistence": "0.2.1-alpha.1",
|
|
33
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"koffi": "3.1.1",
|
|
37
|
-
"@deepseek-ai/dsh-
|
|
38
|
-
"@deepseek-ai/dsh-
|
|
39
|
-
"@deepseek-ai/dsh-session-format-
|
|
40
|
-
"@deepseek-ai/dsh-session-format-
|
|
37
|
+
"@deepseek-ai/dsh-session-format": "0.2.1-alpha.1",
|
|
38
|
+
"@deepseek-ai/dsh-llm": "0.2.1-alpha.1",
|
|
39
|
+
"@deepseek-ai/dsh-session-format-v3-to-v4": "0.2.1-alpha.1",
|
|
40
|
+
"@deepseek-ai/dsh-session-format-catalog": "0.2.1-alpha.1",
|
|
41
41
|
"@deepseek-ai/node-addon-system": "~0.1.2",
|
|
42
|
-
"@deepseek-ai/schemastery": "~3.18.
|
|
42
|
+
"@deepseek-ai/schemastery": "~3.18.5-alpha.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@deepseek-ai/dsh-session": "0.2.
|
|
46
|
-
"@deepseek-ai/dsh-session-format-v0-to-v1": "0.2.
|
|
47
|
-
"@deepseek-ai/
|
|
48
|
-
"@deepseek-ai/
|
|
45
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-session-format-v0-to-v1": "0.2.1-alpha.1",
|
|
47
|
+
"@deepseek-ai/dsh-session-persistence": "0.2.1-alpha.1",
|
|
48
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1"
|
|
49
49
|
}
|
|
50
50
|
}
|