@absolutejs/voice 0.0.22-beta.659 → 0.0.22-beta.660

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.
@@ -625,6 +625,7 @@ var WS_CLOSED = 3;
625
625
  var WS_NORMAL_CLOSURE = 1000;
626
626
  var WS_UNAUTHORIZED = 4401;
627
627
  var WS_FORBIDDEN = 4403;
628
+ var WS_INITIALIZATION_FAILED = 4500;
628
629
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
629
630
  var DEFAULT_PING_INTERVAL = 30000;
630
631
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
@@ -928,8 +929,9 @@ var createVoiceConnection = (path, options = {}) => {
928
929
  state.isConnected = false;
929
930
  clearTimers();
930
931
  rejectPendingCallControls("Voice connection interrupted");
931
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
932
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
932
933
  const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
934
+ const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
933
935
  if (authorizationFailure) {
934
936
  listeners.forEach((listener) => listener({
935
937
  message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
@@ -937,6 +939,13 @@ var createVoiceConnection = (path, options = {}) => {
937
939
  type: "error"
938
940
  }));
939
941
  }
942
+ if (initializationFailure) {
943
+ listeners.forEach((listener) => listener({
944
+ message: "Voice provider initialization failed.",
945
+ recoverable: false,
946
+ type: "error"
947
+ }));
948
+ }
940
949
  if (reconnectable) {
941
950
  scheduleReconnect();
942
951
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -950,7 +959,7 @@ var createVoiceConnection = (path, options = {}) => {
950
959
  },
951
960
  type: "connection"
952
961
  });
953
- } else {
962
+ } else if (!initializationFailure) {
954
963
  emitTerminalFailure("Voice connection could not be restored.");
955
964
  }
956
965
  }
@@ -976,6 +985,9 @@ var createVoiceConnection = (path, options = {}) => {
976
985
  sendSerialized(JSON.stringify(message));
977
986
  };
978
987
  const start = (input = {}) => {
988
+ if (input.sessionId && input.sessionId !== state.sessionId) {
989
+ throw new Error("Voice session switching requires a new connection instance.");
990
+ }
979
991
  if (input.sessionId) {
980
992
  state.sessionId = input.sessionId;
981
993
  }
@@ -542,6 +542,7 @@ var WS_CLOSED = 3;
542
542
  var WS_NORMAL_CLOSURE = 1000;
543
543
  var WS_UNAUTHORIZED = 4401;
544
544
  var WS_FORBIDDEN = 4403;
545
+ var WS_INITIALIZATION_FAILED = 4500;
545
546
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
546
547
  var DEFAULT_PING_INTERVAL = 30000;
547
548
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
@@ -845,8 +846,9 @@ var createVoiceConnection = (path, options = {}) => {
845
846
  state.isConnected = false;
846
847
  clearTimers();
847
848
  rejectPendingCallControls("Voice connection interrupted");
848
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
849
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
849
850
  const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
851
+ const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
850
852
  if (authorizationFailure) {
851
853
  listeners.forEach((listener) => listener({
852
854
  message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
@@ -854,6 +856,13 @@ var createVoiceConnection = (path, options = {}) => {
854
856
  type: "error"
855
857
  }));
856
858
  }
859
+ if (initializationFailure) {
860
+ listeners.forEach((listener) => listener({
861
+ message: "Voice provider initialization failed.",
862
+ recoverable: false,
863
+ type: "error"
864
+ }));
865
+ }
857
866
  if (reconnectable) {
858
867
  scheduleReconnect();
859
868
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -867,7 +876,7 @@ var createVoiceConnection = (path, options = {}) => {
867
876
  },
868
877
  type: "connection"
869
878
  });
870
- } else {
879
+ } else if (!initializationFailure) {
871
880
  emitTerminalFailure("Voice connection could not be restored.");
872
881
  }
873
882
  }
@@ -893,6 +902,9 @@ var createVoiceConnection = (path, options = {}) => {
893
902
  sendSerialized(JSON.stringify(message));
894
903
  };
895
904
  const start = (input = {}) => {
905
+ if (input.sessionId && input.sessionId !== state.sessionId) {
906
+ throw new Error("Voice session switching requires a new connection instance.");
907
+ }
896
908
  if (input.sessionId) {
897
909
  state.sessionId = input.sessionId;
898
910
  }
@@ -106,6 +106,7 @@ var WS_CLOSED = 3;
106
106
  var WS_NORMAL_CLOSURE = 1000;
107
107
  var WS_UNAUTHORIZED = 4401;
108
108
  var WS_FORBIDDEN = 4403;
109
+ var WS_INITIALIZATION_FAILED = 4500;
109
110
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
110
111
  var DEFAULT_PING_INTERVAL = 30000;
111
112
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
@@ -409,8 +410,9 @@ var createVoiceConnection = (path, options = {}) => {
409
410
  state.isConnected = false;
410
411
  clearTimers();
411
412
  rejectPendingCallControls("Voice connection interrupted");
412
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
413
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
413
414
  const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
415
+ const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
414
416
  if (authorizationFailure) {
415
417
  listeners.forEach((listener) => listener({
416
418
  message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
@@ -418,6 +420,13 @@ var createVoiceConnection = (path, options = {}) => {
418
420
  type: "error"
419
421
  }));
420
422
  }
423
+ if (initializationFailure) {
424
+ listeners.forEach((listener) => listener({
425
+ message: "Voice provider initialization failed.",
426
+ recoverable: false,
427
+ type: "error"
428
+ }));
429
+ }
421
430
  if (reconnectable) {
422
431
  scheduleReconnect();
423
432
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -431,7 +440,7 @@ var createVoiceConnection = (path, options = {}) => {
431
440
  },
432
441
  type: "connection"
433
442
  });
434
- } else {
443
+ } else if (!initializationFailure) {
435
444
  emitTerminalFailure("Voice connection could not be restored.");
436
445
  }
437
446
  }
@@ -457,6 +466,9 @@ var createVoiceConnection = (path, options = {}) => {
457
466
  sendSerialized(JSON.stringify(message));
458
467
  };
459
468
  const start = (input = {}) => {
469
+ if (input.sessionId && input.sessionId !== state.sessionId) {
470
+ throw new Error("Voice session switching requires a new connection instance.");
471
+ }
460
472
  if (input.sessionId) {
461
473
  state.sessionId = input.sessionId;
462
474
  }
@@ -341,6 +341,12 @@ export type VoiceSessionRecord<TMeta = Record<string, never>, TResult = unknown>
341
341
  attempts: number;
342
342
  lastDisconnectAt?: number;
343
343
  };
344
+ /** Durable caller pause state so a reconnect or process replacement restores
345
+ * the remaining pause window instead of rearming conversation watchdogs. */
346
+ pause?: {
347
+ expiresAt: number;
348
+ pausedAt: number;
349
+ };
344
350
  lastCommittedTurn?: {
345
351
  signature: string;
346
352
  text: string;
@@ -539,6 +539,7 @@ var WS_CLOSED = 3;
539
539
  var WS_NORMAL_CLOSURE = 1000;
540
540
  var WS_UNAUTHORIZED = 4401;
541
541
  var WS_FORBIDDEN = 4403;
542
+ var WS_INITIALIZATION_FAILED = 4500;
542
543
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
543
544
  var DEFAULT_PING_INTERVAL = 30000;
544
545
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
@@ -842,8 +843,9 @@ var createVoiceConnection = (path, options = {}) => {
842
843
  state.isConnected = false;
843
844
  clearTimers();
844
845
  rejectPendingCallControls("Voice connection interrupted");
845
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
846
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
846
847
  const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
848
+ const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
847
849
  if (authorizationFailure) {
848
850
  listeners.forEach((listener) => listener({
849
851
  message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
@@ -851,6 +853,13 @@ var createVoiceConnection = (path, options = {}) => {
851
853
  type: "error"
852
854
  }));
853
855
  }
856
+ if (initializationFailure) {
857
+ listeners.forEach((listener) => listener({
858
+ message: "Voice provider initialization failed.",
859
+ recoverable: false,
860
+ type: "error"
861
+ }));
862
+ }
854
863
  if (reconnectable) {
855
864
  scheduleReconnect();
856
865
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -864,7 +873,7 @@ var createVoiceConnection = (path, options = {}) => {
864
873
  },
865
874
  type: "connection"
866
875
  });
867
- } else {
876
+ } else if (!initializationFailure) {
868
877
  emitTerminalFailure("Voice connection could not be restored.");
869
878
  }
870
879
  }
@@ -890,6 +899,9 @@ var createVoiceConnection = (path, options = {}) => {
890
899
  sendSerialized(JSON.stringify(message));
891
900
  };
892
901
  const start = (input = {}) => {
902
+ if (input.sessionId && input.sessionId !== state.sessionId) {
903
+ throw new Error("Voice session switching requires a new connection instance.");
904
+ }
893
905
  if (input.sessionId) {
894
906
  state.sessionId = input.sessionId;
895
907
  }
@@ -1,10 +1,10 @@
1
- (()=>{var{defineProperty:x,getOwnPropertyNames:w0,getOwnPropertyDescriptor:J0}=Object,S0=Object.prototype.hasOwnProperty;function Y0(V){return this[V]}var Z0=(V)=>{var g=(l??=new WeakMap).get(V),C;if(g)return g;if(g=x({},"__esModule",{value:!0}),V&&typeof V==="object"||typeof V==="function"){for(var A of w0(V))if(!S0.call(g,A))x(g,A,{get:Y0.bind(V,A),enumerable:!(C=J0(V,A))||C.enumerable})}return l.set(V,g),g},l;var y0=(V)=>V;function h0(V,g){this[V]=y0.bind(null,g)}var Q0=(V,g)=>{for(var C in g)x(V,C,{get:g[C],enumerable:!0,configurable:!0,set:h0.bind(g,C)})};var _V={};Q0(_V,{mount:()=>$0,default:()=>TV,VOICE_EMBED_VERSION:()=>R0});var K0=(V)=>{if(typeof V!=="string")return V;return document.querySelector(V)},c0=(V,g,C,A)=>{let R=g??V.getAttribute("hx-get")??"";if(!R)return"";let _=new URL(R,window.location.origin);if(A)_.searchParams.set(C,A);else _.searchParams.delete(C);return`${_.pathname}${_.search}${_.hash}`},d=(V,g)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=K0(g.element);if(!C)return()=>{};let A=g.eventName??"voice-refresh",R=g.sessionQueryParam??"sessionId",_=()=>{let X=window,H=c0(C,g.route,R,V.sessionId);if(H)C.setAttribute("hx-get",H);X.htmx?.process?.(C),X.htmx?.trigger?.(C,A)},D=V.subscribe(_);return _(),()=>{D()}};var E0=(V)=>Math.max(-1,Math.min(1,V)),q0=(V)=>{let g=new Int16Array(V.length);for(let C=0;C<V.length;C+=1){let A=E0(V[C]??0);g[C]=A<0?A*32768:A*32767}return new Uint8Array(g.buffer)},z0=(V)=>{let g=V instanceof Uint8Array?V:new Uint8Array(V);if(g.byteLength<2)return 0;let C=new Int16Array(g.buffer,g.byteOffset,Math.floor(g.byteLength/2));if(C.length===0)return 0;let A=0;for(let R of C){let _=R/32768;A+=_*_}return Math.min(1,Math.max(0,Math.sqrt(A/C.length)*5.5))},N0=(V,g,C)=>{if(g===C)return V;let A=g/C,R=Math.round(V.length/A),_=new Float32Array(R),D=0,X=0;while(D<_.length){let H=Math.round((D+1)*A),O=0,L=0;for(let W=X;W<H&&W<V.length;W+=1)O+=V[W]??0,L+=1;_[D]=L>0?O/L:0,D+=1,X=H}return _},v=(V)=>{let g=null,C=null,A=null,R=null,_=0,D=null,X=(L)=>{L.processorNode?.disconnect(),L.sourceNode?.disconnect(),L.mediaStream?.getTracks().forEach((W)=>W.stop()),L.audioContext?.close()};return{start:()=>{if(R)return Promise.resolve();if(D)return D;let L=++_,I=(async()=>{if(!V.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let T=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!T)throw Error("Browser microphone capture requires AudioContext support.");let J=null,h=null,Z=null,Q=null;try{if(h=V.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:V.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),L!==_)throw Error("Microphone capture stopped during startup.");if(J=new T,J.state==="suspended")await J.resume();if(L!==_)throw Error("Microphone capture stopped during startup.");if(Q=J.createMediaStreamSource(h),Z=J.createScriptProcessor(4096,1,1),Z.onaudioprocess=(G)=>{let c=G.inputBuffer.getChannelData(0),q=N0(c,J?.sampleRate??48000,V.sampleRateHz??16000),$=q0(q);V.onLevel?.(z0($)),V.onAudio($)},Q.connect(Z),Z.connect(J.destination),L!==_)throw Error("Microphone capture stopped during startup.");g=J,R=h,A=Z,C=Q}catch(G){throw X({audioContext:J,mediaStream:h,processorNode:Z,sourceNode:Q}),G}})().finally(()=>{if(D===I)D=null});return D=I,I},stop:()=>{_+=1,X({audioContext:g,mediaStream:R,processorNode:A,sourceNode:C}),V.onLevel?.(0),g=null,R=null,A=null,C=null}}};var k=(V)=>{if(typeof V==="string"&&V.trim())return V;if(V instanceof Error&&V.message.trim())return V.message;if(V&&typeof V==="object"){let g=V;for(let C of["message","reason","description"]){let A=g[C];if(typeof A==="string"&&A.trim())return A}if("error"in g)return k(g.error);if("cause"in g)return k(g.cause);try{return JSON.stringify(V)}catch{}}return"Unexpected error"},i=(V)=>{switch(V.type){case"audio":return{chunk:Uint8Array.from(atob(V.chunkBase64),(g)=>g.charCodeAt(0)),format:V.format,receivedAt:V.receivedAt,turnId:V.turnId,type:"audio"};case"assistant":return{text:V.text,turnId:V.turnId,type:"assistant"};case"assistant_delta":return{delta:V.delta,turnId:V.turnId,type:"assistant_delta"};case"complete":return{sessionId:V.sessionId,type:"complete"};case"connection":return{reconnect:V.reconnect,type:"connection"};case"call_lifecycle":return{event:V.event,sessionId:V.sessionId,type:"call_lifecycle"};case"error":return{message:k(V.message),recoverable:V.recoverable,type:"error"};case"call_control_ack":return null;case"final":return{transcript:V.transcript,type:"final"};case"partial":return{transcript:V.transcript,type:"partial"};case"replay":return{assistantTexts:V.assistantTexts,call:V.call,partial:V.partial,scenarioId:V.scenarioId,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,status:V.status,turns:V.turns,type:"replay"};case"session":return{sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,type:"session"};case"turn":return{turn:V.turn,type:"turn"};default:return null}};var UV=Math.PI*2;var z=(V,g,C,A)=>{V.push({code:C,message:A,severity:g})};var P0=(V)=>V.length===0?void 0:V.reduce((g,C)=>g+C,0)/V.length,j=(V)=>V.length===0?void 0:Math.max(...V);var K=(V,g)=>{let C=V[g];return typeof C==="number"&&Number.isFinite(C)?C:void 0},b=(V,g)=>{let C=V[g];return typeof C==="boolean"?C:void 0},E=(V,g)=>{let C=V[g];return typeof C==="string"?C:void 0},F=(V)=>String(V.id??E(V,"ssrc")??K(V,"ssrc")??E(V,"trackIdentifier")??E(V,"mid")??"unknown"),o=(V)=>V===void 0?void 0:V*1000;var B0=(V)=>{let g={};for(let[C,A]of Object.entries(V))if(A===null||typeof A==="boolean"||typeof A==="number"||typeof A==="string")g[C]=A;return g};var n=(V={})=>{let g=V.stats??[],C=[],A=g.filter(($)=>$.type==="inbound-rtp"&&E($,"kind")!=="video"),R=g.filter(($)=>$.type==="outbound-rtp"&&E($,"kind")!=="video"),_=g.filter(($)=>$.type==="candidate-pair"),D=g.filter(($)=>($.type==="track"||$.type==="media-source")&&E($,"kind")==="audio"),X=_.filter(($)=>b($,"selected")===!0||b($,"nominated")===!0||E($,"state")==="succeeded").length,H=D.filter(($)=>E($,"readyState")!=="ended"&&E($,"trackState")!=="ended"&&b($,"ended")!==!0).length,O=D.filter(($)=>E($,"readyState")==="ended"||E($,"trackState")==="ended"||b($,"ended")===!0).length,L=A.reduce(($,S)=>$+(K(S,"packetsReceived")??0),0),W=R.reduce(($,S)=>$+(K(S,"packetsSent")??0),0),I=[...A,...R].reduce(($,S)=>$+Math.max(0,K(S,"packetsLost")??0),0),T=L+I,J=T===0?0:I/T,h=A.reduce(($,S)=>$+(K(S,"bytesReceived")??0),0),Z=R.reduce(($,S)=>$+(K(S,"bytesSent")??0),0),Q=j(_.map(($)=>o(K($,"currentRoundTripTime")??K($,"roundTripTime"))).filter(($)=>$!==void 0)),G=j([...A,...R].map(($)=>o(K($,"jitter"))).filter(($)=>$!==void 0)),c=j(A.map(($)=>{let S=K($,"jitterBufferDelay"),B=K($,"jitterBufferEmittedCount");return S!==void 0&&B!==void 0&&B>0?S/B*1000:void 0}).filter(($)=>$!==void 0)),q=D.map(($)=>K($,"audioLevel")).filter(($)=>$!==void 0);if(V.requireConnectedCandidatePair&&_.length>0&&X===0)z(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(V.requireLiveAudioTrack&&H===0)z(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(V.maxPacketLossRatio!==void 0&&J>V.maxPacketLossRatio)z(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(J)} above ${String(V.maxPacketLossRatio)}.`);if(V.maxRoundTripTimeMs!==void 0&&Q!==void 0&&Q>V.maxRoundTripTimeMs)z(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(Q)}ms above ${String(V.maxRoundTripTimeMs)}ms.`);if(V.maxJitterMs!==void 0&&G!==void 0&&G>V.maxJitterMs)z(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(G)}ms above ${String(V.maxJitterMs)}ms.`);return{activeCandidatePairs:X,audioLevelAverage:P0(q),bytesReceived:h,bytesSent:Z,checkedAt:Date.now(),endedAudioTracks:O,inboundPackets:L,issues:C,jitterBufferDelayMs:c,jitterMs:G,liveAudioTracks:H,outboundPackets:W,packetLossRatio:J,packetsLost:I,roundTripTimeMs:Q,status:C.some(($)=>$.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:g.length}},m=async(V)=>{return[...(await V.peerConnection.getStats(V.selector??null)).values()].map(B0)};var u=(V={})=>{let g=V.stats??[],C=V.previousStats??[],A=[],R=new Map(C.map((I)=>[F(I),I])),D=g.filter((I)=>(I.type==="inbound-rtp"||I.type==="outbound-rtp")&&E(I,"kind")!=="video"&&E(I,"mediaType")!=="video").map((I)=>{let T=I.type==="outbound-rtp"?"outbound":"inbound",J=T==="outbound"?"packetsSent":"packetsReceived",h=T==="outbound"?"bytesSent":"bytesReceived",Z=R.get(F(I)),Q=K(I,J),G=Z?K(Z,J):void 0,c=K(I,h),q=Z?K(Z,h):void 0,$=I.timestamp!==void 0&&Z?.timestamp!==void 0?I.timestamp-Z.timestamp:void 0;return{bytesDelta:c!==void 0&&q!==void 0?c-q:void 0,currentPackets:Q,direction:T,id:F(I),packetDelta:Q!==void 0&&G!==void 0?Q-G:void 0,previousPackets:G,timeDeltaMs:$}}),X=D.filter((I)=>I.direction==="inbound"),H=D.filter((I)=>I.direction==="outbound"),O=j(D.map((I)=>I.timeDeltaMs).filter((I)=>I!==void 0)),L=X.filter((I)=>V.maxInboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=V.maxInboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length,W=H.filter((I)=>V.maxOutboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=V.maxOutboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length;if(V.requireInboundAudio&&X.length===0)z(A,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(V.requireOutboundAudio&&H.length===0)z(A,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(V.maxGapMs!==void 0&&O!==void 0&&O>V.maxGapMs)z(A,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(O)}ms above ${String(V.maxGapMs)}ms.`);if(L>0)z(A,"error","media.webrtc_inbound_stalled",`${String(L)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(W>0)z(A,"error","media.webrtc_outbound_stalled",`${String(W)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:X.length,issues:A,maxObservedGapMs:O,outboundAudioStreams:H.length,stalledInboundStreams:L,stalledOutboundStreams:W,status:A.some((I)=>I.severity==="error")?"fail":A.length>0?"warn":"pass",streams:D,totalStats:g.length}};var M0="/api/voice/browser-media",b0=5000,j0=async(V)=>V.peerConnection??await V.getPeerConnection?.()??null,x0=async(V,g)=>{let C=g.fetch??globalThis.fetch;if(!C)return;await C(g.path??M0,{body:JSON.stringify(V),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(V)=>{let g=null,C=[],A=async()=>{let D=await j0(V);if(!D)return;let X=await m({peerConnection:D}),H=n({...V,stats:X}),O=V.continuity===!1?void 0:u({...V.continuity,previousStats:C,stats:X}),L={at:Date.now(),continuity:O,report:H,scenarioId:V.getScenarioId?.()??null,sessionId:V.getSessionId?.()??null};return C=X,V.onReport?.(L),await x0(L,V),L},R=()=>{A().catch((D)=>{V.onError?.(D)})},_=()=>{if(g)clearInterval(g),g=null};return{close:_,reportOnce:A,stop:_,start:()=>{if(g)return;R(),g=setInterval(R,V.intervalMs??b0)}}};var k0=(V,g,C)=>Math.min(C,g*2**(Math.max(1,V)-1));class s extends Error{constructor(V="Voice authorization could not be renewed."){super(V);this.name="VoiceReconnectRejectedError"}}var P=()=>{},F0=()=>Promise.resolve(),f0=()=>P,l0={callControl:F0,close:P,disconnect:P,endTurn:P,send:P,sendAudio:P,simulateDisconnect:P,subscribe:f0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},d0=()=>crypto.randomUUID(),v0=(V,g,C,A)=>{let{hostname:R,port:_,protocol:D}=window.location,X=D==="https:"?"wss:":"ws:",H=_?`:${_}`:"",O=new URL(`${X}//${R}${H}${V}`);if(O.searchParams.set("sessionId",g),C)O.searchParams.set("scenarioId",C);for(let[L,W]of Object.entries(A))if(L!=="sessionId"&&L!=="scenarioId"&&W)O.searchParams.set(L,W);return O.toString()},i0=(V)=>{if(!V||typeof V!=="object"||!("type"in V))return!1;switch(V.type){case"audio":case"assistant":case"call_lifecycle":case"call_control_ack":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},o0=(V)=>{if(typeof V.data!=="string")return null;try{let g=JSON.parse(V.data);return i0(g)?g:null}catch{return null}},a=(V,g={})=>{if(typeof window>"u")return l0;let C=new Set,A=new Map,R=g.reconnect!==!1,_=g.maxReconnectAttempts??15,D=g.reconnectMaxDelayMs??8000,X=g.pingInterval??30000,H=g.reconnectResetAfterMs??30000,O=0,L=!1,W=null,I=(U)=>k0(U,500,D),T={isConnected:!1,pendingMessages:[],scenarioId:g.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:g.sessionId??d0(),ws:null},J=(U)=>{C.forEach((w)=>w(U))},h=(U)=>{C.forEach((w)=>w({message:U,recoverable:!1,type:"error"})),J({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"})},Z=()=>{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},Q=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let U=T.pendingMessages.shift();if(U!==void 0)T.ws.send(U)}},G=(U)=>{for(let w of A.values())clearTimeout(w.timeout),w.reject(Error(U));A.clear()},c=()=>{if(L)return;let U=O;T.reconnectAttempts+=1;let w=I(T.reconnectAttempts),y=Date.now()+w;J({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:y,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(async()=>{if(L||O!==U)return;if(T.reconnectAttempts>_){h("Voice connection could not be restored.");return}try{if(g.prepareReconnect){let Y=new AbortController;W=Y;let M=setTimeout(()=>Y.abort("Voice reconnect preparation timed out"),g.prepareReconnectTimeoutMs??1e4);try{await Promise.race([g.prepareReconnect({attempt:T.reconnectAttempts,path:V,scenarioId:T.scenarioId,sessionId:T.sessionId,signal:Y.signal}),new Promise((f,O0)=>{Y.signal.addEventListener("abort",()=>O0(Error("Voice reconnect preparation timed out.")),{once:!0})})])}finally{if(clearTimeout(M),W===Y)W=null}}}catch(Y){if(L||O!==U)return;if(Y instanceof s){h(Y.message);return}if(T.reconnectAttempts<_)c();else h("Voice authorization could not be renewed for reconnect.");return}if(L||O!==U)return;q()},w)},q=()=>{if(L)return;let U=new WebSocket(v0(V,T.sessionId,T.scenarioId,g.query??{}));U.binaryType="arraybuffer",U.onopen=()=>{if(T.ws!==U){U.close(1000);return}let w=T.reconnectAttempts>0;if(T.isConnected=!0,Q(),w)J({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),T.reconnectResetTimeout=setTimeout(()=>{if(T.ws===U&&U.readyState===1)T.reconnectAttempts=0;T.reconnectResetTimeout=null},H);C.forEach((y)=>y({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(U.readyState===1)U.send(JSON.stringify({type:"ping"}))},X)},U.onmessage=(w)=>{if(T.ws!==U)return;let y=o0(w);if(!y)return;if(y.type==="session")T.sessionId=y.sessionId,T.scenarioId=y.scenarioId??T.scenarioId;if(y.type==="call_control_ack"){let Y=A.get(y.requestId);if(Y)if(clearTimeout(Y.timeout),A.delete(y.requestId),y.ok)Y.resolve();else Y.reject(Error(y.message??`${y.action} was not applied`))}C.forEach((Y)=>Y(y))},U.onclose=(w)=>{if(T.ws!==U)return;T.ws=null,T.isConnected=!1,Z(),G("Voice connection interrupted");let y=R&&w.code!==1000&&w.code!==4401&&w.code!==4403&&T.reconnectAttempts<_,Y=w.code===4401||w.code===4403;if(Y)C.forEach((M)=>M({message:w.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(y)c();else if(R&&w.code!==1000)if(Y)J({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"});else h("Voice connection could not be restored.")},T.ws=U},$=(U)=>{if(L)return;if(T.ws?.readyState===1){T.ws.send(U);return}if(typeof U!=="string")return;if(T.pendingMessages.length>=100)T.pendingMessages.shift();T.pendingMessages.push(U)},S=(U)=>{$(JSON.stringify(U))},B=(U={})=>{if(U.sessionId)T.sessionId=U.sessionId;if(U.scenarioId)T.scenarioId=U.scenarioId;S({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},I0=(U)=>{$(U)},U0=()=>{S({type:"end_turn"})},L0=(U)=>{if(T.ws?.readyState!==1)return Promise.reject(Error(`${U.action} is unavailable while disconnected`));let w=crypto.randomUUID(),{promise:y,reject:Y,resolve:M}=Promise.withResolvers(),f=setTimeout(()=>{A.delete(w),Y(Error(`${U.action} acknowledgement timed out`))},5000);return A.set(w,{reject:Y,resolve:M,timeout:f}),T.ws.send(JSON.stringify({...U,requestId:w,type:"call_control"})),y},W0=(U="client-close")=>{if(L=!0,W?.abort(),W=null,O+=1,Z(),G("Voice connection closed"),T.pendingMessages.length=0,T.ws){let w=T.ws;if(T.ws=null,w.readyState===1)w.send(JSON.stringify({reason:U,type:"close"}));w.close(1000)}T.isConnected=!1,C.clear()},D0=()=>{if(L=!0,W?.abort(),W=null,O+=1,Z(),G("Voice connection disconnected"),T.pendingMessages.length=0,T.ws){let U=T.ws;T.ws=null,U.close(1000)}T.isConnected=!1,C.clear()},X0=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},G0=(U)=>{return C.add(U),()=>{C.delete(U)}};return q(),{callControl:L0,close:W0,disconnect:D0,endTurn:U0,send:S,sendAudio:I0,simulateDisconnect:X0,start:B,subscribe:G0,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var n0=()=>({attempts:0,maxAttempts:0,status:"idle"}),m0=(V,g)=>{let C=g.trim().replace(/\s+/g," ");if(!C)return V;if(!V)return C;if(V===C||V.endsWith(C))return V;if(C.includes(V))return C;return`${V} ${C}`},u0=(V,g)=>{let C=g.trim().replace(/\s+/g," ");if(!V)return C;if(!C||V.endsWith(C))return V;return`${V} ${C}`},r0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:n0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),p=()=>{let V=r0(),g="",C=new Set,A=()=>{C.forEach((_)=>_())};return{dispatch:(_)=>{switch(_.type){case"audio":V={...V,assistantAudio:[...V.assistantAudio,{chunk:_.chunk,format:_.format,receivedAt:_.receivedAt,turnId:_.turnId}]};break;case"assistant":V={...V,assistantStreamingText:"",assistantTexts:[...V.assistantTexts,_.text]};break;case"assistant_delta":V={...V,assistantStreamingText:`${V.assistantStreamingText}${_.delta}`};break;case"complete":V={...V,sessionId:_.sessionId,status:"completed"};break;case"call_lifecycle":V={...V,call:{...V.call,disposition:_.event.type==="end"?_.event.disposition:V.call?.disposition,endedAt:_.event.type==="end"?_.event.at:V.call?.endedAt,events:[...V.call?.events??[],_.event],lastEventAt:_.event.at,startedAt:V.call?.startedAt??_.event.at},sessionId:_.sessionId};break;case"connected":V={...V,isConnected:!0,reconnect:V.reconnect.status==="reconnecting"?{...V.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:V.reconnect};break;case"connection":V={...V,reconnect:_.reconnect};break;case"disconnected":V={...V,isConnected:!1};break;case"error":V={...V,error:_.message,..._.recoverable===!1?{isConnected:!1,status:"failed"}:{}};break;case"final":g=m0(g,_.transcript.text),V={...V,partial:g};break;case"partial":V={...V,partial:u0(g,_.transcript.text)};break;case"replay":g=_.partial,V={...V,assistantStreamingText:"",assistantTexts:[..._.assistantTexts],call:_.call??null,error:null,isConnected:_.status==="active",partial:_.partial,reconnect:V.reconnect.status==="reconnecting"?{...V.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:V.reconnect,scenarioId:_.scenarioId??V.scenarioId,sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status,turns:[..._.turns]};break;case"session":V={...V,error:null,scenarioId:_.scenarioId??V.scenarioId,isConnected:_.status==="active",sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status};break;case"turn":g="",V={...V,partial:"",turns:[...V.turns,_.turn]};break}A()},getServerSnapshot:()=>V,getSnapshot:()=>V,subscribe:(_)=>{return C.add(_),()=>{C.delete(_)}}}};var t=(V,g={})=>{let C=a(V,g),A=p(),R=g.browserMedia&&typeof window<"u"?r({...g.browserMedia,getScenarioId:()=>g.browserMedia?g.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>g.browserMedia?g.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,_=new Set,D=!1,X=(W)=>Promise.resolve().then(()=>{if(D)return;if(!W?.sessionId&&!W?.scenarioId)return;C.start(W),R?.start()}),H=()=>{_.forEach((W)=>W())},O=()=>{if(!g.reconnectReportPath||typeof fetch>"u")return;let W=A.getSnapshot(),I=JSON.stringify({at:Date.now(),reconnect:W.reconnect,scenarioId:W.scenarioId,sessionId:C.getSessionId(),turnIds:W.turns.map((T)=>T.id)});fetch(g.reconnectReportPath,{body:I,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},L=C.subscribe((W)=>{let I=i(W);if(I){if(A.dispatch(I),W.type==="connection")O();H()}});return{start:X,get assistantAudio(){return A.getSnapshot().assistantAudio},get assistantTexts(){return A.getSnapshot().assistantTexts},get assistantStreamingText(){return A.getSnapshot().assistantStreamingText},get call(){return A.getSnapshot().call},callControl(W){return C.callControl(W)},close(W){D=!0,L(),R?.close(),C.close(W),A.dispatch({type:"disconnected"}),H()},disconnect(){D=!0,L(),R?.close(),C.disconnect(),A.dispatch({type:"disconnected"}),H()},endTurn(){C.endTurn()},get error(){return A.getSnapshot().error},getServerSnapshot(){return A.getServerSnapshot()},getSnapshot(){return A.getSnapshot()},get isConnected(){return A.getSnapshot().isConnected},get partial(){return A.getSnapshot().partial},get reconnect(){return A.getSnapshot().reconnect},get scenarioId(){return A.getSnapshot().scenarioId},sendAudio(W){C.sendAudio(W)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return A.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return A.getSnapshot().status},subscribe(W){return _.add(W),()=>{_.delete(W)}},get turns(){return A.getSnapshot().turns}}};var e=(V)=>{if(!V||V.enabled===!1)return;return{enabled:!0,maxGain:V.maxGain??3,noiseGateAttenuation:V.noiseGateAttenuation??0.15,noiseGateThreshold:V.noiseGateThreshold??0.006,targetLevel:V.targetLevel??0.08}};var s0={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}},a0={"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 V0=(V)=>{let g=V?.profile??"fast",C=V?.qualityProfile??"general",A=s0[g],R=a0[C],_=V?.silenceMs??R.silenceMs??A.silenceMs;return{profile:g,qualityProfile:C,minSilenceMs:Math.min(_,V?.minSilenceMs??R.minSilenceMs??A.minSilenceMs),silenceMs:_,speechThreshold:V?.speechThreshold??R.speechThreshold??A.speechThreshold,transcriptStabilityMs:V?.transcriptStabilityMs??R.transcriptStabilityMs??A.transcriptStabilityMs}};var p0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},g0=(V="default")=>{let g=p0[V];return{audioConditioning:e(g.audioConditioning),capture:{channelCount:g.capture?.channelCount??1,sampleRateHz:g.capture?.sampleRateHz??16000},connection:{...g.connection},name:V,sttLifecycle:g.sttLifecycle??"continuous",turnDetection:V0(g.turnDetection)}};var t0=(V)=>({assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,isRecording:!1,partial:V.partial,reconnect:V.reconnect,recordingError:null,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]}),A0=(V,g={})=>{let C=g0(g.preset),A=t(V,{...C.connection,...g.connection}),R=null,_=0,D=!1,X=null,H=t0(A),O=new Set,L=()=>{for(let G of O)G()},W=()=>{if(H={...H,assistantAudio:[...A.assistantAudio],assistantStreamingText:A.assistantStreamingText,assistantTexts:[...A.assistantTexts],call:A.call,error:A.error,isConnected:A.isConnected,partial:A.partial,reconnect:A.reconnect,sessionId:A.sessionId,sessionMetadata:A.sessionMetadata,scenarioId:A.scenarioId,status:A.status,turns:[...A.turns]},g.autoStopOnComplete!==!1&&H.status==="completed"&&H.isRecording)R?.stop(),R=null,H={...H,isRecording:!1};L()},I=A.subscribe(W);W();let T=()=>{if(R)return R;return R=v({channelCount:g.capture?.channelCount??C.capture.channelCount,onLevel:g.capture?.onLevel,onAudio:(G)=>{if(g.capture?.onAudio){g.capture.onAudio(G,A.sendAudio);return}A.sendAudio(G)},sampleRateHz:g.capture?.sampleRateHz??C.capture.sampleRateHz,...g.capture?.stream?{stream:g.capture.stream}:{}}),R},J=()=>{_+=1,R?.stop(),R=null,H={...H,isRecording:!1},L()},h=async()=>{if(D)throw Error("Voice controller is closed.");if(H.isRecording)return;if(X)return X;let G=++_,c=T(),$=(async()=>{try{if(H={...H,recordingError:null},L(),await c.start(),D||G!==_||R!==c){c.stop();return}H={...H,isRecording:!0},L()}catch(S){if(c.stop(),G!==_||R!==c)throw S;throw R=null,H={...H,isRecording:!1,recordingError:S instanceof Error?S.message:String(S)},L(),S}})().finally(()=>{if(X===$)X=null});return X=$,X};return{close:(G)=>{D=!0,I(),J(),A.close(G)},disconnect:()=>{D=!0,I(),J(),A.disconnect()},startRecording:h,stopRecording:J,get assistantAudio(){return H.assistantAudio},get assistantTexts(){return H.assistantTexts},get assistantStreamingText(){return H.assistantStreamingText},bindHTMX(G){return d(A,G)},get call(){return H.call},callControl:(G)=>A.callControl(G),endTurn:()=>A.endTurn(),get error(){return H.error},getServerSnapshot:()=>H,getSnapshot:()=>H,get isConnected(){return H.isConnected},get isRecording(){return H.isRecording},get partial(){return H.partial},get reconnect(){return H.reconnect},get recordingError(){return H.recordingError},get scenarioId(){return H.scenarioId},sendAudio:(G)=>A.sendAudio(G),get sessionId(){return H.sessionId},get sessionMetadata(){return H.sessionMetadata},simulateDisconnect:()=>A.simulateDisconnect(),get status(){return H.status},subscribe:(G)=>{return O.add(G),()=>{O.delete(G)}},toggleRecording:async()=>{if(H.isRecording){J();return}await h()},get turns(){return H.turns}}};var N=(V)=>String(V).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");var C0=(V)=>{if(!V.isConnected)return"idle";if(V.isPlaying)return"speaking";if(V.isRecording&&V.hasActivePartial)return"listening";if(V.isRecording)return"listening";if(V.lastTranscriptAt&&!V.lastAssistantAt)return"thinking";if(V.lastTranscriptAt&&V.lastAssistantAt&&V.lastTranscriptAt>V.lastAssistantAt)return"thinking";return"idle"};var e0={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},VV={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},gV=(V,g)=>{switch(V){case"listening":return g.listening;case"speaking":return g.speaking;case"thinking":return g.thinking;case"idle":return g.idle}},_0=(V)=>{let g={...e0,...V.theme},C={...VV,...V.labels},A=V.state.assistantAudio.at(-1)?.receivedAt,R=V.state.turns.at(-1)?.committedAt,_=C0({hasActivePartial:V.state.partial.length>0,isConnected:V.state.isConnected,isPlaying:!1,isRecording:V.state.isRecording,lastAssistantAt:A,lastTranscriptAt:R}),D=!V.state.isConnected&&V.state.status!=="idle"&&!V.state.error,X=V.state.error?"Error":D?C.connecting:V.state.status==="completed"?C.callEnded:gV(_,C);return{agentState:_,classes:{container:`absolute-voice-widget absolute-voice-widget--${_}`,dot:`absolute-voice-widget__dot${V.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:V.state.isConnected,canMute:V.state.isRecording,canStart:!V.state.isRecording&&V.state.status!=="completed"},errorMessage:V.state.error??void 0,labels:C,partial:V.state.partial||void 0,statusLabel:X,theme:g,title:V.title??"Voice"}},AV=(V)=>typeof V==="number"?`${V}px`:V,T0=(V)=>{let g=V.theme,C=`background:${g.background};border-radius:${AV(g.radius)};color:${g.foreground};font-family:${g.fontFamily};min-width:240px;padding:20px 22px;`,A=`background:${V.errorMessage?g.errorAccent:V.agentState==="idle"?"rgba(148,163,184,0.6)":g.accent};border-radius:50%;height:10px;width:10px;`,R=[];if(V.controls.canStart)R.push(`<button type="button" data-action="start" style="background:${g.accent};border:none;border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(V.labels.startCall)}</button>`);if(V.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:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(V.labels.mute)}</button>`);if(V.controls.canEnd)R.push(`<button type="button" data-action="end" style="background:${g.errorAccent};border:none;border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(V.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${V.agentState}" class="${N(V.classes.container)}" style="${C}">
1
+ (()=>{var{defineProperty:k,getOwnPropertyNames:w0,getOwnPropertyDescriptor:J0}=Object,S0=Object.prototype.hasOwnProperty;function Y0(V){return this[V]}var Z0=(V)=>{var g=(l??=new WeakMap).get(V),C;if(g)return g;if(g=k({},"__esModule",{value:!0}),V&&typeof V==="object"||typeof V==="function"){for(var A of w0(V))if(!S0.call(g,A))k(g,A,{get:Y0.bind(V,A),enumerable:!(C=J0(V,A))||C.enumerable})}return l.set(V,g),g},l;var y0=(V)=>V;function h0(V,g){this[V]=y0.bind(null,g)}var Q0=(V,g)=>{for(var C in g)k(V,C,{get:g[C],enumerable:!0,configurable:!0,set:h0.bind(g,C)})};var _V={};Q0(_V,{mount:()=>$0,default:()=>TV,VOICE_EMBED_VERSION:()=>R0});var K0=(V)=>{if(typeof V!=="string")return V;return document.querySelector(V)},c0=(V,g,C,A)=>{let R=g??V.getAttribute("hx-get")??"";if(!R)return"";let _=new URL(R,window.location.origin);if(A)_.searchParams.set(C,A);else _.searchParams.delete(C);return`${_.pathname}${_.search}${_.hash}`},d=(V,g)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=K0(g.element);if(!C)return()=>{};let A=g.eventName??"voice-refresh",R=g.sessionQueryParam??"sessionId",_=()=>{let X=window,H=c0(C,g.route,R,V.sessionId);if(H)C.setAttribute("hx-get",H);X.htmx?.process?.(C),X.htmx?.trigger?.(C,A)},D=V.subscribe(_);return _(),()=>{D()}};var E0=(V)=>Math.max(-1,Math.min(1,V)),q0=(V)=>{let g=new Int16Array(V.length);for(let C=0;C<V.length;C+=1){let A=E0(V[C]??0);g[C]=A<0?A*32768:A*32767}return new Uint8Array(g.buffer)},N0=(V)=>{let g=V instanceof Uint8Array?V:new Uint8Array(V);if(g.byteLength<2)return 0;let C=new Int16Array(g.buffer,g.byteOffset,Math.floor(g.byteLength/2));if(C.length===0)return 0;let A=0;for(let R of C){let _=R/32768;A+=_*_}return Math.min(1,Math.max(0,Math.sqrt(A/C.length)*5.5))},z0=(V,g,C)=>{if(g===C)return V;let A=g/C,R=Math.round(V.length/A),_=new Float32Array(R),D=0,X=0;while(D<_.length){let H=Math.round((D+1)*A),O=0,L=0;for(let W=X;W<H&&W<V.length;W+=1)O+=V[W]??0,L+=1;_[D]=L>0?O/L:0,D+=1,X=H}return _},v=(V)=>{let g=null,C=null,A=null,R=null,_=0,D=null,X=(L)=>{L.processorNode?.disconnect(),L.sourceNode?.disconnect(),L.mediaStream?.getTracks().forEach((W)=>W.stop()),L.audioContext?.close()};return{start:()=>{if(R)return Promise.resolve();if(D)return D;let L=++_,U=(async()=>{if(!V.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let T=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!T)throw Error("Browser microphone capture requires AudioContext support.");let J=null,h=null,Z=null,Q=null;try{if(h=V.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:V.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),L!==_)throw Error("Microphone capture stopped during startup.");if(J=new T,J.state==="suspended")await J.resume();if(L!==_)throw Error("Microphone capture stopped during startup.");if(Q=J.createMediaStreamSource(h),Z=J.createScriptProcessor(4096,1,1),Z.onaudioprocess=(G)=>{let c=G.inputBuffer.getChannelData(0),q=z0(c,J?.sampleRate??48000,V.sampleRateHz??16000),$=q0(q);V.onLevel?.(N0($)),V.onAudio($)},Q.connect(Z),Z.connect(J.destination),L!==_)throw Error("Microphone capture stopped during startup.");g=J,R=h,A=Z,C=Q}catch(G){throw X({audioContext:J,mediaStream:h,processorNode:Z,sourceNode:Q}),G}})().finally(()=>{if(D===U)D=null});return D=U,U},stop:()=>{_+=1,X({audioContext:g,mediaStream:R,processorNode:A,sourceNode:C}),V.onLevel?.(0),g=null,R=null,A=null,C=null}}};var F=(V)=>{if(typeof V==="string"&&V.trim())return V;if(V instanceof Error&&V.message.trim())return V.message;if(V&&typeof V==="object"){let g=V;for(let C of["message","reason","description"]){let A=g[C];if(typeof A==="string"&&A.trim())return A}if("error"in g)return F(g.error);if("cause"in g)return F(g.cause);try{return JSON.stringify(V)}catch{}}return"Unexpected error"},i=(V)=>{switch(V.type){case"audio":return{chunk:Uint8Array.from(atob(V.chunkBase64),(g)=>g.charCodeAt(0)),format:V.format,receivedAt:V.receivedAt,turnId:V.turnId,type:"audio"};case"assistant":return{text:V.text,turnId:V.turnId,type:"assistant"};case"assistant_delta":return{delta:V.delta,turnId:V.turnId,type:"assistant_delta"};case"complete":return{sessionId:V.sessionId,type:"complete"};case"connection":return{reconnect:V.reconnect,type:"connection"};case"call_lifecycle":return{event:V.event,sessionId:V.sessionId,type:"call_lifecycle"};case"error":return{message:F(V.message),recoverable:V.recoverable,type:"error"};case"call_control_ack":return null;case"final":return{transcript:V.transcript,type:"final"};case"partial":return{transcript:V.transcript,type:"partial"};case"replay":return{assistantTexts:V.assistantTexts,call:V.call,partial:V.partial,scenarioId:V.scenarioId,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,status:V.status,turns:V.turns,type:"replay"};case"session":return{sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,type:"session"};case"turn":return{turn:V.turn,type:"turn"};default:return null}};var UV=Math.PI*2;var N=(V,g,C,A)=>{V.push({code:C,message:A,severity:g})};var P0=(V)=>V.length===0?void 0:V.reduce((g,C)=>g+C,0)/V.length,x=(V)=>V.length===0?void 0:Math.max(...V);var K=(V,g)=>{let C=V[g];return typeof C==="number"&&Number.isFinite(C)?C:void 0},j=(V,g)=>{let C=V[g];return typeof C==="boolean"?C:void 0},E=(V,g)=>{let C=V[g];return typeof C==="string"?C:void 0},f=(V)=>String(V.id??E(V,"ssrc")??K(V,"ssrc")??E(V,"trackIdentifier")??E(V,"mid")??"unknown"),o=(V)=>V===void 0?void 0:V*1000;var B0=(V)=>{let g={};for(let[C,A]of Object.entries(V))if(A===null||typeof A==="boolean"||typeof A==="number"||typeof A==="string")g[C]=A;return g};var n=(V={})=>{let g=V.stats??[],C=[],A=g.filter(($)=>$.type==="inbound-rtp"&&E($,"kind")!=="video"),R=g.filter(($)=>$.type==="outbound-rtp"&&E($,"kind")!=="video"),_=g.filter(($)=>$.type==="candidate-pair"),D=g.filter(($)=>($.type==="track"||$.type==="media-source")&&E($,"kind")==="audio"),X=_.filter(($)=>j($,"selected")===!0||j($,"nominated")===!0||E($,"state")==="succeeded").length,H=D.filter(($)=>E($,"readyState")!=="ended"&&E($,"trackState")!=="ended"&&j($,"ended")!==!0).length,O=D.filter(($)=>E($,"readyState")==="ended"||E($,"trackState")==="ended"||j($,"ended")===!0).length,L=A.reduce(($,S)=>$+(K(S,"packetsReceived")??0),0),W=R.reduce(($,S)=>$+(K(S,"packetsSent")??0),0),U=[...A,...R].reduce(($,S)=>$+Math.max(0,K(S,"packetsLost")??0),0),T=L+U,J=T===0?0:U/T,h=A.reduce(($,S)=>$+(K(S,"bytesReceived")??0),0),Z=R.reduce(($,S)=>$+(K(S,"bytesSent")??0),0),Q=x(_.map(($)=>o(K($,"currentRoundTripTime")??K($,"roundTripTime"))).filter(($)=>$!==void 0)),G=x([...A,...R].map(($)=>o(K($,"jitter"))).filter(($)=>$!==void 0)),c=x(A.map(($)=>{let S=K($,"jitterBufferDelay"),b=K($,"jitterBufferEmittedCount");return S!==void 0&&b!==void 0&&b>0?S/b*1000:void 0}).filter(($)=>$!==void 0)),q=D.map(($)=>K($,"audioLevel")).filter(($)=>$!==void 0);if(V.requireConnectedCandidatePair&&_.length>0&&X===0)N(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(V.requireLiveAudioTrack&&H===0)N(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(V.maxPacketLossRatio!==void 0&&J>V.maxPacketLossRatio)N(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(J)} above ${String(V.maxPacketLossRatio)}.`);if(V.maxRoundTripTimeMs!==void 0&&Q!==void 0&&Q>V.maxRoundTripTimeMs)N(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(Q)}ms above ${String(V.maxRoundTripTimeMs)}ms.`);if(V.maxJitterMs!==void 0&&G!==void 0&&G>V.maxJitterMs)N(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(G)}ms above ${String(V.maxJitterMs)}ms.`);return{activeCandidatePairs:X,audioLevelAverage:P0(q),bytesReceived:h,bytesSent:Z,checkedAt:Date.now(),endedAudioTracks:O,inboundPackets:L,issues:C,jitterBufferDelayMs:c,jitterMs:G,liveAudioTracks:H,outboundPackets:W,packetLossRatio:J,packetsLost:U,roundTripTimeMs:Q,status:C.some(($)=>$.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:g.length}},m=async(V)=>{return[...(await V.peerConnection.getStats(V.selector??null)).values()].map(B0)};var u=(V={})=>{let g=V.stats??[],C=V.previousStats??[],A=[],R=new Map(C.map((U)=>[f(U),U])),D=g.filter((U)=>(U.type==="inbound-rtp"||U.type==="outbound-rtp")&&E(U,"kind")!=="video"&&E(U,"mediaType")!=="video").map((U)=>{let T=U.type==="outbound-rtp"?"outbound":"inbound",J=T==="outbound"?"packetsSent":"packetsReceived",h=T==="outbound"?"bytesSent":"bytesReceived",Z=R.get(f(U)),Q=K(U,J),G=Z?K(Z,J):void 0,c=K(U,h),q=Z?K(Z,h):void 0,$=U.timestamp!==void 0&&Z?.timestamp!==void 0?U.timestamp-Z.timestamp:void 0;return{bytesDelta:c!==void 0&&q!==void 0?c-q:void 0,currentPackets:Q,direction:T,id:f(U),packetDelta:Q!==void 0&&G!==void 0?Q-G:void 0,previousPackets:G,timeDeltaMs:$}}),X=D.filter((U)=>U.direction==="inbound"),H=D.filter((U)=>U.direction==="outbound"),O=x(D.map((U)=>U.timeDeltaMs).filter((U)=>U!==void 0)),L=X.filter((U)=>V.maxInboundPacketStallMs!==void 0&&U.timeDeltaMs!==void 0&&U.timeDeltaMs>=V.maxInboundPacketStallMs&&U.packetDelta!==void 0&&U.packetDelta<=0).length,W=H.filter((U)=>V.maxOutboundPacketStallMs!==void 0&&U.timeDeltaMs!==void 0&&U.timeDeltaMs>=V.maxOutboundPacketStallMs&&U.packetDelta!==void 0&&U.packetDelta<=0).length;if(V.requireInboundAudio&&X.length===0)N(A,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(V.requireOutboundAudio&&H.length===0)N(A,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(V.maxGapMs!==void 0&&O!==void 0&&O>V.maxGapMs)N(A,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(O)}ms above ${String(V.maxGapMs)}ms.`);if(L>0)N(A,"error","media.webrtc_inbound_stalled",`${String(L)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(W>0)N(A,"error","media.webrtc_outbound_stalled",`${String(W)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:X.length,issues:A,maxObservedGapMs:O,outboundAudioStreams:H.length,stalledInboundStreams:L,stalledOutboundStreams:W,status:A.some((U)=>U.severity==="error")?"fail":A.length>0?"warn":"pass",streams:D,totalStats:g.length}};var M0="/api/voice/browser-media",b0=5000,j0=async(V)=>V.peerConnection??await V.getPeerConnection?.()??null,x0=async(V,g)=>{let C=g.fetch??globalThis.fetch;if(!C)return;await C(g.path??M0,{body:JSON.stringify(V),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(V)=>{let g=null,C=[],A=async()=>{let D=await j0(V);if(!D)return;let X=await m({peerConnection:D}),H=n({...V,stats:X}),O=V.continuity===!1?void 0:u({...V.continuity,previousStats:C,stats:X}),L={at:Date.now(),continuity:O,report:H,scenarioId:V.getScenarioId?.()??null,sessionId:V.getSessionId?.()??null};return C=X,V.onReport?.(L),await x0(L,V),L},R=()=>{A().catch((D)=>{V.onError?.(D)})},_=()=>{if(g)clearInterval(g),g=null};return{close:_,reportOnce:A,stop:_,start:()=>{if(g)return;R(),g=setInterval(R,V.intervalMs??b0)}}};var k0=(V,g,C)=>Math.min(C,g*2**(Math.max(1,V)-1));class s extends Error{constructor(V="Voice authorization could not be renewed."){super(V);this.name="VoiceReconnectRejectedError"}}var P=()=>{},F0=()=>Promise.resolve(),f0=()=>P,l0={callControl:F0,close:P,disconnect:P,endTurn:P,send:P,sendAudio:P,simulateDisconnect:P,subscribe:f0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},d0=()=>crypto.randomUUID(),v0=(V,g,C,A)=>{let{hostname:R,port:_,protocol:D}=window.location,X=D==="https:"?"wss:":"ws:",H=_?`:${_}`:"",O=new URL(`${X}//${R}${H}${V}`);if(O.searchParams.set("sessionId",g),C)O.searchParams.set("scenarioId",C);for(let[L,W]of Object.entries(A))if(L!=="sessionId"&&L!=="scenarioId"&&W)O.searchParams.set(L,W);return O.toString()},i0=(V)=>{if(!V||typeof V!=="object"||!("type"in V))return!1;switch(V.type){case"audio":case"assistant":case"call_lifecycle":case"call_control_ack":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},o0=(V)=>{if(typeof V.data!=="string")return null;try{let g=JSON.parse(V.data);return i0(g)?g:null}catch{return null}},a=(V,g={})=>{if(typeof window>"u")return l0;let C=new Set,A=new Map,R=g.reconnect!==!1,_=g.maxReconnectAttempts??15,D=g.reconnectMaxDelayMs??8000,X=g.pingInterval??30000,H=g.reconnectResetAfterMs??30000,O=0,L=!1,W=null,U=(I)=>k0(I,500,D),T={isConnected:!1,pendingMessages:[],scenarioId:g.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:g.sessionId??d0(),ws:null},J=(I)=>{C.forEach((w)=>w(I))},h=(I)=>{C.forEach((w)=>w({message:I,recoverable:!1,type:"error"})),J({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"})},Z=()=>{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},Q=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let I=T.pendingMessages.shift();if(I!==void 0)T.ws.send(I)}},G=(I)=>{for(let w of A.values())clearTimeout(w.timeout),w.reject(Error(I));A.clear()},c=()=>{if(L)return;let I=O;T.reconnectAttempts+=1;let w=U(T.reconnectAttempts),y=Date.now()+w;J({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:y,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(async()=>{if(L||O!==I)return;if(T.reconnectAttempts>_){h("Voice connection could not be restored.");return}try{if(g.prepareReconnect){let Y=new AbortController;W=Y;let B=setTimeout(()=>Y.abort("Voice reconnect preparation timed out"),g.prepareReconnectTimeoutMs??1e4);try{await Promise.race([g.prepareReconnect({attempt:T.reconnectAttempts,path:V,scenarioId:T.scenarioId,sessionId:T.sessionId,signal:Y.signal}),new Promise((M,O0)=>{Y.signal.addEventListener("abort",()=>O0(Error("Voice reconnect preparation timed out.")),{once:!0})})])}finally{if(clearTimeout(B),W===Y)W=null}}}catch(Y){if(L||O!==I)return;if(Y instanceof s){h(Y.message);return}if(T.reconnectAttempts<_)c();else h("Voice authorization could not be renewed for reconnect.");return}if(L||O!==I)return;q()},w)},q=()=>{if(L)return;let I=new WebSocket(v0(V,T.sessionId,T.scenarioId,g.query??{}));I.binaryType="arraybuffer",I.onopen=()=>{if(T.ws!==I){I.close(1000);return}let w=T.reconnectAttempts>0;if(T.isConnected=!0,Q(),w)J({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),T.reconnectResetTimeout=setTimeout(()=>{if(T.ws===I&&I.readyState===1)T.reconnectAttempts=0;T.reconnectResetTimeout=null},H);C.forEach((y)=>y({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(I.readyState===1)I.send(JSON.stringify({type:"ping"}))},X)},I.onmessage=(w)=>{if(T.ws!==I)return;let y=o0(w);if(!y)return;if(y.type==="session")T.sessionId=y.sessionId,T.scenarioId=y.scenarioId??T.scenarioId;if(y.type==="call_control_ack"){let Y=A.get(y.requestId);if(Y)if(clearTimeout(Y.timeout),A.delete(y.requestId),y.ok)Y.resolve();else Y.reject(Error(y.message??`${y.action} was not applied`))}C.forEach((Y)=>Y(y))},I.onclose=(w)=>{if(T.ws!==I)return;T.ws=null,T.isConnected=!1,Z(),G("Voice connection interrupted");let y=R&&w.code!==1000&&w.code!==4401&&w.code!==4403&&w.code!==4500&&T.reconnectAttempts<_,Y=w.code===4401||w.code===4403,B=w.code===4500;if(Y)C.forEach((M)=>M({message:w.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(B)C.forEach((M)=>M({message:"Voice provider initialization failed.",recoverable:!1,type:"error"}));if(y)c();else if(R&&w.code!==1000){if(Y)J({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"});else if(!B)h("Voice connection could not be restored.")}},T.ws=I},$=(I)=>{if(L)return;if(T.ws?.readyState===1){T.ws.send(I);return}if(typeof I!=="string")return;if(T.pendingMessages.length>=100)T.pendingMessages.shift();T.pendingMessages.push(I)},S=(I)=>{$(JSON.stringify(I))},b=(I={})=>{if(I.sessionId&&I.sessionId!==T.sessionId)throw Error("Voice session switching requires a new connection instance.");if(I.sessionId)T.sessionId=I.sessionId;if(I.scenarioId)T.scenarioId=I.scenarioId;S({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},I0=(I)=>{$(I)},U0=()=>{S({type:"end_turn"})},L0=(I)=>{if(T.ws?.readyState!==1)return Promise.reject(Error(`${I.action} is unavailable while disconnected`));let w=crypto.randomUUID(),{promise:y,reject:Y,resolve:B}=Promise.withResolvers(),M=setTimeout(()=>{A.delete(w),Y(Error(`${I.action} acknowledgement timed out`))},5000);return A.set(w,{reject:Y,resolve:B,timeout:M}),T.ws.send(JSON.stringify({...I,requestId:w,type:"call_control"})),y},W0=(I="client-close")=>{if(L=!0,W?.abort(),W=null,O+=1,Z(),G("Voice connection closed"),T.pendingMessages.length=0,T.ws){let w=T.ws;if(T.ws=null,w.readyState===1)w.send(JSON.stringify({reason:I,type:"close"}));w.close(1000)}T.isConnected=!1,C.clear()},D0=()=>{if(L=!0,W?.abort(),W=null,O+=1,Z(),G("Voice connection disconnected"),T.pendingMessages.length=0,T.ws){let I=T.ws;T.ws=null,I.close(1000)}T.isConnected=!1,C.clear()},X0=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},G0=(I)=>{return C.add(I),()=>{C.delete(I)}};return q(),{callControl:L0,close:W0,disconnect:D0,endTurn:U0,send:S,sendAudio:I0,simulateDisconnect:X0,start:b,subscribe:G0,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var n0=()=>({attempts:0,maxAttempts:0,status:"idle"}),m0=(V,g)=>{let C=g.trim().replace(/\s+/g," ");if(!C)return V;if(!V)return C;if(V===C||V.endsWith(C))return V;if(C.includes(V))return C;return`${V} ${C}`},u0=(V,g)=>{let C=g.trim().replace(/\s+/g," ");if(!V)return C;if(!C||V.endsWith(C))return V;return`${V} ${C}`},r0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:n0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),p=()=>{let V=r0(),g="",C=new Set,A=()=>{C.forEach((_)=>_())};return{dispatch:(_)=>{switch(_.type){case"audio":V={...V,assistantAudio:[...V.assistantAudio,{chunk:_.chunk,format:_.format,receivedAt:_.receivedAt,turnId:_.turnId}]};break;case"assistant":V={...V,assistantStreamingText:"",assistantTexts:[...V.assistantTexts,_.text]};break;case"assistant_delta":V={...V,assistantStreamingText:`${V.assistantStreamingText}${_.delta}`};break;case"complete":V={...V,sessionId:_.sessionId,status:"completed"};break;case"call_lifecycle":V={...V,call:{...V.call,disposition:_.event.type==="end"?_.event.disposition:V.call?.disposition,endedAt:_.event.type==="end"?_.event.at:V.call?.endedAt,events:[...V.call?.events??[],_.event],lastEventAt:_.event.at,startedAt:V.call?.startedAt??_.event.at},sessionId:_.sessionId};break;case"connected":V={...V,isConnected:!0,reconnect:V.reconnect.status==="reconnecting"?{...V.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:V.reconnect};break;case"connection":V={...V,reconnect:_.reconnect};break;case"disconnected":V={...V,isConnected:!1};break;case"error":V={...V,error:_.message,..._.recoverable===!1?{isConnected:!1,status:"failed"}:{}};break;case"final":g=m0(g,_.transcript.text),V={...V,partial:g};break;case"partial":V={...V,partial:u0(g,_.transcript.text)};break;case"replay":g=_.partial,V={...V,assistantStreamingText:"",assistantTexts:[..._.assistantTexts],call:_.call??null,error:null,isConnected:_.status==="active",partial:_.partial,reconnect:V.reconnect.status==="reconnecting"?{...V.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:V.reconnect,scenarioId:_.scenarioId??V.scenarioId,sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status,turns:[..._.turns]};break;case"session":V={...V,error:null,scenarioId:_.scenarioId??V.scenarioId,isConnected:_.status==="active",sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status};break;case"turn":g="",V={...V,partial:"",turns:[...V.turns,_.turn]};break}A()},getServerSnapshot:()=>V,getSnapshot:()=>V,subscribe:(_)=>{return C.add(_),()=>{C.delete(_)}}}};var t=(V,g={})=>{let C=a(V,g),A=p(),R=g.browserMedia&&typeof window<"u"?r({...g.browserMedia,getScenarioId:()=>g.browserMedia?g.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>g.browserMedia?g.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,_=new Set,D=!1,X=(W)=>Promise.resolve().then(()=>{if(D)return;if(!W?.sessionId&&!W?.scenarioId)return;C.start(W),R?.start()}),H=()=>{_.forEach((W)=>W())},O=()=>{if(!g.reconnectReportPath||typeof fetch>"u")return;let W=A.getSnapshot(),U=JSON.stringify({at:Date.now(),reconnect:W.reconnect,scenarioId:W.scenarioId,sessionId:C.getSessionId(),turnIds:W.turns.map((T)=>T.id)});fetch(g.reconnectReportPath,{body:U,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},L=C.subscribe((W)=>{let U=i(W);if(U){if(A.dispatch(U),W.type==="connection")O();H()}});return{start:X,get assistantAudio(){return A.getSnapshot().assistantAudio},get assistantTexts(){return A.getSnapshot().assistantTexts},get assistantStreamingText(){return A.getSnapshot().assistantStreamingText},get call(){return A.getSnapshot().call},callControl(W){return C.callControl(W)},close(W){D=!0,L(),R?.close(),C.close(W),A.dispatch({type:"disconnected"}),H()},disconnect(){D=!0,L(),R?.close(),C.disconnect(),A.dispatch({type:"disconnected"}),H()},endTurn(){C.endTurn()},get error(){return A.getSnapshot().error},getServerSnapshot(){return A.getServerSnapshot()},getSnapshot(){return A.getSnapshot()},get isConnected(){return A.getSnapshot().isConnected},get partial(){return A.getSnapshot().partial},get reconnect(){return A.getSnapshot().reconnect},get scenarioId(){return A.getSnapshot().scenarioId},sendAudio(W){C.sendAudio(W)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return A.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return A.getSnapshot().status},subscribe(W){return _.add(W),()=>{_.delete(W)}},get turns(){return A.getSnapshot().turns}}};var e=(V)=>{if(!V||V.enabled===!1)return;return{enabled:!0,maxGain:V.maxGain??3,noiseGateAttenuation:V.noiseGateAttenuation??0.15,noiseGateThreshold:V.noiseGateThreshold??0.006,targetLevel:V.targetLevel??0.08}};var s0={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}},a0={"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 V0=(V)=>{let g=V?.profile??"fast",C=V?.qualityProfile??"general",A=s0[g],R=a0[C],_=V?.silenceMs??R.silenceMs??A.silenceMs;return{profile:g,qualityProfile:C,minSilenceMs:Math.min(_,V?.minSilenceMs??R.minSilenceMs??A.minSilenceMs),silenceMs:_,speechThreshold:V?.speechThreshold??R.speechThreshold??A.speechThreshold,transcriptStabilityMs:V?.transcriptStabilityMs??R.transcriptStabilityMs??A.transcriptStabilityMs}};var p0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},g0=(V="default")=>{let g=p0[V];return{audioConditioning:e(g.audioConditioning),capture:{channelCount:g.capture?.channelCount??1,sampleRateHz:g.capture?.sampleRateHz??16000},connection:{...g.connection},name:V,sttLifecycle:g.sttLifecycle??"continuous",turnDetection:V0(g.turnDetection)}};var t0=(V)=>({assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,isRecording:!1,partial:V.partial,reconnect:V.reconnect,recordingError:null,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]}),A0=(V,g={})=>{let C=g0(g.preset),A=t(V,{...C.connection,...g.connection}),R=null,_=0,D=!1,X=null,H=t0(A),O=new Set,L=()=>{for(let G of O)G()},W=()=>{if(H={...H,assistantAudio:[...A.assistantAudio],assistantStreamingText:A.assistantStreamingText,assistantTexts:[...A.assistantTexts],call:A.call,error:A.error,isConnected:A.isConnected,partial:A.partial,reconnect:A.reconnect,sessionId:A.sessionId,sessionMetadata:A.sessionMetadata,scenarioId:A.scenarioId,status:A.status,turns:[...A.turns]},g.autoStopOnComplete!==!1&&H.status==="completed"&&H.isRecording)R?.stop(),R=null,H={...H,isRecording:!1};L()},U=A.subscribe(W);W();let T=()=>{if(R)return R;return R=v({channelCount:g.capture?.channelCount??C.capture.channelCount,onLevel:g.capture?.onLevel,onAudio:(G)=>{if(g.capture?.onAudio){g.capture.onAudio(G,A.sendAudio);return}A.sendAudio(G)},sampleRateHz:g.capture?.sampleRateHz??C.capture.sampleRateHz,...g.capture?.stream?{stream:g.capture.stream}:{}}),R},J=()=>{_+=1,R?.stop(),R=null,H={...H,isRecording:!1},L()},h=async()=>{if(D)throw Error("Voice controller is closed.");if(H.isRecording)return;if(X)return X;let G=++_,c=T(),$=(async()=>{try{if(H={...H,recordingError:null},L(),await c.start(),D||G!==_||R!==c){c.stop();return}H={...H,isRecording:!0},L()}catch(S){if(c.stop(),G!==_||R!==c)throw S;throw R=null,H={...H,isRecording:!1,recordingError:S instanceof Error?S.message:String(S)},L(),S}})().finally(()=>{if(X===$)X=null});return X=$,X};return{close:(G)=>{D=!0,U(),J(),A.close(G)},disconnect:()=>{D=!0,U(),J(),A.disconnect()},startRecording:h,stopRecording:J,get assistantAudio(){return H.assistantAudio},get assistantTexts(){return H.assistantTexts},get assistantStreamingText(){return H.assistantStreamingText},bindHTMX(G){return d(A,G)},get call(){return H.call},callControl:(G)=>A.callControl(G),endTurn:()=>A.endTurn(),get error(){return H.error},getServerSnapshot:()=>H,getSnapshot:()=>H,get isConnected(){return H.isConnected},get isRecording(){return H.isRecording},get partial(){return H.partial},get reconnect(){return H.reconnect},get recordingError(){return H.recordingError},get scenarioId(){return H.scenarioId},sendAudio:(G)=>A.sendAudio(G),get sessionId(){return H.sessionId},get sessionMetadata(){return H.sessionMetadata},simulateDisconnect:()=>A.simulateDisconnect(),get status(){return H.status},subscribe:(G)=>{return O.add(G),()=>{O.delete(G)}},toggleRecording:async()=>{if(H.isRecording){J();return}await h()},get turns(){return H.turns}}};var z=(V)=>String(V).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");var C0=(V)=>{if(!V.isConnected)return"idle";if(V.isPlaying)return"speaking";if(V.isRecording&&V.hasActivePartial)return"listening";if(V.isRecording)return"listening";if(V.lastTranscriptAt&&!V.lastAssistantAt)return"thinking";if(V.lastTranscriptAt&&V.lastAssistantAt&&V.lastTranscriptAt>V.lastAssistantAt)return"thinking";return"idle"};var e0={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},VV={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},gV=(V,g)=>{switch(V){case"listening":return g.listening;case"speaking":return g.speaking;case"thinking":return g.thinking;case"idle":return g.idle}},_0=(V)=>{let g={...e0,...V.theme},C={...VV,...V.labels},A=V.state.assistantAudio.at(-1)?.receivedAt,R=V.state.turns.at(-1)?.committedAt,_=C0({hasActivePartial:V.state.partial.length>0,isConnected:V.state.isConnected,isPlaying:!1,isRecording:V.state.isRecording,lastAssistantAt:A,lastTranscriptAt:R}),D=!V.state.isConnected&&V.state.status!=="idle"&&!V.state.error,X=V.state.error?"Error":D?C.connecting:V.state.status==="completed"?C.callEnded:gV(_,C);return{agentState:_,classes:{container:`absolute-voice-widget absolute-voice-widget--${_}`,dot:`absolute-voice-widget__dot${V.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:V.state.isConnected,canMute:V.state.isRecording,canStart:!V.state.isRecording&&V.state.status!=="completed"},errorMessage:V.state.error??void 0,labels:C,partial:V.state.partial||void 0,statusLabel:X,theme:g,title:V.title??"Voice"}},AV=(V)=>typeof V==="number"?`${V}px`:V,T0=(V)=>{let g=V.theme,C=`background:${g.background};border-radius:${AV(g.radius)};color:${g.foreground};font-family:${g.fontFamily};min-width:240px;padding:20px 22px;`,A=`background:${V.errorMessage?g.errorAccent:V.agentState==="idle"?"rgba(148,163,184,0.6)":g.accent};border-radius:50%;height:10px;width:10px;`,R=[];if(V.controls.canStart)R.push(`<button type="button" data-action="start" style="background:${g.accent};border:none;border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${z(V.labels.startCall)}</button>`);if(V.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:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${z(V.labels.mute)}</button>`);if(V.controls.canEnd)R.push(`<button type="button" data-action="end" style="background:${g.errorAccent};border:none;border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${z(V.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${V.agentState}" class="${z(V.classes.container)}" style="${C}">
2
2
  <div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
3
- <span aria-hidden="true" class="${N(V.classes.dot)}" style="${A}"></span>
4
- <strong style="font-size:15px;">${N(V.title)}</strong>
5
- <span style="font-size:13px;margin-left:auto;opacity:0.7;">${N(V.statusLabel)}</span>
3
+ <span aria-hidden="true" class="${z(V.classes.dot)}" style="${A}"></span>
4
+ <strong style="font-size:15px;">${z(V.title)}</strong>
5
+ <span style="font-size:13px;margin-left:auto;opacity:0.7;">${z(V.statusLabel)}</span>
6
6
  </div>
7
- ${V.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${N(V.partial)}”</p>`:""}
7
+ ${V.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${z(V.partial)}”</p>`:""}
8
8
  <div style="display:flex;gap:10px;">${R.join("")}</div>
9
- ${V.errorMessage?`<p style="color:${g.errorAccent};font-size:12px;margin-top:12px;">${N(V.errorMessage)}</p>`:""}
9
+ ${V.errorMessage?`<p style="color:${g.errorAccent};font-size:12px;margin-top:12px;">${z(V.errorMessage)}</p>`:""}
10
10
  </div>`};var CV=(V)=>{if(typeof V!=="string")return V;let g=document.querySelector(V);if(!g)throw Error(`AbsoluteVoice.mount: no element matches "${V}"`);return g},$0=(V,g={})=>{let C=CV(V),A=A0(g.path??"/voice",g.controllerOptions),R=null,_=null,D=()=>{let H=_0({...g.labels!==void 0?{labels:g.labels}:{},state:{assistantAudio:A.assistantAudio,error:A.error,isConnected:A.isConnected,isRecording:A.isRecording,partial:A.partial,status:A.status,turns:A.turns},...g.theme!==void 0?{theme:g.theme}:{},...g.title!==void 0?{title:g.title}:{}});C.innerHTML=T0(H);for(let O of C.querySelectorAll("button[data-action]")){let{action:L}=O.dataset;O.addEventListener("click",()=>{if(L==="start")A.startRecording();else if(L==="mute")A.stopRecording();else if(L==="end")A.close()})}if(A.error&&A.error!==R)R=A.error,g.onError?.(A.error);if(A.status!==_)_=A.status,g.onStatusChange?.(A.status)},X=A.subscribe(D);if(D(),g.autoStart)A.startRecording();return{controller:A,async end(){await A.close()},mute(){A.stopRecording()},async start(){await A.startRecording()},unmount(){X(),A.close(),C.innerHTML=""}}},R0="0.0.22-beta.516",H0={mount:$0,version:R0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=H0;var TV=H0;})();
package/dist/index.js CHANGED
@@ -4518,6 +4518,13 @@ var createVoiceSession = (options) => {
4518
4518
  pauseTimeout = null;
4519
4519
  }
4520
4520
  };
4521
+ const schedulePauseTimeout = (remainingMs) => {
4522
+ clearPauseTimeout();
4523
+ pauseTimeout = setTimeout(() => {
4524
+ pauseTimeout = null;
4525
+ api.close("pause-timeout");
4526
+ }, remainingMs);
4527
+ };
4521
4528
  const pauseInternal = async () => {
4522
4529
  if (callerPaused) {
4523
4530
  return;
@@ -4530,14 +4537,17 @@ var createVoiceSession = (options) => {
4530
4537
  clearCallSilenceWatchdog();
4531
4538
  clearStuckCloseWatchdog();
4532
4539
  clearIdleRepromptWatchdog();
4533
- clearPauseTimeout();
4534
- pauseTimeout = setTimeout(() => {
4535
- pauseTimeout = null;
4536
- api.close("pause-timeout");
4537
- }, pauseMaxMs);
4540
+ clearAmdEvaluationTimer();
4541
+ const pausedAt = Date.now();
4542
+ const expiresAt = pausedAt + pauseMaxMs;
4543
+ snapshot.pause = { expiresAt, pausedAt };
4544
+ snapshot.lastActivityAt = pausedAt;
4545
+ await options.store.set(options.id, snapshot);
4546
+ schedulePauseTimeout(pauseMaxMs);
4538
4547
  await appendTrace({
4539
4548
  payload: {
4540
4549
  action: "call.paused",
4550
+ expiresAt,
4541
4551
  maxMs: pauseMaxMs
4542
4552
  },
4543
4553
  session: snapshot,
@@ -4553,7 +4563,11 @@ var createVoiceSession = (options) => {
4553
4563
  kickCallSilenceWatchdog();
4554
4564
  kickStuckCloseWatchdog();
4555
4565
  kickIdleRepromptWatchdog(true);
4566
+ startAmdEvaluationTimer();
4556
4567
  const snapshot = await readSession();
4568
+ snapshot.pause = undefined;
4569
+ snapshot.lastActivityAt = Date.now();
4570
+ await options.store.set(options.id, snapshot);
4557
4571
  await appendTrace({
4558
4572
  payload: {
4559
4573
  action: "call.resumed"
@@ -5911,38 +5925,43 @@ var createVoiceSession = (options) => {
5911
5925
  await handler();
5912
5926
  });
5913
5927
  };
5914
- openedSession.on("partial", ({ transcript }) => {
5915
- const next = options.redact ? options.redact(transcript) : transcript;
5916
- runAdapterEvent("adapter.partial", () => handlePartial(next));
5917
- });
5918
- openedSession.on("final", ({ transcript }) => {
5919
- const next = options.redact ? options.redact(transcript) : transcript;
5920
- runAdapterEvent("adapter.final", () => handleFinal(next));
5921
- });
5922
- openedSession.on("endOfTurn", ({ reason }) => {
5923
- runAdapterEvent("adapter.endOfTurn", async () => {
5924
- clearSilenceTimer();
5925
- await requestTurnCommit(reason);
5928
+ try {
5929
+ openedSession.on("partial", ({ transcript }) => {
5930
+ const next = options.redact ? options.redact(transcript) : transcript;
5931
+ runAdapterEvent("adapter.partial", () => handlePartial(next));
5926
5932
  });
5927
- });
5928
- openedSession.on("error", (event) => {
5929
- runAdapterEvent("adapter.error", () => handleError(event));
5930
- });
5931
- openedSession.on("close", (event) => {
5932
- runAdapterEvent("adapter.close", () => handleClose(event));
5933
- });
5934
- if (options.realtime) {
5935
- openedSession.on("audio", ({ chunk, format, receivedAt }) => {
5936
- runAudioSerial(async () => {
5937
- if (activeAdapterGeneration !== generation) {
5938
- return;
5939
- }
5940
- await sendAssistantAudio(chunk, {
5941
- format,
5942
- receivedAt
5943
- });
5933
+ openedSession.on("final", ({ transcript }) => {
5934
+ const next = options.redact ? options.redact(transcript) : transcript;
5935
+ runAdapterEvent("adapter.final", () => handleFinal(next));
5936
+ });
5937
+ openedSession.on("endOfTurn", ({ reason }) => {
5938
+ runAdapterEvent("adapter.endOfTurn", async () => {
5939
+ clearSilenceTimer();
5940
+ await requestTurnCommit(reason);
5944
5941
  });
5945
5942
  });
5943
+ openedSession.on("error", (event) => {
5944
+ runAdapterEvent("adapter.error", () => handleError(event));
5945
+ });
5946
+ openedSession.on("close", (event) => {
5947
+ runAdapterEvent("adapter.close", () => handleClose(event));
5948
+ });
5949
+ if (options.realtime) {
5950
+ openedSession.on("audio", ({ chunk, format, receivedAt }) => {
5951
+ runAudioSerial(async () => {
5952
+ if (activeAdapterGeneration !== generation) {
5953
+ return;
5954
+ }
5955
+ await sendAssistantAudio(chunk, {
5956
+ format,
5957
+ receivedAt
5958
+ });
5959
+ });
5960
+ });
5961
+ }
5962
+ } catch (error) {
5963
+ await closeAdapter("initialization-failed");
5964
+ throw error;
5946
5965
  }
5947
5966
  return openedSession;
5948
5967
  };
@@ -6853,6 +6872,32 @@ var createVoiceSession = (options) => {
6853
6872
  };
6854
6873
  }
6855
6874
  }
6875
+ const pauseRemainingMs = session.pause ? session.pause.expiresAt - Date.now() : 0;
6876
+ callerPaused = pauseRemainingMs > 0;
6877
+ if (!callerPaused) {
6878
+ session.pause = undefined;
6879
+ }
6880
+ if (session.status === "completed" || session.status === "failed") {
6881
+ await options.store.set(options.id, session);
6882
+ await send({
6883
+ sessionId: options.id,
6884
+ status: session.status,
6885
+ sessionMetadata: session.metadata && typeof session.metadata === "object" ? session.metadata : undefined,
6886
+ scenarioId: session.scenarioId,
6887
+ type: "session"
6888
+ });
6889
+ if (session.status === "completed") {
6890
+ await send({
6891
+ sessionId: options.id,
6892
+ type: "complete"
6893
+ });
6894
+ }
6895
+ return;
6896
+ }
6897
+ session.status = "reconnecting";
6898
+ await options.store.set(options.id, session);
6899
+ await ensureAdapter();
6900
+ session.status = "active";
6856
6901
  if (shouldFireOnSession) {
6857
6902
  pushCallLifecycleEvent(session, {
6858
6903
  type: "start"
@@ -6896,25 +6941,23 @@ var createVoiceSession = (options) => {
6896
6941
  session
6897
6942
  });
6898
6943
  }
6899
- if (session.status === "completed") {
6900
- await send({
6901
- sessionId: options.id,
6902
- type: "complete"
6903
- });
6904
- return;
6905
- }
6906
6944
  resumePendingTurnCommit(session);
6907
- await ensureAdapter();
6908
6945
  warmTTSSession();
6909
- kickCallSilenceWatchdog();
6910
- kickStuckCloseWatchdog();
6911
- kickIdleRepromptWatchdog(true);
6912
- startAmdEvaluationTimer();
6913
- if (options.greeting && session.greetingDeliveredAt === undefined) {
6946
+ if (callerPaused) {
6947
+ schedulePauseTimeout(pauseRemainingMs);
6948
+ } else {
6949
+ kickCallSilenceWatchdog();
6950
+ kickStuckCloseWatchdog();
6951
+ kickIdleRepromptWatchdog(true);
6952
+ }
6953
+ if (!callerPaused) {
6954
+ startAmdEvaluationTimer();
6955
+ }
6956
+ if (!callerPaused && options.greeting && session.greetingDeliveredAt === undefined) {
6914
6957
  session.greetingDeliveredAt = Date.now();
6915
6958
  await options.store.set(options.id, session);
6916
6959
  await speakResolvedLine(options.greeting, session);
6917
- } else if (isResume && options.resumeGreeting && session.turns.length > 0) {
6960
+ } else if (!callerPaused && isResume && options.resumeGreeting && session.turns.length > 0) {
6918
6961
  await speakResolvedLine(options.resumeGreeting, session);
6919
6962
  }
6920
6963
  };
@@ -6942,6 +6985,9 @@ var createVoiceSession = (options) => {
6942
6985
  if (session.status === "completed" || session.status === "failed") {
6943
6986
  return;
6944
6987
  }
6988
+ if (callerPaused) {
6989
+ return;
6990
+ }
6945
6991
  const adapter = await ensureAdapter();
6946
6992
  let inboundAudio = audio;
6947
6993
  if (options.noiseSuppressor) {
@@ -40155,6 +40201,7 @@ var voice = (config) => {
40155
40201
  }
40156
40202
  const monitorBindings = new Map;
40157
40203
  const runtime = {
40204
+ activeSocketConnections: new Map,
40158
40205
  activeSockets: new Map,
40159
40206
  activeSessions: new Map,
40160
40207
  pendingSessions: new Map,
@@ -40392,8 +40439,11 @@ var voice = (config) => {
40392
40439
  await session.connect(buildSocketAdapter(ws, sessionId));
40393
40440
  } catch (error) {
40394
40441
  runtime.logger.warn?.(`[voice] session initialization failed for "${sessionId}": ${error instanceof Error ? error.message : String(error)}`);
40442
+ await session.fail(error).catch((failureError) => {
40443
+ runtime.logger.warn?.(`[voice] failed to persist initialization failure for "${sessionId}": ${failureError instanceof Error ? failureError.message : String(failureError)}`);
40444
+ });
40395
40445
  closedSockets.add(identity);
40396
- ws.close(1011, "voice session initialization failed");
40446
+ ws.close(4500, "voice session initialization failed");
40397
40447
  throw error;
40398
40448
  }
40399
40449
  if (closedSockets.has(identity) || !socketIsOpen(ws) || runtime.activeSockets.get(sessionId) !== identity) {
@@ -40560,6 +40610,7 @@ var voice = (config) => {
40560
40610
  return;
40561
40611
  }
40562
40612
  runtime.activeSockets.delete(socketState.sessionId);
40613
+ runtime.activeSocketConnections.delete(socketState.sessionId);
40563
40614
  const session = runtime.activeSessions.get(socketState.sessionId);
40564
40615
  runtime.activeSessions.delete(socketState.sessionId);
40565
40616
  deregisterMonitorSession(socketState.sessionId, reason ?? `ws-close-${String(code)}`);
@@ -40578,6 +40629,11 @@ var voice = (config) => {
40578
40629
  return;
40579
40630
  }
40580
40631
  const sessionState = resolveSessionId3(runtime, ws);
40632
+ const identity = socketIdentity(ws);
40633
+ if (runtime.activeSockets.get(sessionState.sessionId) !== identity) {
40634
+ ws.close(1000, "superseded");
40635
+ return;
40636
+ }
40581
40637
  const current = runtime.activeSessions.get(sessionState.sessionId);
40582
40638
  const message = parseClientMessage(raw);
40583
40639
  if (message) {
@@ -40590,6 +40646,7 @@ var voice = (config) => {
40590
40646
  if (message.type === "close" && current) {
40591
40647
  await current.close(message.reason);
40592
40648
  runtime.activeSockets.delete(sessionState.sessionId);
40649
+ runtime.activeSocketConnections.delete(sessionState.sessionId);
40593
40650
  runtime.activeSessions.delete(sessionState.sessionId);
40594
40651
  deregisterMonitorSession(sessionState.sessionId, message.reason);
40595
40652
  }
@@ -40651,20 +40708,12 @@ var voice = (config) => {
40651
40708
  }
40652
40709
  }
40653
40710
  if (message.type === "start" && message.sessionId && message.sessionId !== sessionState.sessionId) {
40654
- const currentSession = runtime.activeSessions.get(sessionState.sessionId);
40655
- if (currentSession) {
40656
- await currentSession.close("session-switch");
40657
- runtime.activeSockets.delete(sessionState.sessionId);
40658
- runtime.activeSessions.delete(sessionState.sessionId);
40659
- deregisterMonitorSession(sessionState.sessionId, "session-switch");
40660
- }
40661
- sessionState.sessionId = message.sessionId;
40662
- runtime.activeSockets.set(message.sessionId, socketIdentity(ws));
40663
- runtime.socketSessions.set(socketIdentity(ws), {
40664
- ...sessionState,
40665
- sessionId: message.sessionId,
40666
- scenarioId: sessionState.scenarioId
40667
- });
40711
+ ws.send(JSON.stringify({
40712
+ message: "Voice session switching requires a new WebSocket connection.",
40713
+ recoverable: false,
40714
+ type: "error"
40715
+ }));
40716
+ return;
40668
40717
  }
40669
40718
  if (message.type === "start" && message.scenarioId) {
40670
40719
  sessionState.scenarioId = message.scenarioId;
@@ -40692,7 +40741,15 @@ var voice = (config) => {
40692
40741
  if (closedSockets.has(identity) || !socketIsOpen(ws))
40693
40742
  return;
40694
40743
  const existing = runtime.activeSessions.get(sessionState.sessionId);
40744
+ const displaced = runtime.activeSocketConnections.get(sessionState.sessionId);
40745
+ if (displaced && displaced.identity !== identity) {
40746
+ displaced.close(1000, "superseded");
40747
+ }
40695
40748
  runtime.activeSockets.set(sessionState.sessionId, identity);
40749
+ runtime.activeSocketConnections.set(sessionState.sessionId, {
40750
+ close: ws.close.bind(ws),
40751
+ identity
40752
+ });
40696
40753
  if (existing) {
40697
40754
  await existing.disconnect({
40698
40755
  code: 1006,
@@ -11438,6 +11438,7 @@ var WS_CLOSED = 3;
11438
11438
  var WS_NORMAL_CLOSURE = 1000;
11439
11439
  var WS_UNAUTHORIZED = 4401;
11440
11440
  var WS_FORBIDDEN = 4403;
11441
+ var WS_INITIALIZATION_FAILED = 4500;
11441
11442
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
11442
11443
  var DEFAULT_PING_INTERVAL = 30000;
11443
11444
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
@@ -11741,8 +11742,9 @@ var createVoiceConnection = (path, options = {}) => {
11741
11742
  state.isConnected = false;
11742
11743
  clearTimers();
11743
11744
  rejectPendingCallControls("Voice connection interrupted");
11744
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
11745
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
11745
11746
  const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
11747
+ const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
11746
11748
  if (authorizationFailure) {
11747
11749
  listeners.forEach((listener) => listener({
11748
11750
  message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
@@ -11750,6 +11752,13 @@ var createVoiceConnection = (path, options = {}) => {
11750
11752
  type: "error"
11751
11753
  }));
11752
11754
  }
11755
+ if (initializationFailure) {
11756
+ listeners.forEach((listener) => listener({
11757
+ message: "Voice provider initialization failed.",
11758
+ recoverable: false,
11759
+ type: "error"
11760
+ }));
11761
+ }
11753
11762
  if (reconnectable) {
11754
11763
  scheduleReconnect();
11755
11764
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -11763,7 +11772,7 @@ var createVoiceConnection = (path, options = {}) => {
11763
11772
  },
11764
11773
  type: "connection"
11765
11774
  });
11766
- } else {
11775
+ } else if (!initializationFailure) {
11767
11776
  emitTerminalFailure("Voice connection could not be restored.");
11768
11777
  }
11769
11778
  }
@@ -11789,6 +11798,9 @@ var createVoiceConnection = (path, options = {}) => {
11789
11798
  sendSerialized(JSON.stringify(message));
11790
11799
  };
11791
11800
  const start = (input = {}) => {
11801
+ if (input.sessionId && input.sessionId !== state.sessionId) {
11802
+ throw new Error("Voice session switching requires a new connection instance.");
11803
+ }
11792
11804
  if (input.sessionId) {
11793
11805
  state.sessionId = input.sessionId;
11794
11806
  }
@@ -807,6 +807,7 @@ var WS_CLOSED = 3;
807
807
  var WS_NORMAL_CLOSURE = 1000;
808
808
  var WS_UNAUTHORIZED = 4401;
809
809
  var WS_FORBIDDEN = 4403;
810
+ var WS_INITIALIZATION_FAILED = 4500;
810
811
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
811
812
  var DEFAULT_PING_INTERVAL = 30000;
812
813
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
@@ -1110,8 +1111,9 @@ var createVoiceConnection = (path, options = {}) => {
1110
1111
  state.isConnected = false;
1111
1112
  clearTimers();
1112
1113
  rejectPendingCallControls("Voice connection interrupted");
1113
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
1114
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
1114
1115
  const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
1116
+ const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
1115
1117
  if (authorizationFailure) {
1116
1118
  listeners.forEach((listener) => listener({
1117
1119
  message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
@@ -1119,6 +1121,13 @@ var createVoiceConnection = (path, options = {}) => {
1119
1121
  type: "error"
1120
1122
  }));
1121
1123
  }
1124
+ if (initializationFailure) {
1125
+ listeners.forEach((listener) => listener({
1126
+ message: "Voice provider initialization failed.",
1127
+ recoverable: false,
1128
+ type: "error"
1129
+ }));
1130
+ }
1122
1131
  if (reconnectable) {
1123
1132
  scheduleReconnect();
1124
1133
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -1132,7 +1141,7 @@ var createVoiceConnection = (path, options = {}) => {
1132
1141
  },
1133
1142
  type: "connection"
1134
1143
  });
1135
- } else {
1144
+ } else if (!initializationFailure) {
1136
1145
  emitTerminalFailure("Voice connection could not be restored.");
1137
1146
  }
1138
1147
  }
@@ -1158,6 +1167,9 @@ var createVoiceConnection = (path, options = {}) => {
1158
1167
  sendSerialized(JSON.stringify(message));
1159
1168
  };
1160
1169
  const start = (input = {}) => {
1170
+ if (input.sessionId && input.sessionId !== state.sessionId) {
1171
+ throw new Error("Voice session switching requires a new connection instance.");
1172
+ }
1161
1173
  if (input.sessionId) {
1162
1174
  state.sessionId = input.sessionId;
1163
1175
  }
@@ -3028,6 +3028,7 @@ var WS_CLOSED = 3;
3028
3028
  var WS_NORMAL_CLOSURE = 1000;
3029
3029
  var WS_UNAUTHORIZED = 4401;
3030
3030
  var WS_FORBIDDEN = 4403;
3031
+ var WS_INITIALIZATION_FAILED = 4500;
3031
3032
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
3032
3033
  var DEFAULT_PING_INTERVAL = 30000;
3033
3034
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
@@ -3331,8 +3332,9 @@ var createVoiceConnection = (path, options = {}) => {
3331
3332
  state.isConnected = false;
3332
3333
  clearTimers();
3333
3334
  rejectPendingCallControls("Voice connection interrupted");
3334
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
3335
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
3335
3336
  const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
3337
+ const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
3336
3338
  if (authorizationFailure) {
3337
3339
  listeners.forEach((listener) => listener({
3338
3340
  message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
@@ -3340,6 +3342,13 @@ var createVoiceConnection = (path, options = {}) => {
3340
3342
  type: "error"
3341
3343
  }));
3342
3344
  }
3345
+ if (initializationFailure) {
3346
+ listeners.forEach((listener) => listener({
3347
+ message: "Voice provider initialization failed.",
3348
+ recoverable: false,
3349
+ type: "error"
3350
+ }));
3351
+ }
3343
3352
  if (reconnectable) {
3344
3353
  scheduleReconnect();
3345
3354
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -3353,7 +3362,7 @@ var createVoiceConnection = (path, options = {}) => {
3353
3362
  },
3354
3363
  type: "connection"
3355
3364
  });
3356
- } else {
3365
+ } else if (!initializationFailure) {
3357
3366
  emitTerminalFailure("Voice connection could not be restored.");
3358
3367
  }
3359
3368
  }
@@ -3379,6 +3388,9 @@ var createVoiceConnection = (path, options = {}) => {
3379
3388
  sendSerialized(JSON.stringify(message));
3380
3389
  };
3381
3390
  const start = (input = {}) => {
3391
+ if (input.sessionId && input.sessionId !== state.sessionId) {
3392
+ throw new Error("Voice session switching requires a new connection instance.");
3393
+ }
3382
3394
  if (input.sessionId) {
3383
3395
  state.sessionId = input.sessionId;
3384
3396
  }
@@ -7443,6 +7455,13 @@ var createVoiceSession = (options) => {
7443
7455
  pauseTimeout = null;
7444
7456
  }
7445
7457
  };
7458
+ const schedulePauseTimeout = (remainingMs) => {
7459
+ clearPauseTimeout();
7460
+ pauseTimeout = setTimeout(() => {
7461
+ pauseTimeout = null;
7462
+ api.close("pause-timeout");
7463
+ }, remainingMs);
7464
+ };
7446
7465
  const pauseInternal = async () => {
7447
7466
  if (callerPaused) {
7448
7467
  return;
@@ -7455,14 +7474,17 @@ var createVoiceSession = (options) => {
7455
7474
  clearCallSilenceWatchdog();
7456
7475
  clearStuckCloseWatchdog();
7457
7476
  clearIdleRepromptWatchdog();
7458
- clearPauseTimeout();
7459
- pauseTimeout = setTimeout(() => {
7460
- pauseTimeout = null;
7461
- api.close("pause-timeout");
7462
- }, pauseMaxMs);
7477
+ clearAmdEvaluationTimer();
7478
+ const pausedAt = Date.now();
7479
+ const expiresAt = pausedAt + pauseMaxMs;
7480
+ snapshot.pause = { expiresAt, pausedAt };
7481
+ snapshot.lastActivityAt = pausedAt;
7482
+ await options.store.set(options.id, snapshot);
7483
+ schedulePauseTimeout(pauseMaxMs);
7463
7484
  await appendTrace({
7464
7485
  payload: {
7465
7486
  action: "call.paused",
7487
+ expiresAt,
7466
7488
  maxMs: pauseMaxMs
7467
7489
  },
7468
7490
  session: snapshot,
@@ -7478,7 +7500,11 @@ var createVoiceSession = (options) => {
7478
7500
  kickCallSilenceWatchdog();
7479
7501
  kickStuckCloseWatchdog();
7480
7502
  kickIdleRepromptWatchdog(true);
7503
+ startAmdEvaluationTimer();
7481
7504
  const snapshot = await readSession();
7505
+ snapshot.pause = undefined;
7506
+ snapshot.lastActivityAt = Date.now();
7507
+ await options.store.set(options.id, snapshot);
7482
7508
  await appendTrace({
7483
7509
  payload: {
7484
7510
  action: "call.resumed"
@@ -8836,38 +8862,43 @@ var createVoiceSession = (options) => {
8836
8862
  await handler();
8837
8863
  });
8838
8864
  };
8839
- openedSession.on("partial", ({ transcript }) => {
8840
- const next = options.redact ? options.redact(transcript) : transcript;
8841
- runAdapterEvent("adapter.partial", () => handlePartial(next));
8842
- });
8843
- openedSession.on("final", ({ transcript }) => {
8844
- const next = options.redact ? options.redact(transcript) : transcript;
8845
- runAdapterEvent("adapter.final", () => handleFinal(next));
8846
- });
8847
- openedSession.on("endOfTurn", ({ reason }) => {
8848
- runAdapterEvent("adapter.endOfTurn", async () => {
8849
- clearSilenceTimer();
8850
- await requestTurnCommit(reason);
8865
+ try {
8866
+ openedSession.on("partial", ({ transcript }) => {
8867
+ const next = options.redact ? options.redact(transcript) : transcript;
8868
+ runAdapterEvent("adapter.partial", () => handlePartial(next));
8851
8869
  });
8852
- });
8853
- openedSession.on("error", (event) => {
8854
- runAdapterEvent("adapter.error", () => handleError(event));
8855
- });
8856
- openedSession.on("close", (event) => {
8857
- runAdapterEvent("adapter.close", () => handleClose(event));
8858
- });
8859
- if (options.realtime) {
8860
- openedSession.on("audio", ({ chunk, format, receivedAt }) => {
8861
- runAudioSerial(async () => {
8862
- if (activeAdapterGeneration !== generation) {
8863
- return;
8864
- }
8865
- await sendAssistantAudio(chunk, {
8866
- format,
8867
- receivedAt
8868
- });
8870
+ openedSession.on("final", ({ transcript }) => {
8871
+ const next = options.redact ? options.redact(transcript) : transcript;
8872
+ runAdapterEvent("adapter.final", () => handleFinal(next));
8873
+ });
8874
+ openedSession.on("endOfTurn", ({ reason }) => {
8875
+ runAdapterEvent("adapter.endOfTurn", async () => {
8876
+ clearSilenceTimer();
8877
+ await requestTurnCommit(reason);
8869
8878
  });
8870
8879
  });
8880
+ openedSession.on("error", (event) => {
8881
+ runAdapterEvent("adapter.error", () => handleError(event));
8882
+ });
8883
+ openedSession.on("close", (event) => {
8884
+ runAdapterEvent("adapter.close", () => handleClose(event));
8885
+ });
8886
+ if (options.realtime) {
8887
+ openedSession.on("audio", ({ chunk, format, receivedAt }) => {
8888
+ runAudioSerial(async () => {
8889
+ if (activeAdapterGeneration !== generation) {
8890
+ return;
8891
+ }
8892
+ await sendAssistantAudio(chunk, {
8893
+ format,
8894
+ receivedAt
8895
+ });
8896
+ });
8897
+ });
8898
+ }
8899
+ } catch (error) {
8900
+ await closeAdapter("initialization-failed");
8901
+ throw error;
8871
8902
  }
8872
8903
  return openedSession;
8873
8904
  };
@@ -9778,6 +9809,32 @@ var createVoiceSession = (options) => {
9778
9809
  };
9779
9810
  }
9780
9811
  }
9812
+ const pauseRemainingMs = session.pause ? session.pause.expiresAt - Date.now() : 0;
9813
+ callerPaused = pauseRemainingMs > 0;
9814
+ if (!callerPaused) {
9815
+ session.pause = undefined;
9816
+ }
9817
+ if (session.status === "completed" || session.status === "failed") {
9818
+ await options.store.set(options.id, session);
9819
+ await send({
9820
+ sessionId: options.id,
9821
+ status: session.status,
9822
+ sessionMetadata: session.metadata && typeof session.metadata === "object" ? session.metadata : undefined,
9823
+ scenarioId: session.scenarioId,
9824
+ type: "session"
9825
+ });
9826
+ if (session.status === "completed") {
9827
+ await send({
9828
+ sessionId: options.id,
9829
+ type: "complete"
9830
+ });
9831
+ }
9832
+ return;
9833
+ }
9834
+ session.status = "reconnecting";
9835
+ await options.store.set(options.id, session);
9836
+ await ensureAdapter();
9837
+ session.status = "active";
9781
9838
  if (shouldFireOnSession) {
9782
9839
  pushCallLifecycleEvent(session, {
9783
9840
  type: "start"
@@ -9821,25 +9878,23 @@ var createVoiceSession = (options) => {
9821
9878
  session
9822
9879
  });
9823
9880
  }
9824
- if (session.status === "completed") {
9825
- await send({
9826
- sessionId: options.id,
9827
- type: "complete"
9828
- });
9829
- return;
9830
- }
9831
9881
  resumePendingTurnCommit(session);
9832
- await ensureAdapter();
9833
9882
  warmTTSSession();
9834
- kickCallSilenceWatchdog();
9835
- kickStuckCloseWatchdog();
9836
- kickIdleRepromptWatchdog(true);
9837
- startAmdEvaluationTimer();
9838
- if (options.greeting && session.greetingDeliveredAt === undefined) {
9883
+ if (callerPaused) {
9884
+ schedulePauseTimeout(pauseRemainingMs);
9885
+ } else {
9886
+ kickCallSilenceWatchdog();
9887
+ kickStuckCloseWatchdog();
9888
+ kickIdleRepromptWatchdog(true);
9889
+ }
9890
+ if (!callerPaused) {
9891
+ startAmdEvaluationTimer();
9892
+ }
9893
+ if (!callerPaused && options.greeting && session.greetingDeliveredAt === undefined) {
9839
9894
  session.greetingDeliveredAt = Date.now();
9840
9895
  await options.store.set(options.id, session);
9841
9896
  await speakResolvedLine(options.greeting, session);
9842
- } else if (isResume && options.resumeGreeting && session.turns.length > 0) {
9897
+ } else if (!callerPaused && isResume && options.resumeGreeting && session.turns.length > 0) {
9843
9898
  await speakResolvedLine(options.resumeGreeting, session);
9844
9899
  }
9845
9900
  };
@@ -9867,6 +9922,9 @@ var createVoiceSession = (options) => {
9867
9922
  if (session.status === "completed" || session.status === "failed") {
9868
9923
  return;
9869
9924
  }
9925
+ if (callerPaused) {
9926
+ return;
9927
+ }
9870
9928
  const adapter = await ensureAdapter();
9871
9929
  let inboundAudio = audio;
9872
9930
  if (options.noiseSuppressor) {
package/dist/vue/index.js CHANGED
@@ -10834,6 +10834,7 @@ var WS_CLOSED = 3;
10834
10834
  var WS_NORMAL_CLOSURE = 1000;
10835
10835
  var WS_UNAUTHORIZED = 4401;
10836
10836
  var WS_FORBIDDEN = 4403;
10837
+ var WS_INITIALIZATION_FAILED = 4500;
10837
10838
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
10838
10839
  var DEFAULT_PING_INTERVAL = 30000;
10839
10840
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
@@ -11137,8 +11138,9 @@ var createVoiceConnection = (path, options = {}) => {
11137
11138
  state.isConnected = false;
11138
11139
  clearTimers();
11139
11140
  rejectPendingCallControls("Voice connection interrupted");
11140
- const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
11141
+ const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
11141
11142
  const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
11143
+ const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
11142
11144
  if (authorizationFailure) {
11143
11145
  listeners.forEach((listener) => listener({
11144
11146
  message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
@@ -11146,6 +11148,13 @@ var createVoiceConnection = (path, options = {}) => {
11146
11148
  type: "error"
11147
11149
  }));
11148
11150
  }
11151
+ if (initializationFailure) {
11152
+ listeners.forEach((listener) => listener({
11153
+ message: "Voice provider initialization failed.",
11154
+ recoverable: false,
11155
+ type: "error"
11156
+ }));
11157
+ }
11149
11158
  if (reconnectable) {
11150
11159
  scheduleReconnect();
11151
11160
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -11159,7 +11168,7 @@ var createVoiceConnection = (path, options = {}) => {
11159
11168
  },
11160
11169
  type: "connection"
11161
11170
  });
11162
- } else {
11171
+ } else if (!initializationFailure) {
11163
11172
  emitTerminalFailure("Voice connection could not be restored.");
11164
11173
  }
11165
11174
  }
@@ -11185,6 +11194,9 @@ var createVoiceConnection = (path, options = {}) => {
11185
11194
  sendSerialized(JSON.stringify(message));
11186
11195
  };
11187
11196
  const start = (input = {}) => {
11197
+ if (input.sessionId && input.sessionId !== state.sessionId) {
11198
+ throw new Error("Voice session switching requires a new connection instance.");
11199
+ }
11188
11200
  if (input.sessionId) {
11189
11201
  state.sessionId = input.sessionId;
11190
11202
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.659",
3
+ "version": "0.0.22-beta.660",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",