@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/angular/index.js +5 -662
- package/dist/client/index.js +5 -662
- package/dist/generated/htmxBootstrapBundle.d.ts +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +323 -689
- package/dist/mediaPipelineRoutes.d.ts +55 -1
- package/dist/mediaPipelineSurfaces.d.ts +48 -0
- package/dist/react/index.js +5 -662
- package/dist/svelte/index.js +5 -662
- package/dist/testing/index.js +51 -708
- package/dist/vue/index.js +5 -662
- package/package.json +3 -3
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("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll(\'"\',""").replaceAll("\'","'"),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("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll(\'"\',""").replaceAll("\'","'"),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
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -19683,7 +19312,7 @@ import { Elysia as Elysia27 } from "elysia";
|
|
|
19683
19312
|
var escapeHtml28 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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
|
|
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 =
|
|
23191
|
-
const status =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
23217
|
-
const to =
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
35116
|
-
var
|
|
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") ??
|
|
35141
|
-
const finalTranscriptAt = traceStages?.get("final_transcript") ??
|
|
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,
|
|
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,
|