@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/README.i18n.yaml +2 -2
- package/README.md +9 -4
- package/README.zh.md +9 -4
- package/lib/client.js +1562 -403
- package/lib/index.js +122 -58
- package/lib/types/client/connection.d.ts +8 -14
- package/lib/types/client/fixture.d.ts +31 -0
- package/lib/types/client/index.d.ts +8 -12
- package/lib/types/http-bridge.d.ts +4 -12
- package/lib/types/index.d.ts +5 -2
- package/lib/types/recovery-config.d.ts +27 -0
- package/lib/types/rpc.d.ts +14 -1
- package/package.json +13 -12
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
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
|
-
/**
|
|
231
|
-
function waitForReady(ready,
|
|
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
|
-
|
|
236
|
-
|
|
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 ../../
|
|
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
|
-
*
|
|
435
|
-
*
|
|
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
|
-
*
|
|
442
|
-
* `
|
|
443
|
-
*
|
|
444
|
-
*
|
|
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-
|
|
1386
|
+
* @module @deepseek-ai/dsh-llm/brand
|
|
452
1387
|
*/
|
|
453
1388
|
/**
|
|
454
|
-
*
|
|
455
|
-
* @param
|
|
456
|
-
* @returns
|
|
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
|
-
|
|
467
|
-
|
|
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
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
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
|
|
483
|
-
if (
|
|
484
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
return
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
/**
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
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
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
615
|
-
kind = k;
|
|
616
|
-
run = [delta];
|
|
1632
|
+
default: throw new TypeError(`Unsupported Assistant stream record ${JSON.stringify(record.type)}`);
|
|
617
1633
|
}
|
|
618
|
-
|
|
619
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
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/
|
|
1829
|
-
if (openStep
|
|
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
|
-
|
|
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:
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3872
|
+
attachments: true
|
|
2736
3873
|
}
|
|
2737
3874
|
}
|
|
2738
3875
|
]
|
|
2739
3876
|
};
|
|
2740
3877
|
},
|
|
2741
|
-
execute(id, line,
|
|
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 (
|
|
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
|
|
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
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
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
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
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
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
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
|
-
|
|
3440
|
-
type: "
|
|
3441
|
-
|
|
3442
|
-
|
|
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
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
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
|
-
|
|
3471
|
-
type: "
|
|
3472
|
-
|
|
3473
|
-
|
|
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
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
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
|
-
|
|
3633
|
-
type: "
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
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
|
-
|
|
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(
|
|
3654
|
-
|
|
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:
|
|
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
|
-
|
|
3681
|
-
type: "
|
|
3682
|
-
|
|
3683
|
-
|
|
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:
|
|
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
|
|
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
|
-
},
|
|
5967
|
+
}, {
|
|
5968
|
+
...recovery,
|
|
5969
|
+
...config
|
|
5970
|
+
});
|
|
4812
5971
|
const current = {
|
|
4813
5972
|
token,
|
|
4814
5973
|
source,
|