@absolutejs/voice 0.0.22-beta.655 → 0.0.22-beta.656
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/dist/angular/index.js +37 -3
- package/dist/client/connection.d.ts +3 -0
- package/dist/client/htmxBootstrap.js +37 -3
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +39 -4
- package/dist/embed/index.js +37 -3
- package/dist/embed/voice-widget.js +8 -8
- package/dist/react/index.js +37 -3
- package/dist/svelte/index.js +37 -3
- package/dist/testing/index.js +37 -3
- package/dist/vue/index.js +37 -3
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -593,6 +593,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
593
593
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
594
594
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
595
595
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
596
|
+
|
|
597
|
+
class VoiceReconnectRejectedError extends Error {
|
|
598
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
599
|
+
super(message);
|
|
600
|
+
this.name = "VoiceReconnectRejectedError";
|
|
601
|
+
}
|
|
602
|
+
}
|
|
596
603
|
var noop = () => {};
|
|
597
604
|
var noopAsync = () => Promise.resolve();
|
|
598
605
|
var noopUnsubscribe = () => noop;
|
|
@@ -672,6 +679,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
672
679
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
673
680
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
674
681
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
682
|
+
let lifecycleGeneration = 0;
|
|
683
|
+
let disposed = false;
|
|
675
684
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
676
685
|
const state = {
|
|
677
686
|
isConnected: false,
|
|
@@ -735,6 +744,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
735
744
|
pendingCallControls.clear();
|
|
736
745
|
};
|
|
737
746
|
const scheduleReconnect = () => {
|
|
747
|
+
if (disposed)
|
|
748
|
+
return;
|
|
749
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
738
750
|
state.reconnectAttempts += 1;
|
|
739
751
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
740
752
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -749,6 +761,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
749
761
|
type: "connection"
|
|
750
762
|
});
|
|
751
763
|
state.reconnectTimeout = setTimeout(async () => {
|
|
764
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
765
|
+
return;
|
|
752
766
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
753
767
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
754
768
|
return;
|
|
@@ -760,7 +774,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
760
774
|
scenarioId: state.scenarioId,
|
|
761
775
|
sessionId: state.sessionId
|
|
762
776
|
});
|
|
763
|
-
} catch {
|
|
777
|
+
} catch (error) {
|
|
778
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
779
|
+
return;
|
|
780
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
781
|
+
emitTerminalFailure(error.message);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
764
784
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
765
785
|
scheduleReconnect();
|
|
766
786
|
} else {
|
|
@@ -768,10 +788,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
768
788
|
}
|
|
769
789
|
return;
|
|
770
790
|
}
|
|
791
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
792
|
+
return;
|
|
771
793
|
connect();
|
|
772
794
|
}, delayMs);
|
|
773
795
|
};
|
|
774
796
|
const connect = () => {
|
|
797
|
+
if (disposed)
|
|
798
|
+
return;
|
|
775
799
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
776
800
|
ws.binaryType = "arraybuffer";
|
|
777
801
|
ws.onopen = () => {
|
|
@@ -842,6 +866,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
842
866
|
state.ws = null;
|
|
843
867
|
state.isConnected = false;
|
|
844
868
|
clearTimers();
|
|
869
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
845
870
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
846
871
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
847
872
|
if (authorizationFailure) {
|
|
@@ -907,6 +932,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
907
932
|
send({ type: "end_turn" });
|
|
908
933
|
};
|
|
909
934
|
const callControl = (message) => {
|
|
935
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
936
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
937
|
+
}
|
|
910
938
|
const requestId = crypto.randomUUID();
|
|
911
939
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
912
940
|
const timeout = setTimeout(() => {
|
|
@@ -914,16 +942,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
914
942
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
915
943
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
916
944
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
917
|
-
send({
|
|
945
|
+
state.ws.send(JSON.stringify({
|
|
918
946
|
...message,
|
|
919
947
|
requestId,
|
|
920
948
|
type: "call_control"
|
|
921
|
-
});
|
|
949
|
+
}));
|
|
922
950
|
return promise;
|
|
923
951
|
};
|
|
924
952
|
const close = (reason = "client-close") => {
|
|
953
|
+
disposed = true;
|
|
954
|
+
lifecycleGeneration += 1;
|
|
925
955
|
clearTimers();
|
|
926
956
|
rejectPendingCallControls("Voice connection closed");
|
|
957
|
+
state.pendingMessages.length = 0;
|
|
927
958
|
if (state.ws) {
|
|
928
959
|
const ws = state.ws;
|
|
929
960
|
state.ws = null;
|
|
@@ -936,8 +967,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
936
967
|
listeners.clear();
|
|
937
968
|
};
|
|
938
969
|
const disconnect = () => {
|
|
970
|
+
disposed = true;
|
|
971
|
+
lifecycleGeneration += 1;
|
|
939
972
|
clearTimers();
|
|
940
973
|
rejectPendingCallControls("Voice connection disconnected");
|
|
974
|
+
state.pendingMessages.length = 0;
|
|
941
975
|
if (state.ws) {
|
|
942
976
|
const ws = state.ws;
|
|
943
977
|
state.ws = null;
|
|
@@ -22,5 +22,8 @@ type VoiceConnectionHandle = {
|
|
|
22
22
|
simulateDisconnect: () => void;
|
|
23
23
|
subscribe: (callback: (message: VoiceServerMessage) => void) => () => void;
|
|
24
24
|
};
|
|
25
|
+
export declare class VoiceReconnectRejectedError extends Error {
|
|
26
|
+
constructor(message?: string);
|
|
27
|
+
}
|
|
25
28
|
export declare const createVoiceConnection: (path: string, options?: VoiceConnectionOptions) => VoiceConnectionHandle;
|
|
26
29
|
export {};
|
|
@@ -510,6 +510,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
510
510
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
511
511
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
512
512
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
513
|
+
|
|
514
|
+
class VoiceReconnectRejectedError extends Error {
|
|
515
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
516
|
+
super(message);
|
|
517
|
+
this.name = "VoiceReconnectRejectedError";
|
|
518
|
+
}
|
|
519
|
+
}
|
|
513
520
|
var noop = () => {};
|
|
514
521
|
var noopAsync = () => Promise.resolve();
|
|
515
522
|
var noopUnsubscribe = () => noop;
|
|
@@ -589,6 +596,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
589
596
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
590
597
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
591
598
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
599
|
+
let lifecycleGeneration = 0;
|
|
600
|
+
let disposed = false;
|
|
592
601
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
593
602
|
const state = {
|
|
594
603
|
isConnected: false,
|
|
@@ -652,6 +661,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
652
661
|
pendingCallControls.clear();
|
|
653
662
|
};
|
|
654
663
|
const scheduleReconnect = () => {
|
|
664
|
+
if (disposed)
|
|
665
|
+
return;
|
|
666
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
655
667
|
state.reconnectAttempts += 1;
|
|
656
668
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
657
669
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -666,6 +678,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
666
678
|
type: "connection"
|
|
667
679
|
});
|
|
668
680
|
state.reconnectTimeout = setTimeout(async () => {
|
|
681
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
682
|
+
return;
|
|
669
683
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
670
684
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
671
685
|
return;
|
|
@@ -677,7 +691,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
677
691
|
scenarioId: state.scenarioId,
|
|
678
692
|
sessionId: state.sessionId
|
|
679
693
|
});
|
|
680
|
-
} catch {
|
|
694
|
+
} catch (error) {
|
|
695
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
696
|
+
return;
|
|
697
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
698
|
+
emitTerminalFailure(error.message);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
681
701
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
682
702
|
scheduleReconnect();
|
|
683
703
|
} else {
|
|
@@ -685,10 +705,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
685
705
|
}
|
|
686
706
|
return;
|
|
687
707
|
}
|
|
708
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
709
|
+
return;
|
|
688
710
|
connect();
|
|
689
711
|
}, delayMs);
|
|
690
712
|
};
|
|
691
713
|
const connect = () => {
|
|
714
|
+
if (disposed)
|
|
715
|
+
return;
|
|
692
716
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
693
717
|
ws.binaryType = "arraybuffer";
|
|
694
718
|
ws.onopen = () => {
|
|
@@ -759,6 +783,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
759
783
|
state.ws = null;
|
|
760
784
|
state.isConnected = false;
|
|
761
785
|
clearTimers();
|
|
786
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
762
787
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
763
788
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
764
789
|
if (authorizationFailure) {
|
|
@@ -824,6 +849,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
824
849
|
send({ type: "end_turn" });
|
|
825
850
|
};
|
|
826
851
|
const callControl = (message) => {
|
|
852
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
853
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
854
|
+
}
|
|
827
855
|
const requestId = crypto.randomUUID();
|
|
828
856
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
829
857
|
const timeout = setTimeout(() => {
|
|
@@ -831,16 +859,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
831
859
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
832
860
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
833
861
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
834
|
-
send({
|
|
862
|
+
state.ws.send(JSON.stringify({
|
|
835
863
|
...message,
|
|
836
864
|
requestId,
|
|
837
865
|
type: "call_control"
|
|
838
|
-
});
|
|
866
|
+
}));
|
|
839
867
|
return promise;
|
|
840
868
|
};
|
|
841
869
|
const close = (reason = "client-close") => {
|
|
870
|
+
disposed = true;
|
|
871
|
+
lifecycleGeneration += 1;
|
|
842
872
|
clearTimers();
|
|
843
873
|
rejectPendingCallControls("Voice connection closed");
|
|
874
|
+
state.pendingMessages.length = 0;
|
|
844
875
|
if (state.ws) {
|
|
845
876
|
const ws = state.ws;
|
|
846
877
|
state.ws = null;
|
|
@@ -853,8 +884,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
853
884
|
listeners.clear();
|
|
854
885
|
};
|
|
855
886
|
const disconnect = () => {
|
|
887
|
+
disposed = true;
|
|
888
|
+
lifecycleGeneration += 1;
|
|
856
889
|
clearTimers();
|
|
857
890
|
rejectPendingCallControls("Voice connection disconnected");
|
|
891
|
+
state.pendingMessages.length = 0;
|
|
858
892
|
if (state.ws) {
|
|
859
893
|
const ws = state.ws;
|
|
860
894
|
state.ws = null;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { bindVoiceReactiveSource, voiceSseReactiveSource, } from "./reactiveSource";
|
|
2
2
|
export type { VoiceReactiveSource, VoiceSseReactiveSourceOptions, } from "./reactiveSource";
|
|
3
|
-
export { createVoiceConnection } from "./connection";
|
|
3
|
+
export { createVoiceConnection, VoiceReconnectRejectedError, } from "./connection";
|
|
4
4
|
export { checkBrowserVoiceSupport, probeBrowserVoiceSupport, } from "./browserVoiceSupport";
|
|
5
5
|
export type { BrowserVoiceSupportProbe, BrowserVoiceSupportReport, } from "./browserVoiceSupport";
|
|
6
6
|
export { createVoiceAudioPlayer, decodeVoiceAudioChunk } from "./audioPlayer";
|
package/dist/client/index.js
CHANGED
|
@@ -115,6 +115,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
115
115
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
116
116
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
117
117
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
118
|
+
|
|
119
|
+
class VoiceReconnectRejectedError extends Error {
|
|
120
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
121
|
+
super(message);
|
|
122
|
+
this.name = "VoiceReconnectRejectedError";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
118
125
|
var noop = () => {};
|
|
119
126
|
var noopAsync = () => Promise.resolve();
|
|
120
127
|
var noopUnsubscribe = () => noop;
|
|
@@ -194,6 +201,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
194
201
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
195
202
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
196
203
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
204
|
+
let lifecycleGeneration = 0;
|
|
205
|
+
let disposed = false;
|
|
197
206
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
198
207
|
const state = {
|
|
199
208
|
isConnected: false,
|
|
@@ -257,6 +266,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
257
266
|
pendingCallControls.clear();
|
|
258
267
|
};
|
|
259
268
|
const scheduleReconnect = () => {
|
|
269
|
+
if (disposed)
|
|
270
|
+
return;
|
|
271
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
260
272
|
state.reconnectAttempts += 1;
|
|
261
273
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
262
274
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -271,6 +283,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
271
283
|
type: "connection"
|
|
272
284
|
});
|
|
273
285
|
state.reconnectTimeout = setTimeout(async () => {
|
|
286
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
287
|
+
return;
|
|
274
288
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
275
289
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
276
290
|
return;
|
|
@@ -282,7 +296,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
282
296
|
scenarioId: state.scenarioId,
|
|
283
297
|
sessionId: state.sessionId
|
|
284
298
|
});
|
|
285
|
-
} catch {
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
301
|
+
return;
|
|
302
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
303
|
+
emitTerminalFailure(error.message);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
286
306
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
287
307
|
scheduleReconnect();
|
|
288
308
|
} else {
|
|
@@ -290,10 +310,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
290
310
|
}
|
|
291
311
|
return;
|
|
292
312
|
}
|
|
313
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
314
|
+
return;
|
|
293
315
|
connect();
|
|
294
316
|
}, delayMs);
|
|
295
317
|
};
|
|
296
318
|
const connect = () => {
|
|
319
|
+
if (disposed)
|
|
320
|
+
return;
|
|
297
321
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
298
322
|
ws.binaryType = "arraybuffer";
|
|
299
323
|
ws.onopen = () => {
|
|
@@ -364,6 +388,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
364
388
|
state.ws = null;
|
|
365
389
|
state.isConnected = false;
|
|
366
390
|
clearTimers();
|
|
391
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
367
392
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
368
393
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
369
394
|
if (authorizationFailure) {
|
|
@@ -429,6 +454,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
429
454
|
send({ type: "end_turn" });
|
|
430
455
|
};
|
|
431
456
|
const callControl = (message) => {
|
|
457
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
458
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
459
|
+
}
|
|
432
460
|
const requestId = crypto.randomUUID();
|
|
433
461
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
434
462
|
const timeout = setTimeout(() => {
|
|
@@ -436,16 +464,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
436
464
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
437
465
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
438
466
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
439
|
-
send({
|
|
467
|
+
state.ws.send(JSON.stringify({
|
|
440
468
|
...message,
|
|
441
469
|
requestId,
|
|
442
470
|
type: "call_control"
|
|
443
|
-
});
|
|
471
|
+
}));
|
|
444
472
|
return promise;
|
|
445
473
|
};
|
|
446
474
|
const close = (reason = "client-close") => {
|
|
475
|
+
disposed = true;
|
|
476
|
+
lifecycleGeneration += 1;
|
|
447
477
|
clearTimers();
|
|
448
478
|
rejectPendingCallControls("Voice connection closed");
|
|
479
|
+
state.pendingMessages.length = 0;
|
|
449
480
|
if (state.ws) {
|
|
450
481
|
const ws = state.ws;
|
|
451
482
|
state.ws = null;
|
|
@@ -458,8 +489,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
458
489
|
listeners.clear();
|
|
459
490
|
};
|
|
460
491
|
const disconnect = () => {
|
|
492
|
+
disposed = true;
|
|
493
|
+
lifecycleGeneration += 1;
|
|
461
494
|
clearTimers();
|
|
462
495
|
rejectPendingCallControls("Voice connection disconnected");
|
|
496
|
+
state.pendingMessages.length = 0;
|
|
463
497
|
if (state.ws) {
|
|
464
498
|
const ws = state.ws;
|
|
465
499
|
state.ws = null;
|
|
@@ -12840,5 +12874,6 @@ export {
|
|
|
12840
12874
|
bindVoiceReactiveSource,
|
|
12841
12875
|
bindVoiceProviderSimulationControls,
|
|
12842
12876
|
bindVoiceHTMX,
|
|
12843
|
-
bindVoiceBargeIn
|
|
12877
|
+
bindVoiceBargeIn,
|
|
12878
|
+
VoiceReconnectRejectedError
|
|
12844
12879
|
};
|
package/dist/embed/index.js
CHANGED
|
@@ -507,6 +507,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
507
507
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
508
508
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
509
509
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
510
|
+
|
|
511
|
+
class VoiceReconnectRejectedError extends Error {
|
|
512
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
513
|
+
super(message);
|
|
514
|
+
this.name = "VoiceReconnectRejectedError";
|
|
515
|
+
}
|
|
516
|
+
}
|
|
510
517
|
var noop = () => {};
|
|
511
518
|
var noopAsync = () => Promise.resolve();
|
|
512
519
|
var noopUnsubscribe = () => noop;
|
|
@@ -586,6 +593,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
586
593
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
587
594
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
588
595
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
596
|
+
let lifecycleGeneration = 0;
|
|
597
|
+
let disposed = false;
|
|
589
598
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
590
599
|
const state = {
|
|
591
600
|
isConnected: false,
|
|
@@ -649,6 +658,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
649
658
|
pendingCallControls.clear();
|
|
650
659
|
};
|
|
651
660
|
const scheduleReconnect = () => {
|
|
661
|
+
if (disposed)
|
|
662
|
+
return;
|
|
663
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
652
664
|
state.reconnectAttempts += 1;
|
|
653
665
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
654
666
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -663,6 +675,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
663
675
|
type: "connection"
|
|
664
676
|
});
|
|
665
677
|
state.reconnectTimeout = setTimeout(async () => {
|
|
678
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
679
|
+
return;
|
|
666
680
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
667
681
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
668
682
|
return;
|
|
@@ -674,7 +688,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
674
688
|
scenarioId: state.scenarioId,
|
|
675
689
|
sessionId: state.sessionId
|
|
676
690
|
});
|
|
677
|
-
} catch {
|
|
691
|
+
} catch (error) {
|
|
692
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
693
|
+
return;
|
|
694
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
695
|
+
emitTerminalFailure(error.message);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
678
698
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
679
699
|
scheduleReconnect();
|
|
680
700
|
} else {
|
|
@@ -682,10 +702,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
682
702
|
}
|
|
683
703
|
return;
|
|
684
704
|
}
|
|
705
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
706
|
+
return;
|
|
685
707
|
connect();
|
|
686
708
|
}, delayMs);
|
|
687
709
|
};
|
|
688
710
|
const connect = () => {
|
|
711
|
+
if (disposed)
|
|
712
|
+
return;
|
|
689
713
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
690
714
|
ws.binaryType = "arraybuffer";
|
|
691
715
|
ws.onopen = () => {
|
|
@@ -756,6 +780,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
756
780
|
state.ws = null;
|
|
757
781
|
state.isConnected = false;
|
|
758
782
|
clearTimers();
|
|
783
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
759
784
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
760
785
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
761
786
|
if (authorizationFailure) {
|
|
@@ -821,6 +846,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
821
846
|
send({ type: "end_turn" });
|
|
822
847
|
};
|
|
823
848
|
const callControl = (message) => {
|
|
849
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
850
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
851
|
+
}
|
|
824
852
|
const requestId = crypto.randomUUID();
|
|
825
853
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
826
854
|
const timeout = setTimeout(() => {
|
|
@@ -828,16 +856,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
828
856
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
829
857
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
830
858
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
831
|
-
send({
|
|
859
|
+
state.ws.send(JSON.stringify({
|
|
832
860
|
...message,
|
|
833
861
|
requestId,
|
|
834
862
|
type: "call_control"
|
|
835
|
-
});
|
|
863
|
+
}));
|
|
836
864
|
return promise;
|
|
837
865
|
};
|
|
838
866
|
const close = (reason = "client-close") => {
|
|
867
|
+
disposed = true;
|
|
868
|
+
lifecycleGeneration += 1;
|
|
839
869
|
clearTimers();
|
|
840
870
|
rejectPendingCallControls("Voice connection closed");
|
|
871
|
+
state.pendingMessages.length = 0;
|
|
841
872
|
if (state.ws) {
|
|
842
873
|
const ws = state.ws;
|
|
843
874
|
state.ws = null;
|
|
@@ -850,8 +881,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
850
881
|
listeners.clear();
|
|
851
882
|
};
|
|
852
883
|
const disconnect = () => {
|
|
884
|
+
disposed = true;
|
|
885
|
+
lifecycleGeneration += 1;
|
|
853
886
|
clearTimers();
|
|
854
887
|
rejectPendingCallControls("Voice connection disconnected");
|
|
888
|
+
state.pendingMessages.length = 0;
|
|
855
889
|
if (state.ws) {
|
|
856
890
|
const ws = state.ws;
|
|
857
891
|
state.ws = null;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
(()=>{var{defineProperty:x,getOwnPropertyNames:L0,getOwnPropertyDescriptor:G0}=Object,W0=Object.prototype.hasOwnProperty;function D0(g){return this[g]}var S0=(g)=>{var A=(f??=new WeakMap).get(g),C;if(A)return A;if(A=x({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of L0(g))if(!W0.call(A,V))x(A,V,{get:D0.bind(g,V),enumerable:!(C=G0(g,V))||C.enumerable})}return f.set(g,A),A},f;var y0=(g)=>g;function X0(g,A){this[g]=y0.bind(null,A)}var O0=(g,A)=>{for(var C in A)x(g,C,{get:A[C],enumerable:!0,configurable:!0,set:X0.bind(A,C)})};var e0={};O0(e0,{mount:()=>c0,default:()=>gg,VOICE_EMBED_VERSION:()=>_0});var J0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},Y0=(g,A,C,V)=>{let T=A??g.getAttribute("hx-get")??"";if(!T)return"";let c=new URL(T,window.location.origin);if(V)c.searchParams.set(C,V);else c.searchParams.delete(C);return`${c.pathname}${c.search}${c.hash}`},d=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=J0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",T=A.sessionQueryParam??"sessionId",c=()=>{let I=window,G=Y0(C,A.route,T,g.sessionId);if(G)C.setAttribute("hx-get",G);I.htmx?.process?.(C),I.htmx?.trigger?.(C,V)},U=g.subscribe(c);return c(),()=>{U()}};var Z0=(g)=>Math.max(-1,Math.min(1,g)),h0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=Z0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},w0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let T of C){let c=T/32768;V+=c*c}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},Q0=(g,A,C)=>{if(A===C)return g;let V=A/C,T=Math.round(g.length/V),c=new Float32Array(T),U=0,I=0;while(U<c.length){let G=Math.round((U+1)*V),W=0,_=0;for(let L=I;L<G&&L<g.length;L+=1)W+=g[L]??0,_+=1;c[U]=_>0?W/_:0,U+=1,I=G}return c},l=(g)=>{let A=null,C=null,V=null,T=null,c=async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let I=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!I)throw Error("Browser microphone capture requires AudioContext support.");try{if(T=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new I,A.state==="suspended")await A.resume();C=A.createMediaStreamSource(T),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(G)=>{let W=G.inputBuffer.getChannelData(0),_=Q0(W,A?.sampleRate??48000,g.sampleRateHz??16000),L=h0(_);g.onLevel?.(w0(L)),g.onAudio(L)},C.connect(V),V.connect(A.destination)}catch(G){throw U(),G}},U=()=>{V?.disconnect(),C?.disconnect(),T?.getTracks().forEach((I)=>I.stop()),A?.close(),g.onLevel?.(0),A=null,T=null,V=null,C=null};return{start:c,stop:U}};var k=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return k(A.error);if("cause"in A)return k(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},v=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:k(g.message),recoverable:g.recoverable,type:"error"};case"call_control_ack":return null;case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var _g=Math.PI*2;var P=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var P0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,M=(g)=>g.length===0?void 0:Math.max(...g);var X=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},B=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},J=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},F=(g)=>String(g.id??J(g,"ssrc")??X(g,"ssrc")??J(g,"trackIdentifier")??J(g,"mid")??"unknown"),i=(g)=>g===void 0?void 0:g*1000;var K0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var n=(g={})=>{let A=g.stats??[],C=[],V=A.filter((R)=>R.type==="inbound-rtp"&&J(R,"kind")!=="video"),T=A.filter((R)=>R.type==="outbound-rtp"&&J(R,"kind")!=="video"),c=A.filter((R)=>R.type==="candidate-pair"),U=A.filter((R)=>(R.type==="track"||R.type==="media-source")&&J(R,"kind")==="audio"),I=c.filter((R)=>B(R,"selected")===!0||B(R,"nominated")===!0||J(R,"state")==="succeeded").length,G=U.filter((R)=>J(R,"readyState")!=="ended"&&J(R,"trackState")!=="ended"&&B(R,"ended")!==!0).length,W=U.filter((R)=>J(R,"readyState")==="ended"||J(R,"trackState")==="ended"||B(R,"ended")===!0).length,_=V.reduce((R,O)=>R+(X(O,"packetsReceived")??0),0),L=T.reduce((R,O)=>R+(X(O,"packetsSent")??0),0),H=[...V,...T].reduce((R,O)=>R+Math.max(0,X(O,"packetsLost")??0),0),Z=_+H,E=Z===0?0:H/Z,D=V.reduce((R,O)=>R+(X(O,"bytesReceived")??0),0),h=T.reduce((R,O)=>R+(X(O,"bytesSent")??0),0),w=M(c.map((R)=>i(X(R,"currentRoundTripTime")??X(R,"roundTripTime"))).filter((R)=>R!==void 0)),Q=M([...V,...T].map((R)=>i(X(R,"jitter"))).filter((R)=>R!==void 0)),N=M(V.map((R)=>{let O=X(R,"jitterBufferDelay"),b=X(R,"jitterBufferEmittedCount");return O!==void 0&&b!==void 0&&b>0?O/b*1000:void 0}).filter((R)=>R!==void 0)),z=U.map((R)=>X(R,"audioLevel")).filter((R)=>R!==void 0);if(g.requireConnectedCandidatePair&&c.length>0&&I===0)P(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&G===0)P(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&E>g.maxPacketLossRatio)P(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(E)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&w!==void 0&&w>g.maxRoundTripTimeMs)P(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(w)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&Q!==void 0&&Q>g.maxJitterMs)P(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(Q)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:I,audioLevelAverage:P0(z),bytesReceived:D,bytesSent:h,checkedAt:Date.now(),endedAudioTracks:W,inboundPackets:_,issues:C,jitterBufferDelayMs:N,jitterMs:Q,liveAudioTracks:G,outboundPackets:L,packetLossRatio:E,packetsLost:H,roundTripTimeMs:w,status:C.some((R)=>R.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},o=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(K0)};var m=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],T=new Map(C.map((H)=>[F(H),H])),U=A.filter((H)=>(H.type==="inbound-rtp"||H.type==="outbound-rtp")&&J(H,"kind")!=="video"&&J(H,"mediaType")!=="video").map((H)=>{let Z=H.type==="outbound-rtp"?"outbound":"inbound",E=Z==="outbound"?"packetsSent":"packetsReceived",D=Z==="outbound"?"bytesSent":"bytesReceived",h=T.get(F(H)),w=X(H,E),Q=h?X(h,E):void 0,N=X(H,D),z=h?X(h,D):void 0,R=H.timestamp!==void 0&&h?.timestamp!==void 0?H.timestamp-h.timestamp:void 0;return{bytesDelta:N!==void 0&&z!==void 0?N-z:void 0,currentPackets:w,direction:Z,id:F(H),packetDelta:w!==void 0&&Q!==void 0?w-Q:void 0,previousPackets:Q,timeDeltaMs:R}}),I=U.filter((H)=>H.direction==="inbound"),G=U.filter((H)=>H.direction==="outbound"),W=M(U.map((H)=>H.timeDeltaMs).filter((H)=>H!==void 0)),_=I.filter((H)=>g.maxInboundPacketStallMs!==void 0&&H.timeDeltaMs!==void 0&&H.timeDeltaMs>=g.maxInboundPacketStallMs&&H.packetDelta!==void 0&&H.packetDelta<=0).length,L=G.filter((H)=>g.maxOutboundPacketStallMs!==void 0&&H.timeDeltaMs!==void 0&&H.timeDeltaMs>=g.maxOutboundPacketStallMs&&H.packetDelta!==void 0&&H.packetDelta<=0).length;if(g.requireInboundAudio&&I.length===0)P(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&G.length===0)P(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&W!==void 0&&W>g.maxGapMs)P(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(W)}ms above ${String(g.maxGapMs)}ms.`);if(_>0)P(V,"error","media.webrtc_inbound_stalled",`${String(_)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(L>0)P(V,"error","media.webrtc_outbound_stalled",`${String(L)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:I.length,issues:V,maxObservedGapMs:W,outboundAudioStreams:G.length,stalledInboundStreams:_,stalledOutboundStreams:L,status:V.some((H)=>H.severity==="error")?"fail":V.length>0?"warn":"pass",streams:U,totalStats:A.length}};var E0="/api/voice/browser-media",N0=5000,q0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,z0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??E0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(g)=>{let A=null,C=[],V=async()=>{let U=await q0(g);if(!U)return;let I=await o({peerConnection:U}),G=n({...g,stats:I}),W=g.continuity===!1?void 0:m({...g.continuity,previousStats:C,stats:I}),_={at:Date.now(),continuity:W,report:G,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=I,g.onReport?.(_),await z0(_,g),_},T=()=>{V().catch((U)=>{g.onError?.(U)})},c=()=>{if(A)clearInterval(A),A=null};return{close:c,reportOnce:V,stop:c,start:()=>{if(A)return;T(),A=setInterval(T,g.intervalMs??N0)}}};var b0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var q=()=>{},B0=()=>Promise.resolve(),M0=()=>q,j0={callControl:B0,close:q,disconnect:q,endTurn:q,send:q,sendAudio:q,simulateDisconnect:q,subscribe:M0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},x0=()=>crypto.randomUUID(),k0=(g,A,C,V)=>{let{hostname:T,port:c,protocol:U}=window.location,I=U==="https:"?"wss:":"ws:",G=c?`:${c}`:"",W=new URL(`${I}//${T}${G}${g}`);if(W.searchParams.set("sessionId",A),C)W.searchParams.set("scenarioId",C);for(let[_,L]of Object.entries(V))if(_!=="sessionId"&&_!=="scenarioId"&&L)W.searchParams.set(_,L);return W.toString()},F0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"call_control_ack":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},f0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return F0(A)?A:null}catch{return null}},u=(g,A={})=>{if(typeof window>"u")return j0;let C=new Set,V=new Map,T=A.reconnect!==!1,c=A.maxReconnectAttempts??15,U=A.reconnectMaxDelayMs??8000,I=A.pingInterval??30000,G=A.reconnectResetAfterMs??30000,W=($)=>b0($,500,U),_={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:A.sessionId??x0(),ws:null},L=($)=>{C.forEach((S)=>S($))},H=($)=>{C.forEach((S)=>S({message:$,recoverable:!1,type:"error"})),L({reconnect:{attempts:_.reconnectAttempts,maxAttempts:c,status:"exhausted"},type:"connection"})},Z=()=>{if(_.pingInterval)clearInterval(_.pingInterval),_.pingInterval=null;if(_.reconnectTimeout)clearTimeout(_.reconnectTimeout),_.reconnectTimeout=null;if(_.reconnectResetTimeout)clearTimeout(_.reconnectResetTimeout),_.reconnectResetTimeout=null},E=()=>{if(_.ws?.readyState!==1)return;while(_.pendingMessages.length>0){let $=_.pendingMessages.shift();if($!==void 0)_.ws.send($)}},D=($)=>{for(let S of V.values())clearTimeout(S.timeout),S.reject(Error($));V.clear()},h=()=>{_.reconnectAttempts+=1;let $=W(_.reconnectAttempts),S=Date.now()+$;L({reconnect:{attempts:_.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:c,nextAttemptAt:S,status:"reconnecting"},type:"connection"}),_.reconnectTimeout=setTimeout(async()=>{if(_.reconnectAttempts>c){H("Voice connection could not be restored.");return}try{await A.prepareReconnect?.({attempt:_.reconnectAttempts,path:g,scenarioId:_.scenarioId,sessionId:_.sessionId})}catch{if(_.reconnectAttempts<c)h();else H("Voice authorization could not be renewed for reconnect.");return}w()},$)},w=()=>{let $=new WebSocket(k0(g,_.sessionId,_.scenarioId,A.query??{}));$.binaryType="arraybuffer",$.onopen=()=>{if(_.ws!==$){$.close(1000);return}let S=_.reconnectAttempts>0;if(_.isConnected=!0,E(),S)L({reconnect:{attempts:_.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:c,status:"resumed"},type:"connection"}),_.reconnectResetTimeout=setTimeout(()=>{if(_.ws===$&&$.readyState===1)_.reconnectAttempts=0;_.reconnectResetTimeout=null},G);C.forEach((y)=>y({scenarioId:_.scenarioId??void 0,sessionId:_.sessionId,status:"active",type:"session"})),_.pingInterval=setInterval(()=>{if($.readyState===1)$.send(JSON.stringify({type:"ping"}))},I)},$.onmessage=(S)=>{if(_.ws!==$)return;let y=f0(S);if(!y)return;if(y.type==="session")_.sessionId=y.sessionId,_.scenarioId=y.scenarioId??_.scenarioId;if(y.type==="call_control_ack"){let Y=V.get(y.requestId);if(Y)if(clearTimeout(Y.timeout),V.delete(y.requestId),y.ok)Y.resolve();else Y.reject(Error(y.message??`${y.action} was not applied`))}C.forEach((Y)=>Y(y))},$.onclose=(S)=>{if(_.ws!==$)return;_.ws=null,_.isConnected=!1,Z();let y=T&&S.code!==1000&&S.code!==4401&&S.code!==4403&&_.reconnectAttempts<c,Y=S.code===4401||S.code===4403;if(Y)C.forEach((j)=>j({message:S.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(y)h();else if(T&&S.code!==1000)if(Y)L({reconnect:{attempts:_.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:c,status:"exhausted"},type:"connection"});else H("Voice connection could not be restored.")},_.ws=$},Q=($)=>{if(_.ws?.readyState===1){_.ws.send($);return}if(typeof $!=="string")return;if(_.pendingMessages.length>=100)_.pendingMessages.shift();_.pendingMessages.push($)},N=($)=>{Q(JSON.stringify($))},z=($={})=>{if($.sessionId)_.sessionId=$.sessionId;if($.scenarioId)_.scenarioId=$.scenarioId;N({scenarioId:_.scenarioId??void 0,sessionId:_.sessionId,type:"start"})},R=($)=>{Q($)},O=()=>{N({type:"end_turn"})},b=($)=>{let S=crypto.randomUUID(),{promise:y,reject:Y,resolve:j}=Promise.withResolvers(),U0=setTimeout(()=>{V.delete(S),Y(Error(`${$.action} acknowledgement timed out`))},5000);return V.set(S,{reject:Y,resolve:j,timeout:U0}),N({...$,requestId:S,type:"call_control"}),y},R0=($="client-close")=>{if(Z(),D("Voice connection closed"),_.ws){let S=_.ws;if(_.ws=null,S.readyState===1)S.send(JSON.stringify({reason:$,type:"close"}));S.close(1000)}_.isConnected=!1,C.clear()},$0=()=>{if(Z(),D("Voice connection disconnected"),_.ws){let $=_.ws;_.ws=null,$.close(1000)}_.isConnected=!1,C.clear()},H0=()=>{if(_.ws?.readyState===1)_.ws.close(4000,"absolutejs-voice-reconnect-proof")},I0=($)=>{return C.add($),()=>{C.delete($)}};return w(),{callControl:b,close:R0,disconnect:$0,endTurn:O,send:N,sendAudio:R,simulateDisconnect:H0,start:z,subscribe:I0,getReadyState:()=>_.ws?.readyState??3,getScenarioId:()=>_.scenarioId??"",getSessionId:()=>_.sessionId}};var d0=()=>({attempts:0,maxAttempts:0,status:"idle"}),l0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},v0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},i0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:d0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),s=()=>{let g=i0(),A="",C=new Set,V=()=>{C.forEach((c)=>c())};return{dispatch:(c)=>{switch(c.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:c.chunk,format:c.format,receivedAt:c.receivedAt,turnId:c.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,c.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${c.delta}`};break;case"complete":g={...g,sessionId:c.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:c.event.type==="end"?c.event.disposition:g.call?.disposition,endedAt:c.event.type==="end"?c.event.at:g.call?.endedAt,events:[...g.call?.events??[],c.event],lastEventAt:c.event.at,startedAt:g.call?.startedAt??c.event.at},sessionId:c.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:c.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:c.message,...c.recoverable===!1?{isConnected:!1,status:"failed"}:{}};break;case"final":A=l0(A,c.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:v0(A,c.transcript.text)};break;case"replay":A=c.partial,g={...g,assistantStreamingText:"",assistantTexts:[...c.assistantTexts],call:c.call??null,error:null,isConnected:c.status==="active",partial:c.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:c.scenarioId??g.scenarioId,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata??g.sessionMetadata,status:c.status,turns:[...c.turns]};break;case"session":g={...g,error:null,scenarioId:c.scenarioId??g.scenarioId,isConnected:c.status==="active",sessionId:c.sessionId,sessionMetadata:c.sessionMetadata??g.sessionMetadata,status:c.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,c.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(c)=>{return C.add(c),()=>{C.delete(c)}}}};var a=(g,A={})=>{let C=u(g,A),V=s(),T=A.browserMedia&&typeof window<"u"?r({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,c=new Set,U=(_)=>Promise.resolve().then(()=>{if(!_?.sessionId&&!_?.scenarioId)return;C.start(_),T?.start()}),I=()=>{c.forEach((_)=>_())},G=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let _=V.getSnapshot(),L=JSON.stringify({at:Date.now(),reconnect:_.reconnect,scenarioId:_.scenarioId,sessionId:C.getSessionId(),turnIds:_.turns.map((H)=>H.id)});fetch(A.reconnectReportPath,{body:L,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},W=C.subscribe((_)=>{let L=v(_);if(L){if(V.dispatch(L),_.type==="connection")G();I()}});return{start:U,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl(_){return C.callControl(_)},close(_){W(),T?.close(),C.close(_),V.dispatch({type:"disconnected"}),I()},disconnect(){W(),T?.close(),C.disconnect(),V.dispatch({type:"disconnected"}),I()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio(_){C.sendAudio(_)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe(_){return c.add(_),()=>{c.delete(_)}},get turns(){return V.getSnapshot().turns}}};var p=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var n0={balanced:{qualityProfile:"general",minSilenceMs:400,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",minSilenceMs:300,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",minSilenceMs:600,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},o0={"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},general:{},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var t=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=n0[A],T=o0[C],c=g?.silenceMs??T.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(c,g?.minSilenceMs??T.minSilenceMs??V.minSilenceMs),silenceMs:c,speechThreshold:g?.speechThreshold??T.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??T.transcriptStabilityMs??V.transcriptStabilityMs}};var m0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},e=(g="default")=>{let A=m0[g];return{audioConditioning:p(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:t(A.turnDetection)}};var r0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),g0=(g,A={})=>{let C=e(A.preset),V=a(g,{...C.connection,...A.connection}),T=null,c=r0(V),U=new Set,I=()=>{for(let D of U)D()},G=()=>{if(c={...c,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&c.status==="completed"&&c.isRecording)T?.stop(),T=null,c={...c,isRecording:!1};I()},W=V.subscribe(G);G();let _=()=>{if(T)return T;return T=l({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(D)=>{if(A.capture?.onAudio){A.capture.onAudio(D,V.sendAudio);return}V.sendAudio(D)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),T},L=()=>{T?.stop(),T=null,c={...c,isRecording:!1},I()},H=async()=>{if(c.isRecording)return;try{c={...c,recordingError:null},I(),await _().start(),c={...c,isRecording:!0},I()}catch(D){throw T?.stop(),T=null,c={...c,isRecording:!1,recordingError:D instanceof Error?D.message:String(D)},I(),D}};return{close:(D)=>{W(),L(),V.close(D)},disconnect:()=>{W(),L(),V.disconnect()},startRecording:H,stopRecording:L,get assistantAudio(){return c.assistantAudio},get assistantTexts(){return c.assistantTexts},get assistantStreamingText(){return c.assistantStreamingText},bindHTMX(D){return d(V,D)},get call(){return c.call},callControl:(D)=>V.callControl(D),endTurn:()=>V.endTurn(),get error(){return c.error},getServerSnapshot:()=>c,getSnapshot:()=>c,get isConnected(){return c.isConnected},get isRecording(){return c.isRecording},get partial(){return c.partial},get reconnect(){return c.reconnect},get recordingError(){return c.recordingError},get scenarioId(){return c.scenarioId},sendAudio:(D)=>V.sendAudio(D),get sessionId(){return c.sessionId},get sessionMetadata(){return c.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return c.status},subscribe:(D)=>{return U.add(D),()=>{U.delete(D)}},toggleRecording:async()=>{if(c.isRecording){L();return}await H()},get turns(){return c.turns}}};var K=(g)=>String(g).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var A0=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var u0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},s0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},a0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},V0=(g)=>{let A={...u0,...g.theme},C={...s0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,T=g.state.turns.at(-1)?.committedAt,c=A0({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:T}),U=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,I=g.state.error?"Error":U?C.connecting:g.state.status==="completed"?C.callEnded:a0(c,C);return{agentState:c,classes:{container:`absolute-voice-widget absolute-voice-widget--${c}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:I,theme:A,title:g.title??"Voice"}},p0=(g)=>typeof g==="number"?`${g}px`:g,C0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${p0(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,T=[];if(g.controls.canStart)T.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${K(g.labels.startCall)}</button>`);if(g.controls.canMute)T.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${K(g.labels.mute)}</button>`);if(g.controls.canEnd)T.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${K(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${K(g.classes.container)}" style="${C}">
|
|
1
|
+
(()=>{var{defineProperty:x,getOwnPropertyNames:G0,getOwnPropertyDescriptor:S0}=Object,X0=Object.prototype.hasOwnProperty;function O0(g){return this[g]}var y0=(g)=>{var A=(f??=new WeakMap).get(g),C;if(A)return A;if(A=x({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of G0(g))if(!X0.call(A,V))x(A,V,{get:O0.bind(g,V),enumerable:!(C=S0(g,V))||C.enumerable})}return f.set(g,A),A},f;var J0=(g)=>g;function Y0(g,A){this[g]=J0.bind(null,A)}var Z0=(g,A)=>{for(var C in A)x(g,C,{get:A[C],enumerable:!0,configurable:!0,set:Y0.bind(A,C)})};var Vg={};Z0(Vg,{mount:()=>T0,default:()=>Cg,VOICE_EMBED_VERSION:()=>$0});var w0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},h0=(g,A,C,V)=>{let $=A??g.getAttribute("hx-get")??"";if(!$)return"";let _=new URL($,window.location.origin);if(V)_.searchParams.set(C,V);else _.searchParams.delete(C);return`${_.pathname}${_.search}${_.hash}`},d=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=w0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",$=A.sessionQueryParam??"sessionId",_=()=>{let U=window,c=h0(C,A.route,$,g.sessionId);if(c)C.setAttribute("hx-get",c);U.htmx?.process?.(C),U.htmx?.trigger?.(C,V)},L=g.subscribe(_);return _(),()=>{L()}};var Q0=(g)=>Math.max(-1,Math.min(1,g)),K0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=Q0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},P0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let $ of C){let _=$/32768;V+=_*_}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},N0=(g,A,C)=>{if(A===C)return g;let V=A/C,$=Math.round(g.length/V),_=new Float32Array($),L=0,U=0;while(L<_.length){let c=Math.round((L+1)*V),W=0,R=0;for(let S=U;S<c&&S<g.length;S+=1)W+=g[S]??0,R+=1;_[L]=R>0?W/R:0,L+=1,U=c}return _},l=(g)=>{let A=null,C=null,V=null,$=null,_=async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let U=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!U)throw Error("Browser microphone capture requires AudioContext support.");try{if($=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new U,A.state==="suspended")await A.resume();C=A.createMediaStreamSource($),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(c)=>{let W=c.inputBuffer.getChannelData(0),R=N0(W,A?.sampleRate??48000,g.sampleRateHz??16000),S=K0(R);g.onLevel?.(P0(S)),g.onAudio(S)},C.connect(V),V.connect(A.destination)}catch(c){throw L(),c}},L=()=>{V?.disconnect(),C?.disconnect(),$?.getTracks().forEach((U)=>U.stop()),A?.close(),g.onLevel?.(0),A=null,$=null,V=null,C=null};return{start:_,stop:L}};var k=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return k(A.error);if("cause"in A)return k(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},v=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:k(g.message),recoverable:g.recoverable,type:"error"};case"call_control_ack":return null;case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var Rg=Math.PI*2;var P=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var E0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,M=(g)=>g.length===0?void 0:Math.max(...g);var O=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},B=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},Y=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},F=(g)=>String(g.id??Y(g,"ssrc")??O(g,"ssrc")??Y(g,"trackIdentifier")??Y(g,"mid")??"unknown"),i=(g)=>g===void 0?void 0:g*1000;var q0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var n=(g={})=>{let A=g.stats??[],C=[],V=A.filter((H)=>H.type==="inbound-rtp"&&Y(H,"kind")!=="video"),$=A.filter((H)=>H.type==="outbound-rtp"&&Y(H,"kind")!=="video"),_=A.filter((H)=>H.type==="candidate-pair"),L=A.filter((H)=>(H.type==="track"||H.type==="media-source")&&Y(H,"kind")==="audio"),U=_.filter((H)=>B(H,"selected")===!0||B(H,"nominated")===!0||Y(H,"state")==="succeeded").length,c=L.filter((H)=>Y(H,"readyState")!=="ended"&&Y(H,"trackState")!=="ended"&&B(H,"ended")!==!0).length,W=L.filter((H)=>Y(H,"readyState")==="ended"||Y(H,"trackState")==="ended"||B(H,"ended")===!0).length,R=V.reduce((H,J)=>H+(O(J,"packetsReceived")??0),0),S=$.reduce((H,J)=>H+(O(J,"packetsSent")??0),0),T=[...V,...$].reduce((H,J)=>H+Math.max(0,O(J,"packetsLost")??0),0),Z=R+T,w=Z===0?0:T/Z,D=V.reduce((H,J)=>H+(O(J,"bytesReceived")??0),0),K=$.reduce((H,J)=>H+(O(J,"bytesSent")??0),0),h=M(_.map((H)=>i(O(H,"currentRoundTripTime")??O(H,"roundTripTime"))).filter((H)=>H!==void 0)),Q=M([...V,...$].map((H)=>i(O(H,"jitter"))).filter((H)=>H!==void 0)),E=M(V.map((H)=>{let J=O(H,"jitterBufferDelay"),b=O(H,"jitterBufferEmittedCount");return J!==void 0&&b!==void 0&&b>0?J/b*1000:void 0}).filter((H)=>H!==void 0)),q=L.map((H)=>O(H,"audioLevel")).filter((H)=>H!==void 0);if(g.requireConnectedCandidatePair&&_.length>0&&U===0)P(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&c===0)P(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&w>g.maxPacketLossRatio)P(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(w)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&h!==void 0&&h>g.maxRoundTripTimeMs)P(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(h)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&Q!==void 0&&Q>g.maxJitterMs)P(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(Q)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:U,audioLevelAverage:E0(q),bytesReceived:D,bytesSent:K,checkedAt:Date.now(),endedAudioTracks:W,inboundPackets:R,issues:C,jitterBufferDelayMs:E,jitterMs:Q,liveAudioTracks:c,outboundPackets:S,packetLossRatio:w,packetsLost:T,roundTripTimeMs:h,status:C.some((H)=>H.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},o=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(q0)};var m=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],$=new Map(C.map((T)=>[F(T),T])),L=A.filter((T)=>(T.type==="inbound-rtp"||T.type==="outbound-rtp")&&Y(T,"kind")!=="video"&&Y(T,"mediaType")!=="video").map((T)=>{let Z=T.type==="outbound-rtp"?"outbound":"inbound",w=Z==="outbound"?"packetsSent":"packetsReceived",D=Z==="outbound"?"bytesSent":"bytesReceived",K=$.get(F(T)),h=O(T,w),Q=K?O(K,w):void 0,E=O(T,D),q=K?O(K,D):void 0,H=T.timestamp!==void 0&&K?.timestamp!==void 0?T.timestamp-K.timestamp:void 0;return{bytesDelta:E!==void 0&&q!==void 0?E-q:void 0,currentPackets:h,direction:Z,id:F(T),packetDelta:h!==void 0&&Q!==void 0?h-Q:void 0,previousPackets:Q,timeDeltaMs:H}}),U=L.filter((T)=>T.direction==="inbound"),c=L.filter((T)=>T.direction==="outbound"),W=M(L.map((T)=>T.timeDeltaMs).filter((T)=>T!==void 0)),R=U.filter((T)=>g.maxInboundPacketStallMs!==void 0&&T.timeDeltaMs!==void 0&&T.timeDeltaMs>=g.maxInboundPacketStallMs&&T.packetDelta!==void 0&&T.packetDelta<=0).length,S=c.filter((T)=>g.maxOutboundPacketStallMs!==void 0&&T.timeDeltaMs!==void 0&&T.timeDeltaMs>=g.maxOutboundPacketStallMs&&T.packetDelta!==void 0&&T.packetDelta<=0).length;if(g.requireInboundAudio&&U.length===0)P(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&c.length===0)P(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&W!==void 0&&W>g.maxGapMs)P(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(W)}ms above ${String(g.maxGapMs)}ms.`);if(R>0)P(V,"error","media.webrtc_inbound_stalled",`${String(R)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(S>0)P(V,"error","media.webrtc_outbound_stalled",`${String(S)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:U.length,issues:V,maxObservedGapMs:W,outboundAudioStreams:c.length,stalledInboundStreams:R,stalledOutboundStreams:S,status:V.some((T)=>T.severity==="error")?"fail":V.length>0?"warn":"pass",streams:L,totalStats:A.length}};var z0="/api/voice/browser-media",b0=5000,B0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,M0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??z0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(g)=>{let A=null,C=[],V=async()=>{let L=await B0(g);if(!L)return;let U=await o({peerConnection:L}),c=n({...g,stats:U}),W=g.continuity===!1?void 0:m({...g.continuity,previousStats:C,stats:U}),R={at:Date.now(),continuity:W,report:c,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=U,g.onReport?.(R),await M0(R,g),R},$=()=>{V().catch((L)=>{g.onError?.(L)})},_=()=>{if(A)clearInterval(A),A=null};return{close:_,reportOnce:V,stop:_,start:()=>{if(A)return;$(),A=setInterval($,g.intervalMs??b0)}}};var j0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));class u extends Error{constructor(g="Voice authorization could not be renewed."){super(g);this.name="VoiceReconnectRejectedError"}}var z=()=>{},x0=()=>Promise.resolve(),k0=()=>z,F0={callControl:x0,close:z,disconnect:z,endTurn:z,send:z,sendAudio:z,simulateDisconnect:z,subscribe:k0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},f0=()=>crypto.randomUUID(),d0=(g,A,C,V)=>{let{hostname:$,port:_,protocol:L}=window.location,U=L==="https:"?"wss:":"ws:",c=_?`:${_}`:"",W=new URL(`${U}//${$}${c}${g}`);if(W.searchParams.set("sessionId",A),C)W.searchParams.set("scenarioId",C);for(let[R,S]of Object.entries(V))if(R!=="sessionId"&&R!=="scenarioId"&&S)W.searchParams.set(R,S);return W.toString()},l0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"call_control_ack":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},v0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return l0(A)?A:null}catch{return null}},s=(g,A={})=>{if(typeof window>"u")return F0;let C=new Set,V=new Map,$=A.reconnect!==!1,_=A.maxReconnectAttempts??15,L=A.reconnectMaxDelayMs??8000,U=A.pingInterval??30000,c=A.reconnectResetAfterMs??30000,W=0,R=!1,S=(I)=>j0(I,500,L),T={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:A.sessionId??f0(),ws:null},Z=(I)=>{C.forEach((G)=>G(I))},w=(I)=>{C.forEach((G)=>G({message:I,recoverable:!1,type:"error"})),Z({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"})},D=()=>{if(T.pingInterval)clearInterval(T.pingInterval),T.pingInterval=null;if(T.reconnectTimeout)clearTimeout(T.reconnectTimeout),T.reconnectTimeout=null;if(T.reconnectResetTimeout)clearTimeout(T.reconnectResetTimeout),T.reconnectResetTimeout=null},K=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let I=T.pendingMessages.shift();if(I!==void 0)T.ws.send(I)}},h=(I)=>{for(let G of V.values())clearTimeout(G.timeout),G.reject(Error(I));V.clear()},Q=()=>{if(R)return;let I=W;T.reconnectAttempts+=1;let G=S(T.reconnectAttempts),X=Date.now()+G;Z({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:X,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(async()=>{if(R||W!==I)return;if(T.reconnectAttempts>_){w("Voice connection could not be restored.");return}try{await A.prepareReconnect?.({attempt:T.reconnectAttempts,path:g,scenarioId:T.scenarioId,sessionId:T.sessionId})}catch(y){if(R||W!==I)return;if(y instanceof u){w(y.message);return}if(T.reconnectAttempts<_)Q();else w("Voice authorization could not be renewed for reconnect.");return}if(R||W!==I)return;E()},G)},E=()=>{if(R)return;let I=new WebSocket(d0(g,T.sessionId,T.scenarioId,A.query??{}));I.binaryType="arraybuffer",I.onopen=()=>{if(T.ws!==I){I.close(1000);return}let G=T.reconnectAttempts>0;if(T.isConnected=!0,K(),G)Z({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),T.reconnectResetTimeout=setTimeout(()=>{if(T.ws===I&&I.readyState===1)T.reconnectAttempts=0;T.reconnectResetTimeout=null},c);C.forEach((X)=>X({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(I.readyState===1)I.send(JSON.stringify({type:"ping"}))},U)},I.onmessage=(G)=>{if(T.ws!==I)return;let X=v0(G);if(!X)return;if(X.type==="session")T.sessionId=X.sessionId,T.scenarioId=X.scenarioId??T.scenarioId;if(X.type==="call_control_ack"){let y=V.get(X.requestId);if(y)if(clearTimeout(y.timeout),V.delete(X.requestId),X.ok)y.resolve();else y.reject(Error(X.message??`${X.action} was not applied`))}C.forEach((y)=>y(X))},I.onclose=(G)=>{if(T.ws!==I)return;T.ws=null,T.isConnected=!1,D(),h("Voice connection interrupted");let X=$&&G.code!==1000&&G.code!==4401&&G.code!==4403&&T.reconnectAttempts<_,y=G.code===4401||G.code===4403;if(y)C.forEach((j)=>j({message:G.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(X)Q();else if($&&G.code!==1000)if(y)Z({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"});else w("Voice connection could not be restored.")},T.ws=I},q=(I)=>{if(T.ws?.readyState===1){T.ws.send(I);return}if(typeof I!=="string")return;if(T.pendingMessages.length>=100)T.pendingMessages.shift();T.pendingMessages.push(I)},H=(I)=>{q(JSON.stringify(I))},J=(I={})=>{if(I.sessionId)T.sessionId=I.sessionId;if(I.scenarioId)T.scenarioId=I.scenarioId;H({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},b=(I)=>{q(I)},R0=()=>{H({type:"end_turn"})},I0=(I)=>{if(T.ws?.readyState!==1)return Promise.reject(Error(`${I.action} is unavailable while disconnected`));let G=crypto.randomUUID(),{promise:X,reject:y,resolve:j}=Promise.withResolvers(),D0=setTimeout(()=>{V.delete(G),y(Error(`${I.action} acknowledgement timed out`))},5000);return V.set(G,{reject:y,resolve:j,timeout:D0}),T.ws.send(JSON.stringify({...I,requestId:G,type:"call_control"})),X},U0=(I="client-close")=>{if(R=!0,W+=1,D(),h("Voice connection closed"),T.pendingMessages.length=0,T.ws){let G=T.ws;if(T.ws=null,G.readyState===1)G.send(JSON.stringify({reason:I,type:"close"}));G.close(1000)}T.isConnected=!1,C.clear()},L0=()=>{if(R=!0,W+=1,D(),h("Voice connection disconnected"),T.pendingMessages.length=0,T.ws){let I=T.ws;T.ws=null,I.close(1000)}T.isConnected=!1,C.clear()},W0=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},c0=(I)=>{return C.add(I),()=>{C.delete(I)}};return E(),{callControl:I0,close:U0,disconnect:L0,endTurn:R0,send:H,sendAudio:b,simulateDisconnect:W0,start:J,subscribe:c0,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var i0=()=>({attempts:0,maxAttempts:0,status:"idle"}),n0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},o0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},m0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:i0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),a=()=>{let g=m0(),A="",C=new Set,V=()=>{C.forEach((_)=>_())};return{dispatch:(_)=>{switch(_.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:_.chunk,format:_.format,receivedAt:_.receivedAt,turnId:_.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,_.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${_.delta}`};break;case"complete":g={...g,sessionId:_.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:_.event.type==="end"?_.event.disposition:g.call?.disposition,endedAt:_.event.type==="end"?_.event.at:g.call?.endedAt,events:[...g.call?.events??[],_.event],lastEventAt:_.event.at,startedAt:g.call?.startedAt??_.event.at},sessionId:_.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:_.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:_.message,..._.recoverable===!1?{isConnected:!1,status:"failed"}:{}};break;case"final":A=n0(A,_.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:o0(A,_.transcript.text)};break;case"replay":A=_.partial,g={...g,assistantStreamingText:"",assistantTexts:[..._.assistantTexts],call:_.call??null,error:null,isConnected:_.status==="active",partial:_.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:_.scenarioId??g.scenarioId,sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??g.sessionMetadata,status:_.status,turns:[..._.turns]};break;case"session":g={...g,error:null,scenarioId:_.scenarioId??g.scenarioId,isConnected:_.status==="active",sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??g.sessionMetadata,status:_.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,_.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(_)=>{return C.add(_),()=>{C.delete(_)}}}};var p=(g,A={})=>{let C=s(g,A),V=a(),$=A.browserMedia&&typeof window<"u"?r({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,_=new Set,L=(R)=>Promise.resolve().then(()=>{if(!R?.sessionId&&!R?.scenarioId)return;C.start(R),$?.start()}),U=()=>{_.forEach((R)=>R())},c=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let R=V.getSnapshot(),S=JSON.stringify({at:Date.now(),reconnect:R.reconnect,scenarioId:R.scenarioId,sessionId:C.getSessionId(),turnIds:R.turns.map((T)=>T.id)});fetch(A.reconnectReportPath,{body:S,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},W=C.subscribe((R)=>{let S=v(R);if(S){if(V.dispatch(S),R.type==="connection")c();U()}});return{start:L,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl(R){return C.callControl(R)},close(R){W(),$?.close(),C.close(R),V.dispatch({type:"disconnected"}),U()},disconnect(){W(),$?.close(),C.disconnect(),V.dispatch({type:"disconnected"}),U()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio(R){C.sendAudio(R)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe(R){return _.add(R),()=>{_.delete(R)}},get turns(){return V.getSnapshot().turns}}};var t=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var r0={balanced:{qualityProfile:"general",minSilenceMs:400,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",minSilenceMs:300,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",minSilenceMs:600,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},u0={"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},general:{},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var e=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=r0[A],$=u0[C],_=g?.silenceMs??$.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(_,g?.minSilenceMs??$.minSilenceMs??V.minSilenceMs),silenceMs:_,speechThreshold:g?.speechThreshold??$.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??$.transcriptStabilityMs??V.transcriptStabilityMs}};var s0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},g0=(g="default")=>{let A=s0[g];return{audioConditioning:t(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:e(A.turnDetection)}};var a0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),A0=(g,A={})=>{let C=g0(A.preset),V=p(g,{...C.connection,...A.connection}),$=null,_=a0(V),L=new Set,U=()=>{for(let D of L)D()},c=()=>{if(_={..._,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&_.status==="completed"&&_.isRecording)$?.stop(),$=null,_={..._,isRecording:!1};U()},W=V.subscribe(c);c();let R=()=>{if($)return $;return $=l({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(D)=>{if(A.capture?.onAudio){A.capture.onAudio(D,V.sendAudio);return}V.sendAudio(D)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),$},S=()=>{$?.stop(),$=null,_={..._,isRecording:!1},U()},T=async()=>{if(_.isRecording)return;try{_={..._,recordingError:null},U(),await R().start(),_={..._,isRecording:!0},U()}catch(D){throw $?.stop(),$=null,_={..._,isRecording:!1,recordingError:D instanceof Error?D.message:String(D)},U(),D}};return{close:(D)=>{W(),S(),V.close(D)},disconnect:()=>{W(),S(),V.disconnect()},startRecording:T,stopRecording:S,get assistantAudio(){return _.assistantAudio},get assistantTexts(){return _.assistantTexts},get assistantStreamingText(){return _.assistantStreamingText},bindHTMX(D){return d(V,D)},get call(){return _.call},callControl:(D)=>V.callControl(D),endTurn:()=>V.endTurn(),get error(){return _.error},getServerSnapshot:()=>_,getSnapshot:()=>_,get isConnected(){return _.isConnected},get isRecording(){return _.isRecording},get partial(){return _.partial},get reconnect(){return _.reconnect},get recordingError(){return _.recordingError},get scenarioId(){return _.scenarioId},sendAudio:(D)=>V.sendAudio(D),get sessionId(){return _.sessionId},get sessionMetadata(){return _.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return _.status},subscribe:(D)=>{return L.add(D),()=>{L.delete(D)}},toggleRecording:async()=>{if(_.isRecording){S();return}await T()},get turns(){return _.turns}}};var N=(g)=>String(g).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var V0=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var p0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},t0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},e0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},C0=(g)=>{let A={...p0,...g.theme},C={...t0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,$=g.state.turns.at(-1)?.committedAt,_=V0({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:$}),L=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,U=g.state.error?"Error":L?C.connecting:g.state.status==="completed"?C.callEnded:e0(_,C);return{agentState:_,classes:{container:`absolute-voice-widget absolute-voice-widget--${_}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:U,theme:A,title:g.title??"Voice"}},gg=(g)=>typeof g==="number"?`${g}px`:g,_0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${gg(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,$=[];if(g.controls.canStart)$.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(g.labels.startCall)}</button>`);if(g.controls.canMute)$.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(g.labels.mute)}</button>`);if(g.controls.canEnd)$.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${N(g.classes.container)}" style="${C}">
|
|
2
2
|
<div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
|
|
3
|
-
<span aria-hidden="true" class="${
|
|
4
|
-
<strong style="font-size:15px;">${
|
|
5
|
-
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${
|
|
3
|
+
<span aria-hidden="true" class="${N(g.classes.dot)}" style="${V}"></span>
|
|
4
|
+
<strong style="font-size:15px;">${N(g.title)}</strong>
|
|
5
|
+
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${N(g.statusLabel)}</span>
|
|
6
6
|
</div>
|
|
7
|
-
${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${
|
|
8
|
-
<div style="display:flex;gap:10px;">${
|
|
9
|
-
${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${
|
|
10
|
-
</div>`};var
|
|
7
|
+
${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${N(g.partial)}”</p>`:""}
|
|
8
|
+
<div style="display:flex;gap:10px;">${$.join("")}</div>
|
|
9
|
+
${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${N(g.errorMessage)}</p>`:""}
|
|
10
|
+
</div>`};var Ag=(g)=>{if(typeof g!=="string")return g;let A=document.querySelector(g);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${g}"`);return A},T0=(g,A={})=>{let C=Ag(g),V=A0(A.path??"/voice",A.controllerOptions),$=null,_=null,L=()=>{let c=C0({...A.labels!==void 0?{labels:A.labels}:{},state:{assistantAudio:V.assistantAudio,error:V.error,isConnected:V.isConnected,isRecording:V.isRecording,partial:V.partial,status:V.status,turns:V.turns},...A.theme!==void 0?{theme:A.theme}:{},...A.title!==void 0?{title:A.title}:{}});C.innerHTML=_0(c);for(let W of C.querySelectorAll("button[data-action]")){let{action:R}=W.dataset;W.addEventListener("click",()=>{if(R==="start")V.startRecording();else if(R==="mute")V.stopRecording();else if(R==="end")V.close()})}if(V.error&&V.error!==$)$=V.error,A.onError?.(V.error);if(V.status!==_)_=V.status,A.onStatusChange?.(V.status)},U=V.subscribe(L);if(L(),A.autoStart)V.startRecording();return{controller:V,async end(){await V.close()},mute(){V.stopRecording()},async start(){await V.startRecording()},unmount(){U(),V.close(),C.innerHTML=""}}},$0="0.0.22-beta.516",H0={mount:T0,version:$0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=H0;var Cg=H0;})();
|
package/dist/react/index.js
CHANGED
|
@@ -11447,6 +11447,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
11447
11447
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
11448
11448
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
11449
11449
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
11450
|
+
|
|
11451
|
+
class VoiceReconnectRejectedError extends Error {
|
|
11452
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
11453
|
+
super(message);
|
|
11454
|
+
this.name = "VoiceReconnectRejectedError";
|
|
11455
|
+
}
|
|
11456
|
+
}
|
|
11450
11457
|
var noop = () => {};
|
|
11451
11458
|
var noopAsync = () => Promise.resolve();
|
|
11452
11459
|
var noopUnsubscribe = () => noop;
|
|
@@ -11526,6 +11533,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11526
11533
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
11527
11534
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
11528
11535
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
11536
|
+
let lifecycleGeneration = 0;
|
|
11537
|
+
let disposed = false;
|
|
11529
11538
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
11530
11539
|
const state = {
|
|
11531
11540
|
isConnected: false,
|
|
@@ -11589,6 +11598,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11589
11598
|
pendingCallControls.clear();
|
|
11590
11599
|
};
|
|
11591
11600
|
const scheduleReconnect = () => {
|
|
11601
|
+
if (disposed)
|
|
11602
|
+
return;
|
|
11603
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
11592
11604
|
state.reconnectAttempts += 1;
|
|
11593
11605
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
11594
11606
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -11603,6 +11615,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11603
11615
|
type: "connection"
|
|
11604
11616
|
});
|
|
11605
11617
|
state.reconnectTimeout = setTimeout(async () => {
|
|
11618
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11619
|
+
return;
|
|
11606
11620
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
11607
11621
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
11608
11622
|
return;
|
|
@@ -11614,7 +11628,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11614
11628
|
scenarioId: state.scenarioId,
|
|
11615
11629
|
sessionId: state.sessionId
|
|
11616
11630
|
});
|
|
11617
|
-
} catch {
|
|
11631
|
+
} catch (error) {
|
|
11632
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11633
|
+
return;
|
|
11634
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
11635
|
+
emitTerminalFailure(error.message);
|
|
11636
|
+
return;
|
|
11637
|
+
}
|
|
11618
11638
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
11619
11639
|
scheduleReconnect();
|
|
11620
11640
|
} else {
|
|
@@ -11622,10 +11642,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11622
11642
|
}
|
|
11623
11643
|
return;
|
|
11624
11644
|
}
|
|
11645
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11646
|
+
return;
|
|
11625
11647
|
connect();
|
|
11626
11648
|
}, delayMs);
|
|
11627
11649
|
};
|
|
11628
11650
|
const connect = () => {
|
|
11651
|
+
if (disposed)
|
|
11652
|
+
return;
|
|
11629
11653
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
11630
11654
|
ws.binaryType = "arraybuffer";
|
|
11631
11655
|
ws.onopen = () => {
|
|
@@ -11696,6 +11720,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11696
11720
|
state.ws = null;
|
|
11697
11721
|
state.isConnected = false;
|
|
11698
11722
|
clearTimers();
|
|
11723
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
11699
11724
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
11700
11725
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
11701
11726
|
if (authorizationFailure) {
|
|
@@ -11761,6 +11786,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11761
11786
|
send({ type: "end_turn" });
|
|
11762
11787
|
};
|
|
11763
11788
|
const callControl = (message) => {
|
|
11789
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
11790
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
11791
|
+
}
|
|
11764
11792
|
const requestId = crypto.randomUUID();
|
|
11765
11793
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
11766
11794
|
const timeout = setTimeout(() => {
|
|
@@ -11768,16 +11796,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11768
11796
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
11769
11797
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
11770
11798
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
11771
|
-
send({
|
|
11799
|
+
state.ws.send(JSON.stringify({
|
|
11772
11800
|
...message,
|
|
11773
11801
|
requestId,
|
|
11774
11802
|
type: "call_control"
|
|
11775
|
-
});
|
|
11803
|
+
}));
|
|
11776
11804
|
return promise;
|
|
11777
11805
|
};
|
|
11778
11806
|
const close = (reason = "client-close") => {
|
|
11807
|
+
disposed = true;
|
|
11808
|
+
lifecycleGeneration += 1;
|
|
11779
11809
|
clearTimers();
|
|
11780
11810
|
rejectPendingCallControls("Voice connection closed");
|
|
11811
|
+
state.pendingMessages.length = 0;
|
|
11781
11812
|
if (state.ws) {
|
|
11782
11813
|
const ws = state.ws;
|
|
11783
11814
|
state.ws = null;
|
|
@@ -11790,8 +11821,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11790
11821
|
listeners.clear();
|
|
11791
11822
|
};
|
|
11792
11823
|
const disconnect = () => {
|
|
11824
|
+
disposed = true;
|
|
11825
|
+
lifecycleGeneration += 1;
|
|
11793
11826
|
clearTimers();
|
|
11794
11827
|
rejectPendingCallControls("Voice connection disconnected");
|
|
11828
|
+
state.pendingMessages.length = 0;
|
|
11795
11829
|
if (state.ws) {
|
|
11796
11830
|
const ws = state.ws;
|
|
11797
11831
|
state.ws = null;
|
package/dist/svelte/index.js
CHANGED
|
@@ -775,6 +775,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
775
775
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
776
776
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
777
777
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
778
|
+
|
|
779
|
+
class VoiceReconnectRejectedError extends Error {
|
|
780
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
781
|
+
super(message);
|
|
782
|
+
this.name = "VoiceReconnectRejectedError";
|
|
783
|
+
}
|
|
784
|
+
}
|
|
778
785
|
var noop = () => {};
|
|
779
786
|
var noopAsync = () => Promise.resolve();
|
|
780
787
|
var noopUnsubscribe = () => noop;
|
|
@@ -854,6 +861,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
854
861
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
855
862
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
856
863
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
864
|
+
let lifecycleGeneration = 0;
|
|
865
|
+
let disposed = false;
|
|
857
866
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
858
867
|
const state = {
|
|
859
868
|
isConnected: false,
|
|
@@ -917,6 +926,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
917
926
|
pendingCallControls.clear();
|
|
918
927
|
};
|
|
919
928
|
const scheduleReconnect = () => {
|
|
929
|
+
if (disposed)
|
|
930
|
+
return;
|
|
931
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
920
932
|
state.reconnectAttempts += 1;
|
|
921
933
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
922
934
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -931,6 +943,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
931
943
|
type: "connection"
|
|
932
944
|
});
|
|
933
945
|
state.reconnectTimeout = setTimeout(async () => {
|
|
946
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
947
|
+
return;
|
|
934
948
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
935
949
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
936
950
|
return;
|
|
@@ -942,7 +956,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
942
956
|
scenarioId: state.scenarioId,
|
|
943
957
|
sessionId: state.sessionId
|
|
944
958
|
});
|
|
945
|
-
} catch {
|
|
959
|
+
} catch (error) {
|
|
960
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
961
|
+
return;
|
|
962
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
963
|
+
emitTerminalFailure(error.message);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
946
966
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
947
967
|
scheduleReconnect();
|
|
948
968
|
} else {
|
|
@@ -950,10 +970,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
950
970
|
}
|
|
951
971
|
return;
|
|
952
972
|
}
|
|
973
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
974
|
+
return;
|
|
953
975
|
connect();
|
|
954
976
|
}, delayMs);
|
|
955
977
|
};
|
|
956
978
|
const connect = () => {
|
|
979
|
+
if (disposed)
|
|
980
|
+
return;
|
|
957
981
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
958
982
|
ws.binaryType = "arraybuffer";
|
|
959
983
|
ws.onopen = () => {
|
|
@@ -1024,6 +1048,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1024
1048
|
state.ws = null;
|
|
1025
1049
|
state.isConnected = false;
|
|
1026
1050
|
clearTimers();
|
|
1051
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
1027
1052
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
1028
1053
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
1029
1054
|
if (authorizationFailure) {
|
|
@@ -1089,6 +1114,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1089
1114
|
send({ type: "end_turn" });
|
|
1090
1115
|
};
|
|
1091
1116
|
const callControl = (message) => {
|
|
1117
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
1118
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
1119
|
+
}
|
|
1092
1120
|
const requestId = crypto.randomUUID();
|
|
1093
1121
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
1094
1122
|
const timeout = setTimeout(() => {
|
|
@@ -1096,16 +1124,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1096
1124
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
1097
1125
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
1098
1126
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
1099
|
-
send({
|
|
1127
|
+
state.ws.send(JSON.stringify({
|
|
1100
1128
|
...message,
|
|
1101
1129
|
requestId,
|
|
1102
1130
|
type: "call_control"
|
|
1103
|
-
});
|
|
1131
|
+
}));
|
|
1104
1132
|
return promise;
|
|
1105
1133
|
};
|
|
1106
1134
|
const close = (reason = "client-close") => {
|
|
1135
|
+
disposed = true;
|
|
1136
|
+
lifecycleGeneration += 1;
|
|
1107
1137
|
clearTimers();
|
|
1108
1138
|
rejectPendingCallControls("Voice connection closed");
|
|
1139
|
+
state.pendingMessages.length = 0;
|
|
1109
1140
|
if (state.ws) {
|
|
1110
1141
|
const ws = state.ws;
|
|
1111
1142
|
state.ws = null;
|
|
@@ -1118,8 +1149,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1118
1149
|
listeners.clear();
|
|
1119
1150
|
};
|
|
1120
1151
|
const disconnect = () => {
|
|
1152
|
+
disposed = true;
|
|
1153
|
+
lifecycleGeneration += 1;
|
|
1121
1154
|
clearTimers();
|
|
1122
1155
|
rejectPendingCallControls("Voice connection disconnected");
|
|
1156
|
+
state.pendingMessages.length = 0;
|
|
1123
1157
|
if (state.ws) {
|
|
1124
1158
|
const ws = state.ws;
|
|
1125
1159
|
state.ws = null;
|
package/dist/testing/index.js
CHANGED
|
@@ -2996,6 +2996,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
2996
2996
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
2997
2997
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
2998
2998
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
2999
|
+
|
|
3000
|
+
class VoiceReconnectRejectedError extends Error {
|
|
3001
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
3002
|
+
super(message);
|
|
3003
|
+
this.name = "VoiceReconnectRejectedError";
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
2999
3006
|
var noop = () => {};
|
|
3000
3007
|
var noopAsync = () => Promise.resolve();
|
|
3001
3008
|
var noopUnsubscribe = () => noop;
|
|
@@ -3075,6 +3082,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3075
3082
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
3076
3083
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
3077
3084
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
3085
|
+
let lifecycleGeneration = 0;
|
|
3086
|
+
let disposed = false;
|
|
3078
3087
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
3079
3088
|
const state = {
|
|
3080
3089
|
isConnected: false,
|
|
@@ -3138,6 +3147,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3138
3147
|
pendingCallControls.clear();
|
|
3139
3148
|
};
|
|
3140
3149
|
const scheduleReconnect = () => {
|
|
3150
|
+
if (disposed)
|
|
3151
|
+
return;
|
|
3152
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
3141
3153
|
state.reconnectAttempts += 1;
|
|
3142
3154
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
3143
3155
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -3152,6 +3164,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3152
3164
|
type: "connection"
|
|
3153
3165
|
});
|
|
3154
3166
|
state.reconnectTimeout = setTimeout(async () => {
|
|
3167
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
3168
|
+
return;
|
|
3155
3169
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
3156
3170
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
3157
3171
|
return;
|
|
@@ -3163,7 +3177,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3163
3177
|
scenarioId: state.scenarioId,
|
|
3164
3178
|
sessionId: state.sessionId
|
|
3165
3179
|
});
|
|
3166
|
-
} catch {
|
|
3180
|
+
} catch (error) {
|
|
3181
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
3182
|
+
return;
|
|
3183
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
3184
|
+
emitTerminalFailure(error.message);
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3167
3187
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
3168
3188
|
scheduleReconnect();
|
|
3169
3189
|
} else {
|
|
@@ -3171,10 +3191,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3171
3191
|
}
|
|
3172
3192
|
return;
|
|
3173
3193
|
}
|
|
3194
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
3195
|
+
return;
|
|
3174
3196
|
connect();
|
|
3175
3197
|
}, delayMs);
|
|
3176
3198
|
};
|
|
3177
3199
|
const connect = () => {
|
|
3200
|
+
if (disposed)
|
|
3201
|
+
return;
|
|
3178
3202
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
3179
3203
|
ws.binaryType = "arraybuffer";
|
|
3180
3204
|
ws.onopen = () => {
|
|
@@ -3245,6 +3269,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3245
3269
|
state.ws = null;
|
|
3246
3270
|
state.isConnected = false;
|
|
3247
3271
|
clearTimers();
|
|
3272
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
3248
3273
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
3249
3274
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
3250
3275
|
if (authorizationFailure) {
|
|
@@ -3310,6 +3335,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3310
3335
|
send({ type: "end_turn" });
|
|
3311
3336
|
};
|
|
3312
3337
|
const callControl = (message) => {
|
|
3338
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
3339
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
3340
|
+
}
|
|
3313
3341
|
const requestId = crypto.randomUUID();
|
|
3314
3342
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
3315
3343
|
const timeout = setTimeout(() => {
|
|
@@ -3317,16 +3345,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3317
3345
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
3318
3346
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
3319
3347
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
3320
|
-
send({
|
|
3348
|
+
state.ws.send(JSON.stringify({
|
|
3321
3349
|
...message,
|
|
3322
3350
|
requestId,
|
|
3323
3351
|
type: "call_control"
|
|
3324
|
-
});
|
|
3352
|
+
}));
|
|
3325
3353
|
return promise;
|
|
3326
3354
|
};
|
|
3327
3355
|
const close = (reason = "client-close") => {
|
|
3356
|
+
disposed = true;
|
|
3357
|
+
lifecycleGeneration += 1;
|
|
3328
3358
|
clearTimers();
|
|
3329
3359
|
rejectPendingCallControls("Voice connection closed");
|
|
3360
|
+
state.pendingMessages.length = 0;
|
|
3330
3361
|
if (state.ws) {
|
|
3331
3362
|
const ws = state.ws;
|
|
3332
3363
|
state.ws = null;
|
|
@@ -3339,8 +3370,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3339
3370
|
listeners.clear();
|
|
3340
3371
|
};
|
|
3341
3372
|
const disconnect = () => {
|
|
3373
|
+
disposed = true;
|
|
3374
|
+
lifecycleGeneration += 1;
|
|
3342
3375
|
clearTimers();
|
|
3343
3376
|
rejectPendingCallControls("Voice connection disconnected");
|
|
3377
|
+
state.pendingMessages.length = 0;
|
|
3344
3378
|
if (state.ws) {
|
|
3345
3379
|
const ws = state.ws;
|
|
3346
3380
|
state.ws = null;
|
package/dist/vue/index.js
CHANGED
|
@@ -10843,6 +10843,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
10843
10843
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
10844
10844
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
10845
10845
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
10846
|
+
|
|
10847
|
+
class VoiceReconnectRejectedError extends Error {
|
|
10848
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
10849
|
+
super(message);
|
|
10850
|
+
this.name = "VoiceReconnectRejectedError";
|
|
10851
|
+
}
|
|
10852
|
+
}
|
|
10846
10853
|
var noop = () => {};
|
|
10847
10854
|
var noopAsync = () => Promise.resolve();
|
|
10848
10855
|
var noopUnsubscribe = () => noop;
|
|
@@ -10922,6 +10929,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10922
10929
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
10923
10930
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
10924
10931
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
10932
|
+
let lifecycleGeneration = 0;
|
|
10933
|
+
let disposed = false;
|
|
10925
10934
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
10926
10935
|
const state = {
|
|
10927
10936
|
isConnected: false,
|
|
@@ -10985,6 +10994,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10985
10994
|
pendingCallControls.clear();
|
|
10986
10995
|
};
|
|
10987
10996
|
const scheduleReconnect = () => {
|
|
10997
|
+
if (disposed)
|
|
10998
|
+
return;
|
|
10999
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
10988
11000
|
state.reconnectAttempts += 1;
|
|
10989
11001
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
10990
11002
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -10999,6 +11011,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10999
11011
|
type: "connection"
|
|
11000
11012
|
});
|
|
11001
11013
|
state.reconnectTimeout = setTimeout(async () => {
|
|
11014
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11015
|
+
return;
|
|
11002
11016
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
11003
11017
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
11004
11018
|
return;
|
|
@@ -11010,7 +11024,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11010
11024
|
scenarioId: state.scenarioId,
|
|
11011
11025
|
sessionId: state.sessionId
|
|
11012
11026
|
});
|
|
11013
|
-
} catch {
|
|
11027
|
+
} catch (error) {
|
|
11028
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11029
|
+
return;
|
|
11030
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
11031
|
+
emitTerminalFailure(error.message);
|
|
11032
|
+
return;
|
|
11033
|
+
}
|
|
11014
11034
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
11015
11035
|
scheduleReconnect();
|
|
11016
11036
|
} else {
|
|
@@ -11018,10 +11038,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11018
11038
|
}
|
|
11019
11039
|
return;
|
|
11020
11040
|
}
|
|
11041
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11042
|
+
return;
|
|
11021
11043
|
connect();
|
|
11022
11044
|
}, delayMs);
|
|
11023
11045
|
};
|
|
11024
11046
|
const connect = () => {
|
|
11047
|
+
if (disposed)
|
|
11048
|
+
return;
|
|
11025
11049
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
11026
11050
|
ws.binaryType = "arraybuffer";
|
|
11027
11051
|
ws.onopen = () => {
|
|
@@ -11092,6 +11116,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11092
11116
|
state.ws = null;
|
|
11093
11117
|
state.isConnected = false;
|
|
11094
11118
|
clearTimers();
|
|
11119
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
11095
11120
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
11096
11121
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
11097
11122
|
if (authorizationFailure) {
|
|
@@ -11157,6 +11182,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11157
11182
|
send({ type: "end_turn" });
|
|
11158
11183
|
};
|
|
11159
11184
|
const callControl = (message) => {
|
|
11185
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
11186
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
11187
|
+
}
|
|
11160
11188
|
const requestId = crypto.randomUUID();
|
|
11161
11189
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
11162
11190
|
const timeout = setTimeout(() => {
|
|
@@ -11164,16 +11192,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11164
11192
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
11165
11193
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
11166
11194
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
11167
|
-
send({
|
|
11195
|
+
state.ws.send(JSON.stringify({
|
|
11168
11196
|
...message,
|
|
11169
11197
|
requestId,
|
|
11170
11198
|
type: "call_control"
|
|
11171
|
-
});
|
|
11199
|
+
}));
|
|
11172
11200
|
return promise;
|
|
11173
11201
|
};
|
|
11174
11202
|
const close = (reason = "client-close") => {
|
|
11203
|
+
disposed = true;
|
|
11204
|
+
lifecycleGeneration += 1;
|
|
11175
11205
|
clearTimers();
|
|
11176
11206
|
rejectPendingCallControls("Voice connection closed");
|
|
11207
|
+
state.pendingMessages.length = 0;
|
|
11177
11208
|
if (state.ws) {
|
|
11178
11209
|
const ws = state.ws;
|
|
11179
11210
|
state.ws = null;
|
|
@@ -11186,8 +11217,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11186
11217
|
listeners.clear();
|
|
11187
11218
|
};
|
|
11188
11219
|
const disconnect = () => {
|
|
11220
|
+
disposed = true;
|
|
11221
|
+
lifecycleGeneration += 1;
|
|
11189
11222
|
clearTimers();
|
|
11190
11223
|
rejectPendingCallControls("Voice connection disconnected");
|
|
11224
|
+
state.pendingMessages.length = 0;
|
|
11191
11225
|
if (state.ws) {
|
|
11192
11226
|
const ws = state.ws;
|
|
11193
11227
|
state.ws = null;
|