@absolutejs/voice 0.0.22-beta.654 → 0.0.22-beta.656
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 +67 -28
- package/dist/client/connection.d.ts +3 -0
- package/dist/client/htmxBootstrap.js +67 -28
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +69 -29
- package/dist/embed/index.js +67 -28
- package/dist/embed/voice-widget.js +8 -8
- package/dist/react/index.js +67 -28
- package/dist/svelte/index.js +67 -28
- package/dist/testing/index.js +67 -28
- package/dist/vue/index.js +67 -28
- package/package.json +1 -1
package/dist/embed/index.js
CHANGED
|
@@ -507,6 +507,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
507
507
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
508
508
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
509
509
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
510
|
+
|
|
511
|
+
class VoiceReconnectRejectedError extends Error {
|
|
512
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
513
|
+
super(message);
|
|
514
|
+
this.name = "VoiceReconnectRejectedError";
|
|
515
|
+
}
|
|
516
|
+
}
|
|
510
517
|
var noop = () => {};
|
|
511
518
|
var noopAsync = () => Promise.resolve();
|
|
512
519
|
var noopUnsubscribe = () => noop;
|
|
@@ -586,6 +593,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
586
593
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
587
594
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
588
595
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
596
|
+
let lifecycleGeneration = 0;
|
|
597
|
+
let disposed = false;
|
|
589
598
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
590
599
|
const state = {
|
|
591
600
|
isConnected: false,
|
|
@@ -601,6 +610,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
601
610
|
const emitConnection = (reconnect) => {
|
|
602
611
|
listeners.forEach((listener) => listener(reconnect));
|
|
603
612
|
};
|
|
613
|
+
const emitTerminalFailure = (message) => {
|
|
614
|
+
listeners.forEach((listener) => listener({
|
|
615
|
+
message,
|
|
616
|
+
recoverable: false,
|
|
617
|
+
type: "error"
|
|
618
|
+
}));
|
|
619
|
+
emitConnection({
|
|
620
|
+
reconnect: {
|
|
621
|
+
attempts: state.reconnectAttempts,
|
|
622
|
+
maxAttempts: maxReconnectAttempts,
|
|
623
|
+
status: "exhausted"
|
|
624
|
+
},
|
|
625
|
+
type: "connection"
|
|
626
|
+
});
|
|
627
|
+
};
|
|
604
628
|
const clearTimers = () => {
|
|
605
629
|
if (state.pingInterval) {
|
|
606
630
|
clearInterval(state.pingInterval);
|
|
@@ -634,6 +658,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
634
658
|
pendingCallControls.clear();
|
|
635
659
|
};
|
|
636
660
|
const scheduleReconnect = () => {
|
|
661
|
+
if (disposed)
|
|
662
|
+
return;
|
|
663
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
637
664
|
state.reconnectAttempts += 1;
|
|
638
665
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
639
666
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -648,15 +675,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
648
675
|
type: "connection"
|
|
649
676
|
});
|
|
650
677
|
state.reconnectTimeout = setTimeout(async () => {
|
|
678
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
679
|
+
return;
|
|
651
680
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
652
|
-
|
|
653
|
-
reconnect: {
|
|
654
|
-
attempts: state.reconnectAttempts,
|
|
655
|
-
maxAttempts: maxReconnectAttempts,
|
|
656
|
-
status: "exhausted"
|
|
657
|
-
},
|
|
658
|
-
type: "connection"
|
|
659
|
-
});
|
|
681
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
660
682
|
return;
|
|
661
683
|
}
|
|
662
684
|
try {
|
|
@@ -666,25 +688,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
666
688
|
scenarioId: state.scenarioId,
|
|
667
689
|
sessionId: state.sessionId
|
|
668
690
|
});
|
|
669
|
-
} catch {
|
|
691
|
+
} catch (error) {
|
|
692
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
693
|
+
return;
|
|
694
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
695
|
+
emitTerminalFailure(error.message);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
670
698
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
671
699
|
scheduleReconnect();
|
|
672
700
|
} else {
|
|
673
|
-
|
|
674
|
-
reconnect: {
|
|
675
|
-
attempts: state.reconnectAttempts,
|
|
676
|
-
maxAttempts: maxReconnectAttempts,
|
|
677
|
-
status: "exhausted"
|
|
678
|
-
},
|
|
679
|
-
type: "connection"
|
|
680
|
-
});
|
|
701
|
+
emitTerminalFailure("Voice authorization could not be renewed for reconnect.");
|
|
681
702
|
}
|
|
682
703
|
return;
|
|
683
704
|
}
|
|
705
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
706
|
+
return;
|
|
684
707
|
connect();
|
|
685
708
|
}, delayMs);
|
|
686
709
|
};
|
|
687
710
|
const connect = () => {
|
|
711
|
+
if (disposed)
|
|
712
|
+
return;
|
|
688
713
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
689
714
|
ws.binaryType = "arraybuffer";
|
|
690
715
|
ws.onopen = () => {
|
|
@@ -755,6 +780,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
755
780
|
state.ws = null;
|
|
756
781
|
state.isConnected = false;
|
|
757
782
|
clearTimers();
|
|
783
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
758
784
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
759
785
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
760
786
|
if (authorizationFailure) {
|
|
@@ -767,15 +793,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
767
793
|
if (reconnectable) {
|
|
768
794
|
scheduleReconnect();
|
|
769
795
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
796
|
+
if (authorizationFailure) {
|
|
797
|
+
emitConnection({
|
|
798
|
+
reconnect: {
|
|
799
|
+
attempts: state.reconnectAttempts,
|
|
800
|
+
lastDisconnectAt: Date.now(),
|
|
801
|
+
maxAttempts: maxReconnectAttempts,
|
|
802
|
+
status: "exhausted"
|
|
803
|
+
},
|
|
804
|
+
type: "connection"
|
|
805
|
+
});
|
|
806
|
+
} else {
|
|
807
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
808
|
+
}
|
|
779
809
|
}
|
|
780
810
|
};
|
|
781
811
|
state.ws = ws;
|
|
@@ -816,6 +846,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
816
846
|
send({ type: "end_turn" });
|
|
817
847
|
};
|
|
818
848
|
const callControl = (message) => {
|
|
849
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
850
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
851
|
+
}
|
|
819
852
|
const requestId = crypto.randomUUID();
|
|
820
853
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
821
854
|
const timeout = setTimeout(() => {
|
|
@@ -823,16 +856,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
823
856
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
824
857
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
825
858
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
826
|
-
send({
|
|
859
|
+
state.ws.send(JSON.stringify({
|
|
827
860
|
...message,
|
|
828
861
|
requestId,
|
|
829
862
|
type: "call_control"
|
|
830
|
-
});
|
|
863
|
+
}));
|
|
831
864
|
return promise;
|
|
832
865
|
};
|
|
833
866
|
const close = (reason = "client-close") => {
|
|
867
|
+
disposed = true;
|
|
868
|
+
lifecycleGeneration += 1;
|
|
834
869
|
clearTimers();
|
|
835
870
|
rejectPendingCallControls("Voice connection closed");
|
|
871
|
+
state.pendingMessages.length = 0;
|
|
836
872
|
if (state.ws) {
|
|
837
873
|
const ws = state.ws;
|
|
838
874
|
state.ws = null;
|
|
@@ -845,8 +881,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
845
881
|
listeners.clear();
|
|
846
882
|
};
|
|
847
883
|
const disconnect = () => {
|
|
884
|
+
disposed = true;
|
|
885
|
+
lifecycleGeneration += 1;
|
|
848
886
|
clearTimers();
|
|
849
887
|
rejectPendingCallControls("Voice connection disconnected");
|
|
888
|
+
state.pendingMessages.length = 0;
|
|
850
889
|
if (state.ws) {
|
|
851
890
|
const ws = state.ws;
|
|
852
891
|
state.ws = null;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
(()=>{var{defineProperty:x,getOwnPropertyNames:U0,getOwnPropertyDescriptor:L0}=Object,G0=Object.prototype.hasOwnProperty;function W0(g){return this[g]}var D0=(g)=>{var A=(f??=new WeakMap).get(g),C;if(A)return A;if(A=x({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of U0(g))if(!G0.call(A,V))x(A,V,{get:W0.bind(g,V),enumerable:!(C=L0(g,V))||C.enumerable})}return f.set(g,A),A},f;var S0=(g)=>g;function y0(g,A){this[g]=S0.bind(null,A)}var X0=(g,A)=>{for(var C in A)x(g,C,{get:A[C],enumerable:!0,configurable:!0,set:y0.bind(A,C)})};var t0={};X0(t0,{mount:()=>T0,default:()=>e0,VOICE_EMBED_VERSION:()=>_0});var O0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},h0=(g,A,C,V)=>{let c=A??g.getAttribute("hx-get")??"";if(!c)return"";let T=new URL(c,window.location.origin);if(V)T.searchParams.set(C,V);else T.searchParams.delete(C);return`${T.pathname}${T.search}${T.hash}`},d=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=O0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",c=A.sessionQueryParam??"sessionId",T=()=>{let I=window,G=h0(C,A.route,c,g.sessionId);if(G)C.setAttribute("hx-get",G);I.htmx?.process?.(C),I.htmx?.trigger?.(C,V)},U=g.subscribe(T);return T(),()=>{U()}};var J0=(g)=>Math.max(-1,Math.min(1,g)),Y0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=J0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},Z0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let c of C){let T=c/32768;V+=T*T}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},w0=(g,A,C)=>{if(A===C)return g;let V=A/C,c=Math.round(g.length/V),T=new Float32Array(c),U=0,I=0;while(U<T.length){let G=Math.round((U+1)*V),W=0,_=0;for(let L=I;L<G&&L<g.length;L+=1)W+=g[L]??0,_+=1;T[U]=_>0?W/_:0,U+=1,I=G}return T},l=(g)=>{let A=null,C=null,V=null,c=null,T=async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let I=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!I)throw Error("Browser microphone capture requires AudioContext support.");try{if(c=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new I,A.state==="suspended")await A.resume();C=A.createMediaStreamSource(c),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(G)=>{let W=G.inputBuffer.getChannelData(0),_=w0(W,A?.sampleRate??48000,g.sampleRateHz??16000),L=Y0(_);g.onLevel?.(Z0(L)),g.onAudio(L)},C.connect(V),V.connect(A.destination)}catch(G){throw U(),G}},U=()=>{V?.disconnect(),C?.disconnect(),c?.getTracks().forEach((I)=>I.stop()),A?.close(),g.onLevel?.(0),A=null,c=null,V=null,C=null};return{start:T,stop:U}};var k=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return k(A.error);if("cause"in A)return k(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},v=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:k(g.message),recoverable:g.recoverable,type:"error"};case"call_control_ack":return null;case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var Tg=Math.PI*2;var P=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var Q0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,M=(g)=>g.length===0?void 0:Math.max(...g);var X=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},B=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},J=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},F=(g)=>String(g.id??J(g,"ssrc")??X(g,"ssrc")??J(g,"trackIdentifier")??J(g,"mid")??"unknown"),i=(g)=>g===void 0?void 0:g*1000;var P0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var n=(g={})=>{let A=g.stats??[],C=[],V=A.filter((R)=>R.type==="inbound-rtp"&&J(R,"kind")!=="video"),c=A.filter((R)=>R.type==="outbound-rtp"&&J(R,"kind")!=="video"),T=A.filter((R)=>R.type==="candidate-pair"),U=A.filter((R)=>(R.type==="track"||R.type==="media-source")&&J(R,"kind")==="audio"),I=T.filter((R)=>B(R,"selected")===!0||B(R,"nominated")===!0||J(R,"state")==="succeeded").length,G=U.filter((R)=>J(R,"readyState")!=="ended"&&J(R,"trackState")!=="ended"&&B(R,"ended")!==!0).length,W=U.filter((R)=>J(R,"readyState")==="ended"||J(R,"trackState")==="ended"||B(R,"ended")===!0).length,_=V.reduce((R,O)=>R+(X(O,"packetsReceived")??0),0),L=c.reduce((R,O)=>R+(X(O,"packetsSent")??0),0),$=[...V,...c].reduce((R,O)=>R+Math.max(0,X(O,"packetsLost")??0),0),E=_+$,w=E===0?0:$/E,D=V.reduce((R,O)=>R+(X(O,"bytesReceived")??0),0),Y=c.reduce((R,O)=>R+(X(O,"bytesSent")??0),0),Z=M(T.map((R)=>i(X(R,"currentRoundTripTime")??X(R,"roundTripTime"))).filter((R)=>R!==void 0)),h=M([...V,...c].map((R)=>i(X(R,"jitter"))).filter((R)=>R!==void 0)),z=M(V.map((R)=>{let O=X(R,"jitterBufferDelay"),b=X(R,"jitterBufferEmittedCount");return O!==void 0&&b!==void 0&&b>0?O/b*1000:void 0}).filter((R)=>R!==void 0)),q=U.map((R)=>X(R,"audioLevel")).filter((R)=>R!==void 0);if(g.requireConnectedCandidatePair&&T.length>0&&I===0)P(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&G===0)P(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&w>g.maxPacketLossRatio)P(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(w)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&Z!==void 0&&Z>g.maxRoundTripTimeMs)P(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(Z)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&h!==void 0&&h>g.maxJitterMs)P(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(h)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:I,audioLevelAverage:Q0(q),bytesReceived:D,bytesSent:Y,checkedAt:Date.now(),endedAudioTracks:W,inboundPackets:_,issues:C,jitterBufferDelayMs:z,jitterMs:h,liveAudioTracks:G,outboundPackets:L,packetLossRatio:w,packetsLost:$,roundTripTimeMs:Z,status:C.some((R)=>R.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},o=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(P0)};var m=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],c=new Map(C.map(($)=>[F($),$])),U=A.filter(($)=>($.type==="inbound-rtp"||$.type==="outbound-rtp")&&J($,"kind")!=="video"&&J($,"mediaType")!=="video").map(($)=>{let E=$.type==="outbound-rtp"?"outbound":"inbound",w=E==="outbound"?"packetsSent":"packetsReceived",D=E==="outbound"?"bytesSent":"bytesReceived",Y=c.get(F($)),Z=X($,w),h=Y?X(Y,w):void 0,z=X($,D),q=Y?X(Y,D):void 0,R=$.timestamp!==void 0&&Y?.timestamp!==void 0?$.timestamp-Y.timestamp:void 0;return{bytesDelta:z!==void 0&&q!==void 0?z-q:void 0,currentPackets:Z,direction:E,id:F($),packetDelta:Z!==void 0&&h!==void 0?Z-h:void 0,previousPackets:h,timeDeltaMs:R}}),I=U.filter(($)=>$.direction==="inbound"),G=U.filter(($)=>$.direction==="outbound"),W=M(U.map(($)=>$.timeDeltaMs).filter(($)=>$!==void 0)),_=I.filter(($)=>g.maxInboundPacketStallMs!==void 0&&$.timeDeltaMs!==void 0&&$.timeDeltaMs>=g.maxInboundPacketStallMs&&$.packetDelta!==void 0&&$.packetDelta<=0).length,L=G.filter(($)=>g.maxOutboundPacketStallMs!==void 0&&$.timeDeltaMs!==void 0&&$.timeDeltaMs>=g.maxOutboundPacketStallMs&&$.packetDelta!==void 0&&$.packetDelta<=0).length;if(g.requireInboundAudio&&I.length===0)P(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&G.length===0)P(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&W!==void 0&&W>g.maxGapMs)P(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(W)}ms above ${String(g.maxGapMs)}ms.`);if(_>0)P(V,"error","media.webrtc_inbound_stalled",`${String(_)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(L>0)P(V,"error","media.webrtc_outbound_stalled",`${String(L)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:I.length,issues:V,maxObservedGapMs:W,outboundAudioStreams:G.length,stalledInboundStreams:_,stalledOutboundStreams:L,status:V.some(($)=>$.severity==="error")?"fail":V.length>0?"warn":"pass",streams:U,totalStats:A.length}};var K0="/api/voice/browser-media",E0=5000,N0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,z0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??K0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(g)=>{let A=null,C=[],V=async()=>{let U=await N0(g);if(!U)return;let I=await o({peerConnection:U}),G=n({...g,stats:I}),W=g.continuity===!1?void 0:m({...g.continuity,previousStats:C,stats:I}),_={at:Date.now(),continuity:W,report:G,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=I,g.onReport?.(_),await z0(_,g),_},c=()=>{V().catch((U)=>{g.onError?.(U)})},T=()=>{if(A)clearInterval(A),A=null};return{close:T,reportOnce:V,stop:T,start:()=>{if(A)return;c(),A=setInterval(c,g.intervalMs??E0)}}};var q0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var N=()=>{},b0=()=>Promise.resolve(),B0=()=>N,M0={callControl:b0,close:N,disconnect:N,endTurn:N,send:N,sendAudio:N,simulateDisconnect:N,subscribe:B0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},j0=()=>crypto.randomUUID(),x0=(g,A,C,V)=>{let{hostname:c,port:T,protocol:U}=window.location,I=U==="https:"?"wss:":"ws:",G=T?`:${T}`:"",W=new URL(`${I}//${c}${G}${g}`);if(W.searchParams.set("sessionId",A),C)W.searchParams.set("scenarioId",C);for(let[_,L]of Object.entries(V))if(_!=="sessionId"&&_!=="scenarioId"&&L)W.searchParams.set(_,L);return W.toString()},k0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"call_control_ack":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},F0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return k0(A)?A:null}catch{return null}},u=(g,A={})=>{if(typeof window>"u")return M0;let C=new Set,V=new Map,c=A.reconnect!==!1,T=A.maxReconnectAttempts??15,U=A.reconnectMaxDelayMs??8000,I=A.pingInterval??30000,G=A.reconnectResetAfterMs??30000,W=(H)=>q0(H,500,U),_={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:A.sessionId??j0(),ws:null},L=(H)=>{C.forEach((S)=>S(H))},$=()=>{if(_.pingInterval)clearInterval(_.pingInterval),_.pingInterval=null;if(_.reconnectTimeout)clearTimeout(_.reconnectTimeout),_.reconnectTimeout=null;if(_.reconnectResetTimeout)clearTimeout(_.reconnectResetTimeout),_.reconnectResetTimeout=null},E=()=>{if(_.ws?.readyState!==1)return;while(_.pendingMessages.length>0){let H=_.pendingMessages.shift();if(H!==void 0)_.ws.send(H)}},w=(H)=>{for(let S of V.values())clearTimeout(S.timeout),S.reject(Error(H));V.clear()},D=()=>{_.reconnectAttempts+=1;let H=W(_.reconnectAttempts),S=Date.now()+H;L({reconnect:{attempts:_.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:T,nextAttemptAt:S,status:"reconnecting"},type:"connection"}),_.reconnectTimeout=setTimeout(async()=>{if(_.reconnectAttempts>T){L({reconnect:{attempts:_.reconnectAttempts,maxAttempts:T,status:"exhausted"},type:"connection"});return}try{await A.prepareReconnect?.({attempt:_.reconnectAttempts,path:g,scenarioId:_.scenarioId,sessionId:_.sessionId})}catch{if(_.reconnectAttempts<T)D();else L({reconnect:{attempts:_.reconnectAttempts,maxAttempts:T,status:"exhausted"},type:"connection"});return}Y()},H)},Y=()=>{let H=new WebSocket(x0(g,_.sessionId,_.scenarioId,A.query??{}));H.binaryType="arraybuffer",H.onopen=()=>{if(_.ws!==H){H.close(1000);return}let S=_.reconnectAttempts>0;if(_.isConnected=!0,E(),S)L({reconnect:{attempts:_.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:T,status:"resumed"},type:"connection"}),_.reconnectResetTimeout=setTimeout(()=>{if(_.ws===H&&H.readyState===1)_.reconnectAttempts=0;_.reconnectResetTimeout=null},G);C.forEach((y)=>y({scenarioId:_.scenarioId??void 0,sessionId:_.sessionId,status:"active",type:"session"})),_.pingInterval=setInterval(()=>{if(H.readyState===1)H.send(JSON.stringify({type:"ping"}))},I)},H.onmessage=(S)=>{if(_.ws!==H)return;let y=F0(S);if(!y)return;if(y.type==="session")_.sessionId=y.sessionId,_.scenarioId=y.scenarioId??_.scenarioId;if(y.type==="call_control_ack"){let Q=V.get(y.requestId);if(Q)if(clearTimeout(Q.timeout),V.delete(y.requestId),y.ok)Q.resolve();else Q.reject(Error(y.message??`${y.action} was not applied`))}C.forEach((Q)=>Q(y))},H.onclose=(S)=>{if(_.ws!==H)return;_.ws=null,_.isConnected=!1,$();let y=c&&S.code!==1000&&S.code!==4401&&S.code!==4403&&_.reconnectAttempts<T;if(S.code===4401||S.code===4403)C.forEach((j)=>j({message:S.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(y)D();else if(c&&S.code!==1000)L({reconnect:{attempts:_.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:T,status:"exhausted"},type:"connection"})},_.ws=H},Z=(H)=>{if(_.ws?.readyState===1){_.ws.send(H);return}if(typeof H!=="string")return;if(_.pendingMessages.length>=100)_.pendingMessages.shift();_.pendingMessages.push(H)},h=(H)=>{Z(JSON.stringify(H))},z=(H={})=>{if(H.sessionId)_.sessionId=H.sessionId;if(H.scenarioId)_.scenarioId=H.scenarioId;h({scenarioId:_.scenarioId??void 0,sessionId:_.sessionId,type:"start"})},q=(H)=>{Z(H)},R=()=>{h({type:"end_turn"})},O=(H)=>{let S=crypto.randomUUID(),{promise:y,reject:Q,resolve:j}=Promise.withResolvers(),I0=setTimeout(()=>{V.delete(S),Q(Error(`${H.action} acknowledgement timed out`))},5000);return V.set(S,{reject:Q,resolve:j,timeout:I0}),h({...H,requestId:S,type:"call_control"}),y},b=(H="client-close")=>{if($(),w("Voice connection closed"),_.ws){let S=_.ws;if(_.ws=null,S.readyState===1)S.send(JSON.stringify({reason:H,type:"close"}));S.close(1000)}_.isConnected=!1,C.clear()},R0=()=>{if($(),w("Voice connection disconnected"),_.ws){let H=_.ws;_.ws=null,H.close(1000)}_.isConnected=!1,C.clear()},$0=()=>{if(_.ws?.readyState===1)_.ws.close(4000,"absolutejs-voice-reconnect-proof")},H0=(H)=>{return C.add(H),()=>{C.delete(H)}};return Y(),{callControl:O,close:b,disconnect:R0,endTurn:R,send:h,sendAudio:q,simulateDisconnect:$0,start:z,subscribe:H0,getReadyState:()=>_.ws?.readyState??3,getScenarioId:()=>_.scenarioId??"",getSessionId:()=>_.sessionId}};var f0=()=>({attempts:0,maxAttempts:0,status:"idle"}),d0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},l0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},v0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:f0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),s=()=>{let g=v0(),A="",C=new Set,V=()=>{C.forEach((T)=>T())};return{dispatch:(T)=>{switch(T.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:T.chunk,format:T.format,receivedAt:T.receivedAt,turnId:T.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,T.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${T.delta}`};break;case"complete":g={...g,sessionId:T.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:T.event.type==="end"?T.event.disposition:g.call?.disposition,endedAt:T.event.type==="end"?T.event.at:g.call?.endedAt,events:[...g.call?.events??[],T.event],lastEventAt:T.event.at,startedAt:g.call?.startedAt??T.event.at},sessionId:T.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:T.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:T.message,...T.recoverable===!1?{isConnected:!1,status:"failed"}:{}};break;case"final":A=d0(A,T.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:l0(A,T.transcript.text)};break;case"replay":A=T.partial,g={...g,assistantStreamingText:"",assistantTexts:[...T.assistantTexts],call:T.call??null,error:null,isConnected:T.status==="active",partial:T.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:T.scenarioId??g.scenarioId,sessionId:T.sessionId,sessionMetadata:T.sessionMetadata??g.sessionMetadata,status:T.status,turns:[...T.turns]};break;case"session":g={...g,error:null,scenarioId:T.scenarioId??g.scenarioId,isConnected:T.status==="active",sessionId:T.sessionId,sessionMetadata:T.sessionMetadata??g.sessionMetadata,status:T.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,T.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(T)=>{return C.add(T),()=>{C.delete(T)}}}};var a=(g,A={})=>{let C=u(g,A),V=s(),c=A.browserMedia&&typeof window<"u"?r({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,T=new Set,U=(_)=>Promise.resolve().then(()=>{if(!_?.sessionId&&!_?.scenarioId)return;C.start(_),c?.start()}),I=()=>{T.forEach((_)=>_())},G=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let _=V.getSnapshot(),L=JSON.stringify({at:Date.now(),reconnect:_.reconnect,scenarioId:_.scenarioId,sessionId:C.getSessionId(),turnIds:_.turns.map(($)=>$.id)});fetch(A.reconnectReportPath,{body:L,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},W=C.subscribe((_)=>{let L=v(_);if(L){if(V.dispatch(L),_.type==="connection")G();I()}});return{start:U,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl(_){return C.callControl(_)},close(_){W(),c?.close(),C.close(_),V.dispatch({type:"disconnected"}),I()},disconnect(){W(),c?.close(),C.disconnect(),V.dispatch({type:"disconnected"}),I()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio(_){C.sendAudio(_)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe(_){return T.add(_),()=>{T.delete(_)}},get turns(){return V.getSnapshot().turns}}};var p=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var i0={balanced:{qualityProfile:"general",minSilenceMs:400,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",minSilenceMs:300,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",minSilenceMs:600,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},n0={"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},general:{},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var t=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=i0[A],c=n0[C],T=g?.silenceMs??c.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(T,g?.minSilenceMs??c.minSilenceMs??V.minSilenceMs),silenceMs:T,speechThreshold:g?.speechThreshold??c.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??c.transcriptStabilityMs??V.transcriptStabilityMs}};var o0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},e=(g="default")=>{let A=o0[g];return{audioConditioning:p(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:t(A.turnDetection)}};var m0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),g0=(g,A={})=>{let C=e(A.preset),V=a(g,{...C.connection,...A.connection}),c=null,T=m0(V),U=new Set,I=()=>{for(let D of U)D()},G=()=>{if(T={...T,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&T.status==="completed"&&T.isRecording)c?.stop(),c=null,T={...T,isRecording:!1};I()},W=V.subscribe(G);G();let _=()=>{if(c)return c;return c=l({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(D)=>{if(A.capture?.onAudio){A.capture.onAudio(D,V.sendAudio);return}V.sendAudio(D)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),c},L=()=>{c?.stop(),c=null,T={...T,isRecording:!1},I()},$=async()=>{if(T.isRecording)return;try{T={...T,recordingError:null},I(),await _().start(),T={...T,isRecording:!0},I()}catch(D){throw c?.stop(),c=null,T={...T,isRecording:!1,recordingError:D instanceof Error?D.message:String(D)},I(),D}};return{close:(D)=>{W(),L(),V.close(D)},disconnect:()=>{W(),L(),V.disconnect()},startRecording:$,stopRecording:L,get assistantAudio(){return T.assistantAudio},get assistantTexts(){return T.assistantTexts},get assistantStreamingText(){return T.assistantStreamingText},bindHTMX(D){return d(V,D)},get call(){return T.call},callControl:(D)=>V.callControl(D),endTurn:()=>V.endTurn(),get error(){return T.error},getServerSnapshot:()=>T,getSnapshot:()=>T,get isConnected(){return T.isConnected},get isRecording(){return T.isRecording},get partial(){return T.partial},get reconnect(){return T.reconnect},get recordingError(){return T.recordingError},get scenarioId(){return T.scenarioId},sendAudio:(D)=>V.sendAudio(D),get sessionId(){return T.sessionId},get sessionMetadata(){return T.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return T.status},subscribe:(D)=>{return U.add(D),()=>{U.delete(D)}},toggleRecording:async()=>{if(T.isRecording){L();return}await $()},get turns(){return T.turns}}};var K=(g)=>String(g).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var A0=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var r0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},u0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},s0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},V0=(g)=>{let A={...r0,...g.theme},C={...u0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,c=g.state.turns.at(-1)?.committedAt,T=A0({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:c}),U=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,I=g.state.error?"Error":U?C.connecting:g.state.status==="completed"?C.callEnded:s0(T,C);return{agentState:T,classes:{container:`absolute-voice-widget absolute-voice-widget--${T}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:I,theme:A,title:g.title??"Voice"}},a0=(g)=>typeof g==="number"?`${g}px`:g,C0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${a0(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,c=[];if(g.controls.canStart)c.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${K(g.labels.startCall)}</button>`);if(g.controls.canMute)c.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${K(g.labels.mute)}</button>`);if(g.controls.canEnd)c.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${K(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${K(g.classes.container)}" style="${C}">
|
|
1
|
+
(()=>{var{defineProperty:x,getOwnPropertyNames:G0,getOwnPropertyDescriptor:S0}=Object,X0=Object.prototype.hasOwnProperty;function O0(g){return this[g]}var y0=(g)=>{var A=(f??=new WeakMap).get(g),C;if(A)return A;if(A=x({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of G0(g))if(!X0.call(A,V))x(A,V,{get:O0.bind(g,V),enumerable:!(C=S0(g,V))||C.enumerable})}return f.set(g,A),A},f;var J0=(g)=>g;function Y0(g,A){this[g]=J0.bind(null,A)}var Z0=(g,A)=>{for(var C in A)x(g,C,{get:A[C],enumerable:!0,configurable:!0,set:Y0.bind(A,C)})};var Vg={};Z0(Vg,{mount:()=>T0,default:()=>Cg,VOICE_EMBED_VERSION:()=>$0});var w0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},h0=(g,A,C,V)=>{let $=A??g.getAttribute("hx-get")??"";if(!$)return"";let _=new URL($,window.location.origin);if(V)_.searchParams.set(C,V);else _.searchParams.delete(C);return`${_.pathname}${_.search}${_.hash}`},d=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=w0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",$=A.sessionQueryParam??"sessionId",_=()=>{let U=window,c=h0(C,A.route,$,g.sessionId);if(c)C.setAttribute("hx-get",c);U.htmx?.process?.(C),U.htmx?.trigger?.(C,V)},L=g.subscribe(_);return _(),()=>{L()}};var Q0=(g)=>Math.max(-1,Math.min(1,g)),K0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=Q0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},P0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let $ of C){let _=$/32768;V+=_*_}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},N0=(g,A,C)=>{if(A===C)return g;let V=A/C,$=Math.round(g.length/V),_=new Float32Array($),L=0,U=0;while(L<_.length){let c=Math.round((L+1)*V),W=0,R=0;for(let S=U;S<c&&S<g.length;S+=1)W+=g[S]??0,R+=1;_[L]=R>0?W/R:0,L+=1,U=c}return _},l=(g)=>{let A=null,C=null,V=null,$=null,_=async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let U=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!U)throw Error("Browser microphone capture requires AudioContext support.");try{if($=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new U,A.state==="suspended")await A.resume();C=A.createMediaStreamSource($),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(c)=>{let W=c.inputBuffer.getChannelData(0),R=N0(W,A?.sampleRate??48000,g.sampleRateHz??16000),S=K0(R);g.onLevel?.(P0(S)),g.onAudio(S)},C.connect(V),V.connect(A.destination)}catch(c){throw L(),c}},L=()=>{V?.disconnect(),C?.disconnect(),$?.getTracks().forEach((U)=>U.stop()),A?.close(),g.onLevel?.(0),A=null,$=null,V=null,C=null};return{start:_,stop:L}};var k=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return k(A.error);if("cause"in A)return k(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},v=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:k(g.message),recoverable:g.recoverable,type:"error"};case"call_control_ack":return null;case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var Rg=Math.PI*2;var P=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var E0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,M=(g)=>g.length===0?void 0:Math.max(...g);var O=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},B=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},Y=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},F=(g)=>String(g.id??Y(g,"ssrc")??O(g,"ssrc")??Y(g,"trackIdentifier")??Y(g,"mid")??"unknown"),i=(g)=>g===void 0?void 0:g*1000;var q0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var n=(g={})=>{let A=g.stats??[],C=[],V=A.filter((H)=>H.type==="inbound-rtp"&&Y(H,"kind")!=="video"),$=A.filter((H)=>H.type==="outbound-rtp"&&Y(H,"kind")!=="video"),_=A.filter((H)=>H.type==="candidate-pair"),L=A.filter((H)=>(H.type==="track"||H.type==="media-source")&&Y(H,"kind")==="audio"),U=_.filter((H)=>B(H,"selected")===!0||B(H,"nominated")===!0||Y(H,"state")==="succeeded").length,c=L.filter((H)=>Y(H,"readyState")!=="ended"&&Y(H,"trackState")!=="ended"&&B(H,"ended")!==!0).length,W=L.filter((H)=>Y(H,"readyState")==="ended"||Y(H,"trackState")==="ended"||B(H,"ended")===!0).length,R=V.reduce((H,J)=>H+(O(J,"packetsReceived")??0),0),S=$.reduce((H,J)=>H+(O(J,"packetsSent")??0),0),T=[...V,...$].reduce((H,J)=>H+Math.max(0,O(J,"packetsLost")??0),0),Z=R+T,w=Z===0?0:T/Z,D=V.reduce((H,J)=>H+(O(J,"bytesReceived")??0),0),K=$.reduce((H,J)=>H+(O(J,"bytesSent")??0),0),h=M(_.map((H)=>i(O(H,"currentRoundTripTime")??O(H,"roundTripTime"))).filter((H)=>H!==void 0)),Q=M([...V,...$].map((H)=>i(O(H,"jitter"))).filter((H)=>H!==void 0)),E=M(V.map((H)=>{let J=O(H,"jitterBufferDelay"),b=O(H,"jitterBufferEmittedCount");return J!==void 0&&b!==void 0&&b>0?J/b*1000:void 0}).filter((H)=>H!==void 0)),q=L.map((H)=>O(H,"audioLevel")).filter((H)=>H!==void 0);if(g.requireConnectedCandidatePair&&_.length>0&&U===0)P(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&c===0)P(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&w>g.maxPacketLossRatio)P(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(w)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&h!==void 0&&h>g.maxRoundTripTimeMs)P(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(h)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&Q!==void 0&&Q>g.maxJitterMs)P(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(Q)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:U,audioLevelAverage:E0(q),bytesReceived:D,bytesSent:K,checkedAt:Date.now(),endedAudioTracks:W,inboundPackets:R,issues:C,jitterBufferDelayMs:E,jitterMs:Q,liveAudioTracks:c,outboundPackets:S,packetLossRatio:w,packetsLost:T,roundTripTimeMs:h,status:C.some((H)=>H.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},o=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(q0)};var m=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],$=new Map(C.map((T)=>[F(T),T])),L=A.filter((T)=>(T.type==="inbound-rtp"||T.type==="outbound-rtp")&&Y(T,"kind")!=="video"&&Y(T,"mediaType")!=="video").map((T)=>{let Z=T.type==="outbound-rtp"?"outbound":"inbound",w=Z==="outbound"?"packetsSent":"packetsReceived",D=Z==="outbound"?"bytesSent":"bytesReceived",K=$.get(F(T)),h=O(T,w),Q=K?O(K,w):void 0,E=O(T,D),q=K?O(K,D):void 0,H=T.timestamp!==void 0&&K?.timestamp!==void 0?T.timestamp-K.timestamp:void 0;return{bytesDelta:E!==void 0&&q!==void 0?E-q:void 0,currentPackets:h,direction:Z,id:F(T),packetDelta:h!==void 0&&Q!==void 0?h-Q:void 0,previousPackets:Q,timeDeltaMs:H}}),U=L.filter((T)=>T.direction==="inbound"),c=L.filter((T)=>T.direction==="outbound"),W=M(L.map((T)=>T.timeDeltaMs).filter((T)=>T!==void 0)),R=U.filter((T)=>g.maxInboundPacketStallMs!==void 0&&T.timeDeltaMs!==void 0&&T.timeDeltaMs>=g.maxInboundPacketStallMs&&T.packetDelta!==void 0&&T.packetDelta<=0).length,S=c.filter((T)=>g.maxOutboundPacketStallMs!==void 0&&T.timeDeltaMs!==void 0&&T.timeDeltaMs>=g.maxOutboundPacketStallMs&&T.packetDelta!==void 0&&T.packetDelta<=0).length;if(g.requireInboundAudio&&U.length===0)P(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&c.length===0)P(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&W!==void 0&&W>g.maxGapMs)P(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(W)}ms above ${String(g.maxGapMs)}ms.`);if(R>0)P(V,"error","media.webrtc_inbound_stalled",`${String(R)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(S>0)P(V,"error","media.webrtc_outbound_stalled",`${String(S)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:U.length,issues:V,maxObservedGapMs:W,outboundAudioStreams:c.length,stalledInboundStreams:R,stalledOutboundStreams:S,status:V.some((T)=>T.severity==="error")?"fail":V.length>0?"warn":"pass",streams:L,totalStats:A.length}};var z0="/api/voice/browser-media",b0=5000,B0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,M0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??z0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(g)=>{let A=null,C=[],V=async()=>{let L=await B0(g);if(!L)return;let U=await o({peerConnection:L}),c=n({...g,stats:U}),W=g.continuity===!1?void 0:m({...g.continuity,previousStats:C,stats:U}),R={at:Date.now(),continuity:W,report:c,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=U,g.onReport?.(R),await M0(R,g),R},$=()=>{V().catch((L)=>{g.onError?.(L)})},_=()=>{if(A)clearInterval(A),A=null};return{close:_,reportOnce:V,stop:_,start:()=>{if(A)return;$(),A=setInterval($,g.intervalMs??b0)}}};var j0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));class u extends Error{constructor(g="Voice authorization could not be renewed."){super(g);this.name="VoiceReconnectRejectedError"}}var z=()=>{},x0=()=>Promise.resolve(),k0=()=>z,F0={callControl:x0,close:z,disconnect:z,endTurn:z,send:z,sendAudio:z,simulateDisconnect:z,subscribe:k0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},f0=()=>crypto.randomUUID(),d0=(g,A,C,V)=>{let{hostname:$,port:_,protocol:L}=window.location,U=L==="https:"?"wss:":"ws:",c=_?`:${_}`:"",W=new URL(`${U}//${$}${c}${g}`);if(W.searchParams.set("sessionId",A),C)W.searchParams.set("scenarioId",C);for(let[R,S]of Object.entries(V))if(R!=="sessionId"&&R!=="scenarioId"&&S)W.searchParams.set(R,S);return W.toString()},l0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"call_control_ack":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},v0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return l0(A)?A:null}catch{return null}},s=(g,A={})=>{if(typeof window>"u")return F0;let C=new Set,V=new Map,$=A.reconnect!==!1,_=A.maxReconnectAttempts??15,L=A.reconnectMaxDelayMs??8000,U=A.pingInterval??30000,c=A.reconnectResetAfterMs??30000,W=0,R=!1,S=(I)=>j0(I,500,L),T={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:A.sessionId??f0(),ws:null},Z=(I)=>{C.forEach((G)=>G(I))},w=(I)=>{C.forEach((G)=>G({message:I,recoverable:!1,type:"error"})),Z({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"})},D=()=>{if(T.pingInterval)clearInterval(T.pingInterval),T.pingInterval=null;if(T.reconnectTimeout)clearTimeout(T.reconnectTimeout),T.reconnectTimeout=null;if(T.reconnectResetTimeout)clearTimeout(T.reconnectResetTimeout),T.reconnectResetTimeout=null},K=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let I=T.pendingMessages.shift();if(I!==void 0)T.ws.send(I)}},h=(I)=>{for(let G of V.values())clearTimeout(G.timeout),G.reject(Error(I));V.clear()},Q=()=>{if(R)return;let I=W;T.reconnectAttempts+=1;let G=S(T.reconnectAttempts),X=Date.now()+G;Z({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:X,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(async()=>{if(R||W!==I)return;if(T.reconnectAttempts>_){w("Voice connection could not be restored.");return}try{await A.prepareReconnect?.({attempt:T.reconnectAttempts,path:g,scenarioId:T.scenarioId,sessionId:T.sessionId})}catch(y){if(R||W!==I)return;if(y instanceof u){w(y.message);return}if(T.reconnectAttempts<_)Q();else w("Voice authorization could not be renewed for reconnect.");return}if(R||W!==I)return;E()},G)},E=()=>{if(R)return;let I=new WebSocket(d0(g,T.sessionId,T.scenarioId,A.query??{}));I.binaryType="arraybuffer",I.onopen=()=>{if(T.ws!==I){I.close(1000);return}let G=T.reconnectAttempts>0;if(T.isConnected=!0,K(),G)Z({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),T.reconnectResetTimeout=setTimeout(()=>{if(T.ws===I&&I.readyState===1)T.reconnectAttempts=0;T.reconnectResetTimeout=null},c);C.forEach((X)=>X({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(I.readyState===1)I.send(JSON.stringify({type:"ping"}))},U)},I.onmessage=(G)=>{if(T.ws!==I)return;let X=v0(G);if(!X)return;if(X.type==="session")T.sessionId=X.sessionId,T.scenarioId=X.scenarioId??T.scenarioId;if(X.type==="call_control_ack"){let y=V.get(X.requestId);if(y)if(clearTimeout(y.timeout),V.delete(X.requestId),X.ok)y.resolve();else y.reject(Error(X.message??`${X.action} was not applied`))}C.forEach((y)=>y(X))},I.onclose=(G)=>{if(T.ws!==I)return;T.ws=null,T.isConnected=!1,D(),h("Voice connection interrupted");let X=$&&G.code!==1000&&G.code!==4401&&G.code!==4403&&T.reconnectAttempts<_,y=G.code===4401||G.code===4403;if(y)C.forEach((j)=>j({message:G.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(X)Q();else if($&&G.code!==1000)if(y)Z({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"});else w("Voice connection could not be restored.")},T.ws=I},q=(I)=>{if(T.ws?.readyState===1){T.ws.send(I);return}if(typeof I!=="string")return;if(T.pendingMessages.length>=100)T.pendingMessages.shift();T.pendingMessages.push(I)},H=(I)=>{q(JSON.stringify(I))},J=(I={})=>{if(I.sessionId)T.sessionId=I.sessionId;if(I.scenarioId)T.scenarioId=I.scenarioId;H({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},b=(I)=>{q(I)},R0=()=>{H({type:"end_turn"})},I0=(I)=>{if(T.ws?.readyState!==1)return Promise.reject(Error(`${I.action} is unavailable while disconnected`));let G=crypto.randomUUID(),{promise:X,reject:y,resolve:j}=Promise.withResolvers(),D0=setTimeout(()=>{V.delete(G),y(Error(`${I.action} acknowledgement timed out`))},5000);return V.set(G,{reject:y,resolve:j,timeout:D0}),T.ws.send(JSON.stringify({...I,requestId:G,type:"call_control"})),X},U0=(I="client-close")=>{if(R=!0,W+=1,D(),h("Voice connection closed"),T.pendingMessages.length=0,T.ws){let G=T.ws;if(T.ws=null,G.readyState===1)G.send(JSON.stringify({reason:I,type:"close"}));G.close(1000)}T.isConnected=!1,C.clear()},L0=()=>{if(R=!0,W+=1,D(),h("Voice connection disconnected"),T.pendingMessages.length=0,T.ws){let I=T.ws;T.ws=null,I.close(1000)}T.isConnected=!1,C.clear()},W0=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},c0=(I)=>{return C.add(I),()=>{C.delete(I)}};return E(),{callControl:I0,close:U0,disconnect:L0,endTurn:R0,send:H,sendAudio:b,simulateDisconnect:W0,start:J,subscribe:c0,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var i0=()=>({attempts:0,maxAttempts:0,status:"idle"}),n0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},o0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},m0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:i0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),a=()=>{let g=m0(),A="",C=new Set,V=()=>{C.forEach((_)=>_())};return{dispatch:(_)=>{switch(_.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:_.chunk,format:_.format,receivedAt:_.receivedAt,turnId:_.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,_.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${_.delta}`};break;case"complete":g={...g,sessionId:_.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:_.event.type==="end"?_.event.disposition:g.call?.disposition,endedAt:_.event.type==="end"?_.event.at:g.call?.endedAt,events:[...g.call?.events??[],_.event],lastEventAt:_.event.at,startedAt:g.call?.startedAt??_.event.at},sessionId:_.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:_.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:_.message,..._.recoverable===!1?{isConnected:!1,status:"failed"}:{}};break;case"final":A=n0(A,_.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:o0(A,_.transcript.text)};break;case"replay":A=_.partial,g={...g,assistantStreamingText:"",assistantTexts:[..._.assistantTexts],call:_.call??null,error:null,isConnected:_.status==="active",partial:_.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:_.scenarioId??g.scenarioId,sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??g.sessionMetadata,status:_.status,turns:[..._.turns]};break;case"session":g={...g,error:null,scenarioId:_.scenarioId??g.scenarioId,isConnected:_.status==="active",sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??g.sessionMetadata,status:_.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,_.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(_)=>{return C.add(_),()=>{C.delete(_)}}}};var p=(g,A={})=>{let C=s(g,A),V=a(),$=A.browserMedia&&typeof window<"u"?r({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,_=new Set,L=(R)=>Promise.resolve().then(()=>{if(!R?.sessionId&&!R?.scenarioId)return;C.start(R),$?.start()}),U=()=>{_.forEach((R)=>R())},c=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let R=V.getSnapshot(),S=JSON.stringify({at:Date.now(),reconnect:R.reconnect,scenarioId:R.scenarioId,sessionId:C.getSessionId(),turnIds:R.turns.map((T)=>T.id)});fetch(A.reconnectReportPath,{body:S,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},W=C.subscribe((R)=>{let S=v(R);if(S){if(V.dispatch(S),R.type==="connection")c();U()}});return{start:L,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl(R){return C.callControl(R)},close(R){W(),$?.close(),C.close(R),V.dispatch({type:"disconnected"}),U()},disconnect(){W(),$?.close(),C.disconnect(),V.dispatch({type:"disconnected"}),U()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio(R){C.sendAudio(R)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe(R){return _.add(R),()=>{_.delete(R)}},get turns(){return V.getSnapshot().turns}}};var t=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var r0={balanced:{qualityProfile:"general",minSilenceMs:400,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",minSilenceMs:300,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",minSilenceMs:600,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},u0={"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},general:{},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var e=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=r0[A],$=u0[C],_=g?.silenceMs??$.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(_,g?.minSilenceMs??$.minSilenceMs??V.minSilenceMs),silenceMs:_,speechThreshold:g?.speechThreshold??$.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??$.transcriptStabilityMs??V.transcriptStabilityMs}};var s0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},g0=(g="default")=>{let A=s0[g];return{audioConditioning:t(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:e(A.turnDetection)}};var a0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),A0=(g,A={})=>{let C=g0(A.preset),V=p(g,{...C.connection,...A.connection}),$=null,_=a0(V),L=new Set,U=()=>{for(let D of L)D()},c=()=>{if(_={..._,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&_.status==="completed"&&_.isRecording)$?.stop(),$=null,_={..._,isRecording:!1};U()},W=V.subscribe(c);c();let R=()=>{if($)return $;return $=l({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(D)=>{if(A.capture?.onAudio){A.capture.onAudio(D,V.sendAudio);return}V.sendAudio(D)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),$},S=()=>{$?.stop(),$=null,_={..._,isRecording:!1},U()},T=async()=>{if(_.isRecording)return;try{_={..._,recordingError:null},U(),await R().start(),_={..._,isRecording:!0},U()}catch(D){throw $?.stop(),$=null,_={..._,isRecording:!1,recordingError:D instanceof Error?D.message:String(D)},U(),D}};return{close:(D)=>{W(),S(),V.close(D)},disconnect:()=>{W(),S(),V.disconnect()},startRecording:T,stopRecording:S,get assistantAudio(){return _.assistantAudio},get assistantTexts(){return _.assistantTexts},get assistantStreamingText(){return _.assistantStreamingText},bindHTMX(D){return d(V,D)},get call(){return _.call},callControl:(D)=>V.callControl(D),endTurn:()=>V.endTurn(),get error(){return _.error},getServerSnapshot:()=>_,getSnapshot:()=>_,get isConnected(){return _.isConnected},get isRecording(){return _.isRecording},get partial(){return _.partial},get reconnect(){return _.reconnect},get recordingError(){return _.recordingError},get scenarioId(){return _.scenarioId},sendAudio:(D)=>V.sendAudio(D),get sessionId(){return _.sessionId},get sessionMetadata(){return _.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return _.status},subscribe:(D)=>{return L.add(D),()=>{L.delete(D)}},toggleRecording:async()=>{if(_.isRecording){S();return}await T()},get turns(){return _.turns}}};var N=(g)=>String(g).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var V0=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var p0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},t0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},e0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},C0=(g)=>{let A={...p0,...g.theme},C={...t0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,$=g.state.turns.at(-1)?.committedAt,_=V0({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:$}),L=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,U=g.state.error?"Error":L?C.connecting:g.state.status==="completed"?C.callEnded:e0(_,C);return{agentState:_,classes:{container:`absolute-voice-widget absolute-voice-widget--${_}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:U,theme:A,title:g.title??"Voice"}},gg=(g)=>typeof g==="number"?`${g}px`:g,_0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${gg(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,$=[];if(g.controls.canStart)$.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(g.labels.startCall)}</button>`);if(g.controls.canMute)$.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(g.labels.mute)}</button>`);if(g.controls.canEnd)$.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${N(g.classes.container)}" style="${C}">
|
|
2
2
|
<div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
|
|
3
|
-
<span aria-hidden="true" class="${
|
|
4
|
-
<strong style="font-size:15px;">${
|
|
5
|
-
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${
|
|
3
|
+
<span aria-hidden="true" class="${N(g.classes.dot)}" style="${V}"></span>
|
|
4
|
+
<strong style="font-size:15px;">${N(g.title)}</strong>
|
|
5
|
+
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${N(g.statusLabel)}</span>
|
|
6
6
|
</div>
|
|
7
|
-
${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${
|
|
8
|
-
<div style="display:flex;gap:10px;">${
|
|
9
|
-
${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${
|
|
10
|
-
</div>`};var
|
|
7
|
+
${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${N(g.partial)}”</p>`:""}
|
|
8
|
+
<div style="display:flex;gap:10px;">${$.join("")}</div>
|
|
9
|
+
${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${N(g.errorMessage)}</p>`:""}
|
|
10
|
+
</div>`};var Ag=(g)=>{if(typeof g!=="string")return g;let A=document.querySelector(g);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${g}"`);return A},T0=(g,A={})=>{let C=Ag(g),V=A0(A.path??"/voice",A.controllerOptions),$=null,_=null,L=()=>{let c=C0({...A.labels!==void 0?{labels:A.labels}:{},state:{assistantAudio:V.assistantAudio,error:V.error,isConnected:V.isConnected,isRecording:V.isRecording,partial:V.partial,status:V.status,turns:V.turns},...A.theme!==void 0?{theme:A.theme}:{},...A.title!==void 0?{title:A.title}:{}});C.innerHTML=_0(c);for(let W of C.querySelectorAll("button[data-action]")){let{action:R}=W.dataset;W.addEventListener("click",()=>{if(R==="start")V.startRecording();else if(R==="mute")V.stopRecording();else if(R==="end")V.close()})}if(V.error&&V.error!==$)$=V.error,A.onError?.(V.error);if(V.status!==_)_=V.status,A.onStatusChange?.(V.status)},U=V.subscribe(L);if(L(),A.autoStart)V.startRecording();return{controller:V,async end(){await V.close()},mute(){V.stopRecording()},async start(){await V.startRecording()},unmount(){U(),V.close(),C.innerHTML=""}}},$0="0.0.22-beta.516",H0={mount:T0,version:$0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=H0;var Cg=H0;})();
|
package/dist/react/index.js
CHANGED
|
@@ -11447,6 +11447,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
11447
11447
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
11448
11448
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
11449
11449
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
11450
|
+
|
|
11451
|
+
class VoiceReconnectRejectedError extends Error {
|
|
11452
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
11453
|
+
super(message);
|
|
11454
|
+
this.name = "VoiceReconnectRejectedError";
|
|
11455
|
+
}
|
|
11456
|
+
}
|
|
11450
11457
|
var noop = () => {};
|
|
11451
11458
|
var noopAsync = () => Promise.resolve();
|
|
11452
11459
|
var noopUnsubscribe = () => noop;
|
|
@@ -11526,6 +11533,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11526
11533
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
11527
11534
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
11528
11535
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
11536
|
+
let lifecycleGeneration = 0;
|
|
11537
|
+
let disposed = false;
|
|
11529
11538
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
11530
11539
|
const state = {
|
|
11531
11540
|
isConnected: false,
|
|
@@ -11541,6 +11550,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11541
11550
|
const emitConnection = (reconnect) => {
|
|
11542
11551
|
listeners.forEach((listener) => listener(reconnect));
|
|
11543
11552
|
};
|
|
11553
|
+
const emitTerminalFailure = (message) => {
|
|
11554
|
+
listeners.forEach((listener) => listener({
|
|
11555
|
+
message,
|
|
11556
|
+
recoverable: false,
|
|
11557
|
+
type: "error"
|
|
11558
|
+
}));
|
|
11559
|
+
emitConnection({
|
|
11560
|
+
reconnect: {
|
|
11561
|
+
attempts: state.reconnectAttempts,
|
|
11562
|
+
maxAttempts: maxReconnectAttempts,
|
|
11563
|
+
status: "exhausted"
|
|
11564
|
+
},
|
|
11565
|
+
type: "connection"
|
|
11566
|
+
});
|
|
11567
|
+
};
|
|
11544
11568
|
const clearTimers = () => {
|
|
11545
11569
|
if (state.pingInterval) {
|
|
11546
11570
|
clearInterval(state.pingInterval);
|
|
@@ -11574,6 +11598,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11574
11598
|
pendingCallControls.clear();
|
|
11575
11599
|
};
|
|
11576
11600
|
const scheduleReconnect = () => {
|
|
11601
|
+
if (disposed)
|
|
11602
|
+
return;
|
|
11603
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
11577
11604
|
state.reconnectAttempts += 1;
|
|
11578
11605
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
11579
11606
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -11588,15 +11615,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11588
11615
|
type: "connection"
|
|
11589
11616
|
});
|
|
11590
11617
|
state.reconnectTimeout = setTimeout(async () => {
|
|
11618
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11619
|
+
return;
|
|
11591
11620
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
11592
|
-
|
|
11593
|
-
reconnect: {
|
|
11594
|
-
attempts: state.reconnectAttempts,
|
|
11595
|
-
maxAttempts: maxReconnectAttempts,
|
|
11596
|
-
status: "exhausted"
|
|
11597
|
-
},
|
|
11598
|
-
type: "connection"
|
|
11599
|
-
});
|
|
11621
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
11600
11622
|
return;
|
|
11601
11623
|
}
|
|
11602
11624
|
try {
|
|
@@ -11606,25 +11628,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11606
11628
|
scenarioId: state.scenarioId,
|
|
11607
11629
|
sessionId: state.sessionId
|
|
11608
11630
|
});
|
|
11609
|
-
} catch {
|
|
11631
|
+
} catch (error) {
|
|
11632
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11633
|
+
return;
|
|
11634
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
11635
|
+
emitTerminalFailure(error.message);
|
|
11636
|
+
return;
|
|
11637
|
+
}
|
|
11610
11638
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
11611
11639
|
scheduleReconnect();
|
|
11612
11640
|
} else {
|
|
11613
|
-
|
|
11614
|
-
reconnect: {
|
|
11615
|
-
attempts: state.reconnectAttempts,
|
|
11616
|
-
maxAttempts: maxReconnectAttempts,
|
|
11617
|
-
status: "exhausted"
|
|
11618
|
-
},
|
|
11619
|
-
type: "connection"
|
|
11620
|
-
});
|
|
11641
|
+
emitTerminalFailure("Voice authorization could not be renewed for reconnect.");
|
|
11621
11642
|
}
|
|
11622
11643
|
return;
|
|
11623
11644
|
}
|
|
11645
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11646
|
+
return;
|
|
11624
11647
|
connect();
|
|
11625
11648
|
}, delayMs);
|
|
11626
11649
|
};
|
|
11627
11650
|
const connect = () => {
|
|
11651
|
+
if (disposed)
|
|
11652
|
+
return;
|
|
11628
11653
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
11629
11654
|
ws.binaryType = "arraybuffer";
|
|
11630
11655
|
ws.onopen = () => {
|
|
@@ -11695,6 +11720,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11695
11720
|
state.ws = null;
|
|
11696
11721
|
state.isConnected = false;
|
|
11697
11722
|
clearTimers();
|
|
11723
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
11698
11724
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
11699
11725
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
11700
11726
|
if (authorizationFailure) {
|
|
@@ -11707,15 +11733,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11707
11733
|
if (reconnectable) {
|
|
11708
11734
|
scheduleReconnect();
|
|
11709
11735
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11736
|
+
if (authorizationFailure) {
|
|
11737
|
+
emitConnection({
|
|
11738
|
+
reconnect: {
|
|
11739
|
+
attempts: state.reconnectAttempts,
|
|
11740
|
+
lastDisconnectAt: Date.now(),
|
|
11741
|
+
maxAttempts: maxReconnectAttempts,
|
|
11742
|
+
status: "exhausted"
|
|
11743
|
+
},
|
|
11744
|
+
type: "connection"
|
|
11745
|
+
});
|
|
11746
|
+
} else {
|
|
11747
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
11748
|
+
}
|
|
11719
11749
|
}
|
|
11720
11750
|
};
|
|
11721
11751
|
state.ws = ws;
|
|
@@ -11756,6 +11786,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11756
11786
|
send({ type: "end_turn" });
|
|
11757
11787
|
};
|
|
11758
11788
|
const callControl = (message) => {
|
|
11789
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
11790
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
11791
|
+
}
|
|
11759
11792
|
const requestId = crypto.randomUUID();
|
|
11760
11793
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
11761
11794
|
const timeout = setTimeout(() => {
|
|
@@ -11763,16 +11796,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11763
11796
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
11764
11797
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
11765
11798
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
11766
|
-
send({
|
|
11799
|
+
state.ws.send(JSON.stringify({
|
|
11767
11800
|
...message,
|
|
11768
11801
|
requestId,
|
|
11769
11802
|
type: "call_control"
|
|
11770
|
-
});
|
|
11803
|
+
}));
|
|
11771
11804
|
return promise;
|
|
11772
11805
|
};
|
|
11773
11806
|
const close = (reason = "client-close") => {
|
|
11807
|
+
disposed = true;
|
|
11808
|
+
lifecycleGeneration += 1;
|
|
11774
11809
|
clearTimers();
|
|
11775
11810
|
rejectPendingCallControls("Voice connection closed");
|
|
11811
|
+
state.pendingMessages.length = 0;
|
|
11776
11812
|
if (state.ws) {
|
|
11777
11813
|
const ws = state.ws;
|
|
11778
11814
|
state.ws = null;
|
|
@@ -11785,8 +11821,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11785
11821
|
listeners.clear();
|
|
11786
11822
|
};
|
|
11787
11823
|
const disconnect = () => {
|
|
11824
|
+
disposed = true;
|
|
11825
|
+
lifecycleGeneration += 1;
|
|
11788
11826
|
clearTimers();
|
|
11789
11827
|
rejectPendingCallControls("Voice connection disconnected");
|
|
11828
|
+
state.pendingMessages.length = 0;
|
|
11790
11829
|
if (state.ws) {
|
|
11791
11830
|
const ws = state.ws;
|
|
11792
11831
|
state.ws = null;
|