@deepseek-ai/dsh-client-connection 0.1.2-alpha.5 → 0.1.3-alpha.2

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/lib/client.js CHANGED
@@ -4,13 +4,820 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- //#region lib/types/client/connection.js
8
- const CONNECTION_DEFAULTS = {
9
- backoffBaseMs: 500,
10
- backoffFactor: 2,
11
- backoffMaxMs: 1e4,
12
- generationReadyTimeoutMs: 3e3
7
+ //#region ../../../vendor/cosmokit/lib/index.js
8
+ /** Return true when a value is `null` or `undefined`. */
9
+ function isNullable(value) {
10
+ return value === null || value === void 0;
11
+ }
12
+ /** Return true for non-array object values. */
13
+ function isPlainObject(data) {
14
+ return data && typeof data === "object" && !Array.isArray(data);
15
+ }
16
+ /** Filter object entries and return a new object. */
17
+ function filterKeys(object, filter) {
18
+ return Object.fromEntries(Object.entries(object).filter(([key, value]) => filter(key, value)));
19
+ }
20
+ /** Map object values while preserving the original key set. */
21
+ function mapValues(object, transform) {
22
+ return Object.fromEntries(Object.entries(object).map(([key, value]) => [key, transform(value, key)]));
23
+ }
24
+ /** Pick selected keys from an object, optionally including `undefined` values. */
25
+ function pick(source, keys, forced) {
26
+ if (!keys) return { ...source };
27
+ const result = {};
28
+ for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
29
+ return result;
30
+ }
31
+ /** Test values using `instanceof` with a `toStringTag` fallback. */
32
+ function is(type, value) {
33
+ if (arguments.length === 1) return (value) => is(type, value);
34
+ return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
35
+ }
36
+ function isArrayBufferLike(value) {
37
+ return is("ArrayBuffer", value) || is("SharedArrayBuffer", value);
38
+ }
39
+ function isArrayBufferSource(value) {
40
+ return isArrayBufferLike(value) || ArrayBuffer.isView(value);
41
+ }
42
+ /** Binary source detection and base64/hex conversion helpers. */
43
+ var Binary;
44
+ (function(Binary) {
45
+ Binary.is = isArrayBufferLike;
46
+ Binary.isSource = isArrayBufferSource;
47
+ function fromSource(source) {
48
+ if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
49
+ else return source;
50
+ }
51
+ Binary.fromSource = fromSource;
52
+ function toBase64(source) {
53
+ source = fromSource(source);
54
+ if (typeof Buffer !== "undefined") return Buffer.from(source).toString("base64");
55
+ let binary = "";
56
+ const bytes = new Uint8Array(source);
57
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
58
+ return btoa(binary);
59
+ }
60
+ Binary.toBase64 = toBase64;
61
+ function fromBase64(source) {
62
+ if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "base64"));
63
+ return Uint8Array.from(atob(source), (c) => c.charCodeAt(0));
64
+ }
65
+ Binary.fromBase64 = fromBase64;
66
+ function toHex(source) {
67
+ source = fromSource(source);
68
+ if (typeof Buffer !== "undefined") return Buffer.from(source).toString("hex");
69
+ return Array.from(new Uint8Array(source), (byte) => byte.toString(16).padStart(2, "0")).join("");
70
+ }
71
+ Binary.toHex = toHex;
72
+ function fromHex(source) {
73
+ if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "hex"));
74
+ const hex = source.length % 2 === 0 ? source : source.slice(0, source.length - 1);
75
+ const buffer = [];
76
+ for (let i = 0; i < hex.length; i += 2) buffer.push(parseInt(`${hex[i]}${hex[i + 1]}`, 16));
77
+ return Uint8Array.from(buffer).buffer;
78
+ }
79
+ Binary.fromHex = fromHex;
80
+ })(Binary || (Binary = {}));
81
+ Binary.fromBase64;
82
+ Binary.toBase64;
83
+ Binary.fromHex;
84
+ Binary.toHex;
85
+ /** Deep-clone common JavaScript values while preserving prototypes and cycles. */
86
+ function clone(source, refs = /* @__PURE__ */ new Map()) {
87
+ if (!source || typeof source !== "object") return source;
88
+ if (is("Date", source)) return new Date(source.valueOf());
89
+ if (is("RegExp", source)) return new RegExp(source.source, source.flags);
90
+ if (isArrayBufferLike(source)) return source.slice(0);
91
+ if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
92
+ const cached = refs.get(source);
93
+ if (cached) return cached;
94
+ if (Array.isArray(source)) {
95
+ const result = [];
96
+ refs.set(source, result);
97
+ source.forEach((value, index) => {
98
+ result[index] = Reflect.apply(clone, null, [value, refs]);
99
+ });
100
+ return result;
101
+ }
102
+ const result = Object.create(Object.getPrototypeOf(source));
103
+ refs.set(source, result);
104
+ for (const key of Reflect.ownKeys(source)) {
105
+ const descriptor = { ...Reflect.getOwnPropertyDescriptor(source, key) };
106
+ if ("value" in descriptor) descriptor.value = Reflect.apply(clone, null, [descriptor.value, refs]);
107
+ Reflect.defineProperty(result, key, descriptor);
108
+ }
109
+ return result;
110
+ }
111
+ /** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */
112
+ function deepEqual(a, b, strict) {
113
+ if (a === b) return true;
114
+ if (!strict && isNullable(a) && isNullable(b)) return true;
115
+ if (typeof a !== typeof b) return false;
116
+ if (typeof a !== "object") return false;
117
+ if (!a || !b) return false;
118
+ function check(test, then) {
119
+ return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
120
+ }
121
+ return check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
122
+ if (a.byteLength !== b.byteLength) return false;
123
+ const viewA = new Uint8Array(a);
124
+ const viewB = new Uint8Array(b);
125
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
126
+ return true;
127
+ }) ?? Object.keys({
128
+ ...a,
129
+ ...b
130
+ }).every((key) => deepEqual(a[key], b[key], strict));
131
+ }
132
+ /** Time constants plus parsing and formatting helpers. */
133
+ var Time;
134
+ (function(Time) {
135
+ Time.millisecond = 1;
136
+ Time.second = 1e3;
137
+ Time.minute = Time.second * 60;
138
+ Time.hour = Time.minute * 60;
139
+ Time.day = Time.hour * 24;
140
+ Time.week = Time.day * 7;
141
+ let timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();
142
+ function setTimezoneOffset(offset) {
143
+ timezoneOffset = offset;
144
+ }
145
+ Time.setTimezoneOffset = setTimezoneOffset;
146
+ function getTimezoneOffset() {
147
+ return timezoneOffset;
148
+ }
149
+ Time.getTimezoneOffset = getTimezoneOffset;
150
+ function getDateNumber(date = /* @__PURE__ */ new Date(), offset) {
151
+ if (typeof date === "number") date = new Date(date);
152
+ if (offset === void 0) offset = timezoneOffset;
153
+ return Math.floor((date.valueOf() / Time.minute - offset) / 1440);
154
+ }
155
+ Time.getDateNumber = getDateNumber;
156
+ function fromDateNumber(value, offset) {
157
+ const date = new Date(value * Time.day);
158
+ if (offset === void 0) offset = timezoneOffset;
159
+ return new Date(+date + offset * Time.minute);
160
+ }
161
+ Time.fromDateNumber = fromDateNumber;
162
+ const numeric = /\d+(?:\.\d+)?/.source;
163
+ const timeRegExp = new RegExp(`^${[
164
+ "w(?:eek(?:s)?)?",
165
+ "d(?:ay(?:s)?)?",
166
+ "h(?:our(?:s)?)?",
167
+ "m(?:in(?:ute)?(?:s)?)?",
168
+ "s(?:ec(?:ond)?(?:s)?)?"
169
+ ].map((unit) => `(${numeric}${unit})?`).join("")}$`);
170
+ function parseTime(source) {
171
+ const capture = timeRegExp.exec(source);
172
+ if (!capture) return 0;
173
+ return (parseFloat(capture[1]) * Time.week || 0) + (parseFloat(capture[2]) * Time.day || 0) + (parseFloat(capture[3]) * Time.hour || 0) + (parseFloat(capture[4]) * Time.minute || 0) + (parseFloat(capture[5]) * Time.second || 0);
174
+ }
175
+ Time.parseTime = parseTime;
176
+ function parseDate(date) {
177
+ const parsed = parseTime(date);
178
+ if (parsed) date = Date.now() + parsed;
179
+ else if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).toLocaleDateString()}-${date}`;
180
+ else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).getFullYear()}-${date}`;
181
+ return date ? new Date(date) : /* @__PURE__ */ new Date();
182
+ }
183
+ Time.parseDate = parseDate;
184
+ function format(ms) {
185
+ const abs = Math.abs(ms);
186
+ if (abs >= Time.day - Time.hour / 2) return Math.round(ms / Time.day) + "d";
187
+ else if (abs >= Time.hour - Time.minute / 2) return Math.round(ms / Time.hour) + "h";
188
+ else if (abs >= Time.minute - Time.second / 2) return Math.round(ms / Time.minute) + "m";
189
+ else if (abs >= Time.second) return Math.round(ms / Time.second) + "s";
190
+ return ms + "ms";
191
+ }
192
+ Time.format = format;
193
+ function toDigits(source, length = 2) {
194
+ return source.toString().padStart(length, "0");
195
+ }
196
+ Time.toDigits = toDigits;
197
+ function template(template, time = /* @__PURE__ */ new Date()) {
198
+ return template.replace("yyyy", time.getFullYear().toString()).replace("yy", time.getFullYear().toString().slice(2)).replace("MM", toDigits(time.getMonth() + 1)).replace("dd", toDigits(time.getDate())).replace("hh", toDigits(time.getHours())).replace("mm", toDigits(time.getMinutes())).replace("ss", toDigits(time.getSeconds())).replace("SSS", toDigits(time.getMilliseconds(), 3));
199
+ }
200
+ Time.template = template;
201
+ })(Time || (Time = {}));
202
+ //#endregion
203
+ //#region ../../../vendor/schemastery/lib/index.mjs
204
+ const kSchema = Symbol.for("schemastery");
205
+ const kValidationError = Symbol.for("ValidationError");
206
+ globalThis.__schemastery_index__ ??= 0;
207
+ globalThis.__schemastery_refs__ = void 0;
208
+ var ValidationError = class extends TypeError {
209
+ options;
210
+ name = "ValidationError";
211
+ constructor(message, options) {
212
+ let prefix = "$";
213
+ for (const segment of options.path || []) if (typeof segment === "string") prefix += "." + segment;
214
+ else if (typeof segment === "number") prefix += "[" + segment + "]";
215
+ else if (typeof segment === "symbol") prefix += `[Symbol(${segment.toString()})]`;
216
+ if (prefix.startsWith(".")) prefix = prefix.slice(1);
217
+ super((prefix === "$" ? "" : `${prefix} `) + message);
218
+ this.options = options;
219
+ }
220
+ static is(error) {
221
+ return !!error?.[kValidationError];
222
+ }
223
+ };
224
+ Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
225
+ const Schema = function(options) {
226
+ const schema = function(data, options = {}) {
227
+ return Schema.resolve(data, schema, options)[0];
228
+ };
229
+ if (options.refs) {
230
+ const refs = mapValues(options.refs, (options) => new Schema(options));
231
+ const getRef = (uid) => refs[uid];
232
+ for (const key in refs) {
233
+ const options = refs[key];
234
+ options.sKey = getRef(options.sKey);
235
+ options.inner = getRef(options.inner);
236
+ options.list = options.list && options.list.map(getRef);
237
+ options.dict = options.dict && mapValues(options.dict, getRef);
238
+ }
239
+ return refs[options.uid];
240
+ }
241
+ Object.assign(schema, options);
242
+ if (typeof schema.callback === "string") try {
243
+ schema.callback = new Function("return " + schema.callback)();
244
+ } catch {}
245
+ Object.defineProperty(schema, "uid", { value: globalThis.__schemastery_index__++ });
246
+ Object.setPrototypeOf(schema, Schema.prototype);
247
+ schema.meta ||= {};
248
+ schema.toString = schema.toString.bind(schema);
249
+ return schema;
250
+ };
251
+ Schema.prototype = Object.create(Function.prototype);
252
+ Schema.prototype[kSchema] = true;
253
+ Object.defineProperty(Schema.prototype, "~standard", { get() {
254
+ return {
255
+ version: 1,
256
+ vendor: "schemastery",
257
+ validate: (value) => {
258
+ try {
259
+ return { value: Schema.resolve(value, this, {})[0] };
260
+ } catch (error) {
261
+ if (ValidationError.is(error)) return { issues: [{
262
+ message: error.message,
263
+ path: error.options.path
264
+ }] };
265
+ throw error;
266
+ }
267
+ }
268
+ };
269
+ } });
270
+ Schema.ValidationError = ValidationError;
271
+ Schema.prototype.toJSON = function toJSON() {
272
+ if (globalThis.__schemastery_refs__) {
273
+ globalThis.__schemastery_refs__[this.uid] ??= JSON.parse(JSON.stringify({ ...this }));
274
+ return this.uid;
275
+ }
276
+ globalThis.__schemastery_refs__ = { [this.uid]: { ...this } };
277
+ globalThis.__schemastery_refs__[this.uid] = JSON.parse(JSON.stringify({ ...this }));
278
+ const result = {
279
+ uid: this.uid,
280
+ refs: globalThis.__schemastery_refs__
281
+ };
282
+ globalThis.__schemastery_refs__ = void 0;
283
+ return result;
284
+ };
285
+ Schema.prototype.set = function set(key, value) {
286
+ this.dict[key] = value;
287
+ return this;
288
+ };
289
+ Schema.prototype.push = function push(value) {
290
+ this.list.push(value);
291
+ return this;
13
292
  };
293
+ function mergeDesc(original, messages) {
294
+ const result = typeof original === "string" ? { "": original } : { ...original };
295
+ for (const locale in messages) {
296
+ const value = messages[locale];
297
+ if (value?.$description || value?.$desc) result[locale] = value.$description || value.$desc;
298
+ else if (typeof value === "string") result[locale] = value;
299
+ }
300
+ return result;
301
+ }
302
+ function getInner(value) {
303
+ return value?.$value ?? value?.$inner;
304
+ }
305
+ function extractKeys(data) {
306
+ return filterKeys(data ?? {}, (key) => !key.startsWith("$"));
307
+ }
308
+ Schema.prototype.i18n = function i18n(messages) {
309
+ const schema = Schema(this);
310
+ const desc = mergeDesc(schema.meta.description, messages);
311
+ if (Object.keys(desc).length) schema.meta.description = desc;
312
+ if (schema.dict) schema.dict = mapValues(schema.dict, (inner, key) => {
313
+ return inner.i18n(mapValues(messages, (data) => getInner(data)?.[key] ?? data?.[key]));
314
+ });
315
+ if (schema.list) schema.list = schema.list.map((inner, index) => {
316
+ return inner.i18n(mapValues(messages, (data = {}) => {
317
+ if (Array.isArray(getInner(data))) return getInner(data)[index];
318
+ if (Array.isArray(data)) return data[index];
319
+ return extractKeys(data);
320
+ }));
321
+ });
322
+ if (schema.inner) schema.inner = schema.inner.i18n(mapValues(messages, (data) => {
323
+ if (getInner(data)) return getInner(data);
324
+ return extractKeys(data);
325
+ }));
326
+ if (schema.sKey) schema.sKey = schema.sKey.i18n(mapValues(messages, (data) => data?.$key));
327
+ return schema;
328
+ };
329
+ Schema.prototype.extra = function extra(key, value) {
330
+ const schema = Schema(this);
331
+ schema.meta = {
332
+ ...schema.meta,
333
+ [key]: value
334
+ };
335
+ return schema;
336
+ };
337
+ for (const key of [
338
+ "required",
339
+ "disabled",
340
+ "collapse",
341
+ "hidden",
342
+ "loose"
343
+ ]) Object.assign(Schema.prototype, { [key](value = true) {
344
+ const schema = Schema(this);
345
+ schema.meta = {
346
+ ...schema.meta,
347
+ [key]: value
348
+ };
349
+ return schema;
350
+ } });
351
+ Schema.prototype.deprecated = function deprecated() {
352
+ const schema = Schema(this);
353
+ schema.meta.badges ||= [];
354
+ schema.meta.badges.push({
355
+ text: "deprecated",
356
+ type: "danger"
357
+ });
358
+ return schema;
359
+ };
360
+ Schema.prototype.experimental = function experimental() {
361
+ const schema = Schema(this);
362
+ schema.meta.badges ||= [];
363
+ schema.meta.badges.push({
364
+ text: "experimental",
365
+ type: "warning"
366
+ });
367
+ return schema;
368
+ };
369
+ Schema.prototype.pattern = function pattern(regexp) {
370
+ const schema = Schema(this);
371
+ const pattern = pick(regexp, ["source", "flags"]);
372
+ schema.meta = {
373
+ ...schema.meta,
374
+ pattern
375
+ };
376
+ return schema;
377
+ };
378
+ Schema.prototype.simplify = function simplify(value) {
379
+ if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
380
+ if (isNullable(value)) return value;
381
+ if (this.type === "object" || this.type === "dict") {
382
+ const result = {};
383
+ for (const key in value) {
384
+ const item = (this.type === "object" ? this.dict[key] : this.inner)?.simplify(value[key]);
385
+ if (this.type === "dict" || !isNullable(item)) result[key] = item;
386
+ }
387
+ if (deepEqual(result, this.meta.default, this.type === "dict")) return null;
388
+ return result;
389
+ } else if (this.type === "array" || this.type === "tuple") {
390
+ const result = [];
391
+ value.forEach((value, index) => {
392
+ const schema = this.type === "array" ? this.inner : this.list[index];
393
+ const item = schema ? schema.simplify(value) : value;
394
+ result.push(item);
395
+ });
396
+ return result;
397
+ } else if (this.type === "intersect") {
398
+ const result = {};
399
+ for (const item of this.list) Object.assign(result, item.simplify(value));
400
+ return result;
401
+ } else if (this.type === "union") for (const schema of this.list) try {
402
+ Schema.resolve(value, schema, {});
403
+ return schema.simplify(value);
404
+ } catch {}
405
+ return value;
406
+ };
407
+ Schema.prototype.toString = function toString(inline) {
408
+ return formatters[this.type]?.(this, inline) ?? `Schema<${this.type}>`;
409
+ };
410
+ Schema.prototype.role = function role(role, extra) {
411
+ const schema = Schema(this);
412
+ schema.meta = {
413
+ ...schema.meta,
414
+ role,
415
+ extra
416
+ };
417
+ return schema;
418
+ };
419
+ for (const key of [
420
+ "default",
421
+ "link",
422
+ "comment",
423
+ "description",
424
+ "max",
425
+ "min",
426
+ "step"
427
+ ]) Object.assign(Schema.prototype, { [key](value) {
428
+ const schema = Schema(this);
429
+ schema.meta = {
430
+ ...schema.meta,
431
+ [key]: value
432
+ };
433
+ return schema;
434
+ } });
435
+ const resolvers = {};
436
+ Schema.extend = function extend(type, resolve) {
437
+ resolvers[type] = resolve;
438
+ };
439
+ Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
440
+ if (!schema) return [data];
441
+ if (options.ignore?.(data, schema)) return [data];
442
+ if (isNullable(data) && schema.type !== "lazy") {
443
+ if (schema.meta.required) throw new ValidationError(`missing required value`, options);
444
+ let current = schema;
445
+ let fallback = schema.meta.default;
446
+ while (current?.type === "intersect" && isNullable(fallback)) {
447
+ current = current.list[0];
448
+ fallback = current?.meta.default;
449
+ }
450
+ if (isNullable(fallback)) return [data];
451
+ data = clone(fallback);
452
+ }
453
+ const callback = resolvers[schema.type];
454
+ if (!callback) throw new ValidationError(`unsupported type "${schema.type}"`, options);
455
+ try {
456
+ return callback(data, schema, options, strict);
457
+ } catch (error) {
458
+ if (!schema.meta.loose) throw error;
459
+ return [schema.meta.default];
460
+ }
461
+ };
462
+ Schema.from = function from(source) {
463
+ if (isNullable(source)) return Schema.any();
464
+ else if ([
465
+ "string",
466
+ "number",
467
+ "boolean"
468
+ ].includes(typeof source)) return Schema.const(source).required();
469
+ else if (source[kSchema]) return source;
470
+ else if (typeof source === "function") switch (source) {
471
+ case String: return Schema.string().required();
472
+ case Number: return Schema.number().required();
473
+ case Boolean: return Schema.boolean().required();
474
+ case Function: return Schema.function().required();
475
+ default: return Schema.is(source).required();
476
+ }
477
+ else throw new TypeError(`cannot infer schema from ${source}`);
478
+ };
479
+ Schema.lazy = function lazy(builder) {
480
+ const toJSON = () => {
481
+ if (!schema.inner[kSchema]) {
482
+ schema.inner = schema.builder();
483
+ schema.inner.meta = {
484
+ ...schema.meta,
485
+ ...schema.inner.meta
486
+ };
487
+ }
488
+ return schema.inner.toJSON();
489
+ };
490
+ const schema = new Schema({
491
+ type: "lazy",
492
+ builder,
493
+ inner: { toJSON }
494
+ });
495
+ return schema;
496
+ };
497
+ Schema.natural = function natural() {
498
+ return Schema.number().step(1).min(0);
499
+ };
500
+ Schema.percent = function percent() {
501
+ return Schema.number().step(.01).min(0).max(1).role("slider");
502
+ };
503
+ Schema.date = function date() {
504
+ return Schema.union([Schema.is(Date), Schema.transform(Schema.string().role("datetime"), (value, options) => {
505
+ const date = new Date(value);
506
+ if (isNaN(+date)) throw new ValidationError(`invalid date "${value}"`, options);
507
+ return date;
508
+ }, true)]);
509
+ };
510
+ Schema.regExp = function regExp(flag = "") {
511
+ return Schema.union([Schema.is(RegExp), Schema.transform(Schema.string().role("regexp", { flag }), (value, options) => {
512
+ try {
513
+ return new RegExp(value, flag);
514
+ } catch (e) {
515
+ throw new ValidationError(e.message, options);
516
+ }
517
+ }, true)]);
518
+ };
519
+ Schema.arrayBuffer = function arrayBuffer(encoding) {
520
+ return Schema.union([
521
+ Schema.is(ArrayBuffer),
522
+ Schema.is(SharedArrayBuffer),
523
+ Schema.transform(Schema.any(), (value, options) => {
524
+ if (Binary.isSource(value)) return Binary.fromSource(value);
525
+ throw new ValidationError(`expected ArrayBufferSource but got ${value}`, options);
526
+ }, true),
527
+ ...encoding ? [Schema.transform(Schema.string(), (value, options) => {
528
+ try {
529
+ return encoding === "base64" ? Binary.fromBase64(value) : Binary.fromHex(value);
530
+ } catch (e) {
531
+ throw new ValidationError(e.message, options);
532
+ }
533
+ }, true)] : []
534
+ ]);
535
+ };
536
+ Schema.extend("lazy", (data, schema, options, strict) => {
537
+ if (!schema.inner[kSchema]) {
538
+ schema.inner = schema.builder();
539
+ schema.inner.meta = {
540
+ ...schema.meta,
541
+ ...schema.inner.meta
542
+ };
543
+ }
544
+ return Schema.resolve(data, schema.inner, options, strict);
545
+ });
546
+ Schema.extend("any", (data) => {
547
+ return [data];
548
+ });
549
+ Schema.extend("never", (data, _, options) => {
550
+ throw new ValidationError(`expected nullable but got ${data}`, options);
551
+ });
552
+ Schema.extend("const", (data, { value }, options) => {
553
+ if (deepEqual(data, value)) return [value];
554
+ throw new ValidationError(`expected ${value} but got ${data}`, options);
555
+ });
556
+ function checkWithinRange(data, meta, description, options, skipMin = false) {
557
+ const { max = Infinity, min = -Infinity } = meta;
558
+ if (data > max) throw new ValidationError(`expected ${description} <= ${max} but got ${data}`, options);
559
+ if (data < min && !skipMin) throw new ValidationError(`expected ${description} >= ${min} but got ${data}`, options);
560
+ }
561
+ Schema.extend("string", (data, { meta }, options) => {
562
+ if (typeof data !== "string") throw new ValidationError(`expected string but got ${data}`, options);
563
+ if (meta.pattern) {
564
+ const regexp = new RegExp(meta.pattern.source, meta.pattern.flags);
565
+ if (!regexp.test(data)) throw new ValidationError(`expect string to match regexp ${regexp}`, options);
566
+ }
567
+ checkWithinRange(data.length, meta, "string length", options);
568
+ return [data];
569
+ });
570
+ function decimalShift(data, digits) {
571
+ const str = data.toString();
572
+ if (str.includes("e")) return data * Math.pow(10, digits);
573
+ const index = str.indexOf(".");
574
+ if (index === -1) return data * Math.pow(10, digits);
575
+ const frac = str.slice(index + 1);
576
+ const integer = str.slice(0, index);
577
+ if (frac.length <= digits) return +(integer + frac.padEnd(digits, "0"));
578
+ return +(integer + frac.slice(0, digits) + "." + frac.slice(digits));
579
+ }
580
+ function isMultipleOf(data, min, step) {
581
+ step = Math.abs(step);
582
+ if (!/^\d+\.\d+$/.test(step.toString())) return (data - min) % step === 0;
583
+ const index = step.toString().indexOf(".");
584
+ const digits = step.toString().slice(index + 1).length;
585
+ return Math.abs(decimalShift(data, digits) - decimalShift(min, digits)) % decimalShift(step, digits) === 0;
586
+ }
587
+ Schema.extend("number", (data, { meta }, options) => {
588
+ if (typeof data !== "number") throw new ValidationError(`expected number but got ${data}`, options);
589
+ checkWithinRange(data, meta, "number", options);
590
+ const { step } = meta;
591
+ if (step && !isMultipleOf(data, meta.min ?? 0, step)) throw new ValidationError(`expected number multiple of ${step} but got ${data}`, options);
592
+ return [data];
593
+ });
594
+ Schema.extend("boolean", (data, _, options) => {
595
+ if (typeof data === "boolean") return [data];
596
+ throw new ValidationError(`expected boolean but got ${data}`, options);
597
+ });
598
+ Schema.extend("bitset", (data, { bits, meta }, options) => {
599
+ let value = 0, keys = [];
600
+ if (typeof data === "number") {
601
+ value = data;
602
+ for (const key in bits) if (data & bits[key]) keys.push(key);
603
+ } else if (Array.isArray(data)) {
604
+ keys = data;
605
+ for (const key of keys) {
606
+ if (typeof key !== "string") throw new ValidationError(`expected string but got ${key}`, options);
607
+ if (key in bits) value |= bits[key];
608
+ }
609
+ } else throw new ValidationError(`expected number or array but got ${data}`, options);
610
+ if (value === meta.default) return [value];
611
+ return [value, keys];
612
+ });
613
+ Schema.extend("function", (data, _, options) => {
614
+ if (typeof data === "function") return [data];
615
+ throw new ValidationError(`expected function but got ${data}`, options);
616
+ });
617
+ Schema.extend("is", (data, { constructor }, options) => {
618
+ if (typeof constructor === "function") {
619
+ if (data instanceof constructor) return [data];
620
+ throw new ValidationError(`expected ${constructor.name} but got ${data}`, options);
621
+ } else {
622
+ if (isNullable(data)) throw new ValidationError(`expected ${constructor} but got ${data}`, options);
623
+ let prototype = Object.getPrototypeOf(data);
624
+ while (prototype) {
625
+ if (prototype.constructor?.name === constructor) return [data];
626
+ prototype = Object.getPrototypeOf(prototype);
627
+ }
628
+ throw new ValidationError(`expected ${constructor} but got ${data}`, options);
629
+ }
630
+ });
631
+ function property(data, key, schema, options) {
632
+ try {
633
+ const [value, adapted] = Schema.resolve(data[key], schema, {
634
+ ...options,
635
+ path: [...options.path || [], key]
636
+ });
637
+ if (adapted !== void 0) data[key] = adapted;
638
+ return value;
639
+ } catch (e) {
640
+ if (!options?.autofix) throw e;
641
+ delete data[key];
642
+ return schema.meta.default;
643
+ }
644
+ }
645
+ Schema.extend("array", (data, { inner, meta }, options) => {
646
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
647
+ checkWithinRange(data.length, meta, "array length", options, !isNullable(inner.meta.default));
648
+ return [data.map((_, index) => property(data, index, inner, options))];
649
+ });
650
+ Schema.extend("dict", (data, { inner, sKey }, options, strict) => {
651
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
652
+ const result = {};
653
+ for (const key in data) {
654
+ let rKey;
655
+ try {
656
+ rKey = Schema.resolve(key, sKey, options)[0];
657
+ } catch (error) {
658
+ if (strict) continue;
659
+ throw error;
660
+ }
661
+ result[rKey] = property(data, key, inner, options);
662
+ data[rKey] = data[key];
663
+ if (key !== rKey) delete data[key];
664
+ }
665
+ return [result];
666
+ });
667
+ Schema.extend("tuple", (data, { list }, options, strict) => {
668
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
669
+ const result = list.map((inner, index) => property(data, index, inner, options));
670
+ if (strict) return [result];
671
+ result.push(...data.slice(list.length));
672
+ return [result];
673
+ });
674
+ function merge(result, data) {
675
+ for (const key in data) {
676
+ if (key in result) continue;
677
+ result[key] = data[key];
678
+ }
679
+ }
680
+ Schema.extend("object", (data, { dict }, options, strict) => {
681
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
682
+ const result = {};
683
+ for (const key in dict) {
684
+ const value = property(data, key, dict[key], options);
685
+ if (!isNullable(value) || key in data) result[key] = value;
686
+ }
687
+ if (!strict) merge(result, data);
688
+ return [result];
689
+ });
690
+ Schema.extend("union", (data, { list, toString }, options, strict) => {
691
+ const messages = [];
692
+ for (const inner of list) try {
693
+ return Schema.resolve(data, inner, options, strict);
694
+ } catch (error) {
695
+ messages.push(error);
696
+ }
697
+ throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
698
+ });
699
+ Schema.extend("intersect", (data, { list, toString }, options, strict) => {
700
+ if (!list.length) return [data];
701
+ let result;
702
+ for (const inner of list) {
703
+ const value = Schema.resolve(data, inner, options, true)[0];
704
+ if (isNullable(value)) continue;
705
+ if (isNullable(result)) result = value;
706
+ else if (typeof result !== typeof value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
707
+ else if (typeof value === "object") merge(result ??= {}, value);
708
+ else if (result !== value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
709
+ }
710
+ if (!strict && isPlainObject(data)) merge(result, data);
711
+ return [result];
712
+ });
713
+ Schema.extend("transform", (data, { inner, callback, preserve }, options) => {
714
+ const [result, adapted = data] = Schema.resolve(data, inner, options, true);
715
+ if (preserve) return [callback(result)];
716
+ else return [callback(result), callback(adapted)];
717
+ });
718
+ const formatters = {};
719
+ function defineMethod(name, keys, format) {
720
+ formatters[name] = format;
721
+ Object.assign(Schema, { [name](...args) {
722
+ const schema = new Schema({ type: name });
723
+ keys.forEach((key, index) => {
724
+ switch (key) {
725
+ case "sKey":
726
+ schema.sKey = args[index] ?? Schema.string();
727
+ break;
728
+ case "inner":
729
+ schema.inner = Schema.from(args[index]);
730
+ break;
731
+ case "list":
732
+ schema.list = args[index].map(Schema.from);
733
+ break;
734
+ case "dict":
735
+ schema.dict = mapValues(args[index], Schema.from);
736
+ break;
737
+ case "bits":
738
+ schema.bits = {};
739
+ for (const key in args[index]) {
740
+ if (typeof args[index][key] !== "number") continue;
741
+ schema.bits[key] = args[index][key];
742
+ }
743
+ break;
744
+ case "callback": {
745
+ const callback = schema.callback = args[index];
746
+ callback["toJSON"] ||= () => callback.toString();
747
+ break;
748
+ }
749
+ case "constructor": {
750
+ const constructor = schema.constructor = args[index];
751
+ if (typeof constructor === "function") constructor["toJSON"] ||= () => constructor["name"];
752
+ break;
753
+ }
754
+ default: schema[key] = args[index];
755
+ }
756
+ });
757
+ if (name === "object" || name === "dict") schema.meta.default = {};
758
+ else if (name === "array" || name === "tuple") schema.meta.default = [];
759
+ else if (name === "bitset") schema.meta.default = 0;
760
+ return schema;
761
+ } });
762
+ }
763
+ defineMethod("is", ["constructor"], ({ constructor }) => {
764
+ if (typeof constructor === "function") return constructor.name;
765
+ else return constructor;
766
+ });
767
+ defineMethod("any", [], () => "any");
768
+ defineMethod("never", [], () => "never");
769
+ defineMethod("const", ["value"], ({ value }) => typeof value === "string" ? JSON.stringify(value) : value);
770
+ defineMethod("string", [], () => "string");
771
+ defineMethod("number", [], () => "number");
772
+ defineMethod("boolean", [], () => "boolean");
773
+ defineMethod("bitset", ["bits"], () => "bitset");
774
+ defineMethod("function", [], () => "function");
775
+ defineMethod("array", ["inner"], ({ inner }) => `${inner.toString(true)}[]`);
776
+ defineMethod("dict", ["inner", "sKey"], ({ inner, sKey }) => `{ [key: ${sKey.toString()}]: ${inner.toString()} }`);
777
+ defineMethod("tuple", ["list"], ({ list }) => `[${list.map((inner) => inner.toString()).join(", ")}]`);
778
+ defineMethod("object", ["dict"], ({ dict }) => {
779
+ if (Object.keys(dict).length === 0) return "{}";
780
+ return `{ ${Object.entries(dict).map(([key, inner]) => {
781
+ return `${key}${inner.meta.required ? "" : "?"}: ${inner.toString()}`;
782
+ }).join(", ")} }`;
783
+ });
784
+ defineMethod("union", ["list"], ({ list }, inline) => {
785
+ const result = list.map(({ toString: format }) => format()).join(" | ");
786
+ return inline ? `(${result})` : result;
787
+ });
788
+ defineMethod("intersect", ["list"], ({ list }) => {
789
+ return `${list.map((inner) => inner.toString(true)).join(" & ")}`;
790
+ });
791
+ defineMethod("transform", [
792
+ "inner",
793
+ "callback",
794
+ "preserve"
795
+ ], ({ inner }, isInner) => inner.toString(isInner));
796
+ //#endregion
797
+ //#region lib/types/recovery-config.js
798
+ /** Shared validation for Host-configured and browser-local connection recovery. */
799
+ const MAX_TIMER_MS = 2147483647;
800
+ /** Schema shared by the Host plugin and the Client's recovery input parser. */
801
+ const ConnectionRecoveryConfigSchema = Schema.object({
802
+ backoffBaseMs: Schema.natural().min(1).max(MAX_TIMER_MS).default(500),
803
+ backoffFactor: Schema.number().min(1).max(Number.MAX_VALUE).default(2),
804
+ backoffMaxMs: Schema.natural().min(1).max(MAX_TIMER_MS).default(1e4),
805
+ generationReadyWarnMs: Schema.natural().min(1).max(MAX_TIMER_MS).default(3e3),
806
+ generationReadyTimeoutMs: Schema.natural().min(1).max(MAX_TIMER_MS).default(15e3)
807
+ });
808
+ /**
809
+ * Validate recovery input and supply every timing default before starting work.
810
+ * @param config - Host configuration, page bootstrap data, or direct loop options.
811
+ * @returns validated, complete recovery timing.
812
+ */
813
+ function resolveConnectionConfig(config = {}) {
814
+ const resolved = ConnectionRecoveryConfigSchema(config);
815
+ if (!Number.isFinite(resolved.backoffFactor)) throw new RangeError("connection recovery backoffFactor must be finite");
816
+ return resolved;
817
+ }
818
+ //#endregion
819
+ //#region lib/types/client/connection.js
820
+ /** Connection generation readiness, cancellation, and continuous recovery. */
14
821
  const MANUAL_RECONNECT = /* @__PURE__ */ new Error("connection: manual reconnect requested");
15
822
  const NETWORK_STATE_CHANGED = /* @__PURE__ */ new Error("connection: browser network state changed");
16
823
  function sleep(ms, signal) {
@@ -52,10 +859,7 @@ window.__ModuleLoader__.load({
52
859
  constructor(source, sinks = {}, config = {}) {
53
860
  this.source = source;
54
861
  this.sinks = sinks;
55
- this.config = {
56
- ...CONNECTION_DEFAULTS,
57
- ...config
58
- };
862
+ this.config = resolveConnectionConfig(config);
59
863
  }
60
864
  /** Idempotent: begin the connect/pump/reconnect loop. */
61
865
  start() {
@@ -104,10 +908,9 @@ window.__ModuleLoader__.load({
104
908
  const cap = this.backoffCap(attempt);
105
909
  return cap / 2 + Math.random() * (cap / 2);
106
910
  }
107
- isFinalBackoffTier(attempt) {
108
- const cap = this.backoffCap(attempt);
109
- const nextCap = this.backoffCap(attempt + 1);
110
- return cap >= this.config.backoffMaxMs || !Number.isFinite(nextCap) || nextCap <= cap;
911
+ /** Re-read retry inputs after a potentially reentrant state sink. */
912
+ isRetryInterrupted(immediate) {
913
+ return this.immediateRetry || !this.networkAvailable && !immediate;
111
914
  }
112
915
  /** Read through a method: stop() flips the flag across awaits, so narrowing from the loop condition must not stick. */
113
916
  isRunning() {
@@ -136,17 +939,10 @@ window.__ModuleLoader__.load({
136
939
  this.immediateRetry = false;
137
940
  if (immediate) this.attempt = 0;
138
941
  manualAttempt = immediate;
139
- if (!immediate && this.attempt > 0 && this.isFinalBackoffTier(this.attempt)) {
140
- const retryDelay = new AbortController();
141
- this.retryDelay = retryDelay;
142
- this.emitState("disconnected");
143
- await waitForAbort(retryDelay.signal);
144
- if (this.retryDelay === retryDelay) this.retryDelay = null;
145
- continue;
146
- }
147
942
  const attempt = ++this.attempt;
148
943
  this.emitState("connecting");
149
944
  if (!this.isRunning()) return;
945
+ if (this.isRetryInterrupted(immediate)) continue;
150
946
  if (!immediate) {
151
947
  const retryDelay = new AbortController();
152
948
  this.retryDelay = retryDelay;
@@ -176,7 +972,7 @@ window.__ModuleLoader__.load({
176
972
  rejectSourceLost = reject;
177
973
  });
178
974
  const reportReady = (host) => {
179
- if (sourceReady) return;
975
+ if (sourceReady || gen !== this.generation || !this.isGenerationActive(ac)) return;
180
976
  sourceReady = true;
181
977
  resolveReady(host);
182
978
  };
@@ -198,14 +994,16 @@ window.__ModuleLoader__.load({
198
994
  });
199
995
  });
200
996
  try {
201
- const host = await Promise.race([waitForReady(ready, this.config.generationReadyTimeoutMs, ac.signal), sourceLost]);
997
+ const host = await Promise.race([waitForReady(ready, this.config, ac.signal), sourceLost]);
202
998
  if (ac.signal.aborted) throw new Error("generation aborted during readiness handshake");
203
999
  this.attempt = 0;
204
1000
  this.emitState("connected");
205
1001
  if (this.isGenerationActive(ac)) this.callSink(() => {
206
1002
  this.sinks.onConnected?.(host);
207
1003
  });
208
- } catch {}
1004
+ } catch (error) {
1005
+ if (!ac.signal.aborted) ac.abort(error);
1006
+ }
209
1007
  await failed;
210
1008
  if (!this.isRunning()) return;
211
1009
  if (manualAttempt) this.attempt = 0;
@@ -227,19 +1025,25 @@ window.__ModuleLoader__.load({
227
1025
  }
228
1026
  }
229
1027
  };
230
- /** Await source readiness while reporting, but not cancelling, a slow Host. */
231
- function waitForReady(ready, timeoutMs, signal) {
1028
+ /** Report a slow handshake before the hard deadline ends its generation. */
1029
+ function waitForReady(ready, config, signal) {
232
1030
  return new Promise((resolve, reject) => {
233
1031
  let settled = false;
1032
+ const warning = setTimeout(() => {
1033
+ console.warn(`[connection] generation is still not ready after ${String(config.generationReadyWarnMs)}ms`);
1034
+ }, config.generationReadyWarnMs);
234
1035
  const timeout = setTimeout(() => {
235
- console.warn(`[connection] generation is still not ready after ${String(timeoutMs)}ms`);
236
- }, timeoutMs);
1036
+ const error = /* @__PURE__ */ new Error(`connection generation was not ready within ${String(config.generationReadyTimeoutMs)}ms`);
1037
+ console.warn(`[connection] ${error.message}; cancelling generation`);
1038
+ finish({ error });
1039
+ }, config.generationReadyTimeoutMs);
237
1040
  const aborted = () => {
238
1041
  finish({ error: new Error("connection generation aborted", { cause: signal.reason }) });
239
1042
  };
240
1043
  const finish = (outcome) => {
241
1044
  if (settled) return;
242
1045
  settled = true;
1046
+ clearTimeout(warning);
243
1047
  clearTimeout(timeout);
244
1048
  signal.removeEventListener("abort", aborted);
245
1049
  if ("error" in outcome) reject(outcome.error);
@@ -299,6 +1103,166 @@ window.__ModuleLoader__.load({
299
1103
  }
300
1104
  //#endregion
301
1105
  //#region ../../util/values/lib/index.js
1106
+ /** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
1107
+ /**
1108
+ * Mark an unreachable closed-union branch.
1109
+ * @param value - impossible value; an unhandled typed variant fails at the call site.
1110
+ * @param context - optional switch-site label included in the failure message.
1111
+ * @returns never; a runtime value that escaped its type always throws.
1112
+ */
1113
+ function assertNever(value, context) {
1114
+ const rendered = JSON.stringify(value) ?? String(value);
1115
+ throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
1116
+ }
1117
+ /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
1118
+ function hasIntrinsicConstructor(prototype, name) {
1119
+ const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
1120
+ if (typeof constructor !== "function") return false;
1121
+ try {
1122
+ return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
1123
+ } catch {
1124
+ return false;
1125
+ }
1126
+ }
1127
+ /** Whether a candidate is one realm's intrinsic `Object.prototype`. */
1128
+ function isIntrinsicObjectPrototype(value) {
1129
+ return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
1130
+ }
1131
+ /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
1132
+ function hasPlainArrayPrototype(value) {
1133
+ const prototype = Object.getPrototypeOf(value);
1134
+ if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
1135
+ const objectPrototype = Object.getPrototypeOf(prototype);
1136
+ return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
1137
+ }
1138
+ /** Whether an object is a plain or null-prototype record from any JavaScript realm. */
1139
+ function hasPlainObjectPrototype(value) {
1140
+ const prototype = Object.getPrototypeOf(value);
1141
+ return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
1142
+ }
1143
+ /** Return every JSON-visible object key, or reject own data JSON would discard. */
1144
+ function enumerableStringKeys(value) {
1145
+ const keys = Reflect.ownKeys(value);
1146
+ if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
1147
+ return keys;
1148
+ }
1149
+ /** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
1150
+ function walkJsonValue(value, detach) {
1151
+ const ancestors = /* @__PURE__ */ new Set();
1152
+ let root;
1153
+ const assign = (destination, item) => {
1154
+ if (destination === void 0) return;
1155
+ if (destination.kind === "root") root = item;
1156
+ else if (destination.kind === "array") destination.target[destination.index] = item;
1157
+ else Object.defineProperty(destination.target, destination.key, {
1158
+ value: item,
1159
+ enumerable: true,
1160
+ configurable: true,
1161
+ writable: true
1162
+ });
1163
+ };
1164
+ const tasks = [{
1165
+ kind: "visit",
1166
+ value,
1167
+ ...detach ? { destination: { kind: "root" } } : {}
1168
+ }];
1169
+ for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
1170
+ if (task.kind === "leave") {
1171
+ ancestors.delete(task.source);
1172
+ continue;
1173
+ }
1174
+ if (task.kind === "array-item") {
1175
+ if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
1176
+ tasks.push({
1177
+ kind: "visit",
1178
+ value: task.source[task.index],
1179
+ ...task.target === void 0 ? {} : { destination: {
1180
+ kind: "array",
1181
+ target: task.target,
1182
+ index: task.index
1183
+ } }
1184
+ });
1185
+ continue;
1186
+ }
1187
+ if (task.kind === "object-property") {
1188
+ tasks.push({
1189
+ kind: "visit",
1190
+ value: task.source[task.key],
1191
+ ...task.target === void 0 ? {} : { destination: {
1192
+ kind: "object",
1193
+ target: task.target,
1194
+ key: task.key
1195
+ } }
1196
+ });
1197
+ continue;
1198
+ }
1199
+ const current = task.value;
1200
+ if (current === null) {
1201
+ assign(task.destination, null);
1202
+ continue;
1203
+ }
1204
+ if (typeof current === "boolean" || typeof current === "string") {
1205
+ assign(task.destination, current);
1206
+ continue;
1207
+ }
1208
+ if (typeof current === "number") {
1209
+ if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
1210
+ assign(task.destination, current);
1211
+ continue;
1212
+ }
1213
+ if (typeof current !== "object") return void 0;
1214
+ if (ancestors.has(current)) return void 0;
1215
+ if (Array.isArray(current)) {
1216
+ if (!hasPlainArrayPrototype(current)) return void 0;
1217
+ const length = current.length;
1218
+ if (Reflect.ownKeys(current).length !== length + 1) return void 0;
1219
+ const target = detach ? [] : void 0;
1220
+ if (target !== void 0) assign(task.destination, target);
1221
+ ancestors.add(current);
1222
+ tasks.push({
1223
+ kind: "leave",
1224
+ source: current
1225
+ });
1226
+ for (let index = length - 1; index >= 0; index--) tasks.push({
1227
+ kind: "array-item",
1228
+ source: current,
1229
+ index,
1230
+ ...target === void 0 ? {} : { target }
1231
+ });
1232
+ continue;
1233
+ }
1234
+ if (!hasPlainObjectPrototype(current)) return void 0;
1235
+ const keys = enumerableStringKeys(current);
1236
+ if (keys === void 0) return void 0;
1237
+ const target = detach ? {} : void 0;
1238
+ if (target !== void 0) assign(task.destination, target);
1239
+ ancestors.add(current);
1240
+ tasks.push({
1241
+ kind: "leave",
1242
+ source: current
1243
+ });
1244
+ for (let index = keys.length - 1; index >= 0; index--) {
1245
+ const key = keys[index];
1246
+ /* v8 ignore next -- the loop is bounded by the captured key count. */
1247
+ if (key === void 0) return void 0;
1248
+ tasks.push({
1249
+ kind: "object-property",
1250
+ source: current,
1251
+ key,
1252
+ ...target === void 0 ? {} : { target }
1253
+ });
1254
+ }
1255
+ }
1256
+ return detach ? root : true;
1257
+ }
1258
+ /**
1259
+ * Validate and detach lossless JSON in one read per property.
1260
+ * @param value - candidate value to validate and detach.
1261
+ * @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
1262
+ */
1263
+ function snapshotJsonValue(value) {
1264
+ return walkJsonValue(value, true);
1265
+ }
302
1266
  /**
303
1267
  * Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
304
1268
  * @param value - value to freeze.
@@ -409,214 +1373,302 @@ window.__ModuleLoader__.load({
409
1373
  });
410
1374
  }
411
1375
  //#endregion
412
- //#region ../../core/session/lib/types/types.js
413
- /**
414
- * Admit a numeric value as an existing Session event position.
415
- * @param value - non-negative safe integer admitted by the owning log operation.
416
- * @returns the same number with the Session-sequence brand.
417
- */
418
- function SessionSeq(value) {
419
- if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionSeq must be a non-negative safe integer, got ${String(value)}`);
420
- return brandNumber(value);
421
- }
422
- /**
423
- * Admit a numeric value as a Session log offset.
424
- * @param value - non-negative safe integer used as a gap or prefix length.
425
- * @returns the same number with the Session-log-offset brand.
426
- */
427
- function SessionLogOffset(value) {
428
- if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionLogOffset must be a non-negative safe integer, got ${String(value)}`);
429
- return brandNumber(value);
430
- }
431
- //#endregion
432
- //#region ../../core/session/lib/types/chunk-rows.js
1376
+ //#region ../../llm/llm/lib/types/brand.js
433
1377
  /**
434
- * Lossless row packing for `assistant/chunk` delta runs. Providers stream
435
- * token-sized deltas, so a log stores hundreds of near-identical event lines
436
- * whose JSON envelopes dwarf their payloads (~56× measured on a real DeepSeek
437
- * session). This module packs each run of consecutive same-block delta chunks
438
- * into ONE storage row — `text-chunks`, `reasoning-chunks`, or
439
- * `tool-call-chunks` — and expands rows back to the exact original events.
1378
+ * dsh-llm's owned branded ids: tool-call correlation and provider request
1379
+ * diagnostics.
440
1380
  *
441
- * Packed rows are an encoding vocabulary, NOT session events: they never enter
442
- * `Session.snapshotEvents()`, have no `SessionEventMap` entry, and use bare (slash-less)
443
- * type tags so a reader cannot confuse them with the event taxonomy
444
- * (precedent: the JSONL header line's `session` tag). Persistence and bounded
445
- * history transport both use the codec. The encoder whitelists exact shapes —
446
- * anything it does not fully recognize stays verbatim, so unknown fields or
447
- * future chunk variants lose compression, never data. The decoder validates
448
- * before expanding and fails loud on a malformed row-tagged value instead of
449
- * silently dropping a whole run.
1381
+ * The `Branded<B>` primitive and stateless constructor live in
1382
+ * `@deepseek-ai/dsh-brand` so every owner of a cross-boundary id can brand it
1383
+ * without depending on dsh-llm; see that package's README for the
1384
+ * nominal-typing policy.
450
1385
  *
451
- * @module @deepseek-ai/dsh-session/chunk-rows
1386
+ * @module @deepseek-ai/dsh-llm/brand
452
1387
  */
453
1388
  /**
454
- * Test whether an encoded record is a packed chunk row rather than a Session event.
455
- * @param record - one persistence or bounded-history encoding record.
456
- * @returns Whether the record is a packed chunk row.
457
- */
458
- function isChunkRow(record) {
459
- return record.type === "text-chunks" || record.type === "reasoning-chunks" || record.type === "tool-call-chunks";
460
- }
461
- /**
462
- * Minimum members before a run packs. Below it a row's envelope rivals the
463
- * event lines it replaces. A format constant, not a tunable: both layouts
464
- * decode identically, so changing it never invalidates stored logs.
1389
+ * Brand one loop-owned streaming attempt identifier.
1390
+ * @param id - the opaque Agent-lifecycle-local identifier.
1391
+ * @returns the same string with the attempt-id brand.
465
1392
  */
466
- const MIN_RUN = 3;
467
- function isRecord$1(value) {
468
- return typeof value === "object" && value !== null;
469
- }
470
- /** Exact-key check: `value` has every key in `keys` and nothing else. */
471
- function hasExactKeys(value, keys) {
472
- return Object.keys(value).length === keys.length && keys.every((k) => Object.hasOwn(value, k));
1393
+ function LlmAttemptId(id) {
1394
+ return brandString(id);
473
1395
  }
1396
+ //#endregion
1397
+ //#region ../../llm/llm/lib/types/assistant-stream.js
474
1398
  /**
475
- * Classify an event for packing: its delta kind when the ENTIRE shape
476
- * (envelope, data, chunk exact keys, primitive types, integer seq/time) is
477
- * whitelisted, else `undefined` (store verbatim). Inputs come from live typed
478
- * appends AND parsed fixture files, so the checks are structural, not
479
- * type-trusted. Integer times keep gap encoding exact: a fractional time would
480
- * reconstruct through float subtraction/addition, which need not round-trip.
1399
+ * Lossless compact representation of one model-stream attempt, plus record-level
1400
+ * readers that answer common consumer questions without materializing members.
1401
+ * Readers trust the static record type; expandAssistantStream is the validating
1402
+ * path for records read at a durable boundary.
481
1403
  */
482
- function classify(event) {
483
- if (event.type !== "assistant/chunk") return void 0;
484
- if (!hasExactKeys(event, [
485
- "type",
486
- "seq",
487
- "time",
488
- "data"
489
- ])) return void 0;
490
- if (!Number.isSafeInteger(event.seq) || event.seq < 0 || Object.is(event.seq, -0) || !Number.isSafeInteger(event.time)) return void 0;
491
- const data = event.data;
492
- if (!isRecord$1(data) || !hasExactKeys(data, [
493
- "turn",
494
- "step",
495
- "chunk"
496
- ])) return void 0;
497
- if (typeof data.turn !== "number" || typeof data.step !== "number") return void 0;
498
- const chunk = data.chunk;
499
- if (!isRecord$1(chunk) || typeof chunk.index !== "number") return void 0;
500
- switch (chunk.type) {
501
- case "text-delta":
502
- case "reasoning-delta": return hasExactKeys(chunk, [
503
- "type",
504
- "index",
505
- "text"
506
- ]) && typeof chunk.text === "string" ? chunk.type : void 0;
507
- case "tool-call-delta": return (hasExactKeys(chunk, [
508
- "type",
509
- "index",
510
- "id",
511
- "argumentsDelta"
512
- ]) || hasExactKeys(chunk, [
513
- "type",
514
- "index",
515
- "id",
516
- "name",
517
- "argumentsDelta"
518
- ]) && typeof chunk.name === "string") && typeof chunk.id === "string" && typeof chunk.argumentsDelta === "string" ? chunk.type : void 0;
519
- default: return;
520
- }
1404
+ function safeTime(value) {
1405
+ if (!Number.isSafeInteger(value)) throw new TypeError(`Assistant stream time must be a safe integer, got ${String(value)}`);
1406
+ return value;
521
1407
  }
522
- /** The tool-call fields of a whitelisted delta chunk (only after {@link classify} returned `'tool-call-delta'`). */
523
- function toolCallOf(event) {
524
- return event.data.chunk;
1408
+ function safeIndex(value, label) {
1409
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`${label} index must be a non-negative safe integer`);
1410
+ return value;
525
1411
  }
526
- /** The block index of a whitelisted delta chunk (not every {@link StreamChunk} variant carries one). */
527
- function indexOf(event) {
528
- return event.data.chunk.index;
1412
+ function snapshotChunk(chunk) {
1413
+ const snapshot = snapshotJsonValue(chunk);
1414
+ if (snapshot === void 0) throw new TypeError("Assistant stream chunk must be losslessly JSON-serializable");
1415
+ return snapshot;
529
1416
  }
530
- /** Whether `next` extends a run ending in `prev` (same kind already checked by the caller). */
531
- function continues(prev, next, kind) {
532
- if (next.seq !== prev.seq + 1) return false;
533
- if (!Number.isSafeInteger(next.time - prev.time)) return false;
534
- if (next.data.turn !== prev.data.turn || next.data.step !== prev.data.step) return false;
535
- if (indexOf(next) !== indexOf(prev)) return false;
536
- if (kind !== "tool-call-delta") return true;
537
- const a = toolCallOf(prev);
538
- const b = toolCallOf(next);
539
- return a.id === b.id && Object.hasOwn(a, "name") === Object.hasOwn(b, "name") && a.name === b.name;
1417
+ function safeGap(previous, next) {
1418
+ const gap = next - previous;
1419
+ return Number.isSafeInteger(gap) && previous + gap === next ? gap : void 0;
540
1420
  }
541
- /** Build the row for a completed run (`run.length >= MIN_RUN`, uniform per {@link continues}). */
542
- function buildRow(kind, run) {
543
- const first = run[0];
544
- const base = {
545
- turn: first.data.turn,
546
- step: first.data.step,
547
- index: indexOf(first),
548
- dt: run.slice(1).map((event, i) => event.time - run[i].time)
549
- };
550
- const envelope = {
551
- seq0: first.seq,
552
- time0: first.time
553
- };
554
- if (kind === "tool-call-delta") {
555
- const call = toolCallOf(first);
556
- return {
557
- type: "tool-call-chunks",
558
- ...envelope,
559
- data: {
560
- ...base,
561
- id: brandString(call.id),
562
- ...Object.hasOwn(call, "name") ? { name: call.name } : {},
563
- args: run.map((event) => event.data.chunk.argumentsDelta)
1421
+ /** Incrementally compacts one attempt without retaining a second raw-chunk list. */
1422
+ var AssistantStreamAccumulator = class {
1423
+ records = [];
1424
+ /**
1425
+ * Add one timed chunk to the compact attempt stream.
1426
+ * @param value - model chunk and its original Session timestamp.
1427
+ * @returns a detached immutable copy for assembly and live publication.
1428
+ */
1429
+ push(value) {
1430
+ const time = safeTime(value.time);
1431
+ const chunk = snapshotChunk(value.chunk);
1432
+ const timed = deepFreeze({
1433
+ time,
1434
+ chunk
1435
+ });
1436
+ const previous = this.records.at(-1);
1437
+ switch (chunk.type) {
1438
+ case "text-delta":
1439
+ case "reasoning-delta": {
1440
+ safeIndex(chunk.index, chunk.type);
1441
+ if (typeof chunk.text !== "string") throw new TypeError(`${chunk.type} text must be a string`);
1442
+ const type = chunk.type === "text-delta" ? "text-chunks" : "reasoning-chunks";
1443
+ const gap = previous !== void 0 && previous.type === type ? safeGap(previous.lastTime, time) : void 0;
1444
+ if (previous !== void 0 && previous.type === type && previous.index === chunk.index && gap !== void 0) {
1445
+ previous.dt.push(gap);
1446
+ previous.texts.push(chunk.text);
1447
+ previous.lastTime = time;
1448
+ } else this.records.push({
1449
+ type,
1450
+ time0: time,
1451
+ index: chunk.index,
1452
+ dt: [],
1453
+ texts: [chunk.text],
1454
+ lastTime: time
1455
+ });
1456
+ return timed;
564
1457
  }
565
- };
1458
+ case "tool-call-delta": {
1459
+ safeIndex(chunk.index, chunk.type);
1460
+ if (typeof chunk.id !== "string") throw new TypeError("tool-call-delta id must be a string");
1461
+ if (Object.hasOwn(chunk, "name") && typeof chunk.name !== "string") throw new TypeError("tool-call-delta name must be a string");
1462
+ if (typeof chunk.argumentsDelta !== "string") throw new TypeError("tool-call-delta argumentsDelta must be a string");
1463
+ if (chunk.id.length === 0 || chunk.name === "") {
1464
+ this.records.push({
1465
+ type: "chunk",
1466
+ time,
1467
+ chunk
1468
+ });
1469
+ return timed;
1470
+ }
1471
+ const gap = previous?.type === "tool-call-chunks" ? safeGap(previous.lastTime, time) : void 0;
1472
+ const sameName = previous?.type === "tool-call-chunks" && Object.hasOwn(previous, "name") === Object.hasOwn(chunk, "name") && previous.name === chunk.name;
1473
+ if (previous?.type === "tool-call-chunks" && previous.index === chunk.index && previous.id === chunk.id && sameName && gap !== void 0) {
1474
+ previous.dt.push(gap);
1475
+ previous.args.push(chunk.argumentsDelta);
1476
+ previous.lastTime = time;
1477
+ } else this.records.push({
1478
+ type: "tool-call-chunks",
1479
+ time0: time,
1480
+ index: chunk.index,
1481
+ dt: [],
1482
+ id: chunk.id,
1483
+ ...Object.hasOwn(chunk, "name") ? { name: chunk.name } : {},
1484
+ args: [chunk.argumentsDelta],
1485
+ lastTime: time
1486
+ });
1487
+ return timed;
1488
+ }
1489
+ case "block-start":
1490
+ case "block-end":
1491
+ case "usage":
1492
+ case "finish":
1493
+ this.records.push({
1494
+ type: "chunk",
1495
+ time,
1496
+ chunk
1497
+ });
1498
+ return timed;
1499
+ default: return assertNever(chunk, "AssistantStreamAccumulator.push");
1500
+ }
566
1501
  }
567
- const data = {
568
- ...base,
569
- texts: run.map((event) => event.data.chunk.text)
570
- };
571
- return kind === "text-delta" ? {
572
- type: "text-chunks",
573
- ...envelope,
574
- data
575
- } : {
576
- type: "reasoning-chunks",
577
- ...envelope,
578
- data
579
- };
580
- }
1502
+ /**
1503
+ * Return the current compact attempt stream.
1504
+ * @returns a detached immutable record list suitable for a durable event.
1505
+ */
1506
+ snapshot() {
1507
+ return deepFreeze(this.records.map((record) => {
1508
+ if (record.type === "chunk") return { ...record };
1509
+ const { lastTime: _lastTime, ...durable } = record;
1510
+ if (durable.type === "tool-call-chunks") return {
1511
+ ...durable,
1512
+ dt: [...durable.dt],
1513
+ args: [...durable.args]
1514
+ };
1515
+ return {
1516
+ ...durable,
1517
+ dt: [...durable.dt],
1518
+ texts: [...durable.texts]
1519
+ };
1520
+ }));
1521
+ }
1522
+ };
581
1523
  /**
582
- * Pack an event batch for storage: each run of at least {@link MIN_RUN}
583
- * consecutive whitelisted same-kind, same-block delta chunk events becomes one
584
- * {@link ChunkRow}; every other event passes through verbatim, in order.
585
- * Pure and stateless safe over any array, including a batch whose runs were
586
- * split by flush boundaries (the split runs simply pack per batch).
587
- *
588
- * @param events - the batch to encode, in log order.
589
- * @returns the storage records to write, one JSONL line each.
1524
+ * Expand compact records into the exact timed chunk sequence.
1525
+ * @param stream - compact records from one durable Assistant settlement.
1526
+ * @returns detached timed chunks with every original delta boundary preserved.
1527
+ * @throws {TypeError} when a record or reconstructed timestamp is invalid.
590
1528
  */
591
- function packChunkRuns(events) {
592
- const out = [];
593
- let kind;
594
- let run = [];
595
- const flush = () => {
596
- if (kind !== void 0 && run.length >= MIN_RUN) out.push(buildRow(kind, run));
597
- else out.push(...run);
598
- kind = void 0;
599
- run = [];
600
- };
601
- for (const event of events) {
602
- const k = classify(event);
603
- if (k === void 0) {
604
- flush();
605
- out.push(event);
1529
+ function expandAssistantStream(stream) {
1530
+ const chunks = [];
1531
+ for (const candidate of stream) {
1532
+ const record = validateRecord(candidate);
1533
+ if (record.type === "chunk") {
1534
+ chunks.push({
1535
+ time: record.time,
1536
+ chunk: record.chunk
1537
+ });
606
1538
  continue;
607
1539
  }
608
- const delta = event;
609
- const last = run[run.length - 1];
610
- if (k === kind && last !== void 0 && continues(last, delta, k)) {
611
- run.push(delta);
612
- continue;
1540
+ const members = record.type === "tool-call-chunks" ? record.args : record.texts;
1541
+ let time = record.time0;
1542
+ for (let index = 0; index < members.length; index += 1) {
1543
+ if (index > 0) time += record.dt[index - 1];
1544
+ let chunk;
1545
+ if (record.type === "text-chunks") chunk = {
1546
+ type: "text-delta",
1547
+ index: record.index,
1548
+ text: members[index]
1549
+ };
1550
+ else if (record.type === "reasoning-chunks") chunk = {
1551
+ type: "reasoning-delta",
1552
+ index: record.index,
1553
+ text: members[index]
1554
+ };
1555
+ else chunk = {
1556
+ type: "tool-call-delta",
1557
+ index: record.index,
1558
+ id: record.id,
1559
+ ...Object.hasOwn(record, "name") ? { name: record.name } : {},
1560
+ argumentsDelta: members[index]
1561
+ };
1562
+ chunks.push({
1563
+ time,
1564
+ chunk
1565
+ });
1566
+ }
1567
+ }
1568
+ return chunks;
1569
+ }
1570
+ function validateRecord(value) {
1571
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Assistant stream record must be an object");
1572
+ const record = value;
1573
+ switch (record.type) {
1574
+ case "text-chunks":
1575
+ case "reasoning-chunks": {
1576
+ exactKeys(record, [
1577
+ "type",
1578
+ "time0",
1579
+ "index",
1580
+ "dt",
1581
+ "texts"
1582
+ ], record.type);
1583
+ const texts = stringArray(record.texts, `${record.type} texts`);
1584
+ if (texts.length === 0) throw new TypeError(`${record.type} texts must be non-empty`);
1585
+ validateRun(record, texts.length, record.type);
1586
+ return record;
1587
+ }
1588
+ case "tool-call-chunks": {
1589
+ exactKeys(record, Object.hasOwn(record, "name") ? [
1590
+ "type",
1591
+ "time0",
1592
+ "index",
1593
+ "dt",
1594
+ "id",
1595
+ "name",
1596
+ "args"
1597
+ ] : [
1598
+ "type",
1599
+ "time0",
1600
+ "index",
1601
+ "dt",
1602
+ "id",
1603
+ "args"
1604
+ ], record.type);
1605
+ const args = stringArray(record.args, "tool-call-chunks args");
1606
+ if (args.length === 0) throw new TypeError("tool-call-chunks args must be non-empty");
1607
+ if (typeof record.id !== "string" || record.id.length === 0) throw new TypeError("tool-call-chunks id must be a non-empty string");
1608
+ if (record.name !== void 0 && (typeof record.name !== "string" || record.name.length === 0)) throw new TypeError("tool-call-chunks name must be a non-empty string");
1609
+ validateRun(record, args.length, record.type);
1610
+ return record;
1611
+ }
1612
+ case "chunk": {
1613
+ exactKeys(record, [
1614
+ "type",
1615
+ "time",
1616
+ "chunk"
1617
+ ], "chunk");
1618
+ const time = safeTime(record.time);
1619
+ if (typeof record.chunk !== "object" || record.chunk === null || Array.isArray(record.chunk)) throw new TypeError("Assistant stream raw chunk must be a lossless JSON object");
1620
+ let chunk;
1621
+ try {
1622
+ chunk = snapshotChunk(record.chunk);
1623
+ } catch (error) {
1624
+ throw new TypeError("Assistant stream raw chunk must be a lossless JSON object", { cause: error });
1625
+ }
1626
+ return deepFreeze({
1627
+ type: "chunk",
1628
+ time,
1629
+ chunk
1630
+ });
613
1631
  }
614
- flush();
615
- kind = k;
616
- run = [delta];
1632
+ default: throw new TypeError(`Unsupported Assistant stream record ${JSON.stringify(record.type)}`);
617
1633
  }
618
- flush();
619
- return out;
1634
+ }
1635
+ function validateRun(record, members, label) {
1636
+ safeTime(record.time0);
1637
+ safeIndex(record.index, label);
1638
+ if (!Array.isArray(record.dt) || record.dt.some((value) => !Number.isSafeInteger(value))) throw new TypeError(`${label} dt must contain safe integers`);
1639
+ if (record.dt.length !== members - 1) throw new TypeError(`${label} dt length must be one less than its members`);
1640
+ let time = record.time0;
1641
+ for (const gap of record.dt) {
1642
+ time += gap;
1643
+ if (!Number.isSafeInteger(time)) throw new TypeError(`${label} member times must stay safe integers`);
1644
+ }
1645
+ }
1646
+ function stringArray(value, label) {
1647
+ if (!Array.isArray(value) || value.some((member) => typeof member !== "string")) throw new TypeError(`${label} must be a string array`);
1648
+ return value;
1649
+ }
1650
+ function exactKeys(record, keys, label) {
1651
+ if (Object.keys(record).length !== keys.length || !keys.every((key) => Object.hasOwn(record, key))) throw new TypeError(`${label} Assistant stream record must contain exactly ${keys.join(", ")}`);
1652
+ }
1653
+ //#endregion
1654
+ //#region ../../core/session/lib/types/types.js
1655
+ /**
1656
+ * Admit a numeric value as an existing Session event position.
1657
+ * @param value - non-negative safe integer admitted by the owning log operation.
1658
+ * @returns the same number with the Session-sequence brand.
1659
+ */
1660
+ function SessionSeq(value) {
1661
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionSeq must be a non-negative safe integer, got ${String(value)}`);
1662
+ return brandNumber(value);
1663
+ }
1664
+ /**
1665
+ * Admit a numeric value as a Session log offset.
1666
+ * @param value - non-negative safe integer used as a gap or prefix length.
1667
+ * @returns the same number with the Session-log-offset brand.
1668
+ */
1669
+ function SessionLogOffset(value) {
1670
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionLogOffset must be a non-negative safe integer, got ${String(value)}`);
1671
+ return brandNumber(value);
620
1672
  }
621
1673
  //#endregion
622
1674
  //#region ../../core/session/lib/types/surface.js
@@ -645,7 +1697,7 @@ window.__ModuleLoader__.load({
645
1697
  }
646
1698
  /**
647
1699
  * Project a single event into the LLM message it derives to, or null when it
648
- * produces none — a non-surface event (chunk, boundary, log-only record) or an
1700
+ * produces none — a non-surface event (attempt, boundary, log-only record) or an
649
1701
  * empty-content assistant/message (which exists only to host usage). This is
650
1702
  * THE per-node projection rule: `Session.deriveMessages` folds it over the
651
1703
  * live surface, external reconstructors and pure projections fold the same
@@ -700,10 +1752,11 @@ window.__ModuleLoader__.load({
700
1752
  /** Validate cited source-event seqs against prior log entries and the replacement range. */
701
1753
  function assertProvenance(event, shadowedSeqs) {
702
1754
  const raw = event.sourceEventSeqs;
1755
+ if (event.type === "assistant/message" && raw !== void 0) throw new Error("assistant/message embeds its source stream and cannot carry sourceEventSeqs");
703
1756
  const sources = /* @__PURE__ */ new Set();
704
1757
  if (raw !== void 0) {
705
1758
  if (!Array.isArray(raw)) throw new Error(`sourceEventSeqs on event at seq ${event.seq} must be an array when present`);
706
- if (raw.length === 0 && event.type !== "assistant/message") throw new Error("sourceEventSeqs must not be empty except on assistant/message");
1759
+ if (raw.length === 0) throw new Error("sourceEventSeqs must not be empty");
707
1760
  let nonEarlierSource;
708
1761
  for (const source of raw) {
709
1762
  if (!isEventSeq(source)) throw new Error(`session event "${event.type}" sourceEventSeqs must densely contain non-negative safe integers`);
@@ -1173,6 +2226,43 @@ window.__ModuleLoader__.load({
1173
2226
  cacheWriteTokens: turn % 10 === 0 ? 4 : 0
1174
2227
  };
1175
2228
  }
2229
+ /** Build a lossless settled stream for static fixture messages. */
2230
+ function fixtureSettledStream(message, usage, time) {
2231
+ const stream = [];
2232
+ for (const [index, block] of message.content.entries()) stream.push({
2233
+ type: "chunk",
2234
+ time,
2235
+ chunk: {
2236
+ type: "block-start",
2237
+ index,
2238
+ blockType: block.type
2239
+ }
2240
+ }, {
2241
+ type: "chunk",
2242
+ time,
2243
+ chunk: {
2244
+ type: "block-end",
2245
+ index,
2246
+ block
2247
+ }
2248
+ });
2249
+ stream.push({
2250
+ type: "chunk",
2251
+ time,
2252
+ chunk: {
2253
+ type: "usage",
2254
+ usage
2255
+ }
2256
+ }, {
2257
+ type: "chunk",
2258
+ time,
2259
+ chunk: {
2260
+ type: "finish",
2261
+ reason: { kind: "stop" }
2262
+ }
2263
+ });
2264
+ return stream;
2265
+ }
1176
2266
  /** fx-alpha history script: 75 turns (~150+ messages -> 4 pages at PAGE_MESSAGES=50),
1177
2267
  * mixing reasoning blocks / tool call+result / context. */
1178
2268
  function buildAlphaLog() {
@@ -1181,16 +2271,18 @@ window.__ModuleLoader__.load({
1181
2271
  const push = (e) => {
1182
2272
  const seq = events.length;
1183
2273
  const data = e["data"];
2274
+ const nextTime = time + 800;
1184
2275
  const authored = e["type"] === "assistant/message" && data !== void 0 ? {
1185
2276
  ...e,
1186
2277
  data: {
1187
2278
  ...data,
1188
- usage: fixtureUsage(data["turn"], data["step"])
2279
+ usage: fixtureUsage(data["turn"], data["step"]),
2280
+ stream: fixtureSettledStream(data["message"], fixtureUsage(data["turn"], data["step"]), nextTime)
1189
2281
  }
1190
2282
  } : e;
1191
2283
  events.push({
1192
2284
  seq,
1193
- time: time += 800,
2285
+ time: time = nextTime,
1194
2286
  ...authored
1195
2287
  });
1196
2288
  return seq;
@@ -1762,11 +2854,12 @@ window.__ModuleLoader__.load({
1762
2854
  }
1763
2855
  /** Read one provider usage sample from either durable carrier. */
1764
2856
  function usageSampleOf(event) {
1765
- const item = event;
1766
- const usage = item.type === "assistant/chunk" && item.data.chunk?.type === "usage" ? item.data.chunk.usage : item.type === "assistant/message" ? item.data.usage : void 0;
1767
- return usage === void 0 || item.data.turn === void 0 || item.data.step === void 0 ? void 0 : {
1768
- turn: item.data.turn,
1769
- step: item.data.step,
2857
+ if (event.type !== "assistant/message" && event.type !== "assistant/attempt") return void 0;
2858
+ let usage = event.type === "assistant/message" ? event.data.usage : void 0;
2859
+ for (const member of expandAssistantStream(event.data.stream)) if (member.chunk.type === "usage") usage = member.chunk.usage;
2860
+ return usage === void 0 ? void 0 : {
2861
+ turn: event.data.turn,
2862
+ step: event.data.step,
1770
2863
  usage
1771
2864
  };
1772
2865
  }
@@ -1825,11 +2918,16 @@ window.__ModuleLoader__.load({
1825
2918
  firstTokenTime: null
1826
2919
  };
1827
2920
  break;
1828
- case "assistant/chunk":
1829
- if (openStep !== null && openStep.turn === event.data.turn && openStep.step === event.data.step && openStep.firstTokenTime === null && isFixtureTokenDelta(event.data.chunk)) openStep.firstTokenTime = event.time;
2921
+ case "assistant/attempt": {
2922
+ if (openStep === null || openStep.turn !== event.data.turn || openStep.step !== event.data.step) break;
2923
+ const first = expandAssistantStream(event.data.stream).find((member) => isFixtureTokenDelta(member.chunk))?.time;
2924
+ if (openStep.firstTokenTime === null && first !== void 0) openStep.firstTokenTime = first;
1830
2925
  break;
1831
- case "assistant/message":
2926
+ }
2927
+ case "assistant/message": {
1832
2928
  if (openStep === null || openStep.turn !== event.data.turn || openStep.step !== event.data.step) break;
2929
+ const first = expandAssistantStream(event.data.stream).find((member) => isFixtureTokenDelta(member.chunk))?.time;
2930
+ if (openStep.firstTokenTime === null && first !== void 0) openStep.firstTokenTime = first;
1833
2931
  value.llmMs += Math.max(0, event.time - openStep.startTime);
1834
2932
  if (openStep.firstTokenTime !== null) {
1835
2933
  value.ttftMs += Math.max(0, openStep.firstTokenTime - openStep.startTime);
@@ -1842,6 +2940,7 @@ window.__ModuleLoader__.load({
1842
2940
  }
1843
2941
  openStep = null;
1844
2942
  break;
2943
+ }
1845
2944
  case "tool/call":
1846
2945
  pendingCalls.set(event.data.callId, event.time);
1847
2946
  break;
@@ -2086,41 +3185,10 @@ window.__ModuleLoader__.load({
2086
3185
  }
2087
3186
  }
2088
3187
  return {
2089
- records: packChunkRuns(log.slice(start, end)).map((record) => {
2090
- if (!isChunkRow(record)) return {
2091
- type: "event",
2092
- event: record
2093
- };
2094
- switch (record.type) {
2095
- case "text-chunks": return {
2096
- type: "chunks",
2097
- event: {
2098
- type: "chunkrow/text-chunks",
2099
- seq: record.seq0,
2100
- time: record.time0,
2101
- data: record.data
2102
- }
2103
- };
2104
- case "reasoning-chunks": return {
2105
- type: "chunks",
2106
- event: {
2107
- type: "chunkrow/reasoning-chunks",
2108
- seq: record.seq0,
2109
- time: record.time0,
2110
- data: record.data
2111
- }
2112
- };
2113
- case "tool-call-chunks": return {
2114
- type: "chunks",
2115
- event: {
2116
- type: "chunkrow/tool-call-chunks",
2117
- seq: record.seq0,
2118
- time: record.time0,
2119
- data: record.data
2120
- }
2121
- };
2122
- }
2123
- }),
3188
+ records: log.slice(start, end).map((event) => ({
3189
+ type: "event",
3190
+ event
3191
+ })),
2124
3192
  hasMore: start > 0
2125
3193
  };
2126
3194
  }
@@ -2597,6 +3665,8 @@ window.__ModuleLoader__.load({
2597
3665
  ];
2598
3666
  const controlConns = /* @__PURE__ */ new Set();
2599
3667
  const followConns = /* @__PURE__ */ new Map();
3668
+ const activeAttempts = /* @__PURE__ */ new Map();
3669
+ const assistantRevisions = /* @__PURE__ */ new Map();
2600
3670
  const workspaceConns = /* @__PURE__ */ new Set();
2601
3671
  const remoteEventConns = /* @__PURE__ */ new Map();
2602
3672
  const emitControl = (frame) => {
@@ -2618,6 +3688,72 @@ window.__ModuleLoader__.load({
2618
3688
  const emitFollow = (sessionId, entry) => {
2619
3689
  for (const conn of followConns.get(sessionId) ?? []) conn.push(entry);
2620
3690
  };
3691
+ const emitAssistant = (sessionId, frame) => {
3692
+ for (const conn of followConns.get(sessionId) ?? []) conn.push({
3693
+ type: "assistant-stream",
3694
+ frame
3695
+ });
3696
+ };
3697
+ const nextAssistantRevision = (sessionId) => {
3698
+ const revision = (assistantRevisions.get(sessionId) ?? 0) + 1;
3699
+ assistantRevisions.set(sessionId, revision);
3700
+ return revision;
3701
+ };
3702
+ const beginAssistant = (sessionId, turn, step) => {
3703
+ const attemptId = LlmAttemptId(`${sessionId}:fixture:${String(nextAssistantRevision(sessionId))}`);
3704
+ const lastSeq = logOf(sessionId).length - 1;
3705
+ const startedAfterSeq = lastSeq < 0 ? -1 : SessionSeq(lastSeq);
3706
+ const attempt = {
3707
+ attemptId,
3708
+ startedAfterSeq,
3709
+ turn,
3710
+ step,
3711
+ stream: new AssistantStreamAccumulator(),
3712
+ index: 0
3713
+ };
3714
+ activeAttempts.set(sessionId, attempt);
3715
+ emitAssistant(sessionId, {
3716
+ type: "start",
3717
+ attemptId,
3718
+ revision: assistantRevisions.get(sessionId),
3719
+ startedAfterSeq,
3720
+ turn,
3721
+ step
3722
+ });
3723
+ return attempt;
3724
+ };
3725
+ const pushAssistant = (sessionId, chunk) => {
3726
+ const attempt = activeAttempts.get(sessionId);
3727
+ if (attempt === void 0) throw new Error(`fixture: no active Assistant attempt for ${sessionId}`);
3728
+ const timed = attempt.stream.push({
3729
+ time: Date.now(),
3730
+ chunk
3731
+ });
3732
+ emitAssistant(sessionId, {
3733
+ type: "chunk",
3734
+ attemptId: attempt.attemptId,
3735
+ revision: nextAssistantRevision(sessionId),
3736
+ index: attempt.index++,
3737
+ time: timed.time,
3738
+ chunk: timed.chunk
3739
+ });
3740
+ };
3741
+ const commitAssistant = (sessionId, event) => {
3742
+ const attempt = activeAttempts.get(sessionId);
3743
+ if (attempt === void 0) throw new Error(`fixture: no active Assistant attempt for ${sessionId}`);
3744
+ activeAttempts.delete(sessionId);
3745
+ emitAssistant(sessionId, {
3746
+ type: "end",
3747
+ attemptId: attempt.attemptId,
3748
+ revision: nextAssistantRevision(sessionId),
3749
+ index: attempt.index,
3750
+ outcome: {
3751
+ kind: "committed",
3752
+ eventType: event.type === "assistant/message" ? "assistant/message" : "assistant/attempt",
3753
+ seq: event.seq
3754
+ }
3755
+ });
3756
+ };
2621
3757
  function sessionOk(value) {
2622
3758
  return Promise.resolve({
2623
3759
  ok: true,
@@ -2671,6 +3807,7 @@ window.__ModuleLoader__.load({
2671
3807
  if (summary !== void 0) summary.updatedAt = event.time;
2672
3808
  emitRemote("api-session/activity", [id, event.time]);
2673
3809
  }
3810
+ return event;
2674
3811
  };
2675
3812
  /** Append one durable goal/change (host GoalService parallel). */
2676
3813
  const appendGoalChange = (id, change) => {
@@ -2719,7 +3856,7 @@ window.__ModuleLoader__.load({
2719
3856
  description: "set or view the goal for a long-running task",
2720
3857
  input: {
2721
3858
  hint: "<objective>",
2722
- images: true
3859
+ attachments: true
2723
3860
  }
2724
3861
  },
2725
3862
  {
@@ -2732,26 +3869,26 @@ window.__ModuleLoader__.load({
2732
3869
  description: "Enter or leave plan mode",
2733
3870
  input: {
2734
3871
  hint: "[off|message]",
2735
- images: true
3872
+ attachments: true
2736
3873
  }
2737
3874
  }
2738
3875
  ]
2739
3876
  };
2740
3877
  },
2741
- execute(id, line, images = []) {
3878
+ execute(id, line, attachments = []) {
2742
3879
  const missing = requireGoalSession(id);
2743
3880
  if (missing !== void 0) return missing;
2744
3881
  const match = /^\/(\S+)((?:\s.*)?)$/.exec(line.trim());
2745
3882
  const name = match?.[1];
2746
3883
  const args = match?.[2] ?? "";
2747
- if (images.length > 0 && name !== void 0 && [
3884
+ if (attachments.length > 0 && name !== void 0 && [
2748
3885
  "permission",
2749
3886
  "goal",
2750
3887
  "compact",
2751
3888
  "echo",
2752
3889
  "plan"
2753
3890
  ].includes(name)) {
2754
- const rejection = name !== "goal" && name !== "plan" ? `/${name} does not accept image attachments` : name === "goal" && args.trim() === "" ? "Image attachments only accompany a goal objective: /goal <objective> or /goal edit <objective>." : name === "plan" && args.trim() === "off" ? "Image attachments cannot accompany /plan off." : void 0;
3891
+ const rejection = name !== "goal" && name !== "plan" ? `/${name} does not accept attachments` : name === "goal" && args.trim() === "" ? "Attachments only accompany a goal objective: /goal <objective> or /goal edit <objective>." : name === "plan" && args.trim() === "off" ? "Attachments cannot accompany /plan off." : void 0;
2755
3892
  if (rejection !== void 0) {
2756
3893
  const commandId = `fx-cmd-${logOf(id).length}`;
2757
3894
  append(id, {
@@ -3352,38 +4489,22 @@ window.__ModuleLoader__.load({
3352
4489
  step: 0
3353
4490
  }
3354
4491
  });
3355
- append(sessionId, {
3356
- type: "assistant/chunk",
3357
- data: {
3358
- turn,
3359
- step: 0,
3360
- chunk: {
3361
- type: "block-start",
3362
- index: 0,
3363
- blockType: "reasoning"
3364
- }
3365
- }
4492
+ beginAssistant(sessionId, turn, 0);
4493
+ pushAssistant(sessionId, {
4494
+ type: "block-start",
4495
+ index: 0,
4496
+ blockType: "reasoning"
3366
4497
  });
3367
4498
  const startedAt = Date.now();
3368
4499
  const pump = () => {
3369
4500
  const elapsedIntervals = Math.floor((Date.now() - startedAt) / intervalMs) + 1;
3370
4501
  const due = Math.max(state.emitted + chunksPerInterval, elapsedIntervals * chunksPerInterval);
3371
4502
  const end = Math.min(due, chunkCount);
3372
- for (let index = state.emitted; index < end; index++) {
3373
- const chunkText = index === chunkCount - 1 ? `\n${marker}` : index % 64 === 63 ? "推理\n" : "推理";
3374
- append(sessionId, {
3375
- type: "assistant/chunk",
3376
- data: {
3377
- turn,
3378
- step: 0,
3379
- chunk: {
3380
- type: "reasoning-delta",
3381
- index: 0,
3382
- text: chunkText
3383
- }
3384
- }
3385
- });
3386
- }
4503
+ for (let index = state.emitted; index < end; index++) pushAssistant(sessionId, {
4504
+ type: "reasoning-delta",
4505
+ index: 0,
4506
+ text: index === chunkCount - 1 ? `\n${marker}` : index % 64 === 63 ? "推理\n" : "推理"
4507
+ });
3387
4508
  state.emitted = end;
3388
4509
  if (end < chunkCount) setTimeout(pump, intervalMs);
3389
4510
  else state.emitting = false;
@@ -3424,29 +4545,16 @@ window.__ModuleLoader__.load({
3424
4545
  step: 1
3425
4546
  }
3426
4547
  });
3427
- append(sessionId, {
3428
- type: "assistant/chunk",
3429
- data: {
3430
- turn,
3431
- step: 1,
3432
- chunk: {
3433
- type: "block-start",
3434
- index: 0,
3435
- blockType: "text"
3436
- }
3437
- }
4548
+ beginAssistant(sessionId, turn, 1);
4549
+ pushAssistant(sessionId, {
4550
+ type: "block-start",
4551
+ index: 0,
4552
+ blockType: "text"
3438
4553
  });
3439
- append(sessionId, {
3440
- type: "assistant/chunk",
3441
- data: {
3442
- turn,
3443
- step: 1,
3444
- chunk: {
3445
- type: "text-delta",
3446
- index: 0,
3447
- text: "应撤回的半截回复"
3448
- }
3449
- }
4554
+ pushAssistant(sessionId, {
4555
+ type: "text-delta",
4556
+ index: 0,
4557
+ text: "应撤回的半截回复"
3450
4558
  });
3451
4559
  },
3452
4560
  /** Record one retry decision; the next attempt remains in the same step. */
@@ -3455,32 +4563,39 @@ window.__ModuleLoader__.load({
3455
4563
  const scenario = retryScenarios.get(sessionId);
3456
4564
  if (scenario === void 0) throw new Error(`fixture: no model retry scenario for ${id}`);
3457
4565
  if (!scenario.stepStarted) {
3458
- append(sessionId, {
3459
- type: "assistant/chunk",
3460
- data: {
3461
- turn: scenario.turn,
3462
- step: 1,
3463
- chunk: {
3464
- type: "block-start",
3465
- index: 0,
3466
- blockType: "text"
3467
- }
3468
- }
4566
+ beginAssistant(sessionId, scenario.turn, 1);
4567
+ pushAssistant(sessionId, {
4568
+ type: "block-start",
4569
+ index: 0,
4570
+ blockType: "text"
3469
4571
  });
3470
- append(sessionId, {
3471
- type: "assistant/chunk",
3472
- data: {
3473
- turn: scenario.turn,
3474
- step: 1,
3475
- chunk: {
3476
- type: "text-delta",
3477
- index: 0,
3478
- text: `第 ${String(retry)} 次应撤回的回复`
3479
- }
3480
- }
4572
+ pushAssistant(sessionId, {
4573
+ type: "text-delta",
4574
+ index: 0,
4575
+ text: `第 ${String(retry)} 次应撤回的回复`
3481
4576
  });
3482
4577
  scenario.stepStarted = true;
3483
4578
  }
4579
+ const failure = {
4580
+ code: "TRANSPORT",
4581
+ message: "连接被重置"
4582
+ };
4583
+ pushAssistant(sessionId, {
4584
+ type: "finish",
4585
+ reason: {
4586
+ kind: "error",
4587
+ failure
4588
+ }
4589
+ });
4590
+ const attempt = activeAttempts.get(sessionId);
4591
+ commitAssistant(sessionId, append(sessionId, {
4592
+ type: "assistant/attempt",
4593
+ data: {
4594
+ turn: scenario.turn,
4595
+ step: 1,
4596
+ stream: attempt.stream.snapshot()
4597
+ }
4598
+ }));
3484
4599
  append(sessionId, {
3485
4600
  type: "llm/retry",
3486
4601
  data: {
@@ -3492,10 +4607,7 @@ window.__ModuleLoader__.load({
3492
4607
  retry,
3493
4608
  maxRetries: 2,
3494
4609
  delayMs,
3495
- failure: {
3496
- code: "TRANSPORT",
3497
- message: "连接被重置"
3498
- }
4610
+ failure
3499
4611
  }
3500
4612
  });
3501
4613
  scenario.stepStarted = false;
@@ -3505,6 +4617,28 @@ window.__ModuleLoader__.load({
3505
4617
  const sessionId = sid(id);
3506
4618
  const scenario = retryScenarios.get(sessionId);
3507
4619
  if (scenario === void 0) throw new Error(`fixture: no model retry scenario for ${id}`);
4620
+ const failure = {
4621
+ code: "TRANSPORT",
4622
+ message: "连接被重置"
4623
+ };
4624
+ const active = activeAttempts.get(sessionId);
4625
+ if (active !== void 0) {
4626
+ pushAssistant(sessionId, {
4627
+ type: "finish",
4628
+ reason: {
4629
+ kind: "error",
4630
+ failure
4631
+ }
4632
+ });
4633
+ commitAssistant(sessionId, append(sessionId, {
4634
+ type: "assistant/attempt",
4635
+ data: {
4636
+ turn: scenario.turn,
4637
+ step: 1,
4638
+ stream: active.stream.snapshot()
4639
+ }
4640
+ }));
4641
+ }
3508
4642
  append(sessionId, {
3509
4643
  type: "llm/retry",
3510
4644
  data: {
@@ -3516,10 +4650,7 @@ window.__ModuleLoader__.load({
3516
4650
  retry: 1,
3517
4651
  maxRetries: 2,
3518
4652
  delayMs,
3519
- failure: {
3520
- code: "TRANSPORT",
3521
- message: "连接被重置"
3522
- }
4653
+ failure
3523
4654
  }
3524
4655
  });
3525
4656
  append(sessionId, {
@@ -3548,27 +4679,41 @@ window.__ModuleLoader__.load({
3548
4679
  const scenario = retryScenarios.get(sessionId);
3549
4680
  if (scenario === void 0) throw new Error(`fixture: no model retry scenario for ${id}`);
3550
4681
  retryScenarios.delete(sessionId);
3551
- append(sessionId, {
3552
- type: "assistant/chunk",
3553
- data: {
3554
- turn: scenario.turn,
3555
- step: 1,
3556
- chunk: {
3557
- type: "block-start",
3558
- index: 0,
3559
- blockType: "text"
3560
- }
4682
+ const completed = "重试后的完整回复";
4683
+ beginAssistant(sessionId, scenario.turn, 1);
4684
+ pushAssistant(sessionId, {
4685
+ type: "block-start",
4686
+ index: 0,
4687
+ blockType: "text"
4688
+ });
4689
+ pushAssistant(sessionId, {
4690
+ type: "text-delta",
4691
+ index: 0,
4692
+ text: completed
4693
+ });
4694
+ pushAssistant(sessionId, {
4695
+ type: "block-end",
4696
+ index: 0,
4697
+ block: {
4698
+ type: "text",
4699
+ text: completed
3561
4700
  }
3562
4701
  });
3563
- append(sessionId, {
4702
+ pushAssistant(sessionId, {
4703
+ type: "finish",
4704
+ reason: { kind: "stop" }
4705
+ });
4706
+ const attempt = activeAttempts.get(sessionId);
4707
+ commitAssistant(sessionId, append(sessionId, {
3564
4708
  type: "assistant/message",
3565
4709
  surfaceOp: "append",
3566
4710
  data: {
3567
4711
  turn: scenario.turn,
3568
4712
  step: 1,
3569
- message: assistantMessage(text("重试后的完整回复"))
4713
+ message: assistantMessage(text(completed)),
4714
+ stream: attempt.stream.snapshot()
3570
4715
  }
3571
- });
4716
+ }));
3572
4717
  append(sessionId, {
3573
4718
  type: "step/end",
3574
4719
  data: {
@@ -3611,17 +4756,11 @@ window.__ModuleLoader__.load({
3611
4756
  step
3612
4757
  }
3613
4758
  });
3614
- append(id, {
3615
- type: "assistant/chunk",
3616
- data: {
3617
- turn,
3618
- step,
3619
- chunk: {
3620
- type: "block-start",
3621
- index: 0,
3622
- blockType: "text"
3623
- }
3624
- }
4759
+ beginAssistant(id, turn, step);
4760
+ pushAssistant(id, {
4761
+ type: "block-start",
4762
+ index: 0,
4763
+ blockType: "text"
3625
4764
  });
3626
4765
  /* v8 ignore next -- the ?? arm needs a null match, but every fixture reply is non-empty. */
3627
4766
  const pieces = replyText.match(/[\s\S]{1,6}/gu) ?? [replyText];
@@ -3629,31 +4768,35 @@ window.__ModuleLoader__.load({
3629
4768
  const finish = (aborted) => {
3630
4769
  replays.delete(id);
3631
4770
  const done = pieces.slice(0, i).join("");
3632
- append(id, {
3633
- type: "assistant/chunk",
3634
- data: {
3635
- turn,
3636
- step,
3637
- chunk: {
3638
- type: "block-end",
3639
- index: 0,
3640
- block: {
3641
- type: "text",
3642
- text: done
3643
- }
3644
- }
4771
+ pushAssistant(id, {
4772
+ type: "block-end",
4773
+ index: 0,
4774
+ block: {
4775
+ type: "text",
4776
+ text: done
3645
4777
  }
3646
4778
  });
3647
- append(id, {
4779
+ pushAssistant(id, {
4780
+ type: "usage",
4781
+ usage: fixtureUsage(turn, step)
4782
+ });
4783
+ if (!aborted) pushAssistant(id, {
4784
+ type: "finish",
4785
+ reason: { kind: "stop" }
4786
+ });
4787
+ const attempt = activeAttempts.get(id);
4788
+ commitAssistant(id, append(id, {
3648
4789
  type: "assistant/message",
3649
4790
  surfaceOp: "append",
3650
4791
  data: {
3651
4792
  turn,
3652
4793
  step,
3653
- message: assistantMessage(text(aborted ? `${done}(已中断)` : done)),
3654
- usage: fixtureUsage(turn, step)
4794
+ message: assistantMessage(text(done)),
4795
+ stream: attempt.stream.snapshot(),
4796
+ usage: fixtureUsage(turn, step),
4797
+ ...aborted ? { interrupted: true } : {}
3655
4798
  }
3656
- });
4799
+ }));
3657
4800
  append(id, {
3658
4801
  type: "step/end",
3659
4802
  data: {
@@ -3665,7 +4808,10 @@ window.__ModuleLoader__.load({
3665
4808
  type: "turn/end",
3666
4809
  data: {
3667
4810
  turn,
3668
- reason: { kind: aborted ? "cancelled" : "completed" }
4811
+ reason: aborted ? {
4812
+ kind: "aborted",
4813
+ reason: { kind: "user" }
4814
+ } : { kind: "completed" }
3669
4815
  }
3670
4816
  });
3671
4817
  setRunning(id, false);
@@ -3677,17 +4823,10 @@ window.__ModuleLoader__.load({
3677
4823
  return;
3678
4824
  }
3679
4825
  i++;
3680
- append(id, {
3681
- type: "assistant/chunk",
3682
- data: {
3683
- turn,
3684
- step,
3685
- chunk: {
3686
- type: "text-delta",
3687
- index: 0,
3688
- text: piece
3689
- }
3690
- }
4826
+ pushAssistant(id, {
4827
+ type: "text-delta",
4828
+ index: 0,
4829
+ text: piece
3691
4830
  });
3692
4831
  replays.set(id, {
3693
4832
  timer: setTimeout(tick, 80),
@@ -4152,11 +5291,12 @@ window.__ModuleLoader__.load({
4152
5291
  yield {
4153
5292
  type: "snapshot",
4154
5293
  header: {
4155
- version: 0,
5294
+ version: 2,
4156
5295
  id: sessionId,
4157
5296
  createdAt: summary.updatedAt,
4158
5297
  ...summary.cwd === void 0 ? {} : { cwd: summary.cwd },
4159
5298
  ...summary.parentSessionId === void 0 ? {} : { parentSession: summary.parentSessionId },
5299
+ isSeeded: summary.parentSessionId !== void 0,
4160
5300
  ...summary.origin === void 0 ? {} : { origin: summary.origin },
4161
5301
  ...summary.agentPreset === void 0 ? {} : { agentPreset: summary.agentPreset }
4162
5302
  },
@@ -4166,9 +5306,24 @@ window.__ModuleLoader__.load({
4166
5306
  projections: {
4167
5307
  asOfSeq: cursor,
4168
5308
  values: projectionValuesOf(snapshot)
4169
- }
5309
+ },
5310
+ ...request.assistantStream === true ? { assistantStream: {
5311
+ revision: assistantRevisions.get(sessionId) ?? 0,
5312
+ ...activeAttempts.get(sessionId) === void 0 ? {} : { activeAttempt: {
5313
+ attemptId: activeAttempts.get(sessionId).attemptId,
5314
+ startedAfterSeq: activeAttempts.get(sessionId).startedAfterSeq,
5315
+ turn: activeAttempts.get(sessionId).turn,
5316
+ step: activeAttempts.get(sessionId).step,
5317
+ nextIndex: activeAttempts.get(sessionId).index,
5318
+ stream: activeAttempts.get(sessionId).stream.snapshot()
5319
+ } }
5320
+ } } : {}
4170
5321
  };
4171
5322
  for await (const frame of conn.drain(signal)) {
5323
+ if (frame.type === "assistant-stream") {
5324
+ yield frame;
5325
+ continue;
5326
+ }
4172
5327
  if (frame.event.seq < nextSeq) continue;
4173
5328
  if (frame.event.seq !== nextSeq) throw new Error(`fixture: session event stream skipped seq ${String(nextSeq)}`);
4174
5329
  nextSeq++;
@@ -4710,13 +5865,14 @@ window.__ModuleLoader__.load({
4710
5865
  };
4711
5866
  }
4712
5867
  /**
4713
- * Client plugin body: pick the api by page mode and provide ctx.connection.
5868
+ * Client plugin body: pick physical carriers by page mode and provide ctx.connection.
4714
5869
  * @param ctx - client cordis context.
4715
5870
  */
4716
5871
  function apply(ctx) {
4717
5872
  const pageLocation = typeof location === "undefined" ? void 0 : location;
4718
5873
  const fixtureRpc = pageLocation !== void 0 && new URLSearchParams(pageLocation.search).has("fixture") ? createFixtureConnectionRpc() : void 0;
4719
5874
  const transport = globalThis.__DSH_TRANSPORT__;
5875
+ const recovery = resolveConnectionConfig(globalThis.__DSH_CONNECTION_RECOVERY__);
4720
5876
  const rpc = fixtureRpc ?? createWebConnectionRpc(transport?.fetch, transport?.openStream);
4721
5877
  let generationSource;
4722
5878
  let owner;
@@ -4808,7 +5964,10 @@ window.__ModuleLoader__.load({
4808
5964
  publishState(state);
4809
5965
  sinks.onStateChange?.(state);
4810
5966
  }
4811
- }, config ?? {});
5967
+ }, {
5968
+ ...recovery,
5969
+ ...config
5970
+ });
4812
5971
  const current = {
4813
5972
  token,
4814
5973
  source,