@absolutejs/voice 0.0.22-beta.463 → 0.0.22-beta.465

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5186,7 +5186,7 @@ var createVoiceSession = (options) => {
5186
5186
  };
5187
5187
 
5188
5188
  // src/generated/htmxBootstrapBundle.ts
5189
- var HTMX_BOOTSTRAP_BUNDLE = 'var Ue=(e)=>{if(typeof e!=="string")return e;return document.querySelector(e)},Ne=(e,n,t,o)=>{let r=n??e.getAttribute("hx-get")??"";if(!r)return"";let i=new URL(r,window.location.origin);if(o)i.searchParams.set(t,o);else i.searchParams.delete(t);return`${i.pathname}${i.search}${i.hash}`},de=(e,n)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let t=Ue(n.element);if(!t)return()=>{};let o=n.eventName??"voice-refresh",r=n.sessionQueryParam??"sessionId",i=()=>{let l=window,g=Ne(t,n.route,r,e.sessionId);if(g)t.setAttribute("hx-get",g);l.htmx?.process?.(t),l.htmx?.trigger?.(t,o)},c=e.subscribe(i);return i(),()=>{c()}};var He=(e)=>Math.max(-1,Math.min(1,e)),Ge=(e)=>{let n=new Int16Array(e.length);for(let t=0;t<e.length;t+=1){let o=He(e[t]??0);n[t]=o<0?o*32768:o*32767}return new Uint8Array(n.buffer)},Be=(e)=>{let n=e instanceof Uint8Array?e:new Uint8Array(e);if(n.byteLength<2)return 0;let t=new Int16Array(n.buffer,n.byteOffset,Math.floor(n.byteLength/2));if(t.length===0)return 0;let o=0;for(let r of t){let i=r/32768;o+=i*i}return Math.min(1,Math.max(0,Math.sqrt(o/t.length)*5.5))},We=(e,n,t)=>{if(n===t)return e;let o=n/t,r=Math.round(e.length/o),i=new Float32Array(r),c=0,l=0;while(c<i.length){let g=Math.round((c+1)*o),y=0,d=0;for(let h=l;h<g&&h<e.length;h+=1)y+=e[h]??0,d+=1;i[c]=d>0?y/d:0,c+=1,l=g}return i},ge=(e)=>{let n=null,t=null,o=null,r=null;return{start:async()=>{if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let l=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!l)throw Error("Browser microphone capture requires AudioContext support.");r=await navigator.mediaDevices.getUserMedia({audio:{channelCount:e.channelCount??1}}),n=new l,t=n.createMediaStreamSource(r),o=n.createScriptProcessor(4096,1,1),o.onaudioprocess=(g)=>{let y=g.inputBuffer.getChannelData(0),d=We(y,n?.sampleRate??48000,e.sampleRateHz??16000),h=Ge(d);e.onLevel?.(Be(h)),e.onAudio(h)},t.connect(o),o.connect(n.destination)},stop:()=>{o?.disconnect(),t?.disconnect(),r?.getTracks().forEach((l)=>l.stop()),n?.close(),e.onLevel?.(0),n=null,r=null,o=null,t=null}}};var ee=(e)=>{if(typeof e==="string"&&e.trim())return e;if(e instanceof Error&&e.message.trim())return e.message;if(e&&typeof e==="object"){let n=e;for(let t of["message","reason","description"]){let o=n[t];if(typeof o==="string"&&o.trim())return o}if("error"in n)return ee(n.error);if("cause"in n)return ee(n.cause);try{return JSON.stringify(e)}catch{}}return"Unexpected error"},Ae=(e)=>{switch(e.type){case"audio":return{chunk:Uint8Array.from(atob(e.chunkBase64),(n)=>n.charCodeAt(0)),format:e.format,receivedAt:e.receivedAt,turnId:e.turnId,type:"audio"};case"assistant":return{text:e.text,type:"assistant"};case"complete":return{sessionId:e.sessionId,type:"complete"};case"connection":return{reconnect:e.reconnect,type:"connection"};case"call_lifecycle":return{event:e.event,sessionId:e.sessionId,type:"call_lifecycle"};case"error":return{message:ee(e.message),type:"error"};case"final":return{transcript:e.transcript,type:"final"};case"partial":return{transcript:e.transcript,type:"partial"};case"replay":return{assistantTexts:e.assistantTexts,call:e.call,partial:e.partial,scenarioId:e.scenarioId,sessionId:e.sessionId,sessionMetadata:e.sessionMetadata,status:e.status,turns:e.turns,type:"replay"};case"session":return{sessionId:e.sessionId,sessionMetadata:e.sessionMetadata,scenarioId:e.scenarioId,status:e.status,type:"session"};case"turn":return{turn:e.turn,type:"turn"};default:return null}};var H=(e,n,t,o)=>{e.push({code:t,message:o,severity:n})};var $e=(e)=>e.length===0?void 0:e.reduce((n,t)=>n+t,0)/e.length,K=(e)=>e.length===0?void 0:Math.max(...e);var b=(e,n)=>{let t=e[n];return typeof t==="number"&&Number.isFinite(t)?t:void 0},Z=(e,n)=>{let t=e[n];return typeof t==="boolean"?t:void 0},D=(e,n)=>{let t=e[n];return typeof t==="string"?t:void 0},ne=(e)=>String(e.id??D(e,"ssrc")??b(e,"ssrc")??D(e,"trackIdentifier")??D(e,"mid")??"unknown"),he=(e)=>e===void 0?void 0:e*1000;var ke=(e)=>{let n={};for(let[t,o]of Object.entries(e))if(o===null||typeof o==="boolean"||typeof o==="number"||typeof o==="string")n[t]=o;return n};var ye=(e={})=>{let n=e.stats??[],t=[],o=n.filter((s)=>s.type==="inbound-rtp"&&D(s,"kind")!=="video"),r=n.filter((s)=>s.type==="outbound-rtp"&&D(s,"kind")!=="video"),i=n.filter((s)=>s.type==="candidate-pair"),c=n.filter((s)=>(s.type==="track"||s.type==="media-source")&&D(s,"kind")==="audio"),l=i.filter((s)=>Z(s,"selected")===!0||Z(s,"nominated")===!0||D(s,"state")==="succeeded").length,g=c.filter((s)=>D(s,"readyState")!=="ended"&&D(s,"trackState")!=="ended"&&Z(s,"ended")!==!0).length,y=c.filter((s)=>D(s,"readyState")==="ended"||D(s,"trackState")==="ended"||Z(s,"ended")===!0).length,d=o.reduce((s,S)=>s+(b(S,"packetsReceived")??0),0),h=r.reduce((s,S)=>s+(b(S,"packetsSent")??0),0),a=[...o,...r].reduce((s,S)=>s+Math.max(0,b(S,"packetsLost")??0),0),M=d+a,C=M===0?0:a/M,I=o.reduce((s,S)=>s+(b(S,"bytesReceived")??0),0),w=r.reduce((s,S)=>s+(b(S,"bytesSent")??0),0),R=K(i.map((s)=>he(b(s,"currentRoundTripTime")??b(s,"roundTripTime"))).filter((s)=>s!==void 0)),u=K([...o,...r].map((s)=>he(b(s,"jitter"))).filter((s)=>s!==void 0)),O=K(o.map((s)=>{let S=b(s,"jitterBufferDelay"),L=b(s,"jitterBufferEmittedCount");return S!==void 0&&L!==void 0&&L>0?S/L*1000:void 0}).filter((s)=>s!==void 0)),U=c.map((s)=>b(s,"audioLevel")).filter((s)=>s!==void 0);if(e.requireConnectedCandidatePair&&i.length>0&&l===0)H(t,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(e.requireLiveAudioTrack&&g===0)H(t,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(e.maxPacketLossRatio!==void 0&&C>e.maxPacketLossRatio)H(t,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(C)} above ${String(e.maxPacketLossRatio)}.`);if(e.maxRoundTripTimeMs!==void 0&&R!==void 0&&R>e.maxRoundTripTimeMs)H(t,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(R)}ms above ${String(e.maxRoundTripTimeMs)}ms.`);if(e.maxJitterMs!==void 0&&u!==void 0&&u>e.maxJitterMs)H(t,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(u)}ms above ${String(e.maxJitterMs)}ms.`);return{activeCandidatePairs:l,audioLevelAverage:$e(U),bytesReceived:I,bytesSent:w,checkedAt:Date.now(),endedAudioTracks:y,inboundPackets:d,issues:t,jitterBufferDelayMs:O,jitterMs:u,liveAudioTracks:g,outboundPackets:h,packetLossRatio:C,packetsLost:a,roundTripTimeMs:R,status:t.some((s)=>s.severity==="error")?"fail":t.length>0?"warn":"pass",totalStats:n.length}},Ce=async(e)=>{return[...(await e.peerConnection.getStats(e.selector??null)).values()].map(ke)};var fe=(e={})=>{let n=e.stats??[],t=e.previousStats??[],o=[],r=new Map(t.map((a)=>[ne(a),a])),c=n.filter((a)=>(a.type==="inbound-rtp"||a.type==="outbound-rtp")&&D(a,"kind")!=="video"&&D(a,"mediaType")!=="video").map((a)=>{let M=a.type==="outbound-rtp"?"outbound":"inbound",C=M==="outbound"?"packetsSent":"packetsReceived",I=M==="outbound"?"bytesSent":"bytesReceived",w=r.get(ne(a)),R=b(a,C),u=w?b(w,C):void 0,O=b(a,I),U=w?b(w,I):void 0,s=a.timestamp!==void 0&&w?.timestamp!==void 0?a.timestamp-w.timestamp:void 0;return{bytesDelta:O!==void 0&&U!==void 0?O-U:void 0,currentPackets:R,direction:M,id:ne(a),packetDelta:R!==void 0&&u!==void 0?R-u:void 0,previousPackets:u,timeDeltaMs:s}}),l=c.filter((a)=>a.direction==="inbound"),g=c.filter((a)=>a.direction==="outbound"),y=K(c.map((a)=>a.timeDeltaMs).filter((a)=>a!==void 0)),d=l.filter((a)=>e.maxInboundPacketStallMs!==void 0&&a.timeDeltaMs!==void 0&&a.timeDeltaMs>=e.maxInboundPacketStallMs&&a.packetDelta!==void 0&&a.packetDelta<=0).length,h=g.filter((a)=>e.maxOutboundPacketStallMs!==void 0&&a.timeDeltaMs!==void 0&&a.timeDeltaMs>=e.maxOutboundPacketStallMs&&a.packetDelta!==void 0&&a.packetDelta<=0).length;if(e.requireInboundAudio&&l.length===0)H(o,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(e.requireOutboundAudio&&g.length===0)H(o,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(e.maxGapMs!==void 0&&y!==void 0&&y>e.maxGapMs)H(o,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(y)}ms above ${String(e.maxGapMs)}ms.`);if(d>0)H(o,"error","media.webrtc_inbound_stalled",`${String(d)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(h>0)H(o,"error","media.webrtc_outbound_stalled",`${String(h)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:l.length,issues:o,maxObservedGapMs:y,outboundAudioStreams:g.length,stalledInboundStreams:d,stalledOutboundStreams:h,status:o.some((a)=>a.severity==="error")?"fail":o.length>0?"warn":"pass",streams:c,totalStats:n.length}};var qe="/api/voice/browser-media",Xe=5000,ze=async(e)=>e.peerConnection??await e.getPeerConnection?.()??null,Ye=async(e,n)=>{let t=n.fetch??globalThis.fetch;if(!t)return;await t(n.path??qe,{body:JSON.stringify(e),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},Te=(e)=>{let n=null,t=[],o=async()=>{let c=await ze(e);if(!c)return;let l=await Ce({peerConnection:c}),g=ye({...e,stats:l}),y=e.continuity===!1?void 0:fe({...e.continuity,previousStats:t,stats:l}),d={at:Date.now(),continuity:y,report:g,scenarioId:e.getScenarioId?.()??null,sessionId:e.getSessionId?.()??null};return t=l,e.onReport?.(d),await Ye(d,e),d},r=()=>{o().catch((c)=>{e.onError?.(c)})},i=()=>{if(n)clearInterval(n),n=null};return{close:i,reportOnce:o,start:()=>{if(n)return;r(),n=setInterval(r,e.intervalMs??Xe)},stop:i}};var B=()=>{},Je=()=>B,Qe={callControl:B,close:B,endTurn:B,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",send:B,sendAudio:B,simulateDisconnect:B,start:()=>{},subscribe:Je},Ze=()=>crypto.randomUUID(),Ke=(e,n,t)=>{let{hostname:o,port:r,protocol:i}=window.location,c=i==="https:"?"wss:":"ws:",l=r?`:${r}`:"",g=new URL(`${c}//${o}${l}${e}`);if(g.searchParams.set("sessionId",n),t)g.searchParams.set("scenarioId",t);return g.toString()},me=(e)=>{if(!e||typeof e!=="object"||!("type"in e))return!1;switch(e.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}},je=(e)=>{if(typeof e.data!=="string")return null;try{let n=JSON.parse(e.data);return me(n)?n:null}catch{return null}},Se=(e,n={})=>{if(typeof window>"u")return Qe;let t=new Set,o=n.reconnect!==!1,r=n.maxReconnectAttempts??10,i=n.pingInterval??30000,c={isConnected:!1,pendingMessages:[],scenarioId:n.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:n.sessionId??Ze(),ws:null},l=(s)=>{t.forEach((S)=>S(s))},g=()=>{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 s=c.pendingMessages.shift();if(s!==void 0)c.ws.send(s)}},d=()=>{let s=Date.now()+500;c.reconnectAttempts+=1,l({reconnect:{attempts:c.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:r,nextAttemptAt:s,status:"reconnecting"},type:"connection"}),c.reconnectTimeout=setTimeout(()=>{if(c.reconnectAttempts>r){l({reconnect:{attempts:c.reconnectAttempts,maxAttempts:r,status:"exhausted"},type:"connection"});return}h()},500)},h=()=>{let s=new WebSocket(Ke(e,c.sessionId,c.scenarioId));s.binaryType="arraybuffer",s.onopen=()=>{let S=c.reconnectAttempts>0;if(c.isConnected=!0,y(),S)l({reconnect:{attempts:c.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:r,status:"resumed"},type:"connection"}),c.reconnectAttempts=0;t.forEach((L)=>L({scenarioId:c.scenarioId??void 0,sessionId:c.sessionId,status:"active",type:"session"})),c.pingInterval=setInterval(()=>{if(s.readyState===1)s.send(JSON.stringify({type:"ping"}))},i)},s.onmessage=(S)=>{let L=je(S);if(!L)return;if(L.type==="session")c.sessionId=L.sessionId,c.scenarioId=L.scenarioId??c.scenarioId;t.forEach((X)=>X(L))},s.onclose=(S)=>{if(c.isConnected=!1,g(),o&&S.code!==1000&&c.reconnectAttempts<r)d();else if(o&&S.code!==1000)l({reconnect:{attempts:c.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:r,status:"exhausted"},type:"connection"})},c.ws=s},a=(s)=>{if(c.ws?.readyState===1){c.ws.send(s);return}c.pendingMessages.push(s)},M=(s)=>{a(JSON.stringify(s))},C=(s={})=>{if(s.sessionId)c.sessionId=s.sessionId;if(s.scenarioId)c.scenarioId=s.scenarioId;M({type:"start",sessionId:c.sessionId,scenarioId:c.scenarioId??void 0})},I=(s)=>{a(s)},w=()=>{M({type:"end_turn"})},R=(s)=>{M({...s,type:"call_control"})},u=()=>{if(g(),c.ws)c.ws.close(1000),c.ws=null;c.isConnected=!1,t.clear()},O=()=>{if(c.ws?.readyState===1)c.ws.close(4000,"absolutejs-voice-reconnect-proof")},U=(s)=>{return t.add(s),()=>{t.delete(s)}};return h(),{callControl:R,close:u,endTurn:w,getReadyState:()=>c.ws?.readyState??3,getScenarioId:()=>c.scenarioId??"",getSessionId:()=>c.sessionId,send:M,sendAudio:I,simulateDisconnect:O,start:C,subscribe:U}};var ve=()=>({attempts:0,maxAttempts:0,status:"idle"}),Fe=()=>({assistantAudio:[],assistantTexts:[],call:null,error:null,isConnected:!1,sessionMetadata:null,scenarioId:null,partial:"",reconnect:ve(),sessionId:null,status:"idle",turns:[]}),Me=()=>{let e=Fe(),n=new Set,t=()=>{n.forEach((r)=>r())};return{dispatch:(r)=>{switch(r.type){case"audio":e={...e,assistantAudio:[...e.assistantAudio,{chunk:r.chunk,format:r.format,receivedAt:r.receivedAt,turnId:r.turnId}]};break;case"assistant":e={...e,assistantTexts:[...e.assistantTexts,r.text]};break;case"complete":e={...e,sessionId:r.sessionId,status:"completed"};break;case"call_lifecycle":e={...e,call:{...e.call,disposition:r.event.type==="end"?r.event.disposition:e.call?.disposition,endedAt:r.event.type==="end"?r.event.at:e.call?.endedAt,events:[...e.call?.events??[],r.event],lastEventAt:r.event.at,startedAt:e.call?.startedAt??r.event.at},sessionId:r.sessionId};break;case"connected":e={...e,isConnected:!0,reconnect:e.reconnect.status==="reconnecting"?{...e.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:e.reconnect};break;case"connection":e={...e,reconnect:r.reconnect};break;case"disconnected":e={...e,isConnected:!1};break;case"error":e={...e,error:r.message};break;case"final":e={...e,partial:r.transcript.text,turns:e.turns.map((i)=>i)};break;case"partial":e={...e,partial:r.transcript.text};break;case"replay":e={...e,assistantTexts:[...r.assistantTexts],call:r.call??null,error:null,isConnected:r.status==="active",partial:r.partial,reconnect:e.reconnect.status==="reconnecting"?{...e.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:e.reconnect,scenarioId:r.scenarioId??e.scenarioId,sessionId:r.sessionId,sessionMetadata:r.sessionMetadata??e.sessionMetadata,status:r.status,turns:[...r.turns]};break;case"session":e={...e,error:null,scenarioId:r.scenarioId??e.scenarioId,isConnected:r.status==="active",sessionId:r.sessionId,sessionMetadata:r.sessionMetadata??e.sessionMetadata,status:r.status};break;case"turn":e={...e,partial:"",turns:[...e.turns,r.turn]};break}t()},getServerSnapshot:()=>e,getSnapshot:()=>e,subscribe:(r)=>{return n.add(r),()=>{n.delete(r)}}}};var Ie=(e,n={})=>{let t=Se(e,n),o=Me(),r=n.browserMedia&&typeof window<"u"?Te({...n.browserMedia,getScenarioId:()=>n.browserMedia?n.browserMedia.getScenarioId?.()??t.getScenarioId():t.getScenarioId(),getSessionId:()=>n.browserMedia?n.browserMedia.getSessionId?.()??t.getSessionId():t.getSessionId()}):null,i=new Set,c=(d)=>Promise.resolve().then(()=>{if(!d?.sessionId&&!d?.scenarioId)return;t.start(d),r?.start()}),l=()=>{i.forEach((d)=>d())},g=()=>{if(!n.reconnectReportPath||typeof fetch>"u")return;let d=o.getSnapshot(),h=JSON.stringify({at:Date.now(),reconnect:d.reconnect,scenarioId:d.scenarioId,sessionId:t.getSessionId(),turnIds:d.turns.map((a)=>a.id)});fetch(n.reconnectReportPath,{body:h,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},y=t.subscribe((d)=>{let h=Ae(d);if(h){if(o.dispatch(h),d.type==="connection")g();l()}});return{callControl(d){t.callControl(d)},close(){y(),r?.close(),t.close(),o.dispatch({type:"disconnected"}),l()},endTurn(){t.endTurn()},get error(){return o.getSnapshot().error},getServerSnapshot(){return o.getServerSnapshot()},getSnapshot(){return o.getSnapshot()},get isConnected(){return o.getSnapshot().isConnected},get scenarioId(){return o.getSnapshot().scenarioId},get sessionMetadata(){return o.getSnapshot().sessionMetadata},start:c,get partial(){return o.getSnapshot().partial},get reconnect(){return o.getSnapshot().reconnect},get sessionId(){return t.getSessionId()},get status(){return o.getSnapshot().status},get turns(){return o.getSnapshot().turns},get assistantTexts(){return o.getSnapshot().assistantTexts},get assistantAudio(){return o.getSnapshot().assistantAudio},get call(){return o.getSnapshot().call},sendAudio(d){t.sendAudio(d)},simulateDisconnect(){t.simulateDisconnect()},subscribe(d){return i.add(d),()=>{i.delete(d)}}}};var Ve=(e)=>{if(!e||e.enabled===!1)return;return{enabled:!0,maxGain:e.maxGain??3,noiseGateAttenuation:e.noiseGateAttenuation??0.15,noiseGateThreshold:e.noiseGateThreshold??0.006,targetLevel:e.targetLevel??0.08}};var pe={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}},en={general:{},"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var we=(e)=>{let n=e?.profile??"fast",t=e?.qualityProfile??"general",o=pe[n],r=en[t];return{profile:n,qualityProfile:t,silenceMs:e?.silenceMs??r.silenceMs??o.silenceMs,speechThreshold:e?.speechThreshold??r.speechThreshold??o.speechThreshold,transcriptStabilityMs:e?.transcriptStabilityMs??r.transcriptStabilityMs??o.transcriptStabilityMs}};var nn={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:{qualityProfile:"short-command",profile:"balanced"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"general",profile:"fast"}},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:{qualityProfile:"accent-heavy",profile:"long-form"}},"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:{qualityProfile:"accent-heavy",profile:"long-form"}},"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:{qualityProfile:"noisy-room",profile:"long-form",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:{qualityProfile:"noisy-room",profile:"long-form",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:{qualityProfile:"noisy-room",profile:"long-form",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:{qualityProfile:"noisy-room",profile:"long-form"}}},Le=(e="default")=>{let n=nn[e];return{audioConditioning:Ve(n.audioConditioning),capture:{channelCount:n.capture?.channelCount??1,sampleRateHz:n.capture?.sampleRateHz??16000},connection:{...n.connection},name:e,sttLifecycle:n.sttLifecycle??"continuous",turnDetection:we(n.turnDetection)}};var on=(e)=>({assistantAudio:[...e.assistantAudio],assistantTexts:[...e.assistantTexts],call:e.call,error:e.error,isConnected:e.isConnected,isRecording:!1,partial:e.partial,reconnect:e.reconnect,recordingError:null,sessionId:e.sessionId,sessionMetadata:e.sessionMetadata,scenarioId:e.scenarioId,status:e.status,turns:[...e.turns]}),m=(e,n={})=>{let t=Le(n.preset),o=Ie(e,{...t.connection,...n.connection}),r=null,i=on(o),c=new Set,l=()=>{for(let C of c)C()},g=()=>{if(i={...i,assistantAudio:[...o.assistantAudio],assistantTexts:[...o.assistantTexts],call:o.call,error:o.error,isConnected:o.isConnected,partial:o.partial,reconnect:o.reconnect,sessionId:o.sessionId,sessionMetadata:o.sessionMetadata,scenarioId:o.scenarioId,status:o.status,turns:[...o.turns]},n.autoStopOnComplete!==!1&&i.status==="completed"&&i.isRecording)r?.stop(),r=null,i={...i,isRecording:!1};l()},y=o.subscribe(g);g();let d=()=>{if(r)return r;return r=ge({channelCount:n.capture?.channelCount??t.capture.channelCount,onLevel:n.capture?.onLevel,onAudio:(C)=>{if(n.capture?.onAudio){n.capture.onAudio(C,o.sendAudio);return}o.sendAudio(C)},sampleRateHz:n.capture?.sampleRateHz??t.capture.sampleRateHz}),r},h=()=>{r?.stop(),r=null,i={...i,isRecording:!1},l()},a=async()=>{if(i.isRecording)return;try{i={...i,recordingError:null},l(),await d().start(),i={...i,isRecording:!0},l()}catch(C){throw r=null,i={...i,isRecording:!1,recordingError:C instanceof Error?C.message:String(C)},l(),C}};return{bindHTMX(C){return de(o,C)},callControl:(C)=>o.callControl(C),close:()=>{y(),h(),o.close()},endTurn:()=>o.endTurn(),get error(){return i.error},getServerSnapshot:()=>i,getSnapshot:()=>i,get isConnected(){return i.isConnected},get isRecording(){return i.isRecording},get partial(){return i.partial},get recordingError(){return i.recordingError},get reconnect(){return i.reconnect},sendAudio:(C)=>o.sendAudio(C),simulateDisconnect:()=>o.simulateDisconnect(),get sessionId(){return i.sessionId},get sessionMetadata(){return i.sessionMetadata},get scenarioId(){return i.scenarioId},startRecording:a,get status(){return i.status},stopRecording:h,subscribe:(C)=>{return c.add(C),()=>{c.delete(C)}},toggleRecording:async()=>{if(i.isRecording){h();return}await a()},get turns(){return i.turns},get assistantTexts(){return i.assistantTexts},get assistantAudio(){return i.assistantAudio},get call(){return i.call}}};var tn=()=>({activeSourceCount:0,error:null,isActive:!1,isPlaying:!1,lastInterruptLatencyMs:void 0,lastPlaybackStopLatencyMs:void 0,processedChunkCount:0,queuedChunkCount:0}),cn=()=>{if(typeof window>"u")return typeof AudioContext>"u"?void 0:AudioContext;return window.AudioContext??window.webkitAudioContext},rn=(e,n)=>{let t=n.format;if(t.container!=="raw"||t.encoding!=="pcm_s16le")throw Error(`Unsupported assistant audio format: ${t.container}/${t.encoding}`);let o=n.chunk,r=Math.max(1,t.channels),i=Math.floor(o.byteLength/2),c=Math.max(1,Math.floor(i/r)),l=e.createBuffer(r,c,t.sampleRateHz),g=new DataView(o.buffer,o.byteOffset,o.byteLength);for(let y=0;y<r;y+=1){let d=l.getChannelData(y);for(let h=0;h<c;h+=1){let M=(h*r+y)*2;if(M+1>=o.byteLength){d[h]=0;continue}d[h]=g.getInt16(M,!0)/32768}}return l},j=(e,n={})=>{let t=new Set,o=new Set,r=(n.lookaheadMs??15)/1000,i=tn(),c=null,l=null,g=0,y=Promise.resolve(),d=null,h=null,a=null,M=null,C=()=>{for(let A of t)A()},I=(A)=>{i={...i,...A},C()},w=()=>{if(i.error!==null)I({error:null})},R=()=>{if(M!==null)clearTimeout(M),M=null},u=(A)=>{R(),d=null,I({activeSourceCount:o.size,isPlaying:!1,lastInterruptLatencyMs:A,lastPlaybackStopLatencyMs:i.lastPlaybackStopLatencyMs??A}),a?.(),a=null,h=null},O=(A)=>{if(!A)return 0;return Math.max(0,((A.baseLatency??0)+(A.outputLatency??0))*1000)},U=(A)=>{if(!l)return;let f=1;if(l.gain.setValueAtTime){l.gain.setValueAtTime(f,A?.currentTime??0);return}l.gain.value=f},s=(A)=>{if(!l)return;let f=0;if(l.gain.setValueAtTime){l.gain.setValueAtTime(f,A?.currentTime??0);return}l.gain.value=f},S=()=>{if(d===null||o.size>0)return;u(Date.now()-d)},L=async()=>{if(c)return c;if(n.createAudioContext)c=n.createAudioContext();else{let A=cn();if(!A)throw Error("Assistant audio playback requires AudioContext support.");c=new A}if(c.createGain)l=c.createGain(),l.connect?.(c.destination);return g=c.currentTime,c},X=async(A)=>{let f=await L(),E=rn(f,A),V=f.createBufferSource();V.buffer=E,V.connect(l??f.destination),V.onended=()=>{o.delete(V),V.disconnect?.(),I({activeSourceCount:o.size,isPlaying:o.size>0&&i.isActive}),S()};let Y=Math.max(f.currentTime+r,g);g=Y+E.duration,o.add(V),I({activeSourceCount:o.size,isPlaying:!0}),V.start(Y)},_=(A)=>{for(let f of[...o])f.stop?.();if(g=c?c.currentTime:0,A?.forceClear){for(let f of o)f.disconnect?.();o.clear(),S()}},W=async()=>{if(!i.isActive)return;let A=e.assistantAudio.slice(i.processedChunkCount);if(A.length===0)return;try{w();for(let f of A)await X(f);I({processedChunkCount:e.assistantAudio.length,queuedChunkCount:i.queuedChunkCount+A.length})}catch(f){I({error:f instanceof Error?f.message:String(f)})}},P=()=>{return y=y.then(()=>W(),()=>W()),y},$=e.subscribe(()=>{if(n.autoStart&&!i.isActive&&e.assistantAudio.length>0){N.start();return}if(i.isActive)P()}),N={close:async()=>{if($(),_({forceClear:!0}),R(),a?.(),a=null,h=null,d=null,c&&c.state!=="closed")await c.close();c=null,l?.disconnect?.(),l=null,g=0,I({activeSourceCount:0,isActive:!1,isPlaying:!1})},get activeSourceCount(){return i.activeSourceCount},get error(){return i.error},getSnapshot:()=>i,get isActive(){return i.isActive},get isPlaying(){return i.isPlaying},interrupt:async()=>{let A=Date.now(),f=await L();d=A,s(f);let E=Date.now()-A+O(f);if(I({isActive:!1,isPlaying:o.size>0,lastPlaybackStopLatencyMs:E}),o.size===0){u(E);return}if(!h)h=new Promise((V)=>{a=V});R(),M=setTimeout(()=>{for(let V of o)V.disconnect?.();o.clear(),u(Date.now()-A)},250),_(),await h},get lastInterruptLatencyMs(){return i.lastInterruptLatencyMs},get lastPlaybackStopLatencyMs(){return i.lastPlaybackStopLatencyMs},pause:async()=>{if(!c){I({activeSourceCount:0,isActive:!1,isPlaying:!1});return}await c.suspend(),I({activeSourceCount:o.size,isActive:!1,isPlaying:!1})},get processedChunkCount(){return i.processedChunkCount},get queuedChunkCount(){return i.queuedChunkCount},start:async()=>{try{w();let A=await L();if(U(A),A.state==="suspended")await A.resume();I({activeSourceCount:o.size,isActive:!0,isPlaying:A.state==="running"}),await P()}catch(A){throw I({error:A instanceof Error?A.message:String(A),isActive:!1,isPlaying:!1}),A}},subscribe:(A)=>{return t.add(A),()=>{t.delete(A)}}};return N};var sn=()=>`barge-in:${Date.now()}:${crypto.randomUUID?.()??Math.random().toString(36).slice(2)}`,ln=(e,n)=>{let t=e.filter((c)=>c.status==="stopped"),o=t.map((c)=>c.latencyMs).filter((c)=>typeof c==="number"),r=t.filter((c)=>typeof c.latencyMs==="number"&&c.latencyMs>n).length,i=t.length-r;return{averageLatencyMs:o.length>0?Math.round(o.reduce((c,l)=>c+l,0)/o.length):void 0,events:[...e],failed:r,lastEvent:e.at(-1),passed:i,status:e.length===0?"empty":r>0?"fail":t.length===0?"warn":"pass",thresholdMs:n,total:t.length}},ue=(e={})=>{let n=new Set,t=e.thresholdMs??250,o=e.fetch??globalThis.fetch,r=[],i=()=>{for(let g of n)g()},c=(g)=>{if(!e.path||typeof o!=="function")return;o(e.path,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},method:"POST"}).catch(()=>{})},l=(g,y)=>{let d={at:Date.now(),id:sn(),latencyMs:y.latencyMs,playbackStopLatencyMs:y.playbackStopLatencyMs,reason:y.reason,sessionId:y.sessionId,status:g,thresholdMs:t};return r.push(d),c(d),i(),d};return{getSnapshot:()=>ln(r,t),recordRequested:(g)=>l("requested",g),recordSkipped:(g)=>l("skipped",g),recordStopped:(g)=>l("stopped",g),subscribe:(g)=>{return n.add(g),()=>{n.delete(g)}}}};var an=0.08,dn=(e,n={})=>(n.enabled??!0)&&e>=(n.interruptThreshold??an),oe=(e,n,t={})=>{let o=e.partial,r=(c)=>{if(!n.isPlaying||t.enabled===!1){t.monitor?.recordSkipped({reason:c,sessionId:e.sessionId});return}t.monitor?.recordRequested({reason:c,sessionId:e.sessionId}),n.interrupt().then(()=>{t.monitor?.recordStopped({latencyMs:n.lastInterruptLatencyMs,playbackStopLatencyMs:n.lastPlaybackStopLatencyMs,reason:c,sessionId:e.sessionId})})},i=e.subscribe(()=>{if(t.interruptOnPartial===!1){o=e.partial;return}if(!o&&e.partial)r("partial-transcript");o=e.partial});return{close:()=>{i()},handleLevel:(c)=>{if(dn(c,t))r("input-level")},sendAudio:(c)=>{r("manual-audio"),e.sendAudio(c)}}};var se=48,gn=320,An=88,hn="Guided test",yn="General recording",Cn="Pick a scenario to begin the demo.",fn="I can walk you through a short guided voice test.",Tn="I can capture one freeform recording and confirm that it landed.",Sn="Choose a scenario to begin. Guided test asks follow-up prompts. General recording just captures what you say.",Mn="Click Start general recording to capture one freeform answer.",_e="Speak freely. When you pause, the recording will be captured.",re="Recording saved. Start again if you want another capture.",Ee="Guided test complete. Review the saved summary below.",Pe="All prompts are covered. You can stop the microphone or keep speaking for extra detail.",In="Ready. Start guided test or general recording to begin.",Vn="Live. Answer the prompt, then click Stop microphone when finished.",Re=["Start with a quick introduction about who you are.","Now describe what you are trying to do or test.","Finish with any detail that feels blocked, risky, or unclear."],De=(e,n,t)=>Math.min(t,Math.max(n,e)),q=(e)=>e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll(\'"\',"&quot;").replaceAll("\'","&#39;"),te=(e,n)=>{let t=e[n];if(typeof t==="string"&&t.trim())return t;return null},ie=(e)=>{if(typeof e==="string"&&e.trim())return e;if(e instanceof Error&&e.message.trim())return e.message;if(e&&typeof e==="object"){let n=e,t=te(n,"message")??te(n,"reason")??te(n,"description");if(t)return t;if("error"in n)return ie(n.error);if("cause"in n)return ie(n.cause);try{return JSON.stringify(e)}catch{}}return"Unexpected error"},wn=(e)=>{let n=[e.status];if(e.attempts>0||e.maxAttempts>0)n.push(`${e.attempts}/${e.maxAttempts} attempts`);if(e.nextAttemptAt){let t=Math.max(0,e.nextAttemptAt-Date.now());n.push(`retry in ${Math.ceil(t/100)/10}s`)}return n.join(" \xB7 ")},v=(e=se)=>Array.from({length:e},()=>0),be=(e,n,t=se)=>{let o=e.slice(-(t-1));o.push(De(n,0,1));while(o.length<t)o.unshift(0);return o},Ln=(e,n=gn,t=An)=>{let o=e.length>1?e:v(se),r=n/(o.length-1),i=t/2,c=t*0.34;if(Math.max(...o,0)<=0.015)return`M 0 ${i} L ${n} ${i}`;let g=o.map((d,h)=>{let a=h*0.76,M=Math.sin(a)*0.78+Math.sin(a*0.41)*0.22,C=d*c,I=r*h,w=De(i+M*C,8,t-8);return{x:I,y:w}});if(g.length===0)return`M 0 ${i} L ${n} ${i}`;let y=`M ${g[0]?.x??0} ${g[0]?.y??i}`;for(let d=1;d<g.length;d+=1){let h=g[d-1],a=g[d];if(!h||!a)continue;let M=(h.x+a.x)/2;y+=` Q ${M} ${h.y} ${a.x} ${a.y}`}return y},un=(e)=>{if(!e)return Re;try{let n=JSON.parse(e);if(Array.isArray(n)){let t=n.filter((o)=>typeof o==="string").map((o)=>o.trim()).filter(Boolean);if(t.length>0)return t}}catch{}return Re},ce=(e)=>{if(!e)return;let n=Number(e);return Number.isFinite(n)?n:void 0},Rn=(e,n,t)=>{if(!n)return null;let o=document.querySelector(n);return o instanceof t?o:null},x=(e,n,t,o)=>{let r=n?document.querySelector(n):null;if(r instanceof t)return r;let i=e.querySelector(`#${o}`);if(i instanceof t)return i;throw Error(`Voice HTMX bootstrap could not find the required element "${o}".`)},bn=(e)=>{if(!e.mode)return Cn;if(!e.hasStarted)return e.mode==="guided"?fn:Tn;if(e.status==="completed")return e.mode==="guided"?Ee:re;if(e.mode==="general")return _e;return e.guidedPrompts[e.turnCount]??Pe},_n=(e)=>{if(!e.mode)return Sn;if(e.status==="completed")return e.mode==="guided"?Ee:re;if(!e.hasStarted)return e.mode==="guided"?`Click Start guided test to begin. First prompt: ${e.guidedPrompts[0]??"Answer the first prompt."}`:Mn;if(e.mode==="general")return e.turnCount===0?_e:re;return e.guidedPrompts[e.turnCount]??Pe},En=(e)=>{let n=e.dataset.voiceGuidedPath,t=e.dataset.voiceGeneralPath;if(!n||!t)throw Error("Voice HTMX bootstrap requires data-voice-guided-path and data-voice-general-path.");let o=un(e.dataset.voiceGuidedPrompts),r=e.dataset.voiceGuidedLabel??hn,i=e.dataset.voiceGeneralLabel??yn,c=e.dataset.voiceReconnectReportPath,l=e.dataset.voiceBargeInPath,g=l?ue({path:l,thresholdMs:ce(e.dataset.voiceBargeInThresholdMs)}):null,y=ce(e.dataset.voiceBargeInRecentWindowMs)??4000,d=ce(e.dataset.voiceBargeInSpeechThreshold)??0.04,h=x(document,e.dataset.voiceSync,HTMLElement,"voice-htmx-sync"),a=x(e,e.dataset.voiceConnection,HTMLElement,"metric-connection"),M=x(e,e.dataset.voiceError,HTMLElement,"status-error"),C=x(e,e.dataset.voiceMicrophone,HTMLElement,"status-mic"),I=x(e,e.dataset.voicePrompt,HTMLElement,"status-prompt"),w=Rn(e,e.dataset.voiceReconnect,HTMLElement),R=x(e,e.dataset.voiceChat,HTMLElement,"chat-list"),u=x(e,e.dataset.voiceStartGuided,HTMLButtonElement,"start-guided"),O=x(e,e.dataset.voiceStartGeneral,HTMLButtonElement,"start-general"),U=x(e,e.dataset.voiceStop,HTMLButtonElement,"stop-mic"),s=x(e,e.dataset.voiceMonitor,HTMLElement,"voice-monitor"),S=x(e,e.dataset.voiceMonitorCopy,HTMLElement,"voice-monitor-copy"),L=x(e,e.dataset.voiceWaveGlow,SVGPathElement,"voice-wave-glow"),X=x(e,e.dataset.voiceWavePath,SVGPathElement,"voice-wave-path"),_=null,W={general:!1,guided:!1},P=!1,$=null,N=v(),A=null,f=null,E=m(n,{capture:{onAudio:(T,G)=>{if(A){A.sendAudio(T);return}G(T)},onLevel:(T)=>{A?.handleLevel(T),N=be(N,T),F()}},connection:{reconnectReportPath:c},preset:"guided-intake"}),V=m(t,{capture:{onAudio:(T,G)=>{if(f){f.sendAudio(T);return}G(T)},onLevel:(T)=>{f?.handleLevel(T),N=be(N,T),F()}},connection:{reconnectReportPath:c},preset:"dictation"}),Y=E.bindHTMX({element:h}),xe=V.bindHTMX({element:h}),J=j(E),Q=j(V);A=oe(E,J,{interruptThreshold:d,monitor:g??void 0}),f=oe(V,Q,{interruptThreshold:d,monitor:g??void 0});let z=()=>_==="general"?V:E,Dn=()=>_==="general"?Q:J,F=()=>{let T=Ln(N);L.setAttribute("d",T),X.setAttribute("d",T),S.innerHTML=`<span class="voice-live-dot"></span>${P?"Microphone live":"Microphone idle"}`,S.classList.toggle("is-live",P),s.classList.toggle("is-live",P)},k=()=>{let T=z(),G=(_?W[_]:!1)||T.turns.length>0,ae=T.status;if(a.textContent=T.isConnected?"Connected":"Waiting",M.textContent=$||T.error||"None",w)w.textContent=wn(T.reconnect);C.textContent=P?Vn:In,I.textContent=_n({guidedPrompts:o,hasStarted:G,mode:_,status:ae,turnCount:T.turns.length}),u.hidden=P,O.hidden=P,U.hidden=!P,R.innerHTML=`<article class="voice-chat-message assistant">\n <div class="voice-chat-role">${q(_==="general"?i:_==="guided"?r:"Voice demo")}</div>\n <p class="voice-turn-text">${q(bn({generalLabel:i,guidedLabel:r,guidedPrompts:o,hasStarted:G,mode:_,status:ae,turnCount:T.turns.length}))}</p>\n</article>${T.turns.map((p)=>`<div class="voice-chat-stack">\n <article class="voice-chat-message user">\n <div class="voice-chat-role">You</div>\n <p class="voice-turn-text">${q(p.text)}</p>\n </article>\n ${p.assistantText?`<article class="voice-chat-message assistant">\n <div class="voice-chat-role">${q(_==="general"?i:_==="guided"?r:"Guide")}</div>\n <p class="voice-turn-text">${q(p.assistantText)}</p>\n </article>`:""}\n</div>`).join("")}${T.partial?`<article class="voice-chat-message user pending">\n <div class="voice-chat-role">Speaking</div>\n <p class="voice-turn-text">${q(T.partial)}</p>\n</article>`:""}`,F()},Oe=()=>{z().stopRecording(),P=!1,$=null,N=v(),k()},le=async(T)=>{_=T,W={...W,[T]:!0};try{await z().startRecording(),$=null,P=!0,k()}catch(G){z().stopRecording(),P=!1,N=v(),$=ie(G),k()}};E.subscribe(()=>{if(E.assistantAudio.length>0)J.start().catch(()=>{});k()}),V.subscribe(()=>{if(V.assistantAudio.length>0)Q.start().catch(()=>{});k()}),u.addEventListener("click",()=>{le("guided")}),O.addEventListener("click",()=>{le("general")}),U.addEventListener("click",()=>{Oe()}),e.addEventListener("absolute-voice-simulate-disconnect",()=>{z().simulateDisconnect()}),window.addEventListener("beforeunload",()=>{E.stopRecording(),V.stopRecording(),A?.close(),f?.close(),J.close(),Q.close(),Y(),xe(),E.close(),V.close()}),k()},Pn=()=>{if(typeof window>"u"||typeof document>"u")return;let e=Array.from(document.querySelectorAll("[data-voice-htmx]"));for(let n of e)if(n instanceof HTMLElement)En(n)};Pn();export{Pn as initVoiceHTMX};\n';
5189
+ var HTMX_BOOTSTRAP_BUNDLE = 'var Nn=(n)=>{if(typeof n!=="string")return n;return document.querySelector(n)},Hn=(n,e,o,c)=>{let i=e??n.getAttribute("hx-get")??"";if(!i)return"";let s=new URL(i,window.location.origin);if(c)s.searchParams.set(o,c);else s.searchParams.delete(o);return`${s.pathname}${s.search}${s.hash}`},gn=(n,e)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let o=Nn(e.element);if(!o)return()=>{};let c=e.eventName??"voice-refresh",i=e.sessionQueryParam??"sessionId",s=()=>{let r=window,g=Hn(o,e.route,i,n.sessionId);if(g)o.setAttribute("hx-get",g);r.htmx?.process?.(o),r.htmx?.trigger?.(o,c)},t=n.subscribe(s);return s(),()=>{t()}};var Gn=(n)=>Math.max(-1,Math.min(1,n)),Bn=(n)=>{let e=new Int16Array(n.length);for(let o=0;o<n.length;o+=1){let c=Gn(n[o]??0);e[o]=c<0?c*32768:c*32767}return new Uint8Array(e.buffer)},Wn=(n)=>{let e=n instanceof Uint8Array?n:new Uint8Array(n);if(e.byteLength<2)return 0;let o=new Int16Array(e.buffer,e.byteOffset,Math.floor(e.byteLength/2));if(o.length===0)return 0;let c=0;for(let i of o){let s=i/32768;c+=s*s}return Math.min(1,Math.max(0,Math.sqrt(c/o.length)*5.5))},$n=(n,e,o)=>{if(e===o)return n;let c=e/o,i=Math.round(n.length/c),s=new Float32Array(i),t=0,r=0;while(t<s.length){let g=Math.round((t+1)*c),y=0,d=0;for(let h=r;h<g&&h<n.length;h+=1)y+=n[h]??0,d+=1;s[t]=d>0?y/d:0,t+=1,r=g}return s},An=(n)=>{let e=null,o=null,c=null,i=null;return{start:async()=>{if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let r=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!r)throw Error("Browser microphone capture requires AudioContext support.");i=await navigator.mediaDevices.getUserMedia({audio:{channelCount:n.channelCount??1}}),e=new r,o=e.createMediaStreamSource(i),c=e.createScriptProcessor(4096,1,1),c.onaudioprocess=(g)=>{let y=g.inputBuffer.getChannelData(0),d=$n(y,e?.sampleRate??48000,n.sampleRateHz??16000),h=Bn(d);n.onLevel?.(Wn(h)),n.onAudio(h)},o.connect(c),c.connect(e.destination)},stop:()=>{c?.disconnect(),o?.disconnect(),i?.getTracks().forEach((r)=>r.stop()),e?.close(),n.onLevel?.(0),e=null,i=null,c=null,o=null}}};var nn=(n)=>{if(typeof n==="string"&&n.trim())return n;if(n instanceof Error&&n.message.trim())return n.message;if(n&&typeof n==="object"){let e=n;for(let o of["message","reason","description"]){let c=e[o];if(typeof c==="string"&&c.trim())return c}if("error"in e)return nn(e.error);if("cause"in e)return nn(e.cause);try{return JSON.stringify(n)}catch{}}return"Unexpected error"},hn=(n)=>{switch(n.type){case"audio":return{chunk:Uint8Array.from(atob(n.chunkBase64),(e)=>e.charCodeAt(0)),format:n.format,receivedAt:n.receivedAt,turnId:n.turnId,type:"audio"};case"assistant":return{text:n.text,type:"assistant"};case"complete":return{sessionId:n.sessionId,type:"complete"};case"connection":return{reconnect:n.reconnect,type:"connection"};case"call_lifecycle":return{event:n.event,sessionId:n.sessionId,type:"call_lifecycle"};case"error":return{message:nn(n.message),type:"error"};case"final":return{transcript:n.transcript,type:"final"};case"partial":return{transcript:n.transcript,type:"partial"};case"replay":return{assistantTexts:n.assistantTexts,call:n.call,partial:n.partial,scenarioId:n.scenarioId,sessionId:n.sessionId,sessionMetadata:n.sessionMetadata,status:n.status,turns:n.turns,type:"replay"};case"session":return{sessionId:n.sessionId,sessionMetadata:n.sessionMetadata,scenarioId:n.scenarioId,status:n.status,type:"session"};case"turn":return{turn:n.turn,type:"turn"};default:return null}};var H=(n,e,o,c)=>{n.push({code:o,message:c,severity:e})};var qn=(n)=>n.length===0?void 0:n.reduce((e,o)=>e+o,0)/n.length,K=(n)=>n.length===0?void 0:Math.max(...n);var _=(n,e)=>{let o=n[e];return typeof o==="number"&&Number.isFinite(o)?o:void 0},Z=(n,e)=>{let o=n[e];return typeof o==="boolean"?o:void 0},O=(n,e)=>{let o=n[e];return typeof o==="string"?o:void 0},en=(n)=>String(n.id??O(n,"ssrc")??_(n,"ssrc")??O(n,"trackIdentifier")??O(n,"mid")??"unknown"),yn=(n)=>n===void 0?void 0:n*1000;var Xn=(n)=>{let e={};for(let[o,c]of Object.entries(n))if(c===null||typeof c==="boolean"||typeof c==="number"||typeof c==="string")e[o]=c;return e};var Cn=(n={})=>{let e=n.stats??[],o=[],c=e.filter((l)=>l.type==="inbound-rtp"&&O(l,"kind")!=="video"),i=e.filter((l)=>l.type==="outbound-rtp"&&O(l,"kind")!=="video"),s=e.filter((l)=>l.type==="candidate-pair"),t=e.filter((l)=>(l.type==="track"||l.type==="media-source")&&O(l,"kind")==="audio"),r=s.filter((l)=>Z(l,"selected")===!0||Z(l,"nominated")===!0||O(l,"state")==="succeeded").length,g=t.filter((l)=>O(l,"readyState")!=="ended"&&O(l,"trackState")!=="ended"&&Z(l,"ended")!==!0).length,y=t.filter((l)=>O(l,"readyState")==="ended"||O(l,"trackState")==="ended"||Z(l,"ended")===!0).length,d=c.reduce((l,S)=>l+(_(S,"packetsReceived")??0),0),h=i.reduce((l,S)=>l+(_(S,"packetsSent")??0),0),a=[...c,...i].reduce((l,S)=>l+Math.max(0,_(S,"packetsLost")??0),0),M=d+a,C=M===0?0:a/M,I=c.reduce((l,S)=>l+(_(S,"bytesReceived")??0),0),L=i.reduce((l,S)=>l+(_(S,"bytesSent")??0),0),b=K(s.map((l)=>yn(_(l,"currentRoundTripTime")??_(l,"roundTripTime"))).filter((l)=>l!==void 0)),w=K([...c,...i].map((l)=>yn(_(l,"jitter"))).filter((l)=>l!==void 0)),u=K(c.map((l)=>{let S=_(l,"jitterBufferDelay"),R=_(l,"jitterBufferEmittedCount");return S!==void 0&&R!==void 0&&R>0?S/R*1000:void 0}).filter((l)=>l!==void 0)),U=t.map((l)=>_(l,"audioLevel")).filter((l)=>l!==void 0);if(n.requireConnectedCandidatePair&&s.length>0&&r===0)H(o,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(n.requireLiveAudioTrack&&g===0)H(o,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(n.maxPacketLossRatio!==void 0&&C>n.maxPacketLossRatio)H(o,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(C)} above ${String(n.maxPacketLossRatio)}.`);if(n.maxRoundTripTimeMs!==void 0&&b!==void 0&&b>n.maxRoundTripTimeMs)H(o,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(b)}ms above ${String(n.maxRoundTripTimeMs)}ms.`);if(n.maxJitterMs!==void 0&&w!==void 0&&w>n.maxJitterMs)H(o,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(w)}ms above ${String(n.maxJitterMs)}ms.`);return{activeCandidatePairs:r,audioLevelAverage:qn(U),bytesReceived:I,bytesSent:L,checkedAt:Date.now(),endedAudioTracks:y,inboundPackets:d,issues:o,jitterBufferDelayMs:u,jitterMs:w,liveAudioTracks:g,outboundPackets:h,packetLossRatio:C,packetsLost:a,roundTripTimeMs:b,status:o.some((l)=>l.severity==="error")?"fail":o.length>0?"warn":"pass",totalStats:e.length}},fn=async(n)=>{return[...(await n.peerConnection.getStats(n.selector??null)).values()].map(Xn)};var Tn=(n={})=>{let e=n.stats??[],o=n.previousStats??[],c=[],i=new Map(o.map((a)=>[en(a),a])),t=e.filter((a)=>(a.type==="inbound-rtp"||a.type==="outbound-rtp")&&O(a,"kind")!=="video"&&O(a,"mediaType")!=="video").map((a)=>{let M=a.type==="outbound-rtp"?"outbound":"inbound",C=M==="outbound"?"packetsSent":"packetsReceived",I=M==="outbound"?"bytesSent":"bytesReceived",L=i.get(en(a)),b=_(a,C),w=L?_(L,C):void 0,u=_(a,I),U=L?_(L,I):void 0,l=a.timestamp!==void 0&&L?.timestamp!==void 0?a.timestamp-L.timestamp:void 0;return{bytesDelta:u!==void 0&&U!==void 0?u-U:void 0,currentPackets:b,direction:M,id:en(a),packetDelta:b!==void 0&&w!==void 0?b-w:void 0,previousPackets:w,timeDeltaMs:l}}),r=t.filter((a)=>a.direction==="inbound"),g=t.filter((a)=>a.direction==="outbound"),y=K(t.map((a)=>a.timeDeltaMs).filter((a)=>a!==void 0)),d=r.filter((a)=>n.maxInboundPacketStallMs!==void 0&&a.timeDeltaMs!==void 0&&a.timeDeltaMs>=n.maxInboundPacketStallMs&&a.packetDelta!==void 0&&a.packetDelta<=0).length,h=g.filter((a)=>n.maxOutboundPacketStallMs!==void 0&&a.timeDeltaMs!==void 0&&a.timeDeltaMs>=n.maxOutboundPacketStallMs&&a.packetDelta!==void 0&&a.packetDelta<=0).length;if(n.requireInboundAudio&&r.length===0)H(c,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(n.requireOutboundAudio&&g.length===0)H(c,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(n.maxGapMs!==void 0&&y!==void 0&&y>n.maxGapMs)H(c,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(y)}ms above ${String(n.maxGapMs)}ms.`);if(d>0)H(c,"error","media.webrtc_inbound_stalled",`${String(d)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(h>0)H(c,"error","media.webrtc_outbound_stalled",`${String(h)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:r.length,issues:c,maxObservedGapMs:y,outboundAudioStreams:g.length,stalledInboundStreams:d,stalledOutboundStreams:h,status:c.some((a)=>a.severity==="error")?"fail":c.length>0?"warn":"pass",streams:t,totalStats:e.length}};var zn="/api/voice/browser-media",kn=5000,Yn=async(n)=>n.peerConnection??await n.getPeerConnection?.()??null,Jn=async(n,e)=>{let o=e.fetch??globalThis.fetch;if(!o)return;await o(e.path??zn,{body:JSON.stringify(n),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},Sn=(n)=>{let e=null,o=[],c=async()=>{let t=await Yn(n);if(!t)return;let r=await fn({peerConnection:t}),g=Cn({...n,stats:r}),y=n.continuity===!1?void 0:Tn({...n.continuity,previousStats:o,stats:r}),d={at:Date.now(),continuity:y,report:g,scenarioId:n.getScenarioId?.()??null,sessionId:n.getSessionId?.()??null};return o=r,n.onReport?.(d),await Jn(d,n),d},i=()=>{c().catch((t)=>{n.onError?.(t)})},s=()=>{if(e)clearInterval(e),e=null};return{close:s,reportOnce:c,start:()=>{if(e)return;i(),e=setInterval(i,n.intervalMs??kn)},stop:s}};var B=()=>{},Qn=()=>B,Zn={callControl:B,close:B,endTurn:B,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",send:B,sendAudio:B,simulateDisconnect:B,start:()=>{},subscribe:Qn},Kn=()=>crypto.randomUUID(),jn=(n,e,o)=>{let{hostname:c,port:i,protocol:s}=window.location,t=s==="https:"?"wss:":"ws:",r=i?`:${i}`:"",g=new URL(`${t}//${c}${r}${n}`);if(g.searchParams.set("sessionId",e),o)g.searchParams.set("scenarioId",o);return g.toString()},mn=(n)=>{if(!n||typeof n!=="object"||!("type"in n))return!1;switch(n.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}},Fn=(n)=>{if(typeof n.data!=="string")return null;try{let e=JSON.parse(n.data);return mn(e)?e:null}catch{return null}},Mn=(n,e={})=>{if(typeof window>"u")return Zn;let o=new Set,c=e.reconnect!==!1,i=e.maxReconnectAttempts??10,s=e.pingInterval??30000,t={isConnected:!1,pendingMessages:[],scenarioId:e.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:e.sessionId??Kn(),ws:null},r=(l)=>{o.forEach((S)=>S(l))},g=()=>{if(t.pingInterval)clearInterval(t.pingInterval),t.pingInterval=null;if(t.reconnectTimeout)clearTimeout(t.reconnectTimeout),t.reconnectTimeout=null},y=()=>{if(t.ws?.readyState!==1)return;while(t.pendingMessages.length>0){let l=t.pendingMessages.shift();if(l!==void 0)t.ws.send(l)}},d=()=>{let l=Date.now()+500;t.reconnectAttempts+=1,r({reconnect:{attempts:t.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:i,nextAttemptAt:l,status:"reconnecting"},type:"connection"}),t.reconnectTimeout=setTimeout(()=>{if(t.reconnectAttempts>i){r({reconnect:{attempts:t.reconnectAttempts,maxAttempts:i,status:"exhausted"},type:"connection"});return}h()},500)},h=()=>{let l=new WebSocket(jn(n,t.sessionId,t.scenarioId));l.binaryType="arraybuffer",l.onopen=()=>{let S=t.reconnectAttempts>0;if(t.isConnected=!0,y(),S)r({reconnect:{attempts:t.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:i,status:"resumed"},type:"connection"}),t.reconnectAttempts=0;o.forEach((R)=>R({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"}))},s)},l.onmessage=(S)=>{let R=Fn(S);if(!R)return;if(R.type==="session")t.sessionId=R.sessionId,t.scenarioId=R.scenarioId??t.scenarioId;o.forEach((z)=>z(R))},l.onclose=(S)=>{if(t.isConnected=!1,g(),c&&S.code!==1000&&t.reconnectAttempts<i)d();else if(c&&S.code!==1000)r({reconnect:{attempts:t.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:i,status:"exhausted"},type:"connection"})},t.ws=l},a=(l)=>{if(t.ws?.readyState===1){t.ws.send(l);return}t.pendingMessages.push(l)},M=(l)=>{a(JSON.stringify(l))},C=(l={})=>{if(l.sessionId)t.sessionId=l.sessionId;if(l.scenarioId)t.scenarioId=l.scenarioId;M({type:"start",sessionId:t.sessionId,scenarioId:t.scenarioId??void 0})},I=(l)=>{a(l)},L=()=>{M({type:"end_turn"})},b=(l)=>{M({...l,type:"call_control"})},w=()=>{if(g(),t.ws)t.ws.close(1000),t.ws=null;t.isConnected=!1,o.clear()},u=()=>{if(t.ws?.readyState===1)t.ws.close(4000,"absolutejs-voice-reconnect-proof")},U=(l)=>{return o.add(l),()=>{o.delete(l)}};return h(),{callControl:b,close:w,endTurn:L,getReadyState:()=>t.ws?.readyState??3,getScenarioId:()=>t.scenarioId??"",getSessionId:()=>t.sessionId,send:M,sendAudio:I,simulateDisconnect:u,start:C,subscribe:U}};var vn=()=>({attempts:0,maxAttempts:0,status:"idle"}),pn=()=>({assistantAudio:[],assistantTexts:[],call:null,error:null,isConnected:!1,sessionMetadata:null,scenarioId:null,partial:"",reconnect:vn(),sessionId:null,status:"idle",turns:[]}),In=()=>{let n=pn(),e=new Set,o=()=>{e.forEach((i)=>i())};return{dispatch:(i)=>{switch(i.type){case"audio":n={...n,assistantAudio:[...n.assistantAudio,{chunk:i.chunk,format:i.format,receivedAt:i.receivedAt,turnId:i.turnId}]};break;case"assistant":n={...n,assistantTexts:[...n.assistantTexts,i.text]};break;case"complete":n={...n,sessionId:i.sessionId,status:"completed"};break;case"call_lifecycle":n={...n,call:{...n.call,disposition:i.event.type==="end"?i.event.disposition:n.call?.disposition,endedAt:i.event.type==="end"?i.event.at:n.call?.endedAt,events:[...n.call?.events??[],i.event],lastEventAt:i.event.at,startedAt:n.call?.startedAt??i.event.at},sessionId:i.sessionId};break;case"connected":n={...n,isConnected:!0,reconnect:n.reconnect.status==="reconnecting"?{...n.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:n.reconnect};break;case"connection":n={...n,reconnect:i.reconnect};break;case"disconnected":n={...n,isConnected:!1};break;case"error":n={...n,error:i.message};break;case"final":n={...n,partial:i.transcript.text,turns:n.turns.map((s)=>s)};break;case"partial":n={...n,partial:i.transcript.text};break;case"replay":n={...n,assistantTexts:[...i.assistantTexts],call:i.call??null,error:null,isConnected:i.status==="active",partial:i.partial,reconnect:n.reconnect.status==="reconnecting"?{...n.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:n.reconnect,scenarioId:i.scenarioId??n.scenarioId,sessionId:i.sessionId,sessionMetadata:i.sessionMetadata??n.sessionMetadata,status:i.status,turns:[...i.turns]};break;case"session":n={...n,error:null,scenarioId:i.scenarioId??n.scenarioId,isConnected:i.status==="active",sessionId:i.sessionId,sessionMetadata:i.sessionMetadata??n.sessionMetadata,status:i.status};break;case"turn":n={...n,partial:"",turns:[...n.turns,i.turn]};break}o()},getServerSnapshot:()=>n,getSnapshot:()=>n,subscribe:(i)=>{return e.add(i),()=>{e.delete(i)}}}};var Vn=(n,e={})=>{let o=Mn(n,e),c=In(),i=e.browserMedia&&typeof window<"u"?Sn({...e.browserMedia,getScenarioId:()=>e.browserMedia?e.browserMedia.getScenarioId?.()??o.getScenarioId():o.getScenarioId(),getSessionId:()=>e.browserMedia?e.browserMedia.getSessionId?.()??o.getSessionId():o.getSessionId()}):null,s=new Set,t=(d)=>Promise.resolve().then(()=>{if(!d?.sessionId&&!d?.scenarioId)return;o.start(d),i?.start()}),r=()=>{s.forEach((d)=>d())},g=()=>{if(!e.reconnectReportPath||typeof fetch>"u")return;let d=c.getSnapshot(),h=JSON.stringify({at:Date.now(),reconnect:d.reconnect,scenarioId:d.scenarioId,sessionId:o.getSessionId(),turnIds:d.turns.map((a)=>a.id)});fetch(e.reconnectReportPath,{body:h,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},y=o.subscribe((d)=>{let h=hn(d);if(h){if(c.dispatch(h),d.type==="connection")g();r()}});return{callControl(d){o.callControl(d)},close(){y(),i?.close(),o.close(),c.dispatch({type:"disconnected"}),r()},endTurn(){o.endTurn()},get error(){return c.getSnapshot().error},getServerSnapshot(){return c.getServerSnapshot()},getSnapshot(){return c.getSnapshot()},get isConnected(){return c.getSnapshot().isConnected},get scenarioId(){return c.getSnapshot().scenarioId},get sessionMetadata(){return c.getSnapshot().sessionMetadata},start:t,get partial(){return c.getSnapshot().partial},get reconnect(){return c.getSnapshot().reconnect},get sessionId(){return o.getSessionId()},get status(){return c.getSnapshot().status},get turns(){return c.getSnapshot().turns},get assistantTexts(){return c.getSnapshot().assistantTexts},get assistantAudio(){return c.getSnapshot().assistantAudio},get call(){return c.getSnapshot().call},sendAudio(d){o.sendAudio(d)},simulateDisconnect(){o.simulateDisconnect()},subscribe(d){return s.add(d),()=>{s.delete(d)}}}};var Ln=(n)=>{if(!n||n.enabled===!1)return;return{enabled:!0,maxGain:n.maxGain??3,noiseGateAttenuation:n.noiseGateAttenuation??0.15,noiseGateThreshold:n.noiseGateThreshold??0.006,targetLevel:n.targetLevel??0.08}};var ne={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}},ee={general:{},"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var Rn=(n)=>{let e=n?.profile??"fast",o=n?.qualityProfile??"general",c=ne[e],i=ee[o];return{profile:e,qualityProfile:o,silenceMs:n?.silenceMs??i.silenceMs??c.silenceMs,speechThreshold:n?.speechThreshold??i.speechThreshold??c.speechThreshold,transcriptStabilityMs:n?.transcriptStabilityMs??i.transcriptStabilityMs??c.transcriptStabilityMs}};var ce={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:{qualityProfile:"short-command",profile:"balanced"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"general",profile:"fast"}},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:{qualityProfile:"accent-heavy",profile:"long-form"}},"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:{qualityProfile:"accent-heavy",profile:"long-form"}},"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:{qualityProfile:"noisy-room",profile:"long-form",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:{qualityProfile:"noisy-room",profile:"long-form",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:{qualityProfile:"noisy-room",profile:"long-form",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:{qualityProfile:"noisy-room",profile:"long-form"}}},wn=(n="default")=>{let e=ce[n];return{audioConditioning:Ln(e.audioConditioning),capture:{channelCount:e.capture?.channelCount??1,sampleRateHz:e.capture?.sampleRateHz??16000},connection:{...e.connection},name:n,sttLifecycle:e.sttLifecycle??"continuous",turnDetection:Rn(e.turnDetection)}};var oe=(n)=>({assistantAudio:[...n.assistantAudio],assistantTexts:[...n.assistantTexts],call:n.call,error:n.error,isConnected:n.isConnected,isRecording:!1,partial:n.partial,reconnect:n.reconnect,recordingError:null,sessionId:n.sessionId,sessionMetadata:n.sessionMetadata,scenarioId:n.scenarioId,status:n.status,turns:[...n.turns]}),j=(n,e={})=>{let o=wn(e.preset),c=Vn(n,{...o.connection,...e.connection}),i=null,s=oe(c),t=new Set,r=()=>{for(let C of t)C()},g=()=>{if(s={...s,assistantAudio:[...c.assistantAudio],assistantTexts:[...c.assistantTexts],call:c.call,error:c.error,isConnected:c.isConnected,partial:c.partial,reconnect:c.reconnect,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,scenarioId:c.scenarioId,status:c.status,turns:[...c.turns]},e.autoStopOnComplete!==!1&&s.status==="completed"&&s.isRecording)i?.stop(),i=null,s={...s,isRecording:!1};r()},y=c.subscribe(g);g();let d=()=>{if(i)return i;return i=An({channelCount:e.capture?.channelCount??o.capture.channelCount,onLevel:e.capture?.onLevel,onAudio:(C)=>{if(e.capture?.onAudio){e.capture.onAudio(C,c.sendAudio);return}c.sendAudio(C)},sampleRateHz:e.capture?.sampleRateHz??o.capture.sampleRateHz}),i},h=()=>{i?.stop(),i=null,s={...s,isRecording:!1},r()},a=async()=>{if(s.isRecording)return;try{s={...s,recordingError:null},r(),await d().start(),s={...s,isRecording:!0},r()}catch(C){throw i=null,s={...s,isRecording:!1,recordingError:C instanceof Error?C.message:String(C)},r(),C}};return{bindHTMX(C){return gn(c,C)},callControl:(C)=>c.callControl(C),close:()=>{y(),h(),c.close()},endTurn:()=>c.endTurn(),get error(){return s.error},getServerSnapshot:()=>s,getSnapshot:()=>s,get isConnected(){return s.isConnected},get isRecording(){return s.isRecording},get partial(){return s.partial},get recordingError(){return s.recordingError},get reconnect(){return s.reconnect},sendAudio:(C)=>c.sendAudio(C),simulateDisconnect:()=>c.simulateDisconnect(),get sessionId(){return s.sessionId},get sessionMetadata(){return s.sessionMetadata},get scenarioId(){return s.scenarioId},startRecording:a,get status(){return s.status},stopRecording:h,subscribe:(C)=>{return t.add(C),()=>{t.delete(C)}},toggleRecording:async()=>{if(s.isRecording){h();return}await a()},get turns(){return s.turns},get assistantTexts(){return s.assistantTexts},get assistantAudio(){return s.assistantAudio},get call(){return s.call}}};var te=()=>({activeSourceCount:0,error:null,isActive:!1,isPlaying:!1,lastInterruptLatencyMs:void 0,lastPlaybackStopLatencyMs:void 0,processedChunkCount:0,queuedChunkCount:0}),ie=()=>{if(typeof window>"u")return typeof AudioContext>"u"?void 0:AudioContext;return window.AudioContext??window.webkitAudioContext},se=(n,e)=>{let o=e.format;if(o.container!=="raw"||o.encoding!=="pcm_s16le")throw Error(`Unsupported assistant audio format: ${o.container}/${o.encoding}`);let c=e.chunk,i=Math.max(1,o.channels),s=Math.floor(c.byteLength/2),t=Math.max(1,Math.floor(s/i)),r=n.createBuffer(i,t,o.sampleRateHz),g=new DataView(c.buffer,c.byteOffset,c.byteLength);for(let y=0;y<i;y+=1){let d=r.getChannelData(y);for(let h=0;h<t;h+=1){let M=(h*i+y)*2;if(M+1>=c.byteLength){d[h]=0;continue}d[h]=g.getInt16(M,!0)/32768}}return r},m=(n,e={})=>{let o=new Set,c=new Set,i=(e.lookaheadMs??15)/1000,s=te(),t=null,r=null,g=0,y=Promise.resolve(),d=null,h=null,a=null,M=null,C=()=>{for(let A of o)A()},I=(A)=>{s={...s,...A},C()},L=()=>{if(s.error!==null)I({error:null})},b=()=>{if(M!==null)clearTimeout(M),M=null},w=(A)=>{b(),d=null,I({activeSourceCount:c.size,isPlaying:!1,lastInterruptLatencyMs:A,lastPlaybackStopLatencyMs:s.lastPlaybackStopLatencyMs??A}),a?.(),a=null,h=null},u=(A)=>{if(!A)return 0;return Math.max(0,((A.baseLatency??0)+(A.outputLatency??0))*1000)},U=(A)=>{if(!r)return;let f=1;if(r.gain.setValueAtTime){r.gain.setValueAtTime(f,A?.currentTime??0);return}r.gain.value=f},l=(A)=>{if(!r)return;let f=0;if(r.gain.setValueAtTime){r.gain.setValueAtTime(f,A?.currentTime??0);return}r.gain.value=f},S=()=>{if(d===null||c.size>0)return;w(Date.now()-d)},R=async()=>{if(t)return t;if(e.createAudioContext)t=e.createAudioContext();else{let A=ie();if(!A)throw Error("Assistant audio playback requires AudioContext support.");t=new A}if(t.createGain)r=t.createGain(),r.connect?.(t.destination);return g=t.currentTime,t},z=async(A)=>{let f=await R(),P=se(f,A),V=f.createBufferSource();V.buffer=P,V.connect(r??f.destination),V.onended=()=>{c.delete(V),V.disconnect?.(),I({activeSourceCount:c.size,isPlaying:c.size>0&&s.isActive}),S()};let Y=Math.max(f.currentTime+i,g);g=Y+P.duration,c.add(V),I({activeSourceCount:c.size,isPlaying:!0}),V.start(Y)},E=(A)=>{for(let f of[...c])f.stop?.();if(g=t?t.currentTime:0,A?.forceClear){for(let f of c)f.disconnect?.();c.clear(),S()}},W=async()=>{if(!s.isActive)return;let A=n.assistantAudio.slice(s.processedChunkCount);if(A.length===0)return;try{L();for(let f of A)await z(f);I({processedChunkCount:n.assistantAudio.length,queuedChunkCount:s.queuedChunkCount+A.length})}catch(f){I({error:f instanceof Error?f.message:String(f)})}},D=()=>{return y=y.then(()=>W(),()=>W()),y},$=n.subscribe(()=>{if(e.autoStart&&!s.isActive&&n.assistantAudio.length>0){N.start();return}if(s.isActive)D()}),N={close:async()=>{if($(),E({forceClear:!0}),b(),a?.(),a=null,h=null,d=null,t&&t.state!=="closed")await t.close();t=null,r?.disconnect?.(),r=null,g=0,I({activeSourceCount:0,isActive:!1,isPlaying:!1})},get activeSourceCount(){return s.activeSourceCount},get error(){return s.error},getSnapshot:()=>s,get isActive(){return s.isActive},get isPlaying(){return s.isPlaying},interrupt:async()=>{let A=Date.now(),f=await R();d=A,l(f);let P=Date.now()-A+u(f);if(I({isActive:!1,isPlaying:c.size>0,lastPlaybackStopLatencyMs:P}),c.size===0){w(P);return}if(!h)h=new Promise((V)=>{a=V});b(),M=setTimeout(()=>{for(let V of c)V.disconnect?.();c.clear(),w(Date.now()-A)},250),E(),await h},get lastInterruptLatencyMs(){return s.lastInterruptLatencyMs},get lastPlaybackStopLatencyMs(){return s.lastPlaybackStopLatencyMs},pause:async()=>{if(!t){I({activeSourceCount:0,isActive:!1,isPlaying:!1});return}await t.suspend(),I({activeSourceCount:c.size,isActive:!1,isPlaying:!1})},get processedChunkCount(){return s.processedChunkCount},get queuedChunkCount(){return s.queuedChunkCount},start:async()=>{try{L();let A=await R();if(U(A),A.state==="suspended")await A.resume();I({activeSourceCount:c.size,isActive:!0,isPlaying:A.state==="running"}),await D()}catch(A){throw I({error:A instanceof Error?A.message:String(A),isActive:!1,isPlaying:!1}),A}},subscribe:(A)=>{return o.add(A),()=>{o.delete(A)}}};return N};var le=()=>`barge-in:${Date.now()}:${crypto.randomUUID?.()??Math.random().toString(36).slice(2)}`,re=(n,e)=>{let o=n.filter((t)=>t.status==="stopped"),c=o.map((t)=>t.latencyMs).filter((t)=>typeof t==="number"),i=o.filter((t)=>typeof t.latencyMs==="number"&&t.latencyMs>e).length,s=o.length-i;return{averageLatencyMs:c.length>0?Math.round(c.reduce((t,r)=>t+r,0)/c.length):void 0,events:[...n],failed:i,lastEvent:n.at(-1),passed:s,status:n.length===0?"empty":i>0?"fail":o.length===0?"warn":"pass",thresholdMs:e,total:o.length}},bn=(n={})=>{let e=new Set,o=n.thresholdMs??250,c=n.fetch??globalThis.fetch,i=[],s=()=>{for(let g of e)g()},t=(g)=>{if(!n.path||typeof c!=="function")return;c(n.path,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},method:"POST"}).catch(()=>{})},r=(g,y)=>{let d={at:Date.now(),id:le(),latencyMs:y.latencyMs,playbackStopLatencyMs:y.playbackStopLatencyMs,reason:y.reason,sessionId:y.sessionId,status:g,thresholdMs:o};return i.push(d),t(d),s(),d};return{getSnapshot:()=>re(i,o),recordRequested:(g)=>r("requested",g),recordSkipped:(g)=>r("skipped",g),recordStopped:(g)=>r("stopped",g),subscribe:(g)=>{return e.add(g),()=>{e.delete(g)}}}};var ae=0.08,de=(n,e={})=>(e.enabled??!0)&&n>=(e.interruptThreshold??ae),cn=(n,e,o={})=>{let c=n.partial,i=(t)=>{if(!e.isPlaying||o.enabled===!1){o.monitor?.recordSkipped({reason:t,sessionId:n.sessionId});return}o.monitor?.recordRequested({reason:t,sessionId:n.sessionId}),e.interrupt().then(()=>{o.monitor?.recordStopped({latencyMs:e.lastInterruptLatencyMs,playbackStopLatencyMs:e.lastPlaybackStopLatencyMs,reason:t,sessionId:n.sessionId})})},s=n.subscribe(()=>{if(o.interruptOnPartial===!1){c=n.partial;return}if(!c&&n.partial)i("partial-transcript");c=n.partial});return{close:()=>{s()},handleLevel:(t)=>{if(de(t,o))i("input-level")},sendAudio:(t)=>{i("manual-audio"),n.sendAudio(t)}}};var rn=48,ge=320,Ae=88,he="Guided test",ye="General recording",Ce="Pick a scenario to begin the demo.",fe="I can walk you through a short guided voice test.",Te="I can capture one freeform recording and confirm that it landed.",Se="Choose a scenario to begin. Guided test asks follow-up prompts. General recording just captures what you say.",Me="Click Start general recording to capture one freeform answer.",Pn="Speak freely. When you pause, the recording will be captured.",sn="Recording saved. Start again if you want another capture.",Dn="Guided test complete. Review the saved summary below.",On="All prompts are covered. You can stop the microphone or keep speaking for extra detail.",Ie="Ready. Start guided test or general recording to begin.",Ve="Live. Answer the prompt, then click Stop microphone when finished.",_n=["Start with a quick introduction about who you are.","Now describe what you are trying to do or test.","Finish with any detail that feels blocked, risky, or unclear."],xn=(n,e,o)=>Math.min(o,Math.max(e,n)),X=(n)=>n.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll(\'"\',"&quot;").replaceAll("\'","&#39;"),on=(n,e)=>{let o=n[e];if(typeof o==="string"&&o.trim())return o;return null},ln=(n)=>{if(typeof n==="string"&&n.trim())return n;if(n instanceof Error&&n.message.trim())return n.message;if(n&&typeof n==="object"){let e=n,o=on(e,"message")??on(e,"reason")??on(e,"description");if(o)return o;if("error"in e)return ln(e.error);if("cause"in e)return ln(e.cause);try{return JSON.stringify(n)}catch{}}return"Unexpected error"},Le=(n)=>{let e=[n.status];if(n.attempts>0||n.maxAttempts>0)e.push(`${n.attempts}/${n.maxAttempts} attempts`);if(n.nextAttemptAt){let o=Math.max(0,n.nextAttemptAt-Date.now());e.push(`retry in ${Math.ceil(o/100)/10}s`)}return e.join(" \xB7 ")},F=(n=rn)=>Array.from({length:n},()=>0),En=(n,e,o=rn)=>{let c=n.slice(-(o-1));c.push(xn(e,0,1));while(c.length<o)c.unshift(0);return c},Re=(n,e=ge,o=Ae)=>{let c=n.length>1?n:F(rn),i=e/(c.length-1),s=o/2,t=o*0.34;if(Math.max(...c,0)<=0.015)return`M 0 ${s} L ${e} ${s}`;let g=c.map((d,h)=>{let a=h*0.76,M=Math.sin(a)*0.78+Math.sin(a*0.41)*0.22,C=d*t,I=i*h,L=xn(s+M*C,8,o-8);return{x:I,y:L}});if(g.length===0)return`M 0 ${s} L ${e} ${s}`;let y=`M ${g[0]?.x??0} ${g[0]?.y??s}`;for(let d=1;d<g.length;d+=1){let h=g[d-1],a=g[d];if(!h||!a)continue;let M=(h.x+a.x)/2;y+=` Q ${M} ${h.y} ${a.x} ${a.y}`}return y},we=(n)=>{if(!n)return _n;try{let e=JSON.parse(n);if(Array.isArray(e)){let o=e.filter((c)=>typeof c==="string").map((c)=>c.trim()).filter(Boolean);if(o.length>0)return o}}catch{}return _n},tn=(n)=>{if(!n)return;let e=Number(n);return Number.isFinite(e)?e:void 0},be=(n,e,o)=>{if(!e)return null;let c=document.querySelector(e);return c instanceof o?c:null},x=(n,e,o,c)=>{let i=e?document.querySelector(e):null;if(i instanceof o)return i;let s=n.querySelector(`#${c}`);if(s instanceof o)return s;throw Error(`Voice HTMX bootstrap could not find the required element "${c}".`)},_e=(n)=>{if(!n.mode)return Ce;if(!n.hasStarted)return n.mode==="guided"?fe:Te;if(n.status==="completed")return n.mode==="guided"?Dn:sn;if(n.mode==="general")return Pn;return n.guidedPrompts[n.turnCount]??On},Ee=(n)=>{if(!n.mode)return Se;if(n.status==="completed")return n.mode==="guided"?Dn:sn;if(!n.hasStarted)return n.mode==="guided"?`Click Start guided test to begin. First prompt: ${n.guidedPrompts[0]??"Answer the first prompt."}`:Me;if(n.mode==="general")return n.turnCount===0?Pn:sn;return n.guidedPrompts[n.turnCount]??On},Pe=(n)=>{let e=n.dataset.voiceGuidedPath,o=n.dataset.voiceGeneralPath;if(!e||!o)throw Error("Voice HTMX bootstrap requires data-voice-guided-path and data-voice-general-path.");let c=we(n.dataset.voiceGuidedPrompts),i=n.dataset.voiceGuidedLabel??he,s=n.dataset.voiceGeneralLabel??ye,t=n.dataset.voiceReconnectReportPath,r=n.dataset.voiceBargeInPath,g=r?bn({path:r,thresholdMs:tn(n.dataset.voiceBargeInThresholdMs)}):null,y=tn(n.dataset.voiceBargeInRecentWindowMs)??4000,d=tn(n.dataset.voiceBargeInSpeechThreshold)??0.04,h=x(document,n.dataset.voiceSync,HTMLElement,"voice-htmx-sync"),a=x(n,n.dataset.voiceConnection,HTMLElement,"metric-connection"),M=x(n,n.dataset.voiceError,HTMLElement,"status-error"),C=x(n,n.dataset.voiceMicrophone,HTMLElement,"status-mic"),I=x(n,n.dataset.voicePrompt,HTMLElement,"status-prompt"),L=be(n,n.dataset.voiceReconnect,HTMLElement),b=x(n,n.dataset.voiceChat,HTMLElement,"chat-list"),w=x(n,n.dataset.voiceStartGuided,HTMLButtonElement,"start-guided"),u=x(n,n.dataset.voiceStartGeneral,HTMLButtonElement,"start-general"),U=x(n,n.dataset.voiceStop,HTMLButtonElement,"stop-mic"),l=x(n,n.dataset.voiceMonitor,HTMLElement,"voice-monitor"),S=x(n,n.dataset.voiceMonitorCopy,HTMLElement,"voice-monitor-copy"),R=x(n,n.dataset.voiceWaveGlow,SVGPathElement,"voice-wave-glow"),z=x(n,n.dataset.voiceWavePath,SVGPathElement,"voice-wave-path"),E=null,W={general:!1,guided:!1},D=!1,$=null,N=F(),A=null,f=null,P=j(e,{capture:{onAudio:(T,G)=>{if(A){A.sendAudio(T);return}G(T)},onLevel:(T)=>{A?.handleLevel(T),N=En(N,T),v()}},connection:{reconnectReportPath:t},preset:"guided-intake"}),V=j(o,{capture:{onAudio:(T,G)=>{if(f){f.sendAudio(T);return}G(T)},onLevel:(T)=>{f?.handleLevel(T),N=En(N,T),v()}},connection:{reconnectReportPath:t},preset:"dictation"}),Y=P.bindHTMX({element:h}),un=V.bindHTMX({element:h}),J=m(P),Q=m(V);A=cn(P,J,{interruptThreshold:d,monitor:g??void 0}),f=cn(V,Q,{interruptThreshold:d,monitor:g??void 0});let k=()=>E==="general"?V:P,Oe=()=>E==="general"?Q:J,v=()=>{let T=Re(N);R.setAttribute("d",T),z.setAttribute("d",T),S.innerHTML=`<span class="voice-live-dot"></span>${D?"Microphone live":"Microphone idle"}`,S.classList.toggle("is-live",D),l.classList.toggle("is-live",D)},q=()=>{let T=k(),G=(E?W[E]:!1)||T.turns.length>0,dn=T.status;if(a.textContent=T.isConnected?"Connected":"Waiting",M.textContent=$||T.error||"None",L)L.textContent=Le(T.reconnect);C.textContent=D?Ve:Ie,I.textContent=Ee({guidedPrompts:c,hasStarted:G,mode:E,status:dn,turnCount:T.turns.length}),w.hidden=D,u.hidden=D,U.hidden=!D,b.innerHTML=`<article class="voice-chat-message assistant">\n <div class="voice-chat-role">${X(E==="general"?s:E==="guided"?i:"Voice demo")}</div>\n <p class="voice-turn-text">${X(_e({generalLabel:s,guidedLabel:i,guidedPrompts:c,hasStarted:G,mode:E,status:dn,turnCount:T.turns.length}))}</p>\n</article>${T.turns.map((p)=>`<div class="voice-chat-stack">\n <article class="voice-chat-message user">\n <div class="voice-chat-role">You</div>\n <p class="voice-turn-text">${X(p.text)}</p>\n </article>\n ${p.assistantText?`<article class="voice-chat-message assistant">\n <div class="voice-chat-role">${X(E==="general"?s:E==="guided"?i:"Guide")}</div>\n <p class="voice-turn-text">${X(p.assistantText)}</p>\n </article>`:""}\n</div>`).join("")}${T.partial?`<article class="voice-chat-message user pending">\n <div class="voice-chat-role">Speaking</div>\n <p class="voice-turn-text">${X(T.partial)}</p>\n</article>`:""}`,v()},Un=()=>{k().stopRecording(),D=!1,$=null,N=F(),q()},an=async(T)=>{E=T,W={...W,[T]:!0};try{await k().startRecording(),$=null,D=!0,q()}catch(G){k().stopRecording(),D=!1,N=F(),$=ln(G),q()}};P.subscribe(()=>{if(P.assistantAudio.length>0)J.start().catch(()=>{});q()}),V.subscribe(()=>{if(V.assistantAudio.length>0)Q.start().catch(()=>{});q()}),w.addEventListener("click",()=>{an("guided")}),u.addEventListener("click",()=>{an("general")}),U.addEventListener("click",()=>{Un()}),n.addEventListener("absolute-voice-simulate-disconnect",()=>{k().simulateDisconnect()}),window.addEventListener("beforeunload",()=>{P.stopRecording(),V.stopRecording(),A?.close(),f?.close(),J.close(),Q.close(),Y(),un(),P.close(),V.close()}),q()},De=()=>{if(typeof window>"u"||typeof document>"u")return;let n=Array.from(document.querySelectorAll("[data-voice-htmx]"));for(let e of n)if(e instanceof HTMLElement)Pe(e)};De();export{De as initVoiceHTMX};\n';
5190
5190
 
5191
5191
  // src/profileSwitchRecommendation.ts
5192
5192
  import { Elysia } from "elysia";
@@ -12555,670 +12555,16 @@ var createVoiceDiagnosticsRoutes = (options) => {
12555
12555
  };
12556
12556
  // src/mediaPipelineRoutes.ts
12557
12557
  import { Elysia as Elysia13 } from "elysia";
12558
-
12559
- // node_modules/@absolutejs/media/dist/index.js
12560
- var formatLabel2 = (format) => `${format.container}/${format.encoding}/${String(format.sampleRateHz)}hz/${String(format.channels)}ch`;
12561
- var formatMatches2 = (actual, expected) => actual.container === expected.container && actual.encoding === expected.encoding && actual.sampleRateHz === expected.sampleRateHz && actual.channels === expected.channels;
12562
- var pushIssue = (issues, severity, code, message) => {
12563
- issues.push({ code, message, severity });
12564
- };
12565
- var numericMetadata = (frame, key) => {
12566
- const value = frame.metadata?.[key];
12567
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
12568
- };
12569
- var average = (values) => values.length === 0 ? undefined : values.reduce((total, value) => total + value, 0) / values.length;
12570
- var max = (values) => values.length === 0 ? undefined : Math.max(...values);
12571
- var min = (values) => values.length === 0 ? undefined : Math.min(...values);
12572
- var numericStat = (stat, key) => {
12573
- const value = stat[key];
12574
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
12575
- };
12576
- var booleanStat = (stat, key) => {
12577
- const value = stat[key];
12578
- return typeof value === "boolean" ? value : undefined;
12579
- };
12580
- var stringStat = (stat, key) => {
12581
- const value = stat[key];
12582
- return typeof value === "string" ? value : undefined;
12583
- };
12584
- var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
12585
- var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
12586
- var DEFAULT_TELEPHONY_FORMAT = {
12587
- channels: 1,
12588
- container: "raw",
12589
- encoding: "mulaw",
12590
- sampleRateHz: 8000
12591
- };
12592
- var bytesToBase64 = (audio) => {
12593
- const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
12594
- return Buffer.from(bytes).toString("base64");
12595
- };
12596
- var base64ToBytes = (value) => new Uint8Array(Buffer.from(value, "base64"));
12597
- var unknownRecord = (value) => value && typeof value === "object" ? value : {};
12598
- var firstString2 = (records, keys) => {
12599
- for (const record of records) {
12600
- for (const key of keys) {
12601
- const value = record[key];
12602
- if (typeof value === "string" && value.length > 0) {
12603
- return value;
12604
- }
12605
- if (typeof value === "number" && Number.isFinite(value)) {
12606
- return String(value);
12607
- }
12608
- }
12609
- }
12610
- return;
12611
- };
12612
- var firstNumber = (records, keys) => {
12613
- for (const record of records) {
12614
- for (const key of keys) {
12615
- const value = record[key];
12616
- if (typeof value === "number" && Number.isFinite(value)) {
12617
- return value;
12618
- }
12619
- if (typeof value === "string") {
12620
- const parsed = Number(value);
12621
- if (Number.isFinite(parsed)) {
12622
- return parsed;
12623
- }
12624
- }
12625
- }
12626
- }
12627
- return;
12628
- };
12629
- var telephonyDirection = (track) => {
12630
- const normalized = track?.toLowerCase();
12631
- if (!normalized) {
12632
- return "unknown";
12633
- }
12634
- if (normalized.includes("inbound") || normalized.includes("caller") || normalized.includes("in")) {
12635
- return "inbound";
12636
- }
12637
- if (normalized.includes("outbound") || normalized.includes("assistant") || normalized.includes("out")) {
12638
- return "outbound";
12639
- }
12640
- return "unknown";
12641
- };
12642
- var telephonyFrameKind = (direction) => direction === "outbound" ? "assistant-audio" : "input-audio";
12643
- var telephonyEventKind = (envelope) => {
12644
- const raw = firstString2([envelope], ["event", "type", "eventType"]) ?? firstString2([unknownRecord(envelope.message)], ["event", "type"]);
12645
- const normalized = raw?.toLowerCase().replace(/[_\s-]+/g, "-");
12646
- if (!normalized) {
12647
- return "unknown";
12648
- }
12649
- if (normalized.includes("connected")) {
12650
- return "connected";
12651
- }
12652
- if (normalized.includes("start")) {
12653
- return "start";
12654
- }
12655
- if (normalized.includes("media")) {
12656
- return "media";
12657
- }
12658
- if (normalized.includes("stop") || normalized.includes("closed")) {
12659
- return "stop";
12660
- }
12661
- if (normalized.includes("error") || normalized.includes("failed")) {
12662
- return "error";
12663
- }
12664
- return "unknown";
12665
- };
12666
- var normalizeWebRTCStat = (stat) => {
12667
- const sample = {};
12668
- for (const [key, value] of Object.entries(stat)) {
12669
- if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
12670
- sample[key] = value;
12671
- }
12672
- }
12673
- return sample;
12674
- };
12675
- var parseTelephonyMediaFrame = (input) => {
12676
- const envelope = input.envelope;
12677
- const media = unknownRecord(envelope.media);
12678
- const payload = firstString2([media, envelope], ["payload", "audio", "data"]) ?? firstString2([unknownRecord(envelope.message)], ["payload"]);
12679
- if (!payload) {
12680
- return;
12681
- }
12682
- const carrier = input.carrier ?? firstString2([envelope], ["provider"]) ?? "telephony";
12683
- const streamId = firstString2([media, envelope], ["streamSid", "stream_id", "streamId", "streamId", "callSid", "call_id"]);
12684
- const sequenceNumber = firstString2([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
12685
- const track = firstString2([media, envelope], ["track", "direction"]);
12686
- const direction = telephonyDirection(track);
12687
- const timestamp = firstNumber([media, envelope], ["timestamp", "time", "startedAt"]);
12688
- return {
12689
- at: timestamp,
12690
- audio: base64ToBytes(payload),
12691
- format: input.format ?? DEFAULT_TELEPHONY_FORMAT,
12692
- id: [
12693
- carrier,
12694
- streamId ?? input.sessionId ?? "stream",
12695
- sequenceNumber ?? timestamp ?? Date.now()
12696
- ].join(":"),
12697
- kind: telephonyFrameKind(direction),
12698
- metadata: {
12699
- carrier,
12700
- direction,
12701
- event: firstString2([envelope], ["event", "type"]),
12702
- sequenceNumber,
12703
- streamId,
12704
- track
12705
- },
12706
- sessionId: input.sessionId ?? streamId,
12707
- source: "telephony"
12708
- };
12709
- };
12710
- var serializeTelephonyMediaFrame = (input) => {
12711
- const carrier = input.carrier ?? input.frame.metadata?.carrier ?? "telephony";
12712
- const streamId = input.streamId ?? (typeof input.frame.metadata?.streamId === "string" ? input.frame.metadata.streamId : input.frame.sessionId);
12713
- const sequenceNumber = input.sequenceNumber ?? (typeof input.frame.metadata?.sequenceNumber === "string" || typeof input.frame.metadata?.sequenceNumber === "number" ? input.frame.metadata.sequenceNumber : undefined);
12714
- const direction = input.frame.kind === "assistant-audio" ? "outbound" : "inbound";
12715
- const payload = input.frame.audio ? bytesToBase64(input.frame.audio) : "";
12716
- if (carrier === "twilio") {
12717
- return {
12718
- event: "media",
12719
- sequenceNumber,
12720
- streamSid: streamId,
12721
- media: {
12722
- payload,
12723
- timestamp: input.frame.at,
12724
- track: direction
12725
- }
12726
- };
12727
- }
12728
- if (carrier === "telnyx") {
12729
- return {
12730
- event: "media",
12731
- stream_id: streamId,
12732
- sequence_number: sequenceNumber,
12733
- media: {
12734
- payload,
12735
- timestamp: input.frame.at,
12736
- track: direction
12737
- }
12738
- };
12739
- }
12740
- if (carrier === "plivo") {
12741
- return {
12742
- event: "media",
12743
- streamId,
12744
- sequenceNumber,
12745
- media: {
12746
- payload,
12747
- timestamp: input.frame.at,
12748
- track: direction
12749
- }
12750
- };
12751
- }
12752
- return {
12753
- event: "media",
12754
- provider: carrier,
12755
- sequenceNumber,
12756
- streamId,
12757
- media: {
12758
- payload,
12759
- timestamp: input.frame.at,
12760
- track: direction
12761
- }
12762
- };
12763
- };
12764
- var createTelephonyMediaSerializer = (input) => {
12765
- const format = input.format ?? DEFAULT_TELEPHONY_FORMAT;
12766
- return {
12767
- carrier: input.carrier,
12768
- format,
12769
- parse: (envelope) => parseTelephonyMediaFrame({
12770
- carrier: input.carrier,
12771
- envelope,
12772
- format,
12773
- sessionId: input.sessionId ?? input.streamId
12774
- }),
12775
- serialize: (frame) => serializeTelephonyMediaFrame({
12776
- carrier: input.carrier,
12777
- frame,
12778
- streamId: input.streamId
12779
- })
12780
- };
12781
- };
12782
- var parseTelephonyStreamEvent = (input) => {
12783
- const envelope = input.envelope;
12784
- const media = unknownRecord(envelope.media);
12785
- const start = unknownRecord(envelope.start);
12786
- const stop = unknownRecord(envelope.stop);
12787
- const errorRecord = unknownRecord(envelope.error);
12788
- const kind = telephonyEventKind(envelope);
12789
- const carrier = input.carrier ?? firstString2([envelope], ["provider", "carrier"]) ?? "telephony";
12790
- const frame = kind === "media" ? parseTelephonyMediaFrame({
12791
- carrier,
12792
- envelope,
12793
- format: input.format,
12794
- sessionId: input.sessionId
12795
- }) : undefined;
12796
- const streamId = firstString2([media, start, stop, envelope], ["streamSid", "stream_id", "streamId", "callSid", "call_id"]) ?? input.sessionId;
12797
- const sequenceNumber = firstString2([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
12798
- const track = firstString2([media, envelope], ["track", "direction"]);
12799
- return {
12800
- audioBytes: frame?.audio ? frame.audio instanceof ArrayBuffer ? frame.audio.byteLength : frame.audio.byteLength : 0,
12801
- at: frame?.at ?? firstNumber([media, start, stop, envelope], ["timestamp", "time", "startedAt"]),
12802
- carrier,
12803
- direction: telephonyDirection(track),
12804
- error: firstString2([errorRecord, envelope], ["message", "error", "reason"]),
12805
- kind,
12806
- sequenceNumber,
12807
- streamId
12808
- };
12809
- };
12810
- var buildMediaTelephonyStreamLifecycleReport = (input = {}) => {
12811
- const envelopes = input.envelopes ?? [];
12812
- const events = envelopes.map((envelope) => parseTelephonyStreamEvent({
12813
- carrier: input.carrier,
12814
- envelope
12815
- }));
12816
- const issues = [];
12817
- const startedIndex = events.findIndex((event) => event.kind === "start");
12818
- const firstMediaIndex = events.findIndex((event) => event.kind === "media");
12819
- const stoppedIndex = events.findIndex((event) => event.kind === "stop");
12820
- const started = startedIndex >= 0;
12821
- const stopped = stoppedIndex >= 0;
12822
- const mediaEvents = events.filter((event) => event.kind === "media");
12823
- const audioBytes = events.reduce((total, event) => total + event.audioBytes, 0);
12824
- const minAudioBytes = input.minAudioBytes ?? 1;
12825
- const streamIds = Array.from(new Set(events.map((event) => event.streamId).filter(Boolean)));
12826
- if ((input.requireStart ?? true) && !started) {
12827
- pushIssue(issues, "error", "media.telephony_missing_start", "Telephony media stream did not include a start event.");
12828
- }
12829
- if ((input.requireMedia ?? true) && mediaEvents.length === 0) {
12830
- pushIssue(issues, "error", "media.telephony_missing_media", "Telephony media stream did not include media payload events.");
12831
- }
12832
- if ((input.requireStop ?? true) && !stopped) {
12833
- pushIssue(issues, input.maxMissingStop === false ? "warning" : "error", "media.telephony_missing_stop", "Telephony media stream did not include a stop event.");
12834
- }
12835
- if (started && firstMediaIndex >= 0 && firstMediaIndex < startedIndex) {
12836
- pushIssue(issues, "error", "media.telephony_media_before_start", "Telephony media payload arrived before the stream start event.");
12837
- }
12838
- if (stopped && firstMediaIndex >= 0 && stoppedIndex < firstMediaIndex) {
12839
- pushIssue(issues, "error", "media.telephony_stop_before_media", "Telephony media stream stopped before any media payload arrived.");
12840
- }
12841
- if (mediaEvents.length > 0 && audioBytes < minAudioBytes) {
12842
- pushIssue(issues, "error", "media.telephony_no_audio_bytes", `Telephony media stream parsed ${String(audioBytes)} audio byte(s), below required ${String(minAudioBytes)}.`);
12843
- }
12844
- for (const event of events) {
12845
- if (event.kind === "error") {
12846
- pushIssue(issues, "error", "media.telephony_stream_error", event.error ?? "Telephony media stream emitted an error event.");
12847
- }
12848
- }
12849
- return {
12850
- audioBytes,
12851
- carrier: input.carrier,
12852
- checkedAt: Date.now(),
12853
- events,
12854
- issues,
12855
- mediaEvents: mediaEvents.length,
12856
- started,
12857
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
12858
- stopped,
12859
- streamIds
12860
- };
12861
- };
12862
- var buildMediaResamplingPlan = (input) => {
12863
- const required = !formatMatches2(input.inputFormat, input.outputFormat);
12864
- return {
12865
- inputFormat: input.inputFormat,
12866
- outputFormat: input.outputFormat,
12867
- ratio: input.outputFormat.sampleRateHz / input.inputFormat.sampleRateHz,
12868
- required,
12869
- status: input.inputFormat.container === input.outputFormat.container && input.inputFormat.encoding === input.outputFormat.encoding && input.inputFormat.channels === input.outputFormat.channels ? "pass" : "warn"
12870
- };
12871
- };
12872
- var speechProbability = (frame) => {
12873
- if (frame.metadata?.isSpeech === true) {
12874
- return 1;
12875
- }
12876
- if (frame.metadata?.isSpeech === false) {
12877
- return 0;
12878
- }
12879
- for (const key of ["speechProbability", "voiceProbability", "rms", "energy"]) {
12880
- const value = numericMetadata(frame, key);
12881
- if (value !== undefined) {
12882
- return value;
12883
- }
12884
- }
12885
- return 0;
12886
- };
12887
- var buildMediaVadReport = (input = {}) => {
12888
- const frames = (input.frames ?? []).filter((frame) => frame.kind === "input-audio");
12889
- const speechStartThreshold = input.speechStartThreshold ?? 0.6;
12890
- const speechEndThreshold = input.speechEndThreshold ?? 0.35;
12891
- const minSpeechFrames = input.minSpeechFrames ?? 1;
12892
- const maxSilenceFrames = input.maxSilenceFrames ?? 1;
12893
- const segments = [];
12894
- let activeFrames = [];
12895
- let silenceFrames = 0;
12896
- const closeSegment = () => {
12897
- if (activeFrames.length < minSpeechFrames) {
12898
- activeFrames = [];
12899
- silenceFrames = 0;
12900
- return;
12901
- }
12902
- const first = activeFrames[0];
12903
- const last = activeFrames.at(-1);
12904
- if (!first) {
12905
- return;
12906
- }
12907
- segments.push({
12908
- durationMs: first.at !== undefined && last?.at !== undefined ? last.at - first.at + (last.durationMs ?? 0) : undefined,
12909
- endAt: last?.at !== undefined ? last.at + (last.durationMs ?? 0) : undefined,
12910
- frameCount: activeFrames.length,
12911
- segmentId: `vad:${String(segments.length + 1)}`,
12912
- sessionId: first.sessionId,
12913
- startAt: first.at,
12914
- turnId: first.turnId
12915
- });
12916
- activeFrames = [];
12917
- silenceFrames = 0;
12918
- };
12919
- for (const frame of frames) {
12920
- const probability = speechProbability(frame);
12921
- if (activeFrames.length === 0) {
12922
- if (probability >= speechStartThreshold) {
12923
- activeFrames.push(frame);
12924
- }
12925
- continue;
12926
- }
12927
- activeFrames.push(frame);
12928
- if (probability <= speechEndThreshold) {
12929
- silenceFrames += 1;
12930
- } else {
12931
- silenceFrames = 0;
12932
- }
12933
- if (silenceFrames > maxSilenceFrames) {
12934
- closeSegment();
12935
- }
12936
- }
12937
- closeSegment();
12938
- return {
12939
- checkedAt: Date.now(),
12940
- inputAudioFrames: frames.length,
12941
- segments,
12942
- status: frames.length === 0 ? "warn" : "pass"
12943
- };
12944
- };
12945
- var buildMediaInterruptionReport = (input = {}) => {
12946
- const issues = [];
12947
- const interruptionFrames = (input.frames ?? []).filter((frame) => frame.kind === "interruption");
12948
- const latenciesMs = interruptionFrames.map((frame) => frame.latencyMs).filter((latency) => typeof latency === "number");
12949
- const maxInterruptionLatencyMs = input.maxInterruptionLatencyMs;
12950
- if (interruptionFrames.length === 0) {
12951
- pushIssue(issues, "warning", "media.interruption_missing", "No interruption frame was observed.");
12952
- }
12953
- if (maxInterruptionLatencyMs !== undefined && latenciesMs.some((latency) => latency > maxInterruptionLatencyMs)) {
12954
- pushIssue(issues, "error", "media.interruption_latency", `Interruption latency exceeded ${String(maxInterruptionLatencyMs)}ms.`);
12955
- }
12956
- return {
12957
- checkedAt: Date.now(),
12958
- interruptionFrames: interruptionFrames.length,
12959
- issues,
12960
- latenciesMs,
12961
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass"
12962
- };
12963
- };
12964
- var buildMediaQualityReport = (input = {}) => {
12965
- const frames = [...input.frames ?? []].sort((a, b) => (a.at ?? 0) - (b.at ?? 0));
12966
- const audioFrames = frames.filter((frame) => frame.kind === "input-audio" || frame.kind === "assistant-audio");
12967
- const inputAudioFrames = frames.filter((frame) => frame.kind === "input-audio");
12968
- const assistantAudioFrames = frames.filter((frame) => frame.kind === "assistant-audio");
12969
- const issues = [];
12970
- const gapsMs = [];
12971
- for (const [index, frame] of audioFrames.entries()) {
12972
- const previous = audioFrames[index - 1];
12973
- if (previous?.at === undefined || frame.at === undefined || previous.durationMs === undefined) {
12974
- continue;
12975
- }
12976
- const gap = frame.at - (previous.at + previous.durationMs);
12977
- if (gap > 0) {
12978
- gapsMs.push(gap);
12979
- }
12980
- }
12981
- const jitterMs = audioFrames.map((frame) => numericMetadata(frame, "jitterMs")).filter((value) => value !== undefined).at(-1) ?? max(gapsMs);
12982
- const first = audioFrames.find((frame) => frame.at !== undefined);
12983
- const last = audioFrames.toReversed().find((frame) => frame.at !== undefined);
12984
- const durationMs = first?.at !== undefined && last?.at !== undefined ? last.at - first.at + (last.durationMs ?? 0) : undefined;
12985
- const expectedDurationMs = audioFrames.length > 0 ? audioFrames.reduce((total, frame) => total + (frame.durationMs ?? 0), 0) : undefined;
12986
- const timestampDriftMs = durationMs !== undefined && expectedDurationMs !== undefined ? Math.max(0, durationMs - expectedDurationMs) : undefined;
12987
- const speechScores = inputAudioFrames.map(speechProbability);
12988
- const speechFrames = speechScores.filter((score) => score >= 0.6).length;
12989
- const silenceFrames = speechScores.filter((score) => score <= 0.35).length;
12990
- const unknownSpeechFrames = Math.max(0, inputAudioFrames.length - speechFrames - silenceFrames);
12991
- const speechRatio = inputAudioFrames.length === 0 ? 0 : speechFrames / inputAudioFrames.length;
12992
- const silenceRatio = inputAudioFrames.length === 0 ? 0 : silenceFrames / inputAudioFrames.length;
12993
- const levels = audioFrames.map((frame) => numericMetadata(frame, "level") ?? numericMetadata(frame, "rms") ?? numericMetadata(frame, "energy")).filter((value) => value !== undefined);
12994
- const backpressureEvents = input.transport?.backpressureEvents ?? 0;
12995
- const maxGapMs = input.maxGapMs;
12996
- if (maxGapMs !== undefined && gapsMs.some((gap) => gap > maxGapMs)) {
12997
- pushIssue(issues, "warning", "media.quality_gap", `Observed media gap above ${String(maxGapMs)}ms.`);
12998
- }
12999
- if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
13000
- pushIssue(issues, "warning", "media.quality_jitter", `Observed jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
13001
- }
13002
- if (input.maxTimestampDriftMs !== undefined && timestampDriftMs !== undefined && timestampDriftMs > input.maxTimestampDriftMs) {
13003
- pushIssue(issues, "warning", "media.quality_timestamp_drift", `Observed timestamp drift ${String(timestampDriftMs)}ms above ${String(input.maxTimestampDriftMs)}ms.`);
13004
- }
13005
- if (input.minSpeechRatio !== undefined && inputAudioFrames.length > 0 && speechRatio < input.minSpeechRatio) {
13006
- pushIssue(issues, "warning", "media.quality_speech_ratio", `Observed speech ratio ${String(speechRatio)} below ${String(input.minSpeechRatio)}.`);
13007
- }
13008
- if (input.maxBackpressureEvents !== undefined && backpressureEvents > input.maxBackpressureEvents) {
13009
- pushIssue(issues, "warning", "media.quality_backpressure", `Observed ${String(backpressureEvents)} backpressure event(s), above ${String(input.maxBackpressureEvents)}.`);
13010
- }
13011
- return {
13012
- assistantAudioFrames: assistantAudioFrames.length,
13013
- backpressureEvents,
13014
- checkedAt: Date.now(),
13015
- durationMs,
13016
- gapCount: gapsMs.length,
13017
- gapsMs,
13018
- inputAudioFrames: inputAudioFrames.length,
13019
- issues,
13020
- jitterMs,
13021
- levelAverage: average(levels),
13022
- levelMax: max(levels),
13023
- levelMin: min(levels),
13024
- silenceFrames,
13025
- silenceRatio,
13026
- speechFrames,
13027
- speechRatio,
13028
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
13029
- timestampDriftMs,
13030
- totalFrames: frames.length,
13031
- unknownSpeechFrames
13032
- };
13033
- };
13034
- var buildMediaWebRTCStatsReport = (input = {}) => {
13035
- const stats = input.stats ?? [];
13036
- const issues = [];
13037
- const inbound = stats.filter((stat) => stat.type === "inbound-rtp" && stringStat(stat, "kind") !== "video");
13038
- const outbound = stats.filter((stat) => stat.type === "outbound-rtp" && stringStat(stat, "kind") !== "video");
13039
- const candidatePairs = stats.filter((stat) => stat.type === "candidate-pair");
13040
- const audioTracks = stats.filter((stat) => (stat.type === "track" || stat.type === "media-source") && stringStat(stat, "kind") === "audio");
13041
- const activeCandidatePairs = candidatePairs.filter((stat) => booleanStat(stat, "selected") === true || booleanStat(stat, "nominated") === true || stringStat(stat, "state") === "succeeded").length;
13042
- const liveAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") !== "ended" && stringStat(stat, "trackState") !== "ended" && booleanStat(stat, "ended") !== true).length;
13043
- const endedAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") === "ended" || stringStat(stat, "trackState") === "ended" || booleanStat(stat, "ended") === true).length;
13044
- const inboundPackets = inbound.reduce((total, stat) => total + (numericStat(stat, "packetsReceived") ?? 0), 0);
13045
- const outboundPackets = outbound.reduce((total, stat) => total + (numericStat(stat, "packetsSent") ?? 0), 0);
13046
- const packetsLost = [...inbound, ...outbound].reduce((total, stat) => total + Math.max(0, numericStat(stat, "packetsLost") ?? 0), 0);
13047
- const packetLossDenominator = inboundPackets + packetsLost;
13048
- const packetLossRatio = packetLossDenominator === 0 ? 0 : packetsLost / packetLossDenominator;
13049
- const bytesReceived = inbound.reduce((total, stat) => total + (numericStat(stat, "bytesReceived") ?? 0), 0);
13050
- const bytesSent = outbound.reduce((total, stat) => total + (numericStat(stat, "bytesSent") ?? 0), 0);
13051
- const roundTripTimeMs = max(candidatePairs.map((stat) => secondsToMs(numericStat(stat, "currentRoundTripTime") ?? numericStat(stat, "roundTripTime"))).filter((value) => value !== undefined));
13052
- const jitterMs = max([...inbound, ...outbound].map((stat) => secondsToMs(numericStat(stat, "jitter"))).filter((value) => value !== undefined));
13053
- const jitterBufferDelayMs = max(inbound.map((stat) => {
13054
- const delay = numericStat(stat, "jitterBufferDelay");
13055
- const emitted = numericStat(stat, "jitterBufferEmittedCount");
13056
- return delay !== undefined && emitted !== undefined && emitted > 0 ? delay / emitted * 1000 : undefined;
13057
- }).filter((value) => value !== undefined));
13058
- const audioLevels = audioTracks.map((stat) => numericStat(stat, "audioLevel")).filter((value) => value !== undefined);
13059
- if (input.requireConnectedCandidatePair && candidatePairs.length > 0 && activeCandidatePairs === 0) {
13060
- pushIssue(issues, "error", "media.webrtc_candidate_pair_missing", "No active WebRTC candidate pair was observed.");
13061
- }
13062
- if (input.requireLiveAudioTrack && liveAudioTracks === 0) {
13063
- pushIssue(issues, "error", "media.webrtc_audio_track_missing", "No live WebRTC audio track was observed.");
13064
- }
13065
- if (input.maxPacketLossRatio !== undefined && packetLossRatio > input.maxPacketLossRatio) {
13066
- pushIssue(issues, "warning", "media.webrtc_packet_loss", `Observed WebRTC packet loss ratio ${String(packetLossRatio)} above ${String(input.maxPacketLossRatio)}.`);
13067
- }
13068
- if (input.maxRoundTripTimeMs !== undefined && roundTripTimeMs !== undefined && roundTripTimeMs > input.maxRoundTripTimeMs) {
13069
- pushIssue(issues, "warning", "media.webrtc_round_trip_time", `Observed WebRTC RTT ${String(roundTripTimeMs)}ms above ${String(input.maxRoundTripTimeMs)}ms.`);
13070
- }
13071
- if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
13072
- pushIssue(issues, "warning", "media.webrtc_jitter", `Observed WebRTC jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
13073
- }
13074
- return {
13075
- activeCandidatePairs,
13076
- audioLevelAverage: average(audioLevels),
13077
- bytesReceived,
13078
- bytesSent,
13079
- checkedAt: Date.now(),
13080
- endedAudioTracks,
13081
- inboundPackets,
13082
- issues,
13083
- jitterBufferDelayMs,
13084
- jitterMs,
13085
- liveAudioTracks,
13086
- outboundPackets,
13087
- packetLossRatio,
13088
- packetsLost,
13089
- roundTripTimeMs,
13090
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
13091
- totalStats: stats.length
13092
- };
13093
- };
13094
- var collectMediaWebRTCStats = async (input) => {
13095
- const report = await input.peerConnection.getStats(input.selector ?? null);
13096
- return [...report.values()].map(normalizeWebRTCStat);
13097
- };
13098
- var buildMediaWebRTCStreamContinuityReport = (input = {}) => {
13099
- const stats = input.stats ?? [];
13100
- const previousStats = input.previousStats ?? [];
13101
- const issues = [];
13102
- const previousByKey = new Map(previousStats.map((stat) => [statKey(stat), stat]));
13103
- const audioRtp = stats.filter((stat) => (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") && stringStat(stat, "kind") !== "video" && stringStat(stat, "mediaType") !== "video");
13104
- const streams = audioRtp.map((stat) => {
13105
- const direction = stat.type === "outbound-rtp" ? "outbound" : "inbound";
13106
- const packetsKey = direction === "outbound" ? "packetsSent" : "packetsReceived";
13107
- const bytesKey = direction === "outbound" ? "bytesSent" : "bytesReceived";
13108
- const previous = previousByKey.get(statKey(stat));
13109
- const currentPackets = numericStat(stat, packetsKey);
13110
- const previousPackets = previous ? numericStat(previous, packetsKey) : undefined;
13111
- const currentBytes = numericStat(stat, bytesKey);
13112
- const previousBytes = previous ? numericStat(previous, bytesKey) : undefined;
13113
- const timeDeltaMs = stat.timestamp !== undefined && previous?.timestamp !== undefined ? stat.timestamp - previous.timestamp : undefined;
13114
- return {
13115
- bytesDelta: currentBytes !== undefined && previousBytes !== undefined ? currentBytes - previousBytes : undefined,
13116
- currentPackets,
13117
- direction,
13118
- id: statKey(stat),
13119
- packetDelta: currentPackets !== undefined && previousPackets !== undefined ? currentPackets - previousPackets : undefined,
13120
- previousPackets,
13121
- timeDeltaMs
13122
- };
13123
- });
13124
- const inbound = streams.filter((stream) => stream.direction === "inbound");
13125
- const outbound = streams.filter((stream) => stream.direction === "outbound");
13126
- const maxObservedGapMs = max(streams.map((stream) => stream.timeDeltaMs).filter((value) => value !== undefined));
13127
- const stalledInboundStreams = inbound.filter((stream) => input.maxInboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxInboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
13128
- const stalledOutboundStreams = outbound.filter((stream) => input.maxOutboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxOutboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
13129
- if (input.requireInboundAudio && inbound.length === 0) {
13130
- pushIssue(issues, "error", "media.webrtc_inbound_audio_missing", "No inbound WebRTC audio RTP stream was observed.");
13131
- }
13132
- if (input.requireOutboundAudio && outbound.length === 0) {
13133
- pushIssue(issues, "error", "media.webrtc_outbound_audio_missing", "No outbound WebRTC audio RTP stream was observed.");
13134
- }
13135
- if (input.maxGapMs !== undefined && maxObservedGapMs !== undefined && maxObservedGapMs > input.maxGapMs) {
13136
- pushIssue(issues, "warning", "media.webrtc_stream_gap", `Observed WebRTC stream sample gap ${String(maxObservedGapMs)}ms above ${String(input.maxGapMs)}ms.`);
13137
- }
13138
- if (stalledInboundStreams > 0) {
13139
- pushIssue(issues, "error", "media.webrtc_inbound_stalled", `${String(stalledInboundStreams)} inbound WebRTC audio stream(s) stopped receiving packets.`);
13140
- }
13141
- if (stalledOutboundStreams > 0) {
13142
- pushIssue(issues, "error", "media.webrtc_outbound_stalled", `${String(stalledOutboundStreams)} outbound WebRTC audio stream(s) stopped sending packets.`);
13143
- }
13144
- return {
13145
- checkedAt: Date.now(),
13146
- inboundAudioStreams: inbound.length,
13147
- issues,
13148
- maxObservedGapMs,
13149
- outboundAudioStreams: outbound.length,
13150
- stalledInboundStreams,
13151
- stalledOutboundStreams,
13152
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
13153
- streams,
13154
- totalStats: stats.length
13155
- };
13156
- };
13157
- var buildMediaPipelineCalibrationReport = (input = {}) => {
13158
- const frames = input.frames ?? [];
13159
- const issues = [];
13160
- const inputFrames = frames.filter((frame) => frame.kind === "input-audio");
13161
- const assistantFrames = frames.filter((frame) => frame.kind === "assistant-audio");
13162
- const turnCommitFrames = frames.filter((frame) => frame.kind === "turn-commit");
13163
- const interruptionFrameRecords = frames.filter((frame) => frame.kind === "interruption");
13164
- const traceLinkedFrames = frames.filter((frame) => frame.traceEventId).length;
13165
- const backpressureFrames = frames.filter((frame) => Boolean(frame.metadata?.backpressure)).length;
13166
- const audioLatencies = assistantFrames.map((frame) => frame.latencyMs).filter((latency) => typeof latency === "number");
13167
- const firstAudioLatencyMs = audioLatencies.length > 0 ? Math.min(...audioLatencies) : undefined;
13168
- const jitterValues = frames.map((frame) => numericMetadata(frame, "jitterMs")).filter((value) => value !== undefined);
13169
- const jitterMs = jitterValues.length > 0 ? Math.max(...jitterValues) : undefined;
13170
- const inputFormat = input.inputFormat ?? inputFrames.find((frame) => frame.format)?.format;
13171
- const outputFormat = input.outputFormat ?? assistantFrames.find((frame) => frame.format)?.format;
13172
- const resamplingRequired = Boolean(input.expectedInputFormat && inputFormat && inputFormat.sampleRateHz !== input.expectedInputFormat.sampleRateHz) || Boolean(input.expectedOutputFormat && outputFormat && outputFormat.sampleRateHz !== input.expectedOutputFormat.sampleRateHz);
13173
- const resamplingTargetHz = resamplingRequired && input.expectedInputFormat ? input.expectedInputFormat.sampleRateHz : resamplingRequired ? input.expectedOutputFormat?.sampleRateHz : undefined;
13174
- if (inputFrames.length === 0) {
13175
- pushIssue(issues, "warning", "media.input_audio_missing", "No input audio frames were observed.");
13176
- }
13177
- if (assistantFrames.length === 0) {
13178
- pushIssue(issues, "warning", "media.assistant_audio_missing", "No assistant audio frames were observed.");
13179
- }
13180
- if (input.expectedInputFormat && inputFormat && !formatMatches2(inputFormat, input.expectedInputFormat)) {
13181
- pushIssue(issues, inputFormat.sampleRateHz === input.expectedInputFormat.sampleRateHz ? "warning" : "error", "media.input_format_mismatch", `Input format ${formatLabel2(inputFormat)} does not match expected ${formatLabel2(input.expectedInputFormat)}.`);
13182
- }
13183
- if (input.expectedOutputFormat && outputFormat && !formatMatches2(outputFormat, input.expectedOutputFormat)) {
13184
- pushIssue(issues, outputFormat.sampleRateHz === input.expectedOutputFormat.sampleRateHz ? "warning" : "error", "media.output_format_mismatch", `Output format ${formatLabel2(outputFormat)} does not match expected ${formatLabel2(input.expectedOutputFormat)}.`);
13185
- }
13186
- if (firstAudioLatencyMs !== undefined && input.maxFirstAudioLatencyMs !== undefined && firstAudioLatencyMs > input.maxFirstAudioLatencyMs) {
13187
- pushIssue(issues, "error", "media.first_audio_latency", `First audio latency ${String(firstAudioLatencyMs)}ms exceeds budget ${String(input.maxFirstAudioLatencyMs)}ms.`);
13188
- }
13189
- if (jitterMs !== undefined && input.maxJitterMs !== undefined && jitterMs > input.maxJitterMs) {
13190
- pushIssue(issues, "warning", "media.jitter", `Media jitter ${String(jitterMs)}ms exceeds budget ${String(input.maxJitterMs)}ms.`);
13191
- }
13192
- if (input.maxBackpressureFrames !== undefined && backpressureFrames > input.maxBackpressureFrames) {
13193
- pushIssue(issues, "warning", "media.backpressure", `Backpressure frame count ${String(backpressureFrames)} exceeds budget ${String(input.maxBackpressureFrames)}.`);
13194
- }
13195
- if (input.requireInterruptionFrame && interruptionFrameRecords.length === 0) {
13196
- pushIssue(issues, "warning", "media.interruption_missing", "No interruption frame was observed.");
13197
- }
13198
- if (input.requireTraceEvidence && traceLinkedFrames === 0) {
13199
- pushIssue(issues, "warning", "media.trace_evidence_missing", "No media frames were linked to trace evidence.");
13200
- }
13201
- return {
13202
- assistantAudioFrames: assistantFrames.length,
13203
- backpressureFrames,
13204
- checkedAt: Date.now(),
13205
- firstAudioLatencyMs,
13206
- inputAudioFrames: inputFrames.length,
13207
- inputFormat,
13208
- interruptionFrames: interruptionFrameRecords.length,
13209
- issues,
13210
- jitterMs,
13211
- outputFormat,
13212
- resamplingRequired,
13213
- resamplingTargetHz,
13214
- status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
13215
- surface: input.surface ?? "media-pipeline",
13216
- traceLinkedFrames,
13217
- turnCommitFrames: turnCommitFrames.length
13218
- };
13219
- };
13220
-
13221
- // src/mediaPipelineRoutes.ts
12558
+ import {
12559
+ buildMediaInterruptionReport,
12560
+ buildMediaPipelineCalibrationReport,
12561
+ buildMediaQualityReport,
12562
+ buildMediaResamplingPlan,
12563
+ buildMediaVadReport,
12564
+ summarizeMediaProcessorGraphReport,
12565
+ summarizeMediaQualityReport,
12566
+ summarizeMediaTransportReport
12567
+ } from "@absolutejs/media";
13222
12568
  var escapeHtml16 = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
13223
12569
  var statusRank2 = {
13224
12570
  pass: 0,
@@ -13460,8 +12806,78 @@ var createVoiceMediaPipelineRoutes = (options = {}) => {
13460
12806
  }
13461
12807
  return app;
13462
12808
  };
12809
+ var PROOF_SUMMARY_ISSUE_LIMIT = 8;
12810
+ var collectIssueCodes = (issues) => Array.from(new Set(issues.map((issue) => issue.code)));
12811
+ var summarizeVoiceMediaPipelineReport = (report, options = {}) => {
12812
+ const calibration = report.calibration;
12813
+ const quality = summarizeMediaQualityReport(report.quality);
12814
+ const processorGraph = report.processorGraph ? summarizeMediaProcessorGraphReport(report.processorGraph) : undefined;
12815
+ const transport = report.transport ? summarizeMediaTransportReport(report.transport) : undefined;
12816
+ const interruptionIssueCodes = collectIssueCodes(report.interruption.issues);
12817
+ const issueCodes = Array.from(new Set([
12818
+ ...collectIssueCodes(calibration.issues),
12819
+ ...quality.issueCodes,
12820
+ ...interruptionIssueCodes,
12821
+ ...processorGraph?.issueCodes ?? []
12822
+ ]));
12823
+ const issues = [
12824
+ ...calibration.issues,
12825
+ ...report.quality.issues,
12826
+ ...report.interruption.issues
12827
+ ].slice(0, PROOF_SUMMARY_ISSUE_LIMIT);
12828
+ return {
12829
+ artifacts: options.artifacts,
12830
+ calibration: {
12831
+ assistantAudioFrames: calibration.assistantAudioFrames,
12832
+ backpressureFrames: calibration.backpressureFrames,
12833
+ firstAudioLatencyMs: calibration.firstAudioLatencyMs,
12834
+ inputAudioFrames: calibration.inputAudioFrames,
12835
+ interruptionFrames: calibration.interruptionFrames,
12836
+ issueCodes: collectIssueCodes(calibration.issues),
12837
+ jitterMs: calibration.jitterMs,
12838
+ resamplingRequired: calibration.resamplingRequired,
12839
+ status: calibration.status,
12840
+ surface: calibration.surface,
12841
+ traceLinkedFrames: calibration.traceLinkedFrames,
12842
+ turnCommitFrames: calibration.turnCommitFrames
12843
+ },
12844
+ checkedAt: report.checkedAt,
12845
+ frames: report.frames,
12846
+ interruption: {
12847
+ interruptionFrames: report.interruption.interruptionFrames,
12848
+ issueCodes: interruptionIssueCodes,
12849
+ latenciesMs: report.interruption.latenciesMs,
12850
+ status: report.interruption.status
12851
+ },
12852
+ issueCodes,
12853
+ issues,
12854
+ ok: report.ok,
12855
+ processorGraph,
12856
+ quality,
12857
+ resampling: report.resampling ? {
12858
+ ratio: report.resampling.ratio,
12859
+ required: report.resampling.required,
12860
+ status: report.resampling.status
12861
+ } : undefined,
12862
+ sessionIds: report.sessionIds,
12863
+ status: report.status,
12864
+ surface: report.surface,
12865
+ transport,
12866
+ vad: {
12867
+ inputAudioFrames: report.vad.inputAudioFrames,
12868
+ segmentCount: report.vad.segments.length,
12869
+ status: report.vad.status
12870
+ }
12871
+ };
12872
+ };
13463
12873
  // src/telephonyMediaRoutes.ts
13464
12874
  import { Elysia as Elysia14 } from "elysia";
12875
+ import {
12876
+ buildMediaTelephonyStreamLifecycleReport,
12877
+ createTelephonyMediaSerializer,
12878
+ parseTelephonyMediaFrame,
12879
+ serializeTelephonyMediaFrame
12880
+ } from "@absolutejs/media";
13465
12881
  var demoPayload = Buffer.from(new Uint8Array([1, 2, 3, 4])).toString("base64");
13466
12882
  var demoEnvelope = (carrier) => {
13467
12883
  if (carrier === "twilio") {
@@ -13964,6 +13380,219 @@ var createVoiceBrowserCallProfileRoutes = (options = {}) => {
13964
13380
  }
13965
13381
  return routes;
13966
13382
  };
13383
+ // src/mediaPipelineSurfaces.ts
13384
+ import {
13385
+ buildMediaProcessorGraphArtifact,
13386
+ buildMediaQualityArtifact,
13387
+ buildMediaTransportArtifact,
13388
+ writeMediaArtifact
13389
+ } from "@absolutejs/media";
13390
+ var calibrationIssues = (report) => report.calibration.issues.map((issue) => ({
13391
+ code: issue.code,
13392
+ message: issue.message,
13393
+ severity: issue.severity,
13394
+ source: "calibration",
13395
+ surface: report.surface
13396
+ }));
13397
+ var qualityIssues = (report) => report.quality.issues.map((issue) => ({
13398
+ code: issue.code,
13399
+ message: issue.message,
13400
+ severity: issue.severity,
13401
+ source: "quality",
13402
+ surface: report.surface
13403
+ }));
13404
+ var interruptionIssues = (report) => report.interruption.issues.map((issue) => ({
13405
+ code: issue.code,
13406
+ message: issue.message,
13407
+ severity: issue.severity,
13408
+ source: "interruption",
13409
+ surface: report.surface
13410
+ }));
13411
+ var transportIssues = (report) => {
13412
+ if (!report.transport)
13413
+ return [];
13414
+ const entries = [];
13415
+ if (report.transport.failed) {
13416
+ entries.push({
13417
+ code: "media.transport_failed",
13418
+ message: `Media transport ${report.transport.name} entered the failed state.`,
13419
+ severity: "error",
13420
+ source: "transport",
13421
+ surface: report.surface
13422
+ });
13423
+ }
13424
+ if (report.transport.backpressureEvents > 0) {
13425
+ entries.push({
13426
+ code: "media.transport_backpressure",
13427
+ message: `Media transport ${report.transport.name} reported ${String(report.transport.backpressureEvents)} backpressure event(s).`,
13428
+ severity: "warning",
13429
+ source: "transport",
13430
+ surface: report.surface
13431
+ });
13432
+ }
13433
+ const errorEvents = report.transport.events.filter((event) => event.kind === "error");
13434
+ for (const event of errorEvents) {
13435
+ entries.push({
13436
+ code: "media.transport_error",
13437
+ message: event.error ?? "Media transport error event.",
13438
+ severity: "error",
13439
+ source: "transport",
13440
+ surface: report.surface
13441
+ });
13442
+ }
13443
+ return entries;
13444
+ };
13445
+ var processorGraphIssues = (report) => {
13446
+ if (!report.processorGraph)
13447
+ return [];
13448
+ return report.processorGraph.errors.map((event) => ({
13449
+ code: `media.graph_${event.kind}`,
13450
+ message: event.error ?? `Processor graph reported ${event.kind} (state ${event.state}).`,
13451
+ severity: "error",
13452
+ source: "processor-graph",
13453
+ surface: report.surface
13454
+ }));
13455
+ };
13456
+ var extractVoiceMediaPipelineIssueEntries = (report) => [
13457
+ ...calibrationIssues(report),
13458
+ ...qualityIssues(report),
13459
+ ...interruptionIssues(report),
13460
+ ...transportIssues(report),
13461
+ ...processorGraphIssues(report)
13462
+ ];
13463
+ var toReadinessStatus = (status) => status === "fail" ? "fail" : status === "warn" ? "warn" : "pass";
13464
+ var readinessStatusFromIssues = (issues) => {
13465
+ if (issues.some((issue) => issue.severity === "error"))
13466
+ return "fail";
13467
+ if (issues.length > 0)
13468
+ return "warn";
13469
+ return "pass";
13470
+ };
13471
+ var buildVoiceMediaPipelineReadinessChecks = (report, options = {}) => {
13472
+ const baseHref = options.baseHref ?? "/voice/media-pipeline";
13473
+ const label = options.label ?? "Media pipeline";
13474
+ const checks = [
13475
+ {
13476
+ detail: `Status ${report.status}, surface ${report.surface}, ${String(report.frames)} frame(s).`,
13477
+ href: baseHref,
13478
+ label: `${label}: overall`,
13479
+ status: toReadinessStatus(report.status)
13480
+ },
13481
+ {
13482
+ detail: `${String(report.quality.issues.length)} quality issue(s); gaps ${String(report.quality.gapCount)}, jitter ${String(report.quality.jitterMs ?? "n/a")}ms, speech ratio ${(report.quality.speechRatio * 100).toFixed(1)}%.`,
13483
+ href: baseHref,
13484
+ label: `${label}: media quality`,
13485
+ status: toReadinessStatus(report.quality.status),
13486
+ value: report.quality.gapCount
13487
+ }
13488
+ ];
13489
+ if (report.transport) {
13490
+ checks.push({
13491
+ detail: `${report.transport.state}, in ${String(report.transport.inputFrames)}, out ${String(report.transport.outputFrames)}, backpressure ${String(report.transport.backpressureEvents)}.`,
13492
+ href: baseHref,
13493
+ label: `${label}: transport`,
13494
+ status: toReadinessStatus(report.transport.status),
13495
+ value: report.transport.state
13496
+ });
13497
+ }
13498
+ if (report.processorGraph) {
13499
+ checks.push({
13500
+ detail: `${report.processorGraph.state}, nodes ${String(report.processorGraph.nodes.length)}, errors ${String(report.processorGraph.errors.length)}, dropped ${String(report.processorGraph.droppedFrames)}.`,
13501
+ href: baseHref,
13502
+ label: `${label}: processor graph`,
13503
+ status: toReadinessStatus(report.processorGraph.status),
13504
+ value: report.processorGraph.state
13505
+ });
13506
+ }
13507
+ checks.push({
13508
+ detail: `${String(report.interruption.interruptionFrames)} interruption frame(s); ${String(report.interruption.issues.length)} issue(s).`,
13509
+ href: baseHref,
13510
+ label: `${label}: interruption`,
13511
+ status: readinessStatusFromIssues(report.interruption.issues),
13512
+ value: report.interruption.interruptionFrames
13513
+ });
13514
+ return checks;
13515
+ };
13516
+ var severityToIncident = (severity) => severity === "error" ? "critical" : "warn";
13517
+ var sourceSuffix = {
13518
+ calibration: "calibration",
13519
+ interruption: "interruption",
13520
+ "processor-graph": "graph",
13521
+ quality: "quality",
13522
+ transport: "transport"
13523
+ };
13524
+ var buildVoiceMediaPipelineIncidentEvents = (report, options = {}) => {
13525
+ const baseHref = options.baseHref ?? "/voice/media-pipeline";
13526
+ const category = options.category ?? "monitor";
13527
+ const source = options.source ?? "media-pipeline";
13528
+ const now = options.now ?? (() => Date.now());
13529
+ const at = now();
13530
+ const entries = extractVoiceMediaPipelineIssueEntries(report);
13531
+ return entries.map((entry, index) => ({
13532
+ at,
13533
+ category,
13534
+ detail: entry.message,
13535
+ href: baseHref,
13536
+ id: `media-pipeline:${sourceSuffix[entry.source]}:${entry.code}:${String(index)}`,
13537
+ label: `Media ${sourceSuffix[entry.source]} ${entry.severity}: ${entry.code}`,
13538
+ severity: severityToIncident(entry.severity),
13539
+ source,
13540
+ value: entry.code
13541
+ }));
13542
+ };
13543
+ var buildHref = (base, jsonPath, fallbackSlug) => {
13544
+ if (!base)
13545
+ return;
13546
+ const filename = jsonPath.split("/").pop() ?? `${fallbackSlug}.json`;
13547
+ return `${base.replace(/\/$/, "")}/${filename}`;
13548
+ };
13549
+ var recordFromWrite = (kind, write, hrefBase, slug) => ({
13550
+ href: buildHref(hrefBase, write.jsonPath, slug),
13551
+ jsonPath: write.jsonPath,
13552
+ kind,
13553
+ markdownPath: write.markdownPath,
13554
+ summary: write.summary
13555
+ });
13556
+ var writeVoiceMediaPipelineArtifacts = async (options) => {
13557
+ const slugPrefix = options.slugPrefix ?? "media";
13558
+ const qualitySlug = `${slugPrefix}-quality`;
13559
+ const transportSlug = `${slugPrefix}-transport`;
13560
+ const graphSlug = `${slugPrefix}-processor-graph`;
13561
+ const artifacts = [];
13562
+ const hrefs = {};
13563
+ const qualityArtifact = buildMediaQualityArtifact(options.report.quality);
13564
+ const qualityWrite = await writeMediaArtifact({
13565
+ dir: options.dir,
13566
+ slug: qualitySlug,
13567
+ ...qualityArtifact
13568
+ });
13569
+ const qualityRecord = recordFromWrite("quality", qualityWrite, options.hrefBase, qualitySlug);
13570
+ artifacts.push(qualityRecord);
13571
+ hrefs.quality = qualityRecord.href;
13572
+ if (options.report.transport) {
13573
+ const transportArtifact = buildMediaTransportArtifact(options.report.transport);
13574
+ const transportWrite = await writeMediaArtifact({
13575
+ dir: options.dir,
13576
+ slug: transportSlug,
13577
+ ...transportArtifact
13578
+ });
13579
+ const transportRecord = recordFromWrite("transport", transportWrite, options.hrefBase, transportSlug);
13580
+ artifacts.push(transportRecord);
13581
+ hrefs.transport = transportRecord.href;
13582
+ }
13583
+ if (options.report.processorGraph) {
13584
+ const graphArtifact = buildMediaProcessorGraphArtifact(options.report.processorGraph);
13585
+ const graphWrite = await writeMediaArtifact({
13586
+ dir: options.dir,
13587
+ slug: graphSlug,
13588
+ ...graphArtifact
13589
+ });
13590
+ const graphRecord = recordFromWrite("processor-graph", graphWrite, options.hrefBase, graphSlug);
13591
+ artifacts.push(graphRecord);
13592
+ hrefs.processorGraph = graphRecord.href;
13593
+ }
13594
+ return { artifacts, hrefs };
13595
+ };
13967
13596
  // src/demoReadyRoutes.ts
13968
13597
  import { Elysia as Elysia17 } from "elysia";
13969
13598
  var escapeHtml20 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
@@ -19683,7 +19312,7 @@ import { Elysia as Elysia27 } from "elysia";
19683
19312
  var escapeHtml28 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
19684
19313
  var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
19685
19314
  var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
19686
- var firstString3 = (payload, keys) => {
19315
+ var firstString2 = (payload, keys) => {
19687
19316
  for (const key of keys) {
19688
19317
  const value = getString10(payload[key]);
19689
19318
  if (value) {
@@ -19692,7 +19321,7 @@ var firstString3 = (payload, keys) => {
19692
19321
  }
19693
19322
  return;
19694
19323
  };
19695
- var firstNumber2 = (payload, keys) => {
19324
+ var firstNumber = (payload, keys) => {
19696
19325
  for (const key of keys) {
19697
19326
  const value = getNumber7(payload[key]);
19698
19327
  if (value !== undefined) {
@@ -19701,20 +19330,20 @@ var firstNumber2 = (payload, keys) => {
19701
19330
  }
19702
19331
  return;
19703
19332
  };
19704
- var eventProvider = (event) => firstString3(event.payload, [
19333
+ var eventProvider = (event) => firstString2(event.payload, [
19705
19334
  "provider",
19706
19335
  "selectedProvider",
19707
19336
  "fallbackProvider",
19708
19337
  "variantId"
19709
19338
  ]);
19710
- var eventStatus = (event) => firstString3(event.payload, [
19339
+ var eventStatus = (event) => firstString2(event.payload, [
19711
19340
  "providerStatus",
19712
19341
  "status",
19713
19342
  "disposition",
19714
19343
  "type",
19715
19344
  "reason"
19716
19345
  ]);
19717
- var eventElapsedMs = (event) => firstNumber2(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
19346
+ var eventElapsedMs = (event) => firstNumber(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
19718
19347
  var resolveSessionHref2 = (value, sessionId) => {
19719
19348
  if (value === false) {
19720
19349
  return;
@@ -22811,7 +22440,7 @@ var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
22811
22440
  return assertion;
22812
22441
  };
22813
22442
  var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
22814
- var firstString4 = (source, keys) => {
22443
+ var firstString3 = (source, keys) => {
22815
22444
  for (const key of keys) {
22816
22445
  const value = source[key];
22817
22446
  if (typeof value === "string" && value.trim()) {
@@ -22822,7 +22451,7 @@ var firstString4 = (source, keys) => {
22822
22451
  }
22823
22452
  }
22824
22453
  };
22825
- var firstNumber3 = (source, keys) => {
22454
+ var firstNumber2 = (source, keys) => {
22826
22455
  for (const key of keys) {
22827
22456
  const value = source[key];
22828
22457
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -23187,8 +22816,8 @@ var verifyVoiceTelephonyWebhook = async (input) => {
23187
22816
  var durationMsFromSeconds = (value) => typeof value === "number" ? value * 1000 : undefined;
23188
22817
  var parseVoiceTelephonyWebhookEvent = (input) => {
23189
22818
  const payload = flattenPayload(input.body);
23190
- const provider = firstString4(payload, ["provider", "Provider"]) ?? input.provider;
23191
- const status = firstString4(payload, [
22819
+ const provider = firstString3(payload, ["provider", "Provider"]) ?? input.provider;
22820
+ const status = firstString3(payload, [
23192
22821
  "CallStatus",
23193
22822
  "call_status",
23194
22823
  "callStatus",
@@ -23198,7 +22827,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
23198
22827
  "event_type",
23199
22828
  "type"
23200
22829
  ]);
23201
- const durationMs = firstNumber3(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber3(payload, [
22830
+ const durationMs = firstNumber2(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber2(payload, [
23202
22831
  "CallDuration",
23203
22832
  "call_duration",
23204
22833
  "callDuration",
@@ -23206,21 +22835,21 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
23206
22835
  "dial_call_duration",
23207
22836
  "duration"
23208
22837
  ]));
23209
- const sipCode = firstNumber3(payload, [
22838
+ const sipCode = firstNumber2(payload, [
23210
22839
  "SipResponseCode",
23211
22840
  "sip_response_code",
23212
22841
  "sipCode",
23213
22842
  "sip_code",
23214
22843
  "hangupCauseCode"
23215
22844
  ]);
23216
- const from = firstString4(payload, ["From", "from", "caller_id", "callerId"]);
23217
- const to = firstString4(payload, [
22845
+ const from = firstString3(payload, ["From", "from", "caller_id", "callerId"]);
22846
+ const to = firstString3(payload, [
23218
22847
  "To",
23219
22848
  "to",
23220
22849
  "called_number",
23221
22850
  "calledNumber"
23222
22851
  ]);
23223
- const target = firstString4(payload, [
22852
+ const target = firstString3(payload, [
23224
22853
  "transferTarget",
23225
22854
  "TransferTarget",
23226
22855
  "target",
@@ -23228,7 +22857,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
23228
22857
  "department"
23229
22858
  ]);
23230
22859
  return {
23231
- answeredBy: firstString4(payload, [
22860
+ answeredBy: firstString3(payload, [
23232
22861
  "AnsweredBy",
23233
22862
  "answered_by",
23234
22863
  "answeredBy",
@@ -23242,7 +22871,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
23242
22871
  ...payload
23243
22872
  },
23244
22873
  provider,
23245
- reason: firstString4(payload, [
22874
+ reason: firstString3(payload, [
23246
22875
  "Reason",
23247
22876
  "reason",
23248
22877
  "HangupCause",
@@ -23258,7 +22887,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
23258
22887
  var defaultSessionId = (input) => {
23259
22888
  const payload = flattenPayload(input.body);
23260
22889
  const metadataSessionId = input.event.metadata?.sessionId;
23261
- return firstString4(input.query, ["sessionId", "session_id"]) ?? firstString4(payload, [
22890
+ return firstString3(input.query, ["sessionId", "session_id"]) ?? firstString3(payload, [
23262
22891
  "sessionId",
23263
22892
  "session_id",
23264
22893
  "SessionId",
@@ -23273,7 +22902,7 @@ var defaultSessionId = (input) => {
23273
22902
  };
23274
22903
  var defaultIdempotencyKey = (input) => {
23275
22904
  const payload = flattenPayload(input.body);
23276
- const eventId = firstString4(payload, [
22905
+ const eventId = firstString3(payload, [
23277
22906
  "id",
23278
22907
  "event_id",
23279
22908
  "eventId",
@@ -27041,7 +26670,7 @@ var percentile5 = (values, percentileValue) => {
27041
26670
  const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(percentileValue / 100 * sorted.length) - 1));
27042
26671
  return Math.round(sorted[index] ?? 0);
27043
26672
  };
27044
- var average2 = (values) => values.length === 0 ? undefined : Math.round(values.reduce((total, value) => total + value, 0) / values.length);
26673
+ var average = (values) => values.length === 0 ? undefined : Math.round(values.reduce((total, value) => total + value, 0) / values.length);
27045
26674
  var resolveBudget = (stage, options) => ({
27046
26675
  failAfterMs: options.budgets?.[stage]?.failAfterMs ?? options.failAfterMs ?? DEFAULT_FAIL_AFTER_MS,
27047
26676
  warnAfterMs: options.budgets?.[stage]?.warnAfterMs ?? options.warnAfterMs ?? DEFAULT_WARN_AFTER_MS
@@ -27235,7 +26864,7 @@ var summarizeStage = (stage, measurements, options) => {
27235
26864
  const failed = stageMeasurements.filter((measurement) => measurement.status === "fail").length;
27236
26865
  const warnings = stageMeasurements.filter((measurement) => measurement.status === "warn").length;
27237
26866
  return {
27238
- averageMs: average2(latencies),
26867
+ averageMs: average(latencies),
27239
26868
  budget: resolveBudget(stage, options),
27240
26869
  failed,
27241
26870
  label: STAGE_LABELS[stage],
@@ -35113,7 +34742,7 @@ import { Elysia as Elysia53 } from "elysia";
35113
34742
  var DEFAULT_WARN_AFTER_MS2 = 1800;
35114
34743
  var DEFAULT_FAIL_AFTER_MS2 = 3200;
35115
34744
  var escapeHtml50 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
35116
- var firstNumber4 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
34745
+ var firstNumber3 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
35117
34746
  var getString19 = (value) => typeof value === "string" && value.trim() ? value : undefined;
35118
34747
  var createTraceStageIndex = (events) => {
35119
34748
  const index = new Map;
@@ -35137,8 +34766,8 @@ var createTraceStageIndex = (events) => {
35137
34766
  };
35138
34767
  var summarizeTurn = (sessionId, turn, options) => {
35139
34768
  const traceStages = options.stageIndex?.get(`${sessionId}:${turn.id}`);
35140
- const firstTranscriptAt = traceStages?.get("speech_detected") ?? firstNumber4(turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
35141
- const finalTranscriptAt = traceStages?.get("final_transcript") ?? firstNumber4(turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
34769
+ const firstTranscriptAt = traceStages?.get("speech_detected") ?? firstNumber3(turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
34770
+ const finalTranscriptAt = traceStages?.get("final_transcript") ?? firstNumber3(turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
35142
34771
  const committedAt = traceStages?.get("turn_committed") ?? turn.committedAt;
35143
34772
  const assistantTextStartedAt = traceStages?.get("assistant_text_started") ?? (turn.assistantText ? committedAt : undefined);
35144
34773
  const ttsSendStartedAt = traceStages?.get("tts_send_started");
@@ -39005,7 +38634,7 @@ var statusRank8 = {
39005
38634
  warn: 1,
39006
38635
  fail: 2
39007
38636
  };
39008
- var statusExceeds2 = (actual, max2) => statusRank8[actual] > statusRank8[max2];
38637
+ var statusExceeds2 = (actual, max) => statusRank8[actual] > statusRank8[max];
39009
38638
  var buildVoiceProviderContractMatrix = (input) => {
39010
38639
  const rows = input.contracts.map((contract) => {
39011
38640
  const configured = contract.configured !== false;
@@ -43051,6 +42680,7 @@ var createVoiceProofPackRoutes = (options) => {
43051
42680
  };
43052
42681
  export {
43053
42682
  writeVoiceProofPack,
42683
+ writeVoiceMediaPipelineArtifacts,
43054
42684
  withVoiceOpsTaskId,
43055
42685
  withVoiceIntegrationEventId,
43056
42686
  voiceTelephonyOutcomeToRouteResult,
@@ -43088,6 +42718,7 @@ export {
43088
42718
  summarizeVoiceOpsTaskQueue,
43089
42719
  summarizeVoiceOpsTaskAnalytics,
43090
42720
  summarizeVoiceOpsStatus,
42721
+ summarizeVoiceMediaPipelineReport,
43091
42722
  summarizeVoiceLiveLatency,
43092
42723
  summarizeVoiceIntegrationEvents,
43093
42724
  summarizeVoiceHandoffHealth,
@@ -43294,6 +42925,7 @@ export {
43294
42925
  filterVoiceAuditEvents,
43295
42926
  fetchVoiceProofTarget,
43296
42927
  failVoiceOpsTask,
42928
+ extractVoiceMediaPipelineIssueEntries,
43297
42929
  exportVoiceTrace,
43298
42930
  exportVoiceAuditTrail,
43299
42931
  evaluateVoiceTrace,
@@ -43736,6 +43368,8 @@ export {
43736
43368
  buildVoiceObservabilityArtifactIndex,
43737
43369
  buildVoiceMonitorRunReport,
43738
43370
  buildVoiceMediaPipelineReport,
43371
+ buildVoiceMediaPipelineReadinessChecks,
43372
+ buildVoiceMediaPipelineIncidentEvents,
43739
43373
  buildVoiceLiveOpsControlState,
43740
43374
  buildVoiceLatencySLOGate,
43741
43375
  buildVoiceIncidentTimelineReport,