@voxevolv/client 0.3.0 → 0.3.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/CHANGELOG.md +17 -1
- package/README.md +32 -3
- package/dist/browser/index.js +6 -6
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/index.js.sha384 +1 -1
- package/dist/browser/manifest.json +1 -1
- package/dist/context-queue.d.ts +19 -0
- package/dist/context-queue.d.ts.map +1 -0
- package/dist/context-queue.js +112 -0
- package/dist/context-queue.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +175 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
sha384-
|
|
1
|
+
sha384-0D7tdN1boqxnaeeziL5QCBMckXnVRKf0kS1e1c7P7aDc+MC7TcXTJTOkHOl7G3Ys
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ContextUpdate, ContextUpdatedPayload } from "./protocol.js";
|
|
2
|
+
/** Only replace updates can be coalesced or restored without duplicating data. */
|
|
3
|
+
export declare class ReplacementContextQueue {
|
|
4
|
+
private send;
|
|
5
|
+
private paused;
|
|
6
|
+
private generation;
|
|
7
|
+
private latest;
|
|
8
|
+
private pending;
|
|
9
|
+
private running;
|
|
10
|
+
private failure;
|
|
11
|
+
constructor(send: (update: ContextUpdate, timeoutMs: number) => Promise<ContextUpdatedPayload>);
|
|
12
|
+
submit(update: ContextUpdate, timeoutMs: number): Promise<ContextUpdatedPayload>;
|
|
13
|
+
pause(): void;
|
|
14
|
+
resume(restore?: boolean): Promise<void>;
|
|
15
|
+
clear(error: Error): void;
|
|
16
|
+
private start;
|
|
17
|
+
private drain;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=context-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-queue.d.ts","sourceRoot":"","sources":["../src/context-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAS1E,kFAAkF;AAClF,qBAAa,uBAAuB;IAQtB,OAAO,CAAC,IAAI;IAPxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,OAAO,CAAsB;IAErC,YAAoB,IAAI,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,EAAI;IAE1G,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAqB/E;IAED,KAAK,IAAI,IAAI,CAGZ;IAEK,MAAM,CAAC,OAAO,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB3C;IAED,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAKxB;IAED,OAAO,CAAC,KAAK;YASC,KAAK;CAsBpB"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Only replace updates can be coalesced or restored without duplicating data. */
|
|
2
|
+
export class ReplacementContextQueue {
|
|
3
|
+
send;
|
|
4
|
+
paused = false;
|
|
5
|
+
generation = 0;
|
|
6
|
+
latest = new Map();
|
|
7
|
+
pending = new Map();
|
|
8
|
+
running = new Map();
|
|
9
|
+
failure = null;
|
|
10
|
+
constructor(send) {
|
|
11
|
+
this.send = send;
|
|
12
|
+
}
|
|
13
|
+
submit(update, timeoutMs) {
|
|
14
|
+
const activeKeys = new Set([...this.pending.keys(), ...this.running.keys()]);
|
|
15
|
+
if (activeKeys.size >= 64 && !activeKeys.has(update.key)) {
|
|
16
|
+
return Promise.reject(new Error("Too many pending context keys"));
|
|
17
|
+
}
|
|
18
|
+
// Bound retained image/text memory even for applications using dynamic keys.
|
|
19
|
+
this.latest.delete(update.key);
|
|
20
|
+
this.latest.set(update.key, update);
|
|
21
|
+
let bytes = 0;
|
|
22
|
+
let count = 0;
|
|
23
|
+
for (const [key, value] of [...this.latest].reverse()) {
|
|
24
|
+
bytes += "image" in value ? value.image.size : new TextEncoder().encode(value.text).byteLength;
|
|
25
|
+
count += 1;
|
|
26
|
+
if (bytes > 16 * 1024 * 1024 || count > 64)
|
|
27
|
+
this.latest.delete(key);
|
|
28
|
+
}
|
|
29
|
+
const response = new Promise((resolve, reject) => {
|
|
30
|
+
this.pending.get(update.key)?.reject(new Error("Context update superseded by a newer value"));
|
|
31
|
+
this.pending.set(update.key, { update, timeoutMs, resolve, reject });
|
|
32
|
+
});
|
|
33
|
+
this.start(update.key);
|
|
34
|
+
return response;
|
|
35
|
+
}
|
|
36
|
+
pause() {
|
|
37
|
+
this.generation += 1;
|
|
38
|
+
this.paused = true;
|
|
39
|
+
}
|
|
40
|
+
async resume(restore = false) {
|
|
41
|
+
const generation = this.generation;
|
|
42
|
+
// The SDK cancels tracked commands before restoring the new worker.
|
|
43
|
+
await Promise.all(this.running.values());
|
|
44
|
+
if (generation !== this.generation)
|
|
45
|
+
throw new Error("Context restoration interrupted");
|
|
46
|
+
this.failure = null;
|
|
47
|
+
if (restore) {
|
|
48
|
+
for (const [key, update] of this.latest) {
|
|
49
|
+
if (this.pending.has(key))
|
|
50
|
+
continue;
|
|
51
|
+
this.pending.set(key, {
|
|
52
|
+
update: { ...update, respond: false },
|
|
53
|
+
timeoutMs: 20_000,
|
|
54
|
+
resolve: () => { },
|
|
55
|
+
reject: () => { },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this.paused = false;
|
|
60
|
+
for (const key of this.pending.keys())
|
|
61
|
+
this.start(key);
|
|
62
|
+
await Promise.all(this.running.values());
|
|
63
|
+
if (generation !== this.generation)
|
|
64
|
+
throw new Error("Context restoration interrupted");
|
|
65
|
+
if (this.failure)
|
|
66
|
+
throw this.failure;
|
|
67
|
+
}
|
|
68
|
+
clear(error) {
|
|
69
|
+
this.pause();
|
|
70
|
+
this.latest.clear();
|
|
71
|
+
for (const request of this.pending.values())
|
|
72
|
+
request.reject(error);
|
|
73
|
+
this.pending.clear();
|
|
74
|
+
}
|
|
75
|
+
start(key) {
|
|
76
|
+
if (this.paused || this.running.has(key))
|
|
77
|
+
return;
|
|
78
|
+
const task = this.drain(key).finally(() => {
|
|
79
|
+
this.running.delete(key);
|
|
80
|
+
if (this.pending.has(key))
|
|
81
|
+
this.start(key);
|
|
82
|
+
});
|
|
83
|
+
this.running.set(key, task);
|
|
84
|
+
}
|
|
85
|
+
async drain(key) {
|
|
86
|
+
while (!this.paused) {
|
|
87
|
+
const request = this.pending.get(key);
|
|
88
|
+
if (!request)
|
|
89
|
+
return;
|
|
90
|
+
this.pending.delete(key);
|
|
91
|
+
try {
|
|
92
|
+
request.resolve(await this.send(request.update, request.timeoutMs));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (this.paused && !this.pending.has(key) && this.latest.get(key) === request.update) {
|
|
96
|
+
this.pending.set(key, request);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
request.reject(error instanceof Error ? error : new Error(String(error)));
|
|
100
|
+
}
|
|
101
|
+
if (!this.paused) {
|
|
102
|
+
// Invalid/rejected values must not poison the next restoration.
|
|
103
|
+
if (this.latest.get(key) === request.update)
|
|
104
|
+
this.latest.delete(key);
|
|
105
|
+
this.failure = error instanceof Error ? error : new Error(String(error));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=context-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-queue.js","sourceRoot":"","sources":["../src/context-queue.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface SessionCredentials {
|
|
|
16
16
|
access_token: string;
|
|
17
17
|
access_token_expires_at: string;
|
|
18
18
|
}
|
|
19
|
-
export declare const SDK_VERSION = "0.3.
|
|
19
|
+
export declare const SDK_VERSION = "0.3.2";
|
|
20
20
|
type Listener = (event: RealtimeEvent) => void;
|
|
21
21
|
type StateListener = (state: ConnectionState) => void;
|
|
22
22
|
type GapListener = (gap: EventGap) => void;
|
|
@@ -46,6 +46,7 @@ export interface AutoRejoinOptions {
|
|
|
46
46
|
maxAttempts?: number;
|
|
47
47
|
baseDelayMs?: number;
|
|
48
48
|
maxDelayMs?: number;
|
|
49
|
+
timeoutMs?: number;
|
|
49
50
|
}
|
|
50
51
|
export interface RealtimeSessionOptions {
|
|
51
52
|
connect?: ConnectOptions;
|
|
@@ -129,6 +130,9 @@ export declare class RealtimeSession {
|
|
|
129
130
|
private reconnectStartedAt;
|
|
130
131
|
private pendingReconnect;
|
|
131
132
|
private agentReady;
|
|
133
|
+
private agentStreamId;
|
|
134
|
+
private contextGeneration;
|
|
135
|
+
private readonly replacementContext;
|
|
132
136
|
private readyResolve;
|
|
133
137
|
private replayPromise;
|
|
134
138
|
private pendingCommands;
|
|
@@ -136,6 +140,8 @@ export declare class RealtimeSession {
|
|
|
136
140
|
private apiBase;
|
|
137
141
|
private hasConnectedValue;
|
|
138
142
|
private sessionEnded;
|
|
143
|
+
private pendingEndEvent;
|
|
144
|
+
private endNotificationDelivered;
|
|
139
145
|
private disconnectMode;
|
|
140
146
|
private activeRecovery;
|
|
141
147
|
private autoRejoinPromise;
|
|
@@ -182,6 +188,7 @@ export declare class RealtimeSession {
|
|
|
182
188
|
setMicrophoneEnabled(enabled: boolean): Promise<void>;
|
|
183
189
|
sendCommand<T extends Record<string, unknown>>(type: string, payload: T, timeoutMs?: number): Promise<CommandAckPayload>;
|
|
184
190
|
updateContext(update: ContextUpdate, timeoutMs?: number): Promise<ContextUpdatedPayload>;
|
|
191
|
+
private sendContextUpdate;
|
|
185
192
|
private uploadTextContext;
|
|
186
193
|
private uploadImageContext;
|
|
187
194
|
disconnect(): Promise<void>;
|
|
@@ -192,6 +199,8 @@ export declare class RealtimeSession {
|
|
|
192
199
|
private handleLiveKitReconnecting;
|
|
193
200
|
private handleLiveKitReconnected;
|
|
194
201
|
private handleLiveKitDisconnected;
|
|
202
|
+
private deliverPendingEndEvent;
|
|
203
|
+
private notifyEnded;
|
|
195
204
|
private beginRecovery;
|
|
196
205
|
private completeRecovery;
|
|
197
206
|
private failRecovery;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,IAAI,EAIL,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAUL,KAAK,iBAAiB,EAEtB,KAAK,aAAa,EAElB,KAAK,qBAAqB,EAC1B,KAAK,QAAQ,EACb,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ConnectionState as LiveKitConnectionState, DataPacket_Kind, DisconnectReason, Room, RoomEvent, RpcError, Track, } from "livekit-client";
|
|
2
2
|
import { createClientMessage, CommandError, EventCursor, isCommandAckPayload, isCommandErrorPayload, isContextRejectedPayload, isContextUpdatePayload, isContextUpdatedPayload, isRealtimeEvent, } from "./protocol.js";
|
|
3
|
+
import { ReplacementContextQueue } from "./context-queue.js";
|
|
3
4
|
export { CommandError, EventCursor, createClientMessage, isCommandAckPayload, isCommandErrorPayload, isContextRejectedPayload, isContextUpdatePayload, isContextUpdatedPayload, isRealtimeEvent, } from "./protocol.js";
|
|
4
5
|
export { decodeAgentSnapshotPayload, decodeClientHelloPayload, decodeClientMessage, decodeCommandAckPayload, decodeCommandErrorPayload, decodeContextRejectedPayload, decodeContextUpdatePayload, decodeContextUpdatedPayload, decodeRealtimeEvent, } from "./generated/protocol-codecs.js";
|
|
5
|
-
export const SDK_VERSION = "0.3.
|
|
6
|
+
export const SDK_VERSION = "0.3.2";
|
|
7
|
+
const CLIENT_HELLO_TIMEOUT_MS = 30_000;
|
|
6
8
|
export class RealtimeSessionHttpError extends Error {
|
|
7
9
|
status;
|
|
8
10
|
code;
|
|
@@ -47,6 +49,9 @@ export class RealtimeSession {
|
|
|
47
49
|
reconnectStartedAt = null;
|
|
48
50
|
pendingReconnect = null;
|
|
49
51
|
agentReady = false;
|
|
52
|
+
agentStreamId = null;
|
|
53
|
+
contextGeneration = 0;
|
|
54
|
+
replacementContext = new ReplacementContextQueue((update, timeoutMs) => this.sendContextUpdate(update, timeoutMs));
|
|
50
55
|
readyResolve = null;
|
|
51
56
|
replayPromise = null;
|
|
52
57
|
pendingCommands = new Map();
|
|
@@ -54,6 +59,8 @@ export class RealtimeSession {
|
|
|
54
59
|
apiBase = "/v1";
|
|
55
60
|
hasConnectedValue = false;
|
|
56
61
|
sessionEnded = false;
|
|
62
|
+
pendingEndEvent = null;
|
|
63
|
+
endNotificationDelivered = false;
|
|
57
64
|
disconnectMode = null;
|
|
58
65
|
activeRecovery = null;
|
|
59
66
|
autoRejoinPromise = null;
|
|
@@ -288,6 +295,7 @@ export class RealtimeSession {
|
|
|
288
295
|
if (blockedStates.includes(this.stateValue)) {
|
|
289
296
|
throw new Error(`Cannot connect while session is ${this.stateValue}`);
|
|
290
297
|
}
|
|
298
|
+
const generation = this.contextGeneration;
|
|
291
299
|
const resolved = { ...this.connectDefaults, ...options };
|
|
292
300
|
const audioTrack = resolved.audioTrack ?? null;
|
|
293
301
|
const microphone = resolved.microphone ?? audioTrack === null;
|
|
@@ -301,6 +309,10 @@ export class RealtimeSession {
|
|
|
301
309
|
this.applicationAudioTrack = audioTrack;
|
|
302
310
|
if (!recovering) {
|
|
303
311
|
this.sessionEnded = false;
|
|
312
|
+
this.agentStreamId = null;
|
|
313
|
+
this.reconnectStartedAt = null;
|
|
314
|
+
this.pendingEndEvent = null;
|
|
315
|
+
this.endNotificationDelivered = false;
|
|
304
316
|
this.setState("connecting");
|
|
305
317
|
}
|
|
306
318
|
else {
|
|
@@ -325,6 +337,14 @@ export class RealtimeSession {
|
|
|
325
337
|
if (waitForRemoteAudio)
|
|
326
338
|
await this.waitForRemoteAudioTrack(readyDeadline, signal);
|
|
327
339
|
throwIfAborted(signal);
|
|
340
|
+
if (this.sessionEnded || generation !== this.contextGeneration) {
|
|
341
|
+
throw new Error("Connection restoration interrupted");
|
|
342
|
+
}
|
|
343
|
+
await this.replacementContext.resume(recovering);
|
|
344
|
+
throwIfAborted(signal);
|
|
345
|
+
if (this.sessionEnded || generation !== this.contextGeneration) {
|
|
346
|
+
throw new Error("Connection restoration interrupted");
|
|
347
|
+
}
|
|
328
348
|
this.hasConnectedValue = true;
|
|
329
349
|
this.setState("connected");
|
|
330
350
|
}
|
|
@@ -369,6 +389,9 @@ export class RealtimeSession {
|
|
|
369
389
|
}
|
|
370
390
|
}
|
|
371
391
|
async performRejoin(apiBase, signal, recovering = true) {
|
|
392
|
+
this.contextGeneration += 1;
|
|
393
|
+
this.replacementContext.pause();
|
|
394
|
+
this.rejectPendingCommands(new Error("Restoring the session"));
|
|
372
395
|
let response = await this.fetchRoomToken(apiBase, signal);
|
|
373
396
|
if (response.status === 401 && this.refreshCredentialsCallback) {
|
|
374
397
|
await this.refreshCredentials();
|
|
@@ -389,6 +412,13 @@ export class RealtimeSession {
|
|
|
389
412
|
}
|
|
390
413
|
}
|
|
391
414
|
throwIfAborted(signal);
|
|
415
|
+
if (recovering && this.reconnectStartedAt !== null) {
|
|
416
|
+
this.pendingReconnect = {
|
|
417
|
+
started_at: new Date(this.reconnectStartedAt).toISOString(),
|
|
418
|
+
recovered_at: new Date().toISOString(),
|
|
419
|
+
duration_ms: Date.now() - this.reconnectStartedAt,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
392
422
|
await this.connectWithMode({
|
|
393
423
|
apiBase,
|
|
394
424
|
microphone: this.useManagedMicrophone,
|
|
@@ -404,9 +434,31 @@ export class RealtimeSession {
|
|
|
404
434
|
return this.sendTrackedCommand(message, timeoutMs);
|
|
405
435
|
}
|
|
406
436
|
async updateContext(update, timeoutMs = 20_000) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
437
|
+
if (this.sessionEnded || ["failed", "disconnected"].includes(this.stateValue)) {
|
|
438
|
+
throw new Error("Cannot update context while the session is inactive");
|
|
439
|
+
}
|
|
440
|
+
if (update.mode === "replace")
|
|
441
|
+
return this.replacementContext.submit(update, timeoutMs);
|
|
442
|
+
if (this.activeRecovery)
|
|
443
|
+
throw new Error("Cannot append context during recovery");
|
|
444
|
+
return this.sendContextUpdate(update, timeoutMs);
|
|
445
|
+
}
|
|
446
|
+
async sendContextUpdate(update, timeoutMs) {
|
|
447
|
+
const generation = this.contextGeneration;
|
|
448
|
+
let transferTimeout;
|
|
449
|
+
const transferExpired = new Promise((_resolve, reject) => {
|
|
450
|
+
transferTimeout = globalThis.setTimeout(() => {
|
|
451
|
+
const error = new Error(`Context transfer timed out after ${timeoutMs}ms`);
|
|
452
|
+
this.startAutoRejoin(error);
|
|
453
|
+
reject(error);
|
|
454
|
+
}, timeoutMs);
|
|
455
|
+
});
|
|
456
|
+
const transfer = await Promise.race([
|
|
457
|
+
"image" in update ? this.uploadImageContext(update) : this.uploadTextContext(update),
|
|
458
|
+
transferExpired,
|
|
459
|
+
]).finally(() => globalThis.clearTimeout(transferTimeout));
|
|
460
|
+
if (generation !== this.contextGeneration)
|
|
461
|
+
throw new Error("Context transfer interrupted by recovery");
|
|
410
462
|
const { payload } = transfer;
|
|
411
463
|
if (!isContextUpdatePayload(payload)) {
|
|
412
464
|
throw new TypeError("Invalid context update payload");
|
|
@@ -495,6 +547,8 @@ export class RealtimeSession {
|
|
|
495
547
|
}
|
|
496
548
|
async disconnect() {
|
|
497
549
|
this.cancelAutoRejoin();
|
|
550
|
+
this.contextGeneration += 1;
|
|
551
|
+
this.replacementContext.clear(new Error("Session disconnected"));
|
|
498
552
|
if (this.stateValue === "disconnected")
|
|
499
553
|
return;
|
|
500
554
|
this.rejectPendingCommands(new Error("Session disconnected before command completion"));
|
|
@@ -555,6 +609,11 @@ export class RealtimeSession {
|
|
|
555
609
|
this.notifyListeners(this.stateListeners, state);
|
|
556
610
|
}
|
|
557
611
|
bindRoomEvents() {
|
|
612
|
+
this.room.on(RoomEvent.ParticipantDisconnected, (participant) => {
|
|
613
|
+
if (participant.isAgent && !this.disconnectMode) {
|
|
614
|
+
this.startAutoRejoin(new Error("Agent disconnected from the room"));
|
|
615
|
+
}
|
|
616
|
+
});
|
|
558
617
|
this.room.on(RoomEvent.SignalReconnecting, () => this.handleLiveKitReconnecting());
|
|
559
618
|
this.room.on(RoomEvent.Reconnecting, () => this.handleLiveKitReconnecting());
|
|
560
619
|
this.room.on(RoomEvent.Reconnected, () => {
|
|
@@ -644,6 +703,8 @@ export class RealtimeSession {
|
|
|
644
703
|
});
|
|
645
704
|
}
|
|
646
705
|
handleLiveKitReconnecting() {
|
|
706
|
+
if (this.sessionEnded || this.disconnectMode === "intentional")
|
|
707
|
+
return;
|
|
647
708
|
this.agentReady = false;
|
|
648
709
|
// Once full rejoin owns recovery, late LiveKit reconnect notifications
|
|
649
710
|
// from the old transport must not move the wrapper back to reconnecting.
|
|
@@ -654,6 +715,8 @@ export class RealtimeSession {
|
|
|
654
715
|
this.setState("resuming");
|
|
655
716
|
return;
|
|
656
717
|
}
|
|
718
|
+
this.contextGeneration += 1;
|
|
719
|
+
this.replacementContext.pause();
|
|
657
720
|
this.reconnectStartedAt ??= Date.now();
|
|
658
721
|
this.rejectPendingCommands(new Error("Connection interrupted before command completion"), true);
|
|
659
722
|
if (this.activeRecovery === null) {
|
|
@@ -662,6 +725,9 @@ export class RealtimeSession {
|
|
|
662
725
|
this.setState("reconnecting");
|
|
663
726
|
}
|
|
664
727
|
async handleLiveKitReconnected() {
|
|
728
|
+
if (this.sessionEnded || this.disconnectMode || this.autoRejoinPromise)
|
|
729
|
+
return;
|
|
730
|
+
const generation = this.contextGeneration;
|
|
665
731
|
const recoveredAt = Date.now();
|
|
666
732
|
if (this.reconnectStartedAt !== null) {
|
|
667
733
|
this.pendingReconnect = {
|
|
@@ -679,8 +745,21 @@ export class RealtimeSession {
|
|
|
679
745
|
this.setState(this.hasConnectedValue ? "resuming" : "connected_not_ready");
|
|
680
746
|
try {
|
|
681
747
|
await this.sendHello();
|
|
748
|
+
const deadline = Date.now() + (this.connectDefaults.readyTimeoutMs ?? 15_000);
|
|
749
|
+
await this.waitForAgentReady(Math.max(deadline - Date.now(), 0));
|
|
750
|
+
if (this.connectDefaults.waitForRemoteAudio !== false)
|
|
751
|
+
await this.waitForRemoteAudioTrack(deadline);
|
|
752
|
+
if (this.sessionEnded || generation !== this.contextGeneration)
|
|
753
|
+
return;
|
|
754
|
+
await this.replacementContext.resume(true);
|
|
755
|
+
if (this.sessionEnded || generation !== this.contextGeneration)
|
|
756
|
+
return;
|
|
757
|
+
this.setState("connected");
|
|
758
|
+
this.completeRecovery();
|
|
682
759
|
}
|
|
683
760
|
catch (error) {
|
|
761
|
+
if (this.sessionEnded || generation !== this.contextGeneration)
|
|
762
|
+
return;
|
|
684
763
|
const normalizedError = toError(error);
|
|
685
764
|
if (this.shouldAutoRejoin()) {
|
|
686
765
|
this.startAutoRejoin(normalizedError);
|
|
@@ -693,8 +772,9 @@ export class RealtimeSession {
|
|
|
693
772
|
}
|
|
694
773
|
handleLiveKitDisconnected(reason) {
|
|
695
774
|
this.agentReady = false;
|
|
696
|
-
this.
|
|
697
|
-
|
|
775
|
+
if (this.disconnectMode !== "rejoin") {
|
|
776
|
+
this.pendingReconnect = null;
|
|
777
|
+
}
|
|
698
778
|
// Drop any remaining remote tracks so a stale, ended track cannot
|
|
699
779
|
// satisfy the next connect()'s wait for agent audio.
|
|
700
780
|
this.outputMediaStream?.getTracks().forEach((track) => {
|
|
@@ -707,6 +787,7 @@ export class RealtimeSession {
|
|
|
707
787
|
return;
|
|
708
788
|
if (this.disconnectMode === "intentional") {
|
|
709
789
|
this.activeRecovery = null;
|
|
790
|
+
this.deliverPendingEndEvent();
|
|
710
791
|
this.setState("disconnected");
|
|
711
792
|
return;
|
|
712
793
|
}
|
|
@@ -717,8 +798,24 @@ export class RealtimeSession {
|
|
|
717
798
|
if (this.activeRecovery !== null) {
|
|
718
799
|
this.failRecovery(new Error(`LiveKit disconnected (${disconnectReasonName(reason)})`));
|
|
719
800
|
}
|
|
801
|
+
this.deliverPendingEndEvent();
|
|
720
802
|
this.setState("disconnected");
|
|
721
803
|
}
|
|
804
|
+
// The terminal session.ended event is recorded durably after room teardown,
|
|
805
|
+
// so turn the earlier live announcement into the browser completion signal.
|
|
806
|
+
deliverPendingEndEvent() {
|
|
807
|
+
const ending = this.pendingEndEvent;
|
|
808
|
+
if (!ending)
|
|
809
|
+
return;
|
|
810
|
+
this.pendingEndEvent = null;
|
|
811
|
+
this.notifyEnded({ ...ending, type: "session.ended" });
|
|
812
|
+
}
|
|
813
|
+
notifyEnded(event) {
|
|
814
|
+
if (this.endNotificationDelivered)
|
|
815
|
+
return;
|
|
816
|
+
this.endNotificationDelivered = true;
|
|
817
|
+
this.notifyListeners(this.endedListeners, event);
|
|
818
|
+
}
|
|
722
819
|
beginRecovery(strategy, attempt) {
|
|
723
820
|
this.activeRecovery = { strategy, attempt };
|
|
724
821
|
this.notifyListeners(this.recoveryListeners, {
|
|
@@ -754,15 +851,33 @@ export class RealtimeSession {
|
|
|
754
851
|
return (this.autoRejoinOptions !== null
|
|
755
852
|
&& this.hasConnectedValue
|
|
756
853
|
&& !this.sessionEnded
|
|
854
|
+
&& !["disconnected", "failed"].includes(this.stateValue)
|
|
757
855
|
&& !isTerminalDisconnectReason(reason));
|
|
758
856
|
}
|
|
759
857
|
startAutoRejoin(initialError) {
|
|
760
858
|
if (this.autoRejoinPromise || !this.shouldAutoRejoin())
|
|
761
859
|
return;
|
|
860
|
+
this.agentReady = false;
|
|
861
|
+
this.contextGeneration += 1;
|
|
862
|
+
this.replacementContext.pause();
|
|
863
|
+
this.rejectPendingCommands(initialError);
|
|
864
|
+
this.reconnectStartedAt ??= Date.now();
|
|
762
865
|
const controller = new AbortController();
|
|
763
866
|
this.autoRejoinController = controller;
|
|
867
|
+
const timeout = globalThis.setTimeout(() => {
|
|
868
|
+
if (controller.signal.aborted)
|
|
869
|
+
return;
|
|
870
|
+
const error = new Error("Session recovery timed out");
|
|
871
|
+
controller.abort(error);
|
|
872
|
+
this.contextGeneration += 1;
|
|
873
|
+
this.rejectPendingCommands(error);
|
|
874
|
+
this.replacementContext.clear(error);
|
|
875
|
+
this.failRecovery(error);
|
|
876
|
+
this.setState("failed");
|
|
877
|
+
}, this.autoRejoinOptions.timeoutMs);
|
|
764
878
|
const promise = this.runAutoRejoin(initialError, controller.signal)
|
|
765
879
|
.finally(() => {
|
|
880
|
+
globalThis.clearTimeout(timeout);
|
|
766
881
|
if (this.autoRejoinController === controller) {
|
|
767
882
|
this.autoRejoinController = null;
|
|
768
883
|
this.autoRejoinPromise = null;
|
|
@@ -792,6 +907,7 @@ export class RealtimeSession {
|
|
|
792
907
|
this.beginRecovery("rejoin", attempt);
|
|
793
908
|
await this.performRejoin(this.apiBase, signal);
|
|
794
909
|
throwIfAborted(signal);
|
|
910
|
+
this.reconnectStartedAt = null;
|
|
795
911
|
this.completeRecovery();
|
|
796
912
|
return;
|
|
797
913
|
}
|
|
@@ -804,6 +920,7 @@ export class RealtimeSession {
|
|
|
804
920
|
}
|
|
805
921
|
}
|
|
806
922
|
this.failRecovery(lastError);
|
|
923
|
+
this.replacementContext.clear(lastError);
|
|
807
924
|
this.setState("failed");
|
|
808
925
|
}
|
|
809
926
|
cancelAutoRejoin() {
|
|
@@ -846,12 +963,23 @@ export class RealtimeSession {
|
|
|
846
963
|
}
|
|
847
964
|
}
|
|
848
965
|
async fetchRoomToken(apiBase, signal) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
966
|
+
const controller = new AbortController();
|
|
967
|
+
const cancel = () => controller.abort(signal?.reason);
|
|
968
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
969
|
+
const timeout = globalThis.setTimeout(() => controller.abort(), 10_000);
|
|
970
|
+
try {
|
|
971
|
+
throwIfAborted(signal);
|
|
972
|
+
return await this.fetcher(`${apiBase}/sessions/${this.id}/room-tokens`, {
|
|
973
|
+
method: "POST",
|
|
974
|
+
headers: { Authorization: `Bearer ${this.accessToken}` },
|
|
975
|
+
credentials: this.lifecycleCredentials,
|
|
976
|
+
signal: controller.signal,
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
finally {
|
|
980
|
+
globalThis.clearTimeout(timeout);
|
|
981
|
+
signal?.removeEventListener("abort", cancel);
|
|
982
|
+
}
|
|
855
983
|
}
|
|
856
984
|
async refreshCredentials() {
|
|
857
985
|
if (!this.refreshCredentialsCallback)
|
|
@@ -865,19 +993,33 @@ export class RealtimeSession {
|
|
|
865
993
|
this.credentials = refreshed.connection;
|
|
866
994
|
}
|
|
867
995
|
observeConvenienceEvents(event) {
|
|
996
|
+
if (event.type === "session.ending" || event.type === "session.ended") {
|
|
997
|
+
this.contextGeneration += 1;
|
|
998
|
+
this.rejectPendingCommands(new Error("Session ended"));
|
|
999
|
+
}
|
|
1000
|
+
if (event.type === "session.ending") {
|
|
1001
|
+
// The server announced an intentional hang-up before tearing down the
|
|
1002
|
+
// room, so the coming disconnect must not fail recovery or auto-rejoin.
|
|
1003
|
+
this.sessionEnded = true;
|
|
1004
|
+
this.cancelAutoRejoin();
|
|
1005
|
+
this.replacementContext.clear(new Error("Session ended"));
|
|
1006
|
+
this.pendingEndEvent = event;
|
|
1007
|
+
}
|
|
868
1008
|
if (event.type === "session.ended") {
|
|
1009
|
+
this.pendingEndEvent = null;
|
|
869
1010
|
const recoveryWasActive = (this.autoRejoinPromise !== null
|
|
870
1011
|
|| this.activeRecovery !== null
|
|
871
1012
|
|| this.stateValue === "reconnecting"
|
|
872
1013
|
|| this.stateValue === "resuming");
|
|
873
1014
|
this.sessionEnded = true;
|
|
1015
|
+
this.replacementContext.clear(new Error("Session ended"));
|
|
874
1016
|
if (this.activeRecovery !== null) {
|
|
875
1017
|
this.failRecovery(new Error("Session ended during recovery"));
|
|
876
1018
|
}
|
|
877
1019
|
this.cancelAutoRejoin();
|
|
1020
|
+
this.notifyEnded(event);
|
|
878
1021
|
if (recoveryWasActive)
|
|
879
1022
|
this.setState("disconnected");
|
|
880
|
-
this.notifyListeners(this.endedListeners, event);
|
|
881
1023
|
}
|
|
882
1024
|
if (event.type.endsWith(".error"))
|
|
883
1025
|
this.notifyListeners(this.errorListeners, event);
|
|
@@ -908,7 +1050,7 @@ export class RealtimeSession {
|
|
|
908
1050
|
...(reconnect ? { reconnect } : {}),
|
|
909
1051
|
});
|
|
910
1052
|
this.lastHelloMessageId = message.message_id;
|
|
911
|
-
await this.sendTrackedCommand(message,
|
|
1053
|
+
await this.sendTrackedCommand(message, CLIENT_HELLO_TIMEOUT_MS);
|
|
912
1054
|
if (this.pendingReconnect === reconnect)
|
|
913
1055
|
this.pendingReconnect = null;
|
|
914
1056
|
}
|
|
@@ -919,7 +1061,10 @@ export class RealtimeSession {
|
|
|
919
1061
|
if (pending)
|
|
920
1062
|
globalThis.clearInterval(pending.retry);
|
|
921
1063
|
this.pendingCommands.delete(message.message_id);
|
|
922
|
-
|
|
1064
|
+
const error = new Error(`Command ${message.type} timed out after ${timeoutMs}ms`);
|
|
1065
|
+
reject(error);
|
|
1066
|
+
if (message.type === "context.update")
|
|
1067
|
+
this.startAutoRejoin(error);
|
|
923
1068
|
}, timeoutMs);
|
|
924
1069
|
const retry = globalThis.setInterval(() => {
|
|
925
1070
|
void this.publishCommand(message).catch(() => {
|
|
@@ -1006,6 +1151,9 @@ export class RealtimeSession {
|
|
|
1006
1151
|
});
|
|
1007
1152
|
}
|
|
1008
1153
|
observeReadiness(event) {
|
|
1154
|
+
// Replayed readiness describes an old worker, not the current transport.
|
|
1155
|
+
if (event.stream_id === "durable-replay")
|
|
1156
|
+
return;
|
|
1009
1157
|
const payload = event.payload;
|
|
1010
1158
|
const snapshotReady = event.type === "agent.snapshot"
|
|
1011
1159
|
&& payload.status === "ready"
|
|
@@ -1013,14 +1161,15 @@ export class RealtimeSession {
|
|
|
1013
1161
|
&& payload.ack_message_id === this.lastHelloMessageId;
|
|
1014
1162
|
if (event.type !== "agent.ready" && !snapshotReady)
|
|
1015
1163
|
return;
|
|
1164
|
+
if (this.agentStreamId && this.agentStreamId !== event.stream_id && !this.activeRecovery) {
|
|
1165
|
+
this.startAutoRejoin(new Error("Agent worker was replaced"));
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
this.agentStreamId = event.stream_id;
|
|
1016
1169
|
const wasReady = this.agentReady;
|
|
1017
1170
|
this.agentReady = true;
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
this.setState("connected");
|
|
1021
|
-
if (this.activeRecovery?.strategy === "livekit")
|
|
1022
|
-
this.completeRecovery();
|
|
1023
|
-
}
|
|
1171
|
+
// connect/reconnect owns the transition after hello, audio and context
|
|
1172
|
+
// restoration all finish. Readiness alone is not successful recovery.
|
|
1024
1173
|
this.readyResolve?.();
|
|
1025
1174
|
if (!wasReady)
|
|
1026
1175
|
this.notifyListeners(this.readyListeners, event);
|
|
@@ -1123,10 +1272,14 @@ function resolveAutoRejoinOptions(value) {
|
|
|
1123
1272
|
return null;
|
|
1124
1273
|
const options = value === true ? {} : value;
|
|
1125
1274
|
const resolved = {
|
|
1126
|
-
maxAttempts: options.maxAttempts ??
|
|
1275
|
+
maxAttempts: options.maxAttempts ?? Number.MAX_SAFE_INTEGER,
|
|
1127
1276
|
baseDelayMs: options.baseDelayMs ?? 750,
|
|
1128
1277
|
maxDelayMs: options.maxDelayMs ?? 5_000,
|
|
1278
|
+
timeoutMs: options.timeoutMs ?? 180_000,
|
|
1129
1279
|
};
|
|
1280
|
+
if (!Number.isFinite(resolved.timeoutMs) || resolved.timeoutMs <= 0) {
|
|
1281
|
+
throw new TypeError("autoRejoin.timeoutMs must be a positive finite number");
|
|
1282
|
+
}
|
|
1130
1283
|
if (!Number.isInteger(resolved.maxAttempts) || resolved.maxAttempts < 1) {
|
|
1131
1284
|
throw new TypeError("autoRejoin.maxAttempts must be a positive integer");
|
|
1132
1285
|
}
|