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

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.
@@ -108,29 +108,34 @@ var createMicrophoneCapture = (options) => {
108
108
  if (!AudioContextCtor) {
109
109
  throw new Error("Browser microphone capture requires AudioContext support.");
110
110
  }
111
- mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
112
- audio: {
113
- autoGainControl: true,
114
- channelCount: options.channelCount ?? 1,
115
- echoCancellation: true,
116
- noiseSuppression: true
111
+ try {
112
+ mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
113
+ audio: {
114
+ autoGainControl: true,
115
+ channelCount: options.channelCount ?? 1,
116
+ echoCancellation: true,
117
+ noiseSuppression: true
118
+ }
119
+ });
120
+ audioContext = new AudioContextCtor;
121
+ if (audioContext.state === "suspended") {
122
+ await audioContext.resume();
117
123
  }
118
- });
119
- audioContext = new AudioContextCtor;
120
- if (audioContext.state === "suspended") {
121
- await audioContext.resume();
124
+ sourceNode = audioContext.createMediaStreamSource(mediaStream);
125
+ processorNode = audioContext.createScriptProcessor(4096, 1, 1);
126
+ processorNode.onaudioprocess = (event) => {
127
+ const channel = event.inputBuffer.getChannelData(0);
128
+ const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
129
+ const pcm = floatTo16BitPCM(downsampled);
130
+ options.onLevel?.(getPcmLevel(pcm));
131
+ options.onAudio(pcm);
132
+ };
133
+ sourceNode.connect(processorNode);
134
+ processorNode.connect(audioContext.destination);
135
+ } catch (error) {
136
+ stop();
137
+ throw error;
122
138
  }
123
- sourceNode = audioContext.createMediaStreamSource(mediaStream);
124
- processorNode = audioContext.createScriptProcessor(4096, 1, 1);
125
- processorNode.onaudioprocess = (event) => {
126
- const channel = event.inputBuffer.getChannelData(0);
127
- const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
128
- const pcm = floatTo16BitPCM(downsampled);
129
- options.onLevel?.(getPcmLevel(pcm));
130
- options.onAudio(pcm);
131
- };
132
- sourceNode.connect(processorNode);
133
- processorNode.connect(audioContext.destination);
134
139
  };
135
140
  const stop = () => {
136
141
  processorNode?.disconnect();
@@ -499,6 +504,7 @@ var noopUnsubscribe = () => noop;
499
504
  var NOOP_CONNECTION = {
500
505
  callControl: noop,
501
506
  close: noop,
507
+ disconnect: noop,
502
508
  endTurn: noop,
503
509
  send: noop,
504
510
  sendAudio: noop,
@@ -510,7 +516,7 @@ var NOOP_CONNECTION = {
510
516
  start: () => {}
511
517
  };
512
518
  var createSessionId = () => crypto.randomUUID();
513
- var buildWsUrl = (path, sessionId, scenarioId) => {
519
+ var buildWsUrl = (path, sessionId, scenarioId, query) => {
514
520
  const { hostname, port, protocol } = window.location;
515
521
  const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
516
522
  const portSuffix = port ? `:${port}` : "";
@@ -519,6 +525,11 @@ var buildWsUrl = (path, sessionId, scenarioId) => {
519
525
  if (scenarioId) {
520
526
  url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
521
527
  }
528
+ for (const [key, value] of Object.entries(query)) {
529
+ if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
530
+ url.searchParams.set(key, value);
531
+ }
532
+ }
522
533
  return url.toString();
523
534
  };
524
535
  var isVoiceServerMessage = (value) => {
@@ -628,7 +639,7 @@ var createVoiceConnection = (path, options = {}) => {
628
639
  }, delayMs);
629
640
  };
630
641
  const connect = () => {
631
- const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
642
+ const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
632
643
  ws.binaryType = "arraybuffer";
633
644
  ws.onopen = () => {
634
645
  const wasReconnecting = state.reconnectAttempts > 0;
@@ -724,7 +735,19 @@ var createVoiceConnection = (path, options = {}) => {
724
735
  type: "call_control"
725
736
  });
726
737
  };
727
- const close = () => {
738
+ const close = (reason = "client-close") => {
739
+ clearTimers();
740
+ if (state.ws) {
741
+ if (state.ws.readyState === WS_OPEN) {
742
+ state.ws.send(JSON.stringify({ reason, type: "close" }));
743
+ }
744
+ state.ws.close(WS_NORMAL_CLOSURE);
745
+ state.ws = null;
746
+ }
747
+ state.isConnected = false;
748
+ listeners.clear();
749
+ };
750
+ const disconnect = () => {
728
751
  clearTimers();
729
752
  if (state.ws) {
730
753
  state.ws.close(WS_NORMAL_CLOSURE);
@@ -748,6 +771,7 @@ var createVoiceConnection = (path, options = {}) => {
748
771
  return {
749
772
  callControl,
750
773
  close,
774
+ disconnect,
751
775
  endTurn,
752
776
  send,
753
777
  sendAudio,
@@ -1028,10 +1052,17 @@ var createVoiceStream = (path, options = {}) => {
1028
1052
  callControl(message) {
1029
1053
  connection.callControl(message);
1030
1054
  },
1031
- close() {
1055
+ close(reason) {
1056
+ unsubscribeConnection();
1057
+ browserMediaReporter?.close();
1058
+ connection.close(reason);
1059
+ store.dispatch({ type: "disconnected" });
1060
+ notify();
1061
+ },
1062
+ disconnect() {
1032
1063
  unsubscribeConnection();
1033
1064
  browserMediaReporter?.close();
1034
- connection.close();
1065
+ connection.disconnect();
1035
1066
  store.dispatch({ type: "disconnected" });
1036
1067
  notify();
1037
1068
  },
@@ -1476,6 +1507,7 @@ var createVoiceController = (path, options = {}) => {
1476
1507
  };
1477
1508
  notify();
1478
1509
  } catch (error) {
1510
+ capture?.stop();
1479
1511
  capture = null;
1480
1512
  state = {
1481
1513
  ...state,
@@ -1486,13 +1518,19 @@ var createVoiceController = (path, options = {}) => {
1486
1518
  throw error;
1487
1519
  }
1488
1520
  };
1489
- const close = () => {
1521
+ const close = (reason) => {
1522
+ unsubscribeStream();
1523
+ stopRecording();
1524
+ stream.close(reason);
1525
+ };
1526
+ const disconnect = () => {
1490
1527
  unsubscribeStream();
1491
1528
  stopRecording();
1492
- stream.close();
1529
+ stream.disconnect();
1493
1530
  };
1494
1531
  return {
1495
1532
  close,
1533
+ disconnect,
1496
1534
  startRecording,
1497
1535
  stopRecording,
1498
1536
  get assistantAudio() {
@@ -1,10 +1,10 @@
1
- (()=>{var{defineProperty:j,getOwnPropertyNames:c0,getOwnPropertyDescriptor:R0}=Object,_0=Object.prototype.hasOwnProperty;function $0(g){return this[g]}var I0=(g)=>{var A=(x??=new WeakMap).get(g),C;if(A)return A;if(A=j({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of c0(g))if(!_0.call(A,V))j(A,V,{get:$0.bind(g,V),enumerable:!(C=R0(g,V))||C.enumerable})}return x.set(g,A),A},x;var H0=(g)=>g;function U0(g,A){this[g]=H0.bind(null,A)}var L0=(g,A)=>{for(var C in A)j(g,C,{get:A[C],enumerable:!0,configurable:!0,set:U0.bind(A,C)})};var o0={};L0(o0,{mount:()=>A0,default:()=>m0,VOICE_EMBED_VERSION:()=>V0});var G0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},W0=(g,A,C,V)=>{let R=A??g.getAttribute("hx-get")??"";if(!R)return"";let T=new URL(R,window.location.origin);if(V)T.searchParams.set(C,V);else T.searchParams.delete(C);return`${T.pathname}${T.search}${T.hash}`},k=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=G0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",R=A.sessionQueryParam??"sessionId",T=()=>{let H=window,c=W0(C,A.route,R,g.sessionId);if(c)C.setAttribute("hx-get",c);H.htmx?.process?.(C),H.htmx?.trigger?.(C,V)},L=g.subscribe(T);return T(),()=>{L()}};var y0=(g)=>Math.max(-1,Math.min(1,g)),w0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=y0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},D0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let R of C){let T=R/32768;V+=T*T}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},S0=(g,A,C)=>{if(A===C)return g;let V=A/C,R=Math.round(g.length/V),T=new Float32Array(R),L=0,H=0;while(L<T.length){let c=Math.round((L+1)*V),W=0,I=0;for(let y=H;y<c&&y<g.length;y+=1)W+=g[y]??0,I+=1;T[L]=I>0?W/I:0,L+=1,H=c}return T},F=(g)=>{let A=null,C=null,V=null,R=null;return{start:async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let H=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!H)throw Error("Browser microphone capture requires AudioContext support.");if(R=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new H,A.state==="suspended")await A.resume();C=A.createMediaStreamSource(R),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(c)=>{let W=c.inputBuffer.getChannelData(0),I=S0(W,A?.sampleRate??48000,g.sampleRateHz??16000),y=w0(I);g.onLevel?.(D0(y)),g.onAudio(y)},C.connect(V),V.connect(A.destination)},stop:()=>{V?.disconnect(),C?.disconnect(),R?.getTracks().forEach((H)=>H.stop()),A?.close(),g.onLevel?.(0),A=null,R=null,V=null,C=null}}};var B=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return B(A.error);if("cause"in A)return B(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},f=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:B(g.message),type:"error"};case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var p0=Math.PI*2;var Z=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var X0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,b=(g)=>g.length===0?void 0:Math.max(...g);var w=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},z=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},S=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},M=(g)=>String(g.id??S(g,"ssrc")??w(g,"ssrc")??S(g,"trackIdentifier")??S(g,"mid")??"unknown"),l=(g)=>g===void 0?void 0:g*1000;var h0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var d=(g={})=>{let A=g.stats??[],C=[],V=A.filter((_)=>_.type==="inbound-rtp"&&S(_,"kind")!=="video"),R=A.filter((_)=>_.type==="outbound-rtp"&&S(_,"kind")!=="video"),T=A.filter((_)=>_.type==="candidate-pair"),L=A.filter((_)=>(_.type==="track"||_.type==="media-source")&&S(_,"kind")==="audio"),H=T.filter((_)=>z(_,"selected")===!0||z(_,"nominated")===!0||S(_,"state")==="succeeded").length,c=L.filter((_)=>S(_,"readyState")!=="ended"&&S(_,"trackState")!=="ended"&&z(_,"ended")!==!0).length,W=L.filter((_)=>S(_,"readyState")==="ended"||S(_,"trackState")==="ended"||z(_,"ended")===!0).length,I=V.reduce((_,D)=>_+(w(D,"packetsReceived")??0),0),y=R.reduce((_,D)=>_+(w(D,"packetsSent")??0),0),$=[...V,...R].reduce((_,D)=>_+Math.max(0,w(D,"packetsLost")??0),0),X=I+$,G=X===0?0:$/X,P=V.reduce((_,D)=>_+(w(D,"bytesReceived")??0),0),h=R.reduce((_,D)=>_+(w(D,"bytesSent")??0),0),O=b(T.map((_)=>l(w(_,"currentRoundTripTime")??w(_,"roundTripTime"))).filter((_)=>_!==void 0)),J=b([...V,...R].map((_)=>l(w(_,"jitter"))).filter((_)=>_!==void 0)),E=b(V.map((_)=>{let D=w(_,"jitterBufferDelay"),U=w(_,"jitterBufferEmittedCount");return D!==void 0&&U!==void 0&&U>0?D/U*1000:void 0}).filter((_)=>_!==void 0)),N=L.map((_)=>w(_,"audioLevel")).filter((_)=>_!==void 0);if(g.requireConnectedCandidatePair&&T.length>0&&H===0)Z(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&c===0)Z(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&G>g.maxPacketLossRatio)Z(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(G)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&O!==void 0&&O>g.maxRoundTripTimeMs)Z(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(O)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&J!==void 0&&J>g.maxJitterMs)Z(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(J)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:H,audioLevelAverage:X0(N),bytesReceived:P,bytesSent:h,checkedAt:Date.now(),endedAudioTracks:W,inboundPackets:I,issues:C,jitterBufferDelayMs:E,jitterMs:J,liveAudioTracks:c,outboundPackets:y,packetLossRatio:G,packetsLost:$,roundTripTimeMs:O,status:C.some((_)=>_.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},v=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(h0)};var i=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],R=new Map(C.map(($)=>[M($),$])),L=A.filter(($)=>($.type==="inbound-rtp"||$.type==="outbound-rtp")&&S($,"kind")!=="video"&&S($,"mediaType")!=="video").map(($)=>{let X=$.type==="outbound-rtp"?"outbound":"inbound",G=X==="outbound"?"packetsSent":"packetsReceived",P=X==="outbound"?"bytesSent":"bytesReceived",h=R.get(M($)),O=w($,G),J=h?w(h,G):void 0,E=w($,P),N=h?w(h,P):void 0,_=$.timestamp!==void 0&&h?.timestamp!==void 0?$.timestamp-h.timestamp:void 0;return{bytesDelta:E!==void 0&&N!==void 0?E-N:void 0,currentPackets:O,direction:X,id:M($),packetDelta:O!==void 0&&J!==void 0?O-J:void 0,previousPackets:J,timeDeltaMs:_}}),H=L.filter(($)=>$.direction==="inbound"),c=L.filter(($)=>$.direction==="outbound"),W=b(L.map(($)=>$.timeDeltaMs).filter(($)=>$!==void 0)),I=H.filter(($)=>g.maxInboundPacketStallMs!==void 0&&$.timeDeltaMs!==void 0&&$.timeDeltaMs>=g.maxInboundPacketStallMs&&$.packetDelta!==void 0&&$.packetDelta<=0).length,y=c.filter(($)=>g.maxOutboundPacketStallMs!==void 0&&$.timeDeltaMs!==void 0&&$.timeDeltaMs>=g.maxOutboundPacketStallMs&&$.packetDelta!==void 0&&$.packetDelta<=0).length;if(g.requireInboundAudio&&H.length===0)Z(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&c.length===0)Z(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&W!==void 0&&W>g.maxGapMs)Z(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(W)}ms above ${String(g.maxGapMs)}ms.`);if(I>0)Z(V,"error","media.webrtc_inbound_stalled",`${String(I)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(y>0)Z(V,"error","media.webrtc_outbound_stalled",`${String(y)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:H.length,issues:V,maxObservedGapMs:W,outboundAudioStreams:c.length,stalledInboundStreams:I,stalledOutboundStreams:y,status:V.some(($)=>$.severity==="error")?"fail":V.length>0?"warn":"pass",streams:L,totalStats:A.length}};var O0="/api/voice/browser-media",J0=5000,Y0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,Z0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??O0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},n=(g)=>{let A=null,C=[],V=async()=>{let L=await Y0(g);if(!L)return;let H=await v({peerConnection:L}),c=d({...g,stats:H}),W=g.continuity===!1?void 0:i({...g.continuity,previousStats:C,stats:H}),I={at:Date.now(),continuity:W,report:c,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=H,g.onReport?.(I),await Z0(I,g),I},R=()=>{V().catch((L)=>{g.onError?.(L)})},T=()=>{if(A)clearInterval(A),A=null};return{close:T,reportOnce:V,stop:T,start:()=>{if(A)return;R(),A=setInterval(R,g.intervalMs??J0)}}};var Q0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var q=()=>{},P0=()=>q,K0={callControl:q,close:q,endTurn:q,send:q,sendAudio:q,simulateDisconnect:q,subscribe:P0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},q0=()=>crypto.randomUUID(),E0=(g,A,C)=>{let{hostname:V,port:R,protocol:T}=window.location,L=T==="https:"?"wss:":"ws:",H=R?`:${R}`:"",c=new URL(`${L}//${V}${H}${g}`);if(c.searchParams.set("sessionId",A),C)c.searchParams.set("scenarioId",C);return c.toString()},N0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},z0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return N0(A)?A:null}catch{return null}},o=(g,A={})=>{if(typeof window>"u")return K0;let C=new Set,V=A.reconnect!==!1,R=A.maxReconnectAttempts??15,T=A.reconnectMaxDelayMs??8000,L=A.pingInterval??30000,H=(U)=>Q0(U,500,T),c={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:A.sessionId??q0(),ws:null},W=(U)=>{C.forEach((Y)=>Y(U))},I=()=>{if(c.pingInterval)clearInterval(c.pingInterval),c.pingInterval=null;if(c.reconnectTimeout)clearTimeout(c.reconnectTimeout),c.reconnectTimeout=null},y=()=>{if(c.ws?.readyState!==1)return;while(c.pendingMessages.length>0){let U=c.pendingMessages.shift();if(U!==void 0)c.ws.send(U)}},$=()=>{c.reconnectAttempts+=1;let U=H(c.reconnectAttempts),Y=Date.now()+U;W({reconnect:{attempts:c.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:R,nextAttemptAt:Y,status:"reconnecting"},type:"connection"}),c.reconnectTimeout=setTimeout(()=>{if(c.reconnectAttempts>R){W({reconnect:{attempts:c.reconnectAttempts,maxAttempts:R,status:"exhausted"},type:"connection"});return}X()},U)},X=()=>{let U=new WebSocket(E0(g,c.sessionId,c.scenarioId));U.binaryType="arraybuffer",U.onopen=()=>{let Y=c.reconnectAttempts>0;if(c.isConnected=!0,y(),Y)W({reconnect:{attempts:c.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:R,status:"resumed"},type:"connection"}),c.reconnectAttempts=0;C.forEach((K)=>K({scenarioId:c.scenarioId??void 0,sessionId:c.sessionId,status:"active",type:"session"})),c.pingInterval=setInterval(()=>{if(U.readyState===1)U.send(JSON.stringify({type:"ping"}))},L)},U.onmessage=(Y)=>{let K=z0(Y);if(!K)return;if(K.type==="session")c.sessionId=K.sessionId,c.scenarioId=K.scenarioId??c.scenarioId;C.forEach((T0)=>T0(K))},U.onclose=(Y)=>{if(c.isConnected=!1,I(),V&&Y.code!==1000&&c.reconnectAttempts<R)$();else if(V&&Y.code!==1000)W({reconnect:{attempts:c.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:R,status:"exhausted"},type:"connection"})},c.ws=U},G=(U)=>{if(c.ws?.readyState===1){c.ws.send(U);return}c.pendingMessages.push(U)},P=(U)=>{G(JSON.stringify(U))},h=(U={})=>{if(U.sessionId)c.sessionId=U.sessionId;if(U.scenarioId)c.scenarioId=U.scenarioId;P({scenarioId:c.scenarioId??void 0,sessionId:c.sessionId,type:"start"})},O=(U)=>{G(U)},J=()=>{P({type:"end_turn"})},E=(U)=>{P({...U,type:"call_control"})},N=()=>{if(I(),c.ws)c.ws.close(1000),c.ws=null;c.isConnected=!1,C.clear()},_=()=>{if(c.ws?.readyState===1)c.ws.close(4000,"absolutejs-voice-reconnect-proof")},D=(U)=>{return C.add(U),()=>{C.delete(U)}};return X(),{callControl:E,close:N,endTurn:J,send:P,sendAudio:O,simulateDisconnect:_,start:h,subscribe:D,getReadyState:()=>c.ws?.readyState??3,getScenarioId:()=>c.scenarioId??"",getSessionId:()=>c.sessionId}};var b0=()=>({attempts:0,maxAttempts:0,status:"idle"}),j0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},B0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},M0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:b0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),m=()=>{let g=M0(),A="",C=new Set,V=()=>{C.forEach((T)=>T())};return{dispatch:(T)=>{switch(T.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:T.chunk,format:T.format,receivedAt:T.receivedAt,turnId:T.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,T.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${T.delta}`};break;case"complete":g={...g,sessionId:T.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:T.event.type==="end"?T.event.disposition:g.call?.disposition,endedAt:T.event.type==="end"?T.event.at:g.call?.endedAt,events:[...g.call?.events??[],T.event],lastEventAt:T.event.at,startedAt:g.call?.startedAt??T.event.at},sessionId:T.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:T.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:T.message};break;case"final":A=j0(A,T.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:B0(A,T.transcript.text)};break;case"replay":A=T.partial,g={...g,assistantStreamingText:"",assistantTexts:[...T.assistantTexts],call:T.call??null,error:null,isConnected:T.status==="active",partial:T.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:T.scenarioId??g.scenarioId,sessionId:T.sessionId,sessionMetadata:T.sessionMetadata??g.sessionMetadata,status:T.status,turns:[...T.turns]};break;case"session":g={...g,error:null,scenarioId:T.scenarioId??g.scenarioId,isConnected:T.status==="active",sessionId:T.sessionId,sessionMetadata:T.sessionMetadata??g.sessionMetadata,status:T.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,T.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(T)=>{return C.add(T),()=>{C.delete(T)}}}};var r=(g,A={})=>{let C=o(g,A),V=m(),R=A.browserMedia&&typeof window<"u"?n({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,T=new Set,L=(I)=>Promise.resolve().then(()=>{if(!I?.sessionId&&!I?.scenarioId)return;C.start(I),R?.start()}),H=()=>{T.forEach((I)=>I())},c=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let I=V.getSnapshot(),y=JSON.stringify({at:Date.now(),reconnect:I.reconnect,scenarioId:I.scenarioId,sessionId:C.getSessionId(),turnIds:I.turns.map(($)=>$.id)});fetch(A.reconnectReportPath,{body:y,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},W=C.subscribe((I)=>{let y=f(I);if(y){if(V.dispatch(y),I.type==="connection")c();H()}});return{start:L,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl(I){C.callControl(I)},close(){W(),R?.close(),C.close(),V.dispatch({type:"disconnected"}),H()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio(I){C.sendAudio(I)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe(I){return T.add(I),()=>{T.delete(I)}},get turns(){return V.getSnapshot().turns}}};var u=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var x0={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}},k0={"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 s=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=x0[A],R=k0[C],T=g?.silenceMs??R.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(T,g?.minSilenceMs??R.minSilenceMs??V.minSilenceMs),silenceMs:T,speechThreshold:g?.speechThreshold??R.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??R.transcriptStabilityMs??V.transcriptStabilityMs}};var F0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},a=(g="default")=>{let A=F0[g];return{audioConditioning:u(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:s(A.turnDetection)}};var f0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),p=(g,A={})=>{let C=a(A.preset),V=r(g,{...C.connection,...A.connection}),R=null,T=f0(V),L=new Set,H=()=>{for(let G of L)G()},c=()=>{if(T={...T,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&T.status==="completed"&&T.isRecording)R?.stop(),R=null,T={...T,isRecording:!1};H()},W=V.subscribe(c);c();let I=()=>{if(R)return R;return R=F({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(G)=>{if(A.capture?.onAudio){A.capture.onAudio(G,V.sendAudio);return}V.sendAudio(G)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),R},y=()=>{R?.stop(),R=null,T={...T,isRecording:!1},H()},$=async()=>{if(T.isRecording)return;try{T={...T,recordingError:null},H(),await I().start(),T={...T,isRecording:!0},H()}catch(G){throw R=null,T={...T,isRecording:!1,recordingError:G instanceof Error?G.message:String(G)},H(),G}};return{close:()=>{W(),y(),V.close()},startRecording:$,stopRecording:y,get assistantAudio(){return T.assistantAudio},get assistantTexts(){return T.assistantTexts},get assistantStreamingText(){return T.assistantStreamingText},bindHTMX(G){return k(V,G)},get call(){return T.call},callControl:(G)=>V.callControl(G),endTurn:()=>V.endTurn(),get error(){return T.error},getServerSnapshot:()=>T,getSnapshot:()=>T,get isConnected(){return T.isConnected},get isRecording(){return T.isRecording},get partial(){return T.partial},get reconnect(){return T.reconnect},get recordingError(){return T.recordingError},get scenarioId(){return T.scenarioId},sendAudio:(G)=>V.sendAudio(G),get sessionId(){return T.sessionId},get sessionMetadata(){return T.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return T.status},subscribe:(G)=>{return L.add(G),()=>{L.delete(G)}},toggleRecording:async()=>{if(T.isRecording){y();return}await $()},get turns(){return T.turns}}};var Q=(g)=>String(g).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");var t=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var l0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},d0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},v0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},e=(g)=>{let A={...l0,...g.theme},C={...d0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,R=g.state.turns.at(-1)?.committedAt,T=t({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:R}),L=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,H=g.state.error?"Error":L?C.connecting:g.state.status==="completed"?C.callEnded:v0(T,C);return{agentState:T,classes:{container:`absolute-voice-widget absolute-voice-widget--${T}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:H,theme:A,title:g.title??"Voice"}},i0=(g)=>typeof g==="number"?`${g}px`:g,g0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${i0(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,R=[];if(g.controls.canStart)R.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${Q(g.labels.startCall)}</button>`);if(g.controls.canMute)R.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${Q(g.labels.mute)}</button>`);if(g.controls.canEnd)R.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${Q(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${Q(g.classes.container)}" style="${C}">
1
+ (()=>{var{defineProperty:B,getOwnPropertyNames:R0,getOwnPropertyDescriptor:_0}=Object,$0=Object.prototype.hasOwnProperty;function I0(g){return this[g]}var H0=(g)=>{var A=(k??=new WeakMap).get(g),C;if(A)return A;if(A=B({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of R0(g))if(!$0.call(A,V))B(A,V,{get:I0.bind(g,V),enumerable:!(C=_0(g,V))||C.enumerable})}return k.set(g,A),A},k;var U0=(g)=>g;function L0(g,A){this[g]=U0.bind(null,A)}var G0=(g,A)=>{for(var C in A)B(g,C,{get:A[C],enumerable:!0,configurable:!0,set:L0.bind(A,C)})};var m0={};G0(m0,{mount:()=>V0,default:()=>r0,VOICE_EMBED_VERSION:()=>C0});var W0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},w0=(g,A,C,V)=>{let R=A??g.getAttribute("hx-get")??"";if(!R)return"";let c=new URL(R,window.location.origin);if(V)c.searchParams.set(C,V);else c.searchParams.delete(C);return`${c.pathname}${c.search}${c.hash}`},F=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=W0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",R=A.sessionQueryParam??"sessionId",c=()=>{let H=window,T=w0(C,A.route,R,g.sessionId);if(T)C.setAttribute("hx-get",T);H.htmx?.process?.(C),H.htmx?.trigger?.(C,V)},U=g.subscribe(c);return c(),()=>{U()}};var y0=(g)=>Math.max(-1,Math.min(1,g)),D0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=y0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},S0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let R of C){let c=R/32768;V+=c*c}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},X0=(g,A,C)=>{if(A===C)return g;let V=A/C,R=Math.round(g.length/V),c=new Float32Array(R),U=0,H=0;while(U<c.length){let T=Math.round((U+1)*V),G=0,$=0;for(let w=H;w<T&&w<g.length;w+=1)G+=g[w]??0,$+=1;c[U]=$>0?G/$:0,U+=1,H=T}return c},f=(g)=>{let A=null,C=null,V=null,R=null,c=async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let H=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!H)throw Error("Browser microphone capture requires AudioContext support.");try{if(R=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new H,A.state==="suspended")await A.resume();C=A.createMediaStreamSource(R),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(T)=>{let G=T.inputBuffer.getChannelData(0),$=X0(G,A?.sampleRate??48000,g.sampleRateHz??16000),w=D0($);g.onLevel?.(S0(w)),g.onAudio(w)},C.connect(V),V.connect(A.destination)}catch(T){throw U(),T}},U=()=>{V?.disconnect(),C?.disconnect(),R?.getTracks().forEach((H)=>H.stop()),A?.close(),g.onLevel?.(0),A=null,R=null,V=null,C=null};return{start:c,stop:U}};var M=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return M(A.error);if("cause"in A)return M(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},d=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:M(g.message),type:"error"};case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var t0=Math.PI*2;var Q=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var h0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,j=(g)=>g.length===0?void 0:Math.max(...g);var y=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},b=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},S=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},x=(g)=>String(g.id??S(g,"ssrc")??y(g,"ssrc")??S(g,"trackIdentifier")??S(g,"mid")??"unknown"),l=(g)=>g===void 0?void 0:g*1000;var O0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var v=(g={})=>{let A=g.stats??[],C=[],V=A.filter((_)=>_.type==="inbound-rtp"&&S(_,"kind")!=="video"),R=A.filter((_)=>_.type==="outbound-rtp"&&S(_,"kind")!=="video"),c=A.filter((_)=>_.type==="candidate-pair"),U=A.filter((_)=>(_.type==="track"||_.type==="media-source")&&S(_,"kind")==="audio"),H=c.filter((_)=>b(_,"selected")===!0||b(_,"nominated")===!0||S(_,"state")==="succeeded").length,T=U.filter((_)=>S(_,"readyState")!=="ended"&&S(_,"trackState")!=="ended"&&b(_,"ended")!==!0).length,G=U.filter((_)=>S(_,"readyState")==="ended"||S(_,"trackState")==="ended"||b(_,"ended")===!0).length,$=V.reduce((_,D)=>_+(y(D,"packetsReceived")??0),0),w=R.reduce((_,D)=>_+(y(D,"packetsSent")??0),0),I=[...V,...R].reduce((_,D)=>_+Math.max(0,y(D,"packetsLost")??0),0),X=$+I,h=X===0?0:I/X,W=V.reduce((_,D)=>_+(y(D,"bytesReceived")??0),0),O=R.reduce((_,D)=>_+(y(D,"bytesSent")??0),0),J=j(c.map((_)=>l(y(_,"currentRoundTripTime")??y(_,"roundTripTime"))).filter((_)=>_!==void 0)),Y=j([...V,...R].map((_)=>l(y(_,"jitter"))).filter((_)=>_!==void 0)),N=j(V.map((_)=>{let D=y(_,"jitterBufferDelay"),z=y(_,"jitterBufferEmittedCount");return D!==void 0&&z!==void 0&&z>0?D/z*1000:void 0}).filter((_)=>_!==void 0)),q=U.map((_)=>y(_,"audioLevel")).filter((_)=>_!==void 0);if(g.requireConnectedCandidatePair&&c.length>0&&H===0)Q(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&T===0)Q(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&h>g.maxPacketLossRatio)Q(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(h)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&J!==void 0&&J>g.maxRoundTripTimeMs)Q(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(J)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&Y!==void 0&&Y>g.maxJitterMs)Q(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(Y)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:H,audioLevelAverage:h0(q),bytesReceived:W,bytesSent:O,checkedAt:Date.now(),endedAudioTracks:G,inboundPackets:$,issues:C,jitterBufferDelayMs:N,jitterMs:Y,liveAudioTracks:T,outboundPackets:w,packetLossRatio:h,packetsLost:I,roundTripTimeMs:J,status:C.some((_)=>_.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},i=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(O0)};var n=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],R=new Map(C.map((I)=>[x(I),I])),U=A.filter((I)=>(I.type==="inbound-rtp"||I.type==="outbound-rtp")&&S(I,"kind")!=="video"&&S(I,"mediaType")!=="video").map((I)=>{let X=I.type==="outbound-rtp"?"outbound":"inbound",h=X==="outbound"?"packetsSent":"packetsReceived",W=X==="outbound"?"bytesSent":"bytesReceived",O=R.get(x(I)),J=y(I,h),Y=O?y(O,h):void 0,N=y(I,W),q=O?y(O,W):void 0,_=I.timestamp!==void 0&&O?.timestamp!==void 0?I.timestamp-O.timestamp:void 0;return{bytesDelta:N!==void 0&&q!==void 0?N-q:void 0,currentPackets:J,direction:X,id:x(I),packetDelta:J!==void 0&&Y!==void 0?J-Y:void 0,previousPackets:Y,timeDeltaMs:_}}),H=U.filter((I)=>I.direction==="inbound"),T=U.filter((I)=>I.direction==="outbound"),G=j(U.map((I)=>I.timeDeltaMs).filter((I)=>I!==void 0)),$=H.filter((I)=>g.maxInboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxInboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length,w=T.filter((I)=>g.maxOutboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxOutboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length;if(g.requireInboundAudio&&H.length===0)Q(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&T.length===0)Q(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&G!==void 0&&G>g.maxGapMs)Q(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(G)}ms above ${String(g.maxGapMs)}ms.`);if($>0)Q(V,"error","media.webrtc_inbound_stalled",`${String($)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(w>0)Q(V,"error","media.webrtc_outbound_stalled",`${String(w)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:H.length,issues:V,maxObservedGapMs:G,outboundAudioStreams:T.length,stalledInboundStreams:$,stalledOutboundStreams:w,status:V.some((I)=>I.severity==="error")?"fail":V.length>0?"warn":"pass",streams:U,totalStats:A.length}};var J0="/api/voice/browser-media",Y0=5000,Z0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,Q0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??J0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},o=(g)=>{let A=null,C=[],V=async()=>{let U=await Z0(g);if(!U)return;let H=await i({peerConnection:U}),T=v({...g,stats:H}),G=g.continuity===!1?void 0:n({...g.continuity,previousStats:C,stats:H}),$={at:Date.now(),continuity:G,report:T,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=H,g.onReport?.($),await Q0($,g),$},R=()=>{V().catch((U)=>{g.onError?.(U)})},c=()=>{if(A)clearInterval(A),A=null};return{close:c,reportOnce:V,stop:c,start:()=>{if(A)return;R(),A=setInterval(R,g.intervalMs??Y0)}}};var P0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var E=()=>{},K0=()=>E,E0={callControl:E,close:E,disconnect:E,endTurn:E,send:E,sendAudio:E,simulateDisconnect:E,subscribe:K0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},N0=()=>crypto.randomUUID(),q0=(g,A,C,V)=>{let{hostname:R,port:c,protocol:U}=window.location,H=U==="https:"?"wss:":"ws:",T=c?`:${c}`:"",G=new URL(`${H}//${R}${T}${g}`);if(G.searchParams.set("sessionId",A),C)G.searchParams.set("scenarioId",C);for(let[$,w]of Object.entries(V))if($!=="sessionId"&&$!=="scenarioId"&&w)G.searchParams.set($,w);return G.toString()},z0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},b0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return z0(A)?A:null}catch{return null}},m=(g,A={})=>{if(typeof window>"u")return E0;let C=new Set,V=A.reconnect!==!1,R=A.maxReconnectAttempts??15,c=A.reconnectMaxDelayMs??8000,U=A.pingInterval??30000,H=(L)=>P0(L,500,c),T={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:A.sessionId??N0(),ws:null},G=(L)=>{C.forEach((Z)=>Z(L))},$=()=>{if(T.pingInterval)clearInterval(T.pingInterval),T.pingInterval=null;if(T.reconnectTimeout)clearTimeout(T.reconnectTimeout),T.reconnectTimeout=null},w=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let L=T.pendingMessages.shift();if(L!==void 0)T.ws.send(L)}},I=()=>{T.reconnectAttempts+=1;let L=H(T.reconnectAttempts),Z=Date.now()+L;G({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:R,nextAttemptAt:Z,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(()=>{if(T.reconnectAttempts>R){G({reconnect:{attempts:T.reconnectAttempts,maxAttempts:R,status:"exhausted"},type:"connection"});return}X()},L)},X=()=>{let L=new WebSocket(q0(g,T.sessionId,T.scenarioId,A.query??{}));L.binaryType="arraybuffer",L.onopen=()=>{let Z=T.reconnectAttempts>0;if(T.isConnected=!0,w(),Z)G({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:R,status:"resumed"},type:"connection"}),T.reconnectAttempts=0;C.forEach((K)=>K({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(L.readyState===1)L.send(JSON.stringify({type:"ping"}))},U)},L.onmessage=(Z)=>{let K=b0(Z);if(!K)return;if(K.type==="session")T.sessionId=K.sessionId,T.scenarioId=K.scenarioId??T.scenarioId;C.forEach((T0)=>T0(K))},L.onclose=(Z)=>{if(T.isConnected=!1,$(),V&&Z.code!==1000&&T.reconnectAttempts<R)I();else if(V&&Z.code!==1000)G({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:R,status:"exhausted"},type:"connection"})},T.ws=L},h=(L)=>{if(T.ws?.readyState===1){T.ws.send(L);return}T.pendingMessages.push(L)},W=(L)=>{h(JSON.stringify(L))},O=(L={})=>{if(L.sessionId)T.sessionId=L.sessionId;if(L.scenarioId)T.scenarioId=L.scenarioId;W({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},J=(L)=>{h(L)},Y=()=>{W({type:"end_turn"})},N=(L)=>{W({...L,type:"call_control"})},q=(L="client-close")=>{if($(),T.ws){if(T.ws.readyState===1)T.ws.send(JSON.stringify({reason:L,type:"close"}));T.ws.close(1000),T.ws=null}T.isConnected=!1,C.clear()},_=()=>{if($(),T.ws)T.ws.close(1000),T.ws=null;T.isConnected=!1,C.clear()},D=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},z=(L)=>{return C.add(L),()=>{C.delete(L)}};return X(),{callControl:N,close:q,disconnect:_,endTurn:Y,send:W,sendAudio:J,simulateDisconnect:D,start:O,subscribe:z,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var j0=()=>({attempts:0,maxAttempts:0,status:"idle"}),B0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},M0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},x0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:j0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),r=()=>{let g=x0(),A="",C=new Set,V=()=>{C.forEach((c)=>c())};return{dispatch:(c)=>{switch(c.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:c.chunk,format:c.format,receivedAt:c.receivedAt,turnId:c.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,c.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${c.delta}`};break;case"complete":g={...g,sessionId:c.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:c.event.type==="end"?c.event.disposition:g.call?.disposition,endedAt:c.event.type==="end"?c.event.at:g.call?.endedAt,events:[...g.call?.events??[],c.event],lastEventAt:c.event.at,startedAt:g.call?.startedAt??c.event.at},sessionId:c.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:c.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:c.message};break;case"final":A=B0(A,c.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:M0(A,c.transcript.text)};break;case"replay":A=c.partial,g={...g,assistantStreamingText:"",assistantTexts:[...c.assistantTexts],call:c.call??null,error:null,isConnected:c.status==="active",partial:c.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:c.scenarioId??g.scenarioId,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata??g.sessionMetadata,status:c.status,turns:[...c.turns]};break;case"session":g={...g,error:null,scenarioId:c.scenarioId??g.scenarioId,isConnected:c.status==="active",sessionId:c.sessionId,sessionMetadata:c.sessionMetadata??g.sessionMetadata,status:c.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,c.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(c)=>{return C.add(c),()=>{C.delete(c)}}}};var u=(g,A={})=>{let C=m(g,A),V=r(),R=A.browserMedia&&typeof window<"u"?o({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,c=new Set,U=($)=>Promise.resolve().then(()=>{if(!$?.sessionId&&!$?.scenarioId)return;C.start($),R?.start()}),H=()=>{c.forEach(($)=>$())},T=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let $=V.getSnapshot(),w=JSON.stringify({at:Date.now(),reconnect:$.reconnect,scenarioId:$.scenarioId,sessionId:C.getSessionId(),turnIds:$.turns.map((I)=>I.id)});fetch(A.reconnectReportPath,{body:w,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},G=C.subscribe(($)=>{let w=d($);if(w){if(V.dispatch(w),$.type==="connection")T();H()}});return{start:U,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl($){C.callControl($)},close($){G(),R?.close(),C.close($),V.dispatch({type:"disconnected"}),H()},disconnect(){G(),R?.close(),C.disconnect(),V.dispatch({type:"disconnected"}),H()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio($){C.sendAudio($)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe($){return c.add($),()=>{c.delete($)}},get turns(){return V.getSnapshot().turns}}};var s=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var k0={balanced:{qualityProfile:"general",minSilenceMs:400,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",minSilenceMs:300,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",minSilenceMs:600,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},F0={"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},general:{},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var a=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=k0[A],R=F0[C],c=g?.silenceMs??R.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(c,g?.minSilenceMs??R.minSilenceMs??V.minSilenceMs),silenceMs:c,speechThreshold:g?.speechThreshold??R.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??R.transcriptStabilityMs??V.transcriptStabilityMs}};var f0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},p=(g="default")=>{let A=f0[g];return{audioConditioning:s(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:a(A.turnDetection)}};var d0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),t=(g,A={})=>{let C=p(A.preset),V=u(g,{...C.connection,...A.connection}),R=null,c=d0(V),U=new Set,H=()=>{for(let W of U)W()},T=()=>{if(c={...c,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&c.status==="completed"&&c.isRecording)R?.stop(),R=null,c={...c,isRecording:!1};H()},G=V.subscribe(T);T();let $=()=>{if(R)return R;return R=f({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(W)=>{if(A.capture?.onAudio){A.capture.onAudio(W,V.sendAudio);return}V.sendAudio(W)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),R},w=()=>{R?.stop(),R=null,c={...c,isRecording:!1},H()},I=async()=>{if(c.isRecording)return;try{c={...c,recordingError:null},H(),await $().start(),c={...c,isRecording:!0},H()}catch(W){throw R?.stop(),R=null,c={...c,isRecording:!1,recordingError:W instanceof Error?W.message:String(W)},H(),W}};return{close:(W)=>{G(),w(),V.close(W)},disconnect:()=>{G(),w(),V.disconnect()},startRecording:I,stopRecording:w,get assistantAudio(){return c.assistantAudio},get assistantTexts(){return c.assistantTexts},get assistantStreamingText(){return c.assistantStreamingText},bindHTMX(W){return F(V,W)},get call(){return c.call},callControl:(W)=>V.callControl(W),endTurn:()=>V.endTurn(),get error(){return c.error},getServerSnapshot:()=>c,getSnapshot:()=>c,get isConnected(){return c.isConnected},get isRecording(){return c.isRecording},get partial(){return c.partial},get reconnect(){return c.reconnect},get recordingError(){return c.recordingError},get scenarioId(){return c.scenarioId},sendAudio:(W)=>V.sendAudio(W),get sessionId(){return c.sessionId},get sessionMetadata(){return c.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return c.status},subscribe:(W)=>{return U.add(W),()=>{U.delete(W)}},toggleRecording:async()=>{if(c.isRecording){w();return}await I()},get turns(){return c.turns}}};var P=(g)=>String(g).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");var e=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var l0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},v0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},i0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},g0=(g)=>{let A={...l0,...g.theme},C={...v0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,R=g.state.turns.at(-1)?.committedAt,c=e({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:R}),U=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,H=g.state.error?"Error":U?C.connecting:g.state.status==="completed"?C.callEnded:i0(c,C);return{agentState:c,classes:{container:`absolute-voice-widget absolute-voice-widget--${c}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:H,theme:A,title:g.title??"Voice"}},n0=(g)=>typeof g==="number"?`${g}px`:g,A0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${n0(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,R=[];if(g.controls.canStart)R.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${P(g.labels.startCall)}</button>`);if(g.controls.canMute)R.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${P(g.labels.mute)}</button>`);if(g.controls.canEnd)R.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${P(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${P(g.classes.container)}" style="${C}">
2
2
  <div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
3
- <span aria-hidden="true" class="${Q(g.classes.dot)}" style="${V}"></span>
4
- <strong style="font-size:15px;">${Q(g.title)}</strong>
5
- <span style="font-size:13px;margin-left:auto;opacity:0.7;">${Q(g.statusLabel)}</span>
3
+ <span aria-hidden="true" class="${P(g.classes.dot)}" style="${V}"></span>
4
+ <strong style="font-size:15px;">${P(g.title)}</strong>
5
+ <span style="font-size:13px;margin-left:auto;opacity:0.7;">${P(g.statusLabel)}</span>
6
6
  </div>
7
- ${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${Q(g.partial)}”</p>`:""}
7
+ ${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${P(g.partial)}”</p>`:""}
8
8
  <div style="display:flex;gap:10px;">${R.join("")}</div>
9
- ${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${Q(g.errorMessage)}</p>`:""}
10
- </div>`};var n0=(g)=>{if(typeof g!=="string")return g;let A=document.querySelector(g);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${g}"`);return A},A0=(g,A={})=>{let C=n0(g),V=p(A.path??"/voice",A.controllerOptions),R=null,T=null,L=()=>{let c=e({...A.labels!==void 0?{labels:A.labels}:{},state:{assistantAudio:V.assistantAudio,error:V.error,isConnected:V.isConnected,isRecording:V.isRecording,partial:V.partial,status:V.status,turns:V.turns},...A.theme!==void 0?{theme:A.theme}:{},...A.title!==void 0?{title:A.title}:{}});C.innerHTML=g0(c);for(let W of C.querySelectorAll("button[data-action]")){let{action:I}=W.dataset;W.addEventListener("click",()=>{if(I==="start")V.startRecording();else if(I==="mute")V.stopRecording();else if(I==="end")V.close()})}if(V.error&&V.error!==R)R=V.error,A.onError?.(V.error);if(V.status!==T)T=V.status,A.onStatusChange?.(V.status)},H=V.subscribe(L);if(L(),A.autoStart)V.startRecording();return{controller:V,async end(){await V.close()},mute(){V.stopRecording()},async start(){await V.startRecording()},unmount(){H(),V.close(),C.innerHTML=""}}},V0="0.0.22-beta.516",C0={mount:A0,version:V0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=C0;var m0=C0;})();
9
+ ${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${P(g.errorMessage)}</p>`:""}
10
+ </div>`};var o0=(g)=>{if(typeof g!=="string")return g;let A=document.querySelector(g);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${g}"`);return A},V0=(g,A={})=>{let C=o0(g),V=t(A.path??"/voice",A.controllerOptions),R=null,c=null,U=()=>{let T=g0({...A.labels!==void 0?{labels:A.labels}:{},state:{assistantAudio:V.assistantAudio,error:V.error,isConnected:V.isConnected,isRecording:V.isRecording,partial:V.partial,status:V.status,turns:V.turns},...A.theme!==void 0?{theme:A.theme}:{},...A.title!==void 0?{title:A.title}:{}});C.innerHTML=A0(T);for(let G of C.querySelectorAll("button[data-action]")){let{action:$}=G.dataset;G.addEventListener("click",()=>{if($==="start")V.startRecording();else if($==="mute")V.stopRecording();else if($==="end")V.close()})}if(V.error&&V.error!==R)R=V.error,A.onError?.(V.error);if(V.status!==c)c=V.status,A.onStatusChange?.(V.status)},H=V.subscribe(U);if(U(),A.autoStart)V.startRecording();return{controller:V,async end(){await V.close()},mute(){V.stopRecording()},async start(){await V.startRecording()},unmount(){H(),V.close(),C.innerHTML=""}}},C0="0.0.22-beta.516",c0={mount:V0,version:C0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=c0;var r0=c0;})();
package/dist/index.js CHANGED
@@ -5054,7 +5054,7 @@ var createVoiceSession = (options) => {
5054
5054
  clearSilenceTimer();
5055
5055
  let didFail = false;
5056
5056
  const session = await writeSession((currentSession) => {
5057
- if (currentSession.status === "failed") {
5057
+ if (currentSession.status === "completed" || currentSession.status === "failed") {
5058
5058
  return;
5059
5059
  }
5060
5060
  didFail = true;
@@ -39954,6 +39954,7 @@ var resolveSessionId3 = (runtime, ws) => {
39954
39954
  runtime.socketSessions.set(ws, resolved);
39955
39955
  return resolved;
39956
39956
  };
39957
+ var resolveSocketQuery = (ws) => ws.data && typeof ws.data === "object" && ("query" in ws.data) ? ws.data.query ?? {} : {};
39957
39958
  var resolveMaybeFunction = async (value, input) => typeof value === "function" ? await value(input) : value;
39958
39959
  var toAudioChunk = (raw) => {
39959
39960
  if (raw instanceof ArrayBuffer) {
@@ -40126,6 +40127,31 @@ var voice = (config) => {
40126
40127
  profileSwitchGuardedSessions: new Set,
40127
40128
  socketSessions: new WeakMap
40128
40129
  };
40130
+ const authorizedSockets = new WeakSet;
40131
+ const authorizeSocket = async (ws, sessionId, scenarioId) => {
40132
+ if (!config.authorizeConnection) {
40133
+ authorizedSockets.add(ws);
40134
+ return true;
40135
+ }
40136
+ let authorized = false;
40137
+ try {
40138
+ authorized = await config.authorizeConnection({
40139
+ context: ws.data,
40140
+ path: config.path,
40141
+ query: resolveSocketQuery(ws),
40142
+ scenarioId,
40143
+ sessionId
40144
+ });
40145
+ } catch (error) {
40146
+ runtime.logger.warn?.(`[voice] connection authorization failed for session "${sessionId}": ${error instanceof Error ? error.message : String(error)}`);
40147
+ }
40148
+ if (!authorized) {
40149
+ ws.close(4401, "unauthorized");
40150
+ return false;
40151
+ }
40152
+ authorizedSockets.add(ws);
40153
+ return true;
40154
+ };
40129
40155
  const { monitor } = config;
40130
40156
  const registerMonitorSession = (sessionId, handle) => {
40131
40157
  if (!monitor)
@@ -40451,6 +40477,10 @@ var voice = (config) => {
40451
40477
  }
40452
40478
  },
40453
40479
  message: async (ws, raw) => {
40480
+ if (!authorizedSockets.has(ws)) {
40481
+ ws.close(4401, "unauthorized");
40482
+ return;
40483
+ }
40454
40484
  const sessionState = resolveSessionId3(runtime, ws);
40455
40485
  const current = runtime.activeSessions.get(sessionState.sessionId);
40456
40486
  const message = parseClientMessage(raw);
@@ -40540,6 +40570,9 @@ var voice = (config) => {
40540
40570
  },
40541
40571
  open: async (ws) => {
40542
40572
  const sessionState = resolveSessionId3(runtime, ws);
40573
+ if (!await authorizeSocket(ws, sessionState.sessionId, sessionState.scenarioId ?? undefined)) {
40574
+ return;
40575
+ }
40543
40576
  const existing = runtime.activeSessions.get(sessionState.sessionId);
40544
40577
  if (existing) {
40545
40578
  await existing.close("superseded");
@@ -11444,6 +11444,7 @@ var noopUnsubscribe = () => noop;
11444
11444
  var NOOP_CONNECTION = {
11445
11445
  callControl: noop,
11446
11446
  close: noop,
11447
+ disconnect: noop,
11447
11448
  endTurn: noop,
11448
11449
  send: noop,
11449
11450
  sendAudio: noop,
@@ -11455,7 +11456,7 @@ var NOOP_CONNECTION = {
11455
11456
  start: () => {}
11456
11457
  };
11457
11458
  var createSessionId = () => crypto.randomUUID();
11458
- var buildWsUrl = (path, sessionId, scenarioId) => {
11459
+ var buildWsUrl = (path, sessionId, scenarioId, query) => {
11459
11460
  const { hostname, port, protocol } = window.location;
11460
11461
  const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
11461
11462
  const portSuffix = port ? `:${port}` : "";
@@ -11464,6 +11465,11 @@ var buildWsUrl = (path, sessionId, scenarioId) => {
11464
11465
  if (scenarioId) {
11465
11466
  url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
11466
11467
  }
11468
+ for (const [key, value] of Object.entries(query)) {
11469
+ if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
11470
+ url.searchParams.set(key, value);
11471
+ }
11472
+ }
11467
11473
  return url.toString();
11468
11474
  };
11469
11475
  var isVoiceServerMessage = (value) => {
@@ -11573,7 +11579,7 @@ var createVoiceConnection = (path, options = {}) => {
11573
11579
  }, delayMs);
11574
11580
  };
11575
11581
  const connect = () => {
11576
- const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
11582
+ const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
11577
11583
  ws.binaryType = "arraybuffer";
11578
11584
  ws.onopen = () => {
11579
11585
  const wasReconnecting = state.reconnectAttempts > 0;
@@ -11669,7 +11675,19 @@ var createVoiceConnection = (path, options = {}) => {
11669
11675
  type: "call_control"
11670
11676
  });
11671
11677
  };
11672
- const close = () => {
11678
+ const close = (reason = "client-close") => {
11679
+ clearTimers();
11680
+ if (state.ws) {
11681
+ if (state.ws.readyState === WS_OPEN) {
11682
+ state.ws.send(JSON.stringify({ reason, type: "close" }));
11683
+ }
11684
+ state.ws.close(WS_NORMAL_CLOSURE);
11685
+ state.ws = null;
11686
+ }
11687
+ state.isConnected = false;
11688
+ listeners.clear();
11689
+ };
11690
+ const disconnect = () => {
11673
11691
  clearTimers();
11674
11692
  if (state.ws) {
11675
11693
  state.ws.close(WS_NORMAL_CLOSURE);
@@ -11693,6 +11711,7 @@ var createVoiceConnection = (path, options = {}) => {
11693
11711
  return {
11694
11712
  callControl,
11695
11713
  close,
11714
+ disconnect,
11696
11715
  endTurn,
11697
11716
  send,
11698
11717
  sendAudio,
@@ -11973,10 +11992,17 @@ var createVoiceStream = (path, options = {}) => {
11973
11992
  callControl(message) {
11974
11993
  connection.callControl(message);
11975
11994
  },
11976
- close() {
11995
+ close(reason) {
11977
11996
  unsubscribeConnection();
11978
11997
  browserMediaReporter?.close();
11979
- connection.close();
11998
+ connection.close(reason);
11999
+ store.dispatch({ type: "disconnected" });
12000
+ notify();
12001
+ },
12002
+ disconnect() {
12003
+ unsubscribeConnection();
12004
+ browserMediaReporter?.close();
12005
+ connection.disconnect();
11980
12006
  store.dispatch({ type: "disconnected" });
11981
12007
  notify();
11982
12008
  },
@@ -12179,29 +12205,34 @@ var createMicrophoneCapture = (options) => {
12179
12205
  if (!AudioContextCtor) {
12180
12206
  throw new Error("Browser microphone capture requires AudioContext support.");
12181
12207
  }
12182
- mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
12183
- audio: {
12184
- autoGainControl: true,
12185
- channelCount: options.channelCount ?? 1,
12186
- echoCancellation: true,
12187
- noiseSuppression: true
12208
+ try {
12209
+ mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
12210
+ audio: {
12211
+ autoGainControl: true,
12212
+ channelCount: options.channelCount ?? 1,
12213
+ echoCancellation: true,
12214
+ noiseSuppression: true
12215
+ }
12216
+ });
12217
+ audioContext = new AudioContextCtor;
12218
+ if (audioContext.state === "suspended") {
12219
+ await audioContext.resume();
12188
12220
  }
12189
- });
12190
- audioContext = new AudioContextCtor;
12191
- if (audioContext.state === "suspended") {
12192
- await audioContext.resume();
12193
- }
12194
- sourceNode = audioContext.createMediaStreamSource(mediaStream);
12195
- processorNode = audioContext.createScriptProcessor(4096, 1, 1);
12196
- processorNode.onaudioprocess = (event) => {
12197
- const channel = event.inputBuffer.getChannelData(0);
12198
- const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
12199
- const pcm = floatTo16BitPCM(downsampled);
12200
- options.onLevel?.(getPcmLevel(pcm));
12201
- options.onAudio(pcm);
12202
- };
12203
- sourceNode.connect(processorNode);
12204
- processorNode.connect(audioContext.destination);
12221
+ sourceNode = audioContext.createMediaStreamSource(mediaStream);
12222
+ processorNode = audioContext.createScriptProcessor(4096, 1, 1);
12223
+ processorNode.onaudioprocess = (event) => {
12224
+ const channel = event.inputBuffer.getChannelData(0);
12225
+ const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
12226
+ const pcm = floatTo16BitPCM(downsampled);
12227
+ options.onLevel?.(getPcmLevel(pcm));
12228
+ options.onAudio(pcm);
12229
+ };
12230
+ sourceNode.connect(processorNode);
12231
+ processorNode.connect(audioContext.destination);
12232
+ } catch (error) {
12233
+ stop();
12234
+ throw error;
12235
+ }
12205
12236
  };
12206
12237
  const stop = () => {
12207
12238
  processorNode?.disconnect();
@@ -12644,6 +12675,7 @@ var createVoiceController = (path, options = {}) => {
12644
12675
  };
12645
12676
  notify();
12646
12677
  } catch (error) {
12678
+ capture?.stop();
12647
12679
  capture = null;
12648
12680
  state = {
12649
12681
  ...state,
@@ -12654,13 +12686,19 @@ var createVoiceController = (path, options = {}) => {
12654
12686
  throw error;
12655
12687
  }
12656
12688
  };
12657
- const close = () => {
12689
+ const close = (reason) => {
12690
+ unsubscribeStream();
12691
+ stopRecording();
12692
+ stream.close(reason);
12693
+ };
12694
+ const disconnect = () => {
12658
12695
  unsubscribeStream();
12659
12696
  stopRecording();
12660
- stream.close();
12697
+ stream.disconnect();
12661
12698
  };
12662
12699
  return {
12663
12700
  close,
12701
+ disconnect,
12664
12702
  startRecording,
12665
12703
  stopRecording,
12666
12704
  get assistantAudio() {