@absolutejs/voice 0.0.22-beta.572 → 0.0.22-beta.574

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.
@@ -345,10 +345,16 @@ var createMicrophoneCapture = (options) => {
345
345
  }
346
346
  mediaStream = await navigator.mediaDevices.getUserMedia({
347
347
  audio: {
348
- channelCount: options.channelCount ?? 1
348
+ autoGainControl: true,
349
+ channelCount: options.channelCount ?? 1,
350
+ echoCancellation: true,
351
+ noiseSuppression: true
349
352
  }
350
353
  });
351
354
  audioContext = new AudioContextCtor;
355
+ if (audioContext.state === "suspended") {
356
+ await audioContext.resume();
357
+ }
352
358
  sourceNode = audioContext.createMediaStreamSource(mediaStream);
353
359
  processorNode = audioContext.createScriptProcessor(4096, 1, 1);
354
360
  processorNode.onaudioprocess = (event) => {
@@ -113,10 +113,16 @@ var createMicrophoneCapture = (options) => {
113
113
  }
114
114
  mediaStream = await navigator.mediaDevices.getUserMedia({
115
115
  audio: {
116
- channelCount: options.channelCount ?? 1
116
+ autoGainControl: true,
117
+ channelCount: options.channelCount ?? 1,
118
+ echoCancellation: true,
119
+ noiseSuppression: true
117
120
  }
118
121
  });
119
122
  audioContext = new AudioContextCtor;
123
+ if (audioContext.state === "suspended") {
124
+ await audioContext.resume();
125
+ }
120
126
  sourceNode = audioContext.createMediaStreamSource(mediaStream);
121
127
  processorNode = audioContext.createScriptProcessor(4096, 1, 1);
122
128
  processorNode.onaudioprocess = (event) => {
@@ -1333,10 +1333,16 @@ var createMicrophoneCapture = (options) => {
1333
1333
  }
1334
1334
  mediaStream = await navigator.mediaDevices.getUserMedia({
1335
1335
  audio: {
1336
- channelCount: options.channelCount ?? 1
1336
+ autoGainControl: true,
1337
+ channelCount: options.channelCount ?? 1,
1338
+ echoCancellation: true,
1339
+ noiseSuppression: true
1337
1340
  }
1338
1341
  });
1339
1342
  audioContext = new AudioContextCtor;
1343
+ if (audioContext.state === "suspended") {
1344
+ await audioContext.resume();
1345
+ }
1340
1346
  sourceNode = audioContext.createMediaStreamSource(mediaStream);
1341
1347
  processorNode = audioContext.createScriptProcessor(4096, 1, 1);
1342
1348
  processorNode.onaudioprocess = (event) => {
@@ -110,10 +110,16 @@ var createMicrophoneCapture = (options) => {
110
110
  }
111
111
  mediaStream = await navigator.mediaDevices.getUserMedia({
112
112
  audio: {
113
- channelCount: options.channelCount ?? 1
113
+ autoGainControl: true,
114
+ channelCount: options.channelCount ?? 1,
115
+ echoCancellation: true,
116
+ noiseSuppression: true
114
117
  }
115
118
  });
116
119
  audioContext = new AudioContextCtor;
120
+ if (audioContext.state === "suspended") {
121
+ await audioContext.resume();
122
+ }
117
123
  sourceNode = audioContext.createMediaStreamSource(mediaStream);
118
124
  processorNode = audioContext.createScriptProcessor(4096, 1, 1);
119
125
  processorNode.onaudioprocess = (event) => {
@@ -1,10 +1,10 @@
1
- (()=>{var{defineProperty:H,getOwnPropertyNames:oc,getOwnPropertyDescriptor:lc}=Object,Ac=Object.prototype.hasOwnProperty;function ic(c){return this[c]}var Cc=(c)=>{var n=(Y??=new WeakMap).get(c),o;if(n)return n;if(n=H({},"__esModule",{value:!0}),c&&typeof c==="object"||typeof c==="function"){for(var g of oc(c))if(!Ac.call(n,g))H(n,g,{get:ic.bind(c,g),enumerable:!(o=lc(c,g))||o.enumerable})}return Y.set(c,n),n},Y;var Tc=(c)=>c;function yc(c,n){this[c]=Tc.bind(null,n)}var Vc=(c,n)=>{for(var o in n)H(c,o,{get:n[o],enumerable:!0,configurable:!0,set:yc.bind(n,o)})};var ec={};Vc(ec,{mount:()=>p,default:()=>Bc,VOICE_EMBED_VERSION:()=>cc});var Ic=(c)=>{if(typeof c!=="string")return c;return document.querySelector(c)},Sc=(c,n,o,g)=>{let l=n??c.getAttribute("hx-get")??"";if(!l)return"";let C=new URL(l,window.location.origin);if(g)C.searchParams.set(o,g);else C.searchParams.delete(o);return`${C.pathname}${C.search}${C.hash}`},Q=(c,n)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let o=Ic(n.element);if(!o)return()=>{};let g=n.eventName??"voice-refresh",l=n.sessionQueryParam??"sessionId",C=()=>{let V=window,I=Sc(o,n.route,l,c.sessionId);if(I)o.setAttribute("hx-get",I);V.htmx?.process?.(o),V.htmx?.trigger?.(o,g)},i=c.subscribe(C);return C(),()=>{i()}};var dc=(c)=>Math.max(-1,Math.min(1,c)),Rc=(c)=>{let n=new Int16Array(c.length);for(let o=0;o<c.length;o+=1){let g=dc(c[o]??0);n[o]=g<0?g*32768:g*32767}return new Uint8Array(n.buffer)},hc=(c)=>{let n=c instanceof Uint8Array?c:new Uint8Array(c);if(n.byteLength<2)return 0;let o=new Int16Array(n.buffer,n.byteOffset,Math.floor(n.byteLength/2));if(o.length===0)return 0;let g=0;for(let l of o){let C=l/32768;g+=C*C}return Math.min(1,Math.max(0,Math.sqrt(g/o.length)*5.5))},_c=(c,n,o)=>{if(n===o)return c;let g=n/o,l=Math.round(c.length/g),C=new Float32Array(l),i=0,V=0;while(i<C.length){let I=Math.round((i+1)*g),R=0,y=0;for(let d=V;d<I&&d<c.length;d+=1)R+=c[d]??0,y+=1;C[i]=y>0?R/y:0,i+=1,V=I}return C},J=(c)=>{let n=null,o=null,g=null,l=null;return{start:async()=>{if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let V=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!V)throw Error("Browser microphone capture requires AudioContext support.");l=await navigator.mediaDevices.getUserMedia({audio:{channelCount:c.channelCount??1}}),n=new V,o=n.createMediaStreamSource(l),g=n.createScriptProcessor(4096,1,1),g.onaudioprocess=(I)=>{let R=I.inputBuffer.getChannelData(0),y=_c(R,n?.sampleRate??48000,c.sampleRateHz??16000),d=Rc(y);c.onLevel?.(hc(d)),c.onAudio(d)},o.connect(g),g.connect(n.destination)},stop:()=>{g?.disconnect(),o?.disconnect(),l?.getTracks().forEach((V)=>V.stop()),n?.close(),c.onLevel?.(0),n=null,l=null,g=null,o=null}}};var G=(c)=>{if(typeof c==="string"&&c.trim())return c;if(c instanceof Error&&c.message.trim())return c.message;if(c&&typeof c==="object"){let n=c;for(let o of["message","reason","description"]){let g=n[o];if(typeof g==="string"&&g.trim())return g}if("error"in n)return G(n.error);if("cause"in n)return G(n.cause);try{return JSON.stringify(c)}catch{}}return"Unexpected error"},q=(c)=>{switch(c.type){case"audio":return{chunk:Uint8Array.from(atob(c.chunkBase64),(n)=>n.charCodeAt(0)),format:c.format,receivedAt:c.receivedAt,turnId:c.turnId,type:"audio"};case"assistant":return{text:c.text,turnId:c.turnId,type:"assistant"};case"assistant_delta":return{delta:c.delta,turnId:c.turnId,type:"assistant_delta"};case"complete":return{sessionId:c.sessionId,type:"complete"};case"connection":return{reconnect:c.reconnect,type:"connection"};case"call_lifecycle":return{event:c.event,sessionId:c.sessionId,type:"call_lifecycle"};case"error":return{message:G(c.message),type:"error"};case"final":return{transcript:c.transcript,type:"final"};case"partial":return{transcript:c.transcript,type:"partial"};case"replay":return{assistantTexts:c.assistantTexts,call:c.call,partial:c.partial,scenarioId:c.scenarioId,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,status:c.status,turns:c.turns,type:"replay"};case"session":return{sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,scenarioId:c.scenarioId,status:c.status,type:"session"};case"turn":return{turn:c.turn,type:"turn"};default:return null}};var kc=Math.PI*2;var x=(c,n,o,g)=>{c.push({code:o,message:g,severity:n})};var sc=(c)=>c.length===0?void 0:c.reduce((n,o)=>n+o,0)/c.length,N=(c)=>c.length===0?void 0:Math.max(...c);var _=(c,n)=>{let o=c[n];return typeof o==="number"&&Number.isFinite(o)?o:void 0},$=(c,n)=>{let o=c[n];return typeof o==="boolean"?o:void 0},s=(c,n)=>{let o=c[n];return typeof o==="string"?o:void 0},X=(c)=>String(c.id??s(c,"ssrc")??_(c,"ssrc")??s(c,"trackIdentifier")??s(c,"mid")??"unknown"),Z=(c)=>c===void 0?void 0:c*1000;var wc=(c)=>{let n={};for(let[o,g]of Object.entries(c))if(g===null||typeof g==="boolean"||typeof g==="number"||typeof g==="string")n[o]=g;return n};var z=(c={})=>{let n=c.stats??[],o=[],g=n.filter((A)=>A.type==="inbound-rtp"&&s(A,"kind")!=="video"),l=n.filter((A)=>A.type==="outbound-rtp"&&s(A,"kind")!=="video"),C=n.filter((A)=>A.type==="candidate-pair"),i=n.filter((A)=>(A.type==="track"||A.type==="media-source")&&s(A,"kind")==="audio"),V=C.filter((A)=>$(A,"selected")===!0||$(A,"nominated")===!0||s(A,"state")==="succeeded").length,I=i.filter((A)=>s(A,"readyState")!=="ended"&&s(A,"trackState")!=="ended"&&$(A,"ended")!==!0).length,R=i.filter((A)=>s(A,"readyState")==="ended"||s(A,"trackState")==="ended"||$(A,"ended")===!0).length,y=g.reduce((A,h)=>A+(_(h,"packetsReceived")??0),0),d=l.reduce((A,h)=>A+(_(h,"packetsSent")??0),0),T=[...g,...l].reduce((A,h)=>A+Math.max(0,_(h,"packetsLost")??0),0),w=y+T,S=w===0?0:T/w,E=g.reduce((A,h)=>A+(_(h,"bytesReceived")??0),0),b=l.reduce((A,h)=>A+(_(h,"bytesSent")??0),0),U=N(C.map((A)=>Z(_(A,"currentRoundTripTime")??_(A,"roundTripTime"))).filter((A)=>A!==void 0)),M=N([...g,...l].map((A)=>Z(_(A,"jitter"))).filter((A)=>A!==void 0)),W=N(g.map((A)=>{let h=_(A,"jitterBufferDelay"),L=_(A,"jitterBufferEmittedCount");return h!==void 0&&L!==void 0&&L>0?h/L*1000:void 0}).filter((A)=>A!==void 0)),P=i.map((A)=>_(A,"audioLevel")).filter((A)=>A!==void 0);if(c.requireConnectedCandidatePair&&C.length>0&&V===0)x(o,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(c.requireLiveAudioTrack&&I===0)x(o,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(c.maxPacketLossRatio!==void 0&&S>c.maxPacketLossRatio)x(o,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(S)} above ${String(c.maxPacketLossRatio)}.`);if(c.maxRoundTripTimeMs!==void 0&&U!==void 0&&U>c.maxRoundTripTimeMs)x(o,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(U)}ms above ${String(c.maxRoundTripTimeMs)}ms.`);if(c.maxJitterMs!==void 0&&M!==void 0&&M>c.maxJitterMs)x(o,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(M)}ms above ${String(c.maxJitterMs)}ms.`);return{activeCandidatePairs:V,audioLevelAverage:sc(P),bytesReceived:E,bytesSent:b,checkedAt:Date.now(),endedAudioTracks:R,inboundPackets:y,issues:o,jitterBufferDelayMs:W,jitterMs:M,liveAudioTracks:I,outboundPackets:d,packetLossRatio:S,packetsLost:T,roundTripTimeMs:U,status:o.some((A)=>A.severity==="error")?"fail":o.length>0?"warn":"pass",totalStats:n.length}},e=async(c)=>{return[...(await c.peerConnection.getStats(c.selector??null)).values()].map(wc)};var B=(c={})=>{let n=c.stats??[],o=c.previousStats??[],g=[],l=new Map(o.map((T)=>[X(T),T])),i=n.filter((T)=>(T.type==="inbound-rtp"||T.type==="outbound-rtp")&&s(T,"kind")!=="video"&&s(T,"mediaType")!=="video").map((T)=>{let w=T.type==="outbound-rtp"?"outbound":"inbound",S=w==="outbound"?"packetsSent":"packetsReceived",E=w==="outbound"?"bytesSent":"bytesReceived",b=l.get(X(T)),U=_(T,S),M=b?_(b,S):void 0,W=_(T,E),P=b?_(b,E):void 0,A=T.timestamp!==void 0&&b?.timestamp!==void 0?T.timestamp-b.timestamp:void 0;return{bytesDelta:W!==void 0&&P!==void 0?W-P:void 0,currentPackets:U,direction:w,id:X(T),packetDelta:U!==void 0&&M!==void 0?U-M:void 0,previousPackets:M,timeDeltaMs:A}}),V=i.filter((T)=>T.direction==="inbound"),I=i.filter((T)=>T.direction==="outbound"),R=N(i.map((T)=>T.timeDeltaMs).filter((T)=>T!==void 0)),y=V.filter((T)=>c.maxInboundPacketStallMs!==void 0&&T.timeDeltaMs!==void 0&&T.timeDeltaMs>=c.maxInboundPacketStallMs&&T.packetDelta!==void 0&&T.packetDelta<=0).length,d=I.filter((T)=>c.maxOutboundPacketStallMs!==void 0&&T.timeDeltaMs!==void 0&&T.timeDeltaMs>=c.maxOutboundPacketStallMs&&T.packetDelta!==void 0&&T.packetDelta<=0).length;if(c.requireInboundAudio&&V.length===0)x(g,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(c.requireOutboundAudio&&I.length===0)x(g,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(c.maxGapMs!==void 0&&R!==void 0&&R>c.maxGapMs)x(g,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(R)}ms above ${String(c.maxGapMs)}ms.`);if(y>0)x(g,"error","media.webrtc_inbound_stalled",`${String(y)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(d>0)x(g,"error","media.webrtc_outbound_stalled",`${String(d)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:V.length,issues:g,maxObservedGapMs:R,outboundAudioStreams:I.length,stalledInboundStreams:y,stalledOutboundStreams:d,status:g.some((T)=>T.severity==="error")?"fail":g.length>0?"warn":"pass",streams:i,totalStats:n.length}};var Lc="/api/voice/browser-media",bc=5000,Uc=async(c)=>c.peerConnection??await c.getPeerConnection?.()??null,Mc=async(c,n)=>{let o=n.fetch??globalThis.fetch;if(!o)return;await o(n.path??Lc,{body:JSON.stringify(c),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},K=(c)=>{let n=null,o=[],g=async()=>{let i=await Uc(c);if(!i)return;let V=await e({peerConnection:i}),I=z({...c,stats:V}),R=c.continuity===!1?void 0:B({...c.continuity,previousStats:o,stats:V}),y={at:Date.now(),continuity:R,report:I,scenarioId:c.getScenarioId?.()??null,sessionId:c.getSessionId?.()??null};return o=V,c.onReport?.(y),await Mc(y,c),y},l=()=>{g().catch((i)=>{c.onError?.(i)})},C=()=>{if(n)clearInterval(n),n=null};return{close:C,reportOnce:g,stop:C,start:()=>{if(n)return;l(),n=setInterval(l,c.intervalMs??bc)}}};var D=()=>{},xc=()=>D,Oc={callControl:D,close:D,endTurn:D,send:D,sendAudio:D,simulateDisconnect:D,subscribe:xc,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},Dc=()=>crypto.randomUUID(),Ec=(c,n,o)=>{let{hostname:g,port:l,protocol:C}=window.location,i=C==="https:"?"wss:":"ws:",V=l?`:${l}`:"",I=new URL(`${i}//${g}${V}${c}`);if(I.searchParams.set("sessionId",n),o)I.searchParams.set("scenarioId",o);return I.toString()},Wc=(c)=>{if(!c||typeof c!=="object"||!("type"in c))return!1;switch(c.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}},Pc=(c)=>{if(typeof c.data!=="string")return null;try{let n=JSON.parse(c.data);return Wc(n)?n:null}catch{return null}},j=(c,n={})=>{if(typeof window>"u")return Oc;let o=new Set,g=n.reconnect!==!1,l=n.maxReconnectAttempts??10,C=n.pingInterval??30000,i={isConnected:!1,pendingMessages:[],scenarioId:n.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:n.sessionId??Dc(),ws:null},V=(A)=>{o.forEach((h)=>h(A))},I=()=>{if(i.pingInterval)clearInterval(i.pingInterval),i.pingInterval=null;if(i.reconnectTimeout)clearTimeout(i.reconnectTimeout),i.reconnectTimeout=null},R=()=>{if(i.ws?.readyState!==1)return;while(i.pendingMessages.length>0){let A=i.pendingMessages.shift();if(A!==void 0)i.ws.send(A)}},y=()=>{let A=Date.now()+500;i.reconnectAttempts+=1,V({reconnect:{attempts:i.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:l,nextAttemptAt:A,status:"reconnecting"},type:"connection"}),i.reconnectTimeout=setTimeout(()=>{if(i.reconnectAttempts>l){V({reconnect:{attempts:i.reconnectAttempts,maxAttempts:l,status:"exhausted"},type:"connection"});return}d()},500)},d=()=>{let A=new WebSocket(Ec(c,i.sessionId,i.scenarioId));A.binaryType="arraybuffer",A.onopen=()=>{let h=i.reconnectAttempts>0;if(i.isConnected=!0,R(),h)V({reconnect:{attempts:i.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:l,status:"resumed"},type:"connection"}),i.reconnectAttempts=0;o.forEach((L)=>L({scenarioId:i.scenarioId??void 0,sessionId:i.sessionId,status:"active",type:"session"})),i.pingInterval=setInterval(()=>{if(A.readyState===1)A.send(JSON.stringify({type:"ping"}))},C)},A.onmessage=(h)=>{let L=Pc(h);if(!L)return;if(L.type==="session")i.sessionId=L.sessionId,i.scenarioId=L.scenarioId??i.scenarioId;o.forEach((gc)=>gc(L))},A.onclose=(h)=>{if(i.isConnected=!1,I(),g&&h.code!==1000&&i.reconnectAttempts<l)y();else if(g&&h.code!==1000)V({reconnect:{attempts:i.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:l,status:"exhausted"},type:"connection"})},i.ws=A},T=(A)=>{if(i.ws?.readyState===1){i.ws.send(A);return}i.pendingMessages.push(A)},w=(A)=>{T(JSON.stringify(A))},S=(A={})=>{if(A.sessionId)i.sessionId=A.sessionId;if(A.scenarioId)i.scenarioId=A.scenarioId;w({scenarioId:i.scenarioId??void 0,sessionId:i.sessionId,type:"start"})},E=(A)=>{T(A)},b=()=>{w({type:"end_turn"})},U=(A)=>{w({...A,type:"call_control"})},M=()=>{if(I(),i.ws)i.ws.close(1000),i.ws=null;i.isConnected=!1,o.clear()},W=()=>{if(i.ws?.readyState===1)i.ws.close(4000,"absolutejs-voice-reconnect-proof")},P=(A)=>{return o.add(A),()=>{o.delete(A)}};return d(),{callControl:U,close:M,endTurn:b,send:w,sendAudio:E,simulateDisconnect:W,start:S,subscribe:P,getReadyState:()=>i.ws?.readyState??3,getScenarioId:()=>i.scenarioId??"",getSessionId:()=>i.sessionId}};var $c=()=>({attempts:0,maxAttempts:0,status:"idle"}),Nc=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:$c(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),t=()=>{let c=Nc(),n=new Set,o=()=>{n.forEach((l)=>l())};return{dispatch:(l)=>{switch(l.type){case"audio":c={...c,assistantAudio:[...c.assistantAudio,{chunk:l.chunk,format:l.format,receivedAt:l.receivedAt,turnId:l.turnId}]};break;case"assistant":c={...c,assistantStreamingText:"",assistantTexts:[...c.assistantTexts,l.text]};break;case"assistant_delta":c={...c,assistantStreamingText:`${c.assistantStreamingText}${l.delta}`};break;case"complete":c={...c,sessionId:l.sessionId,status:"completed"};break;case"call_lifecycle":c={...c,call:{...c.call,disposition:l.event.type==="end"?l.event.disposition:c.call?.disposition,endedAt:l.event.type==="end"?l.event.at:c.call?.endedAt,events:[...c.call?.events??[],l.event],lastEventAt:l.event.at,startedAt:c.call?.startedAt??l.event.at},sessionId:l.sessionId};break;case"connected":c={...c,isConnected:!0,reconnect:c.reconnect.status==="reconnecting"?{...c.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:c.reconnect};break;case"connection":c={...c,reconnect:l.reconnect};break;case"disconnected":c={...c,isConnected:!1};break;case"error":c={...c,error:l.message};break;case"final":c={...c,partial:l.transcript.text,turns:c.turns.map((C)=>C)};break;case"partial":c={...c,partial:l.transcript.text};break;case"replay":c={...c,assistantStreamingText:"",assistantTexts:[...l.assistantTexts],call:l.call??null,error:null,isConnected:l.status==="active",partial:l.partial,reconnect:c.reconnect.status==="reconnecting"?{...c.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:c.reconnect,scenarioId:l.scenarioId??c.scenarioId,sessionId:l.sessionId,sessionMetadata:l.sessionMetadata??c.sessionMetadata,status:l.status,turns:[...l.turns]};break;case"session":c={...c,error:null,scenarioId:l.scenarioId??c.scenarioId,isConnected:l.status==="active",sessionId:l.sessionId,sessionMetadata:l.sessionMetadata??c.sessionMetadata,status:l.status};break;case"turn":c={...c,partial:"",turns:[...c.turns,l.turn]};break}o()},getServerSnapshot:()=>c,getSnapshot:()=>c,subscribe:(l)=>{return n.add(l),()=>{n.delete(l)}}}};var f=(c,n={})=>{let o=j(c,n),g=t(),l=n.browserMedia&&typeof window<"u"?K({...n.browserMedia,getScenarioId:()=>n.browserMedia?n.browserMedia.getScenarioId?.()??o.getScenarioId():o.getScenarioId(),getSessionId:()=>n.browserMedia?n.browserMedia.getSessionId?.()??o.getSessionId():o.getSessionId()}):null,C=new Set,i=(y)=>Promise.resolve().then(()=>{if(!y?.sessionId&&!y?.scenarioId)return;o.start(y),l?.start()}),V=()=>{C.forEach((y)=>y())},I=()=>{if(!n.reconnectReportPath||typeof fetch>"u")return;let y=g.getSnapshot(),d=JSON.stringify({at:Date.now(),reconnect:y.reconnect,scenarioId:y.scenarioId,sessionId:o.getSessionId(),turnIds:y.turns.map((T)=>T.id)});fetch(n.reconnectReportPath,{body:d,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},R=o.subscribe((y)=>{let d=q(y);if(d){if(g.dispatch(d),y.type==="connection")I();V()}});return{start:i,get assistantAudio(){return g.getSnapshot().assistantAudio},get assistantTexts(){return g.getSnapshot().assistantTexts},get assistantStreamingText(){return g.getSnapshot().assistantStreamingText},get call(){return g.getSnapshot().call},callControl(y){o.callControl(y)},close(){R(),l?.close(),o.close(),g.dispatch({type:"disconnected"}),V()},endTurn(){o.endTurn()},get error(){return g.getSnapshot().error},getServerSnapshot(){return g.getServerSnapshot()},getSnapshot(){return g.getSnapshot()},get isConnected(){return g.getSnapshot().isConnected},get partial(){return g.getSnapshot().partial},get reconnect(){return g.getSnapshot().reconnect},get scenarioId(){return g.getSnapshot().scenarioId},sendAudio(y){o.sendAudio(y)},get sessionId(){return o.getSessionId()},get sessionMetadata(){return g.getSnapshot().sessionMetadata},simulateDisconnect(){o.simulateDisconnect()},get status(){return g.getSnapshot().status},subscribe(y){return C.add(y),()=>{C.delete(y)}},get turns(){return g.getSnapshot().turns}}};var k=(c)=>{if(!c||c.enabled===!1)return;return{enabled:!0,maxGain:c.maxGain??3,noiseGateAttenuation:c.noiseGateAttenuation??0.15,noiseGateThreshold:c.noiseGateThreshold??0.006,targetLevel:c.targetLevel??0.08}};var Hc={balanced:{qualityProfile:"general",silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},Gc={"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 F=(c)=>{let n=c?.profile??"fast",o=c?.qualityProfile??"general",g=Hc[n],l=Gc[o];return{profile:n,qualityProfile:o,silenceMs:c?.silenceMs??l.silenceMs??g.silenceMs,speechThreshold:c?.speechThreshold??l.speechThreshold??g.speechThreshold,transcriptStabilityMs:c?.transcriptStabilityMs??l.transcriptStabilityMs??g.transcriptStabilityMs}};var Xc={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"}}},r=(c="default")=>{let n=Xc[c];return{audioConditioning:k(n.audioConditioning),capture:{channelCount:n.capture?.channelCount??1,sampleRateHz:n.capture?.sampleRateHz??16000},connection:{...n.connection},name:c,sttLifecycle:n.sttLifecycle??"continuous",turnDetection:F(n.turnDetection)}};var Yc=(c)=>({assistantAudio:[...c.assistantAudio],assistantStreamingText:c.assistantStreamingText,assistantTexts:[...c.assistantTexts],call:c.call,error:c.error,isConnected:c.isConnected,isRecording:!1,partial:c.partial,reconnect:c.reconnect,recordingError:null,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,scenarioId:c.scenarioId,status:c.status,turns:[...c.turns]}),a=(c,n={})=>{let o=r(n.preset),g=f(c,{...o.connection,...n.connection}),l=null,C=Yc(g),i=new Set,V=()=>{for(let S of i)S()},I=()=>{if(C={...C,assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,partial:g.partial,reconnect:g.reconnect,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]},n.autoStopOnComplete!==!1&&C.status==="completed"&&C.isRecording)l?.stop(),l=null,C={...C,isRecording:!1};V()},R=g.subscribe(I);I();let y=()=>{if(l)return l;return l=J({channelCount:n.capture?.channelCount??o.capture.channelCount,onLevel:n.capture?.onLevel,onAudio:(S)=>{if(n.capture?.onAudio){n.capture.onAudio(S,g.sendAudio);return}g.sendAudio(S)},sampleRateHz:n.capture?.sampleRateHz??o.capture.sampleRateHz}),l},d=()=>{l?.stop(),l=null,C={...C,isRecording:!1},V()},T=async()=>{if(C.isRecording)return;try{C={...C,recordingError:null},V(),await y().start(),C={...C,isRecording:!0},V()}catch(S){throw l=null,C={...C,isRecording:!1,recordingError:S instanceof Error?S.message:String(S)},V(),S}};return{close:()=>{R(),d(),g.close()},startRecording:T,stopRecording:d,get assistantAudio(){return C.assistantAudio},get assistantTexts(){return C.assistantTexts},get assistantStreamingText(){return C.assistantStreamingText},bindHTMX(S){return Q(g,S)},get call(){return C.call},callControl:(S)=>g.callControl(S),endTurn:()=>g.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:(S)=>g.sendAudio(S),get sessionId(){return C.sessionId},get sessionMetadata(){return C.sessionMetadata},simulateDisconnect:()=>g.simulateDisconnect(),get status(){return C.status},subscribe:(S)=>{return i.add(S),()=>{i.delete(S)}},toggleRecording:async()=>{if(C.isRecording){d();return}await T()},get turns(){return C.turns}}};var O=(c)=>String(c).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");var v=(c)=>{if(!c.isConnected)return"idle";if(c.isPlaying)return"speaking";if(c.isRecording&&c.hasActivePartial)return"listening";if(c.isRecording)return"listening";if(c.lastTranscriptAt&&!c.lastAssistantAt)return"thinking";if(c.lastTranscriptAt&&c.lastAssistantAt&&c.lastTranscriptAt>c.lastAssistantAt)return"thinking";return"idle"};var Qc={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},Jc={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},qc=(c,n)=>{switch(c){case"listening":return n.listening;case"speaking":return n.speaking;case"thinking":return n.thinking;case"idle":return n.idle}},u=(c)=>{let n={...Qc,...c.theme},o={...Jc,...c.labels},g=c.state.assistantAudio.at(-1)?.receivedAt,l=c.state.turns.at(-1)?.committedAt,C=v({hasActivePartial:c.state.partial.length>0,isConnected:c.state.isConnected,isPlaying:!1,isRecording:c.state.isRecording,lastAssistantAt:g,lastTranscriptAt:l}),i=!c.state.isConnected&&c.state.status!=="idle"&&!c.state.error,V=c.state.error?"Error":i?o.connecting:c.state.status==="completed"?o.callEnded:qc(C,o);return{agentState:C,classes:{container:`absolute-voice-widget absolute-voice-widget--${C}`,dot:`absolute-voice-widget__dot${c.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:c.state.isConnected,canMute:c.state.isRecording,canStart:!c.state.isRecording&&c.state.status!=="completed"},errorMessage:c.state.error??void 0,labels:o,partial:c.state.partial||void 0,statusLabel:V,theme:n,title:c.title??"Voice"}},Zc=(c)=>typeof c==="number"?`${c}px`:c,m=(c)=>{let n=c.theme,o=`background:${n.background};border-radius:${Zc(n.radius)};color:${n.foreground};font-family:${n.fontFamily};min-width:240px;padding:20px 22px;`,g=`background:${c.errorMessage?n.errorAccent:c.agentState==="idle"?"rgba(148,163,184,0.6)":n.accent};border-radius:50%;height:10px;width:10px;`,l=[];if(c.controls.canStart)l.push(`<button type="button" data-action="start" style="background:${n.accent};border:none;border-radius:12px;color:${n.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${O(c.labels.startCall)}</button>`);if(c.controls.canMute)l.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${n.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${O(c.labels.mute)}</button>`);if(c.controls.canEnd)l.push(`<button type="button" data-action="end" style="background:${n.errorAccent};border:none;border-radius:12px;color:${n.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${O(c.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${c.agentState}" class="${O(c.classes.container)}" style="${o}">
1
+ (()=>{var{defineProperty:X,getOwnPropertyNames:Ac,getOwnPropertyDescriptor:Tc}=Object,Cc=Object.prototype.hasOwnProperty;function Ic(c){return this[c]}var Vc=(c)=>{var g=(J??=new WeakMap).get(c),A;if(g)return g;if(g=X({},"__esModule",{value:!0}),c&&typeof c==="object"||typeof c==="function"){for(var n of Ac(c))if(!Cc.call(g,n))X(g,n,{get:Ic.bind(c,n),enumerable:!(A=Tc(c,n))||A.enumerable})}return J.set(c,g),g},J;var lc=(c)=>c;function Sc(c,g){this[c]=lc.bind(null,g)}var yc=(c,g)=>{for(var A in g)X(c,A,{get:g[A],enumerable:!0,configurable:!0,set:Sc.bind(g,A)})};var Bc={};yc(Bc,{mount:()=>p,default:()=>jc,VOICE_EMBED_VERSION:()=>cc});var _c=(c)=>{if(typeof c!=="string")return c;return document.querySelector(c)},Rc=(c,g,A,n)=>{let T=g??c.getAttribute("hx-get")??"";if(!T)return"";let V=new URL(T,window.location.origin);if(n)V.searchParams.set(A,n);else V.searchParams.delete(A);return`${V.pathname}${V.search}${V.hash}`},Z=(c,g)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let A=_c(g.element);if(!A)return()=>{};let n=g.eventName??"voice-refresh",T=g.sessionQueryParam??"sessionId",V=()=>{let y=window,_=Rc(A,g.route,T,c.sessionId);if(_)A.setAttribute("hx-get",_);y.htmx?.process?.(A),y.htmx?.trigger?.(A,n)},I=c.subscribe(V);return V(),()=>{I()}};var hc=(c)=>Math.max(-1,Math.min(1,c)),Lc=(c)=>{let g=new Int16Array(c.length);for(let A=0;A<c.length;A+=1){let n=hc(c[A]??0);g[A]=n<0?n*32768:n*32767}return new Uint8Array(g.buffer)},Uc=(c)=>{let g=c instanceof Uint8Array?c:new Uint8Array(c);if(g.byteLength<2)return 0;let A=new Int16Array(g.buffer,g.byteOffset,Math.floor(g.byteLength/2));if(A.length===0)return 0;let n=0;for(let T of A){let V=T/32768;n+=V*V}return Math.min(1,Math.max(0,Math.sqrt(n/A.length)*5.5))},wc=(c,g,A)=>{if(g===A)return c;let n=g/A,T=Math.round(c.length/n),V=new Float32Array(T),I=0,y=0;while(I<V.length){let _=Math.round((I+1)*n),L=0,S=0;for(let h=y;h<_&&h<c.length;h+=1)L+=c[h]??0,S+=1;V[I]=S>0?L/S:0,I+=1,y=_}return V},q=(c)=>{let g=null,A=null,n=null,T=null;return{start:async()=>{if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let y=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!y)throw Error("Browser microphone capture requires AudioContext support.");if(T=await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:c.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),g=new y,g.state==="suspended")await g.resume();A=g.createMediaStreamSource(T),n=g.createScriptProcessor(4096,1,1),n.onaudioprocess=(_)=>{let L=_.inputBuffer.getChannelData(0),S=wc(L,g?.sampleRate??48000,c.sampleRateHz??16000),h=Lc(S);c.onLevel?.(Uc(h)),c.onAudio(h)},A.connect(n),n.connect(g.destination)},stop:()=>{n?.disconnect(),A?.disconnect(),T?.getTracks().forEach((y)=>y.stop()),g?.close(),c.onLevel?.(0),g=null,T=null,n=null,A=null}}};var Y=(c)=>{if(typeof c==="string"&&c.trim())return c;if(c instanceof Error&&c.message.trim())return c.message;if(c&&typeof c==="object"){let g=c;for(let A of["message","reason","description"]){let n=g[A];if(typeof n==="string"&&n.trim())return n}if("error"in g)return Y(g.error);if("cause"in g)return Y(g.cause);try{return JSON.stringify(c)}catch{}}return"Unexpected error"},z=(c)=>{switch(c.type){case"audio":return{chunk:Uint8Array.from(atob(c.chunkBase64),(g)=>g.charCodeAt(0)),format:c.format,receivedAt:c.receivedAt,turnId:c.turnId,type:"audio"};case"assistant":return{text:c.text,turnId:c.turnId,type:"assistant"};case"assistant_delta":return{delta:c.delta,turnId:c.turnId,type:"assistant_delta"};case"complete":return{sessionId:c.sessionId,type:"complete"};case"connection":return{reconnect:c.reconnect,type:"connection"};case"call_lifecycle":return{event:c.event,sessionId:c.sessionId,type:"call_lifecycle"};case"error":return{message:Y(c.message),type:"error"};case"final":return{transcript:c.transcript,type:"final"};case"partial":return{transcript:c.transcript,type:"partial"};case"replay":return{assistantTexts:c.assistantTexts,call:c.call,partial:c.partial,scenarioId:c.scenarioId,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,status:c.status,turns:c.turns,type:"replay"};case"session":return{sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,scenarioId:c.scenarioId,status:c.status,type:"session"};case"turn":return{turn:c.turn,type:"turn"};default:return null}};var tc=Math.PI*2;var $=(c,g,A,n)=>{c.push({code:A,message:n,severity:g})};var Dc=(c)=>c.length===0?void 0:c.reduce((g,A)=>g+A,0)/c.length,G=(c)=>c.length===0?void 0:Math.max(...c);var w=(c,g)=>{let A=c[g];return typeof A==="number"&&Number.isFinite(A)?A:void 0},x=(c,g)=>{let A=c[g];return typeof A==="boolean"?A:void 0},D=(c,g)=>{let A=c[g];return typeof A==="string"?A:void 0},Q=(c)=>String(c.id??D(c,"ssrc")??w(c,"ssrc")??D(c,"trackIdentifier")??D(c,"mid")??"unknown"),K=(c)=>c===void 0?void 0:c*1000;var bc=(c)=>{let g={};for(let[A,n]of Object.entries(c))if(n===null||typeof n==="boolean"||typeof n==="number"||typeof n==="string")g[A]=n;return g};var o=(c={})=>{let g=c.stats??[],A=[],n=g.filter((C)=>C.type==="inbound-rtp"&&D(C,"kind")!=="video"),T=g.filter((C)=>C.type==="outbound-rtp"&&D(C,"kind")!=="video"),V=g.filter((C)=>C.type==="candidate-pair"),I=g.filter((C)=>(C.type==="track"||C.type==="media-source")&&D(C,"kind")==="audio"),y=V.filter((C)=>x(C,"selected")===!0||x(C,"nominated")===!0||D(C,"state")==="succeeded").length,_=I.filter((C)=>D(C,"readyState")!=="ended"&&D(C,"trackState")!=="ended"&&x(C,"ended")!==!0).length,L=I.filter((C)=>D(C,"readyState")==="ended"||D(C,"trackState")==="ended"||x(C,"ended")===!0).length,S=n.reduce((C,U)=>C+(w(U,"packetsReceived")??0),0),h=T.reduce((C,U)=>C+(w(U,"packetsSent")??0),0),l=[...n,...T].reduce((C,U)=>C+Math.max(0,w(U,"packetsLost")??0),0),b=S+l,R=b===0?0:l/b,W=n.reduce((C,U)=>C+(w(U,"bytesReceived")??0),0),E=T.reduce((C,U)=>C+(w(U,"bytesSent")??0),0),M=G(V.map((C)=>K(w(C,"currentRoundTripTime")??w(C,"roundTripTime"))).filter((C)=>C!==void 0)),i=G([...n,...T].map((C)=>K(w(C,"jitter"))).filter((C)=>C!==void 0)),d=G(n.map((C)=>{let U=w(C,"jitterBufferDelay"),O=w(C,"jitterBufferEmittedCount");return U!==void 0&&O!==void 0&&O>0?U/O*1000:void 0}).filter((C)=>C!==void 0)),N=I.map((C)=>w(C,"audioLevel")).filter((C)=>C!==void 0);if(c.requireConnectedCandidatePair&&V.length>0&&y===0)$(A,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(c.requireLiveAudioTrack&&_===0)$(A,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(c.maxPacketLossRatio!==void 0&&R>c.maxPacketLossRatio)$(A,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(R)} above ${String(c.maxPacketLossRatio)}.`);if(c.maxRoundTripTimeMs!==void 0&&M!==void 0&&M>c.maxRoundTripTimeMs)$(A,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(M)}ms above ${String(c.maxRoundTripTimeMs)}ms.`);if(c.maxJitterMs!==void 0&&i!==void 0&&i>c.maxJitterMs)$(A,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(i)}ms above ${String(c.maxJitterMs)}ms.`);return{activeCandidatePairs:y,audioLevelAverage:Dc(N),bytesReceived:W,bytesSent:E,checkedAt:Date.now(),endedAudioTracks:L,inboundPackets:S,issues:A,jitterBufferDelayMs:d,jitterMs:i,liveAudioTracks:_,outboundPackets:h,packetLossRatio:R,packetsLost:l,roundTripTimeMs:M,status:A.some((C)=>C.severity==="error")?"fail":A.length>0?"warn":"pass",totalStats:g.length}},B=async(c)=>{return[...(await c.peerConnection.getStats(c.selector??null)).values()].map(bc)};var j=(c={})=>{let g=c.stats??[],A=c.previousStats??[],n=[],T=new Map(A.map((l)=>[Q(l),l])),I=g.filter((l)=>(l.type==="inbound-rtp"||l.type==="outbound-rtp")&&D(l,"kind")!=="video"&&D(l,"mediaType")!=="video").map((l)=>{let b=l.type==="outbound-rtp"?"outbound":"inbound",R=b==="outbound"?"packetsSent":"packetsReceived",W=b==="outbound"?"bytesSent":"bytesReceived",E=T.get(Q(l)),M=w(l,R),i=E?w(E,R):void 0,d=w(l,W),N=E?w(E,W):void 0,C=l.timestamp!==void 0&&E?.timestamp!==void 0?l.timestamp-E.timestamp:void 0;return{bytesDelta:d!==void 0&&N!==void 0?d-N:void 0,currentPackets:M,direction:b,id:Q(l),packetDelta:M!==void 0&&i!==void 0?M-i:void 0,previousPackets:i,timeDeltaMs:C}}),y=I.filter((l)=>l.direction==="inbound"),_=I.filter((l)=>l.direction==="outbound"),L=G(I.map((l)=>l.timeDeltaMs).filter((l)=>l!==void 0)),S=y.filter((l)=>c.maxInboundPacketStallMs!==void 0&&l.timeDeltaMs!==void 0&&l.timeDeltaMs>=c.maxInboundPacketStallMs&&l.packetDelta!==void 0&&l.packetDelta<=0).length,h=_.filter((l)=>c.maxOutboundPacketStallMs!==void 0&&l.timeDeltaMs!==void 0&&l.timeDeltaMs>=c.maxOutboundPacketStallMs&&l.packetDelta!==void 0&&l.packetDelta<=0).length;if(c.requireInboundAudio&&y.length===0)$(n,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(c.requireOutboundAudio&&_.length===0)$(n,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(c.maxGapMs!==void 0&&L!==void 0&&L>c.maxGapMs)$(n,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(L)}ms above ${String(c.maxGapMs)}ms.`);if(S>0)$(n,"error","media.webrtc_inbound_stalled",`${String(S)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(h>0)$(n,"error","media.webrtc_outbound_stalled",`${String(h)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:y.length,issues:n,maxObservedGapMs:L,outboundAudioStreams:_.length,stalledInboundStreams:S,stalledOutboundStreams:h,status:n.some((l)=>l.severity==="error")?"fail":n.length>0?"warn":"pass",streams:I,totalStats:g.length}};var Oc="/api/voice/browser-media",Ec=5000,Mc=async(c)=>c.peerConnection??await c.getPeerConnection?.()??null,ic=async(c,g)=>{let A=g.fetch??globalThis.fetch;if(!A)return;await A(g.path??Oc,{body:JSON.stringify(c),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},s=(c)=>{let g=null,A=[],n=async()=>{let I=await Mc(c);if(!I)return;let y=await B({peerConnection:I}),_=o({...c,stats:y}),L=c.continuity===!1?void 0:j({...c.continuity,previousStats:A,stats:y}),S={at:Date.now(),continuity:L,report:_,scenarioId:c.getScenarioId?.()??null,sessionId:c.getSessionId?.()??null};return A=y,c.onReport?.(S),await ic(S,c),S},T=()=>{n().catch((I)=>{c.onError?.(I)})},V=()=>{if(g)clearInterval(g),g=null};return{close:V,reportOnce:n,stop:V,start:()=>{if(g)return;T(),g=setInterval(T,c.intervalMs??Ec)}}};var H=()=>{},$c=()=>H,Pc={callControl:H,close:H,endTurn:H,send:H,sendAudio:H,simulateDisconnect:H,subscribe:$c,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},Hc=()=>crypto.randomUUID(),Wc=(c,g,A)=>{let{hostname:n,port:T,protocol:V}=window.location,I=V==="https:"?"wss:":"ws:",y=T?`:${T}`:"",_=new URL(`${I}//${n}${y}${c}`);if(_.searchParams.set("sessionId",g),A)_.searchParams.set("scenarioId",A);return _.toString()},dc=(c)=>{if(!c||typeof c!=="object"||!("type"in c))return!1;switch(c.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}},Nc=(c)=>{if(typeof c.data!=="string")return null;try{let g=JSON.parse(c.data);return dc(g)?g:null}catch{return null}},k=(c,g={})=>{if(typeof window>"u")return Pc;let A=new Set,n=g.reconnect!==!1,T=g.maxReconnectAttempts??10,V=g.pingInterval??30000,I={isConnected:!1,pendingMessages:[],scenarioId:g.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:g.sessionId??Hc(),ws:null},y=(C)=>{A.forEach((U)=>U(C))},_=()=>{if(I.pingInterval)clearInterval(I.pingInterval),I.pingInterval=null;if(I.reconnectTimeout)clearTimeout(I.reconnectTimeout),I.reconnectTimeout=null},L=()=>{if(I.ws?.readyState!==1)return;while(I.pendingMessages.length>0){let C=I.pendingMessages.shift();if(C!==void 0)I.ws.send(C)}},S=()=>{let C=Date.now()+500;I.reconnectAttempts+=1,y({reconnect:{attempts:I.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:T,nextAttemptAt:C,status:"reconnecting"},type:"connection"}),I.reconnectTimeout=setTimeout(()=>{if(I.reconnectAttempts>T){y({reconnect:{attempts:I.reconnectAttempts,maxAttempts:T,status:"exhausted"},type:"connection"});return}h()},500)},h=()=>{let C=new WebSocket(Wc(c,I.sessionId,I.scenarioId));C.binaryType="arraybuffer",C.onopen=()=>{let U=I.reconnectAttempts>0;if(I.isConnected=!0,L(),U)y({reconnect:{attempts:I.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:T,status:"resumed"},type:"connection"}),I.reconnectAttempts=0;A.forEach((O)=>O({scenarioId:I.scenarioId??void 0,sessionId:I.sessionId,status:"active",type:"session"})),I.pingInterval=setInterval(()=>{if(C.readyState===1)C.send(JSON.stringify({type:"ping"}))},V)},C.onmessage=(U)=>{let O=Nc(U);if(!O)return;if(O.type==="session")I.sessionId=O.sessionId,I.scenarioId=O.scenarioId??I.scenarioId;A.forEach((nc)=>nc(O))},C.onclose=(U)=>{if(I.isConnected=!1,_(),n&&U.code!==1000&&I.reconnectAttempts<T)S();else if(n&&U.code!==1000)y({reconnect:{attempts:I.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:T,status:"exhausted"},type:"connection"})},I.ws=C},l=(C)=>{if(I.ws?.readyState===1){I.ws.send(C);return}I.pendingMessages.push(C)},b=(C)=>{l(JSON.stringify(C))},R=(C={})=>{if(C.sessionId)I.sessionId=C.sessionId;if(C.scenarioId)I.scenarioId=C.scenarioId;b({scenarioId:I.scenarioId??void 0,sessionId:I.sessionId,type:"start"})},W=(C)=>{l(C)},E=()=>{b({type:"end_turn"})},M=(C)=>{b({...C,type:"call_control"})},i=()=>{if(_(),I.ws)I.ws.close(1000),I.ws=null;I.isConnected=!1,A.clear()},d=()=>{if(I.ws?.readyState===1)I.ws.close(4000,"absolutejs-voice-reconnect-proof")},N=(C)=>{return A.add(C),()=>{A.delete(C)}};return h(),{callControl:M,close:i,endTurn:E,send:b,sendAudio:W,simulateDisconnect:d,start:R,subscribe:N,getReadyState:()=>I.ws?.readyState??3,getScenarioId:()=>I.scenarioId??"",getSessionId:()=>I.sessionId}};var xc=()=>({attempts:0,maxAttempts:0,status:"idle"}),Gc=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:xc(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),f=()=>{let c=Gc(),g=new Set,A=()=>{g.forEach((T)=>T())};return{dispatch:(T)=>{switch(T.type){case"audio":c={...c,assistantAudio:[...c.assistantAudio,{chunk:T.chunk,format:T.format,receivedAt:T.receivedAt,turnId:T.turnId}]};break;case"assistant":c={...c,assistantStreamingText:"",assistantTexts:[...c.assistantTexts,T.text]};break;case"assistant_delta":c={...c,assistantStreamingText:`${c.assistantStreamingText}${T.delta}`};break;case"complete":c={...c,sessionId:T.sessionId,status:"completed"};break;case"call_lifecycle":c={...c,call:{...c.call,disposition:T.event.type==="end"?T.event.disposition:c.call?.disposition,endedAt:T.event.type==="end"?T.event.at:c.call?.endedAt,events:[...c.call?.events??[],T.event],lastEventAt:T.event.at,startedAt:c.call?.startedAt??T.event.at},sessionId:T.sessionId};break;case"connected":c={...c,isConnected:!0,reconnect:c.reconnect.status==="reconnecting"?{...c.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:c.reconnect};break;case"connection":c={...c,reconnect:T.reconnect};break;case"disconnected":c={...c,isConnected:!1};break;case"error":c={...c,error:T.message};break;case"final":c={...c,partial:T.transcript.text,turns:c.turns.map((V)=>V)};break;case"partial":c={...c,partial:T.transcript.text};break;case"replay":c={...c,assistantStreamingText:"",assistantTexts:[...T.assistantTexts],call:T.call??null,error:null,isConnected:T.status==="active",partial:T.partial,reconnect:c.reconnect.status==="reconnecting"?{...c.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:c.reconnect,scenarioId:T.scenarioId??c.scenarioId,sessionId:T.sessionId,sessionMetadata:T.sessionMetadata??c.sessionMetadata,status:T.status,turns:[...T.turns]};break;case"session":c={...c,error:null,scenarioId:T.scenarioId??c.scenarioId,isConnected:T.status==="active",sessionId:T.sessionId,sessionMetadata:T.sessionMetadata??c.sessionMetadata,status:T.status};break;case"turn":c={...c,partial:"",turns:[...c.turns,T.turn]};break}A()},getServerSnapshot:()=>c,getSnapshot:()=>c,subscribe:(T)=>{return g.add(T),()=>{g.delete(T)}}}};var F=(c,g={})=>{let A=k(c,g),n=f(),T=g.browserMedia&&typeof window<"u"?s({...g.browserMedia,getScenarioId:()=>g.browserMedia?g.browserMedia.getScenarioId?.()??A.getScenarioId():A.getScenarioId(),getSessionId:()=>g.browserMedia?g.browserMedia.getSessionId?.()??A.getSessionId():A.getSessionId()}):null,V=new Set,I=(S)=>Promise.resolve().then(()=>{if(!S?.sessionId&&!S?.scenarioId)return;A.start(S),T?.start()}),y=()=>{V.forEach((S)=>S())},_=()=>{if(!g.reconnectReportPath||typeof fetch>"u")return;let S=n.getSnapshot(),h=JSON.stringify({at:Date.now(),reconnect:S.reconnect,scenarioId:S.scenarioId,sessionId:A.getSessionId(),turnIds:S.turns.map((l)=>l.id)});fetch(g.reconnectReportPath,{body:h,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},L=A.subscribe((S)=>{let h=z(S);if(h){if(n.dispatch(h),S.type==="connection")_();y()}});return{start:I,get assistantAudio(){return n.getSnapshot().assistantAudio},get assistantTexts(){return n.getSnapshot().assistantTexts},get assistantStreamingText(){return n.getSnapshot().assistantStreamingText},get call(){return n.getSnapshot().call},callControl(S){A.callControl(S)},close(){L(),T?.close(),A.close(),n.dispatch({type:"disconnected"}),y()},endTurn(){A.endTurn()},get error(){return n.getSnapshot().error},getServerSnapshot(){return n.getServerSnapshot()},getSnapshot(){return n.getSnapshot()},get isConnected(){return n.getSnapshot().isConnected},get partial(){return n.getSnapshot().partial},get reconnect(){return n.getSnapshot().reconnect},get scenarioId(){return n.getSnapshot().scenarioId},sendAudio(S){A.sendAudio(S)},get sessionId(){return A.getSessionId()},get sessionMetadata(){return n.getSnapshot().sessionMetadata},simulateDisconnect(){A.simulateDisconnect()},get status(){return n.getSnapshot().status},subscribe(S){return V.add(S),()=>{V.delete(S)}},get turns(){return n.getSnapshot().turns}}};var t=(c)=>{if(!c||c.enabled===!1)return;return{enabled:!0,maxGain:c.maxGain??3,noiseGateAttenuation:c.noiseGateAttenuation??0.15,noiseGateThreshold:c.noiseGateThreshold??0.006,targetLevel:c.targetLevel??0.08}};var Xc={balanced:{qualityProfile:"general",silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},Yc={"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},general:{},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var e=(c)=>{let g=c?.profile??"fast",A=c?.qualityProfile??"general",n=Xc[g],T=Yc[A];return{profile:g,qualityProfile:A,silenceMs:c?.silenceMs??T.silenceMs??n.silenceMs,speechThreshold:c?.speechThreshold??T.speechThreshold??n.speechThreshold,transcriptStabilityMs:c?.transcriptStabilityMs??T.transcriptStabilityMs??n.transcriptStabilityMs}};var Qc={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"}}},v=(c="default")=>{let g=Qc[c];return{audioConditioning:t(g.audioConditioning),capture:{channelCount:g.capture?.channelCount??1,sampleRateHz:g.capture?.sampleRateHz??16000},connection:{...g.connection},name:c,sttLifecycle:g.sttLifecycle??"continuous",turnDetection:e(g.turnDetection)}};var Jc=(c)=>({assistantAudio:[...c.assistantAudio],assistantStreamingText:c.assistantStreamingText,assistantTexts:[...c.assistantTexts],call:c.call,error:c.error,isConnected:c.isConnected,isRecording:!1,partial:c.partial,reconnect:c.reconnect,recordingError:null,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,scenarioId:c.scenarioId,status:c.status,turns:[...c.turns]}),r=(c,g={})=>{let A=v(g.preset),n=F(c,{...A.connection,...g.connection}),T=null,V=Jc(n),I=new Set,y=()=>{for(let R of I)R()},_=()=>{if(V={...V,assistantAudio:[...n.assistantAudio],assistantStreamingText:n.assistantStreamingText,assistantTexts:[...n.assistantTexts],call:n.call,error:n.error,isConnected:n.isConnected,partial:n.partial,reconnect:n.reconnect,sessionId:n.sessionId,sessionMetadata:n.sessionMetadata,scenarioId:n.scenarioId,status:n.status,turns:[...n.turns]},g.autoStopOnComplete!==!1&&V.status==="completed"&&V.isRecording)T?.stop(),T=null,V={...V,isRecording:!1};y()},L=n.subscribe(_);_();let S=()=>{if(T)return T;return T=q({channelCount:g.capture?.channelCount??A.capture.channelCount,onLevel:g.capture?.onLevel,onAudio:(R)=>{if(g.capture?.onAudio){g.capture.onAudio(R,n.sendAudio);return}n.sendAudio(R)},sampleRateHz:g.capture?.sampleRateHz??A.capture.sampleRateHz}),T},h=()=>{T?.stop(),T=null,V={...V,isRecording:!1},y()},l=async()=>{if(V.isRecording)return;try{V={...V,recordingError:null},y(),await S().start(),V={...V,isRecording:!0},y()}catch(R){throw T=null,V={...V,isRecording:!1,recordingError:R instanceof Error?R.message:String(R)},y(),R}};return{close:()=>{L(),h(),n.close()},startRecording:l,stopRecording:h,get assistantAudio(){return V.assistantAudio},get assistantTexts(){return V.assistantTexts},get assistantStreamingText(){return V.assistantStreamingText},bindHTMX(R){return Z(n,R)},get call(){return V.call},callControl:(R)=>n.callControl(R),endTurn:()=>n.endTurn(),get error(){return V.error},getServerSnapshot:()=>V,getSnapshot:()=>V,get isConnected(){return V.isConnected},get isRecording(){return V.isRecording},get partial(){return V.partial},get reconnect(){return V.reconnect},get recordingError(){return V.recordingError},get scenarioId(){return V.scenarioId},sendAudio:(R)=>n.sendAudio(R),get sessionId(){return V.sessionId},get sessionMetadata(){return V.sessionMetadata},simulateDisconnect:()=>n.simulateDisconnect(),get status(){return V.status},subscribe:(R)=>{return I.add(R),()=>{I.delete(R)}},toggleRecording:async()=>{if(V.isRecording){h();return}await l()},get turns(){return V.turns}}};var P=(c)=>String(c).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");var a=(c)=>{if(!c.isConnected)return"idle";if(c.isPlaying)return"speaking";if(c.isRecording&&c.hasActivePartial)return"listening";if(c.isRecording)return"listening";if(c.lastTranscriptAt&&!c.lastAssistantAt)return"thinking";if(c.lastTranscriptAt&&c.lastAssistantAt&&c.lastTranscriptAt>c.lastAssistantAt)return"thinking";return"idle"};var Zc={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},qc={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},zc=(c,g)=>{switch(c){case"listening":return g.listening;case"speaking":return g.speaking;case"thinking":return g.thinking;case"idle":return g.idle}},m=(c)=>{let g={...Zc,...c.theme},A={...qc,...c.labels},n=c.state.assistantAudio.at(-1)?.receivedAt,T=c.state.turns.at(-1)?.committedAt,V=a({hasActivePartial:c.state.partial.length>0,isConnected:c.state.isConnected,isPlaying:!1,isRecording:c.state.isRecording,lastAssistantAt:n,lastTranscriptAt:T}),I=!c.state.isConnected&&c.state.status!=="idle"&&!c.state.error,y=c.state.error?"Error":I?A.connecting:c.state.status==="completed"?A.callEnded:zc(V,A);return{agentState:V,classes:{container:`absolute-voice-widget absolute-voice-widget--${V}`,dot:`absolute-voice-widget__dot${c.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:c.state.isConnected,canMute:c.state.isRecording,canStart:!c.state.isRecording&&c.state.status!=="completed"},errorMessage:c.state.error??void 0,labels:A,partial:c.state.partial||void 0,statusLabel:y,theme:g,title:c.title??"Voice"}},Kc=(c)=>typeof c==="number"?`${c}px`:c,u=(c)=>{let g=c.theme,A=`background:${g.background};border-radius:${Kc(g.radius)};color:${g.foreground};font-family:${g.fontFamily};min-width:240px;padding:20px 22px;`,n=`background:${c.errorMessage?g.errorAccent:c.agentState==="idle"?"rgba(148,163,184,0.6)":g.accent};border-radius:50%;height:10px;width:10px;`,T=[];if(c.controls.canStart)T.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;">${P(c.labels.startCall)}</button>`);if(c.controls.canMute)T.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${P(c.labels.mute)}</button>`);if(c.controls.canEnd)T.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;">${P(c.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${c.agentState}" class="${P(c.classes.container)}" style="${A}">
2
2
  <div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
3
- <span aria-hidden="true" class="${O(c.classes.dot)}" style="${g}"></span>
4
- <strong style="font-size:15px;">${O(c.title)}</strong>
5
- <span style="font-size:13px;margin-left:auto;opacity:0.7;">${O(c.statusLabel)}</span>
3
+ <span aria-hidden="true" class="${P(c.classes.dot)}" style="${n}"></span>
4
+ <strong style="font-size:15px;">${P(c.title)}</strong>
5
+ <span style="font-size:13px;margin-left:auto;opacity:0.7;">${P(c.statusLabel)}</span>
6
6
  </div>
7
- ${c.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${O(c.partial)}”</p>`:""}
8
- <div style="display:flex;gap:10px;">${l.join("")}</div>
9
- ${c.errorMessage?`<p style="color:${n.errorAccent};font-size:12px;margin-top:12px;">${O(c.errorMessage)}</p>`:""}
10
- </div>`};var zc=(c)=>{if(typeof c!=="string")return c;let n=document.querySelector(c);if(!n)throw Error(`AbsoluteVoice.mount: no element matches "${c}"`);return n},p=(c,n={})=>{let o=zc(c),g=a(n.path??"/voice",n.controllerOptions),l=null,C=null,i=()=>{let I=u({...n.labels!==void 0?{labels:n.labels}:{},state:{assistantAudio:g.assistantAudio,error:g.error,isConnected:g.isConnected,isRecording:g.isRecording,partial:g.partial,status:g.status,turns:g.turns},...n.theme!==void 0?{theme:n.theme}:{},...n.title!==void 0?{title:n.title}:{}});o.innerHTML=m(I);for(let R of o.querySelectorAll("button[data-action]")){let{action:y}=R.dataset;R.addEventListener("click",()=>{if(y==="start")g.startRecording();else if(y==="mute")g.stopRecording();else if(y==="end")g.close()})}if(g.error&&g.error!==l)l=g.error,n.onError?.(g.error);if(g.status!==C)C=g.status,n.onStatusChange?.(g.status)},V=g.subscribe(i);if(i(),n.autoStart)g.startRecording();return{controller:g,async end(){await g.close()},mute(){g.stopRecording()},async start(){await g.startRecording()},unmount(){V(),g.close(),o.innerHTML=""}}},cc="0.0.22-beta.516",nc={mount:p,version:cc};if(typeof globalThis<"u")globalThis.AbsoluteVoice=nc;var Bc=nc;})();
7
+ ${c.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${P(c.partial)}”</p>`:""}
8
+ <div style="display:flex;gap:10px;">${T.join("")}</div>
9
+ ${c.errorMessage?`<p style="color:${g.errorAccent};font-size:12px;margin-top:12px;">${P(c.errorMessage)}</p>`:""}
10
+ </div>`};var oc=(c)=>{if(typeof c!=="string")return c;let g=document.querySelector(c);if(!g)throw Error(`AbsoluteVoice.mount: no element matches "${c}"`);return g},p=(c,g={})=>{let A=oc(c),n=r(g.path??"/voice",g.controllerOptions),T=null,V=null,I=()=>{let _=m({...g.labels!==void 0?{labels:g.labels}:{},state:{assistantAudio:n.assistantAudio,error:n.error,isConnected:n.isConnected,isRecording:n.isRecording,partial:n.partial,status:n.status,turns:n.turns},...g.theme!==void 0?{theme:g.theme}:{},...g.title!==void 0?{title:g.title}:{}});A.innerHTML=u(_);for(let L of A.querySelectorAll("button[data-action]")){let{action:S}=L.dataset;L.addEventListener("click",()=>{if(S==="start")n.startRecording();else if(S==="mute")n.stopRecording();else if(S==="end")n.close()})}if(n.error&&n.error!==T)T=n.error,g.onError?.(n.error);if(n.status!==V)V=n.status,g.onStatusChange?.(n.status)},y=n.subscribe(I);if(I(),g.autoStart)n.startRecording();return{controller:n,async end(){await n.close()},mute(){n.stopRecording()},async start(){await n.startRecording()},unmount(){y(),n.close(),A.innerHTML=""}}},cc="0.0.22-beta.516",gc={mount:p,version:cc};if(typeof globalThis<"u")globalThis.AbsoluteVoice=gc;var jc=gc;})();
package/dist/index.js CHANGED
@@ -3870,6 +3870,7 @@ var createVoiceSession = (options) => {
3870
3870
  let adapterGenerationCounter = 0;
3871
3871
  let activeAdapterGeneration = 0;
3872
3872
  let activeTTSTurnId;
3873
+ let assistantSpeechEndsAt = 0;
3873
3874
  let fillerTimer = null;
3874
3875
  let fillerActive = false;
3875
3876
  let fillerToken = 0;
@@ -4262,6 +4263,7 @@ var createVoiceSession = (options) => {
4262
4263
  return;
4263
4264
  }
4264
4265
  activeTTSTurnId = undefined;
4266
+ assistantSpeechEndsAt = Date.now();
4265
4267
  appendTurnLatencyStage({
4266
4268
  metadata: { reason },
4267
4269
  stage: "tts_canceled",
@@ -4304,6 +4306,12 @@ var createVoiceSession = (options) => {
4304
4306
  turnId: activeTTSTurnId,
4305
4307
  type: "audio"
4306
4308
  });
4309
+ const bytesPerSample = input.format.encoding === "pcm_s16le" ? 2 : 1;
4310
+ const bytesPerSecond = input.format.sampleRateHz * input.format.channels * bytesPerSample;
4311
+ if (bytesPerSecond > 0) {
4312
+ const chunkMs = normalizedChunk.byteLength / bytesPerSecond * 1000;
4313
+ assistantSpeechEndsAt = Math.max(assistantSpeechEndsAt, Date.now()) + chunkMs;
4314
+ }
4307
4315
  if (activeTTSTurnId) {
4308
4316
  await appendTurnLatencyStage({
4309
4317
  at: input.receivedAt,
@@ -4413,6 +4421,20 @@ var createVoiceSession = (options) => {
4413
4421
  session
4414
4422
  });
4415
4423
  };
4424
+ const DRAIN_POLL_MS = 200;
4425
+ const DRAIN_TAIL_BUFFER_MS = 300;
4426
+ const DRAIN_MAX_MS = 12000;
4427
+ const drainAssistantSpeech = async () => {
4428
+ const startedAt = Date.now();
4429
+ while (Date.now() - startedAt < DRAIN_MAX_MS) {
4430
+ const remaining = assistantSpeechEndsAt + DRAIN_TAIL_BUFFER_MS - Date.now();
4431
+ if (remaining <= 0)
4432
+ return;
4433
+ await new Promise((resolve) => {
4434
+ setTimeout(resolve, Math.min(remaining, DRAIN_POLL_MS));
4435
+ });
4436
+ }
4437
+ };
4416
4438
  const completeInternal = async (result, input = {}) => {
4417
4439
  clearSilenceTimer();
4418
4440
  const disposition = input.disposition ?? "completed";
@@ -4446,6 +4468,9 @@ var createVoiceSession = (options) => {
4446
4468
  if (!didComplete) {
4447
4469
  return;
4448
4470
  }
4471
+ if (disposition === "completed") {
4472
+ await drainAssistantSpeech();
4473
+ }
4449
4474
  await appendTrace({
4450
4475
  payload: {
4451
4476
  disposition,
@@ -12152,10 +12152,16 @@ var createMicrophoneCapture = (options) => {
12152
12152
  }
12153
12153
  mediaStream = await navigator.mediaDevices.getUserMedia({
12154
12154
  audio: {
12155
- channelCount: options.channelCount ?? 1
12155
+ autoGainControl: true,
12156
+ channelCount: options.channelCount ?? 1,
12157
+ echoCancellation: true,
12158
+ noiseSuppression: true
12156
12159
  }
12157
12160
  });
12158
12161
  audioContext = new AudioContextCtor;
12162
+ if (audioContext.state === "suspended") {
12163
+ await audioContext.resume();
12164
+ }
12159
12165
  sourceNode = audioContext.createMediaStreamSource(mediaStream);
12160
12166
  processorNode = audioContext.createScriptProcessor(4096, 1, 1);
12161
12167
  processorNode.onaudioprocess = (event) => {
@@ -527,10 +527,16 @@ var createMicrophoneCapture = (options) => {
527
527
  }
528
528
  mediaStream = await navigator.mediaDevices.getUserMedia({
529
529
  audio: {
530
- channelCount: options.channelCount ?? 1
530
+ autoGainControl: true,
531
+ channelCount: options.channelCount ?? 1,
532
+ echoCancellation: true,
533
+ noiseSuppression: true
531
534
  }
532
535
  });
533
536
  audioContext = new AudioContextCtor;
537
+ if (audioContext.state === "suspended") {
538
+ await audioContext.resume();
539
+ }
534
540
  sourceNode = audioContext.createMediaStreamSource(mediaStream);
535
541
  processorNode = audioContext.createScriptProcessor(4096, 1, 1);
536
542
  processorNode.onaudioprocess = (event) => {
@@ -2051,10 +2051,16 @@ var createMicrophoneCapture = (options) => {
2051
2051
  }
2052
2052
  mediaStream = await navigator.mediaDevices.getUserMedia({
2053
2053
  audio: {
2054
- channelCount: options.channelCount ?? 1
2054
+ autoGainControl: true,
2055
+ channelCount: options.channelCount ?? 1,
2056
+ echoCancellation: true,
2057
+ noiseSuppression: true
2055
2058
  }
2056
2059
  });
2057
2060
  audioContext = new AudioContextCtor;
2061
+ if (audioContext.state === "suspended") {
2062
+ await audioContext.resume();
2063
+ }
2058
2064
  sourceNode = audioContext.createMediaStreamSource(mediaStream);
2059
2065
  processorNode = audioContext.createScriptProcessor(4096, 1, 1);
2060
2066
  processorNode.onaudioprocess = (event) => {
@@ -5780,6 +5786,7 @@ var createVoiceSession = (options) => {
5780
5786
  let adapterGenerationCounter = 0;
5781
5787
  let activeAdapterGeneration = 0;
5782
5788
  let activeTTSTurnId;
5789
+ let assistantSpeechEndsAt = 0;
5783
5790
  let fillerTimer = null;
5784
5791
  let fillerActive = false;
5785
5792
  let fillerToken = 0;
@@ -6172,6 +6179,7 @@ var createVoiceSession = (options) => {
6172
6179
  return;
6173
6180
  }
6174
6181
  activeTTSTurnId = undefined;
6182
+ assistantSpeechEndsAt = Date.now();
6175
6183
  appendTurnLatencyStage({
6176
6184
  metadata: { reason },
6177
6185
  stage: "tts_canceled",
@@ -6214,6 +6222,12 @@ var createVoiceSession = (options) => {
6214
6222
  turnId: activeTTSTurnId,
6215
6223
  type: "audio"
6216
6224
  });
6225
+ const bytesPerSample = input.format.encoding === "pcm_s16le" ? 2 : 1;
6226
+ const bytesPerSecond = input.format.sampleRateHz * input.format.channels * bytesPerSample;
6227
+ if (bytesPerSecond > 0) {
6228
+ const chunkMs = normalizedChunk.byteLength / bytesPerSecond * 1000;
6229
+ assistantSpeechEndsAt = Math.max(assistantSpeechEndsAt, Date.now()) + chunkMs;
6230
+ }
6217
6231
  if (activeTTSTurnId) {
6218
6232
  await appendTurnLatencyStage({
6219
6233
  at: input.receivedAt,
@@ -6323,6 +6337,20 @@ var createVoiceSession = (options) => {
6323
6337
  session
6324
6338
  });
6325
6339
  };
6340
+ const DRAIN_POLL_MS = 200;
6341
+ const DRAIN_TAIL_BUFFER_MS = 300;
6342
+ const DRAIN_MAX_MS = 12000;
6343
+ const drainAssistantSpeech = async () => {
6344
+ const startedAt = Date.now();
6345
+ while (Date.now() - startedAt < DRAIN_MAX_MS) {
6346
+ const remaining = assistantSpeechEndsAt + DRAIN_TAIL_BUFFER_MS - Date.now();
6347
+ if (remaining <= 0)
6348
+ return;
6349
+ await new Promise((resolve2) => {
6350
+ setTimeout(resolve2, Math.min(remaining, DRAIN_POLL_MS));
6351
+ });
6352
+ }
6353
+ };
6326
6354
  const completeInternal = async (result, input = {}) => {
6327
6355
  clearSilenceTimer();
6328
6356
  const disposition = input.disposition ?? "completed";
@@ -6356,6 +6384,9 @@ var createVoiceSession = (options) => {
6356
6384
  if (!didComplete) {
6357
6385
  return;
6358
6386
  }
6387
+ if (disposition === "completed") {
6388
+ await drainAssistantSpeech();
6389
+ }
6359
6390
  await appendTrace({
6360
6391
  payload: {
6361
6392
  disposition,
package/dist/vue/index.js CHANGED
@@ -11569,10 +11569,16 @@ var createMicrophoneCapture = (options) => {
11569
11569
  }
11570
11570
  mediaStream = await navigator.mediaDevices.getUserMedia({
11571
11571
  audio: {
11572
- channelCount: options.channelCount ?? 1
11572
+ autoGainControl: true,
11573
+ channelCount: options.channelCount ?? 1,
11574
+ echoCancellation: true,
11575
+ noiseSuppression: true
11573
11576
  }
11574
11577
  });
11575
11578
  audioContext = new AudioContextCtor;
11579
+ if (audioContext.state === "suspended") {
11580
+ await audioContext.resume();
11581
+ }
11576
11582
  sourceNode = audioContext.createMediaStreamSource(mediaStream);
11577
11583
  processorNode = audioContext.createScriptProcessor(4096, 1, 1);
11578
11584
  processorNode.onaudioprocess = (event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.572",
3
+ "version": "0.0.22-beta.574",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",