@absolutejs/voice 0.0.22-beta.464 → 0.0.22-beta.466
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 -927
- package/dist/client/htmxBootstrap.js +7 -330
- package/dist/client/index.js +5 -927
- package/dist/generated/htmxBootstrapBundle.d.ts +1 -1
- package/dist/incidentTimeline.d.ts +1 -0
- package/dist/index.js +146 -1009
- package/dist/operationsRecord.d.ts +16 -0
- package/dist/react/index.js +5 -927
- package/dist/svelte/index.js +5 -927
- package/dist/testing/index.js +92 -975
- package/dist/vue/index.js +5 -927
- 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 We=(e)=>{if(typeof e!=="string")return e;return document.querySelector(e)},$e=(e,n,c,o)=>{let s=n??e.getAttribute("hx-get")??"";if(!s)return"";let t=new URL(s,window.location.origin);if(o)t.searchParams.set(c,o);else t.searchParams.delete(c);return`${t.pathname}${t.search}${t.hash}`},fe=(e,n)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let c=We(n.element);if(!c)return()=>{};let o=n.eventName??"voice-refresh",s=n.sessionQueryParam??"sessionId",t=()=>{let r=window,d=$e(c,n.route,s,e.sessionId);if(d)c.setAttribute("hx-get",d);r.htmx?.process?.(c),r.htmx?.trigger?.(c,o)},i=e.subscribe(t);return t(),()=>{i()}};var ke=(e)=>Math.max(-1,Math.min(1,e)),qe=(e)=>{let n=new Int16Array(e.length);for(let c=0;c<e.length;c+=1){let o=ke(e[c]??0);n[c]=o<0?o*32768:o*32767}return new Uint8Array(n.buffer)},Xe=(e)=>{let n=e instanceof Uint8Array?e:new Uint8Array(e);if(n.byteLength<2)return 0;let c=new Int16Array(n.buffer,n.byteOffset,Math.floor(n.byteLength/2));if(c.length===0)return 0;let o=0;for(let s of c){let t=s/32768;o+=t*t}return Math.min(1,Math.max(0,Math.sqrt(o/c.length)*5.5))},ze=(e,n,c)=>{if(n===c)return e;let o=n/c,s=Math.round(e.length/o),t=new Float32Array(s),i=0,r=0;while(i<t.length){let d=Math.round((i+1)*o),f=0,a=0;for(let A=r;A<d&&A<e.length;A+=1)f+=e[A]??0,a+=1;t[i]=a>0?f/a:0,i+=1,r=d}return t},Ae=(e)=>{let n=null,c=null,o=null,s=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.");s=await navigator.mediaDevices.getUserMedia({audio:{channelCount:e.channelCount??1}}),n=new r,c=n.createMediaStreamSource(s),o=n.createScriptProcessor(4096,1,1),o.onaudioprocess=(d)=>{let f=d.inputBuffer.getChannelData(0),a=ze(f,n?.sampleRate??48000,e.sampleRateHz??16000),A=qe(a);e.onLevel?.(Xe(A)),e.onAudio(A)},c.connect(o),o.connect(n.destination)},stop:()=>{o?.disconnect(),c?.disconnect(),s?.getTracks().forEach((r)=>r.stop()),n?.close(),e.onLevel?.(0),n=null,s=null,o=null,c=null}}};var ne=(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 c of["message","reason","description"]){let o=n[c];if(typeof o==="string"&&o.trim())return o}if("error"in n)return ne(n.error);if("cause"in n)return ne(n.cause);try{return JSON.stringify(e)}catch{}}return"Unexpected error"},he=(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:ne(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{mkdir:pn,writeFile:ec}=(()=>({}));function H(e){if(typeof e!=="string")throw TypeError("Path must be a string. Received "+JSON.stringify(e))}function Ce(e,n){var c="",o=0,s=-1,t=0,i;for(var r=0;r<=e.length;++r){if(r<e.length)i=e.charCodeAt(r);else if(i===47)break;else i=47;if(i===47){if(s===r-1||t===1);else if(s!==r-1&&t===2){if(c.length<2||o!==2||c.charCodeAt(c.length-1)!==46||c.charCodeAt(c.length-2)!==46){if(c.length>2){var d=c.lastIndexOf("/");if(d!==c.length-1){if(d===-1)c="",o=0;else c=c.slice(0,d),o=c.length-1-c.lastIndexOf("/");s=r,t=0;continue}}else if(c.length===2||c.length===1){c="",o=0,s=r,t=0;continue}}if(n){if(c.length>0)c+="/..";else c="..";o=2}}else{if(c.length>0)c+="/"+e.slice(s+1,r);else c=e.slice(s+1,r);o=r-s-1}s=r,t=0}else if(i===46&&t!==-1)++t;else t=-1}return c}function Ye(e,n){var c=n.dir||n.root,o=n.base||(n.name||"")+(n.ext||"");if(!c)return o;if(c===n.root)return c+o;return c+e+o}function ce(){var e="",n=!1,c;for(var o=arguments.length-1;o>=-1&&!n;o--){var s;if(o>=0)s=arguments[o];else{if(c===void 0)c=process.cwd();s=c}if(H(s),s.length===0)continue;e=s+"/"+e,n=s.charCodeAt(0)===47}if(e=Ce(e,!n),n)if(e.length>0)return"/"+e;else return"/";else if(e.length>0)return e;else return"."}function ye(e){if(H(e),e.length===0)return".";var n=e.charCodeAt(0)===47,c=e.charCodeAt(e.length-1)===47;if(e=Ce(e,!n),e.length===0&&!n)e=".";if(e.length>0&&c)e+="/";if(n)return"/"+e;return e}function Je(e){return H(e),e.length>0&&e.charCodeAt(0)===47}function Te(){if(arguments.length===0)return".";var e;for(var n=0;n<arguments.length;++n){var c=arguments[n];if(H(c),c.length>0)if(e===void 0)e=c;else e+="/"+c}if(e===void 0)return".";return ye(e)}function Qe(e,n){if(H(e),H(n),e===n)return"";if(e=ce(e),n=ce(n),e===n)return"";var c=1;for(;c<e.length;++c)if(e.charCodeAt(c)!==47)break;var o=e.length,s=o-c,t=1;for(;t<n.length;++t)if(n.charCodeAt(t)!==47)break;var i=n.length,r=i-t,d=s<r?s:r,f=-1,a=0;for(;a<=d;++a){if(a===d){if(r>d){if(n.charCodeAt(t+a)===47)return n.slice(t+a+1);else if(a===0)return n.slice(t+a)}else if(s>d){if(e.charCodeAt(c+a)===47)f=a;else if(a===0)f=0}break}var A=e.charCodeAt(c+a),g=n.charCodeAt(t+a);if(A!==g)break;else if(A===47)f=a}var y="";for(a=c+f+1;a<=o;++a)if(a===o||e.charCodeAt(a)===47)if(y.length===0)y+="..";else y+="/..";if(y.length>0)return y+n.slice(t+f);else{if(t+=f,n.charCodeAt(t)===47)++t;return n.slice(t)}}function Ze(e){return e}function Ke(e){if(H(e),e.length===0)return".";var n=e.charCodeAt(0),c=n===47,o=-1,s=!0;for(var t=e.length-1;t>=1;--t)if(n=e.charCodeAt(t),n===47){if(!s){o=t;break}}else s=!1;if(o===-1)return c?"/":".";if(c&&o===1)return"//";return e.slice(0,o)}function me(e,n){if(n!==void 0&&typeof n!=="string")throw TypeError(\'"ext" argument must be a string\');H(e);var c=0,o=-1,s=!0,t;if(n!==void 0&&n.length>0&&n.length<=e.length){if(n.length===e.length&&n===e)return"";var i=n.length-1,r=-1;for(t=e.length-1;t>=0;--t){var d=e.charCodeAt(t);if(d===47){if(!s){c=t+1;break}}else{if(r===-1)s=!1,r=t+1;if(i>=0)if(d===n.charCodeAt(i)){if(--i===-1)o=t}else i=-1,o=r}}if(c===o)o=r;else if(o===-1)o=e.length;return e.slice(c,o)}else{for(t=e.length-1;t>=0;--t)if(e.charCodeAt(t)===47){if(!s){c=t+1;break}}else if(o===-1)s=!1,o=t+1;if(o===-1)return"";return e.slice(c,o)}}function je(e){H(e);var n=-1,c=0,o=-1,s=!0,t=0;for(var i=e.length-1;i>=0;--i){var r=e.charCodeAt(i);if(r===47){if(!s){c=i+1;break}continue}if(o===-1)s=!1,o=i+1;if(r===46){if(n===-1)n=i;else if(t!==1)t=1}else if(n!==-1)t=-1}if(n===-1||o===-1||t===0||t===1&&n===o-1&&n===c+1)return"";return e.slice(n,o)}function Fe(e){if(e===null||typeof e!=="object")throw TypeError(\'The "pathObject" argument must be of type Object. Received type \'+typeof e);return Ye("/",e)}function ve(e){H(e);var n={root:"",dir:"",base:"",ext:"",name:""};if(e.length===0)return n;var c=e.charCodeAt(0),o=c===47,s;if(o)n.root="/",s=1;else s=0;var t=-1,i=0,r=-1,d=!0,f=e.length-1,a=0;for(;f>=s;--f){if(c=e.charCodeAt(f),c===47){if(!d){i=f+1;break}continue}if(r===-1)d=!1,r=f+1;if(c===46){if(t===-1)t=f;else if(a!==1)a=1}else if(t!==-1)a=-1}if(t===-1||r===-1||a===0||a===1&&t===r-1&&t===i+1){if(r!==-1)if(i===0&&o)n.base=n.name=e.slice(1,r);else n.base=n.name=e.slice(i,r)}else{if(i===0&&o)n.name=e.slice(1,t),n.base=e.slice(1,r);else n.name=e.slice(i,t),n.base=e.slice(i,r);n.ext=e.slice(t,r)}if(i>0)n.dir=e.slice(0,i-1);else if(o)n.dir="/";return n}var pe="/",en=":",jn=((e)=>(e.posix=e,e))({resolve:ce,normalize:ye,isAbsolute:Je,join:Te,relative:Qe,_makeLong:Ze,dirname:Ke,basename:me,extname:je,format:Fe,parse:ve,sep:pe,delimiter:en,win32:null,posix:null});var G=(e,n,c,o)=>{e.push({code:c,message:o,severity:n})};var nn=(e)=>e.length===0?void 0:e.reduce((n,c)=>n+c,0)/e.length,m=(e)=>e.length===0?void 0:Math.max(...e);var u=(e,n)=>{let c=e[n];return typeof c==="number"&&Number.isFinite(c)?c:void 0},K=(e,n)=>{let c=e[n];return typeof c==="boolean"?c:void 0},D=(e,n)=>{let c=e[n];return typeof c==="string"?c:void 0},oe=(e)=>String(e.id??D(e,"ssrc")??u(e,"ssrc")??D(e,"trackIdentifier")??D(e,"mid")??"unknown"),Se=(e)=>e===void 0?void 0:e*1000;var cn=(e)=>{let n={};for(let[c,o]of Object.entries(e))if(o===null||typeof o==="boolean"||typeof o==="number"||typeof o==="string")n[c]=o;return n};var Me=(e={})=>{let n=e.stats??[],c=[],o=n.filter((l)=>l.type==="inbound-rtp"&&D(l,"kind")!=="video"),s=n.filter((l)=>l.type==="outbound-rtp"&&D(l,"kind")!=="video"),t=n.filter((l)=>l.type==="candidate-pair"),i=n.filter((l)=>(l.type==="track"||l.type==="media-source")&&D(l,"kind")==="audio"),r=t.filter((l)=>K(l,"selected")===!0||K(l,"nominated")===!0||D(l,"state")==="succeeded").length,d=i.filter((l)=>D(l,"readyState")!=="ended"&&D(l,"trackState")!=="ended"&&K(l,"ended")!==!0).length,f=i.filter((l)=>D(l,"readyState")==="ended"||D(l,"trackState")==="ended"||K(l,"ended")===!0).length,a=o.reduce((l,M)=>l+(u(M,"packetsReceived")??0),0),A=s.reduce((l,M)=>l+(u(M,"packetsSent")??0),0),g=[...o,...s].reduce((l,M)=>l+Math.max(0,u(M,"packetsLost")??0),0),y=a+g,C=y===0?0:g/y,I=o.reduce((l,M)=>l+(u(M,"bytesReceived")??0),0),L=s.reduce((l,M)=>l+(u(M,"bytesSent")??0),0),R=m(t.map((l)=>Se(u(l,"currentRoundTripTime")??u(l,"roundTripTime"))).filter((l)=>l!==void 0)),w=m([...o,...s].map((l)=>Se(u(l,"jitter"))).filter((l)=>l!==void 0)),x=m(o.map((l)=>{let M=u(l,"jitterBufferDelay"),b=u(l,"jitterBufferEmittedCount");return M!==void 0&&b!==void 0&&b>0?M/b*1000:void 0}).filter((l)=>l!==void 0)),U=i.map((l)=>u(l,"audioLevel")).filter((l)=>l!==void 0);if(e.requireConnectedCandidatePair&&t.length>0&&r===0)G(c,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(e.requireLiveAudioTrack&&d===0)G(c,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(e.maxPacketLossRatio!==void 0&&C>e.maxPacketLossRatio)G(c,"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)G(c,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(R)}ms above ${String(e.maxRoundTripTimeMs)}ms.`);if(e.maxJitterMs!==void 0&&w!==void 0&&w>e.maxJitterMs)G(c,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(w)}ms above ${String(e.maxJitterMs)}ms.`);return{activeCandidatePairs:r,audioLevelAverage:nn(U),bytesReceived:I,bytesSent:L,checkedAt:Date.now(),endedAudioTracks:f,inboundPackets:a,issues:c,jitterBufferDelayMs:x,jitterMs:w,liveAudioTracks:d,outboundPackets:A,packetLossRatio:C,packetsLost:g,roundTripTimeMs:R,status:c.some((l)=>l.severity==="error")?"fail":c.length>0?"warn":"pass",totalStats:n.length}},Ie=async(e)=>{return[...(await e.peerConnection.getStats(e.selector??null)).values()].map(cn)};var Ve=(e={})=>{let n=e.stats??[],c=e.previousStats??[],o=[],s=new Map(c.map((g)=>[oe(g),g])),i=n.filter((g)=>(g.type==="inbound-rtp"||g.type==="outbound-rtp")&&D(g,"kind")!=="video"&&D(g,"mediaType")!=="video").map((g)=>{let y=g.type==="outbound-rtp"?"outbound":"inbound",C=y==="outbound"?"packetsSent":"packetsReceived",I=y==="outbound"?"bytesSent":"bytesReceived",L=s.get(oe(g)),R=u(g,C),w=L?u(L,C):void 0,x=u(g,I),U=L?u(L,I):void 0,l=g.timestamp!==void 0&&L?.timestamp!==void 0?g.timestamp-L.timestamp:void 0;return{bytesDelta:x!==void 0&&U!==void 0?x-U:void 0,currentPackets:R,direction:y,id:oe(g),packetDelta:R!==void 0&&w!==void 0?R-w:void 0,previousPackets:w,timeDeltaMs:l}}),r=i.filter((g)=>g.direction==="inbound"),d=i.filter((g)=>g.direction==="outbound"),f=m(i.map((g)=>g.timeDeltaMs).filter((g)=>g!==void 0)),a=r.filter((g)=>e.maxInboundPacketStallMs!==void 0&&g.timeDeltaMs!==void 0&&g.timeDeltaMs>=e.maxInboundPacketStallMs&&g.packetDelta!==void 0&&g.packetDelta<=0).length,A=d.filter((g)=>e.maxOutboundPacketStallMs!==void 0&&g.timeDeltaMs!==void 0&&g.timeDeltaMs>=e.maxOutboundPacketStallMs&&g.packetDelta!==void 0&&g.packetDelta<=0).length;if(e.requireInboundAudio&&r.length===0)G(o,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(e.requireOutboundAudio&&d.length===0)G(o,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(e.maxGapMs!==void 0&&f!==void 0&&f>e.maxGapMs)G(o,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(f)}ms above ${String(e.maxGapMs)}ms.`);if(a>0)G(o,"error","media.webrtc_inbound_stalled",`${String(a)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(A>0)G(o,"error","media.webrtc_outbound_stalled",`${String(A)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:r.length,issues:o,maxObservedGapMs:f,outboundAudioStreams:d.length,stalledInboundStreams:a,stalledOutboundStreams:A,status:o.some((g)=>g.severity==="error")?"fail":o.length>0?"warn":"pass",streams:i,totalStats:n.length}};var on="/api/voice/browser-media",tn=5000,sn=async(e)=>e.peerConnection??await e.getPeerConnection?.()??null,rn=async(e,n)=>{let c=n.fetch??globalThis.fetch;if(!c)return;await c(n.path??on,{body:JSON.stringify(e),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},Le=(e)=>{let n=null,c=[],o=async()=>{let i=await sn(e);if(!i)return;let r=await Ie({peerConnection:i}),d=Me({...e,stats:r}),f=e.continuity===!1?void 0:Ve({...e.continuity,previousStats:c,stats:r}),a={at:Date.now(),continuity:f,report:d,scenarioId:e.getScenarioId?.()??null,sessionId:e.getSessionId?.()??null};return c=r,e.onReport?.(a),await rn(a,e),a},s=()=>{o().catch((i)=>{e.onError?.(i)})},t=()=>{if(n)clearInterval(n),n=null};return{close:t,reportOnce:o,start:()=>{if(n)return;s(),n=setInterval(s,e.intervalMs??tn)},stop:t}};var W=()=>{},ln=()=>W,an={callControl:W,close:W,endTurn:W,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",send:W,sendAudio:W,simulateDisconnect:W,start:()=>{},subscribe:ln},dn=()=>crypto.randomUUID(),gn=(e,n,c)=>{let{hostname:o,port:s,protocol:t}=window.location,i=t==="https:"?"wss:":"ws:",r=s?`:${s}`:"",d=new URL(`${i}//${o}${r}${e}`);if(d.searchParams.set("sessionId",n),c)d.searchParams.set("scenarioId",c);return d.toString()},fn=(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}},An=(e)=>{if(typeof e.data!=="string")return null;try{let n=JSON.parse(e.data);return fn(n)?n:null}catch{return null}},be=(e,n={})=>{if(typeof window>"u")return an;let c=new Set,o=n.reconnect!==!1,s=n.maxReconnectAttempts??10,t=n.pingInterval??30000,i={isConnected:!1,pendingMessages:[],scenarioId:n.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:n.sessionId??dn(),ws:null},r=(l)=>{c.forEach((M)=>M(l))},d=()=>{if(i.pingInterval)clearInterval(i.pingInterval),i.pingInterval=null;if(i.reconnectTimeout)clearTimeout(i.reconnectTimeout),i.reconnectTimeout=null},f=()=>{if(i.ws?.readyState!==1)return;while(i.pendingMessages.length>0){let l=i.pendingMessages.shift();if(l!==void 0)i.ws.send(l)}},a=()=>{let l=Date.now()+500;i.reconnectAttempts+=1,r({reconnect:{attempts:i.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:s,nextAttemptAt:l,status:"reconnecting"},type:"connection"}),i.reconnectTimeout=setTimeout(()=>{if(i.reconnectAttempts>s){r({reconnect:{attempts:i.reconnectAttempts,maxAttempts:s,status:"exhausted"},type:"connection"});return}A()},500)},A=()=>{let l=new WebSocket(gn(e,i.sessionId,i.scenarioId));l.binaryType="arraybuffer",l.onopen=()=>{let M=i.reconnectAttempts>0;if(i.isConnected=!0,f(),M)r({reconnect:{attempts:i.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:s,status:"resumed"},type:"connection"}),i.reconnectAttempts=0;c.forEach((b)=>b({scenarioId:i.scenarioId??void 0,sessionId:i.sessionId,status:"active",type:"session"})),i.pingInterval=setInterval(()=>{if(l.readyState===1)l.send(JSON.stringify({type:"ping"}))},t)},l.onmessage=(M)=>{let b=An(M);if(!b)return;if(b.type==="session")i.sessionId=b.sessionId,i.scenarioId=b.scenarioId??i.scenarioId;c.forEach((z)=>z(b))},l.onclose=(M)=>{if(i.isConnected=!1,d(),o&&M.code!==1000&&i.reconnectAttempts<s)a();else if(o&&M.code!==1000)r({reconnect:{attempts:i.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:s,status:"exhausted"},type:"connection"})},i.ws=l},g=(l)=>{if(i.ws?.readyState===1){i.ws.send(l);return}i.pendingMessages.push(l)},y=(l)=>{g(JSON.stringify(l))},C=(l={})=>{if(l.sessionId)i.sessionId=l.sessionId;if(l.scenarioId)i.scenarioId=l.scenarioId;y({type:"start",sessionId:i.sessionId,scenarioId:i.scenarioId??void 0})},I=(l)=>{g(l)},L=()=>{y({type:"end_turn"})},R=(l)=>{y({...l,type:"call_control"})},w=()=>{if(d(),i.ws)i.ws.close(1000),i.ws=null;i.isConnected=!1,c.clear()},x=()=>{if(i.ws?.readyState===1)i.ws.close(4000,"absolutejs-voice-reconnect-proof")},U=(l)=>{return c.add(l),()=>{c.delete(l)}};return A(),{callControl:R,close:w,endTurn:L,getReadyState:()=>i.ws?.readyState??3,getScenarioId:()=>i.scenarioId??"",getSessionId:()=>i.sessionId,send:y,sendAudio:I,simulateDisconnect:x,start:C,subscribe:U}};var hn=()=>({attempts:0,maxAttempts:0,status:"idle"}),Cn=()=>({assistantAudio:[],assistantTexts:[],call:null,error:null,isConnected:!1,sessionMetadata:null,scenarioId:null,partial:"",reconnect:hn(),sessionId:null,status:"idle",turns:[]}),we=()=>{let e=Cn(),n=new Set,c=()=>{n.forEach((s)=>s())};return{dispatch:(s)=>{switch(s.type){case"audio":e={...e,assistantAudio:[...e.assistantAudio,{chunk:s.chunk,format:s.format,receivedAt:s.receivedAt,turnId:s.turnId}]};break;case"assistant":e={...e,assistantTexts:[...e.assistantTexts,s.text]};break;case"complete":e={...e,sessionId:s.sessionId,status:"completed"};break;case"call_lifecycle":e={...e,call:{...e.call,disposition:s.event.type==="end"?s.event.disposition:e.call?.disposition,endedAt:s.event.type==="end"?s.event.at:e.call?.endedAt,events:[...e.call?.events??[],s.event],lastEventAt:s.event.at,startedAt:e.call?.startedAt??s.event.at},sessionId:s.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:s.reconnect};break;case"disconnected":e={...e,isConnected:!1};break;case"error":e={...e,error:s.message};break;case"final":e={...e,partial:s.transcript.text,turns:e.turns.map((t)=>t)};break;case"partial":e={...e,partial:s.transcript.text};break;case"replay":e={...e,assistantTexts:[...s.assistantTexts],call:s.call??null,error:null,isConnected:s.status==="active",partial:s.partial,reconnect:e.reconnect.status==="reconnecting"?{...e.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:e.reconnect,scenarioId:s.scenarioId??e.scenarioId,sessionId:s.sessionId,sessionMetadata:s.sessionMetadata??e.sessionMetadata,status:s.status,turns:[...s.turns]};break;case"session":e={...e,error:null,scenarioId:s.scenarioId??e.scenarioId,isConnected:s.status==="active",sessionId:s.sessionId,sessionMetadata:s.sessionMetadata??e.sessionMetadata,status:s.status};break;case"turn":e={...e,partial:"",turns:[...e.turns,s.turn]};break}c()},getServerSnapshot:()=>e,getSnapshot:()=>e,subscribe:(s)=>{return n.add(s),()=>{n.delete(s)}}}};var Re=(e,n={})=>{let c=be(e,n),o=we(),s=n.browserMedia&&typeof window<"u"?Le({...n.browserMedia,getScenarioId:()=>n.browserMedia?n.browserMedia.getScenarioId?.()??c.getScenarioId():c.getScenarioId(),getSessionId:()=>n.browserMedia?n.browserMedia.getSessionId?.()??c.getSessionId():c.getSessionId()}):null,t=new Set,i=(a)=>Promise.resolve().then(()=>{if(!a?.sessionId&&!a?.scenarioId)return;c.start(a),s?.start()}),r=()=>{t.forEach((a)=>a())},d=()=>{if(!n.reconnectReportPath||typeof fetch>"u")return;let a=o.getSnapshot(),A=JSON.stringify({at:Date.now(),reconnect:a.reconnect,scenarioId:a.scenarioId,sessionId:c.getSessionId(),turnIds:a.turns.map((g)=>g.id)});fetch(n.reconnectReportPath,{body:A,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},f=c.subscribe((a)=>{let A=he(a);if(A){if(o.dispatch(A),a.type==="connection")d();r()}});return{callControl(a){c.callControl(a)},close(){f(),s?.close(),c.close(),o.dispatch({type:"disconnected"}),r()},endTurn(){c.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:i,get partial(){return o.getSnapshot().partial},get reconnect(){return o.getSnapshot().reconnect},get sessionId(){return c.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(a){c.sendAudio(a)},simulateDisconnect(){c.simulateDisconnect()},subscribe(a){return t.add(a),()=>{t.delete(a)}}}};var ue=(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 yn={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}},Tn={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 _e=(e)=>{let n=e?.profile??"fast",c=e?.qualityProfile??"general",o=yn[n],s=Tn[c];return{profile:n,qualityProfile:c,silenceMs:e?.silenceMs??s.silenceMs??o.silenceMs,speechThreshold:e?.speechThreshold??s.speechThreshold??o.speechThreshold,transcriptStabilityMs:e?.transcriptStabilityMs??s.transcriptStabilityMs??o.transcriptStabilityMs}};var Sn={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"}}},Ee=(e="default")=>{let n=Sn[e];return{audioConditioning:ue(n.audioConditioning),capture:{channelCount:n.capture?.channelCount??1,sampleRateHz:n.capture?.sampleRateHz??16000},connection:{...n.connection},name:e,sttLifecycle:n.sttLifecycle??"continuous",turnDetection:_e(n.turnDetection)}};var Mn=(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]}),j=(e,n={})=>{let c=Ee(n.preset),o=Re(e,{...c.connection,...n.connection}),s=null,t=Mn(o),i=new Set,r=()=>{for(let C of i)C()},d=()=>{if(t={...t,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&&t.status==="completed"&&t.isRecording)s?.stop(),s=null,t={...t,isRecording:!1};r()},f=o.subscribe(d);d();let a=()=>{if(s)return s;return s=Ae({channelCount:n.capture?.channelCount??c.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??c.capture.sampleRateHz}),s},A=()=>{s?.stop(),s=null,t={...t,isRecording:!1},r()},g=async()=>{if(t.isRecording)return;try{t={...t,recordingError:null},r(),await a().start(),t={...t,isRecording:!0},r()}catch(C){throw s=null,t={...t,isRecording:!1,recordingError:C instanceof Error?C.message:String(C)},r(),C}};return{bindHTMX(C){return fe(o,C)},callControl:(C)=>o.callControl(C),close:()=>{f(),A(),o.close()},endTurn:()=>o.endTurn(),get error(){return t.error},getServerSnapshot:()=>t,getSnapshot:()=>t,get isConnected(){return t.isConnected},get isRecording(){return t.isRecording},get partial(){return t.partial},get recordingError(){return t.recordingError},get reconnect(){return t.reconnect},sendAudio:(C)=>o.sendAudio(C),simulateDisconnect:()=>o.simulateDisconnect(),get sessionId(){return t.sessionId},get sessionMetadata(){return t.sessionMetadata},get scenarioId(){return t.scenarioId},startRecording:g,get status(){return t.status},stopRecording:A,subscribe:(C)=>{return i.add(C),()=>{i.delete(C)}},toggleRecording:async()=>{if(t.isRecording){A();return}await g()},get turns(){return t.turns},get assistantTexts(){return t.assistantTexts},get assistantAudio(){return t.assistantAudio},get call(){return t.call}}};var In=()=>({activeSourceCount:0,error:null,isActive:!1,isPlaying:!1,lastInterruptLatencyMs:void 0,lastPlaybackStopLatencyMs:void 0,processedChunkCount:0,queuedChunkCount:0}),Vn=()=>{if(typeof window>"u")return typeof AudioContext>"u"?void 0:AudioContext;return window.AudioContext??window.webkitAudioContext},Ln=(e,n)=>{let c=n.format;if(c.container!=="raw"||c.encoding!=="pcm_s16le")throw Error(`Unsupported assistant audio format: ${c.container}/${c.encoding}`);let o=n.chunk,s=Math.max(1,c.channels),t=Math.floor(o.byteLength/2),i=Math.max(1,Math.floor(t/s)),r=e.createBuffer(s,i,c.sampleRateHz),d=new DataView(o.buffer,o.byteOffset,o.byteLength);for(let f=0;f<s;f+=1){let a=r.getChannelData(f);for(let A=0;A<i;A+=1){let y=(A*s+f)*2;if(y+1>=o.byteLength){a[A]=0;continue}a[A]=d.getInt16(y,!0)/32768}}return r},F=(e,n={})=>{let c=new Set,o=new Set,s=(n.lookaheadMs??15)/1000,t=In(),i=null,r=null,d=0,f=Promise.resolve(),a=null,A=null,g=null,y=null,C=()=>{for(let h of c)h()},I=(h)=>{t={...t,...h},C()},L=()=>{if(t.error!==null)I({error:null})},R=()=>{if(y!==null)clearTimeout(y),y=null},w=(h)=>{R(),a=null,I({activeSourceCount:o.size,isPlaying:!1,lastInterruptLatencyMs:h,lastPlaybackStopLatencyMs:t.lastPlaybackStopLatencyMs??h}),g?.(),g=null,A=null},x=(h)=>{if(!h)return 0;return Math.max(0,((h.baseLatency??0)+(h.outputLatency??0))*1000)},U=(h)=>{if(!r)return;let T=1;if(r.gain.setValueAtTime){r.gain.setValueAtTime(T,h?.currentTime??0);return}r.gain.value=T},l=(h)=>{if(!r)return;let T=0;if(r.gain.setValueAtTime){r.gain.setValueAtTime(T,h?.currentTime??0);return}r.gain.value=T},M=()=>{if(a===null||o.size>0)return;w(Date.now()-a)},b=async()=>{if(i)return i;if(n.createAudioContext)i=n.createAudioContext();else{let h=Vn();if(!h)throw Error("Assistant audio playback requires AudioContext support.");i=new h}if(i.createGain)r=i.createGain(),r.connect?.(i.destination);return d=i.currentTime,i},z=async(h)=>{let T=await b(),E=Ln(T,h),V=T.createBufferSource();V.buffer=E,V.connect(r??T.destination),V.onended=()=>{o.delete(V),V.disconnect?.(),I({activeSourceCount:o.size,isPlaying:o.size>0&&t.isActive}),M()};let J=Math.max(T.currentTime+s,d);d=J+E.duration,o.add(V),I({activeSourceCount:o.size,isPlaying:!0}),V.start(J)},_=(h)=>{for(let T of[...o])T.stop?.();if(d=i?i.currentTime:0,h?.forceClear){for(let T of o)T.disconnect?.();o.clear(),M()}},$=async()=>{if(!t.isActive)return;let h=e.assistantAudio.slice(t.processedChunkCount);if(h.length===0)return;try{L();for(let T of h)await z(T);I({processedChunkCount:e.assistantAudio.length,queuedChunkCount:t.queuedChunkCount+h.length})}catch(T){I({error:T instanceof Error?T.message:String(T)})}},P=()=>{return f=f.then(()=>$(),()=>$()),f},k=e.subscribe(()=>{if(n.autoStart&&!t.isActive&&e.assistantAudio.length>0){N.start();return}if(t.isActive)P()}),N={close:async()=>{if(k(),_({forceClear:!0}),R(),g?.(),g=null,A=null,a=null,i&&i.state!=="closed")await i.close();i=null,r?.disconnect?.(),r=null,d=0,I({activeSourceCount:0,isActive:!1,isPlaying:!1})},get activeSourceCount(){return t.activeSourceCount},get error(){return t.error},getSnapshot:()=>t,get isActive(){return t.isActive},get isPlaying(){return t.isPlaying},interrupt:async()=>{let h=Date.now(),T=await b();a=h,l(T);let E=Date.now()-h+x(T);if(I({isActive:!1,isPlaying:o.size>0,lastPlaybackStopLatencyMs:E}),o.size===0){w(E);return}if(!A)A=new Promise((V)=>{g=V});R(),y=setTimeout(()=>{for(let V of o)V.disconnect?.();o.clear(),w(Date.now()-h)},250),_(),await A},get lastInterruptLatencyMs(){return t.lastInterruptLatencyMs},get lastPlaybackStopLatencyMs(){return t.lastPlaybackStopLatencyMs},pause:async()=>{if(!i){I({activeSourceCount:0,isActive:!1,isPlaying:!1});return}await i.suspend(),I({activeSourceCount:o.size,isActive:!1,isPlaying:!1})},get processedChunkCount(){return t.processedChunkCount},get queuedChunkCount(){return t.queuedChunkCount},start:async()=>{try{L();let h=await b();if(U(h),h.state==="suspended")await h.resume();I({activeSourceCount:o.size,isActive:!0,isPlaying:h.state==="running"}),await P()}catch(h){throw I({error:h instanceof Error?h.message:String(h),isActive:!1,isPlaying:!1}),h}},subscribe:(h)=>{return c.add(h),()=>{c.delete(h)}}};return N};var bn=()=>`barge-in:${Date.now()}:${crypto.randomUUID?.()??Math.random().toString(36).slice(2)}`,wn=(e,n)=>{let c=e.filter((i)=>i.status==="stopped"),o=c.map((i)=>i.latencyMs).filter((i)=>typeof i==="number"),s=c.filter((i)=>typeof i.latencyMs==="number"&&i.latencyMs>n).length,t=c.length-s;return{averageLatencyMs:o.length>0?Math.round(o.reduce((i,r)=>i+r,0)/o.length):void 0,events:[...e],failed:s,lastEvent:e.at(-1),passed:t,status:e.length===0?"empty":s>0?"fail":c.length===0?"warn":"pass",thresholdMs:n,total:c.length}},Pe=(e={})=>{let n=new Set,c=e.thresholdMs??250,o=e.fetch??globalThis.fetch,s=[],t=()=>{for(let d of n)d()},i=(d)=>{if(!e.path||typeof o!=="function")return;o(e.path,{body:JSON.stringify(d),headers:{"Content-Type":"application/json"},method:"POST"}).catch(()=>{})},r=(d,f)=>{let a={at:Date.now(),id:bn(),latencyMs:f.latencyMs,playbackStopLatencyMs:f.playbackStopLatencyMs,reason:f.reason,sessionId:f.sessionId,status:d,thresholdMs:c};return s.push(a),i(a),t(),a};return{getSnapshot:()=>wn(s,c),recordRequested:(d)=>r("requested",d),recordSkipped:(d)=>r("skipped",d),recordStopped:(d)=>r("stopped",d),subscribe:(d)=>{return n.add(d),()=>{n.delete(d)}}}};var Rn=0.08,un=(e,n={})=>(n.enabled??!0)&&e>=(n.interruptThreshold??Rn),te=(e,n,c={})=>{let o=e.partial,s=(i)=>{if(!n.isPlaying||c.enabled===!1){c.monitor?.recordSkipped({reason:i,sessionId:e.sessionId});return}c.monitor?.recordRequested({reason:i,sessionId:e.sessionId}),n.interrupt().then(()=>{c.monitor?.recordStopped({latencyMs:n.lastInterruptLatencyMs,playbackStopLatencyMs:n.lastPlaybackStopLatencyMs,reason:i,sessionId:e.sessionId})})},t=e.subscribe(()=>{if(c.interruptOnPartial===!1){o=e.partial;return}if(!o&&e.partial)s("partial-transcript");o=e.partial});return{close:()=>{t()},handleLevel:(i)=>{if(un(i,c))s("input-level")},sendAudio:(i)=>{s("manual-audio"),e.sendAudio(i)}}};var ae=48,_n=320,En=88,Pn="Guided test",Dn="General recording",On="Pick a scenario to begin the demo.",xn="I can walk you through a short guided voice test.",Un="I can capture one freeform recording and confirm that it landed.",Nn="Choose a scenario to begin. Guided test asks follow-up prompts. General recording just captures what you say.",Hn="Click Start general recording to capture one freeform answer.",xe="Speak freely. When you pause, the recording will be captured.",re="Recording saved. Start again if you want another capture.",Ue="Guided test complete. Review the saved summary below.",Ne="All prompts are covered. You can stop the microphone or keep speaking for extra detail.",Gn="Ready. Start guided test or general recording to begin.",Bn="Live. Answer the prompt, then click Stop microphone when finished.",De=["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."],He=(e,n,c)=>Math.min(c,Math.max(n,e)),X=(e)=>e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll(\'"\',""").replaceAll("\'","'"),ie=(e,n)=>{let c=e[n];if(typeof c==="string"&&c.trim())return c;return null},le=(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,c=ie(n,"message")??ie(n,"reason")??ie(n,"description");if(c)return c;if("error"in n)return le(n.error);if("cause"in n)return le(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 c=Math.max(0,e.nextAttemptAt-Date.now());n.push(`retry in ${Math.ceil(c/100)/10}s`)}return n.join(" \xB7 ")},v=(e=ae)=>Array.from({length:e},()=>0),Oe=(e,n,c=ae)=>{let o=e.slice(-(c-1));o.push(He(n,0,1));while(o.length<c)o.unshift(0);return o},$n=(e,n=_n,c=En)=>{let o=e.length>1?e:v(ae),s=n/(o.length-1),t=c/2,i=c*0.34;if(Math.max(...o,0)<=0.015)return`M 0 ${t} L ${n} ${t}`;let d=o.map((a,A)=>{let g=A*0.76,y=Math.sin(g)*0.78+Math.sin(g*0.41)*0.22,C=a*i,I=s*A,L=He(t+y*C,8,c-8);return{x:I,y:L}});if(d.length===0)return`M 0 ${t} L ${n} ${t}`;let f=`M ${d[0]?.x??0} ${d[0]?.y??t}`;for(let a=1;a<d.length;a+=1){let A=d[a-1],g=d[a];if(!A||!g)continue;let y=(A.x+g.x)/2;f+=` Q ${y} ${A.y} ${g.x} ${g.y}`}return f},kn=(e)=>{if(!e)return De;try{let n=JSON.parse(e);if(Array.isArray(n)){let c=n.filter((o)=>typeof o==="string").map((o)=>o.trim()).filter(Boolean);if(c.length>0)return c}}catch{}return De},se=(e)=>{if(!e)return;let n=Number(e);return Number.isFinite(n)?n:void 0},qn=(e,n,c)=>{if(!n)return null;let o=document.querySelector(n);return o instanceof c?o:null},O=(e,n,c,o)=>{let s=n?document.querySelector(n):null;if(s instanceof c)return s;let t=e.querySelector(`#${o}`);if(t instanceof c)return t;throw Error(`Voice HTMX bootstrap could not find the required element "${o}".`)},Xn=(e)=>{if(!e.mode)return On;if(!e.hasStarted)return e.mode==="guided"?xn:Un;if(e.status==="completed")return e.mode==="guided"?Ue:re;if(e.mode==="general")return xe;return e.guidedPrompts[e.turnCount]??Ne},zn=(e)=>{if(!e.mode)return Nn;if(e.status==="completed")return e.mode==="guided"?Ue:re;if(!e.hasStarted)return e.mode==="guided"?`Click Start guided test to begin. First prompt: ${e.guidedPrompts[0]??"Answer the first prompt."}`:Hn;if(e.mode==="general")return e.turnCount===0?xe:re;return e.guidedPrompts[e.turnCount]??Ne},Yn=(e)=>{let n=e.dataset.voiceGuidedPath,c=e.dataset.voiceGeneralPath;if(!n||!c)throw Error("Voice HTMX bootstrap requires data-voice-guided-path and data-voice-general-path.");let o=kn(e.dataset.voiceGuidedPrompts),s=e.dataset.voiceGuidedLabel??Pn,t=e.dataset.voiceGeneralLabel??Dn,i=e.dataset.voiceReconnectReportPath,r=e.dataset.voiceBargeInPath,d=r?Pe({path:r,thresholdMs:se(e.dataset.voiceBargeInThresholdMs)}):null,f=se(e.dataset.voiceBargeInRecentWindowMs)??4000,a=se(e.dataset.voiceBargeInSpeechThreshold)??0.04,A=O(document,e.dataset.voiceSync,HTMLElement,"voice-htmx-sync"),g=O(e,e.dataset.voiceConnection,HTMLElement,"metric-connection"),y=O(e,e.dataset.voiceError,HTMLElement,"status-error"),C=O(e,e.dataset.voiceMicrophone,HTMLElement,"status-mic"),I=O(e,e.dataset.voicePrompt,HTMLElement,"status-prompt"),L=qn(e,e.dataset.voiceReconnect,HTMLElement),R=O(e,e.dataset.voiceChat,HTMLElement,"chat-list"),w=O(e,e.dataset.voiceStartGuided,HTMLButtonElement,"start-guided"),x=O(e,e.dataset.voiceStartGeneral,HTMLButtonElement,"start-general"),U=O(e,e.dataset.voiceStop,HTMLButtonElement,"stop-mic"),l=O(e,e.dataset.voiceMonitor,HTMLElement,"voice-monitor"),M=O(e,e.dataset.voiceMonitorCopy,HTMLElement,"voice-monitor-copy"),b=O(e,e.dataset.voiceWaveGlow,SVGPathElement,"voice-wave-glow"),z=O(e,e.dataset.voiceWavePath,SVGPathElement,"voice-wave-path"),_=null,$={general:!1,guided:!1},P=!1,k=null,N=v(),h=null,T=null,E=j(n,{capture:{onAudio:(S,B)=>{if(h){h.sendAudio(S);return}B(S)},onLevel:(S)=>{h?.handleLevel(S),N=Oe(N,S),p()}},connection:{reconnectReportPath:i},preset:"guided-intake"}),V=j(c,{capture:{onAudio:(S,B)=>{if(T){T.sendAudio(S);return}B(S)},onLevel:(S)=>{T?.handleLevel(S),N=Oe(N,S),p()}},connection:{reconnectReportPath:i},preset:"dictation"}),J=E.bindHTMX({element:A}),Ge=V.bindHTMX({element:A}),Q=F(E),Z=F(V);h=te(E,Q,{interruptThreshold:a,monitor:d??void 0}),T=te(V,Z,{interruptThreshold:a,monitor:d??void 0});let Y=()=>_==="general"?V:E,Qn=()=>_==="general"?Z:Q,p=()=>{let S=$n(N);b.setAttribute("d",S),z.setAttribute("d",S),M.innerHTML=`<span class="voice-live-dot"></span>${P?"Microphone live":"Microphone idle"}`,M.classList.toggle("is-live",P),l.classList.toggle("is-live",P)},q=()=>{let S=Y(),B=(_?$[_]:!1)||S.turns.length>0,ge=S.status;if(g.textContent=S.isConnected?"Connected":"Waiting",y.textContent=k||S.error||"None",L)L.textContent=Wn(S.reconnect);C.textContent=P?Bn:Gn,I.textContent=zn({guidedPrompts:o,hasStarted:B,mode:_,status:ge,turnCount:S.turns.length}),w.hidden=P,x.hidden=P,U.hidden=!P,R.innerHTML=`<article class="voice-chat-message assistant">\n <div class="voice-chat-role">${X(_==="general"?t:_==="guided"?s:"Voice demo")}</div>\n <p class="voice-turn-text">${X(Xn({generalLabel:t,guidedLabel:s,guidedPrompts:o,hasStarted:B,mode:_,status:ge,turnCount:S.turns.length}))}</p>\n</article>${S.turns.map((ee)=>`<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(ee.text)}</p>\n </article>\n ${ee.assistantText?`<article class="voice-chat-message assistant">\n <div class="voice-chat-role">${X(_==="general"?t:_==="guided"?s:"Guide")}</div>\n <p class="voice-turn-text">${X(ee.assistantText)}</p>\n </article>`:""}\n</div>`).join("")}${S.partial?`<article class="voice-chat-message user pending">\n <div class="voice-chat-role">Speaking</div>\n <p class="voice-turn-text">${X(S.partial)}</p>\n</article>`:""}`,p()},Be=()=>{Y().stopRecording(),P=!1,k=null,N=v(),q()},de=async(S)=>{_=S,$={...$,[S]:!0};try{await Y().startRecording(),k=null,P=!0,q()}catch(B){Y().stopRecording(),P=!1,N=v(),k=le(B),q()}};E.subscribe(()=>{if(E.assistantAudio.length>0)Q.start().catch(()=>{});q()}),V.subscribe(()=>{if(V.assistantAudio.length>0)Z.start().catch(()=>{});q()}),w.addEventListener("click",()=>{de("guided")}),x.addEventListener("click",()=>{de("general")}),U.addEventListener("click",()=>{Be()}),e.addEventListener("absolute-voice-simulate-disconnect",()=>{Y().simulateDisconnect()}),window.addEventListener("beforeunload",()=>{E.stopRecording(),V.stopRecording(),h?.close(),T?.close(),Q.close(),Z.close(),J(),Ge(),E.close(),V.close()}),q()},Jn=()=>{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)Yn(n)};Jn();export{Jn 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,935 +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
|
-
const value = frame.metadata?.[key];
|
|
12569
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
12570
|
-
};
|
|
12571
|
-
var average = (values) => values.length === 0 ? undefined : values.reduce((total, value) => total + value, 0) / values.length;
|
|
12572
|
-
var max = (values) => values.length === 0 ? undefined : Math.max(...values);
|
|
12573
|
-
var min = (values) => values.length === 0 ? undefined : Math.min(...values);
|
|
12574
|
-
var numericStat = (stat, key) => {
|
|
12575
|
-
const value = stat[key];
|
|
12576
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
12577
|
-
};
|
|
12578
|
-
var booleanStat = (stat, key) => {
|
|
12579
|
-
const value = stat[key];
|
|
12580
|
-
return typeof value === "boolean" ? value : undefined;
|
|
12581
|
-
};
|
|
12582
|
-
var stringStat = (stat, key) => {
|
|
12583
|
-
const value = stat[key];
|
|
12584
|
-
return typeof value === "string" ? value : undefined;
|
|
12585
|
-
};
|
|
12586
|
-
var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
|
|
12587
|
-
var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
|
|
12588
|
-
var DEFAULT_TELEPHONY_FORMAT = {
|
|
12589
|
-
channels: 1,
|
|
12590
|
-
container: "raw",
|
|
12591
|
-
encoding: "mulaw",
|
|
12592
|
-
sampleRateHz: 8000
|
|
12593
|
-
};
|
|
12594
|
-
var bytesToBase64 = (audio) => {
|
|
12595
|
-
const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
|
|
12596
|
-
return Buffer.from(bytes).toString("base64");
|
|
12597
|
-
};
|
|
12598
|
-
var base64ToBytes = (value) => new Uint8Array(Buffer.from(value, "base64"));
|
|
12599
|
-
var unknownRecord = (value) => value && typeof value === "object" ? value : {};
|
|
12600
|
-
var firstString2 = (records, keys) => {
|
|
12601
|
-
for (const record of records) {
|
|
12602
|
-
for (const key of keys) {
|
|
12603
|
-
const value = record[key];
|
|
12604
|
-
if (typeof value === "string" && value.length > 0) {
|
|
12605
|
-
return value;
|
|
12606
|
-
}
|
|
12607
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
12608
|
-
return String(value);
|
|
12609
|
-
}
|
|
12610
|
-
}
|
|
12611
|
-
}
|
|
12612
|
-
return;
|
|
12613
|
-
};
|
|
12614
|
-
var firstNumber = (records, keys) => {
|
|
12615
|
-
for (const record of records) {
|
|
12616
|
-
for (const key of keys) {
|
|
12617
|
-
const value = record[key];
|
|
12618
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
12619
|
-
return value;
|
|
12620
|
-
}
|
|
12621
|
-
if (typeof value === "string") {
|
|
12622
|
-
const parsed = Number(value);
|
|
12623
|
-
if (Number.isFinite(parsed)) {
|
|
12624
|
-
return parsed;
|
|
12625
|
-
}
|
|
12626
|
-
}
|
|
12627
|
-
}
|
|
12628
|
-
}
|
|
12629
|
-
return;
|
|
12630
|
-
};
|
|
12631
|
-
var telephonyDirection = (track) => {
|
|
12632
|
-
const normalized = track?.toLowerCase();
|
|
12633
|
-
if (!normalized) {
|
|
12634
|
-
return "unknown";
|
|
12635
|
-
}
|
|
12636
|
-
if (normalized.includes("inbound") || normalized.includes("caller") || normalized.includes("in")) {
|
|
12637
|
-
return "inbound";
|
|
12638
|
-
}
|
|
12639
|
-
if (normalized.includes("outbound") || normalized.includes("assistant") || normalized.includes("out")) {
|
|
12640
|
-
return "outbound";
|
|
12641
|
-
}
|
|
12642
|
-
return "unknown";
|
|
12643
|
-
};
|
|
12644
|
-
var telephonyFrameKind = (direction) => direction === "outbound" ? "assistant-audio" : "input-audio";
|
|
12645
|
-
var telephonyEventKind = (envelope) => {
|
|
12646
|
-
const raw = firstString2([envelope], ["event", "type", "eventType"]) ?? firstString2([unknownRecord(envelope.message)], ["event", "type"]);
|
|
12647
|
-
const normalized = raw?.toLowerCase().replace(/[_\s-]+/g, "-");
|
|
12648
|
-
if (!normalized) {
|
|
12649
|
-
return "unknown";
|
|
12650
|
-
}
|
|
12651
|
-
if (normalized.includes("connected")) {
|
|
12652
|
-
return "connected";
|
|
12653
|
-
}
|
|
12654
|
-
if (normalized.includes("start")) {
|
|
12655
|
-
return "start";
|
|
12656
|
-
}
|
|
12657
|
-
if (normalized.includes("media")) {
|
|
12658
|
-
return "media";
|
|
12659
|
-
}
|
|
12660
|
-
if (normalized.includes("stop") || normalized.includes("closed")) {
|
|
12661
|
-
return "stop";
|
|
12662
|
-
}
|
|
12663
|
-
if (normalized.includes("error") || normalized.includes("failed")) {
|
|
12664
|
-
return "error";
|
|
12665
|
-
}
|
|
12666
|
-
return "unknown";
|
|
12667
|
-
};
|
|
12668
|
-
var normalizeWebRTCStat = (stat) => {
|
|
12669
|
-
const sample = {};
|
|
12670
|
-
for (const [key, value] of Object.entries(stat)) {
|
|
12671
|
-
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
12672
|
-
sample[key] = value;
|
|
12673
|
-
}
|
|
12674
|
-
}
|
|
12675
|
-
return sample;
|
|
12676
|
-
};
|
|
12677
|
-
var parseTelephonyMediaFrame = (input) => {
|
|
12678
|
-
const envelope = input.envelope;
|
|
12679
|
-
const media = unknownRecord(envelope.media);
|
|
12680
|
-
const payload = firstString2([media, envelope], ["payload", "audio", "data"]) ?? firstString2([unknownRecord(envelope.message)], ["payload"]);
|
|
12681
|
-
if (!payload) {
|
|
12682
|
-
return;
|
|
12683
|
-
}
|
|
12684
|
-
const carrier = input.carrier ?? firstString2([envelope], ["provider"]) ?? "telephony";
|
|
12685
|
-
const streamId = firstString2([media, envelope], ["streamSid", "stream_id", "streamId", "streamId", "callSid", "call_id"]);
|
|
12686
|
-
const sequenceNumber = firstString2([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
|
|
12687
|
-
const track = firstString2([media, envelope], ["track", "direction"]);
|
|
12688
|
-
const direction = telephonyDirection(track);
|
|
12689
|
-
const timestamp = firstNumber([media, envelope], ["timestamp", "time", "startedAt"]);
|
|
12690
|
-
return {
|
|
12691
|
-
at: timestamp,
|
|
12692
|
-
audio: base64ToBytes(payload),
|
|
12693
|
-
format: input.format ?? DEFAULT_TELEPHONY_FORMAT,
|
|
12694
|
-
id: [
|
|
12695
|
-
carrier,
|
|
12696
|
-
streamId ?? input.sessionId ?? "stream",
|
|
12697
|
-
sequenceNumber ?? timestamp ?? Date.now()
|
|
12698
|
-
].join(":"),
|
|
12699
|
-
kind: telephonyFrameKind(direction),
|
|
12700
|
-
metadata: {
|
|
12701
|
-
carrier,
|
|
12702
|
-
direction,
|
|
12703
|
-
event: firstString2([envelope], ["event", "type"]),
|
|
12704
|
-
sequenceNumber,
|
|
12705
|
-
streamId,
|
|
12706
|
-
track
|
|
12707
|
-
},
|
|
12708
|
-
sessionId: input.sessionId ?? streamId,
|
|
12709
|
-
source: "telephony"
|
|
12710
|
-
};
|
|
12711
|
-
};
|
|
12712
|
-
var serializeTelephonyMediaFrame = (input) => {
|
|
12713
|
-
const carrier = input.carrier ?? input.frame.metadata?.carrier ?? "telephony";
|
|
12714
|
-
const streamId = input.streamId ?? (typeof input.frame.metadata?.streamId === "string" ? input.frame.metadata.streamId : input.frame.sessionId);
|
|
12715
|
-
const sequenceNumber = input.sequenceNumber ?? (typeof input.frame.metadata?.sequenceNumber === "string" || typeof input.frame.metadata?.sequenceNumber === "number" ? input.frame.metadata.sequenceNumber : undefined);
|
|
12716
|
-
const direction = input.frame.kind === "assistant-audio" ? "outbound" : "inbound";
|
|
12717
|
-
const payload = input.frame.audio ? bytesToBase64(input.frame.audio) : "";
|
|
12718
|
-
if (carrier === "twilio") {
|
|
12719
|
-
return {
|
|
12720
|
-
event: "media",
|
|
12721
|
-
sequenceNumber,
|
|
12722
|
-
streamSid: streamId,
|
|
12723
|
-
media: {
|
|
12724
|
-
payload,
|
|
12725
|
-
timestamp: input.frame.at,
|
|
12726
|
-
track: direction
|
|
12727
|
-
}
|
|
12728
|
-
};
|
|
12729
|
-
}
|
|
12730
|
-
if (carrier === "telnyx") {
|
|
12731
|
-
return {
|
|
12732
|
-
event: "media",
|
|
12733
|
-
stream_id: streamId,
|
|
12734
|
-
sequence_number: sequenceNumber,
|
|
12735
|
-
media: {
|
|
12736
|
-
payload,
|
|
12737
|
-
timestamp: input.frame.at,
|
|
12738
|
-
track: direction
|
|
12739
|
-
}
|
|
12740
|
-
};
|
|
12741
|
-
}
|
|
12742
|
-
if (carrier === "plivo") {
|
|
12743
|
-
return {
|
|
12744
|
-
event: "media",
|
|
12745
|
-
streamId,
|
|
12746
|
-
sequenceNumber,
|
|
12747
|
-
media: {
|
|
12748
|
-
payload,
|
|
12749
|
-
timestamp: input.frame.at,
|
|
12750
|
-
track: direction
|
|
12751
|
-
}
|
|
12752
|
-
};
|
|
12753
|
-
}
|
|
12754
|
-
return {
|
|
12755
|
-
event: "media",
|
|
12756
|
-
provider: carrier,
|
|
12757
|
-
sequenceNumber,
|
|
12758
|
-
streamId,
|
|
12759
|
-
media: {
|
|
12760
|
-
payload,
|
|
12761
|
-
timestamp: input.frame.at,
|
|
12762
|
-
track: direction
|
|
12763
|
-
}
|
|
12764
|
-
};
|
|
12765
|
-
};
|
|
12766
|
-
var createTelephonyMediaSerializer = (input) => {
|
|
12767
|
-
const format = input.format ?? DEFAULT_TELEPHONY_FORMAT;
|
|
12768
|
-
return {
|
|
12769
|
-
carrier: input.carrier,
|
|
12770
|
-
format,
|
|
12771
|
-
parse: (envelope) => parseTelephonyMediaFrame({
|
|
12772
|
-
carrier: input.carrier,
|
|
12773
|
-
envelope,
|
|
12774
|
-
format,
|
|
12775
|
-
sessionId: input.sessionId ?? input.streamId
|
|
12776
|
-
}),
|
|
12777
|
-
serialize: (frame) => serializeTelephonyMediaFrame({
|
|
12778
|
-
carrier: input.carrier,
|
|
12779
|
-
frame,
|
|
12780
|
-
streamId: input.streamId
|
|
12781
|
-
})
|
|
12782
|
-
};
|
|
12783
|
-
};
|
|
12784
|
-
var parseTelephonyStreamEvent = (input) => {
|
|
12785
|
-
const envelope = input.envelope;
|
|
12786
|
-
const media = unknownRecord(envelope.media);
|
|
12787
|
-
const start = unknownRecord(envelope.start);
|
|
12788
|
-
const stop = unknownRecord(envelope.stop);
|
|
12789
|
-
const errorRecord = unknownRecord(envelope.error);
|
|
12790
|
-
const kind = telephonyEventKind(envelope);
|
|
12791
|
-
const carrier = input.carrier ?? firstString2([envelope], ["provider", "carrier"]) ?? "telephony";
|
|
12792
|
-
const frame = kind === "media" ? parseTelephonyMediaFrame({
|
|
12793
|
-
carrier,
|
|
12794
|
-
envelope,
|
|
12795
|
-
format: input.format,
|
|
12796
|
-
sessionId: input.sessionId
|
|
12797
|
-
}) : undefined;
|
|
12798
|
-
const streamId = firstString2([media, start, stop, envelope], ["streamSid", "stream_id", "streamId", "callSid", "call_id"]) ?? input.sessionId;
|
|
12799
|
-
const sequenceNumber = firstString2([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
|
|
12800
|
-
const track = firstString2([media, envelope], ["track", "direction"]);
|
|
12801
|
-
return {
|
|
12802
|
-
audioBytes: frame?.audio ? frame.audio instanceof ArrayBuffer ? frame.audio.byteLength : frame.audio.byteLength : 0,
|
|
12803
|
-
at: frame?.at ?? firstNumber([media, start, stop, envelope], ["timestamp", "time", "startedAt"]),
|
|
12804
|
-
carrier,
|
|
12805
|
-
direction: telephonyDirection(track),
|
|
12806
|
-
error: firstString2([errorRecord, envelope], ["message", "error", "reason"]),
|
|
12807
|
-
kind,
|
|
12808
|
-
sequenceNumber,
|
|
12809
|
-
streamId
|
|
12810
|
-
};
|
|
12811
|
-
};
|
|
12812
|
-
var buildMediaTelephonyStreamLifecycleReport = (input = {}) => {
|
|
12813
|
-
const envelopes = input.envelopes ?? [];
|
|
12814
|
-
const events = envelopes.map((envelope) => parseTelephonyStreamEvent({
|
|
12815
|
-
carrier: input.carrier,
|
|
12816
|
-
envelope
|
|
12817
|
-
}));
|
|
12818
|
-
const issues = [];
|
|
12819
|
-
const startedIndex = events.findIndex((event) => event.kind === "start");
|
|
12820
|
-
const firstMediaIndex = events.findIndex((event) => event.kind === "media");
|
|
12821
|
-
const stoppedIndex = events.findIndex((event) => event.kind === "stop");
|
|
12822
|
-
const started = startedIndex >= 0;
|
|
12823
|
-
const stopped = stoppedIndex >= 0;
|
|
12824
|
-
const mediaEvents = events.filter((event) => event.kind === "media");
|
|
12825
|
-
const audioBytes = events.reduce((total, event) => total + event.audioBytes, 0);
|
|
12826
|
-
const minAudioBytes = input.minAudioBytes ?? 1;
|
|
12827
|
-
const streamIds = Array.from(new Set(events.map((event) => event.streamId).filter(Boolean)));
|
|
12828
|
-
if ((input.requireStart ?? true) && !started) {
|
|
12829
|
-
pushIssue(issues, "error", "media.telephony_missing_start", "Telephony media stream did not include a start event.");
|
|
12830
|
-
}
|
|
12831
|
-
if ((input.requireMedia ?? true) && mediaEvents.length === 0) {
|
|
12832
|
-
pushIssue(issues, "error", "media.telephony_missing_media", "Telephony media stream did not include media payload events.");
|
|
12833
|
-
}
|
|
12834
|
-
if ((input.requireStop ?? true) && !stopped) {
|
|
12835
|
-
pushIssue(issues, input.maxMissingStop === false ? "warning" : "error", "media.telephony_missing_stop", "Telephony media stream did not include a stop event.");
|
|
12836
|
-
}
|
|
12837
|
-
if (started && firstMediaIndex >= 0 && firstMediaIndex < startedIndex) {
|
|
12838
|
-
pushIssue(issues, "error", "media.telephony_media_before_start", "Telephony media payload arrived before the stream start event.");
|
|
12839
|
-
}
|
|
12840
|
-
if (stopped && firstMediaIndex >= 0 && stoppedIndex < firstMediaIndex) {
|
|
12841
|
-
pushIssue(issues, "error", "media.telephony_stop_before_media", "Telephony media stream stopped before any media payload arrived.");
|
|
12842
|
-
}
|
|
12843
|
-
if (mediaEvents.length > 0 && audioBytes < minAudioBytes) {
|
|
12844
|
-
pushIssue(issues, "error", "media.telephony_no_audio_bytes", `Telephony media stream parsed ${String(audioBytes)} audio byte(s), below required ${String(minAudioBytes)}.`);
|
|
12845
|
-
}
|
|
12846
|
-
for (const event of events) {
|
|
12847
|
-
if (event.kind === "error") {
|
|
12848
|
-
pushIssue(issues, "error", "media.telephony_stream_error", event.error ?? "Telephony media stream emitted an error event.");
|
|
12849
|
-
}
|
|
12850
|
-
}
|
|
12851
|
-
return {
|
|
12852
|
-
audioBytes,
|
|
12853
|
-
carrier: input.carrier,
|
|
12854
|
-
checkedAt: Date.now(),
|
|
12855
|
-
events,
|
|
12856
|
-
issues,
|
|
12857
|
-
mediaEvents: mediaEvents.length,
|
|
12858
|
-
started,
|
|
12859
|
-
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
12860
|
-
stopped,
|
|
12861
|
-
streamIds
|
|
12862
|
-
};
|
|
12863
|
-
};
|
|
12864
|
-
var buildMediaResamplingPlan = (input) => {
|
|
12865
|
-
const required = !formatMatches2(input.inputFormat, input.outputFormat);
|
|
12866
|
-
return {
|
|
12867
|
-
inputFormat: input.inputFormat,
|
|
12868
|
-
outputFormat: input.outputFormat,
|
|
12869
|
-
ratio: input.outputFormat.sampleRateHz / input.inputFormat.sampleRateHz,
|
|
12870
|
-
required,
|
|
12871
|
-
status: input.inputFormat.container === input.outputFormat.container && input.inputFormat.encoding === input.outputFormat.encoding && input.inputFormat.channels === input.outputFormat.channels ? "pass" : "warn"
|
|
12872
|
-
};
|
|
12873
|
-
};
|
|
12874
|
-
var speechProbability = (frame) => {
|
|
12875
|
-
if (frame.metadata?.isSpeech === true) {
|
|
12876
|
-
return 1;
|
|
12877
|
-
}
|
|
12878
|
-
if (frame.metadata?.isSpeech === false) {
|
|
12879
|
-
return 0;
|
|
12880
|
-
}
|
|
12881
|
-
for (const key of ["speechProbability", "voiceProbability", "rms", "energy"]) {
|
|
12882
|
-
const value = numericMetadata(frame, key);
|
|
12883
|
-
if (value !== undefined) {
|
|
12884
|
-
return value;
|
|
12885
|
-
}
|
|
12886
|
-
}
|
|
12887
|
-
return 0;
|
|
12888
|
-
};
|
|
12889
|
-
var buildMediaVadReport = (input = {}) => {
|
|
12890
|
-
const frames = (input.frames ?? []).filter((frame) => frame.kind === "input-audio");
|
|
12891
|
-
const speechStartThreshold = input.speechStartThreshold ?? 0.6;
|
|
12892
|
-
const speechEndThreshold = input.speechEndThreshold ?? 0.35;
|
|
12893
|
-
const minSpeechFrames = input.minSpeechFrames ?? 1;
|
|
12894
|
-
const maxSilenceFrames = input.maxSilenceFrames ?? 1;
|
|
12895
|
-
const segments = [];
|
|
12896
|
-
let activeFrames = [];
|
|
12897
|
-
let silenceFrames = 0;
|
|
12898
|
-
const closeSegment = () => {
|
|
12899
|
-
if (activeFrames.length < minSpeechFrames) {
|
|
12900
|
-
activeFrames = [];
|
|
12901
|
-
silenceFrames = 0;
|
|
12902
|
-
return;
|
|
12903
|
-
}
|
|
12904
|
-
const first = activeFrames[0];
|
|
12905
|
-
const last = activeFrames.at(-1);
|
|
12906
|
-
if (!first) {
|
|
12907
|
-
return;
|
|
12908
|
-
}
|
|
12909
|
-
segments.push({
|
|
12910
|
-
durationMs: first.at !== undefined && last?.at !== undefined ? last.at - first.at + (last.durationMs ?? 0) : undefined,
|
|
12911
|
-
endAt: last?.at !== undefined ? last.at + (last.durationMs ?? 0) : undefined,
|
|
12912
|
-
frameCount: activeFrames.length,
|
|
12913
|
-
segmentId: `vad:${String(segments.length + 1)}`,
|
|
12914
|
-
sessionId: first.sessionId,
|
|
12915
|
-
startAt: first.at,
|
|
12916
|
-
turnId: first.turnId
|
|
12917
|
-
});
|
|
12918
|
-
activeFrames = [];
|
|
12919
|
-
silenceFrames = 0;
|
|
12920
|
-
};
|
|
12921
|
-
for (const frame of frames) {
|
|
12922
|
-
const probability = speechProbability(frame);
|
|
12923
|
-
if (activeFrames.length === 0) {
|
|
12924
|
-
if (probability >= speechStartThreshold) {
|
|
12925
|
-
activeFrames.push(frame);
|
|
12926
|
-
}
|
|
12927
|
-
continue;
|
|
12928
|
-
}
|
|
12929
|
-
activeFrames.push(frame);
|
|
12930
|
-
if (probability <= speechEndThreshold) {
|
|
12931
|
-
silenceFrames += 1;
|
|
12932
|
-
} else {
|
|
12933
|
-
silenceFrames = 0;
|
|
12934
|
-
}
|
|
12935
|
-
if (silenceFrames > maxSilenceFrames) {
|
|
12936
|
-
closeSegment();
|
|
12937
|
-
}
|
|
12938
|
-
}
|
|
12939
|
-
closeSegment();
|
|
12940
|
-
return {
|
|
12941
|
-
checkedAt: Date.now(),
|
|
12942
|
-
inputAudioFrames: frames.length,
|
|
12943
|
-
segments,
|
|
12944
|
-
status: frames.length === 0 ? "warn" : "pass"
|
|
12945
|
-
};
|
|
12946
|
-
};
|
|
12947
|
-
var buildMediaInterruptionReport = (input = {}) => {
|
|
12948
|
-
const issues = [];
|
|
12949
|
-
const interruptionFrames = (input.frames ?? []).filter((frame) => frame.kind === "interruption");
|
|
12950
|
-
const latenciesMs = interruptionFrames.map((frame) => frame.latencyMs).filter((latency) => typeof latency === "number");
|
|
12951
|
-
const maxInterruptionLatencyMs = input.maxInterruptionLatencyMs;
|
|
12952
|
-
if (interruptionFrames.length === 0) {
|
|
12953
|
-
pushIssue(issues, "warning", "media.interruption_missing", "No interruption frame was observed.");
|
|
12954
|
-
}
|
|
12955
|
-
if (maxInterruptionLatencyMs !== undefined && latenciesMs.some((latency) => latency > maxInterruptionLatencyMs)) {
|
|
12956
|
-
pushIssue(issues, "error", "media.interruption_latency", `Interruption latency exceeded ${String(maxInterruptionLatencyMs)}ms.`);
|
|
12957
|
-
}
|
|
12958
|
-
return {
|
|
12959
|
-
checkedAt: Date.now(),
|
|
12960
|
-
interruptionFrames: interruptionFrames.length,
|
|
12961
|
-
issues,
|
|
12962
|
-
latenciesMs,
|
|
12963
|
-
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass"
|
|
12964
|
-
};
|
|
12965
|
-
};
|
|
12966
|
-
var buildMediaQualityReport = (input = {}) => {
|
|
12967
|
-
const frames = [...input.frames ?? []].sort((a, b) => (a.at ?? 0) - (b.at ?? 0));
|
|
12968
|
-
const audioFrames = frames.filter((frame) => frame.kind === "input-audio" || frame.kind === "assistant-audio");
|
|
12969
|
-
const inputAudioFrames = frames.filter((frame) => frame.kind === "input-audio");
|
|
12970
|
-
const assistantAudioFrames = frames.filter((frame) => frame.kind === "assistant-audio");
|
|
12971
|
-
const issues = [];
|
|
12972
|
-
const gapsMs = [];
|
|
12973
|
-
for (const [index, frame] of audioFrames.entries()) {
|
|
12974
|
-
const previous = audioFrames[index - 1];
|
|
12975
|
-
if (previous?.at === undefined || frame.at === undefined || previous.durationMs === undefined) {
|
|
12976
|
-
continue;
|
|
12977
|
-
}
|
|
12978
|
-
const gap = frame.at - (previous.at + previous.durationMs);
|
|
12979
|
-
if (gap > 0) {
|
|
12980
|
-
gapsMs.push(gap);
|
|
12981
|
-
}
|
|
12982
|
-
}
|
|
12983
|
-
const jitterMs = audioFrames.map((frame) => numericMetadata(frame, "jitterMs")).filter((value) => value !== undefined).at(-1) ?? max(gapsMs);
|
|
12984
|
-
const first = audioFrames.find((frame) => frame.at !== undefined);
|
|
12985
|
-
const last = audioFrames.toReversed().find((frame) => frame.at !== undefined);
|
|
12986
|
-
const durationMs = first?.at !== undefined && last?.at !== undefined ? last.at - first.at + (last.durationMs ?? 0) : undefined;
|
|
12987
|
-
const expectedDurationMs = audioFrames.length > 0 ? audioFrames.reduce((total, frame) => total + (frame.durationMs ?? 0), 0) : undefined;
|
|
12988
|
-
const timestampDriftMs = durationMs !== undefined && expectedDurationMs !== undefined ? Math.max(0, durationMs - expectedDurationMs) : undefined;
|
|
12989
|
-
const speechScores = inputAudioFrames.map(speechProbability);
|
|
12990
|
-
const speechFrames = speechScores.filter((score) => score >= 0.6).length;
|
|
12991
|
-
const silenceFrames = speechScores.filter((score) => score <= 0.35).length;
|
|
12992
|
-
const unknownSpeechFrames = Math.max(0, inputAudioFrames.length - speechFrames - silenceFrames);
|
|
12993
|
-
const speechRatio = inputAudioFrames.length === 0 ? 0 : speechFrames / inputAudioFrames.length;
|
|
12994
|
-
const silenceRatio = inputAudioFrames.length === 0 ? 0 : silenceFrames / inputAudioFrames.length;
|
|
12995
|
-
const levels = audioFrames.map((frame) => numericMetadata(frame, "level") ?? numericMetadata(frame, "rms") ?? numericMetadata(frame, "energy")).filter((value) => value !== undefined);
|
|
12996
|
-
const backpressureEvents = input.transport?.backpressureEvents ?? 0;
|
|
12997
|
-
const maxGapMs = input.maxGapMs;
|
|
12998
|
-
if (maxGapMs !== undefined && gapsMs.some((gap) => gap > maxGapMs)) {
|
|
12999
|
-
pushIssue(issues, "warning", "media.quality_gap", `Observed media gap above ${String(maxGapMs)}ms.`);
|
|
13000
|
-
}
|
|
13001
|
-
if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
|
|
13002
|
-
pushIssue(issues, "warning", "media.quality_jitter", `Observed jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
|
|
13003
|
-
}
|
|
13004
|
-
if (input.maxTimestampDriftMs !== undefined && timestampDriftMs !== undefined && timestampDriftMs > input.maxTimestampDriftMs) {
|
|
13005
|
-
pushIssue(issues, "warning", "media.quality_timestamp_drift", `Observed timestamp drift ${String(timestampDriftMs)}ms above ${String(input.maxTimestampDriftMs)}ms.`);
|
|
13006
|
-
}
|
|
13007
|
-
if (input.minSpeechRatio !== undefined && inputAudioFrames.length > 0 && speechRatio < input.minSpeechRatio) {
|
|
13008
|
-
pushIssue(issues, "warning", "media.quality_speech_ratio", `Observed speech ratio ${String(speechRatio)} below ${String(input.minSpeechRatio)}.`);
|
|
13009
|
-
}
|
|
13010
|
-
if (input.maxBackpressureEvents !== undefined && backpressureEvents > input.maxBackpressureEvents) {
|
|
13011
|
-
pushIssue(issues, "warning", "media.quality_backpressure", `Observed ${String(backpressureEvents)} backpressure event(s), above ${String(input.maxBackpressureEvents)}.`);
|
|
13012
|
-
}
|
|
13013
|
-
return {
|
|
13014
|
-
assistantAudioFrames: assistantAudioFrames.length,
|
|
13015
|
-
backpressureEvents,
|
|
13016
|
-
checkedAt: Date.now(),
|
|
13017
|
-
durationMs,
|
|
13018
|
-
gapCount: gapsMs.length,
|
|
13019
|
-
gapsMs,
|
|
13020
|
-
inputAudioFrames: inputAudioFrames.length,
|
|
13021
|
-
issues,
|
|
13022
|
-
jitterMs,
|
|
13023
|
-
levelAverage: average(levels),
|
|
13024
|
-
levelMax: max(levels),
|
|
13025
|
-
levelMin: min(levels),
|
|
13026
|
-
silenceFrames,
|
|
13027
|
-
silenceRatio,
|
|
13028
|
-
speechFrames,
|
|
13029
|
-
speechRatio,
|
|
13030
|
-
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
13031
|
-
timestampDriftMs,
|
|
13032
|
-
totalFrames: frames.length,
|
|
13033
|
-
unknownSpeechFrames
|
|
13034
|
-
};
|
|
13035
|
-
};
|
|
13036
|
-
var buildMediaWebRTCStatsReport = (input = {}) => {
|
|
13037
|
-
const stats = input.stats ?? [];
|
|
13038
|
-
const issues = [];
|
|
13039
|
-
const inbound = stats.filter((stat) => stat.type === "inbound-rtp" && stringStat(stat, "kind") !== "video");
|
|
13040
|
-
const outbound = stats.filter((stat) => stat.type === "outbound-rtp" && stringStat(stat, "kind") !== "video");
|
|
13041
|
-
const candidatePairs = stats.filter((stat) => stat.type === "candidate-pair");
|
|
13042
|
-
const audioTracks = stats.filter((stat) => (stat.type === "track" || stat.type === "media-source") && stringStat(stat, "kind") === "audio");
|
|
13043
|
-
const activeCandidatePairs = candidatePairs.filter((stat) => booleanStat(stat, "selected") === true || booleanStat(stat, "nominated") === true || stringStat(stat, "state") === "succeeded").length;
|
|
13044
|
-
const liveAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") !== "ended" && stringStat(stat, "trackState") !== "ended" && booleanStat(stat, "ended") !== true).length;
|
|
13045
|
-
const endedAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") === "ended" || stringStat(stat, "trackState") === "ended" || booleanStat(stat, "ended") === true).length;
|
|
13046
|
-
const inboundPackets = inbound.reduce((total, stat) => total + (numericStat(stat, "packetsReceived") ?? 0), 0);
|
|
13047
|
-
const outboundPackets = outbound.reduce((total, stat) => total + (numericStat(stat, "packetsSent") ?? 0), 0);
|
|
13048
|
-
const packetsLost = [...inbound, ...outbound].reduce((total, stat) => total + Math.max(0, numericStat(stat, "packetsLost") ?? 0), 0);
|
|
13049
|
-
const packetLossDenominator = inboundPackets + packetsLost;
|
|
13050
|
-
const packetLossRatio = packetLossDenominator === 0 ? 0 : packetsLost / packetLossDenominator;
|
|
13051
|
-
const bytesReceived = inbound.reduce((total, stat) => total + (numericStat(stat, "bytesReceived") ?? 0), 0);
|
|
13052
|
-
const bytesSent = outbound.reduce((total, stat) => total + (numericStat(stat, "bytesSent") ?? 0), 0);
|
|
13053
|
-
const roundTripTimeMs = max(candidatePairs.map((stat) => secondsToMs(numericStat(stat, "currentRoundTripTime") ?? numericStat(stat, "roundTripTime"))).filter((value) => value !== undefined));
|
|
13054
|
-
const jitterMs = max([...inbound, ...outbound].map((stat) => secondsToMs(numericStat(stat, "jitter"))).filter((value) => value !== undefined));
|
|
13055
|
-
const jitterBufferDelayMs = max(inbound.map((stat) => {
|
|
13056
|
-
const delay = numericStat(stat, "jitterBufferDelay");
|
|
13057
|
-
const emitted = numericStat(stat, "jitterBufferEmittedCount");
|
|
13058
|
-
return delay !== undefined && emitted !== undefined && emitted > 0 ? delay / emitted * 1000 : undefined;
|
|
13059
|
-
}).filter((value) => value !== undefined));
|
|
13060
|
-
const audioLevels = audioTracks.map((stat) => numericStat(stat, "audioLevel")).filter((value) => value !== undefined);
|
|
13061
|
-
if (input.requireConnectedCandidatePair && candidatePairs.length > 0 && activeCandidatePairs === 0) {
|
|
13062
|
-
pushIssue(issues, "error", "media.webrtc_candidate_pair_missing", "No active WebRTC candidate pair was observed.");
|
|
13063
|
-
}
|
|
13064
|
-
if (input.requireLiveAudioTrack && liveAudioTracks === 0) {
|
|
13065
|
-
pushIssue(issues, "error", "media.webrtc_audio_track_missing", "No live WebRTC audio track was observed.");
|
|
13066
|
-
}
|
|
13067
|
-
if (input.maxPacketLossRatio !== undefined && packetLossRatio > input.maxPacketLossRatio) {
|
|
13068
|
-
pushIssue(issues, "warning", "media.webrtc_packet_loss", `Observed WebRTC packet loss ratio ${String(packetLossRatio)} above ${String(input.maxPacketLossRatio)}.`);
|
|
13069
|
-
}
|
|
13070
|
-
if (input.maxRoundTripTimeMs !== undefined && roundTripTimeMs !== undefined && roundTripTimeMs > input.maxRoundTripTimeMs) {
|
|
13071
|
-
pushIssue(issues, "warning", "media.webrtc_round_trip_time", `Observed WebRTC RTT ${String(roundTripTimeMs)}ms above ${String(input.maxRoundTripTimeMs)}ms.`);
|
|
13072
|
-
}
|
|
13073
|
-
if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
|
|
13074
|
-
pushIssue(issues, "warning", "media.webrtc_jitter", `Observed WebRTC jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
|
|
13075
|
-
}
|
|
13076
|
-
return {
|
|
13077
|
-
activeCandidatePairs,
|
|
13078
|
-
audioLevelAverage: average(audioLevels),
|
|
13079
|
-
bytesReceived,
|
|
13080
|
-
bytesSent,
|
|
13081
|
-
checkedAt: Date.now(),
|
|
13082
|
-
endedAudioTracks,
|
|
13083
|
-
inboundPackets,
|
|
13084
|
-
issues,
|
|
13085
|
-
jitterBufferDelayMs,
|
|
13086
|
-
jitterMs,
|
|
13087
|
-
liveAudioTracks,
|
|
13088
|
-
outboundPackets,
|
|
13089
|
-
packetLossRatio,
|
|
13090
|
-
packetsLost,
|
|
13091
|
-
roundTripTimeMs,
|
|
13092
|
-
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
13093
|
-
totalStats: stats.length
|
|
13094
|
-
};
|
|
13095
|
-
};
|
|
13096
|
-
var collectMediaWebRTCStats = async (input) => {
|
|
13097
|
-
const report = await input.peerConnection.getStats(input.selector ?? null);
|
|
13098
|
-
return [...report.values()].map(normalizeWebRTCStat);
|
|
13099
|
-
};
|
|
13100
|
-
var buildMediaWebRTCStreamContinuityReport = (input = {}) => {
|
|
13101
|
-
const stats = input.stats ?? [];
|
|
13102
|
-
const previousStats = input.previousStats ?? [];
|
|
13103
|
-
const issues = [];
|
|
13104
|
-
const previousByKey = new Map(previousStats.map((stat) => [statKey(stat), stat]));
|
|
13105
|
-
const audioRtp = stats.filter((stat) => (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") && stringStat(stat, "kind") !== "video" && stringStat(stat, "mediaType") !== "video");
|
|
13106
|
-
const streams = audioRtp.map((stat) => {
|
|
13107
|
-
const direction = stat.type === "outbound-rtp" ? "outbound" : "inbound";
|
|
13108
|
-
const packetsKey = direction === "outbound" ? "packetsSent" : "packetsReceived";
|
|
13109
|
-
const bytesKey = direction === "outbound" ? "bytesSent" : "bytesReceived";
|
|
13110
|
-
const previous = previousByKey.get(statKey(stat));
|
|
13111
|
-
const currentPackets = numericStat(stat, packetsKey);
|
|
13112
|
-
const previousPackets = previous ? numericStat(previous, packetsKey) : undefined;
|
|
13113
|
-
const currentBytes = numericStat(stat, bytesKey);
|
|
13114
|
-
const previousBytes = previous ? numericStat(previous, bytesKey) : undefined;
|
|
13115
|
-
const timeDeltaMs = stat.timestamp !== undefined && previous?.timestamp !== undefined ? stat.timestamp - previous.timestamp : undefined;
|
|
13116
|
-
return {
|
|
13117
|
-
bytesDelta: currentBytes !== undefined && previousBytes !== undefined ? currentBytes - previousBytes : undefined,
|
|
13118
|
-
currentPackets,
|
|
13119
|
-
direction,
|
|
13120
|
-
id: statKey(stat),
|
|
13121
|
-
packetDelta: currentPackets !== undefined && previousPackets !== undefined ? currentPackets - previousPackets : undefined,
|
|
13122
|
-
previousPackets,
|
|
13123
|
-
timeDeltaMs
|
|
13124
|
-
};
|
|
13125
|
-
});
|
|
13126
|
-
const inbound = streams.filter((stream) => stream.direction === "inbound");
|
|
13127
|
-
const outbound = streams.filter((stream) => stream.direction === "outbound");
|
|
13128
|
-
const maxObservedGapMs = max(streams.map((stream) => stream.timeDeltaMs).filter((value) => value !== undefined));
|
|
13129
|
-
const stalledInboundStreams = inbound.filter((stream) => input.maxInboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxInboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
|
|
13130
|
-
const stalledOutboundStreams = outbound.filter((stream) => input.maxOutboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxOutboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
|
|
13131
|
-
if (input.requireInboundAudio && inbound.length === 0) {
|
|
13132
|
-
pushIssue(issues, "error", "media.webrtc_inbound_audio_missing", "No inbound WebRTC audio RTP stream was observed.");
|
|
13133
|
-
}
|
|
13134
|
-
if (input.requireOutboundAudio && outbound.length === 0) {
|
|
13135
|
-
pushIssue(issues, "error", "media.webrtc_outbound_audio_missing", "No outbound WebRTC audio RTP stream was observed.");
|
|
13136
|
-
}
|
|
13137
|
-
if (input.maxGapMs !== undefined && maxObservedGapMs !== undefined && maxObservedGapMs > input.maxGapMs) {
|
|
13138
|
-
pushIssue(issues, "warning", "media.webrtc_stream_gap", `Observed WebRTC stream sample gap ${String(maxObservedGapMs)}ms above ${String(input.maxGapMs)}ms.`);
|
|
13139
|
-
}
|
|
13140
|
-
if (stalledInboundStreams > 0) {
|
|
13141
|
-
pushIssue(issues, "error", "media.webrtc_inbound_stalled", `${String(stalledInboundStreams)} inbound WebRTC audio stream(s) stopped receiving packets.`);
|
|
13142
|
-
}
|
|
13143
|
-
if (stalledOutboundStreams > 0) {
|
|
13144
|
-
pushIssue(issues, "error", "media.webrtc_outbound_stalled", `${String(stalledOutboundStreams)} outbound WebRTC audio stream(s) stopped sending packets.`);
|
|
13145
|
-
}
|
|
13146
|
-
return {
|
|
13147
|
-
checkedAt: Date.now(),
|
|
13148
|
-
inboundAudioStreams: inbound.length,
|
|
13149
|
-
issues,
|
|
13150
|
-
maxObservedGapMs,
|
|
13151
|
-
outboundAudioStreams: outbound.length,
|
|
13152
|
-
stalledInboundStreams,
|
|
13153
|
-
stalledOutboundStreams,
|
|
13154
|
-
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
13155
|
-
streams,
|
|
13156
|
-
totalStats: stats.length
|
|
13157
|
-
};
|
|
13158
|
-
};
|
|
13159
|
-
var buildMediaPipelineCalibrationReport = (input = {}) => {
|
|
13160
|
-
const frames = input.frames ?? [];
|
|
13161
|
-
const issues = [];
|
|
13162
|
-
const inputFrames = frames.filter((frame) => frame.kind === "input-audio");
|
|
13163
|
-
const assistantFrames = frames.filter((frame) => frame.kind === "assistant-audio");
|
|
13164
|
-
const turnCommitFrames = frames.filter((frame) => frame.kind === "turn-commit");
|
|
13165
|
-
const interruptionFrameRecords = frames.filter((frame) => frame.kind === "interruption");
|
|
13166
|
-
const traceLinkedFrames = frames.filter((frame) => frame.traceEventId).length;
|
|
13167
|
-
const backpressureFrames = frames.filter((frame) => Boolean(frame.metadata?.backpressure)).length;
|
|
13168
|
-
const audioLatencies = assistantFrames.map((frame) => frame.latencyMs).filter((latency) => typeof latency === "number");
|
|
13169
|
-
const firstAudioLatencyMs = audioLatencies.length > 0 ? Math.min(...audioLatencies) : undefined;
|
|
13170
|
-
const jitterValues = frames.map((frame) => numericMetadata(frame, "jitterMs")).filter((value) => value !== undefined);
|
|
13171
|
-
const jitterMs = jitterValues.length > 0 ? Math.max(...jitterValues) : undefined;
|
|
13172
|
-
const inputFormat = input.inputFormat ?? inputFrames.find((frame) => frame.format)?.format;
|
|
13173
|
-
const outputFormat = input.outputFormat ?? assistantFrames.find((frame) => frame.format)?.format;
|
|
13174
|
-
const resamplingRequired = Boolean(input.expectedInputFormat && inputFormat && inputFormat.sampleRateHz !== input.expectedInputFormat.sampleRateHz) || Boolean(input.expectedOutputFormat && outputFormat && outputFormat.sampleRateHz !== input.expectedOutputFormat.sampleRateHz);
|
|
13175
|
-
const resamplingTargetHz = resamplingRequired && input.expectedInputFormat ? input.expectedInputFormat.sampleRateHz : resamplingRequired ? input.expectedOutputFormat?.sampleRateHz : undefined;
|
|
13176
|
-
if (inputFrames.length === 0) {
|
|
13177
|
-
pushIssue(issues, "warning", "media.input_audio_missing", "No input audio frames were observed.");
|
|
13178
|
-
}
|
|
13179
|
-
if (assistantFrames.length === 0) {
|
|
13180
|
-
pushIssue(issues, "warning", "media.assistant_audio_missing", "No assistant audio frames were observed.");
|
|
13181
|
-
}
|
|
13182
|
-
if (input.expectedInputFormat && inputFormat && !formatMatches2(inputFormat, input.expectedInputFormat)) {
|
|
13183
|
-
pushIssue(issues, inputFormat.sampleRateHz === input.expectedInputFormat.sampleRateHz ? "warning" : "error", "media.input_format_mismatch", `Input format ${formatLabel2(inputFormat)} does not match expected ${formatLabel2(input.expectedInputFormat)}.`);
|
|
13184
|
-
}
|
|
13185
|
-
if (input.expectedOutputFormat && outputFormat && !formatMatches2(outputFormat, input.expectedOutputFormat)) {
|
|
13186
|
-
pushIssue(issues, outputFormat.sampleRateHz === input.expectedOutputFormat.sampleRateHz ? "warning" : "error", "media.output_format_mismatch", `Output format ${formatLabel2(outputFormat)} does not match expected ${formatLabel2(input.expectedOutputFormat)}.`);
|
|
13187
|
-
}
|
|
13188
|
-
if (firstAudioLatencyMs !== undefined && input.maxFirstAudioLatencyMs !== undefined && firstAudioLatencyMs > input.maxFirstAudioLatencyMs) {
|
|
13189
|
-
pushIssue(issues, "error", "media.first_audio_latency", `First audio latency ${String(firstAudioLatencyMs)}ms exceeds budget ${String(input.maxFirstAudioLatencyMs)}ms.`);
|
|
13190
|
-
}
|
|
13191
|
-
if (jitterMs !== undefined && input.maxJitterMs !== undefined && jitterMs > input.maxJitterMs) {
|
|
13192
|
-
pushIssue(issues, "warning", "media.jitter", `Media jitter ${String(jitterMs)}ms exceeds budget ${String(input.maxJitterMs)}ms.`);
|
|
13193
|
-
}
|
|
13194
|
-
if (input.maxBackpressureFrames !== undefined && backpressureFrames > input.maxBackpressureFrames) {
|
|
13195
|
-
pushIssue(issues, "warning", "media.backpressure", `Backpressure frame count ${String(backpressureFrames)} exceeds budget ${String(input.maxBackpressureFrames)}.`);
|
|
13196
|
-
}
|
|
13197
|
-
if (input.requireInterruptionFrame && interruptionFrameRecords.length === 0) {
|
|
13198
|
-
pushIssue(issues, "warning", "media.interruption_missing", "No interruption frame was observed.");
|
|
13199
|
-
}
|
|
13200
|
-
if (input.requireTraceEvidence && traceLinkedFrames === 0) {
|
|
13201
|
-
pushIssue(issues, "warning", "media.trace_evidence_missing", "No media frames were linked to trace evidence.");
|
|
13202
|
-
}
|
|
13203
|
-
return {
|
|
13204
|
-
assistantAudioFrames: assistantFrames.length,
|
|
13205
|
-
backpressureFrames,
|
|
13206
|
-
checkedAt: Date.now(),
|
|
13207
|
-
firstAudioLatencyMs,
|
|
13208
|
-
inputAudioFrames: inputFrames.length,
|
|
13209
|
-
inputFormat,
|
|
13210
|
-
interruptionFrames: interruptionFrameRecords.length,
|
|
13211
|
-
issues,
|
|
13212
|
-
jitterMs,
|
|
13213
|
-
outputFormat,
|
|
13214
|
-
resamplingRequired,
|
|
13215
|
-
resamplingTargetHz,
|
|
13216
|
-
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
13217
|
-
surface: input.surface ?? "media-pipeline",
|
|
13218
|
-
traceLinkedFrames,
|
|
13219
|
-
turnCommitFrames: turnCommitFrames.length
|
|
13220
|
-
};
|
|
13221
|
-
};
|
|
13222
|
-
var DEFAULT_METADATA_DENY = [
|
|
13223
|
-
"audioPayload",
|
|
13224
|
-
"auth",
|
|
13225
|
-
"authorization",
|
|
13226
|
-
"cookie",
|
|
13227
|
-
"email",
|
|
13228
|
-
"phone",
|
|
13229
|
-
"phoneNumber",
|
|
13230
|
-
"rawPayload",
|
|
13231
|
-
"secret",
|
|
13232
|
-
"token",
|
|
13233
|
-
"transcript",
|
|
13234
|
-
"utterance"
|
|
13235
|
-
];
|
|
13236
|
-
var DEFAULT_TRUNCATE = 8;
|
|
13237
|
-
var issueCodes = (issues) => Array.from(new Set(issues.map((issue) => issue.code)));
|
|
13238
|
-
var lastEventKind = (events) => events[events.length - 1]?.kind;
|
|
13239
|
-
var formatOptionalMs = (value) => value === undefined ? "n/a" : `${String(Math.round(value))}ms`;
|
|
13240
|
-
var formatRatio = (value) => `${(value * 100).toFixed(1)}%`;
|
|
13241
|
-
var escapeMarkdownCell = (value) => value.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
13242
|
-
var renderIssuesTable = (issues) => {
|
|
13243
|
-
if (issues.length === 0) {
|
|
13244
|
-
return `- No issues.
|
|
13245
|
-
`;
|
|
13246
|
-
}
|
|
13247
|
-
const rows = issues.map((issue) => `| ${issue.severity} | ${escapeMarkdownCell(issue.code)} | ${escapeMarkdownCell(issue.message)} |`).join(`
|
|
13248
|
-
`);
|
|
13249
|
-
return `| Severity | Code | Message |
|
|
13250
|
-
| --- | --- | --- |
|
|
13251
|
-
${rows}
|
|
13252
|
-
`;
|
|
13253
|
-
};
|
|
13254
|
-
var summarizeMediaQualityReport = (report) => ({
|
|
13255
|
-
backpressureEvents: report.backpressureEvents,
|
|
13256
|
-
description: `${report.totalFrames} frame(s), ${report.gapCount} gap(s), speech ${formatRatio(report.speechRatio)}, status ${report.status}.`,
|
|
13257
|
-
driftMs: report.timestampDriftMs,
|
|
13258
|
-
frameCount: report.totalFrames,
|
|
13259
|
-
gapCount: report.gapCount,
|
|
13260
|
-
issueCodes: issueCodes(report.issues),
|
|
13261
|
-
issueCount: report.issues.length,
|
|
13262
|
-
jitterMs: report.jitterMs,
|
|
13263
|
-
silenceRatio: report.silenceRatio,
|
|
13264
|
-
speechRatio: report.speechRatio,
|
|
13265
|
-
status: report.status
|
|
13266
|
-
});
|
|
13267
|
-
var summarizeMediaTransportReport = (report) => ({
|
|
13268
|
-
backpressureEvents: report.backpressureEvents,
|
|
13269
|
-
description: `${report.name}: ${report.state}, in ${report.inputFrames}, out ${report.outputFrames}, backpressure ${report.backpressureEvents}.`,
|
|
13270
|
-
errors: report.events.filter((event) => event.kind === "error").length,
|
|
13271
|
-
inputFrames: report.inputFrames,
|
|
13272
|
-
lastEventKind: lastEventKind(report.events),
|
|
13273
|
-
name: report.name,
|
|
13274
|
-
outputFrames: report.outputFrames,
|
|
13275
|
-
state: report.state,
|
|
13276
|
-
status: report.status
|
|
13277
|
-
});
|
|
13278
|
-
var summarizeMediaProcessorGraphReport = (report) => {
|
|
13279
|
-
const errorIssueCodes = Array.from(new Set(report.errors.map((event) => event.kind)));
|
|
13280
|
-
return {
|
|
13281
|
-
backpressureEvents: report.backpressure.events.length,
|
|
13282
|
-
description: `${report.name}: ${report.state}, ${report.nodes.length} node(s), in ${report.inputFrames}, out ${report.emittedFrames}, dropped ${report.droppedFrames}.`,
|
|
13283
|
-
droppedFrames: report.droppedFrames,
|
|
13284
|
-
edgeCount: report.edges.length,
|
|
13285
|
-
edgeEventCount: report.edgeEvents.length,
|
|
13286
|
-
emittedFrames: report.emittedFrames,
|
|
13287
|
-
errorCount: report.errors.length,
|
|
13288
|
-
inputFrames: report.inputFrames,
|
|
13289
|
-
issueCodes: errorIssueCodes,
|
|
13290
|
-
lifecycleEventCount: report.lifecycleEvents.length,
|
|
13291
|
-
name: report.name,
|
|
13292
|
-
nodeCount: report.nodes.length,
|
|
13293
|
-
state: report.state,
|
|
13294
|
-
status: report.status,
|
|
13295
|
-
timingMaxMs: report.timing.maxNodeMs
|
|
13296
|
-
};
|
|
13297
|
-
};
|
|
13298
|
-
var renderMediaQualityMarkdown = (report, options = {}) => {
|
|
13299
|
-
const title = options.title ?? "Media Quality Report";
|
|
13300
|
-
const lines = [
|
|
13301
|
-
`# ${title}`,
|
|
13302
|
-
"",
|
|
13303
|
-
`Status: **${report.status}**`,
|
|
13304
|
-
"",
|
|
13305
|
-
"| Metric | Value |",
|
|
13306
|
-
"| --- | ---: |",
|
|
13307
|
-
`| Total frames | ${report.totalFrames} |`,
|
|
13308
|
-
`| Input audio | ${report.inputAudioFrames} |`,
|
|
13309
|
-
`| Assistant audio | ${report.assistantAudioFrames} |`,
|
|
13310
|
-
`| Gaps | ${report.gapCount} |`,
|
|
13311
|
-
`| Jitter | ${formatOptionalMs(report.jitterMs)} |`,
|
|
13312
|
-
`| Timestamp drift | ${formatOptionalMs(report.timestampDriftMs)} |`,
|
|
13313
|
-
`| Speech ratio | ${formatRatio(report.speechRatio)} |`,
|
|
13314
|
-
`| Silence ratio | ${formatRatio(report.silenceRatio)} |`,
|
|
13315
|
-
`| Backpressure events | ${report.backpressureEvents} |`,
|
|
13316
|
-
"",
|
|
13317
|
-
"## Issues",
|
|
13318
|
-
"",
|
|
13319
|
-
renderIssuesTable(report.issues).trimEnd()
|
|
13320
|
-
];
|
|
13321
|
-
return `${lines.join(`
|
|
13322
|
-
`)}
|
|
13323
|
-
`;
|
|
13324
|
-
};
|
|
13325
|
-
var renderMediaTransportMarkdown = (report, options = {}) => {
|
|
13326
|
-
const title = options.title ?? `Media Transport: ${report.name}`;
|
|
13327
|
-
const limit = options.redact?.truncateArraysAt ?? DEFAULT_TRUNCATE;
|
|
13328
|
-
const events = report.events.slice(-limit);
|
|
13329
|
-
const eventRows = events.length === 0 ? "- No transport events recorded." : ["| At | Kind | State | Buffered | Error |", "| --- | --- | --- | ---: | --- |"].concat(events.map((event) => `| ${event.at} | ${event.kind} | ${event.state} | ${event.bufferedFrames ?? ""} | ${escapeMarkdownCell(event.error ?? "")} |`)).join(`
|
|
13330
|
-
`);
|
|
13331
|
-
const lines = [
|
|
13332
|
-
`# ${title}`,
|
|
13333
|
-
"",
|
|
13334
|
-
`Status: **${report.status}** \xB7 State: **${report.state}**`,
|
|
13335
|
-
"",
|
|
13336
|
-
"| Metric | Value |",
|
|
13337
|
-
"| --- | ---: |",
|
|
13338
|
-
`| Input frames | ${report.inputFrames} |`,
|
|
13339
|
-
`| Output frames | ${report.outputFrames} |`,
|
|
13340
|
-
`| Backpressure events | ${report.backpressureEvents} |`,
|
|
13341
|
-
`| Connected | ${report.connected ? "yes" : "no"} |`,
|
|
13342
|
-
`| Closed | ${report.closed ? "yes" : "no"} |`,
|
|
13343
|
-
`| Failed | ${report.failed ? "yes" : "no"} |`,
|
|
13344
|
-
"",
|
|
13345
|
-
`## Last ${events.length} event(s)`,
|
|
13346
|
-
"",
|
|
13347
|
-
eventRows
|
|
13348
|
-
];
|
|
13349
|
-
return `${lines.join(`
|
|
13350
|
-
`)}
|
|
13351
|
-
`;
|
|
13352
|
-
};
|
|
13353
|
-
var renderMediaProcessorGraphMarkdown = (report, options = {}) => {
|
|
13354
|
-
const title = options.title ?? `Media Processor Graph: ${report.name}`;
|
|
13355
|
-
const limit = options.redact?.truncateArraysAt ?? DEFAULT_TRUNCATE;
|
|
13356
|
-
const nodeRows = report.nodes.map((node) => `| ${escapeMarkdownCell(node.name)} | ${node.kind} | ${node.status} | ${node.inputFrames} | ${node.emittedFrames} | ${node.droppedFrames} | ${node.errors.length} |`).join(`
|
|
13357
|
-
`);
|
|
13358
|
-
const edgeRows = report.edges.slice(0, limit).map((edge) => `| ${escapeMarkdownCell(edge.from)} | ${escapeMarkdownCell(edge.to)} | ${edge.status} | ${edge.emittedFrames} |`).join(`
|
|
13359
|
-
`);
|
|
13360
|
-
const issues = report.errors.map((event) => ({
|
|
13361
|
-
code: event.kind,
|
|
13362
|
-
message: event.error ?? `Processor graph ${event.kind} (state ${event.state}).`,
|
|
13363
|
-
severity: "error"
|
|
13364
|
-
}));
|
|
13365
|
-
const lines = [
|
|
13366
|
-
`# ${title}`,
|
|
13367
|
-
"",
|
|
13368
|
-
`Status: **${report.status}** \xB7 State: **${report.state}**`,
|
|
13369
|
-
"",
|
|
13370
|
-
"| Metric | Value |",
|
|
13371
|
-
"| --- | ---: |",
|
|
13372
|
-
`| Nodes | ${report.nodes.length} |`,
|
|
13373
|
-
`| Input frames | ${report.inputFrames} |`,
|
|
13374
|
-
`| Emitted frames | ${report.emittedFrames} |`,
|
|
13375
|
-
`| Dropped frames | ${report.droppedFrames} |`,
|
|
13376
|
-
`| Lifecycle events | ${report.lifecycleEvents.length} |`,
|
|
13377
|
-
`| Edge events | ${report.edgeEvents.length} |`,
|
|
13378
|
-
`| Backpressure events | ${report.backpressure.events.length} |`,
|
|
13379
|
-
`| Timing max | ${formatOptionalMs(report.timing.maxNodeMs)} |`,
|
|
13380
|
-
`| Timing average | ${formatOptionalMs(report.timing.averageNodeMs)} |`,
|
|
13381
|
-
"",
|
|
13382
|
-
"## Nodes",
|
|
13383
|
-
"",
|
|
13384
|
-
nodeRows ? `| Node | Kind | Status | In | Out | Dropped | Errors |
|
|
13385
|
-
| --- | --- | --- | ---: | ---: | ---: | ---: |
|
|
13386
|
-
${nodeRows}` : "- No nodes.",
|
|
13387
|
-
"",
|
|
13388
|
-
`## Edges (showing up to ${limit})`,
|
|
13389
|
-
"",
|
|
13390
|
-
edgeRows ? `| From | To | Status | Frames |
|
|
13391
|
-
| --- | --- | --- | ---: |
|
|
13392
|
-
${edgeRows}` : "- No edges.",
|
|
13393
|
-
"",
|
|
13394
|
-
"## Errors",
|
|
13395
|
-
"",
|
|
13396
|
-
renderIssuesTable(issues).trimEnd()
|
|
13397
|
-
];
|
|
13398
|
-
return `${lines.join(`
|
|
13399
|
-
`)}
|
|
13400
|
-
`;
|
|
13401
|
-
};
|
|
13402
|
-
var truncateArrays = (value, limit, seen) => {
|
|
13403
|
-
if (Array.isArray(value)) {
|
|
13404
|
-
const head = value.slice(0, limit).map((entry) => truncateArrays(entry, limit, seen));
|
|
13405
|
-
if (value.length > limit) {
|
|
13406
|
-
return [...head, { truncated: value.length - limit }];
|
|
13407
|
-
}
|
|
13408
|
-
return head;
|
|
13409
|
-
}
|
|
13410
|
-
if (value && typeof value === "object") {
|
|
13411
|
-
if (seen.has(value))
|
|
13412
|
-
return value;
|
|
13413
|
-
seen.add(value);
|
|
13414
|
-
const next = {};
|
|
13415
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
13416
|
-
next[key] = truncateArrays(entry, limit, seen);
|
|
13417
|
-
}
|
|
13418
|
-
return next;
|
|
13419
|
-
}
|
|
13420
|
-
return value;
|
|
13421
|
-
};
|
|
13422
|
-
var applyRedaction = (value, options, seen) => {
|
|
13423
|
-
const mode = options.mode ?? "omit";
|
|
13424
|
-
const maskValue = options.maskValue ?? "[redacted]";
|
|
13425
|
-
const allow = new Set(options.metadataAllow ?? []);
|
|
13426
|
-
const deny = new Set(options.metadataDeny ?? DEFAULT_METADATA_DENY);
|
|
13427
|
-
const walk = (input) => {
|
|
13428
|
-
if (Array.isArray(input)) {
|
|
13429
|
-
return input.map((entry) => walk(entry));
|
|
13430
|
-
}
|
|
13431
|
-
if (input && typeof input === "object") {
|
|
13432
|
-
if (seen.has(input))
|
|
13433
|
-
return input;
|
|
13434
|
-
seen.add(input);
|
|
13435
|
-
const next = {};
|
|
13436
|
-
for (const [key, entry] of Object.entries(input)) {
|
|
13437
|
-
if (allow.has(key)) {
|
|
13438
|
-
next[key] = entry;
|
|
13439
|
-
continue;
|
|
13440
|
-
}
|
|
13441
|
-
if (deny.has(key)) {
|
|
13442
|
-
if (mode === "mask")
|
|
13443
|
-
next[key] = maskValue;
|
|
13444
|
-
continue;
|
|
13445
|
-
}
|
|
13446
|
-
next[key] = walk(entry);
|
|
13447
|
-
}
|
|
13448
|
-
return next;
|
|
13449
|
-
}
|
|
13450
|
-
return input;
|
|
13451
|
-
};
|
|
13452
|
-
return walk(value);
|
|
13453
|
-
};
|
|
13454
|
-
var redactMediaReport = (report, options = {}) => {
|
|
13455
|
-
const limit = options.truncateArraysAt ?? DEFAULT_TRUNCATE;
|
|
13456
|
-
const truncated = truncateArrays(report, limit, new WeakSet);
|
|
13457
|
-
return applyRedaction(truncated, options, new WeakSet);
|
|
13458
|
-
};
|
|
13459
|
-
var buildArtifactPair = (report, summary, markdown, options) => {
|
|
13460
|
-
const jsonValue = options.redact ? redactMediaReport(report, options.redact) : report;
|
|
13461
|
-
return {
|
|
13462
|
-
json: JSON.stringify(jsonValue, null, 2),
|
|
13463
|
-
jsonValue,
|
|
13464
|
-
markdown,
|
|
13465
|
-
summary
|
|
13466
|
-
};
|
|
13467
|
-
};
|
|
13468
|
-
var buildMediaQualityArtifact = (report, options = {}) => buildArtifactPair(report, summarizeMediaQualityReport(report), renderMediaQualityMarkdown(report, options), options);
|
|
13469
|
-
var buildMediaTransportArtifact = (report, options = {}) => buildArtifactPair(report, summarizeMediaTransportReport(report), renderMediaTransportMarkdown(report, options), options);
|
|
13470
|
-
var buildMediaProcessorGraphArtifact = (report, options = {}) => buildArtifactPair(report, summarizeMediaProcessorGraphReport(report), renderMediaProcessorGraphMarkdown(report, options), options);
|
|
13471
|
-
var writeMediaArtifact = async (input) => {
|
|
13472
|
-
await mkdir(input.dir, { recursive: true });
|
|
13473
|
-
const jsonPath = join(input.dir, `${input.slug}.json`);
|
|
13474
|
-
const markdownPath = join(input.dir, `${input.slug}.md`);
|
|
13475
|
-
await Promise.all([
|
|
13476
|
-
writeFile(jsonPath, input.json, "utf8"),
|
|
13477
|
-
writeFile(markdownPath, input.markdown, "utf8")
|
|
13478
|
-
]);
|
|
13479
|
-
return {
|
|
13480
|
-
jsonPath,
|
|
13481
|
-
markdownPath,
|
|
13482
|
-
summary: input.summary
|
|
13483
|
-
};
|
|
13484
|
-
};
|
|
13485
|
-
|
|
13486
|
-
// 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";
|
|
13487
12568
|
var escapeHtml16 = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
13488
12569
|
var statusRank2 = {
|
|
13489
12570
|
pass: 0,
|
|
@@ -13733,7 +12814,7 @@ var summarizeVoiceMediaPipelineReport = (report, options = {}) => {
|
|
|
13733
12814
|
const processorGraph = report.processorGraph ? summarizeMediaProcessorGraphReport(report.processorGraph) : undefined;
|
|
13734
12815
|
const transport = report.transport ? summarizeMediaTransportReport(report.transport) : undefined;
|
|
13735
12816
|
const interruptionIssueCodes = collectIssueCodes(report.interruption.issues);
|
|
13736
|
-
const
|
|
12817
|
+
const issueCodes = Array.from(new Set([
|
|
13737
12818
|
...collectIssueCodes(calibration.issues),
|
|
13738
12819
|
...quality.issueCodes,
|
|
13739
12820
|
...interruptionIssueCodes,
|
|
@@ -13768,7 +12849,7 @@ var summarizeVoiceMediaPipelineReport = (report, options = {}) => {
|
|
|
13768
12849
|
latenciesMs: report.interruption.latenciesMs,
|
|
13769
12850
|
status: report.interruption.status
|
|
13770
12851
|
},
|
|
13771
|
-
issueCodes
|
|
12852
|
+
issueCodes,
|
|
13772
12853
|
issues,
|
|
13773
12854
|
ok: report.ok,
|
|
13774
12855
|
processorGraph,
|
|
@@ -13791,6 +12872,12 @@ var summarizeVoiceMediaPipelineReport = (report, options = {}) => {
|
|
|
13791
12872
|
};
|
|
13792
12873
|
// src/telephonyMediaRoutes.ts
|
|
13793
12874
|
import { Elysia as Elysia14 } from "elysia";
|
|
12875
|
+
import {
|
|
12876
|
+
buildMediaTelephonyStreamLifecycleReport,
|
|
12877
|
+
createTelephonyMediaSerializer,
|
|
12878
|
+
parseTelephonyMediaFrame,
|
|
12879
|
+
serializeTelephonyMediaFrame
|
|
12880
|
+
} from "@absolutejs/media";
|
|
13794
12881
|
var demoPayload = Buffer.from(new Uint8Array([1, 2, 3, 4])).toString("base64");
|
|
13795
12882
|
var demoEnvelope = (carrier) => {
|
|
13796
12883
|
if (carrier === "twilio") {
|
|
@@ -14294,6 +13381,12 @@ var createVoiceBrowserCallProfileRoutes = (options = {}) => {
|
|
|
14294
13381
|
return routes;
|
|
14295
13382
|
};
|
|
14296
13383
|
// src/mediaPipelineSurfaces.ts
|
|
13384
|
+
import {
|
|
13385
|
+
buildMediaProcessorGraphArtifact,
|
|
13386
|
+
buildMediaQualityArtifact,
|
|
13387
|
+
buildMediaTransportArtifact,
|
|
13388
|
+
writeMediaArtifact
|
|
13389
|
+
} from "@absolutejs/media";
|
|
14297
13390
|
var calibrationIssues = (report) => report.calibration.issues.map((issue) => ({
|
|
14298
13391
|
code: issue.code,
|
|
14299
13392
|
message: issue.message,
|
|
@@ -19932,6 +19025,11 @@ import { Elysia as Elysia29 } from "elysia";
|
|
|
19932
19025
|
|
|
19933
19026
|
// src/operationsRecord.ts
|
|
19934
19027
|
import { Elysia as Elysia28 } from "elysia";
|
|
19028
|
+
import {
|
|
19029
|
+
summarizeMediaProcessorGraphReport as summarizeMediaProcessorGraphReport2,
|
|
19030
|
+
summarizeMediaQualityReport as summarizeMediaQualityReport2,
|
|
19031
|
+
summarizeMediaTransportReport as summarizeMediaTransportReport2
|
|
19032
|
+
} from "@absolutejs/media";
|
|
19935
19033
|
|
|
19936
19034
|
// src/sessionReplay.ts
|
|
19937
19035
|
import { Elysia as Elysia26 } from "elysia";
|
|
@@ -20219,7 +19317,7 @@ import { Elysia as Elysia27 } from "elysia";
|
|
|
20219
19317
|
var escapeHtml28 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
20220
19318
|
var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
20221
19319
|
var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
20222
|
-
var
|
|
19320
|
+
var firstString2 = (payload, keys) => {
|
|
20223
19321
|
for (const key of keys) {
|
|
20224
19322
|
const value = getString10(payload[key]);
|
|
20225
19323
|
if (value) {
|
|
@@ -20228,7 +19326,7 @@ var firstString3 = (payload, keys) => {
|
|
|
20228
19326
|
}
|
|
20229
19327
|
return;
|
|
20230
19328
|
};
|
|
20231
|
-
var
|
|
19329
|
+
var firstNumber = (payload, keys) => {
|
|
20232
19330
|
for (const key of keys) {
|
|
20233
19331
|
const value = getNumber7(payload[key]);
|
|
20234
19332
|
if (value !== undefined) {
|
|
@@ -20237,20 +19335,20 @@ var firstNumber2 = (payload, keys) => {
|
|
|
20237
19335
|
}
|
|
20238
19336
|
return;
|
|
20239
19337
|
};
|
|
20240
|
-
var eventProvider = (event) =>
|
|
19338
|
+
var eventProvider = (event) => firstString2(event.payload, [
|
|
20241
19339
|
"provider",
|
|
20242
19340
|
"selectedProvider",
|
|
20243
19341
|
"fallbackProvider",
|
|
20244
19342
|
"variantId"
|
|
20245
19343
|
]);
|
|
20246
|
-
var eventStatus = (event) =>
|
|
19344
|
+
var eventStatus = (event) => firstString2(event.payload, [
|
|
20247
19345
|
"providerStatus",
|
|
20248
19346
|
"status",
|
|
20249
19347
|
"disposition",
|
|
20250
19348
|
"type",
|
|
20251
19349
|
"reason"
|
|
20252
19350
|
]);
|
|
20253
|
-
var eventElapsedMs = (event) =>
|
|
19351
|
+
var eventElapsedMs = (event) => firstNumber(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
|
|
20254
19352
|
var resolveSessionHref2 = (value, sessionId) => {
|
|
20255
19353
|
if (value === false) {
|
|
20256
19354
|
return;
|
|
@@ -20592,6 +19690,29 @@ var toTelephonyMediaEvent = (event) => {
|
|
|
20592
19690
|
streamId
|
|
20593
19691
|
};
|
|
20594
19692
|
};
|
|
19693
|
+
var summarizeMediaPipelineForOperationsRecord = (report) => {
|
|
19694
|
+
const quality = summarizeMediaQualityReport2(report.quality);
|
|
19695
|
+
const transport = report.transport ? summarizeMediaTransportReport2(report.transport) : undefined;
|
|
19696
|
+
const processorGraph = report.processorGraph ? summarizeMediaProcessorGraphReport2(report.processorGraph) : undefined;
|
|
19697
|
+
const calibrationCodes = report.calibration.issues.map((issue) => issue.code);
|
|
19698
|
+
const interruptionCodes = report.interruption.issues.map((issue) => issue.code);
|
|
19699
|
+
const issueCodes = Array.from(new Set([
|
|
19700
|
+
...calibrationCodes,
|
|
19701
|
+
...quality.issueCodes,
|
|
19702
|
+
...interruptionCodes,
|
|
19703
|
+
...processorGraph?.issueCodes ?? []
|
|
19704
|
+
]));
|
|
19705
|
+
return {
|
|
19706
|
+
frames: report.frames,
|
|
19707
|
+
issueCodes,
|
|
19708
|
+
jitterMs: report.quality.jitterMs,
|
|
19709
|
+
processorGraphStatus: processorGraph?.status,
|
|
19710
|
+
qualityStatus: quality.status,
|
|
19711
|
+
status: report.status,
|
|
19712
|
+
surface: report.surface,
|
|
19713
|
+
transportStatus: transport?.status
|
|
19714
|
+
};
|
|
19715
|
+
};
|
|
20595
19716
|
var summarizeTelephonyMedia = (events) => {
|
|
20596
19717
|
const mediaEvents = events.map(toTelephonyMediaEvent).filter((event) => event !== undefined);
|
|
20597
19718
|
const eventNames = mediaEvents.map((event) => event.event.toLowerCase());
|
|
@@ -20758,6 +19879,7 @@ var buildVoiceOperationsRecord = async (options) => {
|
|
|
20758
19879
|
tasks,
|
|
20759
19880
|
total: tasks.length
|
|
20760
19881
|
} : undefined,
|
|
19882
|
+
mediaPipeline: options.mediaPipeline ? summarizeMediaPipelineForOperationsRecord(options.mediaPipeline) : undefined,
|
|
20761
19883
|
telephonyMedia: summarizeTelephonyMedia(traceEvents),
|
|
20762
19884
|
timeline: timelineSession?.events ?? [],
|
|
20763
19885
|
tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
|
|
@@ -20975,15 +20097,18 @@ var buildVoiceFailureReplay = (record, options = {}) => {
|
|
|
20975
20097
|
const recovery = step.status === "fallback" ? `recovered through ${step.fallbackProvider ?? step.selectedProvider ?? "fallback"}` : step.status === "degraded" ? `degraded to ${step.fallbackProvider ?? step.selectedProvider ?? "fallback"}` : "failed before recovery";
|
|
20976
20098
|
return `${provider} ${recovery}${step.reason ? `: ${step.reason}` : ""}`;
|
|
20977
20099
|
});
|
|
20100
|
+
const pipelineIssueCodes = record.mediaPipeline?.issueCodes ?? [];
|
|
20978
20101
|
const mediaIssues = [
|
|
20979
20102
|
...mediaSteps.map((step) => step.issue).filter((issue) => typeof issue === "string"),
|
|
20980
20103
|
record.telephonyMedia.total > 0 && record.telephonyMedia.inbound === 0 ? "Carrier stream has no inbound media evidence." : undefined,
|
|
20981
|
-
record.telephonyMedia.total > 0 && record.telephonyMedia.outbound === 0 ? "Carrier stream has no outbound assistant media/control evidence." : undefined
|
|
20104
|
+
record.telephonyMedia.total > 0 && record.telephonyMedia.outbound === 0 ? "Carrier stream has no outbound assistant media/control evidence." : undefined,
|
|
20105
|
+
...pipelineIssueCodes.map((code) => `Media pipeline issue: ${code}.`)
|
|
20982
20106
|
].filter((issue) => typeof issue === "string");
|
|
20983
20107
|
const userHeard = [
|
|
20984
20108
|
...new Set(record.transcript.flatMap((turn) => turn.assistantReplies))
|
|
20985
20109
|
];
|
|
20986
|
-
const
|
|
20110
|
+
const mediaPipelineStatus = record.mediaPipeline?.status;
|
|
20111
|
+
const status = record.providerDecisionSummary.errors > 0 || record.telephonyMedia.errors > 0 || mediaPipelineStatus === "fail" ? "failed" : record.providerDecisionSummary.degraded > 0 || mediaPipelineStatus === "warn" ? "degraded" : record.providerDecisionSummary.fallbacks > 0 || mediaIssues.length > 0 ? "recovered" : "healthy";
|
|
20987
20112
|
const turns = record.transcript.map((turn) => ({
|
|
20988
20113
|
...turn,
|
|
20989
20114
|
media: mediaSteps.filter((step) => record.traceEvents.some((event) => event.turnId === turn.id && event.type === "client.telephony_media" && event.at === step.at)),
|
|
@@ -20996,6 +20121,8 @@ var buildVoiceFailureReplay = (record, options = {}) => {
|
|
|
20996
20121
|
clears: record.telephonyMedia.clears,
|
|
20997
20122
|
errors: record.telephonyMedia.errors,
|
|
20998
20123
|
issues: mediaIssues,
|
|
20124
|
+
pipelineIssueCodes,
|
|
20125
|
+
pipelineStatus: record.mediaPipeline?.status,
|
|
20999
20126
|
steps: mediaSteps,
|
|
21000
20127
|
total: record.telephonyMedia.total
|
|
21001
20128
|
},
|
|
@@ -21052,6 +20179,8 @@ var renderVoiceFailureReplayMarkdown = (report) => {
|
|
|
21052
20179
|
}).join(`
|
|
21053
20180
|
`) : "- none recorded";
|
|
21054
20181
|
const issues = report.summary.issues.length ? report.summary.issues.map((issue) => `- ${issue}`).join(`
|
|
20182
|
+
`) : "- none";
|
|
20183
|
+
const pipelineCodes = report.media.pipelineIssueCodes.length ? report.media.pipelineIssueCodes.map((code) => `- ${code}`).join(`
|
|
21055
20184
|
`) : "- none";
|
|
21056
20185
|
return [
|
|
21057
20186
|
`# Voice Failure Replay: ${report.sessionId}`,
|
|
@@ -21069,6 +20198,9 @@ var renderVoiceFailureReplayMarkdown = (report) => {
|
|
|
21069
20198
|
"## Media Path",
|
|
21070
20199
|
mediaSteps,
|
|
21071
20200
|
"",
|
|
20201
|
+
`## Media Pipeline (status: ${report.media.pipelineStatus ?? "n/a"})`,
|
|
20202
|
+
pipelineCodes,
|
|
20203
|
+
"",
|
|
21072
20204
|
"## What The User Heard",
|
|
21073
20205
|
heard
|
|
21074
20206
|
].join(`
|
|
@@ -22349,8 +21481,8 @@ var createVoiceLiveOpsRoutes = (options = {}) => {
|
|
|
22349
21481
|
};
|
|
22350
21482
|
// src/deliveryRuntime.ts
|
|
22351
21483
|
import { Elysia as Elysia32 } from "elysia";
|
|
22352
|
-
import { mkdir
|
|
22353
|
-
import { dirname, join
|
|
21484
|
+
import { mkdir } from "fs/promises";
|
|
21485
|
+
import { dirname, join } from "path";
|
|
22354
21486
|
var escapeHtml32 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22355
21487
|
var renderSummaryCard = (label, summary) => {
|
|
22356
21488
|
if (!summary) {
|
|
@@ -22375,8 +21507,8 @@ var createDeliveryRuntimeFileSink = (input) => ({
|
|
|
22375
21507
|
deliver: async ({ events }) => {
|
|
22376
21508
|
const firstEvent = events[0];
|
|
22377
21509
|
const fileName = `${Date.now()}-${encodeURIComponent(firstEvent?.id ?? crypto.randomUUID())}.json`;
|
|
22378
|
-
const path =
|
|
22379
|
-
await
|
|
21510
|
+
const path = join(input.directory, input.kind, fileName);
|
|
21511
|
+
await mkdir(dirname(path), { recursive: true });
|
|
22380
21512
|
await Bun.write(path, JSON.stringify({
|
|
22381
21513
|
eventCount: events.length,
|
|
22382
21514
|
events,
|
|
@@ -23347,7 +22479,7 @@ var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
|
|
|
23347
22479
|
return assertion;
|
|
23348
22480
|
};
|
|
23349
22481
|
var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
|
|
23350
|
-
var
|
|
22482
|
+
var firstString3 = (source, keys) => {
|
|
23351
22483
|
for (const key of keys) {
|
|
23352
22484
|
const value = source[key];
|
|
23353
22485
|
if (typeof value === "string" && value.trim()) {
|
|
@@ -23358,7 +22490,7 @@ var firstString4 = (source, keys) => {
|
|
|
23358
22490
|
}
|
|
23359
22491
|
}
|
|
23360
22492
|
};
|
|
23361
|
-
var
|
|
22493
|
+
var firstNumber2 = (source, keys) => {
|
|
23362
22494
|
for (const key of keys) {
|
|
23363
22495
|
const value = source[key];
|
|
23364
22496
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -23723,8 +22855,8 @@ var verifyVoiceTelephonyWebhook = async (input) => {
|
|
|
23723
22855
|
var durationMsFromSeconds = (value) => typeof value === "number" ? value * 1000 : undefined;
|
|
23724
22856
|
var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
23725
22857
|
const payload = flattenPayload(input.body);
|
|
23726
|
-
const provider =
|
|
23727
|
-
const status =
|
|
22858
|
+
const provider = firstString3(payload, ["provider", "Provider"]) ?? input.provider;
|
|
22859
|
+
const status = firstString3(payload, [
|
|
23728
22860
|
"CallStatus",
|
|
23729
22861
|
"call_status",
|
|
23730
22862
|
"callStatus",
|
|
@@ -23734,7 +22866,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
23734
22866
|
"event_type",
|
|
23735
22867
|
"type"
|
|
23736
22868
|
]);
|
|
23737
|
-
const durationMs =
|
|
22869
|
+
const durationMs = firstNumber2(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber2(payload, [
|
|
23738
22870
|
"CallDuration",
|
|
23739
22871
|
"call_duration",
|
|
23740
22872
|
"callDuration",
|
|
@@ -23742,21 +22874,21 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
23742
22874
|
"dial_call_duration",
|
|
23743
22875
|
"duration"
|
|
23744
22876
|
]));
|
|
23745
|
-
const sipCode =
|
|
22877
|
+
const sipCode = firstNumber2(payload, [
|
|
23746
22878
|
"SipResponseCode",
|
|
23747
22879
|
"sip_response_code",
|
|
23748
22880
|
"sipCode",
|
|
23749
22881
|
"sip_code",
|
|
23750
22882
|
"hangupCauseCode"
|
|
23751
22883
|
]);
|
|
23752
|
-
const from =
|
|
23753
|
-
const to =
|
|
22884
|
+
const from = firstString3(payload, ["From", "from", "caller_id", "callerId"]);
|
|
22885
|
+
const to = firstString3(payload, [
|
|
23754
22886
|
"To",
|
|
23755
22887
|
"to",
|
|
23756
22888
|
"called_number",
|
|
23757
22889
|
"calledNumber"
|
|
23758
22890
|
]);
|
|
23759
|
-
const target =
|
|
22891
|
+
const target = firstString3(payload, [
|
|
23760
22892
|
"transferTarget",
|
|
23761
22893
|
"TransferTarget",
|
|
23762
22894
|
"target",
|
|
@@ -23764,7 +22896,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
23764
22896
|
"department"
|
|
23765
22897
|
]);
|
|
23766
22898
|
return {
|
|
23767
|
-
answeredBy:
|
|
22899
|
+
answeredBy: firstString3(payload, [
|
|
23768
22900
|
"AnsweredBy",
|
|
23769
22901
|
"answered_by",
|
|
23770
22902
|
"answeredBy",
|
|
@@ -23778,7 +22910,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
23778
22910
|
...payload
|
|
23779
22911
|
},
|
|
23780
22912
|
provider,
|
|
23781
|
-
reason:
|
|
22913
|
+
reason: firstString3(payload, [
|
|
23782
22914
|
"Reason",
|
|
23783
22915
|
"reason",
|
|
23784
22916
|
"HangupCause",
|
|
@@ -23794,7 +22926,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
23794
22926
|
var defaultSessionId = (input) => {
|
|
23795
22927
|
const payload = flattenPayload(input.body);
|
|
23796
22928
|
const metadataSessionId = input.event.metadata?.sessionId;
|
|
23797
|
-
return
|
|
22929
|
+
return firstString3(input.query, ["sessionId", "session_id"]) ?? firstString3(payload, [
|
|
23798
22930
|
"sessionId",
|
|
23799
22931
|
"session_id",
|
|
23800
22932
|
"SessionId",
|
|
@@ -23809,7 +22941,7 @@ var defaultSessionId = (input) => {
|
|
|
23809
22941
|
};
|
|
23810
22942
|
var defaultIdempotencyKey = (input) => {
|
|
23811
22943
|
const payload = flattenPayload(input.body);
|
|
23812
|
-
const eventId =
|
|
22944
|
+
const eventId = firstString3(payload, [
|
|
23813
22945
|
"id",
|
|
23814
22946
|
"event_id",
|
|
23815
22947
|
"eventId",
|
|
@@ -27577,7 +26709,7 @@ var percentile5 = (values, percentileValue) => {
|
|
|
27577
26709
|
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(percentileValue / 100 * sorted.length) - 1));
|
|
27578
26710
|
return Math.round(sorted[index] ?? 0);
|
|
27579
26711
|
};
|
|
27580
|
-
var
|
|
26712
|
+
var average = (values) => values.length === 0 ? undefined : Math.round(values.reduce((total, value) => total + value, 0) / values.length);
|
|
27581
26713
|
var resolveBudget = (stage, options) => ({
|
|
27582
26714
|
failAfterMs: options.budgets?.[stage]?.failAfterMs ?? options.failAfterMs ?? DEFAULT_FAIL_AFTER_MS,
|
|
27583
26715
|
warnAfterMs: options.budgets?.[stage]?.warnAfterMs ?? options.warnAfterMs ?? DEFAULT_WARN_AFTER_MS
|
|
@@ -27771,7 +26903,7 @@ var summarizeStage = (stage, measurements, options) => {
|
|
|
27771
26903
|
const failed = stageMeasurements.filter((measurement) => measurement.status === "fail").length;
|
|
27772
26904
|
const warnings = stageMeasurements.filter((measurement) => measurement.status === "warn").length;
|
|
27773
26905
|
return {
|
|
27774
|
-
averageMs:
|
|
26906
|
+
averageMs: average(latencies),
|
|
27775
26907
|
budget: resolveBudget(stage, options),
|
|
27776
26908
|
failed,
|
|
27777
26909
|
label: STAGE_LABELS[stage],
|
|
@@ -28582,13 +27714,15 @@ var buildVoiceIncidentTimelineReport = async (options) => {
|
|
|
28582
27714
|
opsRecovery,
|
|
28583
27715
|
monitorIssues,
|
|
28584
27716
|
operationsRecords,
|
|
28585
|
-
failureReplays
|
|
27717
|
+
failureReplays,
|
|
27718
|
+
extraEvents
|
|
28586
27719
|
] = await Promise.all([
|
|
28587
27720
|
resolveValue(options.operationalStatus),
|
|
28588
27721
|
resolveValue(options.opsRecovery),
|
|
28589
27722
|
resolveValue(options.monitorIssues),
|
|
28590
27723
|
resolveValue(options.operationsRecords),
|
|
28591
|
-
resolveValue(options.failureReplays)
|
|
27724
|
+
resolveValue(options.failureReplays),
|
|
27725
|
+
resolveValue(options.extraEvents)
|
|
28592
27726
|
]);
|
|
28593
27727
|
const events = [];
|
|
28594
27728
|
pushOperationalStatusEvents(events, operationalStatus, links);
|
|
@@ -28596,6 +27730,9 @@ var buildVoiceIncidentTimelineReport = async (options) => {
|
|
|
28596
27730
|
pushMonitorEvents(events, monitorIssues, links);
|
|
28597
27731
|
pushOperationsRecordEvents(events, operationsRecords, links);
|
|
28598
27732
|
pushFailureReplayEvents(events, failureReplays, links);
|
|
27733
|
+
if (extraEvents && extraEvents.length > 0) {
|
|
27734
|
+
events.push(...extraEvents);
|
|
27735
|
+
}
|
|
28599
27736
|
const filtered = events.filter((event) => withinWindow(event, now, options.windowMs)).sort((left, right) => right.at - left.at).slice(0, options.limit ?? 50);
|
|
28600
27737
|
const summary = {
|
|
28601
27738
|
critical: filtered.filter((event) => event.severity === "critical").length,
|
|
@@ -28881,8 +28018,8 @@ var createVoiceIncidentTimelineRoutes = (options) => {
|
|
|
28881
28018
|
import { Elysia as Elysia45 } from "elysia";
|
|
28882
28019
|
import { Database as Database4 } from "bun:sqlite";
|
|
28883
28020
|
import { createHash } from "crypto";
|
|
28884
|
-
import { mkdir as
|
|
28885
|
-
import { join as
|
|
28021
|
+
import { mkdir as mkdir2, readFile, stat, unlink } from "fs/promises";
|
|
28022
|
+
import { join as join2 } from "path";
|
|
28886
28023
|
var voiceObservabilityExportSchemaVersion = "1.0.0";
|
|
28887
28024
|
var voiceObservabilityExportSchemaId = "com.absolutejs.voice.observability-export";
|
|
28888
28025
|
var createVoiceObservabilityExportSchema = () => ({
|
|
@@ -29457,15 +28594,15 @@ var loadVoiceObservabilityExportReplaySource = async (source) => {
|
|
|
29457
28594
|
return source;
|
|
29458
28595
|
}
|
|
29459
28596
|
if (source.kind === "file") {
|
|
29460
|
-
const root =
|
|
29461
|
-
const receiptPath = source.receiptDirectory ?
|
|
28597
|
+
const root = join2(source.directory, source.runId);
|
|
28598
|
+
const receiptPath = source.receiptDirectory ? join2(source.receiptDirectory, `${encodeURIComponent(deliveryReceiptId(source.runId))}.json`) : undefined;
|
|
29462
28599
|
const deliveryReceipt = receiptPath ? await Bun.file(receiptPath).text().then(JSON.parse).catch(() => {
|
|
29463
28600
|
return;
|
|
29464
28601
|
}) : undefined;
|
|
29465
28602
|
return {
|
|
29466
|
-
artifactIndex: JSON.parse(await Bun.file(
|
|
28603
|
+
artifactIndex: JSON.parse(await Bun.file(join2(root, "artifact-index.json")).text()),
|
|
29467
28604
|
deliveryReceipt,
|
|
29468
|
-
manifest: JSON.parse(await Bun.file(
|
|
28605
|
+
manifest: JSON.parse(await Bun.file(join2(root, "manifest.json")).text())
|
|
29469
28606
|
};
|
|
29470
28607
|
}
|
|
29471
28608
|
if (source.kind === "s3") {
|
|
@@ -29675,7 +28812,7 @@ var createVoiceMemoryObservabilityExportDeliveryReceiptStore = () => {
|
|
|
29675
28812
|
};
|
|
29676
28813
|
};
|
|
29677
28814
|
var createVoiceFileObservabilityExportDeliveryReceiptStore = (options) => {
|
|
29678
|
-
const receiptPath = (id) =>
|
|
28815
|
+
const receiptPath = (id) => join2(options.directory, `${encodeURIComponent(id)}.json`);
|
|
29679
28816
|
return {
|
|
29680
28817
|
get: async (id) => {
|
|
29681
28818
|
const file = Bun.file(receiptPath(id));
|
|
@@ -29685,10 +28822,10 @@ var createVoiceFileObservabilityExportDeliveryReceiptStore = (options) => {
|
|
|
29685
28822
|
return JSON.parse(await file.text());
|
|
29686
28823
|
},
|
|
29687
28824
|
list: async () => {
|
|
29688
|
-
await
|
|
28825
|
+
await mkdir2(options.directory, { recursive: true });
|
|
29689
28826
|
const receipts = [];
|
|
29690
28827
|
for (const entry of await Array.fromAsync(new Bun.Glob("*.json").scan(options.directory))) {
|
|
29691
|
-
const file = Bun.file(
|
|
28828
|
+
const file = Bun.file(join2(options.directory, entry));
|
|
29692
28829
|
receipts.push(JSON.parse(await file.text()));
|
|
29693
28830
|
}
|
|
29694
28831
|
return receipts.sort((left, right) => right.checkedAt - left.checkedAt);
|
|
@@ -29699,7 +28836,7 @@ var createVoiceFileObservabilityExportDeliveryReceiptStore = (options) => {
|
|
|
29699
28836
|
});
|
|
29700
28837
|
},
|
|
29701
28838
|
set: async (id, receipt) => {
|
|
29702
|
-
await
|
|
28839
|
+
await mkdir2(options.directory, { recursive: true });
|
|
29703
28840
|
await Bun.write(receiptPath(id), `${JSON.stringify(receipt, null, 2)}
|
|
29704
28841
|
`);
|
|
29705
28842
|
}
|
|
@@ -30205,16 +29342,16 @@ var deliverVoiceObservabilityExport = async (options) => {
|
|
|
30205
29342
|
const label = destination.label ?? (destination.kind === "file" ? "File observability export" : destination.kind === "s3" ? "S3 observability export" : destination.kind === "sqlite" ? "SQLite observability export" : destination.kind === "postgres" ? "Postgres observability export" : "Webhook observability export");
|
|
30206
29343
|
try {
|
|
30207
29344
|
if (destination.kind === "file") {
|
|
30208
|
-
const target =
|
|
30209
|
-
await
|
|
30210
|
-
await Bun.write(
|
|
30211
|
-
await Bun.write(
|
|
29345
|
+
const target = join2(destination.directory, runId);
|
|
29346
|
+
await mkdir2(join2(target, "artifacts"), { recursive: true });
|
|
29347
|
+
await Bun.write(join2(target, "manifest.json"), manifest);
|
|
29348
|
+
await Bun.write(join2(target, "artifact-index.json"), index);
|
|
30212
29349
|
if (destination.includeArtifacts !== false) {
|
|
30213
29350
|
for (const artifact of options.report.artifacts) {
|
|
30214
29351
|
if (!artifact.path) {
|
|
30215
29352
|
continue;
|
|
30216
29353
|
}
|
|
30217
|
-
await Bun.write(
|
|
29354
|
+
await Bun.write(join2(target, "artifacts", safeArtifactFileName(artifact)), await readFile(stripArtifactPathAnchor(artifact.path)));
|
|
30218
29355
|
}
|
|
30219
29356
|
}
|
|
30220
29357
|
return {
|
|
@@ -33446,7 +32583,7 @@ var createVoiceDataControlRoutes = (options) => {
|
|
|
33446
32583
|
};
|
|
33447
32584
|
// src/evalRoutes.ts
|
|
33448
32585
|
import { Elysia as Elysia49 } from "elysia";
|
|
33449
|
-
import { mkdir as
|
|
32586
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
33450
32587
|
import { dirname as dirname2 } from "path";
|
|
33451
32588
|
var escapeHtml46 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
33452
32589
|
var rate4 = (count, total) => count / Math.max(1, total);
|
|
@@ -33758,7 +32895,7 @@ var createVoiceFileEvalBaselineStore = (filePath) => ({
|
|
|
33758
32895
|
return text.trim() ? JSON.parse(text) : undefined;
|
|
33759
32896
|
},
|
|
33760
32897
|
set: async (report) => {
|
|
33761
|
-
await
|
|
32898
|
+
await mkdir3(dirname2(filePath), { recursive: true });
|
|
33762
32899
|
await Bun.write(filePath, JSON.stringify(report, null, 2));
|
|
33763
32900
|
}
|
|
33764
32901
|
});
|
|
@@ -35649,7 +34786,7 @@ import { Elysia as Elysia53 } from "elysia";
|
|
|
35649
34786
|
var DEFAULT_WARN_AFTER_MS2 = 1800;
|
|
35650
34787
|
var DEFAULT_FAIL_AFTER_MS2 = 3200;
|
|
35651
34788
|
var escapeHtml50 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
35652
|
-
var
|
|
34789
|
+
var firstNumber3 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
35653
34790
|
var getString19 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
35654
34791
|
var createTraceStageIndex = (events) => {
|
|
35655
34792
|
const index = new Map;
|
|
@@ -35673,8 +34810,8 @@ var createTraceStageIndex = (events) => {
|
|
|
35673
34810
|
};
|
|
35674
34811
|
var summarizeTurn = (sessionId, turn, options) => {
|
|
35675
34812
|
const traceStages = options.stageIndex?.get(`${sessionId}:${turn.id}`);
|
|
35676
|
-
const firstTranscriptAt = traceStages?.get("speech_detected") ??
|
|
35677
|
-
const finalTranscriptAt = traceStages?.get("final_transcript") ??
|
|
34813
|
+
const firstTranscriptAt = traceStages?.get("speech_detected") ?? firstNumber3(turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
|
|
34814
|
+
const finalTranscriptAt = traceStages?.get("final_transcript") ?? firstNumber3(turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
|
|
35678
34815
|
const committedAt = traceStages?.get("turn_committed") ?? turn.committedAt;
|
|
35679
34816
|
const assistantTextStartedAt = traceStages?.get("assistant_text_started") ?? (turn.assistantText ? committedAt : undefined);
|
|
35680
34817
|
const ttsSendStartedAt = traceStages?.get("tts_send_started");
|
|
@@ -36589,21 +35726,21 @@ var createVoicePhoneAgent = (options) => {
|
|
|
36589
35726
|
};
|
|
36590
35727
|
// src/fileStore.ts
|
|
36591
35728
|
import {
|
|
36592
|
-
mkdir as
|
|
35729
|
+
mkdir as mkdir4,
|
|
36593
35730
|
readFile as readFile2,
|
|
36594
35731
|
readdir,
|
|
36595
35732
|
rename,
|
|
36596
35733
|
rm,
|
|
36597
35734
|
stat as stat2,
|
|
36598
|
-
writeFile
|
|
35735
|
+
writeFile
|
|
36599
35736
|
} from "fs/promises";
|
|
36600
|
-
import { join as
|
|
35737
|
+
import { join as join3 } from "path";
|
|
36601
35738
|
var listJsonFiles = async (directory) => {
|
|
36602
35739
|
try {
|
|
36603
35740
|
const entries = await readdir(directory, {
|
|
36604
35741
|
withFileTypes: true
|
|
36605
35742
|
});
|
|
36606
|
-
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) =>
|
|
35743
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => join3(directory, entry.name));
|
|
36607
35744
|
} catch (error) {
|
|
36608
35745
|
if (error.code === "ENOENT") {
|
|
36609
35746
|
return [];
|
|
@@ -36621,7 +35758,7 @@ var listRecentJsonFiles = async (directory, limit) => {
|
|
|
36621
35758
|
}
|
|
36622
35759
|
return (await rebuildRecentJsonFileIndex(directory)).slice(0, limit).map((entry) => entry.path);
|
|
36623
35760
|
};
|
|
36624
|
-
var recentJsonFileIndexPath = (directory) =>
|
|
35761
|
+
var recentJsonFileIndexPath = (directory) => join3(directory, ".recent-index");
|
|
36625
35762
|
var sortRecentJsonFileIndexEntries = (entries) => entries.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
36626
35763
|
var readRecentJsonFileIndex = async (directory) => {
|
|
36627
35764
|
try {
|
|
@@ -36635,12 +35772,12 @@ var readRecentJsonFileIndex = async (directory) => {
|
|
|
36635
35772
|
}
|
|
36636
35773
|
};
|
|
36637
35774
|
var writeRecentJsonFileIndex = async (directory, files) => {
|
|
36638
|
-
await
|
|
35775
|
+
await mkdir4(directory, {
|
|
36639
35776
|
recursive: true
|
|
36640
35777
|
});
|
|
36641
35778
|
const path = recentJsonFileIndexPath(directory);
|
|
36642
35779
|
const tempPath = `${path}.${crypto.randomUUID()}.tmp`;
|
|
36643
|
-
await
|
|
35780
|
+
await writeFile(tempPath, JSON.stringify({
|
|
36644
35781
|
files: sortRecentJsonFileIndexEntries(files).slice(0, 5000),
|
|
36645
35782
|
version: 1
|
|
36646
35783
|
}));
|
|
@@ -36716,15 +35853,15 @@ var omitReadWindow = (filter) => {
|
|
|
36716
35853
|
return next;
|
|
36717
35854
|
};
|
|
36718
35855
|
var encodeStoreId = (id) => `${encodeURIComponent(id)}.json`;
|
|
36719
|
-
var resolveFilePath = (directory, id) =>
|
|
35856
|
+
var resolveFilePath = (directory, id) => join3(directory, encodeStoreId(id));
|
|
36720
35857
|
var createMemoryStoreId = (input) => `${input.assistantId}:${input.namespace}:${input.key}`;
|
|
36721
35858
|
var readJsonFile = async (path) => JSON.parse(await readFile2(path, "utf8"));
|
|
36722
35859
|
var writeJsonFile = async (path, value, options) => {
|
|
36723
|
-
await
|
|
35860
|
+
await mkdir4(options.directory, {
|
|
36724
35861
|
recursive: true
|
|
36725
35862
|
});
|
|
36726
35863
|
const tempPath = `${path}.${crypto.randomUUID()}.tmp`;
|
|
36727
|
-
await
|
|
35864
|
+
await writeFile(tempPath, JSON.stringify(value, null, options.pretty === false ? undefined : 2));
|
|
36728
35865
|
await rename(tempPath, path);
|
|
36729
35866
|
};
|
|
36730
35867
|
var createVoiceFileSessionStore = (options) => {
|
|
@@ -37098,51 +36235,51 @@ var createVoiceFileIncidentBundleStore = (options) => {
|
|
|
37098
36235
|
var createVoiceFileRuntimeStorage = (options) => ({
|
|
37099
36236
|
audit: createVoiceFileAuditEventStore({
|
|
37100
36237
|
...options,
|
|
37101
|
-
directory:
|
|
36238
|
+
directory: join3(options.directory, "audit")
|
|
37102
36239
|
}),
|
|
37103
36240
|
auditDeliveries: createVoiceFileAuditSinkDeliveryStore({
|
|
37104
36241
|
...options,
|
|
37105
|
-
directory:
|
|
36242
|
+
directory: join3(options.directory, "audit-deliveries")
|
|
37106
36243
|
}),
|
|
37107
36244
|
campaigns: createVoiceFileCampaignStore({
|
|
37108
36245
|
...options,
|
|
37109
|
-
directory:
|
|
36246
|
+
directory: join3(options.directory, "campaigns")
|
|
37110
36247
|
}),
|
|
37111
36248
|
events: createVoiceFileIntegrationEventStore({
|
|
37112
36249
|
...options,
|
|
37113
|
-
directory:
|
|
36250
|
+
directory: join3(options.directory, "events")
|
|
37114
36251
|
}),
|
|
37115
36252
|
externalObjects: createVoiceFileExternalObjectMapStore({
|
|
37116
36253
|
...options,
|
|
37117
|
-
directory:
|
|
36254
|
+
directory: join3(options.directory, "external-objects")
|
|
37118
36255
|
}),
|
|
37119
36256
|
incidentBundles: createVoiceFileIncidentBundleStore({
|
|
37120
36257
|
...options,
|
|
37121
|
-
directory:
|
|
36258
|
+
directory: join3(options.directory, "incident-bundles")
|
|
37122
36259
|
}),
|
|
37123
36260
|
memories: createVoiceFileAssistantMemoryStore({
|
|
37124
36261
|
...options,
|
|
37125
|
-
directory:
|
|
36262
|
+
directory: join3(options.directory, "memories")
|
|
37126
36263
|
}),
|
|
37127
36264
|
reviews: createVoiceFileReviewStore({
|
|
37128
36265
|
...options,
|
|
37129
|
-
directory:
|
|
36266
|
+
directory: join3(options.directory, "reviews")
|
|
37130
36267
|
}),
|
|
37131
36268
|
session: createVoiceFileSessionStore({
|
|
37132
36269
|
...options,
|
|
37133
|
-
directory:
|
|
36270
|
+
directory: join3(options.directory, "sessions")
|
|
37134
36271
|
}),
|
|
37135
36272
|
tasks: createVoiceFileTaskStore({
|
|
37136
36273
|
...options,
|
|
37137
|
-
directory:
|
|
36274
|
+
directory: join3(options.directory, "tasks")
|
|
37138
36275
|
}),
|
|
37139
36276
|
traceDeliveries: createVoiceFileTraceSinkDeliveryStore({
|
|
37140
36277
|
...options,
|
|
37141
|
-
directory:
|
|
36278
|
+
directory: join3(options.directory, "trace-deliveries")
|
|
37142
36279
|
}),
|
|
37143
36280
|
traces: createVoiceFileTraceEventStore({
|
|
37144
36281
|
...options,
|
|
37145
|
-
directory:
|
|
36282
|
+
directory: join3(options.directory, "traces")
|
|
37146
36283
|
})
|
|
37147
36284
|
});
|
|
37148
36285
|
var createStoredVoiceCallReviewArtifact = (id, artifact) => withVoiceCallReviewId(id, artifact);
|
|
@@ -39541,7 +38678,7 @@ var statusRank8 = {
|
|
|
39541
38678
|
warn: 1,
|
|
39542
38679
|
fail: 2
|
|
39543
38680
|
};
|
|
39544
|
-
var statusExceeds2 = (actual,
|
|
38681
|
+
var statusExceeds2 = (actual, max) => statusRank8[actual] > statusRank8[max];
|
|
39545
38682
|
var buildVoiceProviderContractMatrix = (input) => {
|
|
39546
38683
|
const rows = input.contracts.map((contract) => {
|
|
39547
38684
|
const configured = contract.configured !== false;
|
|
@@ -42981,8 +42118,8 @@ var shapeTelephonyAssistantText = (text, options = {}) => {
|
|
|
42981
42118
|
};
|
|
42982
42119
|
// src/proofPack.ts
|
|
42983
42120
|
import { Elysia as Elysia69 } from "elysia";
|
|
42984
|
-
import { mkdir as
|
|
42985
|
-
import { dirname as dirname3, join as
|
|
42121
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
42122
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
42986
42123
|
var toGeneratedAt = (value) => value === undefined ? new Date().toISOString() : typeof value === "number" ? new Date(value).toISOString() : value;
|
|
42987
42124
|
var getProofPackMetadata = (proofPack) => {
|
|
42988
42125
|
const built = buildVoiceProofPack(proofPack);
|
|
@@ -43511,10 +42648,10 @@ var writeVoiceProofPack = async (input, options = { outputDir: ".voice-runtime/p
|
|
|
43511
42648
|
...input,
|
|
43512
42649
|
outputDir: options.outputDir
|
|
43513
42650
|
});
|
|
43514
|
-
const jsonPath =
|
|
43515
|
-
const markdownPath =
|
|
43516
|
-
await
|
|
43517
|
-
await
|
|
42651
|
+
const jsonPath = join4(options.outputDir, options.jsonFileName ?? "latest.json");
|
|
42652
|
+
const markdownPath = join4(options.outputDir, options.markdownFileName ?? "latest.md");
|
|
42653
|
+
await mkdir5(dirname3(jsonPath), { recursive: true });
|
|
42654
|
+
await mkdir5(dirname3(markdownPath), { recursive: true });
|
|
43518
42655
|
await Promise.all([
|
|
43519
42656
|
Bun.write(jsonPath, JSON.stringify(proofPack, null, 2)),
|
|
43520
42657
|
Bun.write(markdownPath, renderVoiceProofPackMarkdown(proofPack))
|