@absolutejs/voice 0.0.22-beta.659 → 0.0.22-beta.660
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 +14 -2
- package/dist/client/htmxBootstrap.js +14 -2
- package/dist/client/index.js +14 -2
- package/dist/core/types.d.ts +6 -0
- package/dist/embed/index.js +14 -2
- package/dist/embed/voice-widget.js +6 -6
- package/dist/index.js +120 -63
- package/dist/react/index.js +14 -2
- package/dist/svelte/index.js +14 -2
- package/dist/testing/index.js +108 -50
- package/dist/vue/index.js +14 -2
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -625,6 +625,7 @@ var WS_CLOSED = 3;
|
|
|
625
625
|
var WS_NORMAL_CLOSURE = 1000;
|
|
626
626
|
var WS_UNAUTHORIZED = 4401;
|
|
627
627
|
var WS_FORBIDDEN = 4403;
|
|
628
|
+
var WS_INITIALIZATION_FAILED = 4500;
|
|
628
629
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
629
630
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
630
631
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
@@ -928,8 +929,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
928
929
|
state.isConnected = false;
|
|
929
930
|
clearTimers();
|
|
930
931
|
rejectPendingCallControls("Voice connection interrupted");
|
|
931
|
-
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
932
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
|
|
932
933
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
934
|
+
const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
|
|
933
935
|
if (authorizationFailure) {
|
|
934
936
|
listeners.forEach((listener) => listener({
|
|
935
937
|
message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
|
|
@@ -937,6 +939,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
937
939
|
type: "error"
|
|
938
940
|
}));
|
|
939
941
|
}
|
|
942
|
+
if (initializationFailure) {
|
|
943
|
+
listeners.forEach((listener) => listener({
|
|
944
|
+
message: "Voice provider initialization failed.",
|
|
945
|
+
recoverable: false,
|
|
946
|
+
type: "error"
|
|
947
|
+
}));
|
|
948
|
+
}
|
|
940
949
|
if (reconnectable) {
|
|
941
950
|
scheduleReconnect();
|
|
942
951
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
@@ -950,7 +959,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
950
959
|
},
|
|
951
960
|
type: "connection"
|
|
952
961
|
});
|
|
953
|
-
} else {
|
|
962
|
+
} else if (!initializationFailure) {
|
|
954
963
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
955
964
|
}
|
|
956
965
|
}
|
|
@@ -976,6 +985,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
976
985
|
sendSerialized(JSON.stringify(message));
|
|
977
986
|
};
|
|
978
987
|
const start = (input = {}) => {
|
|
988
|
+
if (input.sessionId && input.sessionId !== state.sessionId) {
|
|
989
|
+
throw new Error("Voice session switching requires a new connection instance.");
|
|
990
|
+
}
|
|
979
991
|
if (input.sessionId) {
|
|
980
992
|
state.sessionId = input.sessionId;
|
|
981
993
|
}
|
|
@@ -542,6 +542,7 @@ var WS_CLOSED = 3;
|
|
|
542
542
|
var WS_NORMAL_CLOSURE = 1000;
|
|
543
543
|
var WS_UNAUTHORIZED = 4401;
|
|
544
544
|
var WS_FORBIDDEN = 4403;
|
|
545
|
+
var WS_INITIALIZATION_FAILED = 4500;
|
|
545
546
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
546
547
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
547
548
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
@@ -845,8 +846,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
845
846
|
state.isConnected = false;
|
|
846
847
|
clearTimers();
|
|
847
848
|
rejectPendingCallControls("Voice connection interrupted");
|
|
848
|
-
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
849
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
|
|
849
850
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
851
|
+
const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
|
|
850
852
|
if (authorizationFailure) {
|
|
851
853
|
listeners.forEach((listener) => listener({
|
|
852
854
|
message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
|
|
@@ -854,6 +856,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
854
856
|
type: "error"
|
|
855
857
|
}));
|
|
856
858
|
}
|
|
859
|
+
if (initializationFailure) {
|
|
860
|
+
listeners.forEach((listener) => listener({
|
|
861
|
+
message: "Voice provider initialization failed.",
|
|
862
|
+
recoverable: false,
|
|
863
|
+
type: "error"
|
|
864
|
+
}));
|
|
865
|
+
}
|
|
857
866
|
if (reconnectable) {
|
|
858
867
|
scheduleReconnect();
|
|
859
868
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
@@ -867,7 +876,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
867
876
|
},
|
|
868
877
|
type: "connection"
|
|
869
878
|
});
|
|
870
|
-
} else {
|
|
879
|
+
} else if (!initializationFailure) {
|
|
871
880
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
872
881
|
}
|
|
873
882
|
}
|
|
@@ -893,6 +902,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
893
902
|
sendSerialized(JSON.stringify(message));
|
|
894
903
|
};
|
|
895
904
|
const start = (input = {}) => {
|
|
905
|
+
if (input.sessionId && input.sessionId !== state.sessionId) {
|
|
906
|
+
throw new Error("Voice session switching requires a new connection instance.");
|
|
907
|
+
}
|
|
896
908
|
if (input.sessionId) {
|
|
897
909
|
state.sessionId = input.sessionId;
|
|
898
910
|
}
|
package/dist/client/index.js
CHANGED
|
@@ -106,6 +106,7 @@ var WS_CLOSED = 3;
|
|
|
106
106
|
var WS_NORMAL_CLOSURE = 1000;
|
|
107
107
|
var WS_UNAUTHORIZED = 4401;
|
|
108
108
|
var WS_FORBIDDEN = 4403;
|
|
109
|
+
var WS_INITIALIZATION_FAILED = 4500;
|
|
109
110
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
110
111
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
111
112
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
@@ -409,8 +410,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
409
410
|
state.isConnected = false;
|
|
410
411
|
clearTimers();
|
|
411
412
|
rejectPendingCallControls("Voice connection interrupted");
|
|
412
|
-
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
413
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
|
|
413
414
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
415
|
+
const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
|
|
414
416
|
if (authorizationFailure) {
|
|
415
417
|
listeners.forEach((listener) => listener({
|
|
416
418
|
message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
|
|
@@ -418,6 +420,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
418
420
|
type: "error"
|
|
419
421
|
}));
|
|
420
422
|
}
|
|
423
|
+
if (initializationFailure) {
|
|
424
|
+
listeners.forEach((listener) => listener({
|
|
425
|
+
message: "Voice provider initialization failed.",
|
|
426
|
+
recoverable: false,
|
|
427
|
+
type: "error"
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
421
430
|
if (reconnectable) {
|
|
422
431
|
scheduleReconnect();
|
|
423
432
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
@@ -431,7 +440,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
431
440
|
},
|
|
432
441
|
type: "connection"
|
|
433
442
|
});
|
|
434
|
-
} else {
|
|
443
|
+
} else if (!initializationFailure) {
|
|
435
444
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
436
445
|
}
|
|
437
446
|
}
|
|
@@ -457,6 +466,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
457
466
|
sendSerialized(JSON.stringify(message));
|
|
458
467
|
};
|
|
459
468
|
const start = (input = {}) => {
|
|
469
|
+
if (input.sessionId && input.sessionId !== state.sessionId) {
|
|
470
|
+
throw new Error("Voice session switching requires a new connection instance.");
|
|
471
|
+
}
|
|
460
472
|
if (input.sessionId) {
|
|
461
473
|
state.sessionId = input.sessionId;
|
|
462
474
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -341,6 +341,12 @@ export type VoiceSessionRecord<TMeta = Record<string, never>, TResult = unknown>
|
|
|
341
341
|
attempts: number;
|
|
342
342
|
lastDisconnectAt?: number;
|
|
343
343
|
};
|
|
344
|
+
/** Durable caller pause state so a reconnect or process replacement restores
|
|
345
|
+
* the remaining pause window instead of rearming conversation watchdogs. */
|
|
346
|
+
pause?: {
|
|
347
|
+
expiresAt: number;
|
|
348
|
+
pausedAt: number;
|
|
349
|
+
};
|
|
344
350
|
lastCommittedTurn?: {
|
|
345
351
|
signature: string;
|
|
346
352
|
text: string;
|
package/dist/embed/index.js
CHANGED
|
@@ -539,6 +539,7 @@ var WS_CLOSED = 3;
|
|
|
539
539
|
var WS_NORMAL_CLOSURE = 1000;
|
|
540
540
|
var WS_UNAUTHORIZED = 4401;
|
|
541
541
|
var WS_FORBIDDEN = 4403;
|
|
542
|
+
var WS_INITIALIZATION_FAILED = 4500;
|
|
542
543
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
543
544
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
544
545
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
@@ -842,8 +843,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
842
843
|
state.isConnected = false;
|
|
843
844
|
clearTimers();
|
|
844
845
|
rejectPendingCallControls("Voice connection interrupted");
|
|
845
|
-
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
846
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
|
|
846
847
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
848
|
+
const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
|
|
847
849
|
if (authorizationFailure) {
|
|
848
850
|
listeners.forEach((listener) => listener({
|
|
849
851
|
message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
|
|
@@ -851,6 +853,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
851
853
|
type: "error"
|
|
852
854
|
}));
|
|
853
855
|
}
|
|
856
|
+
if (initializationFailure) {
|
|
857
|
+
listeners.forEach((listener) => listener({
|
|
858
|
+
message: "Voice provider initialization failed.",
|
|
859
|
+
recoverable: false,
|
|
860
|
+
type: "error"
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
854
863
|
if (reconnectable) {
|
|
855
864
|
scheduleReconnect();
|
|
856
865
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
@@ -864,7 +873,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
864
873
|
},
|
|
865
874
|
type: "connection"
|
|
866
875
|
});
|
|
867
|
-
} else {
|
|
876
|
+
} else if (!initializationFailure) {
|
|
868
877
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
869
878
|
}
|
|
870
879
|
}
|
|
@@ -890,6 +899,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
890
899
|
sendSerialized(JSON.stringify(message));
|
|
891
900
|
};
|
|
892
901
|
const start = (input = {}) => {
|
|
902
|
+
if (input.sessionId && input.sessionId !== state.sessionId) {
|
|
903
|
+
throw new Error("Voice session switching requires a new connection instance.");
|
|
904
|
+
}
|
|
893
905
|
if (input.sessionId) {
|
|
894
906
|
state.sessionId = input.sessionId;
|
|
895
907
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
(()=>{var{defineProperty:x,getOwnPropertyNames:w0,getOwnPropertyDescriptor:J0}=Object,S0=Object.prototype.hasOwnProperty;function Y0(V){return this[V]}var Z0=(V)=>{var g=(l??=new WeakMap).get(V),C;if(g)return g;if(g=x({},"__esModule",{value:!0}),V&&typeof V==="object"||typeof V==="function"){for(var A of w0(V))if(!S0.call(g,A))x(g,A,{get:Y0.bind(V,A),enumerable:!(C=J0(V,A))||C.enumerable})}return l.set(V,g),g},l;var y0=(V)=>V;function h0(V,g){this[V]=y0.bind(null,g)}var Q0=(V,g)=>{for(var C in g)x(V,C,{get:g[C],enumerable:!0,configurable:!0,set:h0.bind(g,C)})};var _V={};Q0(_V,{mount:()=>$0,default:()=>TV,VOICE_EMBED_VERSION:()=>R0});var K0=(V)=>{if(typeof V!=="string")return V;return document.querySelector(V)},c0=(V,g,C,A)=>{let R=g??V.getAttribute("hx-get")??"";if(!R)return"";let _=new URL(R,window.location.origin);if(A)_.searchParams.set(C,A);else _.searchParams.delete(C);return`${_.pathname}${_.search}${_.hash}`},d=(V,g)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=K0(g.element);if(!C)return()=>{};let A=g.eventName??"voice-refresh",R=g.sessionQueryParam??"sessionId",_=()=>{let X=window,H=c0(C,g.route,R,V.sessionId);if(H)C.setAttribute("hx-get",H);X.htmx?.process?.(C),X.htmx?.trigger?.(C,A)},D=V.subscribe(_);return _(),()=>{D()}};var E0=(V)=>Math.max(-1,Math.min(1,V)),q0=(V)=>{let g=new Int16Array(V.length);for(let C=0;C<V.length;C+=1){let A=E0(V[C]??0);g[C]=A<0?A*32768:A*32767}return new Uint8Array(g.buffer)},z0=(V)=>{let g=V instanceof Uint8Array?V:new Uint8Array(V);if(g.byteLength<2)return 0;let C=new Int16Array(g.buffer,g.byteOffset,Math.floor(g.byteLength/2));if(C.length===0)return 0;let A=0;for(let R of C){let _=R/32768;A+=_*_}return Math.min(1,Math.max(0,Math.sqrt(A/C.length)*5.5))},N0=(V,g,C)=>{if(g===C)return V;let A=g/C,R=Math.round(V.length/A),_=new Float32Array(R),D=0,X=0;while(D<_.length){let H=Math.round((D+1)*A),O=0,L=0;for(let W=X;W<H&&W<V.length;W+=1)O+=V[W]??0,L+=1;_[D]=L>0?O/L:0,D+=1,X=H}return _},v=(V)=>{let g=null,C=null,A=null,R=null,_=0,D=null,X=(L)=>{L.processorNode?.disconnect(),L.sourceNode?.disconnect(),L.mediaStream?.getTracks().forEach((W)=>W.stop()),L.audioContext?.close()};return{start:()=>{if(R)return Promise.resolve();if(D)return D;let L=++_,I=(async()=>{if(!V.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let T=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!T)throw Error("Browser microphone capture requires AudioContext support.");let J=null,h=null,Z=null,Q=null;try{if(h=V.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:V.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),L!==_)throw Error("Microphone capture stopped during startup.");if(J=new T,J.state==="suspended")await J.resume();if(L!==_)throw Error("Microphone capture stopped during startup.");if(Q=J.createMediaStreamSource(h),Z=J.createScriptProcessor(4096,1,1),Z.onaudioprocess=(G)=>{let c=G.inputBuffer.getChannelData(0),q=N0(c,J?.sampleRate??48000,V.sampleRateHz??16000),$=q0(q);V.onLevel?.(z0($)),V.onAudio($)},Q.connect(Z),Z.connect(J.destination),L!==_)throw Error("Microphone capture stopped during startup.");g=J,R=h,A=Z,C=Q}catch(G){throw X({audioContext:J,mediaStream:h,processorNode:Z,sourceNode:Q}),G}})().finally(()=>{if(D===I)D=null});return D=I,I},stop:()=>{_+=1,X({audioContext:g,mediaStream:R,processorNode:A,sourceNode:C}),V.onLevel?.(0),g=null,R=null,A=null,C=null}}};var k=(V)=>{if(typeof V==="string"&&V.trim())return V;if(V instanceof Error&&V.message.trim())return V.message;if(V&&typeof V==="object"){let g=V;for(let C of["message","reason","description"]){let A=g[C];if(typeof A==="string"&&A.trim())return A}if("error"in g)return k(g.error);if("cause"in g)return k(g.cause);try{return JSON.stringify(V)}catch{}}return"Unexpected error"},i=(V)=>{switch(V.type){case"audio":return{chunk:Uint8Array.from(atob(V.chunkBase64),(g)=>g.charCodeAt(0)),format:V.format,receivedAt:V.receivedAt,turnId:V.turnId,type:"audio"};case"assistant":return{text:V.text,turnId:V.turnId,type:"assistant"};case"assistant_delta":return{delta:V.delta,turnId:V.turnId,type:"assistant_delta"};case"complete":return{sessionId:V.sessionId,type:"complete"};case"connection":return{reconnect:V.reconnect,type:"connection"};case"call_lifecycle":return{event:V.event,sessionId:V.sessionId,type:"call_lifecycle"};case"error":return{message:k(V.message),recoverable:V.recoverable,type:"error"};case"call_control_ack":return null;case"final":return{transcript:V.transcript,type:"final"};case"partial":return{transcript:V.transcript,type:"partial"};case"replay":return{assistantTexts:V.assistantTexts,call:V.call,partial:V.partial,scenarioId:V.scenarioId,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,status:V.status,turns:V.turns,type:"replay"};case"session":return{sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,type:"session"};case"turn":return{turn:V.turn,type:"turn"};default:return null}};var UV=Math.PI*2;var z=(V,g,C,A)=>{V.push({code:C,message:A,severity:g})};var P0=(V)=>V.length===0?void 0:V.reduce((g,C)=>g+C,0)/V.length,j=(V)=>V.length===0?void 0:Math.max(...V);var K=(V,g)=>{let C=V[g];return typeof C==="number"&&Number.isFinite(C)?C:void 0},b=(V,g)=>{let C=V[g];return typeof C==="boolean"?C:void 0},E=(V,g)=>{let C=V[g];return typeof C==="string"?C:void 0},F=(V)=>String(V.id??E(V,"ssrc")??K(V,"ssrc")??E(V,"trackIdentifier")??E(V,"mid")??"unknown"),o=(V)=>V===void 0?void 0:V*1000;var B0=(V)=>{let g={};for(let[C,A]of Object.entries(V))if(A===null||typeof A==="boolean"||typeof A==="number"||typeof A==="string")g[C]=A;return g};var n=(V={})=>{let g=V.stats??[],C=[],A=g.filter(($)=>$.type==="inbound-rtp"&&E($,"kind")!=="video"),R=g.filter(($)=>$.type==="outbound-rtp"&&E($,"kind")!=="video"),_=g.filter(($)=>$.type==="candidate-pair"),D=g.filter(($)=>($.type==="track"||$.type==="media-source")&&E($,"kind")==="audio"),X=_.filter(($)=>b($,"selected")===!0||b($,"nominated")===!0||E($,"state")==="succeeded").length,H=D.filter(($)=>E($,"readyState")!=="ended"&&E($,"trackState")!=="ended"&&b($,"ended")!==!0).length,O=D.filter(($)=>E($,"readyState")==="ended"||E($,"trackState")==="ended"||b($,"ended")===!0).length,L=A.reduce(($,S)=>$+(K(S,"packetsReceived")??0),0),W=R.reduce(($,S)=>$+(K(S,"packetsSent")??0),0),I=[...A,...R].reduce(($,S)=>$+Math.max(0,K(S,"packetsLost")??0),0),T=L+I,J=T===0?0:I/T,h=A.reduce(($,S)=>$+(K(S,"bytesReceived")??0),0),Z=R.reduce(($,S)=>$+(K(S,"bytesSent")??0),0),Q=j(_.map(($)=>o(K($,"currentRoundTripTime")??K($,"roundTripTime"))).filter(($)=>$!==void 0)),G=j([...A,...R].map(($)=>o(K($,"jitter"))).filter(($)=>$!==void 0)),c=j(A.map(($)=>{let S=K($,"jitterBufferDelay"),B=K($,"jitterBufferEmittedCount");return S!==void 0&&B!==void 0&&B>0?S/B*1000:void 0}).filter(($)=>$!==void 0)),q=D.map(($)=>K($,"audioLevel")).filter(($)=>$!==void 0);if(V.requireConnectedCandidatePair&&_.length>0&&X===0)z(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(V.requireLiveAudioTrack&&H===0)z(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(V.maxPacketLossRatio!==void 0&&J>V.maxPacketLossRatio)z(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(J)} above ${String(V.maxPacketLossRatio)}.`);if(V.maxRoundTripTimeMs!==void 0&&Q!==void 0&&Q>V.maxRoundTripTimeMs)z(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(Q)}ms above ${String(V.maxRoundTripTimeMs)}ms.`);if(V.maxJitterMs!==void 0&&G!==void 0&&G>V.maxJitterMs)z(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(G)}ms above ${String(V.maxJitterMs)}ms.`);return{activeCandidatePairs:X,audioLevelAverage:P0(q),bytesReceived:h,bytesSent:Z,checkedAt:Date.now(),endedAudioTracks:O,inboundPackets:L,issues:C,jitterBufferDelayMs:c,jitterMs:G,liveAudioTracks:H,outboundPackets:W,packetLossRatio:J,packetsLost:I,roundTripTimeMs:Q,status:C.some(($)=>$.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:g.length}},m=async(V)=>{return[...(await V.peerConnection.getStats(V.selector??null)).values()].map(B0)};var u=(V={})=>{let g=V.stats??[],C=V.previousStats??[],A=[],R=new Map(C.map((I)=>[F(I),I])),D=g.filter((I)=>(I.type==="inbound-rtp"||I.type==="outbound-rtp")&&E(I,"kind")!=="video"&&E(I,"mediaType")!=="video").map((I)=>{let T=I.type==="outbound-rtp"?"outbound":"inbound",J=T==="outbound"?"packetsSent":"packetsReceived",h=T==="outbound"?"bytesSent":"bytesReceived",Z=R.get(F(I)),Q=K(I,J),G=Z?K(Z,J):void 0,c=K(I,h),q=Z?K(Z,h):void 0,$=I.timestamp!==void 0&&Z?.timestamp!==void 0?I.timestamp-Z.timestamp:void 0;return{bytesDelta:c!==void 0&&q!==void 0?c-q:void 0,currentPackets:Q,direction:T,id:F(I),packetDelta:Q!==void 0&&G!==void 0?Q-G:void 0,previousPackets:G,timeDeltaMs:$}}),X=D.filter((I)=>I.direction==="inbound"),H=D.filter((I)=>I.direction==="outbound"),O=j(D.map((I)=>I.timeDeltaMs).filter((I)=>I!==void 0)),L=X.filter((I)=>V.maxInboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=V.maxInboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length,W=H.filter((I)=>V.maxOutboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=V.maxOutboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length;if(V.requireInboundAudio&&X.length===0)z(A,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(V.requireOutboundAudio&&H.length===0)z(A,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(V.maxGapMs!==void 0&&O!==void 0&&O>V.maxGapMs)z(A,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(O)}ms above ${String(V.maxGapMs)}ms.`);if(L>0)z(A,"error","media.webrtc_inbound_stalled",`${String(L)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(W>0)z(A,"error","media.webrtc_outbound_stalled",`${String(W)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:X.length,issues:A,maxObservedGapMs:O,outboundAudioStreams:H.length,stalledInboundStreams:L,stalledOutboundStreams:W,status:A.some((I)=>I.severity==="error")?"fail":A.length>0?"warn":"pass",streams:D,totalStats:g.length}};var M0="/api/voice/browser-media",b0=5000,j0=async(V)=>V.peerConnection??await V.getPeerConnection?.()??null,x0=async(V,g)=>{let C=g.fetch??globalThis.fetch;if(!C)return;await C(g.path??M0,{body:JSON.stringify(V),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(V)=>{let g=null,C=[],A=async()=>{let D=await j0(V);if(!D)return;let X=await m({peerConnection:D}),H=n({...V,stats:X}),O=V.continuity===!1?void 0:u({...V.continuity,previousStats:C,stats:X}),L={at:Date.now(),continuity:O,report:H,scenarioId:V.getScenarioId?.()??null,sessionId:V.getSessionId?.()??null};return C=X,V.onReport?.(L),await x0(L,V),L},R=()=>{A().catch((D)=>{V.onError?.(D)})},_=()=>{if(g)clearInterval(g),g=null};return{close:_,reportOnce:A,stop:_,start:()=>{if(g)return;R(),g=setInterval(R,V.intervalMs??b0)}}};var k0=(V,g,C)=>Math.min(C,g*2**(Math.max(1,V)-1));class s extends Error{constructor(V="Voice authorization could not be renewed."){super(V);this.name="VoiceReconnectRejectedError"}}var P=()=>{},F0=()=>Promise.resolve(),f0=()=>P,l0={callControl:F0,close:P,disconnect:P,endTurn:P,send:P,sendAudio:P,simulateDisconnect:P,subscribe:f0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},d0=()=>crypto.randomUUID(),v0=(V,g,C,A)=>{let{hostname:R,port:_,protocol:D}=window.location,X=D==="https:"?"wss:":"ws:",H=_?`:${_}`:"",O=new URL(`${X}//${R}${H}${V}`);if(O.searchParams.set("sessionId",g),C)O.searchParams.set("scenarioId",C);for(let[L,W]of Object.entries(A))if(L!=="sessionId"&&L!=="scenarioId"&&W)O.searchParams.set(L,W);return O.toString()},i0=(V)=>{if(!V||typeof V!=="object"||!("type"in V))return!1;switch(V.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}},o0=(V)=>{if(typeof V.data!=="string")return null;try{let g=JSON.parse(V.data);return i0(g)?g:null}catch{return null}},a=(V,g={})=>{if(typeof window>"u")return l0;let C=new Set,A=new Map,R=g.reconnect!==!1,_=g.maxReconnectAttempts??15,D=g.reconnectMaxDelayMs??8000,X=g.pingInterval??30000,H=g.reconnectResetAfterMs??30000,O=0,L=!1,W=null,I=(U)=>k0(U,500,D),T={isConnected:!1,pendingMessages:[],scenarioId:g.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:g.sessionId??d0(),ws:null},J=(U)=>{C.forEach((w)=>w(U))},h=(U)=>{C.forEach((w)=>w({message:U,recoverable:!1,type:"error"})),J({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"})},Z=()=>{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},Q=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let U=T.pendingMessages.shift();if(U!==void 0)T.ws.send(U)}},G=(U)=>{for(let w of A.values())clearTimeout(w.timeout),w.reject(Error(U));A.clear()},c=()=>{if(L)return;let U=O;T.reconnectAttempts+=1;let w=I(T.reconnectAttempts),y=Date.now()+w;J({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:y,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(async()=>{if(L||O!==U)return;if(T.reconnectAttempts>_){h("Voice connection could not be restored.");return}try{if(g.prepareReconnect){let Y=new AbortController;W=Y;let M=setTimeout(()=>Y.abort("Voice reconnect preparation timed out"),g.prepareReconnectTimeoutMs??1e4);try{await Promise.race([g.prepareReconnect({attempt:T.reconnectAttempts,path:V,scenarioId:T.scenarioId,sessionId:T.sessionId,signal:Y.signal}),new Promise((f,O0)=>{Y.signal.addEventListener("abort",()=>O0(Error("Voice reconnect preparation timed out.")),{once:!0})})])}finally{if(clearTimeout(M),W===Y)W=null}}}catch(Y){if(L||O!==U)return;if(Y instanceof s){h(Y.message);return}if(T.reconnectAttempts<_)c();else h("Voice authorization could not be renewed for reconnect.");return}if(L||O!==U)return;q()},w)},q=()=>{if(L)return;let U=new WebSocket(v0(V,T.sessionId,T.scenarioId,g.query??{}));U.binaryType="arraybuffer",U.onopen=()=>{if(T.ws!==U){U.close(1000);return}let w=T.reconnectAttempts>0;if(T.isConnected=!0,Q(),w)J({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),T.reconnectResetTimeout=setTimeout(()=>{if(T.ws===U&&U.readyState===1)T.reconnectAttempts=0;T.reconnectResetTimeout=null},H);C.forEach((y)=>y({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(U.readyState===1)U.send(JSON.stringify({type:"ping"}))},X)},U.onmessage=(w)=>{if(T.ws!==U)return;let y=o0(w);if(!y)return;if(y.type==="session")T.sessionId=y.sessionId,T.scenarioId=y.scenarioId??T.scenarioId;if(y.type==="call_control_ack"){let Y=A.get(y.requestId);if(Y)if(clearTimeout(Y.timeout),A.delete(y.requestId),y.ok)Y.resolve();else Y.reject(Error(y.message??`${y.action} was not applied`))}C.forEach((Y)=>Y(y))},U.onclose=(w)=>{if(T.ws!==U)return;T.ws=null,T.isConnected=!1,Z(),G("Voice connection interrupted");let y=R&&w.code!==1000&&w.code!==4401&&w.code!==4403&&T.reconnectAttempts<_,Y=w.code===4401||w.code===4403;if(Y)C.forEach((M)=>M({message:w.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(y)c();else if(R&&w.code!==1000)if(Y)J({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"});else h("Voice connection could not be restored.")},T.ws=U},$=(U)=>{if(L)return;if(T.ws?.readyState===1){T.ws.send(U);return}if(typeof U!=="string")return;if(T.pendingMessages.length>=100)T.pendingMessages.shift();T.pendingMessages.push(U)},S=(U)=>{$(JSON.stringify(U))},B=(U={})=>{if(U.sessionId)T.sessionId=U.sessionId;if(U.scenarioId)T.scenarioId=U.scenarioId;S({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},I0=(U)=>{$(U)},U0=()=>{S({type:"end_turn"})},L0=(U)=>{if(T.ws?.readyState!==1)return Promise.reject(Error(`${U.action} is unavailable while disconnected`));let w=crypto.randomUUID(),{promise:y,reject:Y,resolve:M}=Promise.withResolvers(),f=setTimeout(()=>{A.delete(w),Y(Error(`${U.action} acknowledgement timed out`))},5000);return A.set(w,{reject:Y,resolve:M,timeout:f}),T.ws.send(JSON.stringify({...U,requestId:w,type:"call_control"})),y},W0=(U="client-close")=>{if(L=!0,W?.abort(),W=null,O+=1,Z(),G("Voice connection closed"),T.pendingMessages.length=0,T.ws){let w=T.ws;if(T.ws=null,w.readyState===1)w.send(JSON.stringify({reason:U,type:"close"}));w.close(1000)}T.isConnected=!1,C.clear()},D0=()=>{if(L=!0,W?.abort(),W=null,O+=1,Z(),G("Voice connection disconnected"),T.pendingMessages.length=0,T.ws){let U=T.ws;T.ws=null,U.close(1000)}T.isConnected=!1,C.clear()},X0=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},G0=(U)=>{return C.add(U),()=>{C.delete(U)}};return q(),{callControl:L0,close:W0,disconnect:D0,endTurn:U0,send:S,sendAudio:I0,simulateDisconnect:X0,start:B,subscribe:G0,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var n0=()=>({attempts:0,maxAttempts:0,status:"idle"}),m0=(V,g)=>{let C=g.trim().replace(/\s+/g," ");if(!C)return V;if(!V)return C;if(V===C||V.endsWith(C))return V;if(C.includes(V))return C;return`${V} ${C}`},u0=(V,g)=>{let C=g.trim().replace(/\s+/g," ");if(!V)return C;if(!C||V.endsWith(C))return V;return`${V} ${C}`},r0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:n0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),p=()=>{let V=r0(),g="",C=new Set,A=()=>{C.forEach((_)=>_())};return{dispatch:(_)=>{switch(_.type){case"audio":V={...V,assistantAudio:[...V.assistantAudio,{chunk:_.chunk,format:_.format,receivedAt:_.receivedAt,turnId:_.turnId}]};break;case"assistant":V={...V,assistantStreamingText:"",assistantTexts:[...V.assistantTexts,_.text]};break;case"assistant_delta":V={...V,assistantStreamingText:`${V.assistantStreamingText}${_.delta}`};break;case"complete":V={...V,sessionId:_.sessionId,status:"completed"};break;case"call_lifecycle":V={...V,call:{...V.call,disposition:_.event.type==="end"?_.event.disposition:V.call?.disposition,endedAt:_.event.type==="end"?_.event.at:V.call?.endedAt,events:[...V.call?.events??[],_.event],lastEventAt:_.event.at,startedAt:V.call?.startedAt??_.event.at},sessionId:_.sessionId};break;case"connected":V={...V,isConnected:!0,reconnect:V.reconnect.status==="reconnecting"?{...V.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:V.reconnect};break;case"connection":V={...V,reconnect:_.reconnect};break;case"disconnected":V={...V,isConnected:!1};break;case"error":V={...V,error:_.message,..._.recoverable===!1?{isConnected:!1,status:"failed"}:{}};break;case"final":g=m0(g,_.transcript.text),V={...V,partial:g};break;case"partial":V={...V,partial:u0(g,_.transcript.text)};break;case"replay":g=_.partial,V={...V,assistantStreamingText:"",assistantTexts:[..._.assistantTexts],call:_.call??null,error:null,isConnected:_.status==="active",partial:_.partial,reconnect:V.reconnect.status==="reconnecting"?{...V.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:V.reconnect,scenarioId:_.scenarioId??V.scenarioId,sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status,turns:[..._.turns]};break;case"session":V={...V,error:null,scenarioId:_.scenarioId??V.scenarioId,isConnected:_.status==="active",sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status};break;case"turn":g="",V={...V,partial:"",turns:[...V.turns,_.turn]};break}A()},getServerSnapshot:()=>V,getSnapshot:()=>V,subscribe:(_)=>{return C.add(_),()=>{C.delete(_)}}}};var t=(V,g={})=>{let C=a(V,g),A=p(),R=g.browserMedia&&typeof window<"u"?r({...g.browserMedia,getScenarioId:()=>g.browserMedia?g.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>g.browserMedia?g.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,_=new Set,D=!1,X=(W)=>Promise.resolve().then(()=>{if(D)return;if(!W?.sessionId&&!W?.scenarioId)return;C.start(W),R?.start()}),H=()=>{_.forEach((W)=>W())},O=()=>{if(!g.reconnectReportPath||typeof fetch>"u")return;let W=A.getSnapshot(),I=JSON.stringify({at:Date.now(),reconnect:W.reconnect,scenarioId:W.scenarioId,sessionId:C.getSessionId(),turnIds:W.turns.map((T)=>T.id)});fetch(g.reconnectReportPath,{body:I,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},L=C.subscribe((W)=>{let I=i(W);if(I){if(A.dispatch(I),W.type==="connection")O();H()}});return{start:X,get assistantAudio(){return A.getSnapshot().assistantAudio},get assistantTexts(){return A.getSnapshot().assistantTexts},get assistantStreamingText(){return A.getSnapshot().assistantStreamingText},get call(){return A.getSnapshot().call},callControl(W){return C.callControl(W)},close(W){D=!0,L(),R?.close(),C.close(W),A.dispatch({type:"disconnected"}),H()},disconnect(){D=!0,L(),R?.close(),C.disconnect(),A.dispatch({type:"disconnected"}),H()},endTurn(){C.endTurn()},get error(){return A.getSnapshot().error},getServerSnapshot(){return A.getServerSnapshot()},getSnapshot(){return A.getSnapshot()},get isConnected(){return A.getSnapshot().isConnected},get partial(){return A.getSnapshot().partial},get reconnect(){return A.getSnapshot().reconnect},get scenarioId(){return A.getSnapshot().scenarioId},sendAudio(W){C.sendAudio(W)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return A.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return A.getSnapshot().status},subscribe(W){return _.add(W),()=>{_.delete(W)}},get turns(){return A.getSnapshot().turns}}};var e=(V)=>{if(!V||V.enabled===!1)return;return{enabled:!0,maxGain:V.maxGain??3,noiseGateAttenuation:V.noiseGateAttenuation??0.15,noiseGateThreshold:V.noiseGateThreshold??0.006,targetLevel:V.targetLevel??0.08}};var s0={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}},a0={"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 V0=(V)=>{let g=V?.profile??"fast",C=V?.qualityProfile??"general",A=s0[g],R=a0[C],_=V?.silenceMs??R.silenceMs??A.silenceMs;return{profile:g,qualityProfile:C,minSilenceMs:Math.min(_,V?.minSilenceMs??R.minSilenceMs??A.minSilenceMs),silenceMs:_,speechThreshold:V?.speechThreshold??R.speechThreshold??A.speechThreshold,transcriptStabilityMs:V?.transcriptStabilityMs??R.transcriptStabilityMs??A.transcriptStabilityMs}};var p0={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=(V="default")=>{let g=p0[V];return{audioConditioning:e(g.audioConditioning),capture:{channelCount:g.capture?.channelCount??1,sampleRateHz:g.capture?.sampleRateHz??16000},connection:{...g.connection},name:V,sttLifecycle:g.sttLifecycle??"continuous",turnDetection:V0(g.turnDetection)}};var t0=(V)=>({assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,isRecording:!1,partial:V.partial,reconnect:V.reconnect,recordingError:null,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]}),A0=(V,g={})=>{let C=g0(g.preset),A=t(V,{...C.connection,...g.connection}),R=null,_=0,D=!1,X=null,H=t0(A),O=new Set,L=()=>{for(let G of O)G()},W=()=>{if(H={...H,assistantAudio:[...A.assistantAudio],assistantStreamingText:A.assistantStreamingText,assistantTexts:[...A.assistantTexts],call:A.call,error:A.error,isConnected:A.isConnected,partial:A.partial,reconnect:A.reconnect,sessionId:A.sessionId,sessionMetadata:A.sessionMetadata,scenarioId:A.scenarioId,status:A.status,turns:[...A.turns]},g.autoStopOnComplete!==!1&&H.status==="completed"&&H.isRecording)R?.stop(),R=null,H={...H,isRecording:!1};L()},I=A.subscribe(W);W();let T=()=>{if(R)return R;return R=v({channelCount:g.capture?.channelCount??C.capture.channelCount,onLevel:g.capture?.onLevel,onAudio:(G)=>{if(g.capture?.onAudio){g.capture.onAudio(G,A.sendAudio);return}A.sendAudio(G)},sampleRateHz:g.capture?.sampleRateHz??C.capture.sampleRateHz,...g.capture?.stream?{stream:g.capture.stream}:{}}),R},J=()=>{_+=1,R?.stop(),R=null,H={...H,isRecording:!1},L()},h=async()=>{if(D)throw Error("Voice controller is closed.");if(H.isRecording)return;if(X)return X;let G=++_,c=T(),$=(async()=>{try{if(H={...H,recordingError:null},L(),await c.start(),D||G!==_||R!==c){c.stop();return}H={...H,isRecording:!0},L()}catch(S){if(c.stop(),G!==_||R!==c)throw S;throw R=null,H={...H,isRecording:!1,recordingError:S instanceof Error?S.message:String(S)},L(),S}})().finally(()=>{if(X===$)X=null});return X=$,X};return{close:(G)=>{D=!0,I(),J(),A.close(G)},disconnect:()=>{D=!0,I(),J(),A.disconnect()},startRecording:h,stopRecording:J,get assistantAudio(){return H.assistantAudio},get assistantTexts(){return H.assistantTexts},get assistantStreamingText(){return H.assistantStreamingText},bindHTMX(G){return d(A,G)},get call(){return H.call},callControl:(G)=>A.callControl(G),endTurn:()=>A.endTurn(),get error(){return H.error},getServerSnapshot:()=>H,getSnapshot:()=>H,get isConnected(){return H.isConnected},get isRecording(){return H.isRecording},get partial(){return H.partial},get reconnect(){return H.reconnect},get recordingError(){return H.recordingError},get scenarioId(){return H.scenarioId},sendAudio:(G)=>A.sendAudio(G),get sessionId(){return H.sessionId},get sessionMetadata(){return H.sessionMetadata},simulateDisconnect:()=>A.simulateDisconnect(),get status(){return H.status},subscribe:(G)=>{return O.add(G),()=>{O.delete(G)}},toggleRecording:async()=>{if(H.isRecording){J();return}await h()},get turns(){return H.turns}}};var N=(V)=>String(V).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var C0=(V)=>{if(!V.isConnected)return"idle";if(V.isPlaying)return"speaking";if(V.isRecording&&V.hasActivePartial)return"listening";if(V.isRecording)return"listening";if(V.lastTranscriptAt&&!V.lastAssistantAt)return"thinking";if(V.lastTranscriptAt&&V.lastAssistantAt&&V.lastTranscriptAt>V.lastAssistantAt)return"thinking";return"idle"};var e0={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},VV={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},gV=(V,g)=>{switch(V){case"listening":return g.listening;case"speaking":return g.speaking;case"thinking":return g.thinking;case"idle":return g.idle}},_0=(V)=>{let g={...e0,...V.theme},C={...VV,...V.labels},A=V.state.assistantAudio.at(-1)?.receivedAt,R=V.state.turns.at(-1)?.committedAt,_=C0({hasActivePartial:V.state.partial.length>0,isConnected:V.state.isConnected,isPlaying:!1,isRecording:V.state.isRecording,lastAssistantAt:A,lastTranscriptAt:R}),D=!V.state.isConnected&&V.state.status!=="idle"&&!V.state.error,X=V.state.error?"Error":D?C.connecting:V.state.status==="completed"?C.callEnded:gV(_,C);return{agentState:_,classes:{container:`absolute-voice-widget absolute-voice-widget--${_}`,dot:`absolute-voice-widget__dot${V.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:V.state.isConnected,canMute:V.state.isRecording,canStart:!V.state.isRecording&&V.state.status!=="completed"},errorMessage:V.state.error??void 0,labels:C,partial:V.state.partial||void 0,statusLabel:X,theme:g,title:V.title??"Voice"}},AV=(V)=>typeof V==="number"?`${V}px`:V,T0=(V)=>{let g=V.theme,C=`background:${g.background};border-radius:${AV(g.radius)};color:${g.foreground};font-family:${g.fontFamily};min-width:240px;padding:20px 22px;`,A=`background:${V.errorMessage?g.errorAccent:V.agentState==="idle"?"rgba(148,163,184,0.6)":g.accent};border-radius:50%;height:10px;width:10px;`,R=[];if(V.controls.canStart)R.push(`<button type="button" data-action="start" style="background:${g.accent};border:none;border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(V.labels.startCall)}</button>`);if(V.controls.canMute)R.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(V.labels.mute)}</button>`);if(V.controls.canEnd)R.push(`<button type="button" data-action="end" style="background:${g.errorAccent};border:none;border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${N(V.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${V.agentState}" class="${N(V.classes.container)}" style="${C}">
|
|
1
|
+
(()=>{var{defineProperty:k,getOwnPropertyNames:w0,getOwnPropertyDescriptor:J0}=Object,S0=Object.prototype.hasOwnProperty;function Y0(V){return this[V]}var Z0=(V)=>{var g=(l??=new WeakMap).get(V),C;if(g)return g;if(g=k({},"__esModule",{value:!0}),V&&typeof V==="object"||typeof V==="function"){for(var A of w0(V))if(!S0.call(g,A))k(g,A,{get:Y0.bind(V,A),enumerable:!(C=J0(V,A))||C.enumerable})}return l.set(V,g),g},l;var y0=(V)=>V;function h0(V,g){this[V]=y0.bind(null,g)}var Q0=(V,g)=>{for(var C in g)k(V,C,{get:g[C],enumerable:!0,configurable:!0,set:h0.bind(g,C)})};var _V={};Q0(_V,{mount:()=>$0,default:()=>TV,VOICE_EMBED_VERSION:()=>R0});var K0=(V)=>{if(typeof V!=="string")return V;return document.querySelector(V)},c0=(V,g,C,A)=>{let R=g??V.getAttribute("hx-get")??"";if(!R)return"";let _=new URL(R,window.location.origin);if(A)_.searchParams.set(C,A);else _.searchParams.delete(C);return`${_.pathname}${_.search}${_.hash}`},d=(V,g)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=K0(g.element);if(!C)return()=>{};let A=g.eventName??"voice-refresh",R=g.sessionQueryParam??"sessionId",_=()=>{let X=window,H=c0(C,g.route,R,V.sessionId);if(H)C.setAttribute("hx-get",H);X.htmx?.process?.(C),X.htmx?.trigger?.(C,A)},D=V.subscribe(_);return _(),()=>{D()}};var E0=(V)=>Math.max(-1,Math.min(1,V)),q0=(V)=>{let g=new Int16Array(V.length);for(let C=0;C<V.length;C+=1){let A=E0(V[C]??0);g[C]=A<0?A*32768:A*32767}return new Uint8Array(g.buffer)},N0=(V)=>{let g=V instanceof Uint8Array?V:new Uint8Array(V);if(g.byteLength<2)return 0;let C=new Int16Array(g.buffer,g.byteOffset,Math.floor(g.byteLength/2));if(C.length===0)return 0;let A=0;for(let R of C){let _=R/32768;A+=_*_}return Math.min(1,Math.max(0,Math.sqrt(A/C.length)*5.5))},z0=(V,g,C)=>{if(g===C)return V;let A=g/C,R=Math.round(V.length/A),_=new Float32Array(R),D=0,X=0;while(D<_.length){let H=Math.round((D+1)*A),O=0,L=0;for(let W=X;W<H&&W<V.length;W+=1)O+=V[W]??0,L+=1;_[D]=L>0?O/L:0,D+=1,X=H}return _},v=(V)=>{let g=null,C=null,A=null,R=null,_=0,D=null,X=(L)=>{L.processorNode?.disconnect(),L.sourceNode?.disconnect(),L.mediaStream?.getTracks().forEach((W)=>W.stop()),L.audioContext?.close()};return{start:()=>{if(R)return Promise.resolve();if(D)return D;let L=++_,U=(async()=>{if(!V.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let T=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!T)throw Error("Browser microphone capture requires AudioContext support.");let J=null,h=null,Z=null,Q=null;try{if(h=V.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:V.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),L!==_)throw Error("Microphone capture stopped during startup.");if(J=new T,J.state==="suspended")await J.resume();if(L!==_)throw Error("Microphone capture stopped during startup.");if(Q=J.createMediaStreamSource(h),Z=J.createScriptProcessor(4096,1,1),Z.onaudioprocess=(G)=>{let c=G.inputBuffer.getChannelData(0),q=z0(c,J?.sampleRate??48000,V.sampleRateHz??16000),$=q0(q);V.onLevel?.(N0($)),V.onAudio($)},Q.connect(Z),Z.connect(J.destination),L!==_)throw Error("Microphone capture stopped during startup.");g=J,R=h,A=Z,C=Q}catch(G){throw X({audioContext:J,mediaStream:h,processorNode:Z,sourceNode:Q}),G}})().finally(()=>{if(D===U)D=null});return D=U,U},stop:()=>{_+=1,X({audioContext:g,mediaStream:R,processorNode:A,sourceNode:C}),V.onLevel?.(0),g=null,R=null,A=null,C=null}}};var F=(V)=>{if(typeof V==="string"&&V.trim())return V;if(V instanceof Error&&V.message.trim())return V.message;if(V&&typeof V==="object"){let g=V;for(let C of["message","reason","description"]){let A=g[C];if(typeof A==="string"&&A.trim())return A}if("error"in g)return F(g.error);if("cause"in g)return F(g.cause);try{return JSON.stringify(V)}catch{}}return"Unexpected error"},i=(V)=>{switch(V.type){case"audio":return{chunk:Uint8Array.from(atob(V.chunkBase64),(g)=>g.charCodeAt(0)),format:V.format,receivedAt:V.receivedAt,turnId:V.turnId,type:"audio"};case"assistant":return{text:V.text,turnId:V.turnId,type:"assistant"};case"assistant_delta":return{delta:V.delta,turnId:V.turnId,type:"assistant_delta"};case"complete":return{sessionId:V.sessionId,type:"complete"};case"connection":return{reconnect:V.reconnect,type:"connection"};case"call_lifecycle":return{event:V.event,sessionId:V.sessionId,type:"call_lifecycle"};case"error":return{message:F(V.message),recoverable:V.recoverable,type:"error"};case"call_control_ack":return null;case"final":return{transcript:V.transcript,type:"final"};case"partial":return{transcript:V.transcript,type:"partial"};case"replay":return{assistantTexts:V.assistantTexts,call:V.call,partial:V.partial,scenarioId:V.scenarioId,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,status:V.status,turns:V.turns,type:"replay"};case"session":return{sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,type:"session"};case"turn":return{turn:V.turn,type:"turn"};default:return null}};var UV=Math.PI*2;var N=(V,g,C,A)=>{V.push({code:C,message:A,severity:g})};var P0=(V)=>V.length===0?void 0:V.reduce((g,C)=>g+C,0)/V.length,x=(V)=>V.length===0?void 0:Math.max(...V);var K=(V,g)=>{let C=V[g];return typeof C==="number"&&Number.isFinite(C)?C:void 0},j=(V,g)=>{let C=V[g];return typeof C==="boolean"?C:void 0},E=(V,g)=>{let C=V[g];return typeof C==="string"?C:void 0},f=(V)=>String(V.id??E(V,"ssrc")??K(V,"ssrc")??E(V,"trackIdentifier")??E(V,"mid")??"unknown"),o=(V)=>V===void 0?void 0:V*1000;var B0=(V)=>{let g={};for(let[C,A]of Object.entries(V))if(A===null||typeof A==="boolean"||typeof A==="number"||typeof A==="string")g[C]=A;return g};var n=(V={})=>{let g=V.stats??[],C=[],A=g.filter(($)=>$.type==="inbound-rtp"&&E($,"kind")!=="video"),R=g.filter(($)=>$.type==="outbound-rtp"&&E($,"kind")!=="video"),_=g.filter(($)=>$.type==="candidate-pair"),D=g.filter(($)=>($.type==="track"||$.type==="media-source")&&E($,"kind")==="audio"),X=_.filter(($)=>j($,"selected")===!0||j($,"nominated")===!0||E($,"state")==="succeeded").length,H=D.filter(($)=>E($,"readyState")!=="ended"&&E($,"trackState")!=="ended"&&j($,"ended")!==!0).length,O=D.filter(($)=>E($,"readyState")==="ended"||E($,"trackState")==="ended"||j($,"ended")===!0).length,L=A.reduce(($,S)=>$+(K(S,"packetsReceived")??0),0),W=R.reduce(($,S)=>$+(K(S,"packetsSent")??0),0),U=[...A,...R].reduce(($,S)=>$+Math.max(0,K(S,"packetsLost")??0),0),T=L+U,J=T===0?0:U/T,h=A.reduce(($,S)=>$+(K(S,"bytesReceived")??0),0),Z=R.reduce(($,S)=>$+(K(S,"bytesSent")??0),0),Q=x(_.map(($)=>o(K($,"currentRoundTripTime")??K($,"roundTripTime"))).filter(($)=>$!==void 0)),G=x([...A,...R].map(($)=>o(K($,"jitter"))).filter(($)=>$!==void 0)),c=x(A.map(($)=>{let S=K($,"jitterBufferDelay"),b=K($,"jitterBufferEmittedCount");return S!==void 0&&b!==void 0&&b>0?S/b*1000:void 0}).filter(($)=>$!==void 0)),q=D.map(($)=>K($,"audioLevel")).filter(($)=>$!==void 0);if(V.requireConnectedCandidatePair&&_.length>0&&X===0)N(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(V.requireLiveAudioTrack&&H===0)N(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(V.maxPacketLossRatio!==void 0&&J>V.maxPacketLossRatio)N(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(J)} above ${String(V.maxPacketLossRatio)}.`);if(V.maxRoundTripTimeMs!==void 0&&Q!==void 0&&Q>V.maxRoundTripTimeMs)N(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(Q)}ms above ${String(V.maxRoundTripTimeMs)}ms.`);if(V.maxJitterMs!==void 0&&G!==void 0&&G>V.maxJitterMs)N(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(G)}ms above ${String(V.maxJitterMs)}ms.`);return{activeCandidatePairs:X,audioLevelAverage:P0(q),bytesReceived:h,bytesSent:Z,checkedAt:Date.now(),endedAudioTracks:O,inboundPackets:L,issues:C,jitterBufferDelayMs:c,jitterMs:G,liveAudioTracks:H,outboundPackets:W,packetLossRatio:J,packetsLost:U,roundTripTimeMs:Q,status:C.some(($)=>$.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:g.length}},m=async(V)=>{return[...(await V.peerConnection.getStats(V.selector??null)).values()].map(B0)};var u=(V={})=>{let g=V.stats??[],C=V.previousStats??[],A=[],R=new Map(C.map((U)=>[f(U),U])),D=g.filter((U)=>(U.type==="inbound-rtp"||U.type==="outbound-rtp")&&E(U,"kind")!=="video"&&E(U,"mediaType")!=="video").map((U)=>{let T=U.type==="outbound-rtp"?"outbound":"inbound",J=T==="outbound"?"packetsSent":"packetsReceived",h=T==="outbound"?"bytesSent":"bytesReceived",Z=R.get(f(U)),Q=K(U,J),G=Z?K(Z,J):void 0,c=K(U,h),q=Z?K(Z,h):void 0,$=U.timestamp!==void 0&&Z?.timestamp!==void 0?U.timestamp-Z.timestamp:void 0;return{bytesDelta:c!==void 0&&q!==void 0?c-q:void 0,currentPackets:Q,direction:T,id:f(U),packetDelta:Q!==void 0&&G!==void 0?Q-G:void 0,previousPackets:G,timeDeltaMs:$}}),X=D.filter((U)=>U.direction==="inbound"),H=D.filter((U)=>U.direction==="outbound"),O=x(D.map((U)=>U.timeDeltaMs).filter((U)=>U!==void 0)),L=X.filter((U)=>V.maxInboundPacketStallMs!==void 0&&U.timeDeltaMs!==void 0&&U.timeDeltaMs>=V.maxInboundPacketStallMs&&U.packetDelta!==void 0&&U.packetDelta<=0).length,W=H.filter((U)=>V.maxOutboundPacketStallMs!==void 0&&U.timeDeltaMs!==void 0&&U.timeDeltaMs>=V.maxOutboundPacketStallMs&&U.packetDelta!==void 0&&U.packetDelta<=0).length;if(V.requireInboundAudio&&X.length===0)N(A,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(V.requireOutboundAudio&&H.length===0)N(A,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(V.maxGapMs!==void 0&&O!==void 0&&O>V.maxGapMs)N(A,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(O)}ms above ${String(V.maxGapMs)}ms.`);if(L>0)N(A,"error","media.webrtc_inbound_stalled",`${String(L)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(W>0)N(A,"error","media.webrtc_outbound_stalled",`${String(W)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:X.length,issues:A,maxObservedGapMs:O,outboundAudioStreams:H.length,stalledInboundStreams:L,stalledOutboundStreams:W,status:A.some((U)=>U.severity==="error")?"fail":A.length>0?"warn":"pass",streams:D,totalStats:g.length}};var M0="/api/voice/browser-media",b0=5000,j0=async(V)=>V.peerConnection??await V.getPeerConnection?.()??null,x0=async(V,g)=>{let C=g.fetch??globalThis.fetch;if(!C)return;await C(g.path??M0,{body:JSON.stringify(V),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(V)=>{let g=null,C=[],A=async()=>{let D=await j0(V);if(!D)return;let X=await m({peerConnection:D}),H=n({...V,stats:X}),O=V.continuity===!1?void 0:u({...V.continuity,previousStats:C,stats:X}),L={at:Date.now(),continuity:O,report:H,scenarioId:V.getScenarioId?.()??null,sessionId:V.getSessionId?.()??null};return C=X,V.onReport?.(L),await x0(L,V),L},R=()=>{A().catch((D)=>{V.onError?.(D)})},_=()=>{if(g)clearInterval(g),g=null};return{close:_,reportOnce:A,stop:_,start:()=>{if(g)return;R(),g=setInterval(R,V.intervalMs??b0)}}};var k0=(V,g,C)=>Math.min(C,g*2**(Math.max(1,V)-1));class s extends Error{constructor(V="Voice authorization could not be renewed."){super(V);this.name="VoiceReconnectRejectedError"}}var P=()=>{},F0=()=>Promise.resolve(),f0=()=>P,l0={callControl:F0,close:P,disconnect:P,endTurn:P,send:P,sendAudio:P,simulateDisconnect:P,subscribe:f0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},d0=()=>crypto.randomUUID(),v0=(V,g,C,A)=>{let{hostname:R,port:_,protocol:D}=window.location,X=D==="https:"?"wss:":"ws:",H=_?`:${_}`:"",O=new URL(`${X}//${R}${H}${V}`);if(O.searchParams.set("sessionId",g),C)O.searchParams.set("scenarioId",C);for(let[L,W]of Object.entries(A))if(L!=="sessionId"&&L!=="scenarioId"&&W)O.searchParams.set(L,W);return O.toString()},i0=(V)=>{if(!V||typeof V!=="object"||!("type"in V))return!1;switch(V.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}},o0=(V)=>{if(typeof V.data!=="string")return null;try{let g=JSON.parse(V.data);return i0(g)?g:null}catch{return null}},a=(V,g={})=>{if(typeof window>"u")return l0;let C=new Set,A=new Map,R=g.reconnect!==!1,_=g.maxReconnectAttempts??15,D=g.reconnectMaxDelayMs??8000,X=g.pingInterval??30000,H=g.reconnectResetAfterMs??30000,O=0,L=!1,W=null,U=(I)=>k0(I,500,D),T={isConnected:!1,pendingMessages:[],scenarioId:g.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:g.sessionId??d0(),ws:null},J=(I)=>{C.forEach((w)=>w(I))},h=(I)=>{C.forEach((w)=>w({message:I,recoverable:!1,type:"error"})),J({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"})},Z=()=>{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},Q=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let I=T.pendingMessages.shift();if(I!==void 0)T.ws.send(I)}},G=(I)=>{for(let w of A.values())clearTimeout(w.timeout),w.reject(Error(I));A.clear()},c=()=>{if(L)return;let I=O;T.reconnectAttempts+=1;let w=U(T.reconnectAttempts),y=Date.now()+w;J({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:y,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(async()=>{if(L||O!==I)return;if(T.reconnectAttempts>_){h("Voice connection could not be restored.");return}try{if(g.prepareReconnect){let Y=new AbortController;W=Y;let B=setTimeout(()=>Y.abort("Voice reconnect preparation timed out"),g.prepareReconnectTimeoutMs??1e4);try{await Promise.race([g.prepareReconnect({attempt:T.reconnectAttempts,path:V,scenarioId:T.scenarioId,sessionId:T.sessionId,signal:Y.signal}),new Promise((M,O0)=>{Y.signal.addEventListener("abort",()=>O0(Error("Voice reconnect preparation timed out.")),{once:!0})})])}finally{if(clearTimeout(B),W===Y)W=null}}}catch(Y){if(L||O!==I)return;if(Y instanceof s){h(Y.message);return}if(T.reconnectAttempts<_)c();else h("Voice authorization could not be renewed for reconnect.");return}if(L||O!==I)return;q()},w)},q=()=>{if(L)return;let I=new WebSocket(v0(V,T.sessionId,T.scenarioId,g.query??{}));I.binaryType="arraybuffer",I.onopen=()=>{if(T.ws!==I){I.close(1000);return}let w=T.reconnectAttempts>0;if(T.isConnected=!0,Q(),w)J({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},H);C.forEach((y)=>y({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"}))},X)},I.onmessage=(w)=>{if(T.ws!==I)return;let y=o0(w);if(!y)return;if(y.type==="session")T.sessionId=y.sessionId,T.scenarioId=y.scenarioId??T.scenarioId;if(y.type==="call_control_ack"){let Y=A.get(y.requestId);if(Y)if(clearTimeout(Y.timeout),A.delete(y.requestId),y.ok)Y.resolve();else Y.reject(Error(y.message??`${y.action} was not applied`))}C.forEach((Y)=>Y(y))},I.onclose=(w)=>{if(T.ws!==I)return;T.ws=null,T.isConnected=!1,Z(),G("Voice connection interrupted");let y=R&&w.code!==1000&&w.code!==4401&&w.code!==4403&&w.code!==4500&&T.reconnectAttempts<_,Y=w.code===4401||w.code===4403,B=w.code===4500;if(Y)C.forEach((M)=>M({message:w.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(B)C.forEach((M)=>M({message:"Voice provider initialization failed.",recoverable:!1,type:"error"}));if(y)c();else if(R&&w.code!==1000){if(Y)J({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"});else if(!B)h("Voice connection could not be restored.")}},T.ws=I},$=(I)=>{if(L)return;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)},S=(I)=>{$(JSON.stringify(I))},b=(I={})=>{if(I.sessionId&&I.sessionId!==T.sessionId)throw Error("Voice session switching requires a new connection instance.");if(I.sessionId)T.sessionId=I.sessionId;if(I.scenarioId)T.scenarioId=I.scenarioId;S({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},I0=(I)=>{$(I)},U0=()=>{S({type:"end_turn"})},L0=(I)=>{if(T.ws?.readyState!==1)return Promise.reject(Error(`${I.action} is unavailable while disconnected`));let w=crypto.randomUUID(),{promise:y,reject:Y,resolve:B}=Promise.withResolvers(),M=setTimeout(()=>{A.delete(w),Y(Error(`${I.action} acknowledgement timed out`))},5000);return A.set(w,{reject:Y,resolve:B,timeout:M}),T.ws.send(JSON.stringify({...I,requestId:w,type:"call_control"})),y},W0=(I="client-close")=>{if(L=!0,W?.abort(),W=null,O+=1,Z(),G("Voice connection closed"),T.pendingMessages.length=0,T.ws){let w=T.ws;if(T.ws=null,w.readyState===1)w.send(JSON.stringify({reason:I,type:"close"}));w.close(1000)}T.isConnected=!1,C.clear()},D0=()=>{if(L=!0,W?.abort(),W=null,O+=1,Z(),G("Voice connection disconnected"),T.pendingMessages.length=0,T.ws){let I=T.ws;T.ws=null,I.close(1000)}T.isConnected=!1,C.clear()},X0=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},G0=(I)=>{return C.add(I),()=>{C.delete(I)}};return q(),{callControl:L0,close:W0,disconnect:D0,endTurn:U0,send:S,sendAudio:I0,simulateDisconnect:X0,start:b,subscribe:G0,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var n0=()=>({attempts:0,maxAttempts:0,status:"idle"}),m0=(V,g)=>{let C=g.trim().replace(/\s+/g," ");if(!C)return V;if(!V)return C;if(V===C||V.endsWith(C))return V;if(C.includes(V))return C;return`${V} ${C}`},u0=(V,g)=>{let C=g.trim().replace(/\s+/g," ");if(!V)return C;if(!C||V.endsWith(C))return V;return`${V} ${C}`},r0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:n0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),p=()=>{let V=r0(),g="",C=new Set,A=()=>{C.forEach((_)=>_())};return{dispatch:(_)=>{switch(_.type){case"audio":V={...V,assistantAudio:[...V.assistantAudio,{chunk:_.chunk,format:_.format,receivedAt:_.receivedAt,turnId:_.turnId}]};break;case"assistant":V={...V,assistantStreamingText:"",assistantTexts:[...V.assistantTexts,_.text]};break;case"assistant_delta":V={...V,assistantStreamingText:`${V.assistantStreamingText}${_.delta}`};break;case"complete":V={...V,sessionId:_.sessionId,status:"completed"};break;case"call_lifecycle":V={...V,call:{...V.call,disposition:_.event.type==="end"?_.event.disposition:V.call?.disposition,endedAt:_.event.type==="end"?_.event.at:V.call?.endedAt,events:[...V.call?.events??[],_.event],lastEventAt:_.event.at,startedAt:V.call?.startedAt??_.event.at},sessionId:_.sessionId};break;case"connected":V={...V,isConnected:!0,reconnect:V.reconnect.status==="reconnecting"?{...V.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:V.reconnect};break;case"connection":V={...V,reconnect:_.reconnect};break;case"disconnected":V={...V,isConnected:!1};break;case"error":V={...V,error:_.message,..._.recoverable===!1?{isConnected:!1,status:"failed"}:{}};break;case"final":g=m0(g,_.transcript.text),V={...V,partial:g};break;case"partial":V={...V,partial:u0(g,_.transcript.text)};break;case"replay":g=_.partial,V={...V,assistantStreamingText:"",assistantTexts:[..._.assistantTexts],call:_.call??null,error:null,isConnected:_.status==="active",partial:_.partial,reconnect:V.reconnect.status==="reconnecting"?{...V.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:V.reconnect,scenarioId:_.scenarioId??V.scenarioId,sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status,turns:[..._.turns]};break;case"session":V={...V,error:null,scenarioId:_.scenarioId??V.scenarioId,isConnected:_.status==="active",sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status};break;case"turn":g="",V={...V,partial:"",turns:[...V.turns,_.turn]};break}A()},getServerSnapshot:()=>V,getSnapshot:()=>V,subscribe:(_)=>{return C.add(_),()=>{C.delete(_)}}}};var t=(V,g={})=>{let C=a(V,g),A=p(),R=g.browserMedia&&typeof window<"u"?r({...g.browserMedia,getScenarioId:()=>g.browserMedia?g.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>g.browserMedia?g.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,_=new Set,D=!1,X=(W)=>Promise.resolve().then(()=>{if(D)return;if(!W?.sessionId&&!W?.scenarioId)return;C.start(W),R?.start()}),H=()=>{_.forEach((W)=>W())},O=()=>{if(!g.reconnectReportPath||typeof fetch>"u")return;let W=A.getSnapshot(),U=JSON.stringify({at:Date.now(),reconnect:W.reconnect,scenarioId:W.scenarioId,sessionId:C.getSessionId(),turnIds:W.turns.map((T)=>T.id)});fetch(g.reconnectReportPath,{body:U,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},L=C.subscribe((W)=>{let U=i(W);if(U){if(A.dispatch(U),W.type==="connection")O();H()}});return{start:X,get assistantAudio(){return A.getSnapshot().assistantAudio},get assistantTexts(){return A.getSnapshot().assistantTexts},get assistantStreamingText(){return A.getSnapshot().assistantStreamingText},get call(){return A.getSnapshot().call},callControl(W){return C.callControl(W)},close(W){D=!0,L(),R?.close(),C.close(W),A.dispatch({type:"disconnected"}),H()},disconnect(){D=!0,L(),R?.close(),C.disconnect(),A.dispatch({type:"disconnected"}),H()},endTurn(){C.endTurn()},get error(){return A.getSnapshot().error},getServerSnapshot(){return A.getServerSnapshot()},getSnapshot(){return A.getSnapshot()},get isConnected(){return A.getSnapshot().isConnected},get partial(){return A.getSnapshot().partial},get reconnect(){return A.getSnapshot().reconnect},get scenarioId(){return A.getSnapshot().scenarioId},sendAudio(W){C.sendAudio(W)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return A.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return A.getSnapshot().status},subscribe(W){return _.add(W),()=>{_.delete(W)}},get turns(){return A.getSnapshot().turns}}};var e=(V)=>{if(!V||V.enabled===!1)return;return{enabled:!0,maxGain:V.maxGain??3,noiseGateAttenuation:V.noiseGateAttenuation??0.15,noiseGateThreshold:V.noiseGateThreshold??0.006,targetLevel:V.targetLevel??0.08}};var s0={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}},a0={"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 V0=(V)=>{let g=V?.profile??"fast",C=V?.qualityProfile??"general",A=s0[g],R=a0[C],_=V?.silenceMs??R.silenceMs??A.silenceMs;return{profile:g,qualityProfile:C,minSilenceMs:Math.min(_,V?.minSilenceMs??R.minSilenceMs??A.minSilenceMs),silenceMs:_,speechThreshold:V?.speechThreshold??R.speechThreshold??A.speechThreshold,transcriptStabilityMs:V?.transcriptStabilityMs??R.transcriptStabilityMs??A.transcriptStabilityMs}};var p0={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=(V="default")=>{let g=p0[V];return{audioConditioning:e(g.audioConditioning),capture:{channelCount:g.capture?.channelCount??1,sampleRateHz:g.capture?.sampleRateHz??16000},connection:{...g.connection},name:V,sttLifecycle:g.sttLifecycle??"continuous",turnDetection:V0(g.turnDetection)}};var t0=(V)=>({assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,isRecording:!1,partial:V.partial,reconnect:V.reconnect,recordingError:null,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]}),A0=(V,g={})=>{let C=g0(g.preset),A=t(V,{...C.connection,...g.connection}),R=null,_=0,D=!1,X=null,H=t0(A),O=new Set,L=()=>{for(let G of O)G()},W=()=>{if(H={...H,assistantAudio:[...A.assistantAudio],assistantStreamingText:A.assistantStreamingText,assistantTexts:[...A.assistantTexts],call:A.call,error:A.error,isConnected:A.isConnected,partial:A.partial,reconnect:A.reconnect,sessionId:A.sessionId,sessionMetadata:A.sessionMetadata,scenarioId:A.scenarioId,status:A.status,turns:[...A.turns]},g.autoStopOnComplete!==!1&&H.status==="completed"&&H.isRecording)R?.stop(),R=null,H={...H,isRecording:!1};L()},U=A.subscribe(W);W();let T=()=>{if(R)return R;return R=v({channelCount:g.capture?.channelCount??C.capture.channelCount,onLevel:g.capture?.onLevel,onAudio:(G)=>{if(g.capture?.onAudio){g.capture.onAudio(G,A.sendAudio);return}A.sendAudio(G)},sampleRateHz:g.capture?.sampleRateHz??C.capture.sampleRateHz,...g.capture?.stream?{stream:g.capture.stream}:{}}),R},J=()=>{_+=1,R?.stop(),R=null,H={...H,isRecording:!1},L()},h=async()=>{if(D)throw Error("Voice controller is closed.");if(H.isRecording)return;if(X)return X;let G=++_,c=T(),$=(async()=>{try{if(H={...H,recordingError:null},L(),await c.start(),D||G!==_||R!==c){c.stop();return}H={...H,isRecording:!0},L()}catch(S){if(c.stop(),G!==_||R!==c)throw S;throw R=null,H={...H,isRecording:!1,recordingError:S instanceof Error?S.message:String(S)},L(),S}})().finally(()=>{if(X===$)X=null});return X=$,X};return{close:(G)=>{D=!0,U(),J(),A.close(G)},disconnect:()=>{D=!0,U(),J(),A.disconnect()},startRecording:h,stopRecording:J,get assistantAudio(){return H.assistantAudio},get assistantTexts(){return H.assistantTexts},get assistantStreamingText(){return H.assistantStreamingText},bindHTMX(G){return d(A,G)},get call(){return H.call},callControl:(G)=>A.callControl(G),endTurn:()=>A.endTurn(),get error(){return H.error},getServerSnapshot:()=>H,getSnapshot:()=>H,get isConnected(){return H.isConnected},get isRecording(){return H.isRecording},get partial(){return H.partial},get reconnect(){return H.reconnect},get recordingError(){return H.recordingError},get scenarioId(){return H.scenarioId},sendAudio:(G)=>A.sendAudio(G),get sessionId(){return H.sessionId},get sessionMetadata(){return H.sessionMetadata},simulateDisconnect:()=>A.simulateDisconnect(),get status(){return H.status},subscribe:(G)=>{return O.add(G),()=>{O.delete(G)}},toggleRecording:async()=>{if(H.isRecording){J();return}await h()},get turns(){return H.turns}}};var z=(V)=>String(V).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var C0=(V)=>{if(!V.isConnected)return"idle";if(V.isPlaying)return"speaking";if(V.isRecording&&V.hasActivePartial)return"listening";if(V.isRecording)return"listening";if(V.lastTranscriptAt&&!V.lastAssistantAt)return"thinking";if(V.lastTranscriptAt&&V.lastAssistantAt&&V.lastTranscriptAt>V.lastAssistantAt)return"thinking";return"idle"};var e0={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},VV={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},gV=(V,g)=>{switch(V){case"listening":return g.listening;case"speaking":return g.speaking;case"thinking":return g.thinking;case"idle":return g.idle}},_0=(V)=>{let g={...e0,...V.theme},C={...VV,...V.labels},A=V.state.assistantAudio.at(-1)?.receivedAt,R=V.state.turns.at(-1)?.committedAt,_=C0({hasActivePartial:V.state.partial.length>0,isConnected:V.state.isConnected,isPlaying:!1,isRecording:V.state.isRecording,lastAssistantAt:A,lastTranscriptAt:R}),D=!V.state.isConnected&&V.state.status!=="idle"&&!V.state.error,X=V.state.error?"Error":D?C.connecting:V.state.status==="completed"?C.callEnded:gV(_,C);return{agentState:_,classes:{container:`absolute-voice-widget absolute-voice-widget--${_}`,dot:`absolute-voice-widget__dot${V.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:V.state.isConnected,canMute:V.state.isRecording,canStart:!V.state.isRecording&&V.state.status!=="completed"},errorMessage:V.state.error??void 0,labels:C,partial:V.state.partial||void 0,statusLabel:X,theme:g,title:V.title??"Voice"}},AV=(V)=>typeof V==="number"?`${V}px`:V,T0=(V)=>{let g=V.theme,C=`background:${g.background};border-radius:${AV(g.radius)};color:${g.foreground};font-family:${g.fontFamily};min-width:240px;padding:20px 22px;`,A=`background:${V.errorMessage?g.errorAccent:V.agentState==="idle"?"rgba(148,163,184,0.6)":g.accent};border-radius:50%;height:10px;width:10px;`,R=[];if(V.controls.canStart)R.push(`<button type="button" data-action="start" style="background:${g.accent};border:none;border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${z(V.labels.startCall)}</button>`);if(V.controls.canMute)R.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${z(V.labels.mute)}</button>`);if(V.controls.canEnd)R.push(`<button type="button" data-action="end" style="background:${g.errorAccent};border:none;border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${z(V.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${V.agentState}" class="${z(V.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="${z(V.classes.dot)}" style="${A}"></span>
|
|
4
|
+
<strong style="font-size:15px;">${z(V.title)}</strong>
|
|
5
|
+
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${z(V.statusLabel)}</span>
|
|
6
6
|
</div>
|
|
7
|
-
${V.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${
|
|
7
|
+
${V.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${z(V.partial)}”</p>`:""}
|
|
8
8
|
<div style="display:flex;gap:10px;">${R.join("")}</div>
|
|
9
|
-
${V.errorMessage?`<p style="color:${g.errorAccent};font-size:12px;margin-top:12px;">${
|
|
9
|
+
${V.errorMessage?`<p style="color:${g.errorAccent};font-size:12px;margin-top:12px;">${z(V.errorMessage)}</p>`:""}
|
|
10
10
|
</div>`};var CV=(V)=>{if(typeof V!=="string")return V;let g=document.querySelector(V);if(!g)throw Error(`AbsoluteVoice.mount: no element matches "${V}"`);return g},$0=(V,g={})=>{let C=CV(V),A=A0(g.path??"/voice",g.controllerOptions),R=null,_=null,D=()=>{let H=_0({...g.labels!==void 0?{labels:g.labels}:{},state:{assistantAudio:A.assistantAudio,error:A.error,isConnected:A.isConnected,isRecording:A.isRecording,partial:A.partial,status:A.status,turns:A.turns},...g.theme!==void 0?{theme:g.theme}:{},...g.title!==void 0?{title:g.title}:{}});C.innerHTML=T0(H);for(let O of C.querySelectorAll("button[data-action]")){let{action:L}=O.dataset;O.addEventListener("click",()=>{if(L==="start")A.startRecording();else if(L==="mute")A.stopRecording();else if(L==="end")A.close()})}if(A.error&&A.error!==R)R=A.error,g.onError?.(A.error);if(A.status!==_)_=A.status,g.onStatusChange?.(A.status)},X=A.subscribe(D);if(D(),g.autoStart)A.startRecording();return{controller:A,async end(){await A.close()},mute(){A.stopRecording()},async start(){await A.startRecording()},unmount(){X(),A.close(),C.innerHTML=""}}},R0="0.0.22-beta.516",H0={mount:$0,version:R0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=H0;var TV=H0;})();
|
package/dist/index.js
CHANGED
|
@@ -4518,6 +4518,13 @@ var createVoiceSession = (options) => {
|
|
|
4518
4518
|
pauseTimeout = null;
|
|
4519
4519
|
}
|
|
4520
4520
|
};
|
|
4521
|
+
const schedulePauseTimeout = (remainingMs) => {
|
|
4522
|
+
clearPauseTimeout();
|
|
4523
|
+
pauseTimeout = setTimeout(() => {
|
|
4524
|
+
pauseTimeout = null;
|
|
4525
|
+
api.close("pause-timeout");
|
|
4526
|
+
}, remainingMs);
|
|
4527
|
+
};
|
|
4521
4528
|
const pauseInternal = async () => {
|
|
4522
4529
|
if (callerPaused) {
|
|
4523
4530
|
return;
|
|
@@ -4530,14 +4537,17 @@ var createVoiceSession = (options) => {
|
|
|
4530
4537
|
clearCallSilenceWatchdog();
|
|
4531
4538
|
clearStuckCloseWatchdog();
|
|
4532
4539
|
clearIdleRepromptWatchdog();
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4540
|
+
clearAmdEvaluationTimer();
|
|
4541
|
+
const pausedAt = Date.now();
|
|
4542
|
+
const expiresAt = pausedAt + pauseMaxMs;
|
|
4543
|
+
snapshot.pause = { expiresAt, pausedAt };
|
|
4544
|
+
snapshot.lastActivityAt = pausedAt;
|
|
4545
|
+
await options.store.set(options.id, snapshot);
|
|
4546
|
+
schedulePauseTimeout(pauseMaxMs);
|
|
4538
4547
|
await appendTrace({
|
|
4539
4548
|
payload: {
|
|
4540
4549
|
action: "call.paused",
|
|
4550
|
+
expiresAt,
|
|
4541
4551
|
maxMs: pauseMaxMs
|
|
4542
4552
|
},
|
|
4543
4553
|
session: snapshot,
|
|
@@ -4553,7 +4563,11 @@ var createVoiceSession = (options) => {
|
|
|
4553
4563
|
kickCallSilenceWatchdog();
|
|
4554
4564
|
kickStuckCloseWatchdog();
|
|
4555
4565
|
kickIdleRepromptWatchdog(true);
|
|
4566
|
+
startAmdEvaluationTimer();
|
|
4556
4567
|
const snapshot = await readSession();
|
|
4568
|
+
snapshot.pause = undefined;
|
|
4569
|
+
snapshot.lastActivityAt = Date.now();
|
|
4570
|
+
await options.store.set(options.id, snapshot);
|
|
4557
4571
|
await appendTrace({
|
|
4558
4572
|
payload: {
|
|
4559
4573
|
action: "call.resumed"
|
|
@@ -5911,38 +5925,43 @@ var createVoiceSession = (options) => {
|
|
|
5911
5925
|
await handler();
|
|
5912
5926
|
});
|
|
5913
5927
|
};
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
openedSession.on("final", ({ transcript }) => {
|
|
5919
|
-
const next = options.redact ? options.redact(transcript) : transcript;
|
|
5920
|
-
runAdapterEvent("adapter.final", () => handleFinal(next));
|
|
5921
|
-
});
|
|
5922
|
-
openedSession.on("endOfTurn", ({ reason }) => {
|
|
5923
|
-
runAdapterEvent("adapter.endOfTurn", async () => {
|
|
5924
|
-
clearSilenceTimer();
|
|
5925
|
-
await requestTurnCommit(reason);
|
|
5928
|
+
try {
|
|
5929
|
+
openedSession.on("partial", ({ transcript }) => {
|
|
5930
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
5931
|
+
runAdapterEvent("adapter.partial", () => handlePartial(next));
|
|
5926
5932
|
});
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
openedSession.on("audio", ({ chunk, format, receivedAt }) => {
|
|
5936
|
-
runAudioSerial(async () => {
|
|
5937
|
-
if (activeAdapterGeneration !== generation) {
|
|
5938
|
-
return;
|
|
5939
|
-
}
|
|
5940
|
-
await sendAssistantAudio(chunk, {
|
|
5941
|
-
format,
|
|
5942
|
-
receivedAt
|
|
5943
|
-
});
|
|
5933
|
+
openedSession.on("final", ({ transcript }) => {
|
|
5934
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
5935
|
+
runAdapterEvent("adapter.final", () => handleFinal(next));
|
|
5936
|
+
});
|
|
5937
|
+
openedSession.on("endOfTurn", ({ reason }) => {
|
|
5938
|
+
runAdapterEvent("adapter.endOfTurn", async () => {
|
|
5939
|
+
clearSilenceTimer();
|
|
5940
|
+
await requestTurnCommit(reason);
|
|
5944
5941
|
});
|
|
5945
5942
|
});
|
|
5943
|
+
openedSession.on("error", (event) => {
|
|
5944
|
+
runAdapterEvent("adapter.error", () => handleError(event));
|
|
5945
|
+
});
|
|
5946
|
+
openedSession.on("close", (event) => {
|
|
5947
|
+
runAdapterEvent("adapter.close", () => handleClose(event));
|
|
5948
|
+
});
|
|
5949
|
+
if (options.realtime) {
|
|
5950
|
+
openedSession.on("audio", ({ chunk, format, receivedAt }) => {
|
|
5951
|
+
runAudioSerial(async () => {
|
|
5952
|
+
if (activeAdapterGeneration !== generation) {
|
|
5953
|
+
return;
|
|
5954
|
+
}
|
|
5955
|
+
await sendAssistantAudio(chunk, {
|
|
5956
|
+
format,
|
|
5957
|
+
receivedAt
|
|
5958
|
+
});
|
|
5959
|
+
});
|
|
5960
|
+
});
|
|
5961
|
+
}
|
|
5962
|
+
} catch (error) {
|
|
5963
|
+
await closeAdapter("initialization-failed");
|
|
5964
|
+
throw error;
|
|
5946
5965
|
}
|
|
5947
5966
|
return openedSession;
|
|
5948
5967
|
};
|
|
@@ -6853,6 +6872,32 @@ var createVoiceSession = (options) => {
|
|
|
6853
6872
|
};
|
|
6854
6873
|
}
|
|
6855
6874
|
}
|
|
6875
|
+
const pauseRemainingMs = session.pause ? session.pause.expiresAt - Date.now() : 0;
|
|
6876
|
+
callerPaused = pauseRemainingMs > 0;
|
|
6877
|
+
if (!callerPaused) {
|
|
6878
|
+
session.pause = undefined;
|
|
6879
|
+
}
|
|
6880
|
+
if (session.status === "completed" || session.status === "failed") {
|
|
6881
|
+
await options.store.set(options.id, session);
|
|
6882
|
+
await send({
|
|
6883
|
+
sessionId: options.id,
|
|
6884
|
+
status: session.status,
|
|
6885
|
+
sessionMetadata: session.metadata && typeof session.metadata === "object" ? session.metadata : undefined,
|
|
6886
|
+
scenarioId: session.scenarioId,
|
|
6887
|
+
type: "session"
|
|
6888
|
+
});
|
|
6889
|
+
if (session.status === "completed") {
|
|
6890
|
+
await send({
|
|
6891
|
+
sessionId: options.id,
|
|
6892
|
+
type: "complete"
|
|
6893
|
+
});
|
|
6894
|
+
}
|
|
6895
|
+
return;
|
|
6896
|
+
}
|
|
6897
|
+
session.status = "reconnecting";
|
|
6898
|
+
await options.store.set(options.id, session);
|
|
6899
|
+
await ensureAdapter();
|
|
6900
|
+
session.status = "active";
|
|
6856
6901
|
if (shouldFireOnSession) {
|
|
6857
6902
|
pushCallLifecycleEvent(session, {
|
|
6858
6903
|
type: "start"
|
|
@@ -6896,25 +6941,23 @@ var createVoiceSession = (options) => {
|
|
|
6896
6941
|
session
|
|
6897
6942
|
});
|
|
6898
6943
|
}
|
|
6899
|
-
if (session.status === "completed") {
|
|
6900
|
-
await send({
|
|
6901
|
-
sessionId: options.id,
|
|
6902
|
-
type: "complete"
|
|
6903
|
-
});
|
|
6904
|
-
return;
|
|
6905
|
-
}
|
|
6906
6944
|
resumePendingTurnCommit(session);
|
|
6907
|
-
await ensureAdapter();
|
|
6908
6945
|
warmTTSSession();
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6946
|
+
if (callerPaused) {
|
|
6947
|
+
schedulePauseTimeout(pauseRemainingMs);
|
|
6948
|
+
} else {
|
|
6949
|
+
kickCallSilenceWatchdog();
|
|
6950
|
+
kickStuckCloseWatchdog();
|
|
6951
|
+
kickIdleRepromptWatchdog(true);
|
|
6952
|
+
}
|
|
6953
|
+
if (!callerPaused) {
|
|
6954
|
+
startAmdEvaluationTimer();
|
|
6955
|
+
}
|
|
6956
|
+
if (!callerPaused && options.greeting && session.greetingDeliveredAt === undefined) {
|
|
6914
6957
|
session.greetingDeliveredAt = Date.now();
|
|
6915
6958
|
await options.store.set(options.id, session);
|
|
6916
6959
|
await speakResolvedLine(options.greeting, session);
|
|
6917
|
-
} else if (isResume && options.resumeGreeting && session.turns.length > 0) {
|
|
6960
|
+
} else if (!callerPaused && isResume && options.resumeGreeting && session.turns.length > 0) {
|
|
6918
6961
|
await speakResolvedLine(options.resumeGreeting, session);
|
|
6919
6962
|
}
|
|
6920
6963
|
};
|
|
@@ -6942,6 +6985,9 @@ var createVoiceSession = (options) => {
|
|
|
6942
6985
|
if (session.status === "completed" || session.status === "failed") {
|
|
6943
6986
|
return;
|
|
6944
6987
|
}
|
|
6988
|
+
if (callerPaused) {
|
|
6989
|
+
return;
|
|
6990
|
+
}
|
|
6945
6991
|
const adapter = await ensureAdapter();
|
|
6946
6992
|
let inboundAudio = audio;
|
|
6947
6993
|
if (options.noiseSuppressor) {
|
|
@@ -40155,6 +40201,7 @@ var voice = (config) => {
|
|
|
40155
40201
|
}
|
|
40156
40202
|
const monitorBindings = new Map;
|
|
40157
40203
|
const runtime = {
|
|
40204
|
+
activeSocketConnections: new Map,
|
|
40158
40205
|
activeSockets: new Map,
|
|
40159
40206
|
activeSessions: new Map,
|
|
40160
40207
|
pendingSessions: new Map,
|
|
@@ -40392,8 +40439,11 @@ var voice = (config) => {
|
|
|
40392
40439
|
await session.connect(buildSocketAdapter(ws, sessionId));
|
|
40393
40440
|
} catch (error) {
|
|
40394
40441
|
runtime.logger.warn?.(`[voice] session initialization failed for "${sessionId}": ${error instanceof Error ? error.message : String(error)}`);
|
|
40442
|
+
await session.fail(error).catch((failureError) => {
|
|
40443
|
+
runtime.logger.warn?.(`[voice] failed to persist initialization failure for "${sessionId}": ${failureError instanceof Error ? failureError.message : String(failureError)}`);
|
|
40444
|
+
});
|
|
40395
40445
|
closedSockets.add(identity);
|
|
40396
|
-
ws.close(
|
|
40446
|
+
ws.close(4500, "voice session initialization failed");
|
|
40397
40447
|
throw error;
|
|
40398
40448
|
}
|
|
40399
40449
|
if (closedSockets.has(identity) || !socketIsOpen(ws) || runtime.activeSockets.get(sessionId) !== identity) {
|
|
@@ -40560,6 +40610,7 @@ var voice = (config) => {
|
|
|
40560
40610
|
return;
|
|
40561
40611
|
}
|
|
40562
40612
|
runtime.activeSockets.delete(socketState.sessionId);
|
|
40613
|
+
runtime.activeSocketConnections.delete(socketState.sessionId);
|
|
40563
40614
|
const session = runtime.activeSessions.get(socketState.sessionId);
|
|
40564
40615
|
runtime.activeSessions.delete(socketState.sessionId);
|
|
40565
40616
|
deregisterMonitorSession(socketState.sessionId, reason ?? `ws-close-${String(code)}`);
|
|
@@ -40578,6 +40629,11 @@ var voice = (config) => {
|
|
|
40578
40629
|
return;
|
|
40579
40630
|
}
|
|
40580
40631
|
const sessionState = resolveSessionId3(runtime, ws);
|
|
40632
|
+
const identity = socketIdentity(ws);
|
|
40633
|
+
if (runtime.activeSockets.get(sessionState.sessionId) !== identity) {
|
|
40634
|
+
ws.close(1000, "superseded");
|
|
40635
|
+
return;
|
|
40636
|
+
}
|
|
40581
40637
|
const current = runtime.activeSessions.get(sessionState.sessionId);
|
|
40582
40638
|
const message = parseClientMessage(raw);
|
|
40583
40639
|
if (message) {
|
|
@@ -40590,6 +40646,7 @@ var voice = (config) => {
|
|
|
40590
40646
|
if (message.type === "close" && current) {
|
|
40591
40647
|
await current.close(message.reason);
|
|
40592
40648
|
runtime.activeSockets.delete(sessionState.sessionId);
|
|
40649
|
+
runtime.activeSocketConnections.delete(sessionState.sessionId);
|
|
40593
40650
|
runtime.activeSessions.delete(sessionState.sessionId);
|
|
40594
40651
|
deregisterMonitorSession(sessionState.sessionId, message.reason);
|
|
40595
40652
|
}
|
|
@@ -40651,20 +40708,12 @@ var voice = (config) => {
|
|
|
40651
40708
|
}
|
|
40652
40709
|
}
|
|
40653
40710
|
if (message.type === "start" && message.sessionId && message.sessionId !== sessionState.sessionId) {
|
|
40654
|
-
|
|
40655
|
-
|
|
40656
|
-
|
|
40657
|
-
|
|
40658
|
-
|
|
40659
|
-
|
|
40660
|
-
}
|
|
40661
|
-
sessionState.sessionId = message.sessionId;
|
|
40662
|
-
runtime.activeSockets.set(message.sessionId, socketIdentity(ws));
|
|
40663
|
-
runtime.socketSessions.set(socketIdentity(ws), {
|
|
40664
|
-
...sessionState,
|
|
40665
|
-
sessionId: message.sessionId,
|
|
40666
|
-
scenarioId: sessionState.scenarioId
|
|
40667
|
-
});
|
|
40711
|
+
ws.send(JSON.stringify({
|
|
40712
|
+
message: "Voice session switching requires a new WebSocket connection.",
|
|
40713
|
+
recoverable: false,
|
|
40714
|
+
type: "error"
|
|
40715
|
+
}));
|
|
40716
|
+
return;
|
|
40668
40717
|
}
|
|
40669
40718
|
if (message.type === "start" && message.scenarioId) {
|
|
40670
40719
|
sessionState.scenarioId = message.scenarioId;
|
|
@@ -40692,7 +40741,15 @@ var voice = (config) => {
|
|
|
40692
40741
|
if (closedSockets.has(identity) || !socketIsOpen(ws))
|
|
40693
40742
|
return;
|
|
40694
40743
|
const existing = runtime.activeSessions.get(sessionState.sessionId);
|
|
40744
|
+
const displaced = runtime.activeSocketConnections.get(sessionState.sessionId);
|
|
40745
|
+
if (displaced && displaced.identity !== identity) {
|
|
40746
|
+
displaced.close(1000, "superseded");
|
|
40747
|
+
}
|
|
40695
40748
|
runtime.activeSockets.set(sessionState.sessionId, identity);
|
|
40749
|
+
runtime.activeSocketConnections.set(sessionState.sessionId, {
|
|
40750
|
+
close: ws.close.bind(ws),
|
|
40751
|
+
identity
|
|
40752
|
+
});
|
|
40696
40753
|
if (existing) {
|
|
40697
40754
|
await existing.disconnect({
|
|
40698
40755
|
code: 1006,
|
package/dist/react/index.js
CHANGED
|
@@ -11438,6 +11438,7 @@ var WS_CLOSED = 3;
|
|
|
11438
11438
|
var WS_NORMAL_CLOSURE = 1000;
|
|
11439
11439
|
var WS_UNAUTHORIZED = 4401;
|
|
11440
11440
|
var WS_FORBIDDEN = 4403;
|
|
11441
|
+
var WS_INITIALIZATION_FAILED = 4500;
|
|
11441
11442
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
11442
11443
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
11443
11444
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
@@ -11741,8 +11742,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11741
11742
|
state.isConnected = false;
|
|
11742
11743
|
clearTimers();
|
|
11743
11744
|
rejectPendingCallControls("Voice connection interrupted");
|
|
11744
|
-
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
11745
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
|
|
11745
11746
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
11747
|
+
const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
|
|
11746
11748
|
if (authorizationFailure) {
|
|
11747
11749
|
listeners.forEach((listener) => listener({
|
|
11748
11750
|
message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
|
|
@@ -11750,6 +11752,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11750
11752
|
type: "error"
|
|
11751
11753
|
}));
|
|
11752
11754
|
}
|
|
11755
|
+
if (initializationFailure) {
|
|
11756
|
+
listeners.forEach((listener) => listener({
|
|
11757
|
+
message: "Voice provider initialization failed.",
|
|
11758
|
+
recoverable: false,
|
|
11759
|
+
type: "error"
|
|
11760
|
+
}));
|
|
11761
|
+
}
|
|
11753
11762
|
if (reconnectable) {
|
|
11754
11763
|
scheduleReconnect();
|
|
11755
11764
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
@@ -11763,7 +11772,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11763
11772
|
},
|
|
11764
11773
|
type: "connection"
|
|
11765
11774
|
});
|
|
11766
|
-
} else {
|
|
11775
|
+
} else if (!initializationFailure) {
|
|
11767
11776
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
11768
11777
|
}
|
|
11769
11778
|
}
|
|
@@ -11789,6 +11798,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11789
11798
|
sendSerialized(JSON.stringify(message));
|
|
11790
11799
|
};
|
|
11791
11800
|
const start = (input = {}) => {
|
|
11801
|
+
if (input.sessionId && input.sessionId !== state.sessionId) {
|
|
11802
|
+
throw new Error("Voice session switching requires a new connection instance.");
|
|
11803
|
+
}
|
|
11792
11804
|
if (input.sessionId) {
|
|
11793
11805
|
state.sessionId = input.sessionId;
|
|
11794
11806
|
}
|
package/dist/svelte/index.js
CHANGED
|
@@ -807,6 +807,7 @@ var WS_CLOSED = 3;
|
|
|
807
807
|
var WS_NORMAL_CLOSURE = 1000;
|
|
808
808
|
var WS_UNAUTHORIZED = 4401;
|
|
809
809
|
var WS_FORBIDDEN = 4403;
|
|
810
|
+
var WS_INITIALIZATION_FAILED = 4500;
|
|
810
811
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
811
812
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
812
813
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
@@ -1110,8 +1111,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1110
1111
|
state.isConnected = false;
|
|
1111
1112
|
clearTimers();
|
|
1112
1113
|
rejectPendingCallControls("Voice connection interrupted");
|
|
1113
|
-
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
1114
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
|
|
1114
1115
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
1116
|
+
const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
|
|
1115
1117
|
if (authorizationFailure) {
|
|
1116
1118
|
listeners.forEach((listener) => listener({
|
|
1117
1119
|
message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
|
|
@@ -1119,6 +1121,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1119
1121
|
type: "error"
|
|
1120
1122
|
}));
|
|
1121
1123
|
}
|
|
1124
|
+
if (initializationFailure) {
|
|
1125
|
+
listeners.forEach((listener) => listener({
|
|
1126
|
+
message: "Voice provider initialization failed.",
|
|
1127
|
+
recoverable: false,
|
|
1128
|
+
type: "error"
|
|
1129
|
+
}));
|
|
1130
|
+
}
|
|
1122
1131
|
if (reconnectable) {
|
|
1123
1132
|
scheduleReconnect();
|
|
1124
1133
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
@@ -1132,7 +1141,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1132
1141
|
},
|
|
1133
1142
|
type: "connection"
|
|
1134
1143
|
});
|
|
1135
|
-
} else {
|
|
1144
|
+
} else if (!initializationFailure) {
|
|
1136
1145
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
1137
1146
|
}
|
|
1138
1147
|
}
|
|
@@ -1158,6 +1167,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1158
1167
|
sendSerialized(JSON.stringify(message));
|
|
1159
1168
|
};
|
|
1160
1169
|
const start = (input = {}) => {
|
|
1170
|
+
if (input.sessionId && input.sessionId !== state.sessionId) {
|
|
1171
|
+
throw new Error("Voice session switching requires a new connection instance.");
|
|
1172
|
+
}
|
|
1161
1173
|
if (input.sessionId) {
|
|
1162
1174
|
state.sessionId = input.sessionId;
|
|
1163
1175
|
}
|
package/dist/testing/index.js
CHANGED
|
@@ -3028,6 +3028,7 @@ var WS_CLOSED = 3;
|
|
|
3028
3028
|
var WS_NORMAL_CLOSURE = 1000;
|
|
3029
3029
|
var WS_UNAUTHORIZED = 4401;
|
|
3030
3030
|
var WS_FORBIDDEN = 4403;
|
|
3031
|
+
var WS_INITIALIZATION_FAILED = 4500;
|
|
3031
3032
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
3032
3033
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
3033
3034
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
@@ -3331,8 +3332,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3331
3332
|
state.isConnected = false;
|
|
3332
3333
|
clearTimers();
|
|
3333
3334
|
rejectPendingCallControls("Voice connection interrupted");
|
|
3334
|
-
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
3335
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
|
|
3335
3336
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
3337
|
+
const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
|
|
3336
3338
|
if (authorizationFailure) {
|
|
3337
3339
|
listeners.forEach((listener) => listener({
|
|
3338
3340
|
message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
|
|
@@ -3340,6 +3342,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3340
3342
|
type: "error"
|
|
3341
3343
|
}));
|
|
3342
3344
|
}
|
|
3345
|
+
if (initializationFailure) {
|
|
3346
|
+
listeners.forEach((listener) => listener({
|
|
3347
|
+
message: "Voice provider initialization failed.",
|
|
3348
|
+
recoverable: false,
|
|
3349
|
+
type: "error"
|
|
3350
|
+
}));
|
|
3351
|
+
}
|
|
3343
3352
|
if (reconnectable) {
|
|
3344
3353
|
scheduleReconnect();
|
|
3345
3354
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
@@ -3353,7 +3362,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3353
3362
|
},
|
|
3354
3363
|
type: "connection"
|
|
3355
3364
|
});
|
|
3356
|
-
} else {
|
|
3365
|
+
} else if (!initializationFailure) {
|
|
3357
3366
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
3358
3367
|
}
|
|
3359
3368
|
}
|
|
@@ -3379,6 +3388,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3379
3388
|
sendSerialized(JSON.stringify(message));
|
|
3380
3389
|
};
|
|
3381
3390
|
const start = (input = {}) => {
|
|
3391
|
+
if (input.sessionId && input.sessionId !== state.sessionId) {
|
|
3392
|
+
throw new Error("Voice session switching requires a new connection instance.");
|
|
3393
|
+
}
|
|
3382
3394
|
if (input.sessionId) {
|
|
3383
3395
|
state.sessionId = input.sessionId;
|
|
3384
3396
|
}
|
|
@@ -7443,6 +7455,13 @@ var createVoiceSession = (options) => {
|
|
|
7443
7455
|
pauseTimeout = null;
|
|
7444
7456
|
}
|
|
7445
7457
|
};
|
|
7458
|
+
const schedulePauseTimeout = (remainingMs) => {
|
|
7459
|
+
clearPauseTimeout();
|
|
7460
|
+
pauseTimeout = setTimeout(() => {
|
|
7461
|
+
pauseTimeout = null;
|
|
7462
|
+
api.close("pause-timeout");
|
|
7463
|
+
}, remainingMs);
|
|
7464
|
+
};
|
|
7446
7465
|
const pauseInternal = async () => {
|
|
7447
7466
|
if (callerPaused) {
|
|
7448
7467
|
return;
|
|
@@ -7455,14 +7474,17 @@ var createVoiceSession = (options) => {
|
|
|
7455
7474
|
clearCallSilenceWatchdog();
|
|
7456
7475
|
clearStuckCloseWatchdog();
|
|
7457
7476
|
clearIdleRepromptWatchdog();
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7477
|
+
clearAmdEvaluationTimer();
|
|
7478
|
+
const pausedAt = Date.now();
|
|
7479
|
+
const expiresAt = pausedAt + pauseMaxMs;
|
|
7480
|
+
snapshot.pause = { expiresAt, pausedAt };
|
|
7481
|
+
snapshot.lastActivityAt = pausedAt;
|
|
7482
|
+
await options.store.set(options.id, snapshot);
|
|
7483
|
+
schedulePauseTimeout(pauseMaxMs);
|
|
7463
7484
|
await appendTrace({
|
|
7464
7485
|
payload: {
|
|
7465
7486
|
action: "call.paused",
|
|
7487
|
+
expiresAt,
|
|
7466
7488
|
maxMs: pauseMaxMs
|
|
7467
7489
|
},
|
|
7468
7490
|
session: snapshot,
|
|
@@ -7478,7 +7500,11 @@ var createVoiceSession = (options) => {
|
|
|
7478
7500
|
kickCallSilenceWatchdog();
|
|
7479
7501
|
kickStuckCloseWatchdog();
|
|
7480
7502
|
kickIdleRepromptWatchdog(true);
|
|
7503
|
+
startAmdEvaluationTimer();
|
|
7481
7504
|
const snapshot = await readSession();
|
|
7505
|
+
snapshot.pause = undefined;
|
|
7506
|
+
snapshot.lastActivityAt = Date.now();
|
|
7507
|
+
await options.store.set(options.id, snapshot);
|
|
7482
7508
|
await appendTrace({
|
|
7483
7509
|
payload: {
|
|
7484
7510
|
action: "call.resumed"
|
|
@@ -8836,38 +8862,43 @@ var createVoiceSession = (options) => {
|
|
|
8836
8862
|
await handler();
|
|
8837
8863
|
});
|
|
8838
8864
|
};
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
openedSession.on("final", ({ transcript }) => {
|
|
8844
|
-
const next = options.redact ? options.redact(transcript) : transcript;
|
|
8845
|
-
runAdapterEvent("adapter.final", () => handleFinal(next));
|
|
8846
|
-
});
|
|
8847
|
-
openedSession.on("endOfTurn", ({ reason }) => {
|
|
8848
|
-
runAdapterEvent("adapter.endOfTurn", async () => {
|
|
8849
|
-
clearSilenceTimer();
|
|
8850
|
-
await requestTurnCommit(reason);
|
|
8865
|
+
try {
|
|
8866
|
+
openedSession.on("partial", ({ transcript }) => {
|
|
8867
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
8868
|
+
runAdapterEvent("adapter.partial", () => handlePartial(next));
|
|
8851
8869
|
});
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
openedSession.on("audio", ({ chunk, format, receivedAt }) => {
|
|
8861
|
-
runAudioSerial(async () => {
|
|
8862
|
-
if (activeAdapterGeneration !== generation) {
|
|
8863
|
-
return;
|
|
8864
|
-
}
|
|
8865
|
-
await sendAssistantAudio(chunk, {
|
|
8866
|
-
format,
|
|
8867
|
-
receivedAt
|
|
8868
|
-
});
|
|
8870
|
+
openedSession.on("final", ({ transcript }) => {
|
|
8871
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
8872
|
+
runAdapterEvent("adapter.final", () => handleFinal(next));
|
|
8873
|
+
});
|
|
8874
|
+
openedSession.on("endOfTurn", ({ reason }) => {
|
|
8875
|
+
runAdapterEvent("adapter.endOfTurn", async () => {
|
|
8876
|
+
clearSilenceTimer();
|
|
8877
|
+
await requestTurnCommit(reason);
|
|
8869
8878
|
});
|
|
8870
8879
|
});
|
|
8880
|
+
openedSession.on("error", (event) => {
|
|
8881
|
+
runAdapterEvent("adapter.error", () => handleError(event));
|
|
8882
|
+
});
|
|
8883
|
+
openedSession.on("close", (event) => {
|
|
8884
|
+
runAdapterEvent("adapter.close", () => handleClose(event));
|
|
8885
|
+
});
|
|
8886
|
+
if (options.realtime) {
|
|
8887
|
+
openedSession.on("audio", ({ chunk, format, receivedAt }) => {
|
|
8888
|
+
runAudioSerial(async () => {
|
|
8889
|
+
if (activeAdapterGeneration !== generation) {
|
|
8890
|
+
return;
|
|
8891
|
+
}
|
|
8892
|
+
await sendAssistantAudio(chunk, {
|
|
8893
|
+
format,
|
|
8894
|
+
receivedAt
|
|
8895
|
+
});
|
|
8896
|
+
});
|
|
8897
|
+
});
|
|
8898
|
+
}
|
|
8899
|
+
} catch (error) {
|
|
8900
|
+
await closeAdapter("initialization-failed");
|
|
8901
|
+
throw error;
|
|
8871
8902
|
}
|
|
8872
8903
|
return openedSession;
|
|
8873
8904
|
};
|
|
@@ -9778,6 +9809,32 @@ var createVoiceSession = (options) => {
|
|
|
9778
9809
|
};
|
|
9779
9810
|
}
|
|
9780
9811
|
}
|
|
9812
|
+
const pauseRemainingMs = session.pause ? session.pause.expiresAt - Date.now() : 0;
|
|
9813
|
+
callerPaused = pauseRemainingMs > 0;
|
|
9814
|
+
if (!callerPaused) {
|
|
9815
|
+
session.pause = undefined;
|
|
9816
|
+
}
|
|
9817
|
+
if (session.status === "completed" || session.status === "failed") {
|
|
9818
|
+
await options.store.set(options.id, session);
|
|
9819
|
+
await send({
|
|
9820
|
+
sessionId: options.id,
|
|
9821
|
+
status: session.status,
|
|
9822
|
+
sessionMetadata: session.metadata && typeof session.metadata === "object" ? session.metadata : undefined,
|
|
9823
|
+
scenarioId: session.scenarioId,
|
|
9824
|
+
type: "session"
|
|
9825
|
+
});
|
|
9826
|
+
if (session.status === "completed") {
|
|
9827
|
+
await send({
|
|
9828
|
+
sessionId: options.id,
|
|
9829
|
+
type: "complete"
|
|
9830
|
+
});
|
|
9831
|
+
}
|
|
9832
|
+
return;
|
|
9833
|
+
}
|
|
9834
|
+
session.status = "reconnecting";
|
|
9835
|
+
await options.store.set(options.id, session);
|
|
9836
|
+
await ensureAdapter();
|
|
9837
|
+
session.status = "active";
|
|
9781
9838
|
if (shouldFireOnSession) {
|
|
9782
9839
|
pushCallLifecycleEvent(session, {
|
|
9783
9840
|
type: "start"
|
|
@@ -9821,25 +9878,23 @@ var createVoiceSession = (options) => {
|
|
|
9821
9878
|
session
|
|
9822
9879
|
});
|
|
9823
9880
|
}
|
|
9824
|
-
if (session.status === "completed") {
|
|
9825
|
-
await send({
|
|
9826
|
-
sessionId: options.id,
|
|
9827
|
-
type: "complete"
|
|
9828
|
-
});
|
|
9829
|
-
return;
|
|
9830
|
-
}
|
|
9831
9881
|
resumePendingTurnCommit(session);
|
|
9832
|
-
await ensureAdapter();
|
|
9833
9882
|
warmTTSSession();
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9883
|
+
if (callerPaused) {
|
|
9884
|
+
schedulePauseTimeout(pauseRemainingMs);
|
|
9885
|
+
} else {
|
|
9886
|
+
kickCallSilenceWatchdog();
|
|
9887
|
+
kickStuckCloseWatchdog();
|
|
9888
|
+
kickIdleRepromptWatchdog(true);
|
|
9889
|
+
}
|
|
9890
|
+
if (!callerPaused) {
|
|
9891
|
+
startAmdEvaluationTimer();
|
|
9892
|
+
}
|
|
9893
|
+
if (!callerPaused && options.greeting && session.greetingDeliveredAt === undefined) {
|
|
9839
9894
|
session.greetingDeliveredAt = Date.now();
|
|
9840
9895
|
await options.store.set(options.id, session);
|
|
9841
9896
|
await speakResolvedLine(options.greeting, session);
|
|
9842
|
-
} else if (isResume && options.resumeGreeting && session.turns.length > 0) {
|
|
9897
|
+
} else if (!callerPaused && isResume && options.resumeGreeting && session.turns.length > 0) {
|
|
9843
9898
|
await speakResolvedLine(options.resumeGreeting, session);
|
|
9844
9899
|
}
|
|
9845
9900
|
};
|
|
@@ -9867,6 +9922,9 @@ var createVoiceSession = (options) => {
|
|
|
9867
9922
|
if (session.status === "completed" || session.status === "failed") {
|
|
9868
9923
|
return;
|
|
9869
9924
|
}
|
|
9925
|
+
if (callerPaused) {
|
|
9926
|
+
return;
|
|
9927
|
+
}
|
|
9870
9928
|
const adapter = await ensureAdapter();
|
|
9871
9929
|
let inboundAudio = audio;
|
|
9872
9930
|
if (options.noiseSuppressor) {
|
package/dist/vue/index.js
CHANGED
|
@@ -10834,6 +10834,7 @@ var WS_CLOSED = 3;
|
|
|
10834
10834
|
var WS_NORMAL_CLOSURE = 1000;
|
|
10835
10835
|
var WS_UNAUTHORIZED = 4401;
|
|
10836
10836
|
var WS_FORBIDDEN = 4403;
|
|
10837
|
+
var WS_INITIALIZATION_FAILED = 4500;
|
|
10837
10838
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
10838
10839
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
10839
10840
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
@@ -11137,8 +11138,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11137
11138
|
state.isConnected = false;
|
|
11138
11139
|
clearTimers();
|
|
11139
11140
|
rejectPendingCallControls("Voice connection interrupted");
|
|
11140
|
-
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
11141
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && event.code !== WS_INITIALIZATION_FAILED && state.reconnectAttempts < maxReconnectAttempts;
|
|
11141
11142
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
11143
|
+
const initializationFailure = event.code === WS_INITIALIZATION_FAILED;
|
|
11142
11144
|
if (authorizationFailure) {
|
|
11143
11145
|
listeners.forEach((listener) => listener({
|
|
11144
11146
|
message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
|
|
@@ -11146,6 +11148,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11146
11148
|
type: "error"
|
|
11147
11149
|
}));
|
|
11148
11150
|
}
|
|
11151
|
+
if (initializationFailure) {
|
|
11152
|
+
listeners.forEach((listener) => listener({
|
|
11153
|
+
message: "Voice provider initialization failed.",
|
|
11154
|
+
recoverable: false,
|
|
11155
|
+
type: "error"
|
|
11156
|
+
}));
|
|
11157
|
+
}
|
|
11149
11158
|
if (reconnectable) {
|
|
11150
11159
|
scheduleReconnect();
|
|
11151
11160
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
@@ -11159,7 +11168,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11159
11168
|
},
|
|
11160
11169
|
type: "connection"
|
|
11161
11170
|
});
|
|
11162
|
-
} else {
|
|
11171
|
+
} else if (!initializationFailure) {
|
|
11163
11172
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
11164
11173
|
}
|
|
11165
11174
|
}
|
|
@@ -11185,6 +11194,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11185
11194
|
sendSerialized(JSON.stringify(message));
|
|
11186
11195
|
};
|
|
11187
11196
|
const start = (input = {}) => {
|
|
11197
|
+
if (input.sessionId && input.sessionId !== state.sessionId) {
|
|
11198
|
+
throw new Error("Voice session switching requires a new connection instance.");
|
|
11199
|
+
}
|
|
11188
11200
|
if (input.sessionId) {
|
|
11189
11201
|
state.sessionId = input.sessionId;
|
|
11190
11202
|
}
|