@absolutejs/voice 0.0.22-beta.587 → 0.0.22-beta.588
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 +9 -4
- package/dist/client/connection.d.ts +4 -0
- package/dist/client/htmxBootstrap.js +9 -4
- package/dist/client/index.js +9 -4
- package/dist/core/types.d.ts +6 -0
- package/dist/core/writeBehindStore.d.ts +41 -0
- package/dist/embed/index.js +9 -4
- package/dist/embed/voice-widget.js +8 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +108 -0
- package/dist/react/index.js +9 -4
- package/dist/svelte/index.js +9 -4
- package/dist/testing/index.js +9 -4
- package/dist/vue/index.js +9 -4
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -574,9 +574,11 @@ var createVoiceBrowserMediaReporter = (options) => {
|
|
|
574
574
|
var WS_OPEN = 1;
|
|
575
575
|
var WS_CLOSED = 3;
|
|
576
576
|
var WS_NORMAL_CLOSURE = 1000;
|
|
577
|
-
var DEFAULT_MAX_RECONNECT_ATTEMPTS =
|
|
577
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
578
578
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
579
|
-
var
|
|
579
|
+
var RECONNECT_BASE_DELAY_MS = 500;
|
|
580
|
+
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
581
|
+
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
580
582
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
581
583
|
var noop = () => {};
|
|
582
584
|
var noopUnsubscribe = () => noop;
|
|
@@ -645,7 +647,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
645
647
|
const listeners = new Set;
|
|
646
648
|
const shouldReconnect = options.reconnect !== false;
|
|
647
649
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
650
|
+
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
648
651
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
652
|
+
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
649
653
|
const state = {
|
|
650
654
|
isConnected: false,
|
|
651
655
|
pendingMessages: [],
|
|
@@ -681,8 +685,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
681
685
|
}
|
|
682
686
|
};
|
|
683
687
|
const scheduleReconnect = () => {
|
|
684
|
-
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
685
688
|
state.reconnectAttempts += 1;
|
|
689
|
+
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
690
|
+
const nextAttemptAt = Date.now() + delayMs;
|
|
686
691
|
emitConnection({
|
|
687
692
|
reconnect: {
|
|
688
693
|
attempts: state.reconnectAttempts,
|
|
@@ -706,7 +711,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
706
711
|
return;
|
|
707
712
|
}
|
|
708
713
|
connect();
|
|
709
|
-
},
|
|
714
|
+
}, delayMs);
|
|
710
715
|
};
|
|
711
716
|
const connect = () => {
|
|
712
717
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { VoiceClientMessage, VoiceConnectionOptions, VoiceServerMessage } from "../core/types";
|
|
2
|
+
/** Exponential reconnect backoff for attempt N (1-based): baseMs doubles each
|
|
3
|
+
* attempt, capped at maxDelayMs. Exported so the backoff window is unit-tested
|
|
4
|
+
* without a DOM/WebSocket harness. */
|
|
5
|
+
export declare const computeVoiceReconnectDelayMs: (attempt: number, baseMs: number, maxDelayMs: number) => number;
|
|
2
6
|
type VoiceConnectionHandle = {
|
|
3
7
|
callControl: (message: Omit<VoiceClientMessage & {
|
|
4
8
|
type: "call_control";
|
|
@@ -491,9 +491,11 @@ var createVoiceBrowserMediaReporter = (options) => {
|
|
|
491
491
|
var WS_OPEN = 1;
|
|
492
492
|
var WS_CLOSED = 3;
|
|
493
493
|
var WS_NORMAL_CLOSURE = 1000;
|
|
494
|
-
var DEFAULT_MAX_RECONNECT_ATTEMPTS =
|
|
494
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
495
495
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
496
|
-
var
|
|
496
|
+
var RECONNECT_BASE_DELAY_MS = 500;
|
|
497
|
+
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
498
|
+
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
497
499
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
498
500
|
var noop = () => {};
|
|
499
501
|
var noopUnsubscribe = () => noop;
|
|
@@ -562,7 +564,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
562
564
|
const listeners = new Set;
|
|
563
565
|
const shouldReconnect = options.reconnect !== false;
|
|
564
566
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
567
|
+
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
565
568
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
569
|
+
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
566
570
|
const state = {
|
|
567
571
|
isConnected: false,
|
|
568
572
|
pendingMessages: [],
|
|
@@ -598,8 +602,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
598
602
|
}
|
|
599
603
|
};
|
|
600
604
|
const scheduleReconnect = () => {
|
|
601
|
-
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
602
605
|
state.reconnectAttempts += 1;
|
|
606
|
+
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
607
|
+
const nextAttemptAt = Date.now() + delayMs;
|
|
603
608
|
emitConnection({
|
|
604
609
|
reconnect: {
|
|
605
610
|
attempts: state.reconnectAttempts,
|
|
@@ -623,7 +628,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
623
628
|
return;
|
|
624
629
|
}
|
|
625
630
|
connect();
|
|
626
|
-
},
|
|
631
|
+
}, delayMs);
|
|
627
632
|
};
|
|
628
633
|
const connect = () => {
|
|
629
634
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
package/dist/client/index.js
CHANGED
|
@@ -104,9 +104,11 @@ var voiceSseReactiveSource = (topic, options = {}) => (refresh) => {
|
|
|
104
104
|
var WS_OPEN = 1;
|
|
105
105
|
var WS_CLOSED = 3;
|
|
106
106
|
var WS_NORMAL_CLOSURE = 1000;
|
|
107
|
-
var DEFAULT_MAX_RECONNECT_ATTEMPTS =
|
|
107
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
108
108
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
109
|
-
var
|
|
109
|
+
var RECONNECT_BASE_DELAY_MS = 500;
|
|
110
|
+
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
111
|
+
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
110
112
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
111
113
|
var noop = () => {};
|
|
112
114
|
var noopUnsubscribe = () => noop;
|
|
@@ -175,7 +177,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
175
177
|
const listeners = new Set;
|
|
176
178
|
const shouldReconnect = options.reconnect !== false;
|
|
177
179
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
180
|
+
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
178
181
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
182
|
+
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
179
183
|
const state = {
|
|
180
184
|
isConnected: false,
|
|
181
185
|
pendingMessages: [],
|
|
@@ -211,8 +215,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
211
215
|
}
|
|
212
216
|
};
|
|
213
217
|
const scheduleReconnect = () => {
|
|
214
|
-
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
215
218
|
state.reconnectAttempts += 1;
|
|
219
|
+
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
220
|
+
const nextAttemptAt = Date.now() + delayMs;
|
|
216
221
|
emitConnection({
|
|
217
222
|
reconnect: {
|
|
218
223
|
attempts: state.reconnectAttempts,
|
|
@@ -236,7 +241,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
236
241
|
return;
|
|
237
242
|
}
|
|
238
243
|
connect();
|
|
239
|
-
},
|
|
244
|
+
}, delayMs);
|
|
240
245
|
};
|
|
241
246
|
const connect = () => {
|
|
242
247
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1113,6 +1113,12 @@ export type VoiceConnectionOptions = {
|
|
|
1113
1113
|
reconnect?: boolean;
|
|
1114
1114
|
reconnectReportPath?: string;
|
|
1115
1115
|
maxReconnectAttempts?: number;
|
|
1116
|
+
/** Cap on the exponential reconnect backoff (ms). The delay doubles from 500ms
|
|
1117
|
+
* up to this ceiling each attempt, so the total retry window is roughly
|
|
1118
|
+
* maxReconnectAttempts spread across it. Default 8000 — with the default 15
|
|
1119
|
+
* attempts that's a ~95s window, enough to ride out a server redeploy without
|
|
1120
|
+
* the caller losing the call. */
|
|
1121
|
+
reconnectMaxDelayMs?: number;
|
|
1116
1122
|
pingInterval?: number;
|
|
1117
1123
|
sessionId?: string;
|
|
1118
1124
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { VoiceLogger, VoiceSessionRecord, VoiceSessionStore } from "./types";
|
|
2
|
+
export type VoiceWriteBehindStore<TSession extends VoiceSessionRecord = VoiceSessionRecord> = VoiceSessionStore<TSession> & {
|
|
3
|
+
/** Force every pending snapshot out to the persistent store. Call on graceful
|
|
4
|
+
* shutdown (after the deploy drain) so an in-flight call survives the restart
|
|
5
|
+
* even if it was killed at the drain ceiling. Resolves once the persistent
|
|
6
|
+
* store has the latest snapshot of every dirty session. */
|
|
7
|
+
flush: () => Promise<void>;
|
|
8
|
+
/** Stop the debounce timer. Call after the final flush() on shutdown. */
|
|
9
|
+
dispose: () => void;
|
|
10
|
+
};
|
|
11
|
+
export type VoiceWriteBehindStoreOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = {
|
|
12
|
+
/** The durable store snapshots are written back to (Postgres / SQLite / file).
|
|
13
|
+
* This is what a fresh process reads from to resume a call after a restart. */
|
|
14
|
+
persistent: VoiceSessionStore<TSession>;
|
|
15
|
+
/** The synchronous hot-path store. Defaults to an in-memory Map store — the
|
|
16
|
+
* config that keeps up with high-frequency STT callbacks without dropping
|
|
17
|
+
* transcripts. Override only for tests. */
|
|
18
|
+
memory?: VoiceSessionStore<TSession>;
|
|
19
|
+
/** How long a write may sit in memory before it's flushed to the persistent
|
|
20
|
+
* store. Bounds the worst-case state lost on a hard crash. Default 750ms. A
|
|
21
|
+
* turn commit (turns array grows) bypasses this and flushes immediately. */
|
|
22
|
+
flushDebounceMs?: number;
|
|
23
|
+
logger?: VoiceLogger;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A session store that is fast AND durable.
|
|
27
|
+
*
|
|
28
|
+
* The in-memory store is authoritative for the live call: get/set are
|
|
29
|
+
* synchronous, so the hot path (STT partials/finals firing every ~100ms) never
|
|
30
|
+
* waits on the database and concurrent writes can't race. Every write is ALSO
|
|
31
|
+
* recorded as a pending snapshot and flushed to the persistent store on a
|
|
32
|
+
* debounce (coalescing a burst of partials into ~one DB write), with a turn
|
|
33
|
+
* commit flushing immediately so completed turns are durable right away.
|
|
34
|
+
*
|
|
35
|
+
* On a cold `get`/`getOrCreate` miss — e.g. a fresh process after a deploy — it
|
|
36
|
+
* hydrates the session from the persistent store and repopulates memory. That's
|
|
37
|
+
* the resume path: the client reconnects with the same session id, the server
|
|
38
|
+
* finds the persisted session, so it does NOT re-fire the greeting and replays
|
|
39
|
+
* the prior turns instead of starting over.
|
|
40
|
+
*/
|
|
41
|
+
export declare const createVoiceWriteBehindStore: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceWriteBehindStoreOptions<TSession>) => VoiceWriteBehindStore<TSession>;
|
package/dist/embed/index.js
CHANGED
|
@@ -488,9 +488,11 @@ var createVoiceBrowserMediaReporter = (options) => {
|
|
|
488
488
|
var WS_OPEN = 1;
|
|
489
489
|
var WS_CLOSED = 3;
|
|
490
490
|
var WS_NORMAL_CLOSURE = 1000;
|
|
491
|
-
var DEFAULT_MAX_RECONNECT_ATTEMPTS =
|
|
491
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
492
492
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
493
|
-
var
|
|
493
|
+
var RECONNECT_BASE_DELAY_MS = 500;
|
|
494
|
+
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
495
|
+
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
494
496
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
495
497
|
var noop = () => {};
|
|
496
498
|
var noopUnsubscribe = () => noop;
|
|
@@ -559,7 +561,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
559
561
|
const listeners = new Set;
|
|
560
562
|
const shouldReconnect = options.reconnect !== false;
|
|
561
563
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
564
|
+
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
562
565
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
566
|
+
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
563
567
|
const state = {
|
|
564
568
|
isConnected: false,
|
|
565
569
|
pendingMessages: [],
|
|
@@ -595,8 +599,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
595
599
|
}
|
|
596
600
|
};
|
|
597
601
|
const scheduleReconnect = () => {
|
|
598
|
-
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
599
602
|
state.reconnectAttempts += 1;
|
|
603
|
+
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
604
|
+
const nextAttemptAt = Date.now() + delayMs;
|
|
600
605
|
emitConnection({
|
|
601
606
|
reconnect: {
|
|
602
607
|
attempts: state.reconnectAttempts,
|
|
@@ -620,7 +625,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
620
625
|
return;
|
|
621
626
|
}
|
|
622
627
|
connect();
|
|
623
|
-
},
|
|
628
|
+
}, delayMs);
|
|
624
629
|
};
|
|
625
630
|
const connect = () => {
|
|
626
631
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
(()=>{var{defineProperty:q,getOwnPropertyNames:Vc,getOwnPropertyDescriptor:Ic}=Object,Rc=Object.prototype.hasOwnProperty;function _c(c){return this[c]}var yc=(c)=>{var g=(z??=new WeakMap).get(c),C;if(g)return g;if(g=q({},"__esModule",{value:!0}),c&&typeof c==="object"||typeof c==="function"){for(var A of Vc(c))if(!Rc.call(g,A))q(g,A,{get:_c.bind(c,A),enumerable:!(C=Ic(c,A))||C.enumerable})}return z.set(c,g),g},z;var Sc=(c)=>c;function wc(c,g){this[c]=Sc.bind(null,g)}var Lc=(c,g)=>{for(var C in g)q(c,C,{get:g[C],enumerable:!0,configurable:!0,set:wc.bind(g,C)})};var vc={};Lc(vc,{mount:()=>cc,default:()=>sc,VOICE_EMBED_VERSION:()=>gc});var Uc=(c)=>{if(typeof c!=="string")return c;return document.querySelector(c)},Oc=(c,g,C,A)=>{let _=g??c.getAttribute("hx-get")??"";if(!_)return"";let V=new URL(_,window.location.origin);if(A)V.searchParams.set(C,A);else V.searchParams.delete(C);return`${V.pathname}${V.search}${V.hash}`},k=(c,g)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=Uc(g.element);if(!C)return()=>{};let A=g.eventName??"voice-refresh",_=g.sessionQueryParam??"sessionId",V=()=>{let w=window,L=Oc(C,g.route,_,c.sessionId);if(L)C.setAttribute("hx-get",L);w.htmx?.process?.(C),w.htmx?.trigger?.(C,A)},R=c.subscribe(V);return V(),()=>{R()}};var $c=(c)=>Math.max(-1,Math.min(1,c)),hc=(c)=>{let g=new Int16Array(c.length);for(let C=0;C<c.length;C+=1){let A=$c(c[C]??0);g[C]=A<0?A*32768:A*32767}return new Uint8Array(g.buffer)},Dc=(c)=>{let g=c instanceof Uint8Array?c:new Uint8Array(c);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 _ of C){let V=_/32768;A+=V*V}return Math.min(1,Math.max(0,Math.sqrt(A/C.length)*5.5))},Tc=(c,g,C)=>{if(g===C)return c;let A=g/C,_=Math.round(c.length/A),V=new Float32Array(_),R=0,w=0;while(R<V.length){let L=Math.round((R+1)*A),$=0,S=0;for(let O=w;O<L&&O<c.length;O+=1)$+=c[O]??0,S+=1;V[R]=S>0?$/S:0,R+=1,w=L}return V},l=(c)=>{let g=null,C=null,A=null,_=null;return{start:async()=>{if(!c.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let w=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!w)throw Error("Browser microphone capture requires AudioContext support.");if(_=c.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:c.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),g=new w,g.state==="suspended")await g.resume();C=g.createMediaStreamSource(_),A=g.createScriptProcessor(4096,1,1),A.onaudioprocess=(L)=>{let $=L.inputBuffer.getChannelData(0),S=Tc($,g?.sampleRate??48000,c.sampleRateHz??16000),O=hc(S);c.onLevel?.(Dc(O)),c.onAudio(O)},C.connect(A),A.connect(g.destination)},stop:()=>{A?.disconnect(),C?.disconnect(),_?.getTracks().forEach((w)=>w.stop()),g?.close(),c.onLevel?.(0),g=null,_=null,A=null,C=null}}};var B=(c)=>{if(typeof c==="string"&&c.trim())return c;if(c instanceof Error&&c.message.trim())return c.message;if(c&&typeof c==="object"){let g=c;for(let C of["message","reason","description"]){let A=g[C];if(typeof A==="string"&&A.trim())return A}if("error"in g)return B(g.error);if("cause"in g)return B(g.cause);try{return JSON.stringify(c)}catch{}}return"Unexpected error"},x=(c)=>{switch(c.type){case"audio":return{chunk:Uint8Array.from(atob(c.chunkBase64),(g)=>g.charCodeAt(0)),format:c.format,receivedAt:c.receivedAt,turnId:c.turnId,type:"audio"};case"assistant":return{text:c.text,turnId:c.turnId,type:"assistant"};case"assistant_delta":return{delta:c.delta,turnId:c.turnId,type:"assistant_delta"};case"complete":return{sessionId:c.sessionId,type:"complete"};case"connection":return{reconnect:c.reconnect,type:"connection"};case"call_lifecycle":return{event:c.event,sessionId:c.sessionId,type:"call_lifecycle"};case"error":return{message:B(c.message),type:"error"};case"final":return{transcript:c.transcript,type:"final"};case"partial":return{transcript:c.transcript,type:"partial"};case"replay":return{assistantTexts:c.assistantTexts,call:c.call,partial:c.partial,scenarioId:c.scenarioId,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,status:c.status,turns:c.turns,type:"replay"};case"session":return{sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,scenarioId:c.scenarioId,status:c.status,type:"session"};case"turn":return{turn:c.turn,type:"turn"};default:return null}};var ec=Math.PI*2;var M=(c,g,C,A)=>{c.push({code:C,message:A,severity:g})};var Hc=(c)=>c.length===0?void 0:c.reduce((g,C)=>g+C,0)/c.length,Q=(c)=>c.length===0?void 0:Math.max(...c);var D=(c,g)=>{let C=c[g];return typeof C==="number"&&Number.isFinite(C)?C:void 0},Z=(c,g)=>{let C=c[g];return typeof C==="boolean"?C:void 0},T=(c,g)=>{let C=c[g];return typeof C==="string"?C:void 0},j=(c)=>String(c.id??T(c,"ssrc")??D(c,"ssrc")??T(c,"trackIdentifier")??T(c,"mid")??"unknown"),d=(c)=>c===void 0?void 0:c*1000;var Gc=(c)=>{let g={};for(let[C,A]of Object.entries(c))if(A===null||typeof A==="boolean"||typeof A==="number"||typeof A==="string")g[C]=A;return g};var f=(c={})=>{let g=c.stats??[],C=[],A=g.filter((I)=>I.type==="inbound-rtp"&&T(I,"kind")!=="video"),_=g.filter((I)=>I.type==="outbound-rtp"&&T(I,"kind")!=="video"),V=g.filter((I)=>I.type==="candidate-pair"),R=g.filter((I)=>(I.type==="track"||I.type==="media-source")&&T(I,"kind")==="audio"),w=V.filter((I)=>Z(I,"selected")===!0||Z(I,"nominated")===!0||T(I,"state")==="succeeded").length,L=R.filter((I)=>T(I,"readyState")!=="ended"&&T(I,"trackState")!=="ended"&&Z(I,"ended")!==!0).length,$=R.filter((I)=>T(I,"readyState")==="ended"||T(I,"trackState")==="ended"||Z(I,"ended")===!0).length,S=A.reduce((I,h)=>I+(D(h,"packetsReceived")??0),0),O=_.reduce((I,h)=>I+(D(h,"packetsSent")??0),0),y=[...A,..._].reduce((I,h)=>I+Math.max(0,D(h,"packetsLost")??0),0),H=S+y,U=H===0?0:y/H,J=A.reduce((I,h)=>I+(D(h,"bytesReceived")??0),0),N=_.reduce((I,h)=>I+(D(h,"bytesSent")??0),0),W=Q(V.map((I)=>d(D(I,"currentRoundTripTime")??D(I,"roundTripTime"))).filter((I)=>I!==void 0)),E=Q([...A,..._].map((I)=>d(D(I,"jitter"))).filter((I)=>I!==void 0)),P=Q(A.map((I)=>{let h=D(I,"jitterBufferDelay"),G=D(I,"jitterBufferEmittedCount");return h!==void 0&&G!==void 0&&G>0?h/G*1000:void 0}).filter((I)=>I!==void 0)),b=R.map((I)=>D(I,"audioLevel")).filter((I)=>I!==void 0);if(c.requireConnectedCandidatePair&&V.length>0&&w===0)M(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(c.requireLiveAudioTrack&&L===0)M(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(c.maxPacketLossRatio!==void 0&&U>c.maxPacketLossRatio)M(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(U)} above ${String(c.maxPacketLossRatio)}.`);if(c.maxRoundTripTimeMs!==void 0&&W!==void 0&&W>c.maxRoundTripTimeMs)M(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(W)}ms above ${String(c.maxRoundTripTimeMs)}ms.`);if(c.maxJitterMs!==void 0&&E!==void 0&&E>c.maxJitterMs)M(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(E)}ms above ${String(c.maxJitterMs)}ms.`);return{activeCandidatePairs:w,audioLevelAverage:Hc(b),bytesReceived:J,bytesSent:N,checkedAt:Date.now(),endedAudioTracks:$,inboundPackets:S,issues:C,jitterBufferDelayMs:P,jitterMs:E,liveAudioTracks:L,outboundPackets:O,packetLossRatio:U,packetsLost:y,roundTripTimeMs:W,status:C.some((I)=>I.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:g.length}},F=async(c)=>{return[...(await c.peerConnection.getStats(c.selector??null)).values()].map(Gc)};var n=(c={})=>{let g=c.stats??[],C=c.previousStats??[],A=[],_=new Map(C.map((y)=>[j(y),y])),R=g.filter((y)=>(y.type==="inbound-rtp"||y.type==="outbound-rtp")&&T(y,"kind")!=="video"&&T(y,"mediaType")!=="video").map((y)=>{let H=y.type==="outbound-rtp"?"outbound":"inbound",U=H==="outbound"?"packetsSent":"packetsReceived",J=H==="outbound"?"bytesSent":"bytesReceived",N=_.get(j(y)),W=D(y,U),E=N?D(N,U):void 0,P=D(y,J),b=N?D(N,J):void 0,I=y.timestamp!==void 0&&N?.timestamp!==void 0?y.timestamp-N.timestamp:void 0;return{bytesDelta:P!==void 0&&b!==void 0?P-b:void 0,currentPackets:W,direction:H,id:j(y),packetDelta:W!==void 0&&E!==void 0?W-E:void 0,previousPackets:E,timeDeltaMs:I}}),w=R.filter((y)=>y.direction==="inbound"),L=R.filter((y)=>y.direction==="outbound"),$=Q(R.map((y)=>y.timeDeltaMs).filter((y)=>y!==void 0)),S=w.filter((y)=>c.maxInboundPacketStallMs!==void 0&&y.timeDeltaMs!==void 0&&y.timeDeltaMs>=c.maxInboundPacketStallMs&&y.packetDelta!==void 0&&y.packetDelta<=0).length,O=L.filter((y)=>c.maxOutboundPacketStallMs!==void 0&&y.timeDeltaMs!==void 0&&y.timeDeltaMs>=c.maxOutboundPacketStallMs&&y.packetDelta!==void 0&&y.packetDelta<=0).length;if(c.requireInboundAudio&&w.length===0)M(A,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(c.requireOutboundAudio&&L.length===0)M(A,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(c.maxGapMs!==void 0&&$!==void 0&&$>c.maxGapMs)M(A,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String($)}ms above ${String(c.maxGapMs)}ms.`);if(S>0)M(A,"error","media.webrtc_inbound_stalled",`${String(S)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(O>0)M(A,"error","media.webrtc_outbound_stalled",`${String(O)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:w.length,issues:A,maxObservedGapMs:$,outboundAudioStreams:L.length,stalledInboundStreams:S,stalledOutboundStreams:O,status:A.some((y)=>y.severity==="error")?"fail":A.length>0?"warn":"pass",streams:R,totalStats:g.length}};var Nc="/api/voice/browser-media",Wc=5000,Ec=async(c)=>c.peerConnection??await c.getPeerConnection?.()??null,Mc=async(c,g)=>{let C=g.fetch??globalThis.fetch;if(!C)return;await C(g.path??Nc,{body:JSON.stringify(c),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},o=(c)=>{let g=null,C=[],A=async()=>{let R=await Ec(c);if(!R)return;let w=await F({peerConnection:R}),L=f({...c,stats:w}),$=c.continuity===!1?void 0:n({...c.continuity,previousStats:C,stats:w}),S={at:Date.now(),continuity:$,report:L,scenarioId:c.getScenarioId?.()??null,sessionId:c.getSessionId?.()??null};return C=w,c.onReport?.(S),await Mc(S,c),S},_=()=>{A().catch((R)=>{c.onError?.(R)})},V=()=>{if(g)clearInterval(g),g=null};return{close:V,reportOnce:A,stop:V,start:()=>{if(g)return;_(),g=setInterval(_,c.intervalMs??Wc)}}};var Y=()=>{},Xc=()=>Y,Yc={callControl:Y,close:Y,endTurn:Y,send:Y,sendAudio:Y,simulateDisconnect:Y,subscribe:Xc,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},Jc=()=>crypto.randomUUID(),Pc=(c,g,C)=>{let{hostname:A,port:_,protocol:V}=window.location,R=V==="https:"?"wss:":"ws:",w=_?`:${_}`:"",L=new URL(`${R}//${A}${w}${c}`);if(L.searchParams.set("sessionId",g),C)L.searchParams.set("scenarioId",C);return L.toString()},bc=(c)=>{if(!c||typeof c!=="object"||!("type"in c))return!1;switch(c.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},Zc=(c)=>{if(typeof c.data!=="string")return null;try{let g=JSON.parse(c.data);return bc(g)?g:null}catch{return null}},i=(c,g={})=>{if(typeof window>"u")return Yc;let C=new Set,A=g.reconnect!==!1,_=g.maxReconnectAttempts??10,V=g.pingInterval??30000,R={isConnected:!1,pendingMessages:[],scenarioId:g.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:g.sessionId??Jc(),ws:null},w=(I)=>{C.forEach((h)=>h(I))},L=()=>{if(R.pingInterval)clearInterval(R.pingInterval),R.pingInterval=null;if(R.reconnectTimeout)clearTimeout(R.reconnectTimeout),R.reconnectTimeout=null},$=()=>{if(R.ws?.readyState!==1)return;while(R.pendingMessages.length>0){let I=R.pendingMessages.shift();if(I!==void 0)R.ws.send(I)}},S=()=>{let I=Date.now()+500;R.reconnectAttempts+=1,w({reconnect:{attempts:R.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:I,status:"reconnecting"},type:"connection"}),R.reconnectTimeout=setTimeout(()=>{if(R.reconnectAttempts>_){w({reconnect:{attempts:R.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"});return}O()},500)},O=()=>{let I=new WebSocket(Pc(c,R.sessionId,R.scenarioId));I.binaryType="arraybuffer",I.onopen=()=>{let h=R.reconnectAttempts>0;if(R.isConnected=!0,$(),h)w({reconnect:{attempts:R.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),R.reconnectAttempts=0;C.forEach((G)=>G({scenarioId:R.scenarioId??void 0,sessionId:R.sessionId,status:"active",type:"session"})),R.pingInterval=setInterval(()=>{if(I.readyState===1)I.send(JSON.stringify({type:"ping"}))},V)},I.onmessage=(h)=>{let G=Zc(h);if(!G)return;if(G.type==="session")R.sessionId=G.sessionId,R.scenarioId=G.scenarioId??R.scenarioId;C.forEach((Cc)=>Cc(G))},I.onclose=(h)=>{if(R.isConnected=!1,L(),A&&h.code!==1000&&R.reconnectAttempts<_)S();else if(A&&h.code!==1000)w({reconnect:{attempts:R.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"})},R.ws=I},y=(I)=>{if(R.ws?.readyState===1){R.ws.send(I);return}R.pendingMessages.push(I)},H=(I)=>{y(JSON.stringify(I))},U=(I={})=>{if(I.sessionId)R.sessionId=I.sessionId;if(I.scenarioId)R.scenarioId=I.scenarioId;H({scenarioId:R.scenarioId??void 0,sessionId:R.sessionId,type:"start"})},J=(I)=>{y(I)},N=()=>{H({type:"end_turn"})},W=(I)=>{H({...I,type:"call_control"})},E=()=>{if(L(),R.ws)R.ws.close(1000),R.ws=null;R.isConnected=!1,C.clear()},P=()=>{if(R.ws?.readyState===1)R.ws.close(4000,"absolutejs-voice-reconnect-proof")},b=(I)=>{return C.add(I),()=>{C.delete(I)}};return O(),{callControl:W,close:E,endTurn:N,send:H,sendAudio:J,simulateDisconnect:P,start:U,subscribe:b,getReadyState:()=>R.ws?.readyState??3,getScenarioId:()=>R.scenarioId??"",getSessionId:()=>R.sessionId}};var Qc=()=>({attempts:0,maxAttempts:0,status:"idle"}),Kc=(c,g)=>{let C=g.trim().replace(/\s+/g," ");if(!C)return c;if(!c)return C;if(c===C||c.endsWith(C))return c;if(C.includes(c))return C;return`${c} ${C}`},qc=(c,g)=>{let C=g.trim().replace(/\s+/g," ");if(!c)return C;if(!C||c.endsWith(C))return c;return`${c} ${C}`},Bc=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:Qc(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),v=()=>{let c=Bc(),g="",C=new Set,A=()=>{C.forEach((V)=>V())};return{dispatch:(V)=>{switch(V.type){case"audio":c={...c,assistantAudio:[...c.assistantAudio,{chunk:V.chunk,format:V.format,receivedAt:V.receivedAt,turnId:V.turnId}]};break;case"assistant":c={...c,assistantStreamingText:"",assistantTexts:[...c.assistantTexts,V.text]};break;case"assistant_delta":c={...c,assistantStreamingText:`${c.assistantStreamingText}${V.delta}`};break;case"complete":c={...c,sessionId:V.sessionId,status:"completed"};break;case"call_lifecycle":c={...c,call:{...c.call,disposition:V.event.type==="end"?V.event.disposition:c.call?.disposition,endedAt:V.event.type==="end"?V.event.at:c.call?.endedAt,events:[...c.call?.events??[],V.event],lastEventAt:V.event.at,startedAt:c.call?.startedAt??V.event.at},sessionId:V.sessionId};break;case"connected":c={...c,isConnected:!0,reconnect:c.reconnect.status==="reconnecting"?{...c.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:c.reconnect};break;case"connection":c={...c,reconnect:V.reconnect};break;case"disconnected":c={...c,isConnected:!1};break;case"error":c={...c,error:V.message};break;case"final":g=Kc(g,V.transcript.text),c={...c,partial:g};break;case"partial":c={...c,partial:qc(g,V.transcript.text)};break;case"replay":g=V.partial,c={...c,assistantStreamingText:"",assistantTexts:[...V.assistantTexts],call:V.call??null,error:null,isConnected:V.status==="active",partial:V.partial,reconnect:c.reconnect.status==="reconnecting"?{...c.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:c.reconnect,scenarioId:V.scenarioId??c.scenarioId,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata??c.sessionMetadata,status:V.status,turns:[...V.turns]};break;case"session":c={...c,error:null,scenarioId:V.scenarioId??c.scenarioId,isConnected:V.status==="active",sessionId:V.sessionId,sessionMetadata:V.sessionMetadata??c.sessionMetadata,status:V.status};break;case"turn":g="",c={...c,partial:"",turns:[...c.turns,V.turn]};break}A()},getServerSnapshot:()=>c,getSnapshot:()=>c,subscribe:(V)=>{return C.add(V),()=>{C.delete(V)}}}};var s=(c,g={})=>{let C=i(c,g),A=v(),_=g.browserMedia&&typeof window<"u"?o({...g.browserMedia,getScenarioId:()=>g.browserMedia?g.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>g.browserMedia?g.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,V=new Set,R=(S)=>Promise.resolve().then(()=>{if(!S?.sessionId&&!S?.scenarioId)return;C.start(S),_?.start()}),w=()=>{V.forEach((S)=>S())},L=()=>{if(!g.reconnectReportPath||typeof fetch>"u")return;let S=A.getSnapshot(),O=JSON.stringify({at:Date.now(),reconnect:S.reconnect,scenarioId:S.scenarioId,sessionId:C.getSessionId(),turnIds:S.turns.map((y)=>y.id)});fetch(g.reconnectReportPath,{body:O,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},$=C.subscribe((S)=>{let O=x(S);if(O){if(A.dispatch(O),S.type==="connection")L();w()}});return{start:R,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(S){C.callControl(S)},close(){$(),_?.close(),C.close(),A.dispatch({type:"disconnected"}),w()},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(S){C.sendAudio(S)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return A.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return A.getSnapshot().status},subscribe(S){return V.add(S),()=>{V.delete(S)}},get turns(){return A.getSnapshot().turns}}};var m=(c)=>{if(!c||c.enabled===!1)return;return{enabled:!0,maxGain:c.maxGain??3,noiseGateAttenuation:c.noiseGateAttenuation??0.15,noiseGateThreshold:c.noiseGateThreshold??0.006,targetLevel:c.targetLevel??0.08}};var K=1200;var jc={balanced:{qualityProfile:"general",semanticVetoMaxMs:0,semanticVetoRecheckMs:K,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",semanticVetoMaxMs:0,semanticVetoRecheckMs:K,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",semanticVetoMaxMs:0,semanticVetoRecheckMs:K,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},zc={"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}},kc="fast",lc="general",r=(c)=>{let g=c?.profile??kc,C=c?.qualityProfile??lc,A=jc[g],_=zc[C];return{profile:g,qualityProfile:C,semanticVetoMaxMs:c?.semanticVetoMaxMs??A.semanticVetoMaxMs,semanticVetoRecheckMs:c?.semanticVetoRecheckMs??A.semanticVetoRecheckMs,silenceMs:c?.silenceMs??_.silenceMs??A.silenceMs,speechThreshold:c?.speechThreshold??_.speechThreshold??A.speechThreshold,transcriptStabilityMs:c?.transcriptStabilityMs??_.transcriptStabilityMs??A.transcriptStabilityMs}};var xc={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"}}},a=(c="default")=>{let g=xc[c];return{audioConditioning:m(g.audioConditioning),capture:{channelCount:g.capture?.channelCount??1,sampleRateHz:g.capture?.sampleRateHz??16000},connection:{...g.connection},name:c,sttLifecycle:g.sttLifecycle??"continuous",turnDetection:r(g.turnDetection)}};var dc=(c)=>({assistantAudio:[...c.assistantAudio],assistantStreamingText:c.assistantStreamingText,assistantTexts:[...c.assistantTexts],call:c.call,error:c.error,isConnected:c.isConnected,isRecording:!1,partial:c.partial,reconnect:c.reconnect,recordingError:null,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata,scenarioId:c.scenarioId,status:c.status,turns:[...c.turns]}),u=(c,g={})=>{let C=a(g.preset),A=s(c,{...C.connection,...g.connection}),_=null,V=dc(A),R=new Set,w=()=>{for(let U of R)U()},L=()=>{if(V={...V,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&&V.status==="completed"&&V.isRecording)_?.stop(),_=null,V={...V,isRecording:!1};w()},$=A.subscribe(L);L();let S=()=>{if(_)return _;return _=l({channelCount:g.capture?.channelCount??C.capture.channelCount,onLevel:g.capture?.onLevel,onAudio:(U)=>{if(g.capture?.onAudio){g.capture.onAudio(U,A.sendAudio);return}A.sendAudio(U)},sampleRateHz:g.capture?.sampleRateHz??C.capture.sampleRateHz,...g.capture?.stream?{stream:g.capture.stream}:{}}),_},O=()=>{_?.stop(),_=null,V={...V,isRecording:!1},w()},y=async()=>{if(V.isRecording)return;try{V={...V,recordingError:null},w(),await S().start(),V={...V,isRecording:!0},w()}catch(U){throw _=null,V={...V,isRecording:!1,recordingError:U instanceof Error?U.message:String(U)},w(),U}};return{close:()=>{$(),O(),A.close()},startRecording:y,stopRecording:O,get assistantAudio(){return V.assistantAudio},get assistantTexts(){return V.assistantTexts},get assistantStreamingText(){return V.assistantStreamingText},bindHTMX(U){return k(A,U)},get call(){return V.call},callControl:(U)=>A.callControl(U),endTurn:()=>A.endTurn(),get error(){return V.error},getServerSnapshot:()=>V,getSnapshot:()=>V,get isConnected(){return V.isConnected},get isRecording(){return V.isRecording},get partial(){return V.partial},get reconnect(){return V.reconnect},get recordingError(){return V.recordingError},get scenarioId(){return V.scenarioId},sendAudio:(U)=>A.sendAudio(U),get sessionId(){return V.sessionId},get sessionMetadata(){return V.sessionMetadata},simulateDisconnect:()=>A.simulateDisconnect(),get status(){return V.status},subscribe:(U)=>{return R.add(U),()=>{R.delete(U)}},toggleRecording:async()=>{if(V.isRecording){O();return}await y()},get turns(){return V.turns}}};var X=(c)=>String(c).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var e=(c)=>{if(!c.isConnected)return"idle";if(c.isPlaying)return"speaking";if(c.isRecording&&c.hasActivePartial)return"listening";if(c.isRecording)return"listening";if(c.lastTranscriptAt&&!c.lastAssistantAt)return"thinking";if(c.lastTranscriptAt&&c.lastAssistantAt&&c.lastTranscriptAt>c.lastAssistantAt)return"thinking";return"idle"};var fc={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},Fc={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},nc=(c,g)=>{switch(c){case"listening":return g.listening;case"speaking":return g.speaking;case"thinking":return g.thinking;case"idle":return g.idle}},p=(c)=>{let g={...fc,...c.theme},C={...Fc,...c.labels},A=c.state.assistantAudio.at(-1)?.receivedAt,_=c.state.turns.at(-1)?.committedAt,V=e({hasActivePartial:c.state.partial.length>0,isConnected:c.state.isConnected,isPlaying:!1,isRecording:c.state.isRecording,lastAssistantAt:A,lastTranscriptAt:_}),R=!c.state.isConnected&&c.state.status!=="idle"&&!c.state.error,w=c.state.error?"Error":R?C.connecting:c.state.status==="completed"?C.callEnded:nc(V,C);return{agentState:V,classes:{container:`absolute-voice-widget absolute-voice-widget--${V}`,dot:`absolute-voice-widget__dot${c.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:c.state.isConnected,canMute:c.state.isRecording,canStart:!c.state.isRecording&&c.state.status!=="completed"},errorMessage:c.state.error??void 0,labels:C,partial:c.state.partial||void 0,statusLabel:w,theme:g,title:c.title??"Voice"}},oc=(c)=>typeof c==="number"?`${c}px`:c,t=(c)=>{let g=c.theme,C=`background:${g.background};border-radius:${oc(g.radius)};color:${g.foreground};font-family:${g.fontFamily};min-width:240px;padding:20px 22px;`,A=`background:${c.errorMessage?g.errorAccent:c.agentState==="idle"?"rgba(148,163,184,0.6)":g.accent};border-radius:50%;height:10px;width:10px;`,_=[];if(c.controls.canStart)_.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;">${X(c.labels.startCall)}</button>`);if(c.controls.canMute)_.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${g.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${X(c.labels.mute)}</button>`);if(c.controls.canEnd)_.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;">${X(c.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${c.agentState}" class="${X(c.classes.container)}" style="${C}">
|
|
1
|
+
(()=>{var{defineProperty:E,getOwnPropertyNames:I0,getOwnPropertyDescriptor:H0}=Object,_0=Object.prototype.hasOwnProperty;function G0(g){return this[g]}var L0=(g)=>{var A=(x??=new WeakMap).get(g),C;if(A)return A;if(A=E({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of I0(g))if(!_0.call(A,V))E(A,V,{get:G0.bind(g,V),enumerable:!(C=H0(g,V))||C.enumerable})}return x.set(g,A),A},x;var U0=(g)=>g;function w0(g,A){this[g]=U0.bind(null,A)}var y0=(g,A)=>{for(var C in A)E(g,C,{get:A[C],enumerable:!0,configurable:!0,set:w0.bind(A,C)})};var r0={};y0(r0,{mount:()=>V0,default:()=>s0,VOICE_EMBED_VERSION:()=>C0});var X0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},h0=(g,A,C,V)=>{let I=A??g.getAttribute("hx-get")??"";if(!I)return"";let R=new URL(I,window.location.origin);if(V)R.searchParams.set(C,V);else R.searchParams.delete(C);return`${R.pathname}${R.search}${R.hash}`},F=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=X0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",I=A.sessionQueryParam??"sessionId",R=()=>{let L=window,$=h0(C,A.route,I,g.sessionId);if($)C.setAttribute("hx-get",$);L.htmx?.process?.(C),L.htmx?.trigger?.(C,V)},w=g.subscribe(R);return R(),()=>{w()}};var D0=(g)=>Math.max(-1,Math.min(1,g)),J0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=D0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},Y0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let I of C){let R=I/32768;V+=R*R}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},Z0=(g,A,C)=>{if(A===C)return g;let V=A/C,I=Math.round(g.length/V),R=new Float32Array(I),w=0,L=0;while(w<R.length){let $=Math.round((w+1)*V),X=0,G=0;for(let h=L;h<$&&h<g.length;h+=1)X+=g[h]??0,G+=1;R[w]=G>0?X/G:0,w+=1,L=$}return R},f=(g)=>{let A=null,C=null,V=null,I=null;return{start:async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let L=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!L)throw Error("Browser microphone capture requires AudioContext support.");if(I=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new L,A.state==="suspended")await A.resume();C=A.createMediaStreamSource(I),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=($)=>{let X=$.inputBuffer.getChannelData(0),G=Z0(X,A?.sampleRate??48000,g.sampleRateHz??16000),h=J0(G);g.onLevel?.(Y0(h)),g.onAudio(h)},C.connect(V),V.connect(A.destination)},stop:()=>{V?.disconnect(),C?.disconnect(),I?.getTracks().forEach((L)=>L.stop()),A?.close(),g.onLevel?.(0),A=null,I=null,V=null,C=null}}};var M=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return M(A.error);if("cause"in A)return M(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},l=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:M(g.message),type:"error"};case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var gg=Math.PI*2;var T=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var O0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,z=(g)=>g.length===0?void 0:Math.max(...g);var D=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},B=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},Y=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},k=(g)=>String(g.id??Y(g,"ssrc")??D(g,"ssrc")??Y(g,"trackIdentifier")??Y(g,"mid")??"unknown"),d=(g)=>g===void 0?void 0:g*1000;var S0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var v=(g={})=>{let A=g.stats??[],C=[],V=A.filter((H)=>H.type==="inbound-rtp"&&Y(H,"kind")!=="video"),I=A.filter((H)=>H.type==="outbound-rtp"&&Y(H,"kind")!=="video"),R=A.filter((H)=>H.type==="candidate-pair"),w=A.filter((H)=>(H.type==="track"||H.type==="media-source")&&Y(H,"kind")==="audio"),L=R.filter((H)=>B(H,"selected")===!0||B(H,"nominated")===!0||Y(H,"state")==="succeeded").length,$=w.filter((H)=>Y(H,"readyState")!=="ended"&&Y(H,"trackState")!=="ended"&&B(H,"ended")!==!0).length,X=w.filter((H)=>Y(H,"readyState")==="ended"||Y(H,"trackState")==="ended"||B(H,"ended")===!0).length,G=V.reduce((H,J)=>H+(D(J,"packetsReceived")??0),0),h=I.reduce((H,J)=>H+(D(J,"packetsSent")??0),0),_=[...V,...I].reduce((H,J)=>H+Math.max(0,D(J,"packetsLost")??0),0),Z=G+_,y=Z===0?0:_/Z,K=V.reduce((H,J)=>H+(D(J,"bytesReceived")??0),0),O=I.reduce((H,J)=>H+(D(J,"bytesSent")??0),0),S=z(R.map((H)=>d(D(H,"currentRoundTripTime")??D(H,"roundTripTime"))).filter((H)=>H!==void 0)),W=z([...V,...I].map((H)=>d(D(H,"jitter"))).filter((H)=>H!==void 0)),N=z(V.map((H)=>{let J=D(H,"jitterBufferDelay"),U=D(H,"jitterBufferEmittedCount");return J!==void 0&&U!==void 0&&U>0?J/U*1000:void 0}).filter((H)=>H!==void 0)),j=w.map((H)=>D(H,"audioLevel")).filter((H)=>H!==void 0);if(g.requireConnectedCandidatePair&&R.length>0&&L===0)T(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&$===0)T(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&y>g.maxPacketLossRatio)T(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(y)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&S!==void 0&&S>g.maxRoundTripTimeMs)T(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(S)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&W!==void 0&&W>g.maxJitterMs)T(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(W)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:L,audioLevelAverage:O0(j),bytesReceived:K,bytesSent:O,checkedAt:Date.now(),endedAudioTracks:X,inboundPackets:G,issues:C,jitterBufferDelayMs:N,jitterMs:W,liveAudioTracks:$,outboundPackets:h,packetLossRatio:y,packetsLost:_,roundTripTimeMs:S,status:C.some((H)=>H.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},i=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(S0)};var o=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],I=new Map(C.map((_)=>[k(_),_])),w=A.filter((_)=>(_.type==="inbound-rtp"||_.type==="outbound-rtp")&&Y(_,"kind")!=="video"&&Y(_,"mediaType")!=="video").map((_)=>{let Z=_.type==="outbound-rtp"?"outbound":"inbound",y=Z==="outbound"?"packetsSent":"packetsReceived",K=Z==="outbound"?"bytesSent":"bytesReceived",O=I.get(k(_)),S=D(_,y),W=O?D(O,y):void 0,N=D(_,K),j=O?D(O,K):void 0,H=_.timestamp!==void 0&&O?.timestamp!==void 0?_.timestamp-O.timestamp:void 0;return{bytesDelta:N!==void 0&&j!==void 0?N-j:void 0,currentPackets:S,direction:Z,id:k(_),packetDelta:S!==void 0&&W!==void 0?S-W:void 0,previousPackets:W,timeDeltaMs:H}}),L=w.filter((_)=>_.direction==="inbound"),$=w.filter((_)=>_.direction==="outbound"),X=z(w.map((_)=>_.timeDeltaMs).filter((_)=>_!==void 0)),G=L.filter((_)=>g.maxInboundPacketStallMs!==void 0&&_.timeDeltaMs!==void 0&&_.timeDeltaMs>=g.maxInboundPacketStallMs&&_.packetDelta!==void 0&&_.packetDelta<=0).length,h=$.filter((_)=>g.maxOutboundPacketStallMs!==void 0&&_.timeDeltaMs!==void 0&&_.timeDeltaMs>=g.maxOutboundPacketStallMs&&_.packetDelta!==void 0&&_.packetDelta<=0).length;if(g.requireInboundAudio&&L.length===0)T(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&$.length===0)T(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&X!==void 0&&X>g.maxGapMs)T(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(X)}ms above ${String(g.maxGapMs)}ms.`);if(G>0)T(V,"error","media.webrtc_inbound_stalled",`${String(G)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(h>0)T(V,"error","media.webrtc_outbound_stalled",`${String(h)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:L.length,issues:V,maxObservedGapMs:X,outboundAudioStreams:$.length,stalledInboundStreams:G,stalledOutboundStreams:h,status:V.some((_)=>_.severity==="error")?"fail":V.length>0?"warn":"pass",streams:w,totalStats:A.length}};var W0="/api/voice/browser-media",Q0=5000,T0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,c0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??W0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},n=(g)=>{let A=null,C=[],V=async()=>{let w=await T0(g);if(!w)return;let L=await i({peerConnection:w}),$=v({...g,stats:L}),X=g.continuity===!1?void 0:o({...g.continuity,previousStats:C,stats:L}),G={at:Date.now(),continuity:X,report:$,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=L,g.onReport?.(G),await c0(G,g),G},I=()=>{V().catch((w)=>{g.onError?.(w)})},R=()=>{if(A)clearInterval(A),A=null};return{close:R,reportOnce:V,stop:R,start:()=>{if(A)return;I(),A=setInterval(I,g.intervalMs??Q0)}}};var K0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var q=()=>{},P0=()=>q,q0={callControl:q,close:q,endTurn:q,send:q,sendAudio:q,simulateDisconnect:q,subscribe:P0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},N0=()=>crypto.randomUUID(),j0=(g,A,C)=>{let{hostname:V,port:I,protocol:R}=window.location,w=R==="https:"?"wss:":"ws:",L=I?`:${I}`:"",$=new URL(`${w}//${V}${L}${g}`);if($.searchParams.set("sessionId",A),C)$.searchParams.set("scenarioId",C);return $.toString()},B0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},z0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return B0(A)?A:null}catch{return null}},m=(g,A={})=>{if(typeof window>"u")return q0;let C=new Set,V=A.reconnect!==!1,I=A.maxReconnectAttempts??15,R=A.reconnectMaxDelayMs??8000,w=A.pingInterval??30000,L=(U)=>K0(U,500,R),$={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:A.sessionId??N0(),ws:null},X=(U)=>{C.forEach((Q)=>Q(U))},G=()=>{if($.pingInterval)clearInterval($.pingInterval),$.pingInterval=null;if($.reconnectTimeout)clearTimeout($.reconnectTimeout),$.reconnectTimeout=null},h=()=>{if($.ws?.readyState!==1)return;while($.pendingMessages.length>0){let U=$.pendingMessages.shift();if(U!==void 0)$.ws.send(U)}},_=()=>{$.reconnectAttempts+=1;let U=L($.reconnectAttempts),Q=Date.now()+U;X({reconnect:{attempts:$.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:I,nextAttemptAt:Q,status:"reconnecting"},type:"connection"}),$.reconnectTimeout=setTimeout(()=>{if($.reconnectAttempts>I){X({reconnect:{attempts:$.reconnectAttempts,maxAttempts:I,status:"exhausted"},type:"connection"});return}Z()},U)},Z=()=>{let U=new WebSocket(j0(g,$.sessionId,$.scenarioId));U.binaryType="arraybuffer",U.onopen=()=>{let Q=$.reconnectAttempts>0;if($.isConnected=!0,h(),Q)X({reconnect:{attempts:$.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:I,status:"resumed"},type:"connection"}),$.reconnectAttempts=0;C.forEach((P)=>P({scenarioId:$.scenarioId??void 0,sessionId:$.sessionId,status:"active",type:"session"})),$.pingInterval=setInterval(()=>{if(U.readyState===1)U.send(JSON.stringify({type:"ping"}))},w)},U.onmessage=(Q)=>{let P=z0(Q);if(!P)return;if(P.type==="session")$.sessionId=P.sessionId,$.scenarioId=P.scenarioId??$.scenarioId;C.forEach(($0)=>$0(P))},U.onclose=(Q)=>{if($.isConnected=!1,G(),V&&Q.code!==1000&&$.reconnectAttempts<I)_();else if(V&&Q.code!==1000)X({reconnect:{attempts:$.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:I,status:"exhausted"},type:"connection"})},$.ws=U},y=(U)=>{if($.ws?.readyState===1){$.ws.send(U);return}$.pendingMessages.push(U)},K=(U)=>{y(JSON.stringify(U))},O=(U={})=>{if(U.sessionId)$.sessionId=U.sessionId;if(U.scenarioId)$.scenarioId=U.scenarioId;K({scenarioId:$.scenarioId??void 0,sessionId:$.sessionId,type:"start"})},S=(U)=>{y(U)},W=()=>{K({type:"end_turn"})},N=(U)=>{K({...U,type:"call_control"})},j=()=>{if(G(),$.ws)$.ws.close(1000),$.ws=null;$.isConnected=!1,C.clear()},H=()=>{if($.ws?.readyState===1)$.ws.close(4000,"absolutejs-voice-reconnect-proof")},J=(U)=>{return C.add(U),()=>{C.delete(U)}};return Z(),{callControl:N,close:j,endTurn:W,send:K,sendAudio:S,simulateDisconnect:H,start:O,subscribe:J,getReadyState:()=>$.ws?.readyState??3,getScenarioId:()=>$.scenarioId??"",getSessionId:()=>$.sessionId}};var b0=()=>({attempts:0,maxAttempts:0,status:"idle"}),E0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},M0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},k0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:b0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),u=()=>{let g=k0(),A="",C=new Set,V=()=>{C.forEach((R)=>R())};return{dispatch:(R)=>{switch(R.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:R.chunk,format:R.format,receivedAt:R.receivedAt,turnId:R.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,R.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${R.delta}`};break;case"complete":g={...g,sessionId:R.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:R.event.type==="end"?R.event.disposition:g.call?.disposition,endedAt:R.event.type==="end"?R.event.at:g.call?.endedAt,events:[...g.call?.events??[],R.event],lastEventAt:R.event.at,startedAt:g.call?.startedAt??R.event.at},sessionId:R.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:R.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:R.message};break;case"final":A=E0(A,R.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:M0(A,R.transcript.text)};break;case"replay":A=R.partial,g={...g,assistantStreamingText:"",assistantTexts:[...R.assistantTexts],call:R.call??null,error:null,isConnected:R.status==="active",partial:R.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:R.scenarioId??g.scenarioId,sessionId:R.sessionId,sessionMetadata:R.sessionMetadata??g.sessionMetadata,status:R.status,turns:[...R.turns]};break;case"session":g={...g,error:null,scenarioId:R.scenarioId??g.scenarioId,isConnected:R.status==="active",sessionId:R.sessionId,sessionMetadata:R.sessionMetadata??g.sessionMetadata,status:R.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,R.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(R)=>{return C.add(R),()=>{C.delete(R)}}}};var r=(g,A={})=>{let C=m(g,A),V=u(),I=A.browserMedia&&typeof window<"u"?n({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,R=new Set,w=(G)=>Promise.resolve().then(()=>{if(!G?.sessionId&&!G?.scenarioId)return;C.start(G),I?.start()}),L=()=>{R.forEach((G)=>G())},$=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let G=V.getSnapshot(),h=JSON.stringify({at:Date.now(),reconnect:G.reconnect,scenarioId:G.scenarioId,sessionId:C.getSessionId(),turnIds:G.turns.map((_)=>_.id)});fetch(A.reconnectReportPath,{body:h,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},X=C.subscribe((G)=>{let h=l(G);if(h){if(V.dispatch(h),G.type==="connection")$();L()}});return{start:w,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl(G){C.callControl(G)},close(){X(),I?.close(),C.close(),V.dispatch({type:"disconnected"}),L()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio(G){C.sendAudio(G)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe(G){return R.add(G),()=>{R.delete(G)}},get turns(){return V.getSnapshot().turns}}};var s=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var b=1200;var x0={balanced:{qualityProfile:"general",semanticVetoMaxMs:0,semanticVetoRecheckMs:b,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",semanticVetoMaxMs:0,semanticVetoRecheckMs:b,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",semanticVetoMaxMs:0,semanticVetoRecheckMs:b,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},F0={"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}},f0="fast",l0="general",a=(g)=>{let A=g?.profile??f0,C=g?.qualityProfile??l0,V=x0[A],I=F0[C];return{profile:A,qualityProfile:C,semanticVetoMaxMs:g?.semanticVetoMaxMs??V.semanticVetoMaxMs,semanticVetoRecheckMs:g?.semanticVetoRecheckMs??V.semanticVetoRecheckMs,silenceMs:g?.silenceMs??I.silenceMs??V.silenceMs,speechThreshold:g?.speechThreshold??I.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??I.transcriptStabilityMs??V.transcriptStabilityMs}};var d0={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"}}},p=(g="default")=>{let A=d0[g];return{audioConditioning:s(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:a(A.turnDetection)}};var v0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),t=(g,A={})=>{let C=p(A.preset),V=r(g,{...C.connection,...A.connection}),I=null,R=v0(V),w=new Set,L=()=>{for(let y of w)y()},$=()=>{if(R={...R,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&R.status==="completed"&&R.isRecording)I?.stop(),I=null,R={...R,isRecording:!1};L()},X=V.subscribe($);$();let G=()=>{if(I)return I;return I=f({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(y)=>{if(A.capture?.onAudio){A.capture.onAudio(y,V.sendAudio);return}V.sendAudio(y)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),I},h=()=>{I?.stop(),I=null,R={...R,isRecording:!1},L()},_=async()=>{if(R.isRecording)return;try{R={...R,recordingError:null},L(),await G().start(),R={...R,isRecording:!0},L()}catch(y){throw I=null,R={...R,isRecording:!1,recordingError:y instanceof Error?y.message:String(y)},L(),y}};return{close:()=>{X(),h(),V.close()},startRecording:_,stopRecording:h,get assistantAudio(){return R.assistantAudio},get assistantTexts(){return R.assistantTexts},get assistantStreamingText(){return R.assistantStreamingText},bindHTMX(y){return F(V,y)},get call(){return R.call},callControl:(y)=>V.callControl(y),endTurn:()=>V.endTurn(),get error(){return R.error},getServerSnapshot:()=>R,getSnapshot:()=>R,get isConnected(){return R.isConnected},get isRecording(){return R.isRecording},get partial(){return R.partial},get reconnect(){return R.reconnect},get recordingError(){return R.recordingError},get scenarioId(){return R.scenarioId},sendAudio:(y)=>V.sendAudio(y),get sessionId(){return R.sessionId},get sessionMetadata(){return R.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return R.status},subscribe:(y)=>{return w.add(y),()=>{w.delete(y)}},toggleRecording:async()=>{if(R.isRecording){h();return}await _()},get turns(){return R.turns}}};var c=(g)=>String(g).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var e=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var i0={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},o0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},n0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},g0=(g)=>{let A={...i0,...g.theme},C={...o0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,I=g.state.turns.at(-1)?.committedAt,R=e({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:I}),w=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,L=g.state.error?"Error":w?C.connecting:g.state.status==="completed"?C.callEnded:n0(R,C);return{agentState:R,classes:{container:`absolute-voice-widget absolute-voice-widget--${R}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:L,theme:A,title:g.title??"Voice"}},m0=(g)=>typeof g==="number"?`${g}px`:g,A0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${m0(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,I=[];if(g.controls.canStart)I.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${c(g.labels.startCall)}</button>`);if(g.controls.canMute)I.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${c(g.labels.mute)}</button>`);if(g.controls.canEnd)I.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${c(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${c(g.classes.container)}" style="${C}">
|
|
2
2
|
<div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
|
|
3
|
-
<span aria-hidden="true" class="${
|
|
4
|
-
<strong style="font-size:15px;">${
|
|
5
|
-
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${
|
|
3
|
+
<span aria-hidden="true" class="${c(g.classes.dot)}" style="${V}"></span>
|
|
4
|
+
<strong style="font-size:15px;">${c(g.title)}</strong>
|
|
5
|
+
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${c(g.statusLabel)}</span>
|
|
6
6
|
</div>
|
|
7
|
-
${
|
|
8
|
-
<div style="display:flex;gap:10px;">${
|
|
9
|
-
${
|
|
10
|
-
</div>`};var
|
|
7
|
+
${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${c(g.partial)}”</p>`:""}
|
|
8
|
+
<div style="display:flex;gap:10px;">${I.join("")}</div>
|
|
9
|
+
${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${c(g.errorMessage)}</p>`:""}
|
|
10
|
+
</div>`};var u0=(g)=>{if(typeof g!=="string")return g;let A=document.querySelector(g);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${g}"`);return A},V0=(g,A={})=>{let C=u0(g),V=t(A.path??"/voice",A.controllerOptions),I=null,R=null,w=()=>{let $=g0({...A.labels!==void 0?{labels:A.labels}:{},state:{assistantAudio:V.assistantAudio,error:V.error,isConnected:V.isConnected,isRecording:V.isRecording,partial:V.partial,status:V.status,turns:V.turns},...A.theme!==void 0?{theme:A.theme}:{},...A.title!==void 0?{title:A.title}:{}});C.innerHTML=A0($);for(let X of C.querySelectorAll("button[data-action]")){let{action:G}=X.dataset;X.addEventListener("click",()=>{if(G==="start")V.startRecording();else if(G==="mute")V.stopRecording();else if(G==="end")V.close()})}if(V.error&&V.error!==I)I=V.error,A.onError?.(V.error);if(V.status!==R)R=V.status,A.onStatusChange?.(V.status)},L=V.subscribe(w);if(w(),A.autoStart)V.startRecording();return{controller:V,async end(){await V.close()},mute(){V.stopRecording()},async start(){await V.startRecording()},unmount(){L(),V.close(),C.innerHTML=""}}},C0="0.0.22-beta.516",R0={mount:V0,version:C0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=R0;var s0=R0;})();
|
package/dist/index.d.ts
CHANGED
|
@@ -192,6 +192,7 @@ export { createVoiceSQLiteAuditEventStore, createVoiceSQLiteAuditSinkDeliverySto
|
|
|
192
192
|
export { createVoicePostgresAuditEventStore, createVoicePostgresAuditSinkDeliveryStore, createVoicePostgresCampaignStore, createVoicePostgresExternalObjectMapStore, createVoicePostgresIntegrationEventStore, createVoicePostgresReviewStore, createVoicePostgresRuntimeStorage, createVoicePostgresSessionStore, createVoicePostgresTaskStore, createVoicePostgresTelephonyWebhookIdempotencyStore, createVoicePostgresTraceSinkDeliveryStore, createVoicePostgresTraceEventStore, } from "./core/postgresStore";
|
|
193
193
|
export { createVoiceS3RecordingStore, createVoiceS3ReviewStore, } from "./core/s3Store";
|
|
194
194
|
export { createVoiceMemoryStore } from "./core/memoryStore";
|
|
195
|
+
export { createVoiceWriteBehindStore, type VoiceWriteBehindStore, type VoiceWriteBehindStoreOptions, } from "./core/writeBehindStore";
|
|
195
196
|
export { createVoiceCRMActivitySink, createVoiceHelpdeskTicketSink, createVoiceIntegrationHTTPSink, createVoiceHubSpotTaskSink, createVoiceHubSpotTaskSyncSinks, createVoiceHubSpotTaskUpdateSink, createVoiceLinearIssueSink, createVoiceLinearIssueSyncSinks, createVoiceLinearIssueUpdateSink, createVoiceZendeskTicketSink, createVoiceZendeskTicketSyncSinks, createVoiceZendeskTicketUpdateSink, deliverVoiceIntegrationEventToSinks, } from "./core/opsSinks";
|
|
196
197
|
export { createVoiceOpsWebhookEnvelope, createVoiceOpsWebhookReceiverRoutes, createVoiceOpsWebhookSink, verifyVoiceOpsWebhookSignature, } from "./core/opsWebhook";
|
|
197
198
|
export { applyVoiceHandoffDeliveryResult, createVoiceHandoffDeliveryRecord, createVoiceMemoryHandoffDeliveryStore, createVoiceTwilioRedirectHandoffAdapter, createVoiceWebhookHandoffAdapter, deliverVoiceHandoff, deliverVoiceHandoffDelivery, } from "./core/handoff";
|
package/dist/index.js
CHANGED
|
@@ -47076,6 +47076,113 @@ var createVoiceMemoryStore = () => {
|
|
|
47076
47076
|
};
|
|
47077
47077
|
return { get, getOrCreate, list, remove, set };
|
|
47078
47078
|
};
|
|
47079
|
+
// src/core/writeBehindStore.ts
|
|
47080
|
+
var DEFAULT_FLUSH_DEBOUNCE_MS = 750;
|
|
47081
|
+
var MAX_FLUSH_DRAIN_PASSES = 5;
|
|
47082
|
+
var createVoiceWriteBehindStore = (options) => {
|
|
47083
|
+
const memory = options.memory ?? createVoiceMemoryStore();
|
|
47084
|
+
const { persistent } = options;
|
|
47085
|
+
const flushDebounceMs = options.flushDebounceMs ?? DEFAULT_FLUSH_DEBOUNCE_MS;
|
|
47086
|
+
const logger = options.logger;
|
|
47087
|
+
const pending = new Map;
|
|
47088
|
+
const persistedTurnCount = new Map;
|
|
47089
|
+
let timer = null;
|
|
47090
|
+
let flushChain = Promise.resolve();
|
|
47091
|
+
const doFlush = async () => {
|
|
47092
|
+
if (pending.size === 0) {
|
|
47093
|
+
return;
|
|
47094
|
+
}
|
|
47095
|
+
const batch = new Map(pending);
|
|
47096
|
+
pending.clear();
|
|
47097
|
+
for (const [id, value] of batch) {
|
|
47098
|
+
try {
|
|
47099
|
+
await persistent.set(id, value);
|
|
47100
|
+
persistedTurnCount.set(id, value.turns.length);
|
|
47101
|
+
} catch (error) {
|
|
47102
|
+
if (!pending.has(id)) {
|
|
47103
|
+
pending.set(id, value);
|
|
47104
|
+
}
|
|
47105
|
+
logger?.warn?.("voice write-behind flush failed; will retry", {
|
|
47106
|
+
error: error instanceof Error ? error.message : String(error),
|
|
47107
|
+
sessionId: id
|
|
47108
|
+
});
|
|
47109
|
+
scheduleFlush();
|
|
47110
|
+
}
|
|
47111
|
+
}
|
|
47112
|
+
};
|
|
47113
|
+
const runFlush = () => {
|
|
47114
|
+
flushChain = flushChain.then(doFlush, doFlush);
|
|
47115
|
+
return flushChain;
|
|
47116
|
+
};
|
|
47117
|
+
const scheduleFlush = () => {
|
|
47118
|
+
if (timer) {
|
|
47119
|
+
return;
|
|
47120
|
+
}
|
|
47121
|
+
timer = setTimeout(() => {
|
|
47122
|
+
timer = null;
|
|
47123
|
+
runFlush();
|
|
47124
|
+
}, flushDebounceMs);
|
|
47125
|
+
};
|
|
47126
|
+
const markDirty = (id, value) => {
|
|
47127
|
+
pending.set(id, value);
|
|
47128
|
+
if (value.turns.length > (persistedTurnCount.get(id) ?? 0)) {
|
|
47129
|
+
runFlush();
|
|
47130
|
+
} else {
|
|
47131
|
+
scheduleFlush();
|
|
47132
|
+
}
|
|
47133
|
+
};
|
|
47134
|
+
const hydrate = async (id) => {
|
|
47135
|
+
const persisted = await persistent.get(id);
|
|
47136
|
+
if (!persisted) {
|
|
47137
|
+
return;
|
|
47138
|
+
}
|
|
47139
|
+
await memory.set(id, persisted);
|
|
47140
|
+
persistedTurnCount.set(id, persisted.turns.length);
|
|
47141
|
+
return persisted;
|
|
47142
|
+
};
|
|
47143
|
+
const get = async (id) => {
|
|
47144
|
+
const live = await memory.get(id);
|
|
47145
|
+
if (live) {
|
|
47146
|
+
return live;
|
|
47147
|
+
}
|
|
47148
|
+
return hydrate(id);
|
|
47149
|
+
};
|
|
47150
|
+
const getOrCreate = async (id) => {
|
|
47151
|
+
const existing = await get(id);
|
|
47152
|
+
if (existing) {
|
|
47153
|
+
return existing;
|
|
47154
|
+
}
|
|
47155
|
+
return memory.getOrCreate(id);
|
|
47156
|
+
};
|
|
47157
|
+
const set = async (id, value) => {
|
|
47158
|
+
await memory.set(id, value);
|
|
47159
|
+
markDirty(id, value);
|
|
47160
|
+
};
|
|
47161
|
+
const remove = async (id) => {
|
|
47162
|
+
pending.delete(id);
|
|
47163
|
+
persistedTurnCount.delete(id);
|
|
47164
|
+
await memory.remove(id);
|
|
47165
|
+
await persistent.remove(id);
|
|
47166
|
+
};
|
|
47167
|
+
const list = () => memory.list();
|
|
47168
|
+
const flush = async () => {
|
|
47169
|
+
if (timer) {
|
|
47170
|
+
clearTimeout(timer);
|
|
47171
|
+
timer = null;
|
|
47172
|
+
}
|
|
47173
|
+
for (let pass = 0;pass < MAX_FLUSH_DRAIN_PASSES && pending.size > 0; pass += 1) {
|
|
47174
|
+
await runFlush();
|
|
47175
|
+
}
|
|
47176
|
+
await flushChain;
|
|
47177
|
+
};
|
|
47178
|
+
const dispose = () => {
|
|
47179
|
+
if (timer) {
|
|
47180
|
+
clearTimeout(timer);
|
|
47181
|
+
timer = null;
|
|
47182
|
+
}
|
|
47183
|
+
};
|
|
47184
|
+
return { dispose, flush, get, getOrCreate, list, remove, set };
|
|
47185
|
+
};
|
|
47079
47186
|
// src/core/scribe.ts
|
|
47080
47187
|
var createVoiceScribe = async (options) => {
|
|
47081
47188
|
const session = await options.stt.open({
|
|
@@ -52916,6 +53023,7 @@ export {
|
|
|
52916
53023
|
createVoiceZendeskTicketUpdateSink,
|
|
52917
53024
|
createVoiceZendeskTicketSyncSinks,
|
|
52918
53025
|
createVoiceZendeskTicketSink,
|
|
53026
|
+
createVoiceWriteBehindStore,
|
|
52919
53027
|
createVoiceWorkflowScenario,
|
|
52920
53028
|
createVoiceWorkflowContractPreset,
|
|
52921
53029
|
createVoiceWorkflowContractHandler,
|
package/dist/react/index.js
CHANGED
|
@@ -11433,9 +11433,11 @@ var createVoiceBrowserMediaReporter = (options) => {
|
|
|
11433
11433
|
var WS_OPEN = 1;
|
|
11434
11434
|
var WS_CLOSED = 3;
|
|
11435
11435
|
var WS_NORMAL_CLOSURE = 1000;
|
|
11436
|
-
var DEFAULT_MAX_RECONNECT_ATTEMPTS =
|
|
11436
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
11437
11437
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
11438
|
-
var
|
|
11438
|
+
var RECONNECT_BASE_DELAY_MS = 500;
|
|
11439
|
+
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
11440
|
+
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
11439
11441
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
11440
11442
|
var noop = () => {};
|
|
11441
11443
|
var noopUnsubscribe = () => noop;
|
|
@@ -11504,7 +11506,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11504
11506
|
const listeners = new Set;
|
|
11505
11507
|
const shouldReconnect = options.reconnect !== false;
|
|
11506
11508
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
11509
|
+
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
11507
11510
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
11511
|
+
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
11508
11512
|
const state = {
|
|
11509
11513
|
isConnected: false,
|
|
11510
11514
|
pendingMessages: [],
|
|
@@ -11540,8 +11544,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11540
11544
|
}
|
|
11541
11545
|
};
|
|
11542
11546
|
const scheduleReconnect = () => {
|
|
11543
|
-
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
11544
11547
|
state.reconnectAttempts += 1;
|
|
11548
|
+
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
11549
|
+
const nextAttemptAt = Date.now() + delayMs;
|
|
11545
11550
|
emitConnection({
|
|
11546
11551
|
reconnect: {
|
|
11547
11552
|
attempts: state.reconnectAttempts,
|
|
@@ -11565,7 +11570,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11565
11570
|
return;
|
|
11566
11571
|
}
|
|
11567
11572
|
connect();
|
|
11568
|
-
},
|
|
11573
|
+
}, delayMs);
|
|
11569
11574
|
};
|
|
11570
11575
|
const connect = () => {
|
|
11571
11576
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
package/dist/svelte/index.js
CHANGED
|
@@ -756,9 +756,11 @@ var createVoiceBrowserMediaReporter = (options) => {
|
|
|
756
756
|
var WS_OPEN = 1;
|
|
757
757
|
var WS_CLOSED = 3;
|
|
758
758
|
var WS_NORMAL_CLOSURE = 1000;
|
|
759
|
-
var DEFAULT_MAX_RECONNECT_ATTEMPTS =
|
|
759
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
760
760
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
761
|
-
var
|
|
761
|
+
var RECONNECT_BASE_DELAY_MS = 500;
|
|
762
|
+
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
763
|
+
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
762
764
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
763
765
|
var noop = () => {};
|
|
764
766
|
var noopUnsubscribe = () => noop;
|
|
@@ -827,7 +829,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
827
829
|
const listeners = new Set;
|
|
828
830
|
const shouldReconnect = options.reconnect !== false;
|
|
829
831
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
832
|
+
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
830
833
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
834
|
+
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
831
835
|
const state = {
|
|
832
836
|
isConnected: false,
|
|
833
837
|
pendingMessages: [],
|
|
@@ -863,8 +867,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
863
867
|
}
|
|
864
868
|
};
|
|
865
869
|
const scheduleReconnect = () => {
|
|
866
|
-
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
867
870
|
state.reconnectAttempts += 1;
|
|
871
|
+
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
872
|
+
const nextAttemptAt = Date.now() + delayMs;
|
|
868
873
|
emitConnection({
|
|
869
874
|
reconnect: {
|
|
870
875
|
attempts: state.reconnectAttempts,
|
|
@@ -888,7 +893,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
888
893
|
return;
|
|
889
894
|
}
|
|
890
895
|
connect();
|
|
891
|
-
},
|
|
896
|
+
}, delayMs);
|
|
892
897
|
};
|
|
893
898
|
const connect = () => {
|
|
894
899
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
package/dist/testing/index.js
CHANGED
|
@@ -2506,9 +2506,11 @@ var createVoiceBrowserMediaReporter = (options) => {
|
|
|
2506
2506
|
var WS_OPEN = 1;
|
|
2507
2507
|
var WS_CLOSED = 3;
|
|
2508
2508
|
var WS_NORMAL_CLOSURE = 1000;
|
|
2509
|
-
var DEFAULT_MAX_RECONNECT_ATTEMPTS =
|
|
2509
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
2510
2510
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
2511
|
-
var
|
|
2511
|
+
var RECONNECT_BASE_DELAY_MS = 500;
|
|
2512
|
+
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
2513
|
+
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
2512
2514
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
2513
2515
|
var noop = () => {};
|
|
2514
2516
|
var noopUnsubscribe = () => noop;
|
|
@@ -2577,7 +2579,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
2577
2579
|
const listeners = new Set;
|
|
2578
2580
|
const shouldReconnect = options.reconnect !== false;
|
|
2579
2581
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
2582
|
+
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
2580
2583
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
2584
|
+
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
2581
2585
|
const state = {
|
|
2582
2586
|
isConnected: false,
|
|
2583
2587
|
pendingMessages: [],
|
|
@@ -2613,8 +2617,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
2613
2617
|
}
|
|
2614
2618
|
};
|
|
2615
2619
|
const scheduleReconnect = () => {
|
|
2616
|
-
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
2617
2620
|
state.reconnectAttempts += 1;
|
|
2621
|
+
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
2622
|
+
const nextAttemptAt = Date.now() + delayMs;
|
|
2618
2623
|
emitConnection({
|
|
2619
2624
|
reconnect: {
|
|
2620
2625
|
attempts: state.reconnectAttempts,
|
|
@@ -2638,7 +2643,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
2638
2643
|
return;
|
|
2639
2644
|
}
|
|
2640
2645
|
connect();
|
|
2641
|
-
},
|
|
2646
|
+
}, delayMs);
|
|
2642
2647
|
};
|
|
2643
2648
|
const connect = () => {
|
|
2644
2649
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
package/dist/vue/index.js
CHANGED
|
@@ -10829,9 +10829,11 @@ var createVoiceBrowserMediaReporter = (options) => {
|
|
|
10829
10829
|
var WS_OPEN = 1;
|
|
10830
10830
|
var WS_CLOSED = 3;
|
|
10831
10831
|
var WS_NORMAL_CLOSURE = 1000;
|
|
10832
|
-
var DEFAULT_MAX_RECONNECT_ATTEMPTS =
|
|
10832
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
10833
10833
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
10834
|
-
var
|
|
10834
|
+
var RECONNECT_BASE_DELAY_MS = 500;
|
|
10835
|
+
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
10836
|
+
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
10835
10837
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
10836
10838
|
var noop = () => {};
|
|
10837
10839
|
var noopUnsubscribe = () => noop;
|
|
@@ -10900,7 +10902,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10900
10902
|
const listeners = new Set;
|
|
10901
10903
|
const shouldReconnect = options.reconnect !== false;
|
|
10902
10904
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
10905
|
+
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
10903
10906
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
10907
|
+
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
10904
10908
|
const state = {
|
|
10905
10909
|
isConnected: false,
|
|
10906
10910
|
pendingMessages: [],
|
|
@@ -10936,8 +10940,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10936
10940
|
}
|
|
10937
10941
|
};
|
|
10938
10942
|
const scheduleReconnect = () => {
|
|
10939
|
-
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
10940
10943
|
state.reconnectAttempts += 1;
|
|
10944
|
+
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
10945
|
+
const nextAttemptAt = Date.now() + delayMs;
|
|
10941
10946
|
emitConnection({
|
|
10942
10947
|
reconnect: {
|
|
10943
10948
|
attempts: state.reconnectAttempts,
|
|
@@ -10961,7 +10966,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10961
10966
|
return;
|
|
10962
10967
|
}
|
|
10963
10968
|
connect();
|
|
10964
|
-
},
|
|
10969
|
+
}, delayMs);
|
|
10965
10970
|
};
|
|
10966
10971
|
const connect = () => {
|
|
10967
10972
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|