@absolutejs/voice 0.0.22-beta.649 → 0.0.22-beta.650

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.
@@ -581,6 +581,7 @@ var WS_CLOSED = 3;
581
581
  var WS_NORMAL_CLOSURE = 1000;
582
582
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
583
583
  var DEFAULT_PING_INTERVAL = 30000;
584
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
584
585
  var RECONNECT_BASE_DELAY_MS = 500;
585
586
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
586
587
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
@@ -660,6 +661,7 @@ var createVoiceConnection = (path, options = {}) => {
660
661
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
661
662
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
662
663
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
664
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
663
665
  const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
664
666
  const state = {
665
667
  isConnected: false,
@@ -667,6 +669,7 @@ var createVoiceConnection = (path, options = {}) => {
667
669
  scenarioId: options.scenarioId ?? null,
668
670
  pingInterval: null,
669
671
  reconnectAttempts: 0,
672
+ reconnectResetTimeout: null,
670
673
  reconnectTimeout: null,
671
674
  sessionId: options.sessionId ?? createSessionId(),
672
675
  ws: null
@@ -683,6 +686,10 @@ var createVoiceConnection = (path, options = {}) => {
683
686
  clearTimeout(state.reconnectTimeout);
684
687
  state.reconnectTimeout = null;
685
688
  }
689
+ if (state.reconnectResetTimeout) {
690
+ clearTimeout(state.reconnectResetTimeout);
691
+ state.reconnectResetTimeout = null;
692
+ }
686
693
  };
687
694
  const flushPendingMessages = () => {
688
695
  if (state.ws?.readyState !== WS_OPEN) {
@@ -728,6 +735,10 @@ var createVoiceConnection = (path, options = {}) => {
728
735
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
729
736
  ws.binaryType = "arraybuffer";
730
737
  ws.onopen = () => {
738
+ if (state.ws !== ws) {
739
+ ws.close(WS_NORMAL_CLOSURE);
740
+ return;
741
+ }
731
742
  const wasReconnecting = state.reconnectAttempts > 0;
732
743
  state.isConnected = true;
733
744
  flushPendingMessages();
@@ -741,7 +752,12 @@ var createVoiceConnection = (path, options = {}) => {
741
752
  },
742
753
  type: "connection"
743
754
  });
744
- state.reconnectAttempts = 0;
755
+ state.reconnectResetTimeout = setTimeout(() => {
756
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
757
+ state.reconnectAttempts = 0;
758
+ }
759
+ state.reconnectResetTimeout = null;
760
+ }, reconnectResetAfterMs);
745
761
  }
746
762
  listeners.forEach((listener) => listener({
747
763
  scenarioId: state.scenarioId ?? undefined,
@@ -756,6 +772,8 @@ var createVoiceConnection = (path, options = {}) => {
756
772
  }, pingInterval);
757
773
  };
758
774
  ws.onmessage = (event) => {
775
+ if (state.ws !== ws)
776
+ return;
759
777
  const parsed = parseServerMessage(event);
760
778
  if (!parsed) {
761
779
  return;
@@ -767,6 +785,9 @@ var createVoiceConnection = (path, options = {}) => {
767
785
  listeners.forEach((listener) => listener(parsed));
768
786
  };
769
787
  ws.onclose = (event) => {
788
+ if (state.ws !== ws)
789
+ return;
790
+ state.ws = null;
770
791
  state.isConnected = false;
771
792
  clearTimers();
772
793
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
@@ -824,11 +845,12 @@ var createVoiceConnection = (path, options = {}) => {
824
845
  const close = (reason = "client-close") => {
825
846
  clearTimers();
826
847
  if (state.ws) {
827
- if (state.ws.readyState === WS_OPEN) {
828
- state.ws.send(JSON.stringify({ reason, type: "close" }));
829
- }
830
- state.ws.close(WS_NORMAL_CLOSURE);
848
+ const ws = state.ws;
831
849
  state.ws = null;
850
+ if (ws.readyState === WS_OPEN) {
851
+ ws.send(JSON.stringify({ reason, type: "close" }));
852
+ }
853
+ ws.close(WS_NORMAL_CLOSURE);
832
854
  }
833
855
  state.isConnected = false;
834
856
  listeners.clear();
@@ -836,8 +858,9 @@ var createVoiceConnection = (path, options = {}) => {
836
858
  const disconnect = () => {
837
859
  clearTimers();
838
860
  if (state.ws) {
839
- state.ws.close(WS_NORMAL_CLOSURE);
861
+ const ws = state.ws;
840
862
  state.ws = null;
863
+ ws.close(WS_NORMAL_CLOSURE);
841
864
  }
842
865
  state.isConnected = false;
843
866
  listeners.clear();
@@ -498,6 +498,7 @@ var WS_CLOSED = 3;
498
498
  var WS_NORMAL_CLOSURE = 1000;
499
499
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
500
500
  var DEFAULT_PING_INTERVAL = 30000;
501
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
501
502
  var RECONNECT_BASE_DELAY_MS = 500;
502
503
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
503
504
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
@@ -577,6 +578,7 @@ var createVoiceConnection = (path, options = {}) => {
577
578
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
578
579
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
579
580
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
581
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
580
582
  const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
581
583
  const state = {
582
584
  isConnected: false,
@@ -584,6 +586,7 @@ var createVoiceConnection = (path, options = {}) => {
584
586
  scenarioId: options.scenarioId ?? null,
585
587
  pingInterval: null,
586
588
  reconnectAttempts: 0,
589
+ reconnectResetTimeout: null,
587
590
  reconnectTimeout: null,
588
591
  sessionId: options.sessionId ?? createSessionId(),
589
592
  ws: null
@@ -600,6 +603,10 @@ var createVoiceConnection = (path, options = {}) => {
600
603
  clearTimeout(state.reconnectTimeout);
601
604
  state.reconnectTimeout = null;
602
605
  }
606
+ if (state.reconnectResetTimeout) {
607
+ clearTimeout(state.reconnectResetTimeout);
608
+ state.reconnectResetTimeout = null;
609
+ }
603
610
  };
604
611
  const flushPendingMessages = () => {
605
612
  if (state.ws?.readyState !== WS_OPEN) {
@@ -645,6 +652,10 @@ var createVoiceConnection = (path, options = {}) => {
645
652
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
646
653
  ws.binaryType = "arraybuffer";
647
654
  ws.onopen = () => {
655
+ if (state.ws !== ws) {
656
+ ws.close(WS_NORMAL_CLOSURE);
657
+ return;
658
+ }
648
659
  const wasReconnecting = state.reconnectAttempts > 0;
649
660
  state.isConnected = true;
650
661
  flushPendingMessages();
@@ -658,7 +669,12 @@ var createVoiceConnection = (path, options = {}) => {
658
669
  },
659
670
  type: "connection"
660
671
  });
661
- state.reconnectAttempts = 0;
672
+ state.reconnectResetTimeout = setTimeout(() => {
673
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
674
+ state.reconnectAttempts = 0;
675
+ }
676
+ state.reconnectResetTimeout = null;
677
+ }, reconnectResetAfterMs);
662
678
  }
663
679
  listeners.forEach((listener) => listener({
664
680
  scenarioId: state.scenarioId ?? undefined,
@@ -673,6 +689,8 @@ var createVoiceConnection = (path, options = {}) => {
673
689
  }, pingInterval);
674
690
  };
675
691
  ws.onmessage = (event) => {
692
+ if (state.ws !== ws)
693
+ return;
676
694
  const parsed = parseServerMessage(event);
677
695
  if (!parsed) {
678
696
  return;
@@ -684,6 +702,9 @@ var createVoiceConnection = (path, options = {}) => {
684
702
  listeners.forEach((listener) => listener(parsed));
685
703
  };
686
704
  ws.onclose = (event) => {
705
+ if (state.ws !== ws)
706
+ return;
707
+ state.ws = null;
687
708
  state.isConnected = false;
688
709
  clearTimers();
689
710
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
@@ -741,11 +762,12 @@ var createVoiceConnection = (path, options = {}) => {
741
762
  const close = (reason = "client-close") => {
742
763
  clearTimers();
743
764
  if (state.ws) {
744
- if (state.ws.readyState === WS_OPEN) {
745
- state.ws.send(JSON.stringify({ reason, type: "close" }));
746
- }
747
- state.ws.close(WS_NORMAL_CLOSURE);
765
+ const ws = state.ws;
748
766
  state.ws = null;
767
+ if (ws.readyState === WS_OPEN) {
768
+ ws.send(JSON.stringify({ reason, type: "close" }));
769
+ }
770
+ ws.close(WS_NORMAL_CLOSURE);
749
771
  }
750
772
  state.isConnected = false;
751
773
  listeners.clear();
@@ -753,8 +775,9 @@ var createVoiceConnection = (path, options = {}) => {
753
775
  const disconnect = () => {
754
776
  clearTimers();
755
777
  if (state.ws) {
756
- state.ws.close(WS_NORMAL_CLOSURE);
778
+ const ws = state.ws;
757
779
  state.ws = null;
780
+ ws.close(WS_NORMAL_CLOSURE);
758
781
  }
759
782
  state.isConnected = false;
760
783
  listeners.clear();
@@ -106,6 +106,7 @@ var WS_CLOSED = 3;
106
106
  var WS_NORMAL_CLOSURE = 1000;
107
107
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
108
108
  var DEFAULT_PING_INTERVAL = 30000;
109
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
109
110
  var RECONNECT_BASE_DELAY_MS = 500;
110
111
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
111
112
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
@@ -185,6 +186,7 @@ var createVoiceConnection = (path, options = {}) => {
185
186
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
186
187
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
187
188
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
189
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
188
190
  const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
189
191
  const state = {
190
192
  isConnected: false,
@@ -192,6 +194,7 @@ var createVoiceConnection = (path, options = {}) => {
192
194
  scenarioId: options.scenarioId ?? null,
193
195
  pingInterval: null,
194
196
  reconnectAttempts: 0,
197
+ reconnectResetTimeout: null,
195
198
  reconnectTimeout: null,
196
199
  sessionId: options.sessionId ?? createSessionId(),
197
200
  ws: null
@@ -208,6 +211,10 @@ var createVoiceConnection = (path, options = {}) => {
208
211
  clearTimeout(state.reconnectTimeout);
209
212
  state.reconnectTimeout = null;
210
213
  }
214
+ if (state.reconnectResetTimeout) {
215
+ clearTimeout(state.reconnectResetTimeout);
216
+ state.reconnectResetTimeout = null;
217
+ }
211
218
  };
212
219
  const flushPendingMessages = () => {
213
220
  if (state.ws?.readyState !== WS_OPEN) {
@@ -253,6 +260,10 @@ var createVoiceConnection = (path, options = {}) => {
253
260
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
254
261
  ws.binaryType = "arraybuffer";
255
262
  ws.onopen = () => {
263
+ if (state.ws !== ws) {
264
+ ws.close(WS_NORMAL_CLOSURE);
265
+ return;
266
+ }
256
267
  const wasReconnecting = state.reconnectAttempts > 0;
257
268
  state.isConnected = true;
258
269
  flushPendingMessages();
@@ -266,7 +277,12 @@ var createVoiceConnection = (path, options = {}) => {
266
277
  },
267
278
  type: "connection"
268
279
  });
269
- state.reconnectAttempts = 0;
280
+ state.reconnectResetTimeout = setTimeout(() => {
281
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
282
+ state.reconnectAttempts = 0;
283
+ }
284
+ state.reconnectResetTimeout = null;
285
+ }, reconnectResetAfterMs);
270
286
  }
271
287
  listeners.forEach((listener) => listener({
272
288
  scenarioId: state.scenarioId ?? undefined,
@@ -281,6 +297,8 @@ var createVoiceConnection = (path, options = {}) => {
281
297
  }, pingInterval);
282
298
  };
283
299
  ws.onmessage = (event) => {
300
+ if (state.ws !== ws)
301
+ return;
284
302
  const parsed = parseServerMessage(event);
285
303
  if (!parsed) {
286
304
  return;
@@ -292,6 +310,9 @@ var createVoiceConnection = (path, options = {}) => {
292
310
  listeners.forEach((listener) => listener(parsed));
293
311
  };
294
312
  ws.onclose = (event) => {
313
+ if (state.ws !== ws)
314
+ return;
315
+ state.ws = null;
295
316
  state.isConnected = false;
296
317
  clearTimers();
297
318
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
@@ -349,11 +370,12 @@ var createVoiceConnection = (path, options = {}) => {
349
370
  const close = (reason = "client-close") => {
350
371
  clearTimers();
351
372
  if (state.ws) {
352
- if (state.ws.readyState === WS_OPEN) {
353
- state.ws.send(JSON.stringify({ reason, type: "close" }));
354
- }
355
- state.ws.close(WS_NORMAL_CLOSURE);
373
+ const ws = state.ws;
356
374
  state.ws = null;
375
+ if (ws.readyState === WS_OPEN) {
376
+ ws.send(JSON.stringify({ reason, type: "close" }));
377
+ }
378
+ ws.close(WS_NORMAL_CLOSURE);
357
379
  }
358
380
  state.isConnected = false;
359
381
  listeners.clear();
@@ -361,8 +383,9 @@ var createVoiceConnection = (path, options = {}) => {
361
383
  const disconnect = () => {
362
384
  clearTimers();
363
385
  if (state.ws) {
364
- state.ws.close(WS_NORMAL_CLOSURE);
386
+ const ws = state.ws;
365
387
  state.ws = null;
388
+ ws.close(WS_NORMAL_CLOSURE);
366
389
  }
367
390
  state.isConnected = false;
368
391
  listeners.clear();
@@ -879,6 +879,7 @@ export type VoicePluginConfig<TContext = unknown, TSession extends VoiceSessionR
879
879
  */
880
880
  authorizeConnection?: (input: {
881
881
  context: TContext;
882
+ headers: Headers;
882
883
  path: string;
883
884
  query: Record<string, unknown>;
884
885
  scenarioId?: string;
@@ -1343,6 +1344,10 @@ export type VoiceConnectionOptions = {
1343
1344
  * attempts that's a ~95s window, enough to ride out a server redeploy without
1344
1345
  * the caller losing the call. */
1345
1346
  reconnectMaxDelayMs?: number;
1347
+ /** A reconnected socket must remain open this long before its retry budget is
1348
+ * reset. This prevents a server that accepts and immediately drops sockets
1349
+ * from creating an unbounded reconnect loop. Default 30000. */
1350
+ reconnectResetAfterMs?: number;
1346
1351
  pingInterval?: number;
1347
1352
  /** Additional query values sent on every initial or reconnecting socket. */
1348
1353
  query?: Record<string, string>;
@@ -495,6 +495,7 @@ var WS_CLOSED = 3;
495
495
  var WS_NORMAL_CLOSURE = 1000;
496
496
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
497
497
  var DEFAULT_PING_INTERVAL = 30000;
498
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
498
499
  var RECONNECT_BASE_DELAY_MS = 500;
499
500
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
500
501
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
@@ -574,6 +575,7 @@ var createVoiceConnection = (path, options = {}) => {
574
575
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
575
576
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
576
577
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
578
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
577
579
  const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
578
580
  const state = {
579
581
  isConnected: false,
@@ -581,6 +583,7 @@ var createVoiceConnection = (path, options = {}) => {
581
583
  scenarioId: options.scenarioId ?? null,
582
584
  pingInterval: null,
583
585
  reconnectAttempts: 0,
586
+ reconnectResetTimeout: null,
584
587
  reconnectTimeout: null,
585
588
  sessionId: options.sessionId ?? createSessionId(),
586
589
  ws: null
@@ -597,6 +600,10 @@ var createVoiceConnection = (path, options = {}) => {
597
600
  clearTimeout(state.reconnectTimeout);
598
601
  state.reconnectTimeout = null;
599
602
  }
603
+ if (state.reconnectResetTimeout) {
604
+ clearTimeout(state.reconnectResetTimeout);
605
+ state.reconnectResetTimeout = null;
606
+ }
600
607
  };
601
608
  const flushPendingMessages = () => {
602
609
  if (state.ws?.readyState !== WS_OPEN) {
@@ -642,6 +649,10 @@ var createVoiceConnection = (path, options = {}) => {
642
649
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
643
650
  ws.binaryType = "arraybuffer";
644
651
  ws.onopen = () => {
652
+ if (state.ws !== ws) {
653
+ ws.close(WS_NORMAL_CLOSURE);
654
+ return;
655
+ }
645
656
  const wasReconnecting = state.reconnectAttempts > 0;
646
657
  state.isConnected = true;
647
658
  flushPendingMessages();
@@ -655,7 +666,12 @@ var createVoiceConnection = (path, options = {}) => {
655
666
  },
656
667
  type: "connection"
657
668
  });
658
- state.reconnectAttempts = 0;
669
+ state.reconnectResetTimeout = setTimeout(() => {
670
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
671
+ state.reconnectAttempts = 0;
672
+ }
673
+ state.reconnectResetTimeout = null;
674
+ }, reconnectResetAfterMs);
659
675
  }
660
676
  listeners.forEach((listener) => listener({
661
677
  scenarioId: state.scenarioId ?? undefined,
@@ -670,6 +686,8 @@ var createVoiceConnection = (path, options = {}) => {
670
686
  }, pingInterval);
671
687
  };
672
688
  ws.onmessage = (event) => {
689
+ if (state.ws !== ws)
690
+ return;
673
691
  const parsed = parseServerMessage(event);
674
692
  if (!parsed) {
675
693
  return;
@@ -681,6 +699,9 @@ var createVoiceConnection = (path, options = {}) => {
681
699
  listeners.forEach((listener) => listener(parsed));
682
700
  };
683
701
  ws.onclose = (event) => {
702
+ if (state.ws !== ws)
703
+ return;
704
+ state.ws = null;
684
705
  state.isConnected = false;
685
706
  clearTimers();
686
707
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
@@ -738,11 +759,12 @@ var createVoiceConnection = (path, options = {}) => {
738
759
  const close = (reason = "client-close") => {
739
760
  clearTimers();
740
761
  if (state.ws) {
741
- if (state.ws.readyState === WS_OPEN) {
742
- state.ws.send(JSON.stringify({ reason, type: "close" }));
743
- }
744
- state.ws.close(WS_NORMAL_CLOSURE);
762
+ const ws = state.ws;
745
763
  state.ws = null;
764
+ if (ws.readyState === WS_OPEN) {
765
+ ws.send(JSON.stringify({ reason, type: "close" }));
766
+ }
767
+ ws.close(WS_NORMAL_CLOSURE);
746
768
  }
747
769
  state.isConnected = false;
748
770
  listeners.clear();
@@ -750,8 +772,9 @@ var createVoiceConnection = (path, options = {}) => {
750
772
  const disconnect = () => {
751
773
  clearTimers();
752
774
  if (state.ws) {
753
- state.ws.close(WS_NORMAL_CLOSURE);
775
+ const ws = state.ws;
754
776
  state.ws = null;
777
+ ws.close(WS_NORMAL_CLOSURE);
755
778
  }
756
779
  state.isConnected = false;
757
780
  listeners.clear();
@@ -1,10 +1,10 @@
1
- (()=>{var{defineProperty:B,getOwnPropertyNames:R0,getOwnPropertyDescriptor:_0}=Object,$0=Object.prototype.hasOwnProperty;function I0(g){return this[g]}var H0=(g)=>{var A=(k??=new WeakMap).get(g),C;if(A)return A;if(A=B({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of R0(g))if(!$0.call(A,V))B(A,V,{get:I0.bind(g,V),enumerable:!(C=_0(g,V))||C.enumerable})}return k.set(g,A),A},k;var U0=(g)=>g;function L0(g,A){this[g]=U0.bind(null,A)}var G0=(g,A)=>{for(var C in A)B(g,C,{get:A[C],enumerable:!0,configurable:!0,set:L0.bind(A,C)})};var m0={};G0(m0,{mount:()=>V0,default:()=>r0,VOICE_EMBED_VERSION:()=>C0});var W0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},w0=(g,A,C,V)=>{let R=A??g.getAttribute("hx-get")??"";if(!R)return"";let c=new URL(R,window.location.origin);if(V)c.searchParams.set(C,V);else c.searchParams.delete(C);return`${c.pathname}${c.search}${c.hash}`},F=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=W0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",R=A.sessionQueryParam??"sessionId",c=()=>{let H=window,T=w0(C,A.route,R,g.sessionId);if(T)C.setAttribute("hx-get",T);H.htmx?.process?.(C),H.htmx?.trigger?.(C,V)},U=g.subscribe(c);return c(),()=>{U()}};var y0=(g)=>Math.max(-1,Math.min(1,g)),D0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=y0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},S0=(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 R of C){let c=R/32768;V+=c*c}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},X0=(g,A,C)=>{if(A===C)return g;let V=A/C,R=Math.round(g.length/V),c=new Float32Array(R),U=0,H=0;while(U<c.length){let T=Math.round((U+1)*V),G=0,$=0;for(let w=H;w<T&&w<g.length;w+=1)G+=g[w]??0,$+=1;c[U]=$>0?G/$:0,U+=1,H=T}return c},f=(g)=>{let A=null,C=null,V=null,R=null,c=async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let H=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!H)throw Error("Browser microphone capture requires AudioContext support.");try{if(R=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new H,A.state==="suspended")await A.resume();C=A.createMediaStreamSource(R),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(T)=>{let G=T.inputBuffer.getChannelData(0),$=X0(G,A?.sampleRate??48000,g.sampleRateHz??16000),w=D0($);g.onLevel?.(S0(w)),g.onAudio(w)},C.connect(V),V.connect(A.destination)}catch(T){throw U(),T}},U=()=>{V?.disconnect(),C?.disconnect(),R?.getTracks().forEach((H)=>H.stop()),A?.close(),g.onLevel?.(0),A=null,R=null,V=null,C=null};return{start:c,stop:U}};var M=(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 M(A.error);if("cause"in A)return M(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},d=(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:M(g.message),type:"error"};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 t0=Math.PI*2;var Q=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var h0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,j=(g)=>g.length===0?void 0:Math.max(...g);var y=(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},S=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},x=(g)=>String(g.id??S(g,"ssrc")??y(g,"ssrc")??S(g,"trackIdentifier")??S(g,"mid")??"unknown"),l=(g)=>g===void 0?void 0:g*1000;var O0=(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 v=(g={})=>{let A=g.stats??[],C=[],V=A.filter((_)=>_.type==="inbound-rtp"&&S(_,"kind")!=="video"),R=A.filter((_)=>_.type==="outbound-rtp"&&S(_,"kind")!=="video"),c=A.filter((_)=>_.type==="candidate-pair"),U=A.filter((_)=>(_.type==="track"||_.type==="media-source")&&S(_,"kind")==="audio"),H=c.filter((_)=>b(_,"selected")===!0||b(_,"nominated")===!0||S(_,"state")==="succeeded").length,T=U.filter((_)=>S(_,"readyState")!=="ended"&&S(_,"trackState")!=="ended"&&b(_,"ended")!==!0).length,G=U.filter((_)=>S(_,"readyState")==="ended"||S(_,"trackState")==="ended"||b(_,"ended")===!0).length,$=V.reduce((_,D)=>_+(y(D,"packetsReceived")??0),0),w=R.reduce((_,D)=>_+(y(D,"packetsSent")??0),0),I=[...V,...R].reduce((_,D)=>_+Math.max(0,y(D,"packetsLost")??0),0),X=$+I,h=X===0?0:I/X,W=V.reduce((_,D)=>_+(y(D,"bytesReceived")??0),0),O=R.reduce((_,D)=>_+(y(D,"bytesSent")??0),0),J=j(c.map((_)=>l(y(_,"currentRoundTripTime")??y(_,"roundTripTime"))).filter((_)=>_!==void 0)),Y=j([...V,...R].map((_)=>l(y(_,"jitter"))).filter((_)=>_!==void 0)),N=j(V.map((_)=>{let D=y(_,"jitterBufferDelay"),z=y(_,"jitterBufferEmittedCount");return D!==void 0&&z!==void 0&&z>0?D/z*1000:void 0}).filter((_)=>_!==void 0)),q=U.map((_)=>y(_,"audioLevel")).filter((_)=>_!==void 0);if(g.requireConnectedCandidatePair&&c.length>0&&H===0)Q(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&T===0)Q(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&h>g.maxPacketLossRatio)Q(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(h)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&J!==void 0&&J>g.maxRoundTripTimeMs)Q(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(J)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&Y!==void 0&&Y>g.maxJitterMs)Q(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(Y)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:H,audioLevelAverage:h0(q),bytesReceived:W,bytesSent:O,checkedAt:Date.now(),endedAudioTracks:G,inboundPackets:$,issues:C,jitterBufferDelayMs:N,jitterMs:Y,liveAudioTracks:T,outboundPackets:w,packetLossRatio:h,packetsLost:I,roundTripTimeMs:J,status:C.some((_)=>_.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},i=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(O0)};var n=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],R=new Map(C.map((I)=>[x(I),I])),U=A.filter((I)=>(I.type==="inbound-rtp"||I.type==="outbound-rtp")&&S(I,"kind")!=="video"&&S(I,"mediaType")!=="video").map((I)=>{let X=I.type==="outbound-rtp"?"outbound":"inbound",h=X==="outbound"?"packetsSent":"packetsReceived",W=X==="outbound"?"bytesSent":"bytesReceived",O=R.get(x(I)),J=y(I,h),Y=O?y(O,h):void 0,N=y(I,W),q=O?y(O,W):void 0,_=I.timestamp!==void 0&&O?.timestamp!==void 0?I.timestamp-O.timestamp:void 0;return{bytesDelta:N!==void 0&&q!==void 0?N-q:void 0,currentPackets:J,direction:X,id:x(I),packetDelta:J!==void 0&&Y!==void 0?J-Y:void 0,previousPackets:Y,timeDeltaMs:_}}),H=U.filter((I)=>I.direction==="inbound"),T=U.filter((I)=>I.direction==="outbound"),G=j(U.map((I)=>I.timeDeltaMs).filter((I)=>I!==void 0)),$=H.filter((I)=>g.maxInboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxInboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length,w=T.filter((I)=>g.maxOutboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxOutboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length;if(g.requireInboundAudio&&H.length===0)Q(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&T.length===0)Q(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&G!==void 0&&G>g.maxGapMs)Q(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(G)}ms above ${String(g.maxGapMs)}ms.`);if($>0)Q(V,"error","media.webrtc_inbound_stalled",`${String($)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(w>0)Q(V,"error","media.webrtc_outbound_stalled",`${String(w)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:H.length,issues:V,maxObservedGapMs:G,outboundAudioStreams:T.length,stalledInboundStreams:$,stalledOutboundStreams:w,status:V.some((I)=>I.severity==="error")?"fail":V.length>0?"warn":"pass",streams:U,totalStats:A.length}};var J0="/api/voice/browser-media",Y0=5000,Z0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,Q0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??J0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},o=(g)=>{let A=null,C=[],V=async()=>{let U=await Z0(g);if(!U)return;let H=await i({peerConnection:U}),T=v({...g,stats:H}),G=g.continuity===!1?void 0:n({...g.continuity,previousStats:C,stats:H}),$={at:Date.now(),continuity:G,report:T,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=H,g.onReport?.($),await Q0($,g),$},R=()=>{V().catch((U)=>{g.onError?.(U)})},c=()=>{if(A)clearInterval(A),A=null};return{close:c,reportOnce:V,stop:c,start:()=>{if(A)return;R(),A=setInterval(R,g.intervalMs??Y0)}}};var P0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var E=()=>{},K0=()=>E,E0={callControl:E,close:E,disconnect:E,endTurn:E,send:E,sendAudio:E,simulateDisconnect:E,subscribe:K0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},N0=()=>crypto.randomUUID(),q0=(g,A,C,V)=>{let{hostname:R,port:c,protocol:U}=window.location,H=U==="https:"?"wss:":"ws:",T=c?`:${c}`:"",G=new URL(`${H}//${R}${T}${g}`);if(G.searchParams.set("sessionId",A),C)G.searchParams.set("scenarioId",C);for(let[$,w]of Object.entries(V))if($!=="sessionId"&&$!=="scenarioId"&&w)G.searchParams.set($,w);return G.toString()},z0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},b0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return z0(A)?A:null}catch{return null}},m=(g,A={})=>{if(typeof window>"u")return E0;let C=new Set,V=A.reconnect!==!1,R=A.maxReconnectAttempts??15,c=A.reconnectMaxDelayMs??8000,U=A.pingInterval??30000,H=(L)=>P0(L,500,c),T={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:A.sessionId??N0(),ws:null},G=(L)=>{C.forEach((Z)=>Z(L))},$=()=>{if(T.pingInterval)clearInterval(T.pingInterval),T.pingInterval=null;if(T.reconnectTimeout)clearTimeout(T.reconnectTimeout),T.reconnectTimeout=null},w=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let L=T.pendingMessages.shift();if(L!==void 0)T.ws.send(L)}},I=()=>{T.reconnectAttempts+=1;let L=H(T.reconnectAttempts),Z=Date.now()+L;G({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:R,nextAttemptAt:Z,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(()=>{if(T.reconnectAttempts>R){G({reconnect:{attempts:T.reconnectAttempts,maxAttempts:R,status:"exhausted"},type:"connection"});return}X()},L)},X=()=>{let L=new WebSocket(q0(g,T.sessionId,T.scenarioId,A.query??{}));L.binaryType="arraybuffer",L.onopen=()=>{let Z=T.reconnectAttempts>0;if(T.isConnected=!0,w(),Z)G({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:R,status:"resumed"},type:"connection"}),T.reconnectAttempts=0;C.forEach((K)=>K({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(L.readyState===1)L.send(JSON.stringify({type:"ping"}))},U)},L.onmessage=(Z)=>{let K=b0(Z);if(!K)return;if(K.type==="session")T.sessionId=K.sessionId,T.scenarioId=K.scenarioId??T.scenarioId;C.forEach((T0)=>T0(K))},L.onclose=(Z)=>{if(T.isConnected=!1,$(),V&&Z.code!==1000&&T.reconnectAttempts<R)I();else if(V&&Z.code!==1000)G({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:R,status:"exhausted"},type:"connection"})},T.ws=L},h=(L)=>{if(T.ws?.readyState===1){T.ws.send(L);return}T.pendingMessages.push(L)},W=(L)=>{h(JSON.stringify(L))},O=(L={})=>{if(L.sessionId)T.sessionId=L.sessionId;if(L.scenarioId)T.scenarioId=L.scenarioId;W({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},J=(L)=>{h(L)},Y=()=>{W({type:"end_turn"})},N=(L)=>{W({...L,type:"call_control"})},q=(L="client-close")=>{if($(),T.ws){if(T.ws.readyState===1)T.ws.send(JSON.stringify({reason:L,type:"close"}));T.ws.close(1000),T.ws=null}T.isConnected=!1,C.clear()},_=()=>{if($(),T.ws)T.ws.close(1000),T.ws=null;T.isConnected=!1,C.clear()},D=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},z=(L)=>{return C.add(L),()=>{C.delete(L)}};return X(),{callControl:N,close:q,disconnect:_,endTurn:Y,send:W,sendAudio:J,simulateDisconnect:D,start:O,subscribe:z,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var j0=()=>({attempts:0,maxAttempts:0,status:"idle"}),B0=(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}`},M0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},x0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:j0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),r=()=>{let g=x0(),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};break;case"final":A=B0(A,c.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:M0(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 u=(g,A={})=>{let C=m(g,A),V=r(),R=A.browserMedia&&typeof window<"u"?o({...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($),R?.start()}),H=()=>{c.forEach(($)=>$())},T=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let $=V.getSnapshot(),w=JSON.stringify({at:Date.now(),reconnect:$.reconnect,scenarioId:$.scenarioId,sessionId:C.getSessionId(),turnIds:$.turns.map((I)=>I.id)});fetch(A.reconnectReportPath,{body:w,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},G=C.subscribe(($)=>{let w=d($);if(w){if(V.dispatch(w),$.type==="connection")T();H()}});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($){C.callControl($)},close($){G(),R?.close(),C.close($),V.dispatch({type:"disconnected"}),H()},disconnect(){G(),R?.close(),C.disconnect(),V.dispatch({type:"disconnected"}),H()},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 s=(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 k0={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}},F0={"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 a=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=k0[A],R=F0[C],c=g?.silenceMs??R.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(c,g?.minSilenceMs??R.minSilenceMs??V.minSilenceMs),silenceMs:c,speechThreshold:g?.speechThreshold??R.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??R.transcriptStabilityMs??V.transcriptStabilityMs}};var f0={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"}}},p=(g="default")=>{let A=f0[g];return{audioConditioning:s(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:a(A.turnDetection)}};var d0=(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]}),t=(g,A={})=>{let C=p(A.preset),V=u(g,{...C.connection,...A.connection}),R=null,c=d0(V),U=new Set,H=()=>{for(let W of U)W()},T=()=>{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)R?.stop(),R=null,c={...c,isRecording:!1};H()},G=V.subscribe(T);T();let $=()=>{if(R)return R;return R=f({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(W)=>{if(A.capture?.onAudio){A.capture.onAudio(W,V.sendAudio);return}V.sendAudio(W)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),R},w=()=>{R?.stop(),R=null,c={...c,isRecording:!1},H()},I=async()=>{if(c.isRecording)return;try{c={...c,recordingError:null},H(),await $().start(),c={...c,isRecording:!0},H()}catch(W){throw R?.stop(),R=null,c={...c,isRecording:!1,recordingError:W instanceof Error?W.message:String(W)},H(),W}};return{close:(W)=>{G(),w(),V.close(W)},disconnect:()=>{G(),w(),V.disconnect()},startRecording:I,stopRecording:w,get assistantAudio(){return c.assistantAudio},get assistantTexts(){return c.assistantTexts},get assistantStreamingText(){return c.assistantStreamingText},bindHTMX(W){return F(V,W)},get call(){return c.call},callControl:(W)=>V.callControl(W),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:(W)=>V.sendAudio(W),get sessionId(){return c.sessionId},get sessionMetadata(){return c.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return c.status},subscribe:(W)=>{return U.add(W),()=>{U.delete(W)}},toggleRecording:async()=>{if(c.isRecording){w();return}await I()},get turns(){return c.turns}}};var P=(g)=>String(g).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");var e=(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 l0={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},v0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},i0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},g0=(g)=>{let A={...l0,...g.theme},C={...v0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,R=g.state.turns.at(-1)?.committedAt,c=e({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:R}),U=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,H=g.state.error?"Error":U?C.connecting:g.state.status==="completed"?C.callEnded:i0(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:H,theme:A,title:g.title??"Voice"}},n0=(g)=>typeof g==="number"?`${g}px`:g,A0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${n0(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;`,R=[];if(g.controls.canStart)R.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;">${P(g.labels.startCall)}</button>`);if(g.controls.canMute)R.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;">${P(g.labels.mute)}</button>`);if(g.controls.canEnd)R.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;">${P(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${P(g.classes.container)}" style="${C}">
1
+ (()=>{var{defineProperty:B,getOwnPropertyNames:R0,getOwnPropertyDescriptor:$0}=Object,I0=Object.prototype.hasOwnProperty;function H0(g){return this[g]}var U0=(g)=>{var A=(k??=new WeakMap).get(g),C;if(A)return A;if(A=B({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of R0(g))if(!I0.call(A,V))B(A,V,{get:H0.bind(g,V),enumerable:!(C=$0(g,V))||C.enumerable})}return k.set(g,A),A},k;var L0=(g)=>g;function G0(g,A){this[g]=L0.bind(null,A)}var W0=(g,A)=>{for(var C in A)B(g,C,{get:A[C],enumerable:!0,configurable:!0,set:G0.bind(A,C)})};var r0={};W0(r0,{mount:()=>V0,default:()=>u0,VOICE_EMBED_VERSION:()=>C0});var y0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},D0=(g,A,C,V)=>{let _=A??g.getAttribute("hx-get")??"";if(!_)return"";let c=new URL(_,window.location.origin);if(V)c.searchParams.set(C,V);else c.searchParams.delete(C);return`${c.pathname}${c.search}${c.hash}`},F=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=y0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",_=A.sessionQueryParam??"sessionId",c=()=>{let U=window,W=D0(C,A.route,_,g.sessionId);if(W)C.setAttribute("hx-get",W);U.htmx?.process?.(C),U.htmx?.trigger?.(C,V)},L=g.subscribe(c);return c(),()=>{L()}};var S0=(g)=>Math.max(-1,Math.min(1,g)),X0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=S0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},h0=(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 c=_/32768;V+=c*c}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},O0=(g,A,C)=>{if(A===C)return g;let V=A/C,_=Math.round(g.length/V),c=new Float32Array(_),L=0,U=0;while(L<c.length){let W=Math.round((L+1)*V),T=0,$=0;for(let G=U;G<W&&G<g.length;G+=1)T+=g[G]??0,$+=1;c[L]=$>0?T/$:0,L+=1,U=W}return c},f=(g)=>{let A=null,C=null,V=null,_=null,c=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=(W)=>{let T=W.inputBuffer.getChannelData(0),$=O0(T,A?.sampleRate??48000,g.sampleRateHz??16000),G=X0($);g.onLevel?.(h0(G)),g.onAudio(G)},C.connect(V),V.connect(A.destination)}catch(W){throw L(),W}},L=()=>{V?.disconnect(),C?.disconnect(),_?.getTracks().forEach((U)=>U.stop()),A?.close(),g.onLevel?.(0),A=null,_=null,V=null,C=null};return{start:c,stop:L}};var M=(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 M(A.error);if("cause"in A)return M(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},d=(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:M(g.message),type:"error"};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 e0=Math.PI*2;var w=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var J0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,j=(g)=>g.length===0?void 0:Math.max(...g);var D=(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},O=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},x=(g)=>String(g.id??O(g,"ssrc")??D(g,"ssrc")??O(g,"trackIdentifier")??O(g,"mid")??"unknown"),l=(g)=>g===void 0?void 0:g*1000;var Y0=(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 v=(g={})=>{let A=g.stats??[],C=[],V=A.filter((R)=>R.type==="inbound-rtp"&&O(R,"kind")!=="video"),_=A.filter((R)=>R.type==="outbound-rtp"&&O(R,"kind")!=="video"),c=A.filter((R)=>R.type==="candidate-pair"),L=A.filter((R)=>(R.type==="track"||R.type==="media-source")&&O(R,"kind")==="audio"),U=c.filter((R)=>b(R,"selected")===!0||b(R,"nominated")===!0||O(R,"state")==="succeeded").length,W=L.filter((R)=>O(R,"readyState")!=="ended"&&O(R,"trackState")!=="ended"&&b(R,"ended")!==!0).length,T=L.filter((R)=>O(R,"readyState")==="ended"||O(R,"trackState")==="ended"||b(R,"ended")===!0).length,$=V.reduce((R,S)=>R+(D(S,"packetsReceived")??0),0),G=_.reduce((R,S)=>R+(D(S,"packetsSent")??0),0),I=[...V,..._].reduce((R,S)=>R+Math.max(0,D(S,"packetsLost")??0),0),P=$+I,J=P===0?0:I/P,y=V.reduce((R,S)=>R+(D(S,"bytesReceived")??0),0),h=_.reduce((R,S)=>R+(D(S,"bytesSent")??0),0),Y=j(c.map((R)=>l(D(R,"currentRoundTripTime")??D(R,"roundTripTime"))).filter((R)=>R!==void 0)),Z=j([...V,..._].map((R)=>l(D(R,"jitter"))).filter((R)=>R!==void 0)),N=j(V.map((R)=>{let S=D(R,"jitterBufferDelay"),z=D(R,"jitterBufferEmittedCount");return S!==void 0&&z!==void 0&&z>0?S/z*1000:void 0}).filter((R)=>R!==void 0)),q=L.map((R)=>D(R,"audioLevel")).filter((R)=>R!==void 0);if(g.requireConnectedCandidatePair&&c.length>0&&U===0)w(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&W===0)w(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&J>g.maxPacketLossRatio)w(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(J)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&Y!==void 0&&Y>g.maxRoundTripTimeMs)w(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(Y)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&Z!==void 0&&Z>g.maxJitterMs)w(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(Z)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:U,audioLevelAverage:J0(q),bytesReceived:y,bytesSent:h,checkedAt:Date.now(),endedAudioTracks:T,inboundPackets:$,issues:C,jitterBufferDelayMs:N,jitterMs:Z,liveAudioTracks:W,outboundPackets:G,packetLossRatio:J,packetsLost:I,roundTripTimeMs:Y,status:C.some((R)=>R.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},i=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(Y0)};var n=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],_=new Map(C.map((I)=>[x(I),I])),L=A.filter((I)=>(I.type==="inbound-rtp"||I.type==="outbound-rtp")&&O(I,"kind")!=="video"&&O(I,"mediaType")!=="video").map((I)=>{let P=I.type==="outbound-rtp"?"outbound":"inbound",J=P==="outbound"?"packetsSent":"packetsReceived",y=P==="outbound"?"bytesSent":"bytesReceived",h=_.get(x(I)),Y=D(I,J),Z=h?D(h,J):void 0,N=D(I,y),q=h?D(h,y):void 0,R=I.timestamp!==void 0&&h?.timestamp!==void 0?I.timestamp-h.timestamp:void 0;return{bytesDelta:N!==void 0&&q!==void 0?N-q:void 0,currentPackets:Y,direction:P,id:x(I),packetDelta:Y!==void 0&&Z!==void 0?Y-Z:void 0,previousPackets:Z,timeDeltaMs:R}}),U=L.filter((I)=>I.direction==="inbound"),W=L.filter((I)=>I.direction==="outbound"),T=j(L.map((I)=>I.timeDeltaMs).filter((I)=>I!==void 0)),$=U.filter((I)=>g.maxInboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxInboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length,G=W.filter((I)=>g.maxOutboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxOutboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length;if(g.requireInboundAudio&&U.length===0)w(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&W.length===0)w(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&T!==void 0&&T>g.maxGapMs)w(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(T)}ms above ${String(g.maxGapMs)}ms.`);if($>0)w(V,"error","media.webrtc_inbound_stalled",`${String($)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(G>0)w(V,"error","media.webrtc_outbound_stalled",`${String(G)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:U.length,issues:V,maxObservedGapMs:T,outboundAudioStreams:W.length,stalledInboundStreams:$,stalledOutboundStreams:G,status:V.some((I)=>I.severity==="error")?"fail":V.length>0?"warn":"pass",streams:L,totalStats:A.length}};var Z0="/api/voice/browser-media",w0=5000,Q0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,P0=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"})},o=(g)=>{let A=null,C=[],V=async()=>{let L=await Q0(g);if(!L)return;let U=await i({peerConnection:L}),W=v({...g,stats:U}),T=g.continuity===!1?void 0:n({...g.continuity,previousStats:C,stats:U}),$={at:Date.now(),continuity:T,report:W,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=U,g.onReport?.($),await P0($,g),$},_=()=>{V().catch((L)=>{g.onError?.(L)})},c=()=>{if(A)clearInterval(A),A=null};return{close:c,reportOnce:V,stop:c,start:()=>{if(A)return;_(),A=setInterval(_,g.intervalMs??w0)}}};var K0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var E=()=>{},E0=()=>E,N0={callControl:E,close:E,disconnect:E,endTurn:E,send:E,sendAudio:E,simulateDisconnect:E,subscribe:E0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},q0=()=>crypto.randomUUID(),z0=(g,A,C,V)=>{let{hostname:_,port:c,protocol:L}=window.location,U=L==="https:"?"wss:":"ws:",W=c?`:${c}`:"",T=new URL(`${U}//${_}${W}${g}`);if(T.searchParams.set("sessionId",A),C)T.searchParams.set("scenarioId",C);for(let[$,G]of Object.entries(V))if($!=="sessionId"&&$!=="scenarioId"&&G)T.searchParams.set($,G);return T.toString()},b0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},j0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return b0(A)?A:null}catch{return null}},m=(g,A={})=>{if(typeof window>"u")return N0;let C=new Set,V=A.reconnect!==!1,_=A.maxReconnectAttempts??15,c=A.reconnectMaxDelayMs??8000,L=A.pingInterval??30000,U=A.reconnectResetAfterMs??30000,W=(H)=>K0(H,500,c),T={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:A.sessionId??q0(),ws:null},$=(H)=>{C.forEach((X)=>X(H))},G=()=>{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},I=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let H=T.pendingMessages.shift();if(H!==void 0)T.ws.send(H)}},P=()=>{T.reconnectAttempts+=1;let H=W(T.reconnectAttempts),X=Date.now()+H;$({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:X,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(()=>{if(T.reconnectAttempts>_){$({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"});return}J()},H)},J=()=>{let H=new WebSocket(z0(g,T.sessionId,T.scenarioId,A.query??{}));H.binaryType="arraybuffer",H.onopen=()=>{if(T.ws!==H){H.close(1000);return}let X=T.reconnectAttempts>0;if(T.isConnected=!0,I(),X)$({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),T.reconnectResetTimeout=setTimeout(()=>{if(T.ws===H&&H.readyState===1)T.reconnectAttempts=0;T.reconnectResetTimeout=null},U);C.forEach((K)=>K({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(H.readyState===1)H.send(JSON.stringify({type:"ping"}))},L)},H.onmessage=(X)=>{if(T.ws!==H)return;let K=j0(X);if(!K)return;if(K.type==="session")T.sessionId=K.sessionId,T.scenarioId=K.scenarioId??T.scenarioId;C.forEach((_0)=>_0(K))},H.onclose=(X)=>{if(T.ws!==H)return;if(T.ws=null,T.isConnected=!1,G(),V&&X.code!==1000&&T.reconnectAttempts<_)P();else if(V&&X.code!==1000)$({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"})},T.ws=H},y=(H)=>{if(T.ws?.readyState===1){T.ws.send(H);return}T.pendingMessages.push(H)},h=(H)=>{y(JSON.stringify(H))},Y=(H={})=>{if(H.sessionId)T.sessionId=H.sessionId;if(H.scenarioId)T.scenarioId=H.scenarioId;h({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},Z=(H)=>{y(H)},N=()=>{h({type:"end_turn"})},q=(H)=>{h({...H,type:"call_control"})},R=(H="client-close")=>{if(G(),T.ws){let X=T.ws;if(T.ws=null,X.readyState===1)X.send(JSON.stringify({reason:H,type:"close"}));X.close(1000)}T.isConnected=!1,C.clear()},S=()=>{if(G(),T.ws){let H=T.ws;T.ws=null,H.close(1000)}T.isConnected=!1,C.clear()},z=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},T0=(H)=>{return C.add(H),()=>{C.delete(H)}};return J(),{callControl:q,close:R,disconnect:S,endTurn:N,send:h,sendAudio:Z,simulateDisconnect:z,start:Y,subscribe:T0,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var B0=()=>({attempts:0,maxAttempts:0,status:"idle"}),M0=(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}`},x0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},k0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:B0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),r=()=>{let g=k0(),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};break;case"final":A=M0(A,c.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:x0(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 u=(g,A={})=>{let C=m(g,A),V=r(),_=A.browserMedia&&typeof window<"u"?o({...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,L=($)=>Promise.resolve().then(()=>{if(!$?.sessionId&&!$?.scenarioId)return;C.start($),_?.start()}),U=()=>{c.forEach(($)=>$())},W=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let $=V.getSnapshot(),G=JSON.stringify({at:Date.now(),reconnect:$.reconnect,scenarioId:$.scenarioId,sessionId:C.getSessionId(),turnIds:$.turns.map((I)=>I.id)});fetch(A.reconnectReportPath,{body:G,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},T=C.subscribe(($)=>{let G=d($);if(G){if(V.dispatch(G),$.type==="connection")W();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($){C.callControl($)},close($){T(),_?.close(),C.close($),V.dispatch({type:"disconnected"}),U()},disconnect(){T(),_?.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($){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 s=(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 F0={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}},f0={"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 a=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=F0[A],_=f0[C],c=g?.silenceMs??_.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(c,g?.minSilenceMs??_.minSilenceMs??V.minSilenceMs),silenceMs:c,speechThreshold:g?.speechThreshold??_.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??_.transcriptStabilityMs??V.transcriptStabilityMs}};var d0={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"}}},p=(g="default")=>{let A=d0[g];return{audioConditioning:s(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:a(A.turnDetection)}};var l0=(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]}),t=(g,A={})=>{let C=p(A.preset),V=u(g,{...C.connection,...A.connection}),_=null,c=l0(V),L=new Set,U=()=>{for(let y of L)y()},W=()=>{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)_?.stop(),_=null,c={...c,isRecording:!1};U()},T=V.subscribe(W);W();let $=()=>{if(_)return _;return _=f({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(y)=>{if(A.capture?.onAudio){A.capture.onAudio(y,V.sendAudio);return}V.sendAudio(y)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),_},G=()=>{_?.stop(),_=null,c={...c,isRecording:!1},U()},I=async()=>{if(c.isRecording)return;try{c={...c,recordingError:null},U(),await $().start(),c={...c,isRecording:!0},U()}catch(y){throw _?.stop(),_=null,c={...c,isRecording:!1,recordingError:y instanceof Error?y.message:String(y)},U(),y}};return{close:(y)=>{T(),G(),V.close(y)},disconnect:()=>{T(),G(),V.disconnect()},startRecording:I,stopRecording:G,get assistantAudio(){return c.assistantAudio},get assistantTexts(){return c.assistantTexts},get assistantStreamingText(){return c.assistantStreamingText},bindHTMX(y){return F(V,y)},get call(){return c.call},callControl:(y)=>V.callControl(y),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:(y)=>V.sendAudio(y),get sessionId(){return c.sessionId},get sessionMetadata(){return c.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return c.status},subscribe:(y)=>{return L.add(y),()=>{L.delete(y)}},toggleRecording:async()=>{if(c.isRecording){G();return}await I()},get turns(){return c.turns}}};var Q=(g)=>String(g).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");var e=(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 v0={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},i0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},n0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},g0=(g)=>{let A={...v0,...g.theme},C={...i0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,_=g.state.turns.at(-1)?.committedAt,c=e({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:n0(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:U,theme:A,title:g.title??"Voice"}},o0=(g)=>typeof g==="number"?`${g}px`:g,A0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${o0(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;">${Q(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;">${Q(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;">${Q(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${Q(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="${P(g.classes.dot)}" style="${V}"></span>
4
- <strong style="font-size:15px;">${P(g.title)}</strong>
5
- <span style="font-size:13px;margin-left:auto;opacity:0.7;">${P(g.statusLabel)}</span>
3
+ <span aria-hidden="true" class="${Q(g.classes.dot)}" style="${V}"></span>
4
+ <strong style="font-size:15px;">${Q(g.title)}</strong>
5
+ <span style="font-size:13px;margin-left:auto;opacity:0.7;">${Q(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;">“${P(g.partial)}”</p>`:""}
8
- <div style="display:flex;gap:10px;">${R.join("")}</div>
9
- ${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${P(g.errorMessage)}</p>`:""}
10
- </div>`};var o0=(g)=>{if(typeof g!=="string")return g;let A=document.querySelector(g);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${g}"`);return A},V0=(g,A={})=>{let C=o0(g),V=t(A.path??"/voice",A.controllerOptions),R=null,c=null,U=()=>{let T=g0({...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=A0(T);for(let G of C.querySelectorAll("button[data-action]")){let{action:$}=G.dataset;G.addEventListener("click",()=>{if($==="start")V.startRecording();else if($==="mute")V.stopRecording();else if($==="end")V.close()})}if(V.error&&V.error!==R)R=V.error,A.onError?.(V.error);if(V.status!==c)c=V.status,A.onStatusChange?.(V.status)},H=V.subscribe(U);if(U(),A.autoStart)V.startRecording();return{controller:V,async end(){await V.close()},mute(){V.stopRecording()},async start(){await V.startRecording()},unmount(){H(),V.close(),C.innerHTML=""}}},C0="0.0.22-beta.516",c0={mount:V0,version:C0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=c0;var r0=c0;})();
7
+ ${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${Q(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;">${Q(g.errorMessage)}</p>`:""}
10
+ </div>`};var m0=(g)=>{if(typeof g!=="string")return g;let A=document.querySelector(g);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${g}"`);return A},V0=(g,A={})=>{let C=m0(g),V=t(A.path??"/voice",A.controllerOptions),_=null,c=null,L=()=>{let W=g0({...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=A0(W);for(let T of C.querySelectorAll("button[data-action]")){let{action:$}=T.dataset;T.addEventListener("click",()=>{if($==="start")V.startRecording();else if($==="mute")V.stopRecording();else if($==="end")V.close()})}if(V.error&&V.error!==_)_=V.error,A.onError?.(V.error);if(V.status!==c)c=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=""}}},C0="0.0.22-beta.516",c0={mount:V0,version:C0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=c0;var u0=c0;})();
package/dist/index.js CHANGED
@@ -39955,6 +39955,27 @@ var resolveSessionId3 = (runtime, ws) => {
39955
39955
  return resolved;
39956
39956
  };
39957
39957
  var resolveSocketQuery = (ws) => ws.data && typeof ws.data === "object" && ("query" in ws.data) ? ws.data.query ?? {} : {};
39958
+ var resolveSocketHeaders = (ws) => {
39959
+ if (!ws.data || typeof ws.data !== "object")
39960
+ return new Headers;
39961
+ if ("request" in ws.data && ws.data.request instanceof Request) {
39962
+ return ws.data.request.headers;
39963
+ }
39964
+ if ("headers" in ws.data) {
39965
+ const { headers } = ws.data;
39966
+ if (headers instanceof Headers)
39967
+ return headers;
39968
+ if (headers && typeof headers === "object") {
39969
+ const resolved = new Headers;
39970
+ for (const [key, value] of Object.entries(headers)) {
39971
+ if (typeof value === "string")
39972
+ resolved.set(key, value);
39973
+ }
39974
+ return resolved;
39975
+ }
39976
+ }
39977
+ return new Headers;
39978
+ };
39958
39979
  var resolveMaybeFunction = async (value, input) => typeof value === "function" ? await value(input) : value;
39959
39980
  var toAudioChunk = (raw) => {
39960
39981
  if (raw instanceof ArrayBuffer) {
@@ -40120,6 +40141,7 @@ var voice = (config) => {
40120
40141
  }
40121
40142
  const monitorBindings = new Map;
40122
40143
  const runtime = {
40144
+ activeSockets: new Map,
40123
40145
  activeSessions: new Map,
40124
40146
  pendingSessions: new Map,
40125
40147
  logger: resolveLogger(config.logger),
@@ -40137,6 +40159,7 @@ var voice = (config) => {
40137
40159
  try {
40138
40160
  authorized = await config.authorizeConnection({
40139
40161
  context: ws.data,
40162
+ headers: resolveSocketHeaders(ws),
40140
40163
  path: config.path,
40141
40164
  query: resolveSocketQuery(ws),
40142
40165
  scenarioId,
@@ -40461,9 +40484,10 @@ var voice = (config) => {
40461
40484
  return new Elysia70({ name: "absolutejs-voice" }).ws(config.path, {
40462
40485
  close: async (ws, code, reason) => {
40463
40486
  const socketState = runtime.socketSessions.get(ws);
40464
- if (!socketState) {
40487
+ if (!socketState || runtime.activeSockets.get(socketState.sessionId) !== ws) {
40465
40488
  return;
40466
40489
  }
40490
+ runtime.activeSockets.delete(socketState.sessionId);
40467
40491
  const session = runtime.activeSessions.get(socketState.sessionId);
40468
40492
  runtime.activeSessions.delete(socketState.sessionId);
40469
40493
  deregisterMonitorSession(socketState.sessionId, reason ?? `ws-close-${String(code)}`);
@@ -40493,6 +40517,7 @@ var voice = (config) => {
40493
40517
  }
40494
40518
  if (message.type === "close" && current) {
40495
40519
  await current.close(message.reason);
40520
+ runtime.activeSockets.delete(sessionState.sessionId);
40496
40521
  runtime.activeSessions.delete(sessionState.sessionId);
40497
40522
  deregisterMonitorSession(sessionState.sessionId, message.reason);
40498
40523
  }
@@ -40542,10 +40567,12 @@ var voice = (config) => {
40542
40567
  const currentSession = runtime.activeSessions.get(sessionState.sessionId);
40543
40568
  if (currentSession) {
40544
40569
  await currentSession.close("session-switch");
40570
+ runtime.activeSockets.delete(sessionState.sessionId);
40545
40571
  runtime.activeSessions.delete(sessionState.sessionId);
40546
40572
  deregisterMonitorSession(sessionState.sessionId, "session-switch");
40547
40573
  }
40548
40574
  sessionState.sessionId = message.sessionId;
40575
+ runtime.activeSockets.set(message.sessionId, ws);
40549
40576
  runtime.socketSessions.set(ws, {
40550
40577
  ...sessionState,
40551
40578
  sessionId: message.sessionId,
@@ -40574,8 +40601,14 @@ var voice = (config) => {
40574
40601
  return;
40575
40602
  }
40576
40603
  const existing = runtime.activeSessions.get(sessionState.sessionId);
40604
+ runtime.activeSockets.set(sessionState.sessionId, ws);
40577
40605
  if (existing) {
40578
- await existing.close("superseded");
40606
+ await existing.disconnect({
40607
+ code: 1006,
40608
+ reason: "superseded",
40609
+ recoverable: true,
40610
+ type: "close"
40611
+ });
40579
40612
  runtime.activeSessions.delete(sessionState.sessionId);
40580
40613
  deregisterMonitorSession(sessionState.sessionId, "superseded");
40581
40614
  }
@@ -11435,6 +11435,7 @@ var WS_CLOSED = 3;
11435
11435
  var WS_NORMAL_CLOSURE = 1000;
11436
11436
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
11437
11437
  var DEFAULT_PING_INTERVAL = 30000;
11438
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
11438
11439
  var RECONNECT_BASE_DELAY_MS = 500;
11439
11440
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
11440
11441
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
@@ -11514,6 +11515,7 @@ var createVoiceConnection = (path, options = {}) => {
11514
11515
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
11515
11516
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
11516
11517
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
11518
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
11517
11519
  const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
11518
11520
  const state = {
11519
11521
  isConnected: false,
@@ -11521,6 +11523,7 @@ var createVoiceConnection = (path, options = {}) => {
11521
11523
  scenarioId: options.scenarioId ?? null,
11522
11524
  pingInterval: null,
11523
11525
  reconnectAttempts: 0,
11526
+ reconnectResetTimeout: null,
11524
11527
  reconnectTimeout: null,
11525
11528
  sessionId: options.sessionId ?? createSessionId(),
11526
11529
  ws: null
@@ -11537,6 +11540,10 @@ var createVoiceConnection = (path, options = {}) => {
11537
11540
  clearTimeout(state.reconnectTimeout);
11538
11541
  state.reconnectTimeout = null;
11539
11542
  }
11543
+ if (state.reconnectResetTimeout) {
11544
+ clearTimeout(state.reconnectResetTimeout);
11545
+ state.reconnectResetTimeout = null;
11546
+ }
11540
11547
  };
11541
11548
  const flushPendingMessages = () => {
11542
11549
  if (state.ws?.readyState !== WS_OPEN) {
@@ -11582,6 +11589,10 @@ var createVoiceConnection = (path, options = {}) => {
11582
11589
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
11583
11590
  ws.binaryType = "arraybuffer";
11584
11591
  ws.onopen = () => {
11592
+ if (state.ws !== ws) {
11593
+ ws.close(WS_NORMAL_CLOSURE);
11594
+ return;
11595
+ }
11585
11596
  const wasReconnecting = state.reconnectAttempts > 0;
11586
11597
  state.isConnected = true;
11587
11598
  flushPendingMessages();
@@ -11595,7 +11606,12 @@ var createVoiceConnection = (path, options = {}) => {
11595
11606
  },
11596
11607
  type: "connection"
11597
11608
  });
11598
- state.reconnectAttempts = 0;
11609
+ state.reconnectResetTimeout = setTimeout(() => {
11610
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
11611
+ state.reconnectAttempts = 0;
11612
+ }
11613
+ state.reconnectResetTimeout = null;
11614
+ }, reconnectResetAfterMs);
11599
11615
  }
11600
11616
  listeners.forEach((listener) => listener({
11601
11617
  scenarioId: state.scenarioId ?? undefined,
@@ -11610,6 +11626,8 @@ var createVoiceConnection = (path, options = {}) => {
11610
11626
  }, pingInterval);
11611
11627
  };
11612
11628
  ws.onmessage = (event) => {
11629
+ if (state.ws !== ws)
11630
+ return;
11613
11631
  const parsed = parseServerMessage(event);
11614
11632
  if (!parsed) {
11615
11633
  return;
@@ -11621,6 +11639,9 @@ var createVoiceConnection = (path, options = {}) => {
11621
11639
  listeners.forEach((listener) => listener(parsed));
11622
11640
  };
11623
11641
  ws.onclose = (event) => {
11642
+ if (state.ws !== ws)
11643
+ return;
11644
+ state.ws = null;
11624
11645
  state.isConnected = false;
11625
11646
  clearTimers();
11626
11647
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
@@ -11678,11 +11699,12 @@ var createVoiceConnection = (path, options = {}) => {
11678
11699
  const close = (reason = "client-close") => {
11679
11700
  clearTimers();
11680
11701
  if (state.ws) {
11681
- if (state.ws.readyState === WS_OPEN) {
11682
- state.ws.send(JSON.stringify({ reason, type: "close" }));
11683
- }
11684
- state.ws.close(WS_NORMAL_CLOSURE);
11702
+ const ws = state.ws;
11685
11703
  state.ws = null;
11704
+ if (ws.readyState === WS_OPEN) {
11705
+ ws.send(JSON.stringify({ reason, type: "close" }));
11706
+ }
11707
+ ws.close(WS_NORMAL_CLOSURE);
11686
11708
  }
11687
11709
  state.isConnected = false;
11688
11710
  listeners.clear();
@@ -11690,8 +11712,9 @@ var createVoiceConnection = (path, options = {}) => {
11690
11712
  const disconnect = () => {
11691
11713
  clearTimers();
11692
11714
  if (state.ws) {
11693
- state.ws.close(WS_NORMAL_CLOSURE);
11715
+ const ws = state.ws;
11694
11716
  state.ws = null;
11717
+ ws.close(WS_NORMAL_CLOSURE);
11695
11718
  }
11696
11719
  state.isConnected = false;
11697
11720
  listeners.clear();
@@ -763,6 +763,7 @@ var WS_CLOSED = 3;
763
763
  var WS_NORMAL_CLOSURE = 1000;
764
764
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
765
765
  var DEFAULT_PING_INTERVAL = 30000;
766
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
766
767
  var RECONNECT_BASE_DELAY_MS = 500;
767
768
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
768
769
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
@@ -842,6 +843,7 @@ var createVoiceConnection = (path, options = {}) => {
842
843
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
843
844
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
844
845
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
846
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
845
847
  const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
846
848
  const state = {
847
849
  isConnected: false,
@@ -849,6 +851,7 @@ var createVoiceConnection = (path, options = {}) => {
849
851
  scenarioId: options.scenarioId ?? null,
850
852
  pingInterval: null,
851
853
  reconnectAttempts: 0,
854
+ reconnectResetTimeout: null,
852
855
  reconnectTimeout: null,
853
856
  sessionId: options.sessionId ?? createSessionId(),
854
857
  ws: null
@@ -865,6 +868,10 @@ var createVoiceConnection = (path, options = {}) => {
865
868
  clearTimeout(state.reconnectTimeout);
866
869
  state.reconnectTimeout = null;
867
870
  }
871
+ if (state.reconnectResetTimeout) {
872
+ clearTimeout(state.reconnectResetTimeout);
873
+ state.reconnectResetTimeout = null;
874
+ }
868
875
  };
869
876
  const flushPendingMessages = () => {
870
877
  if (state.ws?.readyState !== WS_OPEN) {
@@ -910,6 +917,10 @@ var createVoiceConnection = (path, options = {}) => {
910
917
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
911
918
  ws.binaryType = "arraybuffer";
912
919
  ws.onopen = () => {
920
+ if (state.ws !== ws) {
921
+ ws.close(WS_NORMAL_CLOSURE);
922
+ return;
923
+ }
913
924
  const wasReconnecting = state.reconnectAttempts > 0;
914
925
  state.isConnected = true;
915
926
  flushPendingMessages();
@@ -923,7 +934,12 @@ var createVoiceConnection = (path, options = {}) => {
923
934
  },
924
935
  type: "connection"
925
936
  });
926
- state.reconnectAttempts = 0;
937
+ state.reconnectResetTimeout = setTimeout(() => {
938
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
939
+ state.reconnectAttempts = 0;
940
+ }
941
+ state.reconnectResetTimeout = null;
942
+ }, reconnectResetAfterMs);
927
943
  }
928
944
  listeners.forEach((listener) => listener({
929
945
  scenarioId: state.scenarioId ?? undefined,
@@ -938,6 +954,8 @@ var createVoiceConnection = (path, options = {}) => {
938
954
  }, pingInterval);
939
955
  };
940
956
  ws.onmessage = (event) => {
957
+ if (state.ws !== ws)
958
+ return;
941
959
  const parsed = parseServerMessage(event);
942
960
  if (!parsed) {
943
961
  return;
@@ -949,6 +967,9 @@ var createVoiceConnection = (path, options = {}) => {
949
967
  listeners.forEach((listener) => listener(parsed));
950
968
  };
951
969
  ws.onclose = (event) => {
970
+ if (state.ws !== ws)
971
+ return;
972
+ state.ws = null;
952
973
  state.isConnected = false;
953
974
  clearTimers();
954
975
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
@@ -1006,11 +1027,12 @@ var createVoiceConnection = (path, options = {}) => {
1006
1027
  const close = (reason = "client-close") => {
1007
1028
  clearTimers();
1008
1029
  if (state.ws) {
1009
- if (state.ws.readyState === WS_OPEN) {
1010
- state.ws.send(JSON.stringify({ reason, type: "close" }));
1011
- }
1012
- state.ws.close(WS_NORMAL_CLOSURE);
1030
+ const ws = state.ws;
1013
1031
  state.ws = null;
1032
+ if (ws.readyState === WS_OPEN) {
1033
+ ws.send(JSON.stringify({ reason, type: "close" }));
1034
+ }
1035
+ ws.close(WS_NORMAL_CLOSURE);
1014
1036
  }
1015
1037
  state.isConnected = false;
1016
1038
  listeners.clear();
@@ -1018,8 +1040,9 @@ var createVoiceConnection = (path, options = {}) => {
1018
1040
  const disconnect = () => {
1019
1041
  clearTimers();
1020
1042
  if (state.ws) {
1021
- state.ws.close(WS_NORMAL_CLOSURE);
1043
+ const ws = state.ws;
1022
1044
  state.ws = null;
1045
+ ws.close(WS_NORMAL_CLOSURE);
1023
1046
  }
1024
1047
  state.isConnected = false;
1025
1048
  listeners.clear();
@@ -2984,6 +2984,7 @@ var WS_CLOSED = 3;
2984
2984
  var WS_NORMAL_CLOSURE = 1000;
2985
2985
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
2986
2986
  var DEFAULT_PING_INTERVAL = 30000;
2987
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
2987
2988
  var RECONNECT_BASE_DELAY_MS = 500;
2988
2989
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
2989
2990
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
@@ -3063,6 +3064,7 @@ var createVoiceConnection = (path, options = {}) => {
3063
3064
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
3064
3065
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
3065
3066
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
3067
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
3066
3068
  const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
3067
3069
  const state = {
3068
3070
  isConnected: false,
@@ -3070,6 +3072,7 @@ var createVoiceConnection = (path, options = {}) => {
3070
3072
  scenarioId: options.scenarioId ?? null,
3071
3073
  pingInterval: null,
3072
3074
  reconnectAttempts: 0,
3075
+ reconnectResetTimeout: null,
3073
3076
  reconnectTimeout: null,
3074
3077
  sessionId: options.sessionId ?? createSessionId(),
3075
3078
  ws: null
@@ -3086,6 +3089,10 @@ var createVoiceConnection = (path, options = {}) => {
3086
3089
  clearTimeout(state.reconnectTimeout);
3087
3090
  state.reconnectTimeout = null;
3088
3091
  }
3092
+ if (state.reconnectResetTimeout) {
3093
+ clearTimeout(state.reconnectResetTimeout);
3094
+ state.reconnectResetTimeout = null;
3095
+ }
3089
3096
  };
3090
3097
  const flushPendingMessages = () => {
3091
3098
  if (state.ws?.readyState !== WS_OPEN) {
@@ -3131,6 +3138,10 @@ var createVoiceConnection = (path, options = {}) => {
3131
3138
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
3132
3139
  ws.binaryType = "arraybuffer";
3133
3140
  ws.onopen = () => {
3141
+ if (state.ws !== ws) {
3142
+ ws.close(WS_NORMAL_CLOSURE);
3143
+ return;
3144
+ }
3134
3145
  const wasReconnecting = state.reconnectAttempts > 0;
3135
3146
  state.isConnected = true;
3136
3147
  flushPendingMessages();
@@ -3144,7 +3155,12 @@ var createVoiceConnection = (path, options = {}) => {
3144
3155
  },
3145
3156
  type: "connection"
3146
3157
  });
3147
- state.reconnectAttempts = 0;
3158
+ state.reconnectResetTimeout = setTimeout(() => {
3159
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
3160
+ state.reconnectAttempts = 0;
3161
+ }
3162
+ state.reconnectResetTimeout = null;
3163
+ }, reconnectResetAfterMs);
3148
3164
  }
3149
3165
  listeners.forEach((listener) => listener({
3150
3166
  scenarioId: state.scenarioId ?? undefined,
@@ -3159,6 +3175,8 @@ var createVoiceConnection = (path, options = {}) => {
3159
3175
  }, pingInterval);
3160
3176
  };
3161
3177
  ws.onmessage = (event) => {
3178
+ if (state.ws !== ws)
3179
+ return;
3162
3180
  const parsed = parseServerMessage(event);
3163
3181
  if (!parsed) {
3164
3182
  return;
@@ -3170,6 +3188,9 @@ var createVoiceConnection = (path, options = {}) => {
3170
3188
  listeners.forEach((listener) => listener(parsed));
3171
3189
  };
3172
3190
  ws.onclose = (event) => {
3191
+ if (state.ws !== ws)
3192
+ return;
3193
+ state.ws = null;
3173
3194
  state.isConnected = false;
3174
3195
  clearTimers();
3175
3196
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
@@ -3227,11 +3248,12 @@ var createVoiceConnection = (path, options = {}) => {
3227
3248
  const close = (reason = "client-close") => {
3228
3249
  clearTimers();
3229
3250
  if (state.ws) {
3230
- if (state.ws.readyState === WS_OPEN) {
3231
- state.ws.send(JSON.stringify({ reason, type: "close" }));
3232
- }
3233
- state.ws.close(WS_NORMAL_CLOSURE);
3251
+ const ws = state.ws;
3234
3252
  state.ws = null;
3253
+ if (ws.readyState === WS_OPEN) {
3254
+ ws.send(JSON.stringify({ reason, type: "close" }));
3255
+ }
3256
+ ws.close(WS_NORMAL_CLOSURE);
3235
3257
  }
3236
3258
  state.isConnected = false;
3237
3259
  listeners.clear();
@@ -3239,8 +3261,9 @@ var createVoiceConnection = (path, options = {}) => {
3239
3261
  const disconnect = () => {
3240
3262
  clearTimers();
3241
3263
  if (state.ws) {
3242
- state.ws.close(WS_NORMAL_CLOSURE);
3264
+ const ws = state.ws;
3243
3265
  state.ws = null;
3266
+ ws.close(WS_NORMAL_CLOSURE);
3244
3267
  }
3245
3268
  state.isConnected = false;
3246
3269
  listeners.clear();
package/dist/vue/index.js CHANGED
@@ -10831,6 +10831,7 @@ var WS_CLOSED = 3;
10831
10831
  var WS_NORMAL_CLOSURE = 1000;
10832
10832
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
10833
10833
  var DEFAULT_PING_INTERVAL = 30000;
10834
+ var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
10834
10835
  var RECONNECT_BASE_DELAY_MS = 500;
10835
10836
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
10836
10837
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
@@ -10910,6 +10911,7 @@ var createVoiceConnection = (path, options = {}) => {
10910
10911
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
10911
10912
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
10912
10913
  const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
10914
+ const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
10913
10915
  const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
10914
10916
  const state = {
10915
10917
  isConnected: false,
@@ -10917,6 +10919,7 @@ var createVoiceConnection = (path, options = {}) => {
10917
10919
  scenarioId: options.scenarioId ?? null,
10918
10920
  pingInterval: null,
10919
10921
  reconnectAttempts: 0,
10922
+ reconnectResetTimeout: null,
10920
10923
  reconnectTimeout: null,
10921
10924
  sessionId: options.sessionId ?? createSessionId(),
10922
10925
  ws: null
@@ -10933,6 +10936,10 @@ var createVoiceConnection = (path, options = {}) => {
10933
10936
  clearTimeout(state.reconnectTimeout);
10934
10937
  state.reconnectTimeout = null;
10935
10938
  }
10939
+ if (state.reconnectResetTimeout) {
10940
+ clearTimeout(state.reconnectResetTimeout);
10941
+ state.reconnectResetTimeout = null;
10942
+ }
10936
10943
  };
10937
10944
  const flushPendingMessages = () => {
10938
10945
  if (state.ws?.readyState !== WS_OPEN) {
@@ -10978,6 +10985,10 @@ var createVoiceConnection = (path, options = {}) => {
10978
10985
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
10979
10986
  ws.binaryType = "arraybuffer";
10980
10987
  ws.onopen = () => {
10988
+ if (state.ws !== ws) {
10989
+ ws.close(WS_NORMAL_CLOSURE);
10990
+ return;
10991
+ }
10981
10992
  const wasReconnecting = state.reconnectAttempts > 0;
10982
10993
  state.isConnected = true;
10983
10994
  flushPendingMessages();
@@ -10991,7 +11002,12 @@ var createVoiceConnection = (path, options = {}) => {
10991
11002
  },
10992
11003
  type: "connection"
10993
11004
  });
10994
- state.reconnectAttempts = 0;
11005
+ state.reconnectResetTimeout = setTimeout(() => {
11006
+ if (state.ws === ws && ws.readyState === WS_OPEN) {
11007
+ state.reconnectAttempts = 0;
11008
+ }
11009
+ state.reconnectResetTimeout = null;
11010
+ }, reconnectResetAfterMs);
10995
11011
  }
10996
11012
  listeners.forEach((listener) => listener({
10997
11013
  scenarioId: state.scenarioId ?? undefined,
@@ -11006,6 +11022,8 @@ var createVoiceConnection = (path, options = {}) => {
11006
11022
  }, pingInterval);
11007
11023
  };
11008
11024
  ws.onmessage = (event) => {
11025
+ if (state.ws !== ws)
11026
+ return;
11009
11027
  const parsed = parseServerMessage(event);
11010
11028
  if (!parsed) {
11011
11029
  return;
@@ -11017,6 +11035,9 @@ var createVoiceConnection = (path, options = {}) => {
11017
11035
  listeners.forEach((listener) => listener(parsed));
11018
11036
  };
11019
11037
  ws.onclose = (event) => {
11038
+ if (state.ws !== ws)
11039
+ return;
11040
+ state.ws = null;
11020
11041
  state.isConnected = false;
11021
11042
  clearTimers();
11022
11043
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
@@ -11074,11 +11095,12 @@ var createVoiceConnection = (path, options = {}) => {
11074
11095
  const close = (reason = "client-close") => {
11075
11096
  clearTimers();
11076
11097
  if (state.ws) {
11077
- if (state.ws.readyState === WS_OPEN) {
11078
- state.ws.send(JSON.stringify({ reason, type: "close" }));
11079
- }
11080
- state.ws.close(WS_NORMAL_CLOSURE);
11098
+ const ws = state.ws;
11081
11099
  state.ws = null;
11100
+ if (ws.readyState === WS_OPEN) {
11101
+ ws.send(JSON.stringify({ reason, type: "close" }));
11102
+ }
11103
+ ws.close(WS_NORMAL_CLOSURE);
11082
11104
  }
11083
11105
  state.isConnected = false;
11084
11106
  listeners.clear();
@@ -11086,8 +11108,9 @@ var createVoiceConnection = (path, options = {}) => {
11086
11108
  const disconnect = () => {
11087
11109
  clearTimers();
11088
11110
  if (state.ws) {
11089
- state.ws.close(WS_NORMAL_CLOSURE);
11111
+ const ws = state.ws;
11090
11112
  state.ws = null;
11113
+ ws.close(WS_NORMAL_CLOSURE);
11091
11114
  }
11092
11115
  state.isConnected = false;
11093
11116
  listeners.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.649",
3
+ "version": "0.0.22-beta.650",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",