@deepseek-ai/dsh-api-session-controller 0.1.2-rc.1 → 0.1.5-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +16 -6
- package/README.zh.md +16 -6
- package/lib/client.js +838 -58
- package/lib/index.js +376 -243
- package/lib/typert.host.js +236 -178
- package/lib/typert.remote-client.js +97 -115
- package/lib/types/agent.js +6 -8
- package/lib/types/assistant-stream.d.ts +26 -0
- package/lib/types/assistant-stream.js +83 -0
- package/lib/types/client/contract/events.d.ts +38 -7
- package/lib/types/client/contract/events.js +21 -0
- package/lib/types/client/contract/session.d.ts +7 -7
- package/lib/types/client/contract/snapshot.d.ts +15 -2
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.js +2 -0
- package/lib/types/client/session-wire-event.d.ts +11 -0
- package/lib/types/client/session-wire-event.js +43 -0
- package/lib/types/client/sessions/assistant-stream.d.ts +51 -0
- package/lib/types/client/sessions/assistant-stream.js +168 -0
- package/lib/types/client/sessions/history-records.d.ts +2 -2
- package/lib/types/client/sessions/history-records.js +3 -8
- package/lib/types/client/sessions/queue-mirror.js +3 -3
- package/lib/types/client/sessions/remotes.d.ts +2 -2
- package/lib/types/client/sessions/session.d.ts +3 -1
- package/lib/types/client/sessions/session.js +63 -15
- package/lib/types/client/transport.d.ts +7 -3
- package/lib/types/client/transport.js +24 -3
- package/lib/types/commands.d.ts +1 -1
- package/lib/types/commands.js +112 -32
- package/lib/types/control.d.ts +0 -1
- package/lib/types/control.js +19 -22
- package/lib/types/history.d.ts +2 -1
- package/lib/types/history.js +66 -37
- package/lib/types/index.d.ts +4 -4
- package/lib/types/index.js +15 -5
- package/lib/types/list.d.ts +2 -9
- package/lib/types/list.js +10 -124
- package/lib/types/media-references.d.ts +16 -0
- package/lib/types/media-references.js +77 -0
- package/lib/types/types.d.ts +85 -35
- package/package.json +71 -58
package/lib/client.js
CHANGED
|
@@ -47,7 +47,7 @@ window.__ModuleLoader__.load({
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* Read the first logical sequence represented by one wire record.
|
|
50
|
-
* @param record - validated
|
|
50
|
+
* @param record - validated Session event.
|
|
51
51
|
* @returns inclusive first Session sequence.
|
|
52
52
|
*/
|
|
53
53
|
function historyRecordFirstSeq(record) {
|
|
@@ -55,13 +55,254 @@ window.__ModuleLoader__.load({
|
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Read the final logical sequence represented by one wire record.
|
|
58
|
-
* @param record - validated
|
|
58
|
+
* @param record - validated Session event.
|
|
59
59
|
* @returns inclusive final Session sequence.
|
|
60
60
|
*/
|
|
61
61
|
function historyRecordLastSeq(record) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
return record.event.seq;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region ../../util/brand/lib/index.js
|
|
66
|
+
/**
|
|
67
|
+
* Apply a compile-time number brand without changing the value.
|
|
68
|
+
* @param value - number admitted by the domain that owns the target brand.
|
|
69
|
+
* @returns the same number with the requested compile-time brand.
|
|
70
|
+
*/
|
|
71
|
+
function brandNumber(value) {
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region ../../core/session/lib/types/types.js
|
|
76
|
+
/**
|
|
77
|
+
* Admit a numeric value as an existing Session event position.
|
|
78
|
+
* @param value - non-negative safe integer admitted by the owning log operation.
|
|
79
|
+
* @returns the same number with the Session-sequence brand.
|
|
80
|
+
*/
|
|
81
|
+
function SessionSeq(value) {
|
|
82
|
+
if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionSeq must be a non-negative safe integer, got ${String(value)}`);
|
|
83
|
+
return brandNumber(value);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Admit a numeric value as a Session log offset.
|
|
87
|
+
* @param value - non-negative safe integer used as a gap or prefix length.
|
|
88
|
+
* @returns the same number with the Session-log-offset brand.
|
|
89
|
+
*/
|
|
90
|
+
function SessionLogOffset(value) {
|
|
91
|
+
if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionLogOffset must be a non-negative safe integer, got ${String(value)}`);
|
|
92
|
+
return brandNumber(value);
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region ../../core/session/lib/types/known-event-types.js
|
|
96
|
+
/**
|
|
97
|
+
* GENERATED by `scripts/gen-persistence-catalog.ts` — do not edit by hand; run
|
|
98
|
+
* `pnpm run gen-persistence-catalog` to regenerate (verified fresh by
|
|
99
|
+
* `pnpm run verify-persistence-catalog`, part of `doc-sync`).
|
|
100
|
+
* @module @deepseek-ai/dsh-session/known-event-types
|
|
101
|
+
*/
|
|
102
|
+
/**
|
|
103
|
+
* Every `SessionEventMap` member declared in this repository — the event
|
|
104
|
+
* vocabulary this build understands. The persistence read path refuses to
|
|
105
|
+
* interpret a log containing a type outside this set unless the event
|
|
106
|
+
* carries the envelope's `ignorable` marker (see `SessionEvent.ignorable`
|
|
107
|
+
* in `./types.ts`): such a log was likely written by a newer harness, and
|
|
108
|
+
* silently skipping a required event would reconstruct a wrong session.
|
|
109
|
+
* Downstream (out-of-repo) plugin events are outside this list by
|
|
110
|
+
* construction. The persisted `SessionEvent.ignorable` marker is the
|
|
111
|
+
* compatibility mechanism; event-name registration was rejected because
|
|
112
|
+
* it does not classify omission safety and would make reads
|
|
113
|
+
* composition-dependent. The rationale is in
|
|
114
|
+
* `.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md`.
|
|
115
|
+
*/
|
|
116
|
+
const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
117
|
+
"agent-preset/selected",
|
|
118
|
+
"agent/inbox/spliced",
|
|
119
|
+
"approval/asked",
|
|
120
|
+
"approval/decided",
|
|
121
|
+
"approval/policy",
|
|
122
|
+
"assistant/attempt",
|
|
123
|
+
"assistant/message",
|
|
124
|
+
"command/done",
|
|
125
|
+
"command/run",
|
|
126
|
+
"compaction/end",
|
|
127
|
+
"compaction/prune",
|
|
128
|
+
"compaction/start",
|
|
129
|
+
"compaction/summary",
|
|
130
|
+
"feedback/message-delete",
|
|
131
|
+
"feedback/message-put",
|
|
132
|
+
"feedback/record",
|
|
133
|
+
"goal/change",
|
|
134
|
+
"hook/invoked",
|
|
135
|
+
"hook/result",
|
|
136
|
+
"llm/retry",
|
|
137
|
+
"llm/retry-started",
|
|
138
|
+
"model/selection",
|
|
139
|
+
"permission/preset",
|
|
140
|
+
"plan/mode",
|
|
141
|
+
"request/context",
|
|
142
|
+
"request/header",
|
|
143
|
+
"sandbox/mode",
|
|
144
|
+
"schedule/change",
|
|
145
|
+
"session-log-deepseek/delivery-accepted",
|
|
146
|
+
"session/end-seed",
|
|
147
|
+
"session/title",
|
|
148
|
+
"session/title-llm-request",
|
|
149
|
+
"step/end",
|
|
150
|
+
"step/start",
|
|
151
|
+
"subagent/descriptor",
|
|
152
|
+
"subagent/model-selection-policy",
|
|
153
|
+
"system/message",
|
|
154
|
+
"team/member",
|
|
155
|
+
"team/message/delivered",
|
|
156
|
+
"team/message/queued",
|
|
157
|
+
"team/task",
|
|
158
|
+
"todo/write",
|
|
159
|
+
"tool-workflow/agent-end",
|
|
160
|
+
"tool-workflow/agent-start",
|
|
161
|
+
"tool-workflow/run-end",
|
|
162
|
+
"tool-workflow/run-start",
|
|
163
|
+
"tool/call",
|
|
164
|
+
"tool/ptc-dispatch",
|
|
165
|
+
"tool/ptc-dispatch-start",
|
|
166
|
+
"tool/result",
|
|
167
|
+
"turn/end",
|
|
168
|
+
"turn/start",
|
|
169
|
+
"user/message",
|
|
170
|
+
"web/deepseek-search-llm-request"
|
|
171
|
+
]);
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region ../../core/session/lib/types/surface.js
|
|
174
|
+
/** Runtime counterpart of the message-producing event union. */
|
|
175
|
+
const SURFACE_EVENT_TYPES = new Set([
|
|
176
|
+
"system/message",
|
|
177
|
+
"user/message",
|
|
178
|
+
"assistant/message",
|
|
179
|
+
"tool/result"
|
|
180
|
+
]);
|
|
181
|
+
/**
|
|
182
|
+
* Whether an event type can join the model-visible surface.
|
|
183
|
+
* @param type - event type to test.
|
|
184
|
+
* @returns true for one of the four message-producing event types.
|
|
185
|
+
*/
|
|
186
|
+
function isSurfaceEligibleType(type) {
|
|
187
|
+
return SURFACE_EVENT_TYPES.has(type);
|
|
188
|
+
}
|
|
189
|
+
/** Whether a payload field is a JSON object rather than an array or scalar. */
|
|
190
|
+
function isRecord(value) {
|
|
191
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Reject noncanonical request-header fields and contradictory tool failure metadata.
|
|
195
|
+
* This does not validate complete event payloads or embedded provider streams.
|
|
196
|
+
* @param event - event whose locally related payload fields are inspected.
|
|
197
|
+
* @param subject - event location to include in validation errors.
|
|
198
|
+
* @throws when request data/header is not an object, optional header fields are empty, or tool failure metadata contradicts its message.
|
|
199
|
+
*/
|
|
200
|
+
function validateSessionEventData(event, subject) {
|
|
201
|
+
const data = event.data;
|
|
202
|
+
if (event.type === "request/header") {
|
|
203
|
+
if (!isRecord(data)) throw new Error(`${subject} data must be an object`);
|
|
204
|
+
const header = data["header"];
|
|
205
|
+
if (!isRecord(header)) throw new Error(`${subject} header must be an object`);
|
|
206
|
+
if (Object.hasOwn(header, "system")) throw new Error(`${subject} must omit header.system; use system/message`);
|
|
207
|
+
if (Array.isArray(header["tools"]) && header["tools"].length === 0) throw new Error(`${subject} must omit empty tools`);
|
|
208
|
+
const defaults = header["adapterDefaults"];
|
|
209
|
+
if (isRecord(defaults) && Object.keys(defaults).length === 0) throw new Error(`${subject} must omit empty adapterDefaults`);
|
|
210
|
+
} else if (event.type === "tool/result") {
|
|
211
|
+
if (!isRecord(data)) throw new Error(`${subject} data must be an object`);
|
|
212
|
+
if (data["error"] === void 0) return;
|
|
213
|
+
const message = data["message"];
|
|
214
|
+
const content = isRecord(message) ? message["content"] : void 0;
|
|
215
|
+
const block = Array.isArray(content) ? content[0] : void 0;
|
|
216
|
+
if (!isRecord(block) || block["isError"] !== true) throw new Error(`${subject} error requires message content[0].isError === true`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** Whether a runtime value is a non-negative safe event sequence. */
|
|
220
|
+
function isEventSeq(value) {
|
|
221
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0);
|
|
222
|
+
}
|
|
223
|
+
/** Whether a runtime value is the exact positional-replacement shape. */
|
|
224
|
+
function isReplaceOp(value) {
|
|
225
|
+
const op = value;
|
|
226
|
+
return Object.keys(op).length === 3 && Object.hasOwn(op, "op") && Object.hasOwn(op, "startSeq") && Object.hasOwn(op, "endSeq") && op["op"] === "replace" && isEventSeq(op["startSeq"]) && isEventSeq(op["endSeq"]);
|
|
227
|
+
}
|
|
228
|
+
/** Validate event-local surface eligibility and return its operation. */
|
|
229
|
+
function surfaceOpOf(event) {
|
|
230
|
+
const raw = event;
|
|
231
|
+
if (!isSurfaceEligibleType(event.type)) {
|
|
232
|
+
if (!KNOWN_SESSION_EVENT_TYPES.has(event.type) && event.ignorable === true) return;
|
|
233
|
+
if (raw.surfaceOp !== void 0) throw new Error(`session event "${event.type}" is not surface-eligible and cannot carry surfaceOp`);
|
|
234
|
+
if (raw.sourceEventSeqs !== void 0) throw new Error(`session event "${event.type}" is not surface-eligible and cannot carry sourceEventSeqs`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const op = raw.surfaceOp;
|
|
238
|
+
if (op === void 0) throw new Error(`session event "${event.type}" is surface-eligible and requires a surfaceOp marker`);
|
|
239
|
+
if (op === "append") return op;
|
|
240
|
+
if (op === null || typeof op !== "object" || Array.isArray(op)) throw new Error(`session event "${event.type}" carries an invalid surfaceOp`);
|
|
241
|
+
if (!isReplaceOp(op)) throw new Error(`session event "${event.type}" carries an invalid replace surfaceOp`);
|
|
242
|
+
return op;
|
|
243
|
+
}
|
|
244
|
+
/** Validate cited source-event seqs against prior log entries and the replacement range. */
|
|
245
|
+
function assertProvenance(event, shadowedSeqs) {
|
|
246
|
+
const raw = event.sourceEventSeqs;
|
|
247
|
+
if (event.type === "assistant/message" && raw !== void 0) throw new Error("assistant/message embeds its source stream and cannot carry sourceEventSeqs");
|
|
248
|
+
const sources = /* @__PURE__ */ new Set();
|
|
249
|
+
if (raw !== void 0) {
|
|
250
|
+
if (!Array.isArray(raw)) throw new Error(`sourceEventSeqs on event at seq ${event.seq} must be an array when present`);
|
|
251
|
+
if (raw.length === 0) throw new Error("sourceEventSeqs must not be empty");
|
|
252
|
+
let nonEarlierSource;
|
|
253
|
+
for (const source of raw) {
|
|
254
|
+
if (!isEventSeq(source)) throw new Error(`session event "${event.type}" sourceEventSeqs must densely contain non-negative safe integers`);
|
|
255
|
+
sources.add(source);
|
|
256
|
+
if (nonEarlierSource === void 0 && source >= event.seq) nonEarlierSource = source;
|
|
257
|
+
}
|
|
258
|
+
if (sources.size !== raw.length) throw new Error("sourceEventSeqs must not contain duplicates");
|
|
259
|
+
if (nonEarlierSource !== void 0) throw new Error(`sourceEventSeqs must reference earlier events: ${nonEarlierSource} >= current seq ${event.seq}`);
|
|
260
|
+
}
|
|
261
|
+
const missing = shadowedSeqs.filter((seq) => !sources.has(seq));
|
|
262
|
+
if (missing.length > 0) throw new Error(`surface replace: sourceEventSeqs must include every shadowed surface node; missing ${missing.join(", ")}`);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Validate one event's surface metadata without checking membership in a log or surface.
|
|
266
|
+
* @param event - event whose marker and source sequence values are inspected.
|
|
267
|
+
* Unknown ignorable records retain opaque metadata and never change the surface.
|
|
268
|
+
* @returns the validated operation, or undefined for a log-only or unknown ignorable event.
|
|
269
|
+
* @throws when metadata violates event-local eligibility, marker, or source-sequence rules.
|
|
270
|
+
*/
|
|
271
|
+
function validateSurfaceMetadata(event) {
|
|
272
|
+
const op = surfaceOpOf(event);
|
|
273
|
+
if (op !== void 0 && op !== "append" && (op.startSeq >= event.seq || op.endSeq >= event.seq)) throw new Error(`surface replace at seq ${event.seq}: startSeq and endSeq must reference earlier events`);
|
|
274
|
+
if (op !== void 0) assertProvenance(event, []);
|
|
275
|
+
return op;
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region lib/types/client/session-wire-event.js
|
|
279
|
+
/** Event-local acceptance for raw Session journal responses; payloads remain owner-defined JSON. */
|
|
280
|
+
/**
|
|
281
|
+
* Reject non-current event envelopes without stripping or normalizing wire fields.
|
|
282
|
+
* Range membership and source existence require the durable log and remain Host-owned.
|
|
283
|
+
* @param value - one event received in a follow frame or history page.
|
|
284
|
+
* @returns nothing after narrowing the accepted event envelope.
|
|
285
|
+
* @throws when the envelope or current event-local metadata is invalid.
|
|
286
|
+
*/
|
|
287
|
+
function assertSessionWireEvent(value) {
|
|
288
|
+
const subject = "session wire event";
|
|
289
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${subject} must be an object`);
|
|
290
|
+
const event = value;
|
|
291
|
+
for (const key of Object.keys(event)) switch (key) {
|
|
292
|
+
case "type":
|
|
293
|
+
case "seq":
|
|
294
|
+
case "time":
|
|
295
|
+
case "data":
|
|
296
|
+
case "ignorable":
|
|
297
|
+
case "surfaceOp":
|
|
298
|
+
case "sourceEventSeqs": break;
|
|
299
|
+
default: throw new Error(`${subject} has unexpected field ${key}`);
|
|
300
|
+
}
|
|
301
|
+
const seq = event["seq"];
|
|
302
|
+
if (typeof event["type"] !== "string" || typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0 || Object.is(seq, -0) || typeof event["time"] !== "number" || !Number.isSafeInteger(event["time"]) || !Object.hasOwn(event, "data") || event["data"] === void 0 || Object.hasOwn(event, "ignorable") && event["ignorable"] !== true) throw new Error(`${subject} has an invalid envelope`);
|
|
303
|
+
const current = event;
|
|
304
|
+
validateSurfaceMetadata(current);
|
|
305
|
+
validateSessionEventData(current, subject);
|
|
65
306
|
}
|
|
66
307
|
//#endregion
|
|
67
308
|
//#region lib/types/types.js
|
|
@@ -80,12 +321,14 @@ window.__ModuleLoader__.load({
|
|
|
80
321
|
...change,
|
|
81
322
|
entries: historyEntries(change.entries)
|
|
82
323
|
};
|
|
83
|
-
case "append":
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
324
|
+
case "append": return {
|
|
325
|
+
type: "append",
|
|
326
|
+
entry: change.entry
|
|
327
|
+
};
|
|
328
|
+
case "notification": return {
|
|
329
|
+
type: "assistant-stream",
|
|
330
|
+
frame: change.notification
|
|
331
|
+
};
|
|
89
332
|
}
|
|
90
333
|
}
|
|
91
334
|
/**
|
|
@@ -138,22 +381,39 @@ window.__ModuleLoader__.load({
|
|
|
138
381
|
}
|
|
139
382
|
/** @inheritdoc */
|
|
140
383
|
async *follow(request, signal) {
|
|
384
|
+
let assistantRevision;
|
|
141
385
|
for await (const frame of this.remote.session.follow({
|
|
142
386
|
address: this.address,
|
|
387
|
+
assistantStream: true,
|
|
143
388
|
...request.maxMessages === void 0 ? {} : { maxMessages: request.maxMessages }
|
|
144
389
|
}, signal)) {
|
|
145
390
|
if (frame.type === "snapshot") {
|
|
391
|
+
for (const record of frame.records) assertSessionWireEvent(record.event);
|
|
392
|
+
if (frame.assistantStream === void 0) throw new RemoteError("gateway/internal", "session assistant stream omitted its opted-in opening baseline", {});
|
|
393
|
+
assistantRevision = frame.assistantStream.revision;
|
|
146
394
|
yield {
|
|
147
395
|
type: "opened",
|
|
148
396
|
cursor: frame.cursor,
|
|
149
397
|
page: {
|
|
150
398
|
records: frame.records,
|
|
151
399
|
hasMore: frame.hasMore,
|
|
152
|
-
projections: frame.projections
|
|
400
|
+
projections: frame.projections,
|
|
401
|
+
assistantStream: frame.assistantStream
|
|
153
402
|
}
|
|
154
403
|
};
|
|
155
404
|
continue;
|
|
156
405
|
}
|
|
406
|
+
if (frame.type === "assistant-stream") {
|
|
407
|
+
const expected = (assistantRevision ?? 0) + 1;
|
|
408
|
+
if (frame.frame.revision !== expected) throw new _deepseek_ai_dsh_api_gateway_client.RemoteStreamCarrierError(`session assistant stream skipped revision ${String(expected)}`);
|
|
409
|
+
assistantRevision = frame.frame.revision;
|
|
410
|
+
yield {
|
|
411
|
+
type: "notification",
|
|
412
|
+
notification: frame.frame
|
|
413
|
+
};
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
assertSessionWireEvent(frame.event);
|
|
157
417
|
yield {
|
|
158
418
|
type: "entry",
|
|
159
419
|
entry: frame
|
|
@@ -168,6 +428,7 @@ window.__ModuleLoader__.load({
|
|
|
168
428
|
...request
|
|
169
429
|
}, signal);
|
|
170
430
|
if (!result.ok) throw result.error;
|
|
431
|
+
for (const record of result.value.records) assertSessionWireEvent(record.event);
|
|
171
432
|
return result.value;
|
|
172
433
|
}
|
|
173
434
|
/** @inheritdoc */
|
|
@@ -176,36 +437,6 @@ window.__ModuleLoader__.load({
|
|
|
176
437
|
}
|
|
177
438
|
};
|
|
178
439
|
//#endregion
|
|
179
|
-
//#region ../../util/brand/lib/index.js
|
|
180
|
-
/**
|
|
181
|
-
* Apply a compile-time number brand without changing the value.
|
|
182
|
-
* @param value - number admitted by the domain that owns the target brand.
|
|
183
|
-
* @returns the same number with the requested compile-time brand.
|
|
184
|
-
*/
|
|
185
|
-
function brandNumber(value) {
|
|
186
|
-
return value;
|
|
187
|
-
}
|
|
188
|
-
//#endregion
|
|
189
|
-
//#region ../../core/session/lib/types/types.js
|
|
190
|
-
/**
|
|
191
|
-
* Admit a numeric value as an existing Session event position.
|
|
192
|
-
* @param value - non-negative safe integer admitted by the owning log operation.
|
|
193
|
-
* @returns the same number with the Session-sequence brand.
|
|
194
|
-
*/
|
|
195
|
-
function SessionSeq(value) {
|
|
196
|
-
if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionSeq must be a non-negative safe integer, got ${String(value)}`);
|
|
197
|
-
return brandNumber(value);
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Admit a numeric value as a Session log offset.
|
|
201
|
-
* @param value - non-negative safe integer used as a gap or prefix length.
|
|
202
|
-
* @returns the same number with the Session-log-offset brand.
|
|
203
|
-
*/
|
|
204
|
-
function SessionLogOffset(value) {
|
|
205
|
-
if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionLogOffset must be a non-negative safe integer, got ${String(value)}`);
|
|
206
|
-
return brandNumber(value);
|
|
207
|
-
}
|
|
208
|
-
//#endregion
|
|
209
440
|
//#region ../../util/workspace-path/lib/index.js
|
|
210
441
|
/**
|
|
211
442
|
* Read the final non-empty segment of a Workspace path for display.
|
|
@@ -679,6 +910,25 @@ window.__ModuleLoader__.load({
|
|
|
679
910
|
entries
|
|
680
911
|
});
|
|
681
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Replace one attempt's transient rows with its committed durable settlement.
|
|
915
|
+
* @param attemptId - process-local attempt whose live rows are now redundant.
|
|
916
|
+
* @param entry - durable settlement committed for that attempt.
|
|
917
|
+
*/
|
|
918
|
+
settleAssistant(attemptId, entry) {
|
|
919
|
+
const entries = materialize(this.window).filter((candidate) => candidate.type !== "transient" || candidate.event.data.attemptId !== attemptId);
|
|
920
|
+
if (entry !== void 0) {
|
|
921
|
+
const index = entries.findIndex((candidate) => candidate.event.seq > entry.event.seq);
|
|
922
|
+
if (index < 0) entries.push(entry);
|
|
923
|
+
else entries.splice(index, 0, entry);
|
|
924
|
+
}
|
|
925
|
+
this.window = leaf(entries);
|
|
926
|
+
this.publish(this.snapshot.hasMore, {
|
|
927
|
+
kind: "settle-assistant",
|
|
928
|
+
attemptId,
|
|
929
|
+
...entry === void 0 ? {} : { entry }
|
|
930
|
+
});
|
|
931
|
+
}
|
|
682
932
|
publish(hasMore, change) {
|
|
683
933
|
this.snapshot = windowSnapshot(this.window, hasMore, this.snapshot.revision + 1, change);
|
|
684
934
|
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.listeners, "[session-controller] event feed");
|
|
@@ -701,7 +951,7 @@ window.__ModuleLoader__.load({
|
|
|
701
951
|
//#region lib/types/client/sessions/queue-mirror.js
|
|
702
952
|
const QUEUE_PREVIEW_CHARS = 200;
|
|
703
953
|
function previewOf(content) {
|
|
704
|
-
const flat = content.filter((block) => block.type !== "image").map((block) => block.type === "text" ? block.text : `[${block.type}]`).join(" ").replace(/\s+/g, " ").trim();
|
|
954
|
+
const flat = content.filter((block) => block.type !== "image" && block.type !== "file").map((block) => block.type === "text" ? block.text : `[${block.type}]`).join(" ").replace(/\s+/g, " ").trim();
|
|
705
955
|
const chars = Array.from(flat);
|
|
706
956
|
return chars.length > QUEUE_PREVIEW_CHARS ? `${chars.slice(0, QUEUE_PREVIEW_CHARS).join("")}…` : flat;
|
|
707
957
|
}
|
|
@@ -752,6 +1002,498 @@ window.__ModuleLoader__.load({
|
|
|
752
1002
|
}
|
|
753
1003
|
};
|
|
754
1004
|
//#endregion
|
|
1005
|
+
//#region ../../util/values/lib/index.js
|
|
1006
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
1007
|
+
function hasIntrinsicConstructor(prototype, name) {
|
|
1008
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
1009
|
+
if (typeof constructor !== "function") return false;
|
|
1010
|
+
try {
|
|
1011
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
1012
|
+
} catch {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
1017
|
+
function isIntrinsicObjectPrototype(value) {
|
|
1018
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
1019
|
+
}
|
|
1020
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
1021
|
+
function hasPlainArrayPrototype(value) {
|
|
1022
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1023
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
1024
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
1025
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
1026
|
+
}
|
|
1027
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
1028
|
+
function hasPlainObjectPrototype(value) {
|
|
1029
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1030
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
1031
|
+
}
|
|
1032
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
1033
|
+
function enumerableStringKeys(value) {
|
|
1034
|
+
const keys = Reflect.ownKeys(value);
|
|
1035
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
1036
|
+
return keys;
|
|
1037
|
+
}
|
|
1038
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
1039
|
+
function walkJsonValue(value, detach) {
|
|
1040
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
1041
|
+
let root;
|
|
1042
|
+
const assign = (destination, item) => {
|
|
1043
|
+
if (destination === void 0) return;
|
|
1044
|
+
if (destination.kind === "root") root = item;
|
|
1045
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
1046
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
1047
|
+
value: item,
|
|
1048
|
+
enumerable: true,
|
|
1049
|
+
configurable: true,
|
|
1050
|
+
writable: true
|
|
1051
|
+
});
|
|
1052
|
+
};
|
|
1053
|
+
const tasks = [{
|
|
1054
|
+
kind: "visit",
|
|
1055
|
+
value,
|
|
1056
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
1057
|
+
}];
|
|
1058
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
1059
|
+
if (task.kind === "leave") {
|
|
1060
|
+
ancestors.delete(task.source);
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
if (task.kind === "array-item") {
|
|
1064
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
1065
|
+
tasks.push({
|
|
1066
|
+
kind: "visit",
|
|
1067
|
+
value: task.source[task.index],
|
|
1068
|
+
...task.target === void 0 ? {} : { destination: {
|
|
1069
|
+
kind: "array",
|
|
1070
|
+
target: task.target,
|
|
1071
|
+
index: task.index
|
|
1072
|
+
} }
|
|
1073
|
+
});
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
if (task.kind === "object-property") {
|
|
1077
|
+
tasks.push({
|
|
1078
|
+
kind: "visit",
|
|
1079
|
+
value: task.source[task.key],
|
|
1080
|
+
...task.target === void 0 ? {} : { destination: {
|
|
1081
|
+
kind: "object",
|
|
1082
|
+
target: task.target,
|
|
1083
|
+
key: task.key
|
|
1084
|
+
} }
|
|
1085
|
+
});
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const current = task.value;
|
|
1089
|
+
if (current === null) {
|
|
1090
|
+
assign(task.destination, null);
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
1094
|
+
assign(task.destination, current);
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (typeof current === "number") {
|
|
1098
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
1099
|
+
assign(task.destination, current);
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
if (typeof current !== "object") return void 0;
|
|
1103
|
+
if (ancestors.has(current)) return void 0;
|
|
1104
|
+
if (Array.isArray(current)) {
|
|
1105
|
+
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
1106
|
+
const length = current.length;
|
|
1107
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
1108
|
+
const target = detach ? [] : void 0;
|
|
1109
|
+
if (target !== void 0) assign(task.destination, target);
|
|
1110
|
+
ancestors.add(current);
|
|
1111
|
+
tasks.push({
|
|
1112
|
+
kind: "leave",
|
|
1113
|
+
source: current
|
|
1114
|
+
});
|
|
1115
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
1116
|
+
kind: "array-item",
|
|
1117
|
+
source: current,
|
|
1118
|
+
index,
|
|
1119
|
+
...target === void 0 ? {} : { target }
|
|
1120
|
+
});
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
1124
|
+
const keys = enumerableStringKeys(current);
|
|
1125
|
+
if (keys === void 0) return void 0;
|
|
1126
|
+
const target = detach ? {} : void 0;
|
|
1127
|
+
if (target !== void 0) assign(task.destination, target);
|
|
1128
|
+
ancestors.add(current);
|
|
1129
|
+
tasks.push({
|
|
1130
|
+
kind: "leave",
|
|
1131
|
+
source: current
|
|
1132
|
+
});
|
|
1133
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
1134
|
+
const key = keys[index];
|
|
1135
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
1136
|
+
if (key === void 0) return void 0;
|
|
1137
|
+
tasks.push({
|
|
1138
|
+
kind: "object-property",
|
|
1139
|
+
source: current,
|
|
1140
|
+
key,
|
|
1141
|
+
...target === void 0 ? {} : { target }
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return detach ? root : true;
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Validate and detach lossless JSON in one read per property.
|
|
1149
|
+
* @param value - candidate value to validate and detach.
|
|
1150
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
1151
|
+
*/
|
|
1152
|
+
function snapshotJsonValue(value) {
|
|
1153
|
+
return walkJsonValue(value, true);
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
1157
|
+
* @param value - value to freeze.
|
|
1158
|
+
* @returns the same value after every reachable enumerable child is frozen.
|
|
1159
|
+
*/
|
|
1160
|
+
function deepFreeze(value) {
|
|
1161
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1162
|
+
const pending = [{
|
|
1163
|
+
kind: "visit",
|
|
1164
|
+
node: value
|
|
1165
|
+
}];
|
|
1166
|
+
while (pending.length > 0) {
|
|
1167
|
+
const task = pending.pop();
|
|
1168
|
+
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
1169
|
+
if (task === void 0) continue;
|
|
1170
|
+
if (task.kind === "property") {
|
|
1171
|
+
pending.push({
|
|
1172
|
+
kind: "visit",
|
|
1173
|
+
node: task.source[task.key]
|
|
1174
|
+
});
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
const node = task.node;
|
|
1178
|
+
if (node === null || typeof node !== "object") continue;
|
|
1179
|
+
if (node instanceof AbortSignal) continue;
|
|
1180
|
+
if (seen.has(node)) continue;
|
|
1181
|
+
seen.add(node);
|
|
1182
|
+
Object.freeze(node);
|
|
1183
|
+
const keys = Object.keys(node);
|
|
1184
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
1185
|
+
const key = keys[index];
|
|
1186
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
1187
|
+
if (key === void 0) continue;
|
|
1188
|
+
pending.push({
|
|
1189
|
+
kind: "property",
|
|
1190
|
+
source: node,
|
|
1191
|
+
key
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return value;
|
|
1196
|
+
}
|
|
1197
|
+
//#endregion
|
|
1198
|
+
//#region ../../llm/llm/lib/types/assistant-stream.js
|
|
1199
|
+
/**
|
|
1200
|
+
* Lossless compact representation of one model-stream attempt, plus record-level
|
|
1201
|
+
* readers that answer common consumer questions without materializing members.
|
|
1202
|
+
* Readers trust the static record type; expandAssistantStream is the validating
|
|
1203
|
+
* path for records read at a durable boundary.
|
|
1204
|
+
*/
|
|
1205
|
+
function safeTime(value) {
|
|
1206
|
+
if (!Number.isSafeInteger(value)) throw new TypeError(`Assistant stream time must be a safe integer, got ${String(value)}`);
|
|
1207
|
+
return value;
|
|
1208
|
+
}
|
|
1209
|
+
function safeIndex(value, label) {
|
|
1210
|
+
if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`${label} index must be a non-negative safe integer`);
|
|
1211
|
+
return value;
|
|
1212
|
+
}
|
|
1213
|
+
function snapshotChunk(chunk) {
|
|
1214
|
+
const snapshot = snapshotJsonValue(chunk);
|
|
1215
|
+
if (snapshot === void 0) throw new TypeError("Assistant stream chunk must be losslessly JSON-serializable");
|
|
1216
|
+
return snapshot;
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Expand compact records into the exact timed chunk sequence.
|
|
1220
|
+
* @param stream - compact records from one durable Assistant settlement.
|
|
1221
|
+
* @returns detached timed chunks with every original delta boundary preserved.
|
|
1222
|
+
* @throws {TypeError} when a record or reconstructed timestamp is invalid.
|
|
1223
|
+
*/
|
|
1224
|
+
function expandAssistantStream(stream) {
|
|
1225
|
+
const chunks = [];
|
|
1226
|
+
for (const candidate of stream) {
|
|
1227
|
+
const record = validateRecord(candidate);
|
|
1228
|
+
if (record.type === "chunk") {
|
|
1229
|
+
chunks.push({
|
|
1230
|
+
time: record.time,
|
|
1231
|
+
chunk: record.chunk
|
|
1232
|
+
});
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
const members = record.type === "tool-call-chunks" ? record.args : record.texts;
|
|
1236
|
+
let time = record.time0;
|
|
1237
|
+
for (let index = 0; index < members.length; index += 1) {
|
|
1238
|
+
if (index > 0) time += record.dt[index - 1];
|
|
1239
|
+
let chunk;
|
|
1240
|
+
if (record.type === "text-chunks") chunk = {
|
|
1241
|
+
type: "text-delta",
|
|
1242
|
+
index: record.index,
|
|
1243
|
+
text: members[index]
|
|
1244
|
+
};
|
|
1245
|
+
else if (record.type === "reasoning-chunks") chunk = {
|
|
1246
|
+
type: "reasoning-delta",
|
|
1247
|
+
index: record.index,
|
|
1248
|
+
text: members[index]
|
|
1249
|
+
};
|
|
1250
|
+
else chunk = {
|
|
1251
|
+
type: "tool-call-delta",
|
|
1252
|
+
index: record.index,
|
|
1253
|
+
id: record.id,
|
|
1254
|
+
...Object.hasOwn(record, "name") ? { name: record.name } : {},
|
|
1255
|
+
argumentsDelta: members[index]
|
|
1256
|
+
};
|
|
1257
|
+
chunks.push({
|
|
1258
|
+
time,
|
|
1259
|
+
chunk
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return chunks;
|
|
1264
|
+
}
|
|
1265
|
+
function validateRecord(value) {
|
|
1266
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Assistant stream record must be an object");
|
|
1267
|
+
const record = value;
|
|
1268
|
+
switch (record.type) {
|
|
1269
|
+
case "text-chunks":
|
|
1270
|
+
case "reasoning-chunks": {
|
|
1271
|
+
exactKeys(record, [
|
|
1272
|
+
"type",
|
|
1273
|
+
"time0",
|
|
1274
|
+
"index",
|
|
1275
|
+
"dt",
|
|
1276
|
+
"texts"
|
|
1277
|
+
], record.type);
|
|
1278
|
+
const texts = stringArray(record.texts, `${record.type} texts`);
|
|
1279
|
+
if (texts.length === 0) throw new TypeError(`${record.type} texts must be non-empty`);
|
|
1280
|
+
validateRun(record, texts.length, record.type);
|
|
1281
|
+
return record;
|
|
1282
|
+
}
|
|
1283
|
+
case "tool-call-chunks": {
|
|
1284
|
+
exactKeys(record, Object.hasOwn(record, "name") ? [
|
|
1285
|
+
"type",
|
|
1286
|
+
"time0",
|
|
1287
|
+
"index",
|
|
1288
|
+
"dt",
|
|
1289
|
+
"id",
|
|
1290
|
+
"name",
|
|
1291
|
+
"args"
|
|
1292
|
+
] : [
|
|
1293
|
+
"type",
|
|
1294
|
+
"time0",
|
|
1295
|
+
"index",
|
|
1296
|
+
"dt",
|
|
1297
|
+
"id",
|
|
1298
|
+
"args"
|
|
1299
|
+
], record.type);
|
|
1300
|
+
const args = stringArray(record.args, "tool-call-chunks args");
|
|
1301
|
+
if (args.length === 0) throw new TypeError("tool-call-chunks args must be non-empty");
|
|
1302
|
+
if (typeof record.id !== "string" || record.id.length === 0) throw new TypeError("tool-call-chunks id must be a non-empty string");
|
|
1303
|
+
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");
|
|
1304
|
+
validateRun(record, args.length, record.type);
|
|
1305
|
+
return record;
|
|
1306
|
+
}
|
|
1307
|
+
case "chunk": {
|
|
1308
|
+
exactKeys(record, [
|
|
1309
|
+
"type",
|
|
1310
|
+
"time",
|
|
1311
|
+
"chunk"
|
|
1312
|
+
], "chunk");
|
|
1313
|
+
const time = safeTime(record.time);
|
|
1314
|
+
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");
|
|
1315
|
+
let chunk;
|
|
1316
|
+
try {
|
|
1317
|
+
chunk = snapshotChunk(record.chunk);
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
throw new TypeError("Assistant stream raw chunk must be a lossless JSON object", { cause: error });
|
|
1320
|
+
}
|
|
1321
|
+
return deepFreeze({
|
|
1322
|
+
type: "chunk",
|
|
1323
|
+
time,
|
|
1324
|
+
chunk
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
default: throw new TypeError(`Unsupported Assistant stream record ${JSON.stringify(record.type)}`);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function validateRun(record, members, label) {
|
|
1331
|
+
safeTime(record.time0);
|
|
1332
|
+
safeIndex(record.index, label);
|
|
1333
|
+
if (!Array.isArray(record.dt) || record.dt.some((value) => !Number.isSafeInteger(value))) throw new TypeError(`${label} dt must contain safe integers`);
|
|
1334
|
+
if (record.dt.length !== members - 1) throw new TypeError(`${label} dt length must be one less than its members`);
|
|
1335
|
+
let time = record.time0;
|
|
1336
|
+
for (const gap of record.dt) {
|
|
1337
|
+
time += gap;
|
|
1338
|
+
if (!Number.isSafeInteger(time)) throw new TypeError(`${label} member times must stay safe integers`);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function stringArray(value, label) {
|
|
1342
|
+
if (!Array.isArray(value) || value.some((member) => typeof member !== "string")) throw new TypeError(`${label} must be a string array`);
|
|
1343
|
+
return value;
|
|
1344
|
+
}
|
|
1345
|
+
function exactKeys(record, keys, label) {
|
|
1346
|
+
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(", ")}`);
|
|
1347
|
+
}
|
|
1348
|
+
//#endregion
|
|
1349
|
+
//#region lib/types/client/sessions/assistant-stream.js
|
|
1350
|
+
/** Web presentation fold joining transient Assistant frames to one durable v2 settlement. */
|
|
1351
|
+
/** Keeps transient Assistant presentation behind one settlement-aware interface. */
|
|
1352
|
+
var ClientAssistantStream = class {
|
|
1353
|
+
activeAttempt;
|
|
1354
|
+
pending = /* @__PURE__ */ new Map();
|
|
1355
|
+
publishedSeqs = /* @__PURE__ */ new Set();
|
|
1356
|
+
durableCursor = -1;
|
|
1357
|
+
transientInGap = 0;
|
|
1358
|
+
/**
|
|
1359
|
+
* Replace the durable Web window and adopt an optional reconnect baseline.
|
|
1360
|
+
* @param entries - durable entries in the replacement window.
|
|
1361
|
+
* @param baseline - compact prefix for an Assistant attempt that is still live.
|
|
1362
|
+
* @returns immediately visible durable entries plus reconstructed transient chunks.
|
|
1363
|
+
*/
|
|
1364
|
+
replace(entries, baseline) {
|
|
1365
|
+
this.pending.clear();
|
|
1366
|
+
this.transientInGap = 0;
|
|
1367
|
+
this.activeAttempt = void 0;
|
|
1368
|
+
const opening = baseline?.activeAttempt;
|
|
1369
|
+
if (opening !== void 0) this.activeAttempt = {
|
|
1370
|
+
attemptId: opening.attemptId,
|
|
1371
|
+
startedAfterSeq: opening.startedAfterSeq,
|
|
1372
|
+
turn: opening.turn,
|
|
1373
|
+
step: opening.step,
|
|
1374
|
+
nextIndex: opening.nextIndex
|
|
1375
|
+
};
|
|
1376
|
+
const visible = [...entries];
|
|
1377
|
+
this.publishedSeqs = new Set(visible.map((entry) => entry.event.seq));
|
|
1378
|
+
this.durableCursor = visible.reduce((cursor, entry) => Math.max(cursor, entry.event.seq), -1);
|
|
1379
|
+
if (opening !== void 0) for (const [index, member] of expandAssistantStream(opening.stream).entries()) {
|
|
1380
|
+
this.transientInGap += 1;
|
|
1381
|
+
visible.push({
|
|
1382
|
+
type: "transient",
|
|
1383
|
+
event: {
|
|
1384
|
+
type: "assistant/live-chunk",
|
|
1385
|
+
seq: this.durableCursor + 1 - 1 / (this.transientInGap + 1),
|
|
1386
|
+
time: member.time,
|
|
1387
|
+
data: {
|
|
1388
|
+
attemptId: opening.attemptId,
|
|
1389
|
+
turn: opening.turn,
|
|
1390
|
+
step: opening.step,
|
|
1391
|
+
chunk: member.chunk
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
if (index + 1 >= opening.nextIndex) break;
|
|
1396
|
+
}
|
|
1397
|
+
return visible;
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Stage one durable v2 settlement while its matching live attempt is open.
|
|
1401
|
+
* @param entry - newly followed durable entry.
|
|
1402
|
+
* @returns a publication decision, or `undefined` when no entry becomes visible.
|
|
1403
|
+
*/
|
|
1404
|
+
acceptDurable(entry) {
|
|
1405
|
+
const event = entry.event;
|
|
1406
|
+
this.durableCursor = Math.max(this.durableCursor, event.seq);
|
|
1407
|
+
this.transientInGap = 0;
|
|
1408
|
+
const settlement = assistantSettlementEntry(entry);
|
|
1409
|
+
if (settlement !== void 0 && this.attemptForSettlement(settlement.event) !== void 0) {
|
|
1410
|
+
if (this.pending.has(event.seq)) return { type: "rebaseline" };
|
|
1411
|
+
this.pending.set(event.seq, settlement);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
return this.publish(entry);
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Fold one dense transient frame and release its named durable settlement.
|
|
1418
|
+
* @param frame - next Assistant stream frame received by the follow connection.
|
|
1419
|
+
* @returns a transient, publication, or rebaseline decision, or `undefined` when no entry becomes visible.
|
|
1420
|
+
*/
|
|
1421
|
+
acceptFrame(frame) {
|
|
1422
|
+
switch (frame.type) {
|
|
1423
|
+
case "start":
|
|
1424
|
+
if (this.activeAttempt !== void 0 || this.pending.size > 0) return { type: "rebaseline" };
|
|
1425
|
+
this.pending.clear();
|
|
1426
|
+
this.activeAttempt = {
|
|
1427
|
+
attemptId: frame.attemptId,
|
|
1428
|
+
startedAfterSeq: frame.startedAfterSeq,
|
|
1429
|
+
turn: frame.turn,
|
|
1430
|
+
step: frame.step,
|
|
1431
|
+
nextIndex: 0
|
|
1432
|
+
};
|
|
1433
|
+
return;
|
|
1434
|
+
case "chunk": {
|
|
1435
|
+
const attempt = this.activeAttempt;
|
|
1436
|
+
if (attempt === void 0 || attempt.attemptId !== frame.attemptId) return void 0;
|
|
1437
|
+
if (frame.index !== attempt.nextIndex) return { type: "rebaseline" };
|
|
1438
|
+
attempt.nextIndex += 1;
|
|
1439
|
+
this.transientInGap += 1;
|
|
1440
|
+
return {
|
|
1441
|
+
type: "transient",
|
|
1442
|
+
entry: {
|
|
1443
|
+
type: "transient",
|
|
1444
|
+
event: {
|
|
1445
|
+
type: "assistant/live-chunk",
|
|
1446
|
+
seq: this.durableCursor + 1 - 1 / (this.transientInGap + 1),
|
|
1447
|
+
time: frame.time,
|
|
1448
|
+
data: {
|
|
1449
|
+
attemptId: frame.attemptId,
|
|
1450
|
+
turn: attempt.turn,
|
|
1451
|
+
step: attempt.step,
|
|
1452
|
+
chunk: frame.chunk
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
case "end": {
|
|
1459
|
+
const attempt = this.activeAttempt;
|
|
1460
|
+
if (attempt === void 0 || attempt.attemptId !== frame.attemptId) return;
|
|
1461
|
+
this.activeAttempt = void 0;
|
|
1462
|
+
if (frame.index !== attempt.nextIndex) return { type: "rebaseline" };
|
|
1463
|
+
if (frame.outcome.kind === "abandoned") return this.pending.size === 0 ? {
|
|
1464
|
+
type: "abandonment",
|
|
1465
|
+
attemptId: attempt.attemptId
|
|
1466
|
+
} : { type: "rebaseline" };
|
|
1467
|
+
if (this.publishedSeqs.has(frame.outcome.seq)) return void 0;
|
|
1468
|
+
const entry = this.pending.get(frame.outcome.seq);
|
|
1469
|
+
if (entry === void 0 || entry.event.type !== frame.outcome.eventType) return { type: "rebaseline" };
|
|
1470
|
+
this.pending.delete(frame.outcome.seq);
|
|
1471
|
+
this.publishedSeqs.add(entry.event.seq);
|
|
1472
|
+
return {
|
|
1473
|
+
type: "settlement",
|
|
1474
|
+
attemptId: attempt.attemptId,
|
|
1475
|
+
entry
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
attemptForSettlement(event) {
|
|
1481
|
+
const attempt = this.activeAttempt;
|
|
1482
|
+
if (attempt === void 0 || event.type === "assistant/message" && event.surfaceOp !== "append" || event.seq <= attempt.startedAfterSeq || attempt.turn !== event.data.turn || attempt.step !== event.data.step) return void 0;
|
|
1483
|
+
return attempt;
|
|
1484
|
+
}
|
|
1485
|
+
publish(entry) {
|
|
1486
|
+
this.publishedSeqs.add(entry.event.seq);
|
|
1487
|
+
return {
|
|
1488
|
+
type: "publish",
|
|
1489
|
+
entry
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
function assistantSettlementEntry(entry) {
|
|
1494
|
+
return entry.event.type === "assistant/message" || entry.event.type === "assistant/attempt" ? entry : void 0;
|
|
1495
|
+
}
|
|
1496
|
+
//#endregion
|
|
755
1497
|
//#region lib/types/client/sessions/session.js
|
|
756
1498
|
function projectionsBaseline(value) {
|
|
757
1499
|
return {
|
|
@@ -784,6 +1526,7 @@ window.__ModuleLoader__.load({
|
|
|
784
1526
|
jumpPromise = null;
|
|
785
1527
|
/** Authoritative stream-only inbox snapshot; pending work never hits history. */
|
|
786
1528
|
queueMirror = new SessionQueueMirror();
|
|
1529
|
+
assistantStream = new ClientAssistantStream();
|
|
787
1530
|
running = false;
|
|
788
1531
|
address;
|
|
789
1532
|
parentAvailable;
|
|
@@ -879,7 +1622,7 @@ window.__ModuleLoader__.load({
|
|
|
879
1622
|
placement: this.running ? input.mode === "steer" ? "steering" : "queued" : "transcript",
|
|
880
1623
|
time: Date.now(),
|
|
881
1624
|
text: input.text,
|
|
882
|
-
|
|
1625
|
+
attachments: input.attachments
|
|
883
1626
|
}];
|
|
884
1627
|
this.submissionSettlements.set(requestId, {
|
|
885
1628
|
onRetire: input.onRetire,
|
|
@@ -896,7 +1639,7 @@ window.__ModuleLoader__.load({
|
|
|
896
1639
|
}
|
|
897
1640
|
/**
|
|
898
1641
|
* Send (queue/steer passed through 1:1); failures land in the snapshot's promptError.
|
|
899
|
-
* @param content - text
|
|
1642
|
+
* @param content - text, browser-owned temporary image uploads, and staged-file receipts.
|
|
900
1643
|
* @param mode - queue appends after the current turn; steer interrupts it.
|
|
901
1644
|
* @param signal - optional caller cancellation for the complete admission round-trip.
|
|
902
1645
|
* @param requestId - identity from {@link beginSubmission}; a failed identified prompt retires its echo.
|
|
@@ -918,13 +1661,19 @@ window.__ModuleLoader__.load({
|
|
|
918
1661
|
content,
|
|
919
1662
|
clientTimeZone
|
|
920
1663
|
}, signal);
|
|
921
|
-
} else {
|
|
1664
|
+
} else if (content.some((part) => part.type === "file")) result = {
|
|
1665
|
+
ok: false,
|
|
1666
|
+
error: new RemoteError("subagent/attachment-invalid", "subagent continuation does not accept files", { reason: "SUBAGENT_FILE_UNSUPPORTED" })
|
|
1667
|
+
};
|
|
1668
|
+
else {
|
|
1669
|
+
const routedContent = content;
|
|
922
1670
|
const routed = await this.remote.subagents.prompt({
|
|
923
1671
|
requestId: randomUUID(),
|
|
924
1672
|
parentSessionId: this.address.parentSessionId,
|
|
925
1673
|
childSessionId: this.address.childSessionId,
|
|
926
1674
|
mode: "continuable",
|
|
927
|
-
|
|
1675
|
+
delivery: mode,
|
|
1676
|
+
content: routedContent,
|
|
928
1677
|
clientTimeZone: resolvedClientTimeZone()
|
|
929
1678
|
}, signal);
|
|
930
1679
|
result = routed.ok ? {
|
|
@@ -1257,24 +2006,53 @@ window.__ModuleLoader__.load({
|
|
|
1257
2006
|
acceptEventChange(change) {
|
|
1258
2007
|
switch (change.type) {
|
|
1259
2008
|
case "replace":
|
|
1260
|
-
this.installWindow(change.entries, change.hasMore, change.page.projections === void 0 ? void 0 : projectionsBaseline(change.page.projections));
|
|
2009
|
+
this.installWindow(change.entries, change.hasMore, change.page.projections === void 0 ? void 0 : projectionsBaseline(change.page.projections), change.page.assistantStream);
|
|
1261
2010
|
return;
|
|
1262
2011
|
case "prepend":
|
|
1263
2012
|
this.prependWindow(change.entries, change.hasMore);
|
|
1264
2013
|
return;
|
|
1265
|
-
case "append":
|
|
2014
|
+
case "append":
|
|
2015
|
+
this.publishAssistantEntry(this.assistantStream.acceptDurable(change.entry));
|
|
2016
|
+
return;
|
|
2017
|
+
case "assistant-stream": this.publishAssistantEntry(this.assistantStream.acceptFrame(change.frame));
|
|
1266
2018
|
}
|
|
1267
2019
|
}
|
|
1268
2020
|
/** Replace the complete contiguous window and apply page-owned projection metadata. */
|
|
1269
|
-
installWindow(entries, hasMore, projections) {
|
|
2021
|
+
installWindow(entries, hasMore, projections, assistantStream) {
|
|
2022
|
+
const visible = this.assistantStream.replace(entries, assistantStream);
|
|
1270
2023
|
this.baseSeq = SessionLogOffset(entries[0]?.event.seq ?? 0);
|
|
1271
2024
|
this.hasMore = hasMore;
|
|
1272
|
-
if (
|
|
2025
|
+
if (visible.some((entry) => entry.event.type === "turn/start")) this.firstPromptPendingTurn = false;
|
|
1273
2026
|
if (projections !== void 0) this.projections.seed(projections);
|
|
1274
|
-
this.eventSource.replace(
|
|
1275
|
-
for (const entry of
|
|
2027
|
+
this.eventSource.replace(visible, hasMore);
|
|
2028
|
+
for (const entry of visible) this.observeSubmissionEvent(entry.event);
|
|
1276
2029
|
this.notifier.markDirty();
|
|
1277
2030
|
}
|
|
2031
|
+
publishAssistantEntry(result) {
|
|
2032
|
+
if (result?.type === "rebaseline") {
|
|
2033
|
+
const events = this.events;
|
|
2034
|
+
queueMicrotask(() => {
|
|
2035
|
+
if (events !== void 0 && this.events === events) events.restart();
|
|
2036
|
+
});
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
if (result?.type === "settlement") {
|
|
2040
|
+
this.eventSource.settleAssistant(result.attemptId, result.entry);
|
|
2041
|
+
this.observeSubmissionEvent(result.entry.event);
|
|
2042
|
+
this.notifier.markDirty();
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
if (result?.type === "abandonment") {
|
|
2046
|
+
this.eventSource.settleAssistant(result.attemptId);
|
|
2047
|
+
this.notifier.markDirty();
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (result?.type === "publish" && this.appendLive(result.entry)) this.notifier.markDirty();
|
|
2051
|
+
else if (result?.type === "transient") {
|
|
2052
|
+
this.eventSource.append(result.entry);
|
|
2053
|
+
this.notifier.markDirty();
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
1278
2056
|
/** Prepend one stream-validated history page. */
|
|
1279
2057
|
prependWindow(entries, hasMore) {
|
|
1280
2058
|
this.baseSeq = entries[0] === void 0 ? this.baseSeq : SessionLogOffset(entries[0].event.seq);
|
|
@@ -1297,12 +2075,12 @@ window.__ModuleLoader__.load({
|
|
|
1297
2075
|
const data = event.data;
|
|
1298
2076
|
const source = data?.source;
|
|
1299
2077
|
if (source?.kind !== "user" || typeof source.rpcId !== "string") return;
|
|
1300
|
-
this.scheduleObservedRetirement(source.rpcId,
|
|
2078
|
+
this.scheduleObservedRetirement(source.rpcId, attachmentRefsIn(data?.content));
|
|
1301
2079
|
}
|
|
1302
2080
|
/** Retire echoes whose prompts landed in the host inbox instead of the log (running-turn submissions). */
|
|
1303
2081
|
observeSubmissionQueue(items) {
|
|
1304
2082
|
if (this.submissionSettlements.size === 0) return;
|
|
1305
|
-
for (const item of items) if (item.rpcId !== void 0) this.scheduleObservedRetirement(item.rpcId,
|
|
2083
|
+
for (const item of items) if (item.rpcId !== void 0) this.scheduleObservedRetirement(item.rpcId, attachmentRefsIn(item.message.content));
|
|
1306
2084
|
}
|
|
1307
2085
|
/**
|
|
1308
2086
|
* Latch one observed settlement and remove the echo an animation frame
|
|
@@ -1389,14 +2167,14 @@ window.__ModuleLoader__.load({
|
|
|
1389
2167
|
});
|
|
1390
2168
|
else setTimeout(fn, 0);
|
|
1391
2169
|
}
|
|
1392
|
-
/**
|
|
1393
|
-
function
|
|
2170
|
+
/** Attachment references in one structurally-read content block list, in block order. */
|
|
2171
|
+
function attachmentRefsIn(content) {
|
|
1394
2172
|
if (!Array.isArray(content)) return [];
|
|
1395
2173
|
const refs = [];
|
|
1396
2174
|
for (const block of content) {
|
|
1397
2175
|
if (typeof block !== "object" || block === null) continue;
|
|
1398
2176
|
const candidate = block;
|
|
1399
|
-
if (candidate.type === "image" && typeof candidate.attachment === "object" && candidate.attachment !== null) refs.push(candidate.attachment);
|
|
2177
|
+
if ((candidate.type === "image" || candidate.type === "file") && typeof candidate.attachment === "object" && candidate.attachment !== null) refs.push(candidate.attachment);
|
|
1400
2178
|
}
|
|
1401
2179
|
return refs;
|
|
1402
2180
|
}
|
|
@@ -2709,6 +3487,8 @@ window.__ModuleLoader__.load({
|
|
|
2709
3487
|
/** Client Session object layer, Agent scopes, and Remote lifecycle wiring. */
|
|
2710
3488
|
/** Required Remote and Context projection services. */
|
|
2711
3489
|
const inject = [
|
|
3490
|
+
"connection",
|
|
3491
|
+
"fileUpload",
|
|
2712
3492
|
"typert",
|
|
2713
3493
|
"remote",
|
|
2714
3494
|
"remote.commands",
|