@absolutejs/voice 0.0.22-beta.665 → 0.0.22-beta.666

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.
@@ -511,6 +511,11 @@ var serverMessageToAction = (message) => {
511
511
  transcript: message.transcript,
512
512
  type: "partial"
513
513
  };
514
+ case "playback_rate":
515
+ return {
516
+ rate: message.rate,
517
+ type: "playback_rate"
518
+ };
514
519
  case "replay":
515
520
  return {
516
521
  assistantTexts: message.assistantTexts,
@@ -693,6 +698,7 @@ var isVoiceServerMessage = (value) => {
693
698
  case "error":
694
699
  case "final":
695
700
  case "partial":
701
+ case "playback_rate":
696
702
  case "pong":
697
703
  case "replay":
698
704
  case "session":
@@ -1119,6 +1125,7 @@ var createInitialState = () => ({
1119
1125
  isConnected: false,
1120
1126
  paused: false,
1121
1127
  partial: "",
1128
+ playbackRate: null,
1122
1129
  reconnect: createInitialReconnectState(),
1123
1130
  scenarioId: null,
1124
1131
  sessionId: null,
@@ -1227,6 +1234,12 @@ var createVoiceStreamStore = () => {
1227
1234
  partial: joinPartial(turnFinalText, action.transcript.text)
1228
1235
  };
1229
1236
  break;
1237
+ case "playback_rate":
1238
+ state = {
1239
+ ...state,
1240
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
1241
+ };
1242
+ break;
1230
1243
  case "replay":
1231
1244
  turnFinalText = action.partial;
1232
1245
  state = {
@@ -1392,6 +1405,9 @@ var createVoiceStream = (path, options = {}) => {
1392
1405
  get partial() {
1393
1406
  return store.getSnapshot().partial;
1394
1407
  },
1408
+ get playbackRate() {
1409
+ return store.getSnapshot().playbackRate;
1410
+ },
1395
1411
  get paused() {
1396
1412
  return store.getSnapshot().paused;
1397
1413
  },
@@ -1764,6 +1780,7 @@ var createInitialState2 = (stream) => ({
1764
1780
  paused: stream.paused,
1765
1781
  pauseExpiresAt: stream.pauseExpiresAt,
1766
1782
  partial: stream.partial,
1783
+ playbackRate: stream.playbackRate,
1767
1784
  reconnect: stream.reconnect,
1768
1785
  recordingError: null,
1769
1786
  sessionId: stream.sessionId,
@@ -1801,6 +1818,7 @@ var createVoiceController = (path, options = {}) => {
1801
1818
  paused: stream.paused,
1802
1819
  pauseExpiresAt: stream.pauseExpiresAt,
1803
1820
  partial: stream.partial,
1821
+ playbackRate: stream.playbackRate,
1804
1822
  reconnect: stream.reconnect,
1805
1823
  sessionId: stream.sessionId,
1806
1824
  sessionMetadata: stream.sessionMetadata,
@@ -1947,6 +1965,9 @@ var createVoiceController = (path, options = {}) => {
1947
1965
  get partial() {
1948
1966
  return state.partial;
1949
1967
  },
1968
+ get playbackRate() {
1969
+ return state.playbackRate;
1970
+ },
1950
1971
  get paused() {
1951
1972
  return state.paused;
1952
1973
  },
@@ -13,6 +13,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
13
13
  message?: undefined;
14
14
  recoverable?: undefined;
15
15
  transcript?: undefined;
16
+ rate?: undefined;
16
17
  assistantTexts?: undefined;
17
18
  call?: undefined;
18
19
  partial?: undefined;
@@ -37,6 +38,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
37
38
  message?: undefined;
38
39
  recoverable?: undefined;
39
40
  transcript?: undefined;
41
+ rate?: undefined;
40
42
  assistantTexts?: undefined;
41
43
  call?: undefined;
42
44
  partial?: undefined;
@@ -61,6 +63,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
61
63
  message?: undefined;
62
64
  recoverable?: undefined;
63
65
  transcript?: undefined;
66
+ rate?: undefined;
64
67
  assistantTexts?: undefined;
65
68
  call?: undefined;
66
69
  partial?: undefined;
@@ -85,6 +88,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
85
88
  message?: undefined;
86
89
  recoverable?: undefined;
87
90
  transcript?: undefined;
91
+ rate?: undefined;
88
92
  assistantTexts?: undefined;
89
93
  call?: undefined;
90
94
  partial?: undefined;
@@ -109,6 +113,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
109
113
  message?: undefined;
110
114
  recoverable?: undefined;
111
115
  transcript?: undefined;
116
+ rate?: undefined;
112
117
  assistantTexts?: undefined;
113
118
  call?: undefined;
114
119
  partial?: undefined;
@@ -133,6 +138,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
133
138
  message?: undefined;
134
139
  recoverable?: undefined;
135
140
  transcript?: undefined;
141
+ rate?: undefined;
136
142
  assistantTexts?: undefined;
137
143
  call?: undefined;
138
144
  partial?: undefined;
@@ -157,6 +163,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
157
163
  reconnect?: undefined;
158
164
  event?: undefined;
159
165
  transcript?: undefined;
166
+ rate?: undefined;
160
167
  assistantTexts?: undefined;
161
168
  call?: undefined;
162
169
  partial?: undefined;
@@ -181,6 +188,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
181
188
  event?: undefined;
182
189
  message?: undefined;
183
190
  recoverable?: undefined;
191
+ rate?: undefined;
184
192
  assistantTexts?: undefined;
185
193
  call?: undefined;
186
194
  partial?: undefined;
@@ -205,6 +213,32 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
205
213
  event?: undefined;
206
214
  message?: undefined;
207
215
  recoverable?: undefined;
216
+ rate?: undefined;
217
+ assistantTexts?: undefined;
218
+ call?: undefined;
219
+ partial?: undefined;
220
+ scenarioId?: undefined;
221
+ sessionMetadata?: undefined;
222
+ status?: undefined;
223
+ turns?: undefined;
224
+ paused?: undefined;
225
+ pauseExpiresAt?: undefined;
226
+ turn?: undefined;
227
+ } | {
228
+ rate: number;
229
+ type: "playback_rate";
230
+ chunk?: undefined;
231
+ format?: undefined;
232
+ receivedAt?: undefined;
233
+ turnId?: undefined;
234
+ text?: undefined;
235
+ delta?: undefined;
236
+ sessionId?: undefined;
237
+ reconnect?: undefined;
238
+ event?: undefined;
239
+ message?: undefined;
240
+ recoverable?: undefined;
241
+ transcript?: undefined;
208
242
  assistantTexts?: undefined;
209
243
  call?: undefined;
210
244
  partial?: undefined;
@@ -236,6 +270,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
236
270
  message?: undefined;
237
271
  recoverable?: undefined;
238
272
  transcript?: undefined;
273
+ rate?: undefined;
239
274
  paused?: undefined;
240
275
  pauseExpiresAt?: undefined;
241
276
  turn?: undefined;
@@ -258,6 +293,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
258
293
  message?: undefined;
259
294
  recoverable?: undefined;
260
295
  transcript?: undefined;
296
+ rate?: undefined;
261
297
  assistantTexts?: undefined;
262
298
  call?: undefined;
263
299
  partial?: undefined;
@@ -278,6 +314,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
278
314
  message?: undefined;
279
315
  recoverable?: undefined;
280
316
  transcript?: undefined;
317
+ rate?: undefined;
281
318
  assistantTexts?: undefined;
282
319
  call?: undefined;
283
320
  partial?: undefined;
@@ -279,6 +279,11 @@ var serverMessageToAction = (message) => {
279
279
  transcript: message.transcript,
280
280
  type: "partial"
281
281
  };
282
+ case "playback_rate":
283
+ return {
284
+ rate: message.rate,
285
+ type: "playback_rate"
286
+ };
282
287
  case "replay":
283
288
  return {
284
289
  assistantTexts: message.assistantTexts,
@@ -610,6 +615,7 @@ var isVoiceServerMessage = (value) => {
610
615
  case "error":
611
616
  case "final":
612
617
  case "partial":
618
+ case "playback_rate":
613
619
  case "pong":
614
620
  case "replay":
615
621
  case "session":
@@ -1036,6 +1042,7 @@ var createInitialState = () => ({
1036
1042
  isConnected: false,
1037
1043
  paused: false,
1038
1044
  partial: "",
1045
+ playbackRate: null,
1039
1046
  reconnect: createInitialReconnectState(),
1040
1047
  scenarioId: null,
1041
1048
  sessionId: null,
@@ -1144,6 +1151,12 @@ var createVoiceStreamStore = () => {
1144
1151
  partial: joinPartial(turnFinalText, action.transcript.text)
1145
1152
  };
1146
1153
  break;
1154
+ case "playback_rate":
1155
+ state = {
1156
+ ...state,
1157
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
1158
+ };
1159
+ break;
1147
1160
  case "replay":
1148
1161
  turnFinalText = action.partial;
1149
1162
  state = {
@@ -1309,6 +1322,9 @@ var createVoiceStream = (path, options = {}) => {
1309
1322
  get partial() {
1310
1323
  return store.getSnapshot().partial;
1311
1324
  },
1325
+ get playbackRate() {
1326
+ return store.getSnapshot().playbackRate;
1327
+ },
1312
1328
  get paused() {
1313
1329
  return store.getSnapshot().paused;
1314
1330
  },
@@ -1644,6 +1660,7 @@ var createInitialState2 = (stream) => ({
1644
1660
  paused: stream.paused,
1645
1661
  pauseExpiresAt: stream.pauseExpiresAt,
1646
1662
  partial: stream.partial,
1663
+ playbackRate: stream.playbackRate,
1647
1664
  reconnect: stream.reconnect,
1648
1665
  recordingError: null,
1649
1666
  sessionId: stream.sessionId,
@@ -1681,6 +1698,7 @@ var createVoiceController = (path, options = {}) => {
1681
1698
  paused: stream.paused,
1682
1699
  pauseExpiresAt: stream.pauseExpiresAt,
1683
1700
  partial: stream.partial,
1701
+ playbackRate: stream.playbackRate,
1684
1702
  reconnect: stream.reconnect,
1685
1703
  sessionId: stream.sessionId,
1686
1704
  sessionMetadata: stream.sessionMetadata,
@@ -1827,6 +1845,9 @@ var createVoiceController = (path, options = {}) => {
1827
1845
  get partial() {
1828
1846
  return state.partial;
1829
1847
  },
1848
+ get playbackRate() {
1849
+ return state.playbackRate;
1850
+ },
1830
1851
  get paused() {
1831
1852
  return state.paused;
1832
1853
  },
@@ -172,6 +172,7 @@ var isVoiceServerMessage = (value) => {
172
172
  case "error":
173
173
  case "final":
174
174
  case "partial":
175
+ case "playback_rate":
175
176
  case "pong":
176
177
  case "replay":
177
178
  case "session":
@@ -1376,6 +1377,11 @@ var serverMessageToAction = (message) => {
1376
1377
  transcript: message.transcript,
1377
1378
  type: "partial"
1378
1379
  };
1380
+ case "playback_rate":
1381
+ return {
1382
+ rate: message.rate,
1383
+ type: "playback_rate"
1384
+ };
1379
1385
  case "replay":
1380
1386
  return {
1381
1387
  assistantTexts: message.assistantTexts,
@@ -1522,6 +1528,7 @@ var createInitialState2 = () => ({
1522
1528
  isConnected: false,
1523
1529
  paused: false,
1524
1530
  partial: "",
1531
+ playbackRate: null,
1525
1532
  reconnect: createInitialReconnectState(),
1526
1533
  scenarioId: null,
1527
1534
  sessionId: null,
@@ -1630,6 +1637,12 @@ var createVoiceStreamStore = () => {
1630
1637
  partial: joinPartial(turnFinalText, action.transcript.text)
1631
1638
  };
1632
1639
  break;
1640
+ case "playback_rate":
1641
+ state = {
1642
+ ...state,
1643
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
1644
+ };
1645
+ break;
1633
1646
  case "replay":
1634
1647
  turnFinalText = action.partial;
1635
1648
  state = {
@@ -1795,6 +1808,9 @@ var createVoiceStream = (path, options = {}) => {
1795
1808
  get partial() {
1796
1809
  return store.getSnapshot().partial;
1797
1810
  },
1811
+ get playbackRate() {
1812
+ return store.getSnapshot().playbackRate;
1813
+ },
1798
1814
  get paused() {
1799
1815
  return store.getSnapshot().paused;
1800
1816
  },
@@ -2360,6 +2376,7 @@ var createInitialState3 = (stream) => ({
2360
2376
  paused: stream.paused,
2361
2377
  pauseExpiresAt: stream.pauseExpiresAt,
2362
2378
  partial: stream.partial,
2379
+ playbackRate: stream.playbackRate,
2363
2380
  reconnect: stream.reconnect,
2364
2381
  recordingError: null,
2365
2382
  sessionId: stream.sessionId,
@@ -2397,6 +2414,7 @@ var createVoiceController = (path, options = {}) => {
2397
2414
  paused: stream.paused,
2398
2415
  pauseExpiresAt: stream.pauseExpiresAt,
2399
2416
  partial: stream.partial,
2417
+ playbackRate: stream.playbackRate,
2400
2418
  reconnect: stream.reconnect,
2401
2419
  sessionId: stream.sessionId,
2402
2420
  sessionMetadata: stream.sessionMetadata,
@@ -2543,6 +2561,9 @@ var createVoiceController = (path, options = {}) => {
2543
2561
  get partial() {
2544
2562
  return state.partial;
2545
2563
  },
2564
+ get playbackRate() {
2565
+ return state.playbackRate;
2566
+ },
2546
2567
  get paused() {
2547
2568
  return state.paused;
2548
2569
  },
@@ -2651,10 +2672,18 @@ var createVoiceDuplexController = (path, options = {}) => {
2651
2672
  }
2652
2673
  });
2653
2674
  const audioPlayer = createVoiceAudioPlayer(controller, options.audioPlayer);
2675
+ let appliedPlaybackRate = controller.playbackRate;
2676
+ const unsubscribePlaybackRate = controller.subscribe(() => {
2677
+ if (controller.playbackRate !== null && controller.playbackRate !== appliedPlaybackRate) {
2678
+ appliedPlaybackRate = controller.playbackRate;
2679
+ audioPlayer.setPlaybackRate(controller.playbackRate);
2680
+ }
2681
+ });
2654
2682
  bargeInBinding = bindVoiceBargeIn(controller, audioPlayer, options.bargeIn);
2655
2683
  const close = () => {
2656
2684
  bargeInBinding?.close();
2657
2685
  bargeInBinding = null;
2686
+ unsubscribePlaybackRate();
2658
2687
  audioPlayer.close();
2659
2688
  controller.close();
2660
2689
  };
@@ -601,6 +601,8 @@ export type VoiceSessionHandle<TContext = unknown, TSession extends VoiceSession
601
601
  /** Lift a caller-driven pause: watchdogs re-arm and turns process again. */
602
602
  resume: () => Promise<void>;
603
603
  snapshot: () => Promise<TSession>;
604
+ /** Ask connected browser clients to change assistant-audio playback speed. */
605
+ setPlaybackRate: (rate: number) => Promise<number>;
604
606
  /**
605
607
  * Mutate the live turn-detection config for this session — useful when a
606
608
  * tool call wants to dial silenceMs up ("the caller asked for more time")
@@ -1335,6 +1337,11 @@ export type VoiceServerAudioMessage = {
1335
1337
  receivedAt: number;
1336
1338
  turnId?: string;
1337
1339
  };
1340
+ /** Requests a live assistant-audio playback-rate change in browser clients. */
1341
+ export type VoiceServerPlaybackRateMessage = {
1342
+ type: "playback_rate";
1343
+ rate: number;
1344
+ };
1338
1345
  export type VoiceServerCompleteMessage = {
1339
1346
  type: "complete";
1340
1347
  sessionId: string;
@@ -1356,7 +1363,7 @@ export type VoiceServerConnectionMessage = {
1356
1363
  type: "connection";
1357
1364
  reconnect: VoiceReconnectClientState;
1358
1365
  };
1359
- export type VoiceServerMessage<TResult = unknown> = VoiceServerSessionMessage | VoiceServerReplayMessage<TResult> | VoiceServerPartialMessage | VoiceServerFinalMessage | VoiceServerTurnMessage<TResult> | VoiceServerAssistantMessage | VoiceServerAssistantDeltaMessage | VoiceServerAudioMessage | VoiceServerCallControlAckMessage | VoiceServerCallLifecycleMessage | VoiceServerCompleteMessage | VoiceServerErrorMessage | VoiceServerPongMessage | VoiceServerConnectionMessage;
1366
+ export type VoiceServerMessage<TResult = unknown> = VoiceServerSessionMessage | VoiceServerReplayMessage<TResult> | VoiceServerPartialMessage | VoiceServerFinalMessage | VoiceServerTurnMessage<TResult> | VoiceServerAssistantMessage | VoiceServerAssistantDeltaMessage | VoiceServerAudioMessage | VoiceServerPlaybackRateMessage | VoiceServerCallControlAckMessage | VoiceServerCallLifecycleMessage | VoiceServerCompleteMessage | VoiceServerErrorMessage | VoiceServerPongMessage | VoiceServerConnectionMessage;
1360
1367
  export type VoiceConnectionOptions = {
1361
1368
  browserMedia?: false | VoiceBrowserMediaReporterOptions;
1362
1369
  protocols?: string[];
@@ -1559,6 +1566,8 @@ export type VoiceStreamState<TResult = unknown> = {
1559
1566
  }>;
1560
1567
  error: string | null;
1561
1568
  isConnected: boolean;
1569
+ /** Latest server-requested assistant playback rate, or null if unchanged. */
1570
+ playbackRate: number | null;
1562
1571
  };
1563
1572
  export type VoiceStream<TResult = unknown> = {
1564
1573
  call: VoiceCallLifecycleState | null;
@@ -1578,6 +1587,7 @@ export type VoiceStream<TResult = unknown> = {
1578
1587
  getSnapshot: () => VoiceStreamState<TResult>;
1579
1588
  isConnected: boolean;
1580
1589
  partial: string;
1590
+ playbackRate: number | null;
1581
1591
  reconnect: VoiceReconnectClientState;
1582
1592
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
1583
1593
  simulateDisconnect: () => void;
@@ -1678,6 +1688,7 @@ export type VoiceController<TResult = unknown> = {
1678
1688
  isConnected: boolean;
1679
1689
  isRecording: boolean;
1680
1690
  partial: string;
1691
+ playbackRate: number | null;
1681
1692
  reconnect: VoiceReconnectClientState;
1682
1693
  recordingError: string | null;
1683
1694
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
@@ -1762,6 +1773,9 @@ export type VoiceStoreAction<TResult = unknown> = {
1762
1773
  type: "error";
1763
1774
  message: string;
1764
1775
  recoverable?: boolean;
1776
+ } | {
1777
+ type: "playback_rate";
1778
+ rate: number;
1765
1779
  } | {
1766
1780
  type: "connected";
1767
1781
  } | {
@@ -276,6 +276,11 @@ var serverMessageToAction = (message) => {
276
276
  transcript: message.transcript,
277
277
  type: "partial"
278
278
  };
279
+ case "playback_rate":
280
+ return {
281
+ rate: message.rate,
282
+ type: "playback_rate"
283
+ };
279
284
  case "replay":
280
285
  return {
281
286
  assistantTexts: message.assistantTexts,
@@ -607,6 +612,7 @@ var isVoiceServerMessage = (value) => {
607
612
  case "error":
608
613
  case "final":
609
614
  case "partial":
615
+ case "playback_rate":
610
616
  case "pong":
611
617
  case "replay":
612
618
  case "session":
@@ -1033,6 +1039,7 @@ var createInitialState = () => ({
1033
1039
  isConnected: false,
1034
1040
  paused: false,
1035
1041
  partial: "",
1042
+ playbackRate: null,
1036
1043
  reconnect: createInitialReconnectState(),
1037
1044
  scenarioId: null,
1038
1045
  sessionId: null,
@@ -1141,6 +1148,12 @@ var createVoiceStreamStore = () => {
1141
1148
  partial: joinPartial(turnFinalText, action.transcript.text)
1142
1149
  };
1143
1150
  break;
1151
+ case "playback_rate":
1152
+ state = {
1153
+ ...state,
1154
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
1155
+ };
1156
+ break;
1144
1157
  case "replay":
1145
1158
  turnFinalText = action.partial;
1146
1159
  state = {
@@ -1306,6 +1319,9 @@ var createVoiceStream = (path, options = {}) => {
1306
1319
  get partial() {
1307
1320
  return store.getSnapshot().partial;
1308
1321
  },
1322
+ get playbackRate() {
1323
+ return store.getSnapshot().playbackRate;
1324
+ },
1309
1325
  get paused() {
1310
1326
  return store.getSnapshot().paused;
1311
1327
  },
@@ -1641,6 +1657,7 @@ var createInitialState2 = (stream) => ({
1641
1657
  paused: stream.paused,
1642
1658
  pauseExpiresAt: stream.pauseExpiresAt,
1643
1659
  partial: stream.partial,
1660
+ playbackRate: stream.playbackRate,
1644
1661
  reconnect: stream.reconnect,
1645
1662
  recordingError: null,
1646
1663
  sessionId: stream.sessionId,
@@ -1678,6 +1695,7 @@ var createVoiceController = (path, options = {}) => {
1678
1695
  paused: stream.paused,
1679
1696
  pauseExpiresAt: stream.pauseExpiresAt,
1680
1697
  partial: stream.partial,
1698
+ playbackRate: stream.playbackRate,
1681
1699
  reconnect: stream.reconnect,
1682
1700
  sessionId: stream.sessionId,
1683
1701
  sessionMetadata: stream.sessionMetadata,
@@ -1824,6 +1842,9 @@ var createVoiceController = (path, options = {}) => {
1824
1842
  get partial() {
1825
1843
  return state.partial;
1826
1844
  },
1845
+ get playbackRate() {
1846
+ return state.playbackRate;
1847
+ },
1827
1848
  get paused() {
1828
1849
  return state.paused;
1829
1850
  },
@@ -1,10 +1,10 @@
1
- (()=>{var{defineProperty:k,getOwnPropertyNames:S0,getOwnPropertyDescriptor:w0}=Object,J0=Object.prototype.hasOwnProperty;function h0(V){return this[V]}var Y0=(V)=>{var A=(l??=new WeakMap).get(V),C;if(A)return A;if(A=k({},"__esModule",{value:!0}),V&&typeof V==="object"||typeof V==="function"){for(var g of S0(V))if(!J0.call(A,g))k(A,g,{get:h0.bind(V,g),enumerable:!(C=w0(V,g))||C.enumerable})}return l.set(V,A),A},l;var Z0=(V)=>V;function y0(V,A){this[V]=Z0.bind(null,A)}var Q0=(V,A)=>{for(var C in A)k(V,C,{get:A[C],enumerable:!0,configurable:!0,set:y0.bind(A,C)})};var _V={};Q0(_V,{mount:()=>$0,default:()=>TV,VOICE_EMBED_VERSION:()=>R0});var E0=(V)=>{if(typeof V!=="string")return V;return document.querySelector(V)},K0=(V,A,C,g)=>{let H=A??V.getAttribute("hx-get")??"";if(!H)return"";let _=new URL(H,window.location.origin);if(g)_.searchParams.set(C,g);else _.searchParams.delete(C);return`${_.pathname}${_.search}${_.hash}`},d=(V,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=E0(A.element);if(!C)return()=>{};let g=A.eventName??"voice-refresh",H=A.sessionQueryParam??"sessionId",_=()=>{let X=window,R=K0(C,A.route,H,V.sessionId);if(R)C.setAttribute("hx-get",R);X.htmx?.process?.(C),X.htmx?.trigger?.(C,g)},D=V.subscribe(_);return _(),()=>{D()}};var c0=(V)=>Math.max(-1,Math.min(1,V)),q0=(V)=>{let A=new Int16Array(V.length);for(let C=0;C<V.length;C+=1){let g=c0(V[C]??0);A[C]=g<0?g*32768:g*32767}return new Uint8Array(A.buffer)},N0=(V)=>{let A=V instanceof Uint8Array?V:new Uint8Array(V);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 g=0;for(let H of C){let _=H/32768;g+=_*_}return Math.min(1,Math.max(0,Math.sqrt(g/C.length)*5.5))},z0=(V,A,C)=>{if(A===C)return V;let g=A/C,H=Math.round(V.length/g),_=new Float32Array(H),D=0,X=0;while(D<_.length){let R=Math.round((D+1)*g),O=0,L=0;for(let W=X;W<R&&W<V.length;W+=1)O+=V[W]??0,L+=1;_[D]=L>0?O/L:0,D+=1,X=R}return _},v=(V)=>{let A=null,C=null,g=null,H=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(H)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 w=null,Z=null,Y=null,Q=null;try{if(Z=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(w=new T,w.state==="suspended")await w.resume();if(L!==_)throw Error("Microphone capture stopped during startup.");if(Q=w.createMediaStreamSource(Z),Y=w.createScriptProcessor(4096,1,1),Y.onaudioprocess=(G)=>{let K=G.inputBuffer.getChannelData(0),q=z0(K,w?.sampleRate??48000,V.sampleRateHz??16000),$=q0(q);V.onLevel?.(N0($)),V.onAudio($)},Q.connect(Y),Y.connect(w.destination),L!==_)throw Error("Microphone capture stopped during startup.");A=w,H=Z,g=Y,C=Q}catch(G){throw X({audioContext:w,mediaStream:Z,processorNode:Y,sourceNode:Q}),G}})().finally(()=>{if(D===U)D=null});return D=U,U},stop:()=>{_+=1,X({audioContext:A,mediaStream:H,processorNode:g,sourceNode:C}),V.onLevel?.(0),A=null,H=null,g=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 A=V;for(let C of["message","reason","description"]){let g=A[C];if(typeof g==="string"&&g.trim())return g}if("error"in A)return F(A.error);if("cause"in A)return F(A.cause);try{return JSON.stringify(V)}catch{}}return"Unexpected error"},i=(V)=>{switch(V.type){case"audio":return{chunk:Uint8Array.from(atob(V.chunkBase64),(A)=>A.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{paused:V.paused,pauseExpiresAt:V.pauseExpiresAt,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,A,C,g)=>{V.push({code:C,message:g,severity:A})};var P0=(V)=>V.length===0?void 0:V.reduce((A,C)=>A+C,0)/V.length,x=(V)=>V.length===0?void 0:Math.max(...V);var E=(V,A)=>{let C=V[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},j=(V,A)=>{let C=V[A];return typeof C==="boolean"?C:void 0},c=(V,A)=>{let C=V[A];return typeof C==="string"?C:void 0},f=(V)=>String(V.id??c(V,"ssrc")??E(V,"ssrc")??c(V,"trackIdentifier")??c(V,"mid")??"unknown"),o=(V)=>V===void 0?void 0:V*1000;var B0=(V)=>{let A={};for(let[C,g]of Object.entries(V))if(g===null||typeof g==="boolean"||typeof g==="number"||typeof g==="string")A[C]=g;return A};var n=(V={})=>{let A=V.stats??[],C=[],g=A.filter(($)=>$.type==="inbound-rtp"&&c($,"kind")!=="video"),H=A.filter(($)=>$.type==="outbound-rtp"&&c($,"kind")!=="video"),_=A.filter(($)=>$.type==="candidate-pair"),D=A.filter(($)=>($.type==="track"||$.type==="media-source")&&c($,"kind")==="audio"),X=_.filter(($)=>j($,"selected")===!0||j($,"nominated")===!0||c($,"state")==="succeeded").length,R=D.filter(($)=>c($,"readyState")!=="ended"&&c($,"trackState")!=="ended"&&j($,"ended")!==!0).length,O=D.filter(($)=>c($,"readyState")==="ended"||c($,"trackState")==="ended"||j($,"ended")===!0).length,L=g.reduce(($,J)=>$+(E(J,"packetsReceived")??0),0),W=H.reduce(($,J)=>$+(E(J,"packetsSent")??0),0),U=[...g,...H].reduce(($,J)=>$+Math.max(0,E(J,"packetsLost")??0),0),T=L+U,w=T===0?0:U/T,Z=g.reduce(($,J)=>$+(E(J,"bytesReceived")??0),0),Y=H.reduce(($,J)=>$+(E(J,"bytesSent")??0),0),Q=x(_.map(($)=>o(E($,"currentRoundTripTime")??E($,"roundTripTime"))).filter(($)=>$!==void 0)),G=x([...g,...H].map(($)=>o(E($,"jitter"))).filter(($)=>$!==void 0)),K=x(g.map(($)=>{let J=E($,"jitterBufferDelay"),b=E($,"jitterBufferEmittedCount");return J!==void 0&&b!==void 0&&b>0?J/b*1000:void 0}).filter(($)=>$!==void 0)),q=D.map(($)=>E($,"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&&R===0)N(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(V.maxPacketLossRatio!==void 0&&w>V.maxPacketLossRatio)N(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(w)} 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:Z,bytesSent:Y,checkedAt:Date.now(),endedAudioTracks:O,inboundPackets:L,issues:C,jitterBufferDelayMs:K,jitterMs:G,liveAudioTracks:R,outboundPackets:W,packetLossRatio:w,packetsLost:U,roundTripTimeMs:Q,status:C.some(($)=>$.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},m=async(V)=>{return[...(await V.peerConnection.getStats(V.selector??null)).values()].map(B0)};var u=(V={})=>{let A=V.stats??[],C=V.previousStats??[],g=[],H=new Map(C.map((U)=>[f(U),U])),D=A.filter((U)=>(U.type==="inbound-rtp"||U.type==="outbound-rtp")&&c(U,"kind")!=="video"&&c(U,"mediaType")!=="video").map((U)=>{let T=U.type==="outbound-rtp"?"outbound":"inbound",w=T==="outbound"?"packetsSent":"packetsReceived",Z=T==="outbound"?"bytesSent":"bytesReceived",Y=H.get(f(U)),Q=E(U,w),G=Y?E(Y,w):void 0,K=E(U,Z),q=Y?E(Y,Z):void 0,$=U.timestamp!==void 0&&Y?.timestamp!==void 0?U.timestamp-Y.timestamp:void 0;return{bytesDelta:K!==void 0&&q!==void 0?K-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"),R=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=R.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(g,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(V.requireOutboundAudio&&R.length===0)N(g,"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(g,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(O)}ms above ${String(V.maxGapMs)}ms.`);if(L>0)N(g,"error","media.webrtc_inbound_stalled",`${String(L)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(W>0)N(g,"error","media.webrtc_outbound_stalled",`${String(W)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:X.length,issues:g,maxObservedGapMs:O,outboundAudioStreams:R.length,stalledInboundStreams:L,stalledOutboundStreams:W,status:g.some((U)=>U.severity==="error")?"fail":g.length>0?"warn":"pass",streams:D,totalStats:A.length}};var M0="/api/voice/browser-media",b0=5000,j0=async(V)=>V.peerConnection??await V.getPeerConnection?.()??null,x0=async(V,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??M0,{body:JSON.stringify(V),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(V)=>{let A=null,C=[],g=async()=>{let D=await j0(V);if(!D)return;let X=await m({peerConnection:D}),R=n({...V,stats:X}),O=V.continuity===!1?void 0:u({...V.continuity,previousStats:C,stats:X}),L={at:Date.now(),continuity:O,report:R,scenarioId:V.getScenarioId?.()??null,sessionId:V.getSessionId?.()??null};return C=X,V.onReport?.(L),await x0(L,V),L},H=()=>{g().catch((D)=>{V.onError?.(D)})},_=()=>{if(A)clearInterval(A),A=null};return{close:_,reportOnce:g,stop:_,start:()=>{if(A)return;H(),A=setInterval(H,V.intervalMs??b0)}}};var k0=(V,A,C)=>Math.min(C,A*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,A,C,g)=>{let{hostname:H,port:_,protocol:D}=window.location,X=D==="https:"?"wss:":"ws:",R=_?`:${_}`:"",O=new URL(`${X}//${H}${R}${V}`);if(O.searchParams.set("sessionId",A),C)O.searchParams.set("scenarioId",C);for(let[L,W]of Object.entries(g))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 A=JSON.parse(V.data);return i0(A)?A:null}catch{return null}},a=(V,A={})=>{if(typeof window>"u")return l0;let C=new Set,g=new Map,H=A.reconnect!==!1,_=A.maxReconnectAttempts??15,D=A.reconnectMaxDelayMs??8000,X=A.pingInterval??30000,R=A.reconnectResetAfterMs??30000,O=0,L=!1,W=null,U=(I)=>k0(I,500,D),T={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:A.sessionId??d0(),ws:null},w=(I)=>{C.forEach((S)=>S(I))},Z=(I)=>{C.forEach((S)=>S({message:I,recoverable:!1,type:"error"})),w({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"})},Y=()=>{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 S of g.values())clearTimeout(S.timeout),S.reject(Error(I));g.clear()},K=()=>{if(L)return;let I=O;T.reconnectAttempts+=1;let S=U(T.reconnectAttempts),y=Date.now()+S;w({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>_){Z("Voice connection could not be restored.");return}try{if(A.prepareReconnect){let h=new AbortController;W=h;let B=setTimeout(()=>h.abort("Voice reconnect preparation timed out"),A.prepareReconnectTimeoutMs??1e4);try{await Promise.race([A.prepareReconnect({attempt:T.reconnectAttempts,path:V,scenarioId:T.scenarioId,sessionId:T.sessionId,signal:h.signal}),new Promise((M,O0)=>{h.signal.addEventListener("abort",()=>O0(Error("Voice reconnect preparation timed out.")),{once:!0})})])}finally{if(clearTimeout(B),W===h)W=null}}}catch(h){if(L||O!==I)return;if(h instanceof s){Z(h.message);return}if(T.reconnectAttempts<_)K();else Z("Voice authorization could not be renewed for reconnect.");return}if(L||O!==I)return;q()},S)},q=()=>{if(L)return;let I=new WebSocket(v0(V,T.sessionId,T.scenarioId,A.query??{}));I.binaryType="arraybuffer",I.onopen=()=>{if(T.ws!==I){I.close(1000);return}let S=T.reconnectAttempts>0;if(T.isConnected=!0,Q(),S)w({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},R);T.pingInterval=setInterval(()=>{if(I.readyState===1)I.send(JSON.stringify({type:"ping"}))},X)},I.onmessage=(S)=>{if(T.ws!==I)return;let y=o0(S);if(!y)return;if(y.type==="session")T.sessionId=y.sessionId,T.scenarioId=y.scenarioId??T.scenarioId;if(y.type==="call_control_ack"){let h=g.get(y.requestId);if(h)if(clearTimeout(h.timeout),g.delete(y.requestId),y.ok)h.resolve();else h.reject(Error(y.message??`${y.action} was not applied`))}C.forEach((h)=>h(y))},I.onclose=(S)=>{if(T.ws!==I)return;T.ws=null,T.isConnected=!1,Y(),G("Voice connection interrupted");let y=H&&S.code!==1000&&S.code!==4401&&S.code!==4403&&S.code!==4500&&T.reconnectAttempts<_,h=S.code===4401||S.code===4403,B=S.code===4500;if(h)C.forEach((M)=>M({message:S.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)K();else if(H&&S.code!==1000){if(h)w({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"});else if(!B)Z("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)},J=(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;J({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},I0=(I)=>{$(I)},U0=()=>{J({type:"end_turn"})},L0=(I)=>{if(T.ws?.readyState!==1)return Promise.reject(Error(`${I.action} is unavailable while disconnected`));let S=crypto.randomUUID(),{promise:y,reject:h,resolve:B}=Promise.withResolvers(),M=setTimeout(()=>{g.delete(S),h(Error(`${I.action} acknowledgement timed out`))},5000);return g.set(S,{reject:h,resolve:B,timeout:M}),T.ws.send(JSON.stringify({...I,requestId:S,type:"call_control"})),y},W0=(I="client-close")=>{if(L=!0,W?.abort(),W=null,O+=1,Y(),G("Voice connection closed"),T.pendingMessages.length=0,T.ws){let S=T.ws;if(T.ws=null,S.readyState===1)S.send(JSON.stringify({reason:I,type:"close"}));S.close(1000)}T.isConnected=!1,C.clear()},D0=()=>{if(L=!0,W?.abort(),W=null,O+=1,Y(),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:J,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,A)=>{let C=A.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,A)=>{let C=A.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,paused:!1,partial:"",reconnect:n0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),p=()=>{let V=r0(),A="",C=new Set,g=()=>{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":A=m0(A,_.transcript.text),V={...V,partial:A};break;case"partial":V={...V,partial:u0(A,_.transcript.text)};break;case"replay":A=_.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,paused:_.paused??!1,pauseExpiresAt:_.pauseExpiresAt,scenarioId:_.scenarioId??V.scenarioId,isConnected:_.status==="active",sessionId:_.sessionId,sessionMetadata:_.sessionMetadata??V.sessionMetadata,status:_.status};break;case"turn":A="",V={...V,partial:"",turns:[...V.turns,_.turn]};break}g()},getServerSnapshot:()=>V,getSnapshot:()=>V,subscribe:(_)=>{return C.add(_),()=>{C.delete(_)}}}};var t=(V,A={})=>{let C=a(V,A),g=p(),H=A.browserMedia&&typeof window<"u"?r({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,_=new Set,D=!1,X=(W)=>Promise.resolve().then(()=>{if(D)return;if(!W?.sessionId&&!W?.scenarioId)return;C.start(W),H?.start()}),R=()=>{_.forEach((W)=>W())},O=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let W=g.getSnapshot(),U=JSON.stringify({at:Date.now(),reconnect:W.reconnect,scenarioId:W.scenarioId,sessionId:C.getSessionId(),turnIds:W.turns.map((T)=>T.id)});fetch(A.reconnectReportPath,{body:U,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},L=C.subscribe((W)=>{let U=i(W);if(U){if(g.dispatch(U),W.type==="connection")O();R()}});return{start:X,get assistantAudio(){return g.getSnapshot().assistantAudio},get assistantTexts(){return g.getSnapshot().assistantTexts},get assistantStreamingText(){return g.getSnapshot().assistantStreamingText},get call(){return g.getSnapshot().call},callControl(W){return C.callControl(W)},close(W){D=!0,L(),H?.close(),C.close(W),g.dispatch({type:"disconnected"}),R()},disconnect(){D=!0,L(),H?.close(),C.disconnect(),g.dispatch({type:"disconnected"}),R()},endTurn(){C.endTurn()},get error(){return g.getSnapshot().error},getServerSnapshot(){return g.getServerSnapshot()},getSnapshot(){return g.getSnapshot()},get isConnected(){return g.getSnapshot().isConnected},get partial(){return g.getSnapshot().partial},get paused(){return g.getSnapshot().paused},get pauseExpiresAt(){return g.getSnapshot().pauseExpiresAt},get reconnect(){return g.getSnapshot().reconnect},get scenarioId(){return g.getSnapshot().scenarioId},sendAudio(W){C.sendAudio(W)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return g.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return g.getSnapshot().status},subscribe(W){return _.add(W),()=>{_.delete(W)}},get turns(){return g.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 A=V?.profile??"fast",C=V?.qualityProfile??"general",g=s0[A],H=a0[C],_=V?.silenceMs??H.silenceMs??g.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(_,V?.minSilenceMs??H.minSilenceMs??g.minSilenceMs),silenceMs:_,speechThreshold:V?.speechThreshold??H.speechThreshold??g.speechThreshold,transcriptStabilityMs:V?.transcriptStabilityMs??H.transcriptStabilityMs??g.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"}}},A0=(V="default")=>{let A=p0[V];return{audioConditioning:e(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:V,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:V0(A.turnDetection)}};var t0=(V)=>({assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,isRecording:!1,paused:V.paused,pauseExpiresAt:V.pauseExpiresAt,partial:V.partial,reconnect:V.reconnect,recordingError:null,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]}),g0=(V,A={})=>{let C=A0(A.preset),g=t(V,{...C.connection,...A.connection}),H=null,_=0,D=!1,X=null,R=t0(g),O=new Set,L=()=>{for(let G of O)G()},W=()=>{if(R={...R,assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,paused:g.paused,pauseExpiresAt:g.pauseExpiresAt,partial:g.partial,reconnect:g.reconnect,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]},A.autoStopOnComplete!==!1&&R.status==="completed"&&R.isRecording)H?.stop(),H=null,R={...R,isRecording:!1};L()},U=g.subscribe(W);W();let T=()=>{if(H)return H;return H=v({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(G)=>{if(A.capture?.onAudio){A.capture.onAudio(G,g.sendAudio);return}g.sendAudio(G)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),H},w=()=>{_+=1,H?.stop(),H=null,R={...R,isRecording:!1},L()},Z=async()=>{if(D)throw Error("Voice controller is closed.");if(R.isRecording)return;if(X)return X;let G=++_,K=T(),$=(async()=>{try{if(R={...R,recordingError:null},L(),await K.start(),D||G!==_||H!==K){K.stop();return}R={...R,isRecording:!0},L()}catch(J){if(K.stop(),G!==_||H!==K)throw J;throw H=null,R={...R,isRecording:!1,recordingError:J instanceof Error?J.message:String(J)},L(),J}})().finally(()=>{if(X===$)X=null});return X=$,X};return{close:(G)=>{D=!0,U(),w(),g.close(G)},disconnect:()=>{D=!0,U(),w(),g.disconnect()},startRecording:Z,stopRecording:w,get assistantAudio(){return R.assistantAudio},get assistantTexts(){return R.assistantTexts},get assistantStreamingText(){return R.assistantStreamingText},bindHTMX(G){return d(g,G)},get call(){return R.call},callControl:(G)=>g.callControl(G),endTurn:()=>g.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 paused(){return R.paused},get pauseExpiresAt(){return R.pauseExpiresAt},get reconnect(){return R.reconnect},get recordingError(){return R.recordingError},get scenarioId(){return R.scenarioId},sendAudio:(G)=>g.sendAudio(G),get sessionId(){return R.sessionId},get sessionMetadata(){return R.sessionMetadata},simulateDisconnect:()=>g.simulateDisconnect(),get status(){return R.status},subscribe:(G)=>{return O.add(G),()=>{O.delete(G)}},toggleRecording:async()=>{if(R.isRecording){w();return}await Z()},get turns(){return R.turns}}};var z=(V)=>String(V).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");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"},AV=(V,A)=>{switch(V){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},_0=(V)=>{let A={...e0,...V.theme},C={...VV,...V.labels},g=V.state.assistantAudio.at(-1)?.receivedAt,H=V.state.turns.at(-1)?.committedAt,_=C0({hasActivePartial:V.state.partial.length>0,isConnected:V.state.isConnected,isPlaying:!1,isRecording:V.state.isRecording,lastAssistantAt:g,lastTranscriptAt:H}),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:AV(_,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:A,title:V.title??"Voice"}},gV=(V)=>typeof V==="number"?`${V}px`:V,T0=(V)=>{let A=V.theme,C=`background:${A.background};border-radius:${gV(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,g=`background:${V.errorMessage?A.errorAccent:V.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,H=[];if(V.controls.canStart)H.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;">${z(V.labels.startCall)}</button>`);if(V.controls.canMute)H.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;">${z(V.labels.mute)}</button>`);if(V.controls.canEnd)H.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;">${z(V.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${V.agentState}" class="${z(V.classes.container)}" style="${C}">
1
+ (()=>{var{defineProperty:k,getOwnPropertyNames:S0,getOwnPropertyDescriptor:O0}=Object,w0=Object.prototype.hasOwnProperty;function h0(V){return this[V]}var J0=(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 S0(V))if(!w0.call(g,A))k(g,A,{get:h0.bind(V,A),enumerable:!(C=O0(V,A))||C.enumerable})}return l.set(V,g),g},l;var Y0=(V)=>V;function Z0(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:Z0.bind(g,C)})};var _V={};Q0(_V,{mount:()=>T0,default:()=>RV,VOICE_EMBED_VERSION:()=>$0});var E0=(V)=>{if(typeof V!=="string")return V;return document.querySelector(V)},c0=(V,g,C,A)=>{let H=g??V.getAttribute("hx-get")??"";if(!H)return"";let _=new URL(H,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=E0(g.element);if(!C)return()=>{};let A=g.eventName??"voice-refresh",H=g.sessionQueryParam??"sessionId",_=()=>{let D=window,$=c0(C,g.route,H,V.sessionId);if($)C.setAttribute("hx-get",$);D.htmx?.process?.(C),D.htmx?.trigger?.(C,A)},y=V.subscribe(_);return _(),()=>{y()}};var K0=(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=K0(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 H of C){let _=H/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,H=Math.round(V.length/A),_=new Float32Array(H),y=0,D=0;while(y<_.length){let $=Math.round((y+1)*A),G=0,L=0;for(let W=D;W<$&&W<V.length;W+=1)G+=V[W]??0,L+=1;_[y]=L>0?G/L:0,y+=1,D=$}return _},v=(V)=>{let g=null,C=null,A=null,H=null,_=0,y=null,D=(L)=>{L.processorNode?.disconnect(),L.sourceNode?.disconnect(),L.mediaStream?.getTracks().forEach((W)=>W.stop()),L.audioContext?.close()};return{start:()=>{if(H)return Promise.resolve();if(y)return y;let L=++_,U=(async()=>{if(!V.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let R=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!R)throw Error("Browser microphone capture requires AudioContext support.");let O=null,Y=null,J=null,Q=null;try{if(Y=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(O=new R,O.state==="suspended")await O.resume();if(L!==_)throw Error("Microphone capture stopped during startup.");if(Q=O.createMediaStreamSource(Y),J=O.createScriptProcessor(4096,1,1),J.onaudioprocess=(X)=>{let c=X.inputBuffer.getChannelData(0),q=z0(c,O?.sampleRate??48000,V.sampleRateHz??16000),T=q0(q);V.onLevel?.(N0(T)),V.onAudio(T)},Q.connect(J),J.connect(O.destination),L!==_)throw Error("Microphone capture stopped during startup.");g=O,H=Y,A=J,C=Q}catch(X){throw D({audioContext:O,mediaStream:Y,processorNode:J,sourceNode:Q}),X}})().finally(()=>{if(y===U)y=null});return y=U,U},stop:()=>{_+=1,D({audioContext:g,mediaStream:H,processorNode:A,sourceNode:C}),V.onLevel?.(0),g=null,H=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"playback_rate":return{rate:V.rate,type:"playback_rate"};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{paused:V.paused,pauseExpiresAt:V.pauseExpiresAt,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 b0=(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 E=(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},K=(V,g)=>{let C=V[g];return typeof C==="string"?C:void 0},f=(V)=>String(V.id??K(V,"ssrc")??E(V,"ssrc")??K(V,"trackIdentifier")??K(V,"mid")??"unknown"),o=(V)=>V===void 0?void 0:V*1000;var P0=(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((T)=>T.type==="inbound-rtp"&&K(T,"kind")!=="video"),H=g.filter((T)=>T.type==="outbound-rtp"&&K(T,"kind")!=="video"),_=g.filter((T)=>T.type==="candidate-pair"),y=g.filter((T)=>(T.type==="track"||T.type==="media-source")&&K(T,"kind")==="audio"),D=_.filter((T)=>j(T,"selected")===!0||j(T,"nominated")===!0||K(T,"state")==="succeeded").length,$=y.filter((T)=>K(T,"readyState")!=="ended"&&K(T,"trackState")!=="ended"&&j(T,"ended")!==!0).length,G=y.filter((T)=>K(T,"readyState")==="ended"||K(T,"trackState")==="ended"||j(T,"ended")===!0).length,L=A.reduce((T,w)=>T+(E(w,"packetsReceived")??0),0),W=H.reduce((T,w)=>T+(E(w,"packetsSent")??0),0),U=[...A,...H].reduce((T,w)=>T+Math.max(0,E(w,"packetsLost")??0),0),R=L+U,O=R===0?0:U/R,Y=A.reduce((T,w)=>T+(E(w,"bytesReceived")??0),0),J=H.reduce((T,w)=>T+(E(w,"bytesSent")??0),0),Q=x(_.map((T)=>o(E(T,"currentRoundTripTime")??E(T,"roundTripTime"))).filter((T)=>T!==void 0)),X=x([...A,...H].map((T)=>o(E(T,"jitter"))).filter((T)=>T!==void 0)),c=x(A.map((T)=>{let w=E(T,"jitterBufferDelay"),M=E(T,"jitterBufferEmittedCount");return w!==void 0&&M!==void 0&&M>0?w/M*1000:void 0}).filter((T)=>T!==void 0)),q=y.map((T)=>E(T,"audioLevel")).filter((T)=>T!==void 0);if(V.requireConnectedCandidatePair&&_.length>0&&D===0)N(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(V.requireLiveAudioTrack&&$===0)N(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(V.maxPacketLossRatio!==void 0&&O>V.maxPacketLossRatio)N(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(O)} 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&&X!==void 0&&X>V.maxJitterMs)N(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(X)}ms above ${String(V.maxJitterMs)}ms.`);return{activeCandidatePairs:D,audioLevelAverage:b0(q),bytesReceived:Y,bytesSent:J,checkedAt:Date.now(),endedAudioTracks:G,inboundPackets:L,issues:C,jitterBufferDelayMs:c,jitterMs:X,liveAudioTracks:$,outboundPackets:W,packetLossRatio:O,packetsLost:U,roundTripTimeMs:Q,status:C.some((T)=>T.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:g.length}},m=async(V)=>{return[...(await V.peerConnection.getStats(V.selector??null)).values()].map(P0)};var u=(V={})=>{let g=V.stats??[],C=V.previousStats??[],A=[],H=new Map(C.map((U)=>[f(U),U])),y=g.filter((U)=>(U.type==="inbound-rtp"||U.type==="outbound-rtp")&&K(U,"kind")!=="video"&&K(U,"mediaType")!=="video").map((U)=>{let R=U.type==="outbound-rtp"?"outbound":"inbound",O=R==="outbound"?"packetsSent":"packetsReceived",Y=R==="outbound"?"bytesSent":"bytesReceived",J=H.get(f(U)),Q=E(U,O),X=J?E(J,O):void 0,c=E(U,Y),q=J?E(J,Y):void 0,T=U.timestamp!==void 0&&J?.timestamp!==void 0?U.timestamp-J.timestamp:void 0;return{bytesDelta:c!==void 0&&q!==void 0?c-q:void 0,currentPackets:Q,direction:R,id:f(U),packetDelta:Q!==void 0&&X!==void 0?Q-X:void 0,previousPackets:X,timeDeltaMs:T}}),D=y.filter((U)=>U.direction==="inbound"),$=y.filter((U)=>U.direction==="outbound"),G=x(y.map((U)=>U.timeDeltaMs).filter((U)=>U!==void 0)),L=D.filter((U)=>V.maxInboundPacketStallMs!==void 0&&U.timeDeltaMs!==void 0&&U.timeDeltaMs>=V.maxInboundPacketStallMs&&U.packetDelta!==void 0&&U.packetDelta<=0).length,W=$.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&&D.length===0)N(A,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(V.requireOutboundAudio&&$.length===0)N(A,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(V.maxGapMs!==void 0&&G!==void 0&&G>V.maxGapMs)N(A,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(G)}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:D.length,issues:A,maxObservedGapMs:G,outboundAudioStreams:$.length,stalledInboundStreams:L,stalledOutboundStreams:W,status:A.some((U)=>U.severity==="error")?"fail":A.length>0?"warn":"pass",streams:y,totalStats:g.length}};var B0="/api/voice/browser-media",M0=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??B0,{body:JSON.stringify(V),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},r=(V)=>{let g=null,C=[],A=async()=>{let y=await j0(V);if(!y)return;let D=await m({peerConnection:y}),$=n({...V,stats:D}),G=V.continuity===!1?void 0:u({...V.continuity,previousStats:C,stats:D}),L={at:Date.now(),continuity:G,report:$,scenarioId:V.getScenarioId?.()??null,sessionId:V.getSessionId?.()??null};return C=D,V.onReport?.(L),await x0(L,V),L},H=()=>{A().catch((y)=>{V.onError?.(y)})},_=()=>{if(g)clearInterval(g),g=null};return{close:_,reportOnce:A,stop:_,start:()=>{if(g)return;H(),g=setInterval(H,V.intervalMs??M0)}}};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 b=()=>{},F0=()=>Promise.resolve(),f0=()=>b,l0={callControl:F0,close:b,disconnect:b,endTurn:b,send:b,sendAudio:b,simulateDisconnect:b,subscribe:f0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},d0=()=>crypto.randomUUID(),v0=(V,g,C,A)=>{let{hostname:H,port:_,protocol:y}=window.location,D=y==="https:"?"wss:":"ws:",$=_?`:${_}`:"",G=new URL(`${D}//${H}${$}${V}`);if(G.searchParams.set("sessionId",g),C)G.searchParams.set("scenarioId",C);for(let[L,W]of Object.entries(A))if(L!=="sessionId"&&L!=="scenarioId"&&W)G.searchParams.set(L,W);return G.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"playback_rate":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,H=g.reconnect!==!1,_=g.maxReconnectAttempts??15,y=g.reconnectMaxDelayMs??8000,D=g.pingInterval??30000,$=g.reconnectResetAfterMs??30000,G=0,L=!1,W=null,U=(I)=>k0(I,500,y),R={isConnected:!1,pendingMessages:[],scenarioId:g.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:g.sessionId??d0(),ws:null},O=(I)=>{C.forEach((S)=>S(I))},Y=(I)=>{C.forEach((S)=>S({message:I,recoverable:!1,type:"error"})),O({reconnect:{attempts:R.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"})},J=()=>{if(R.pingInterval)clearInterval(R.pingInterval),R.pingInterval=null;if(R.reconnectTimeout)clearTimeout(R.reconnectTimeout),R.reconnectTimeout=null;if(R.reconnectResetTimeout)clearTimeout(R.reconnectResetTimeout),R.reconnectResetTimeout=null},Q=()=>{if(R.ws?.readyState!==1)return;while(R.pendingMessages.length>0){let I=R.pendingMessages.shift();if(I!==void 0)R.ws.send(I)}},X=(I)=>{for(let S of A.values())clearTimeout(S.timeout),S.reject(Error(I));A.clear()},c=()=>{if(L)return;let I=G;R.reconnectAttempts+=1;let S=U(R.reconnectAttempts),Z=Date.now()+S;O({reconnect:{attempts:R.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:Z,status:"reconnecting"},type:"connection"}),R.reconnectTimeout=setTimeout(async()=>{if(L||G!==I)return;if(R.reconnectAttempts>_){Y("Voice connection could not be restored.");return}try{if(g.prepareReconnect){let h=new AbortController;W=h;let P=setTimeout(()=>h.abort("Voice reconnect preparation timed out"),g.prepareReconnectTimeoutMs??1e4);try{await Promise.race([g.prepareReconnect({attempt:R.reconnectAttempts,path:V,scenarioId:R.scenarioId,sessionId:R.sessionId,signal:h.signal}),new Promise((B,G0)=>{h.signal.addEventListener("abort",()=>G0(Error("Voice reconnect preparation timed out.")),{once:!0})})])}finally{if(clearTimeout(P),W===h)W=null}}}catch(h){if(L||G!==I)return;if(h instanceof s){Y(h.message);return}if(R.reconnectAttempts<_)c();else Y("Voice authorization could not be renewed for reconnect.");return}if(L||G!==I)return;q()},S)},q=()=>{if(L)return;let I=new WebSocket(v0(V,R.sessionId,R.scenarioId,g.query??{}));I.binaryType="arraybuffer",I.onopen=()=>{if(R.ws!==I){I.close(1000);return}let S=R.reconnectAttempts>0;if(R.isConnected=!0,Q(),S)O({reconnect:{attempts:R.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),R.reconnectResetTimeout=setTimeout(()=>{if(R.ws===I&&I.readyState===1)R.reconnectAttempts=0;R.reconnectResetTimeout=null},$);R.pingInterval=setInterval(()=>{if(I.readyState===1)I.send(JSON.stringify({type:"ping"}))},D)},I.onmessage=(S)=>{if(R.ws!==I)return;let Z=o0(S);if(!Z)return;if(Z.type==="session")R.sessionId=Z.sessionId,R.scenarioId=Z.scenarioId??R.scenarioId;if(Z.type==="call_control_ack"){let h=A.get(Z.requestId);if(h)if(clearTimeout(h.timeout),A.delete(Z.requestId),Z.ok)h.resolve();else h.reject(Error(Z.message??`${Z.action} was not applied`))}C.forEach((h)=>h(Z))},I.onclose=(S)=>{if(R.ws!==I)return;R.ws=null,R.isConnected=!1,J(),X("Voice connection interrupted");let Z=H&&S.code!==1000&&S.code!==4401&&S.code!==4403&&S.code!==4500&&R.reconnectAttempts<_,h=S.code===4401||S.code===4403,P=S.code===4500;if(h)C.forEach((B)=>B({message:S.code===4401?"Voice authorization expired or was rejected.":"Voice authorization was forbidden.",recoverable:!1,type:"error"}));if(P)C.forEach((B)=>B({message:"Voice provider initialization failed.",recoverable:!1,type:"error"}));if(Z)c();else if(H&&S.code!==1000){if(h)O({reconnect:{attempts:R.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"});else if(!P)Y("Voice connection could not be restored.")}},R.ws=I},T=(I)=>{if(L)return;if(R.ws?.readyState===1){R.ws.send(I);return}if(typeof I!=="string")return;if(R.pendingMessages.length>=100)R.pendingMessages.shift();R.pendingMessages.push(I)},w=(I)=>{T(JSON.stringify(I))},M=(I={})=>{if(I.sessionId&&I.sessionId!==R.sessionId)throw Error("Voice session switching requires a new connection instance.");if(I.sessionId)R.sessionId=I.sessionId;if(I.scenarioId)R.scenarioId=I.scenarioId;w({scenarioId:R.scenarioId??void 0,sessionId:R.sessionId,type:"start"})},I0=(I)=>{T(I)},U0=()=>{w({type:"end_turn"})},L0=(I)=>{if(R.ws?.readyState!==1)return Promise.reject(Error(`${I.action} is unavailable while disconnected`));let S=crypto.randomUUID(),{promise:Z,reject:h,resolve:P}=Promise.withResolvers(),B=setTimeout(()=>{A.delete(S),h(Error(`${I.action} acknowledgement timed out`))},5000);return A.set(S,{reject:h,resolve:P,timeout:B}),R.ws.send(JSON.stringify({...I,requestId:S,type:"call_control"})),Z},W0=(I="client-close")=>{if(L=!0,W?.abort(),W=null,G+=1,J(),X("Voice connection closed"),R.pendingMessages.length=0,R.ws){let S=R.ws;if(R.ws=null,S.readyState===1)S.send(JSON.stringify({reason:I,type:"close"}));S.close(1000)}R.isConnected=!1,C.clear()},y0=()=>{if(L=!0,W?.abort(),W=null,G+=1,J(),X("Voice connection disconnected"),R.pendingMessages.length=0,R.ws){let I=R.ws;R.ws=null,I.close(1000)}R.isConnected=!1,C.clear()},D0=()=>{if(R.ws?.readyState===1)R.ws.close(4000,"absolutejs-voice-reconnect-proof")},X0=(I)=>{return C.add(I),()=>{C.delete(I)}};return q(),{callControl:L0,close:W0,disconnect:y0,endTurn:U0,send:w,sendAudio:I0,simulateDisconnect:D0,start:M,subscribe:X0,getReadyState:()=>R.ws?.readyState??3,getScenarioId:()=>R.scenarioId??"",getSessionId:()=>R.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,paused:!1,partial:"",playbackRate:null,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"playback_rate":V={...V,playbackRate:Math.max(0.5,Math.min(2,_.rate))};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,paused:_.paused??!1,pauseExpiresAt:_.pauseExpiresAt,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(),H=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,y=!1,D=(W)=>Promise.resolve().then(()=>{if(y)return;if(!W?.sessionId&&!W?.scenarioId)return;C.start(W),H?.start()}),$=()=>{_.forEach((W)=>W())},G=()=>{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((R)=>R.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")G();$()}});return{start:D,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){y=!0,L(),H?.close(),C.close(W),A.dispatch({type:"disconnected"}),$()},disconnect(){y=!0,L(),H?.close(),C.disconnect(),A.dispatch({type:"disconnected"}),$()},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 playbackRate(){return A.getSnapshot().playbackRate},get paused(){return A.getSnapshot().paused},get pauseExpiresAt(){return A.getSnapshot().pauseExpiresAt},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],H=a0[C],_=V?.silenceMs??H.silenceMs??A.silenceMs;return{profile:g,qualityProfile:C,minSilenceMs:Math.min(_,V?.minSilenceMs??H.minSilenceMs??A.minSilenceMs),silenceMs:_,speechThreshold:V?.speechThreshold??H.speechThreshold??A.speechThreshold,transcriptStabilityMs:V?.transcriptStabilityMs??H.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,paused:V.paused,pauseExpiresAt:V.pauseExpiresAt,partial:V.partial,playbackRate:V.playbackRate,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}),H=null,_=0,y=!1,D=null,$=t0(A),G=new Set,L=()=>{for(let X of G)X()},W=()=>{if($={...$,assistantAudio:[...A.assistantAudio],assistantStreamingText:A.assistantStreamingText,assistantTexts:[...A.assistantTexts],call:A.call,error:A.error,isConnected:A.isConnected,paused:A.paused,pauseExpiresAt:A.pauseExpiresAt,partial:A.partial,playbackRate:A.playbackRate,reconnect:A.reconnect,sessionId:A.sessionId,sessionMetadata:A.sessionMetadata,scenarioId:A.scenarioId,status:A.status,turns:[...A.turns]},g.autoStopOnComplete!==!1&&$.status==="completed"&&$.isRecording)H?.stop(),H=null,$={...$,isRecording:!1};L()},U=A.subscribe(W);W();let R=()=>{if(H)return H;return H=v({channelCount:g.capture?.channelCount??C.capture.channelCount,onLevel:g.capture?.onLevel,onAudio:(X)=>{if(g.capture?.onAudio){g.capture.onAudio(X,A.sendAudio);return}A.sendAudio(X)},sampleRateHz:g.capture?.sampleRateHz??C.capture.sampleRateHz,...g.capture?.stream?{stream:g.capture.stream}:{}}),H},O=()=>{_+=1,H?.stop(),H=null,$={...$,isRecording:!1},L()},Y=async()=>{if(y)throw Error("Voice controller is closed.");if($.isRecording)return;if(D)return D;let X=++_,c=R(),T=(async()=>{try{if($={...$,recordingError:null},L(),await c.start(),y||X!==_||H!==c){c.stop();return}$={...$,isRecording:!0},L()}catch(w){if(c.stop(),X!==_||H!==c)throw w;throw H=null,$={...$,isRecording:!1,recordingError:w instanceof Error?w.message:String(w)},L(),w}})().finally(()=>{if(D===T)D=null});return D=T,D};return{close:(X)=>{y=!0,U(),O(),A.close(X)},disconnect:()=>{y=!0,U(),O(),A.disconnect()},startRecording:Y,stopRecording:O,get assistantAudio(){return $.assistantAudio},get assistantTexts(){return $.assistantTexts},get assistantStreamingText(){return $.assistantStreamingText},bindHTMX(X){return d(A,X)},get call(){return $.call},callControl:(X)=>A.callControl(X),endTurn:()=>A.endTurn(),get error(){return $.error},getServerSnapshot:()=>$,getSnapshot:()=>$,get isConnected(){return $.isConnected},get isRecording(){return $.isRecording},get partial(){return $.partial},get playbackRate(){return $.playbackRate},get paused(){return $.paused},get pauseExpiresAt(){return $.pauseExpiresAt},get reconnect(){return $.reconnect},get recordingError(){return $.recordingError},get scenarioId(){return $.scenarioId},sendAudio:(X)=>A.sendAudio(X),get sessionId(){return $.sessionId},get sessionMetadata(){return $.sessionMetadata},simulateDisconnect:()=>A.simulateDisconnect(),get status(){return $.status},subscribe:(X)=>{return G.add(X),()=>{G.delete(X)}},toggleRecording:async()=>{if($.isRecording){O();return}await Y()},get turns(){return $.turns}}};var z=(V)=>String(V).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;");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,H=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:H}),y=!V.state.isConnected&&V.state.status!=="idle"&&!V.state.error,D=V.state.error?"Error":y?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:D,theme:g,title:V.title??"Voice"}},AV=(V)=>typeof V==="number"?`${V}px`:V,R0=(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;`,H=[];if(V.controls.canStart)H.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)H.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)H.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="${z(V.classes.dot)}" style="${g}"></span>
3
+ <span aria-hidden="true" class="${z(V.classes.dot)}" style="${A}"></span>
4
4
  <strong style="font-size:15px;">${z(V.title)}</strong>
5
5
  <span style="font-size:13px;margin-left:auto;opacity:0.7;">${z(V.statusLabel)}</span>
6
6
  </div>
7
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;">${H.join("")}</div>
9
- ${V.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${z(V.errorMessage)}</p>`:""}
10
- </div>`};var CV=(V)=>{if(typeof V!=="string")return V;let A=document.querySelector(V);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${V}"`);return A},$0=(V,A={})=>{let C=CV(V),g=g0(A.path??"/voice",A.controllerOptions),H=null,_=null,D=()=>{let R=_0({...A.labels!==void 0?{labels:A.labels}:{},state:{assistantAudio:g.assistantAudio,error:g.error,isConnected:g.isConnected,isRecording:g.isRecording,partial:g.partial,status:g.status,turns:g.turns},...A.theme!==void 0?{theme:A.theme}:{},...A.title!==void 0?{title:A.title}:{}});C.innerHTML=T0(R);for(let O of C.querySelectorAll("button[data-action]")){let{action:L}=O.dataset;O.addEventListener("click",()=>{if(L==="start")g.startRecording();else if(L==="mute")g.stopRecording();else if(L==="end")g.close()})}if(g.error&&g.error!==H)H=g.error,A.onError?.(g.error);if(g.status!==_)_=g.status,A.onStatusChange?.(g.status)},X=g.subscribe(D);if(D(),A.autoStart)g.startRecording();return{controller:g,async end(){await g.close()},mute(){g.stopRecording()},async start(){await g.startRecording()},unmount(){X(),g.close(),C.innerHTML=""}}},R0="0.0.22-beta.516",H0={mount:$0,version:R0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=H0;var TV=H0;})();
9
+ ${V.errorMessage?`<p style="color:${g.errorAccent};font-size:12px;margin-top:12px;">${z(V.errorMessage)}</p>`:""}
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},T0=(V,g={})=>{let C=CV(V),A=A0(g.path??"/voice",g.controllerOptions),H=null,_=null,y=()=>{let $=_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=R0($);for(let G of C.querySelectorAll("button[data-action]")){let{action:L}=G.dataset;G.addEventListener("click",()=>{if(L==="start")A.startRecording();else if(L==="mute")A.stopRecording();else if(L==="end")A.close()})}if(A.error&&A.error!==H)H=A.error,g.onError?.(A.error);if(A.status!==_)_=A.status,g.onStatusChange?.(A.status)},D=A.subscribe(y);if(y(),g.autoStart)A.startRecording();return{controller:A,async end(){await A.close()},mute(){A.stopRecording()},async start(){await A.startRecording()},unmount(){D(),A.close(),C.innerHTML=""}}},$0="0.0.22-beta.516",H0={mount:T0,version:$0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=H0;var RV=H0;})();
package/dist/index.js CHANGED
@@ -4298,6 +4298,11 @@ var createVoiceSession = (options) => {
4298
4298
  let lastSpeechEnergyAt = 0;
4299
4299
  let sttHealthPhaseStart = 0;
4300
4300
  let sttDeafConfirmedAt = 0;
4301
+ const resetSttHealthWindow = () => {
4302
+ lastSpeechEnergyAt = 0;
4303
+ sttHealthPhaseStart = 0;
4304
+ sttDeafConfirmedAt = 0;
4305
+ };
4301
4306
  let lastSttRecoverySpokenAt = 0;
4302
4307
  let activeTTSTurnId;
4303
4308
  let assistantSpeechEndsAt = 0;
@@ -6729,6 +6734,7 @@ var createVoiceSession = (options) => {
6729
6734
  markTurnCommitted(currentSession, finalText, transcripts);
6730
6735
  });
6731
6736
  speechDetected = false;
6737
+ resetSttHealthWindow();
6732
6738
  rewindFallbackTurnAudio();
6733
6739
  logger.info("voice turn committed", {
6734
6740
  reason,
@@ -7056,30 +7062,35 @@ var createVoiceSession = (options) => {
7056
7062
  clearSilenceTimer();
7057
7063
  }
7058
7064
  const nowMs = Date.now();
7059
- if (nowMs - lastSpeechEnergyAt > STT_HEALTH_SPEECH_GAP_MS) {
7060
- sttHealthPhaseStart = nowMs;
7061
- }
7062
- lastSpeechEnergyAt = nowMs;
7063
- const lastTranscriptAt = latest.currentTurn.lastTranscriptAt ?? 0;
7064
- if (!options.realtime && sttSession && lastTranscriptAt < sttHealthPhaseStart && nowMs - sttHealthPhaseStart >= STT_HEALTH_STALE_MS) {
7065
- sttDeafConfirmedAt = nowMs;
7066
- sttReconnectCount = nowMs - lastSttReconnectAt < STT_RECONNECT_FLAP_WINDOW_MS ? sttReconnectCount + 1 : 1;
7067
- lastSttReconnectAt = nowMs;
7068
- sttHealthPhaseStart = nowMs;
7069
- maybeSpeakSttRecovery(nowMs, latest);
7070
- if (sttReconnectCount <= MAX_STT_RECONNECTS_IN_FLAP_WINDOW) {
7071
- await appendTrace({
7072
- payload: {
7073
- action: "stt-health-reconnect",
7074
- attempt: sttReconnectCount,
7075
- classification: "stt-deaf",
7076
- reason: `no transcript for ${STT_HEALTH_STALE_MS}ms of continuous speech`
7077
- },
7078
- session: latest,
7079
- type: "session.error"
7080
- });
7081
- await closeAdapter("stt stale; health-reconnect");
7082
- return;
7065
+ if (assistantTurnInProgress) {
7066
+ resetSttHealthWindow();
7067
+ } else {
7068
+ if (nowMs - lastSpeechEnergyAt > STT_HEALTH_SPEECH_GAP_MS) {
7069
+ sttHealthPhaseStart = nowMs;
7070
+ }
7071
+ lastSpeechEnergyAt = nowMs;
7072
+ const lastTranscriptAt = latest.currentTurn.lastTranscriptAt ?? 0;
7073
+ if (!options.realtime && sttSession && lastTranscriptAt < sttHealthPhaseStart && nowMs - sttHealthPhaseStart >= STT_HEALTH_STALE_MS) {
7074
+ sttDeafConfirmedAt = nowMs;
7075
+ sttReconnectCount = nowMs - lastSttReconnectAt < STT_RECONNECT_FLAP_WINDOW_MS ? sttReconnectCount + 1 : 1;
7076
+ lastSttReconnectAt = nowMs;
7077
+ sttHealthPhaseStart = nowMs;
7078
+ maybeSpeakSttRecovery(nowMs, latest);
7079
+ if (sttReconnectCount <= MAX_STT_RECONNECTS_IN_FLAP_WINDOW) {
7080
+ await appendTrace({
7081
+ payload: {
7082
+ action: "stt-health-reconnect",
7083
+ attempt: sttReconnectCount,
7084
+ classification: "stt-deaf",
7085
+ recoverable: true,
7086
+ reason: `no transcript for ${STT_HEALTH_STALE_MS}ms of continuous speech`
7087
+ },
7088
+ session: latest,
7089
+ type: "session.error"
7090
+ });
7091
+ await closeAdapter("stt stale; health-reconnect");
7092
+ return;
7093
+ }
7083
7094
  }
7084
7095
  }
7085
7096
  } else if (speechDetected) {
@@ -7209,6 +7220,11 @@ var createVoiceSession = (options) => {
7209
7220
  speechThreshold: turnDetection.speechThreshold,
7210
7221
  transcriptStabilityMs: turnDetection.transcriptStabilityMs
7211
7222
  };
7223
+ }),
7224
+ setPlaybackRate: async (rate) => runSerial("api.setPlaybackRate", async () => {
7225
+ const clamped = Math.max(0.5, Math.min(2, Number(rate) || 1));
7226
+ await send({ rate: clamped, type: "playback_rate" });
7227
+ return clamped;
7212
7228
  })
7213
7229
  };
7214
7230
  return api;
@@ -44831,6 +44847,7 @@ var createContractApi = (session) => ({
44831
44847
  speechThreshold: 0,
44832
44848
  transcriptStabilityMs: 0
44833
44849
  }),
44850
+ setPlaybackRate: async (rate5) => rate5,
44834
44851
  snapshot: async () => session,
44835
44852
  transfer: async () => {}
44836
44853
  });
@@ -11324,6 +11324,11 @@ var serverMessageToAction = (message) => {
11324
11324
  transcript: message.transcript,
11325
11325
  type: "partial"
11326
11326
  };
11327
+ case "playback_rate":
11328
+ return {
11329
+ rate: message.rate,
11330
+ type: "playback_rate"
11331
+ };
11327
11332
  case "replay":
11328
11333
  return {
11329
11334
  assistantTexts: message.assistantTexts,
@@ -11506,6 +11511,7 @@ var isVoiceServerMessage = (value) => {
11506
11511
  case "error":
11507
11512
  case "final":
11508
11513
  case "partial":
11514
+ case "playback_rate":
11509
11515
  case "pong":
11510
11516
  case "replay":
11511
11517
  case "session":
@@ -11932,6 +11938,7 @@ var createInitialState = () => ({
11932
11938
  isConnected: false,
11933
11939
  paused: false,
11934
11940
  partial: "",
11941
+ playbackRate: null,
11935
11942
  reconnect: createInitialReconnectState(),
11936
11943
  scenarioId: null,
11937
11944
  sessionId: null,
@@ -12040,6 +12047,12 @@ var createVoiceStreamStore = () => {
12040
12047
  partial: joinPartial(turnFinalText, action.transcript.text)
12041
12048
  };
12042
12049
  break;
12050
+ case "playback_rate":
12051
+ state = {
12052
+ ...state,
12053
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
12054
+ };
12055
+ break;
12043
12056
  case "replay":
12044
12057
  turnFinalText = action.partial;
12045
12058
  state = {
@@ -12205,6 +12218,9 @@ var createVoiceStream = (path, options = {}) => {
12205
12218
  get partial() {
12206
12219
  return store.getSnapshot().partial;
12207
12220
  },
12221
+ get playbackRate() {
12222
+ return store.getSnapshot().playbackRate;
12223
+ },
12208
12224
  get paused() {
12209
12225
  return store.getSnapshot().paused;
12210
12226
  },
@@ -12809,6 +12825,7 @@ var createInitialState2 = (stream) => ({
12809
12825
  paused: stream.paused,
12810
12826
  pauseExpiresAt: stream.pauseExpiresAt,
12811
12827
  partial: stream.partial,
12828
+ playbackRate: stream.playbackRate,
12812
12829
  reconnect: stream.reconnect,
12813
12830
  recordingError: null,
12814
12831
  sessionId: stream.sessionId,
@@ -12846,6 +12863,7 @@ var createVoiceController = (path, options = {}) => {
12846
12863
  paused: stream.paused,
12847
12864
  pauseExpiresAt: stream.pauseExpiresAt,
12848
12865
  partial: stream.partial,
12866
+ playbackRate: stream.playbackRate,
12849
12867
  reconnect: stream.reconnect,
12850
12868
  sessionId: stream.sessionId,
12851
12869
  sessionMetadata: stream.sessionMetadata,
@@ -12992,6 +13010,9 @@ var createVoiceController = (path, options = {}) => {
12992
13010
  get partial() {
12993
13011
  return state.partial;
12994
13012
  },
13013
+ get playbackRate() {
13014
+ return state.playbackRate;
13015
+ },
12995
13016
  get paused() {
12996
13017
  return state.paused;
12997
13018
  },
@@ -29,6 +29,7 @@ export declare const useVoiceController: <TResult = unknown>(path: string, optio
29
29
  }>;
30
30
  error: string | null;
31
31
  isConnected: boolean;
32
+ playbackRate: number | null;
32
33
  isRecording: boolean;
33
34
  recordingError: string | null;
34
35
  };
@@ -25,4 +25,5 @@ export declare const useVoiceStream: <TResult = unknown>(path: string, options?:
25
25
  }>;
26
26
  error: string | null;
27
27
  isConnected: boolean;
28
+ playbackRate: number | null;
28
29
  };
@@ -693,6 +693,11 @@ var serverMessageToAction = (message) => {
693
693
  transcript: message.transcript,
694
694
  type: "partial"
695
695
  };
696
+ case "playback_rate":
697
+ return {
698
+ rate: message.rate,
699
+ type: "playback_rate"
700
+ };
696
701
  case "replay":
697
702
  return {
698
703
  assistantTexts: message.assistantTexts,
@@ -875,6 +880,7 @@ var isVoiceServerMessage = (value) => {
875
880
  case "error":
876
881
  case "final":
877
882
  case "partial":
883
+ case "playback_rate":
878
884
  case "pong":
879
885
  case "replay":
880
886
  case "session":
@@ -1301,6 +1307,7 @@ var createInitialState = () => ({
1301
1307
  isConnected: false,
1302
1308
  paused: false,
1303
1309
  partial: "",
1310
+ playbackRate: null,
1304
1311
  reconnect: createInitialReconnectState(),
1305
1312
  scenarioId: null,
1306
1313
  sessionId: null,
@@ -1409,6 +1416,12 @@ var createVoiceStreamStore = () => {
1409
1416
  partial: joinPartial(turnFinalText, action.transcript.text)
1410
1417
  };
1411
1418
  break;
1419
+ case "playback_rate":
1420
+ state = {
1421
+ ...state,
1422
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
1423
+ };
1424
+ break;
1412
1425
  case "replay":
1413
1426
  turnFinalText = action.partial;
1414
1427
  state = {
@@ -1574,6 +1587,9 @@ var createVoiceStream = (path, options = {}) => {
1574
1587
  get partial() {
1575
1588
  return store.getSnapshot().partial;
1576
1589
  },
1590
+ get playbackRate() {
1591
+ return store.getSnapshot().playbackRate;
1592
+ },
1577
1593
  get paused() {
1578
1594
  return store.getSnapshot().paused;
1579
1595
  },
@@ -1946,6 +1962,7 @@ var createInitialState2 = (stream) => ({
1946
1962
  paused: stream.paused,
1947
1963
  pauseExpiresAt: stream.pauseExpiresAt,
1948
1964
  partial: stream.partial,
1965
+ playbackRate: stream.playbackRate,
1949
1966
  reconnect: stream.reconnect,
1950
1967
  recordingError: null,
1951
1968
  sessionId: stream.sessionId,
@@ -1983,6 +2000,7 @@ var createVoiceController = (path, options = {}) => {
1983
2000
  paused: stream.paused,
1984
2001
  pauseExpiresAt: stream.pauseExpiresAt,
1985
2002
  partial: stream.partial,
2003
+ playbackRate: stream.playbackRate,
1986
2004
  reconnect: stream.reconnect,
1987
2005
  sessionId: stream.sessionId,
1988
2006
  sessionMetadata: stream.sessionMetadata,
@@ -2129,6 +2147,9 @@ var createVoiceController = (path, options = {}) => {
2129
2147
  get partial() {
2130
2148
  return state.partial;
2131
2149
  },
2150
+ get playbackRate() {
2151
+ return state.playbackRate;
2152
+ },
2132
2153
  get paused() {
2133
2154
  return state.paused;
2134
2155
  },
@@ -2914,6 +2914,11 @@ var serverMessageToAction = (message) => {
2914
2914
  transcript: message.transcript,
2915
2915
  type: "partial"
2916
2916
  };
2917
+ case "playback_rate":
2918
+ return {
2919
+ rate: message.rate,
2920
+ type: "playback_rate"
2921
+ };
2917
2922
  case "replay":
2918
2923
  return {
2919
2924
  assistantTexts: message.assistantTexts,
@@ -3096,6 +3101,7 @@ var isVoiceServerMessage = (value) => {
3096
3101
  case "error":
3097
3102
  case "final":
3098
3103
  case "partial":
3104
+ case "playback_rate":
3099
3105
  case "pong":
3100
3106
  case "replay":
3101
3107
  case "session":
@@ -3522,6 +3528,7 @@ var createInitialState2 = () => ({
3522
3528
  isConnected: false,
3523
3529
  paused: false,
3524
3530
  partial: "",
3531
+ playbackRate: null,
3525
3532
  reconnect: createInitialReconnectState(),
3526
3533
  scenarioId: null,
3527
3534
  sessionId: null,
@@ -3630,6 +3637,12 @@ var createVoiceStreamStore = () => {
3630
3637
  partial: joinPartial(turnFinalText, action.transcript.text)
3631
3638
  };
3632
3639
  break;
3640
+ case "playback_rate":
3641
+ state = {
3642
+ ...state,
3643
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
3644
+ };
3645
+ break;
3633
3646
  case "replay":
3634
3647
  turnFinalText = action.partial;
3635
3648
  state = {
@@ -3795,6 +3808,9 @@ var createVoiceStream = (path, options = {}) => {
3795
3808
  get partial() {
3796
3809
  return store.getSnapshot().partial;
3797
3810
  },
3811
+ get playbackRate() {
3812
+ return store.getSnapshot().playbackRate;
3813
+ },
3798
3814
  get paused() {
3799
3815
  return store.getSnapshot().paused;
3800
3816
  },
@@ -4167,6 +4183,7 @@ var createInitialState3 = (stream) => ({
4167
4183
  paused: stream.paused,
4168
4184
  pauseExpiresAt: stream.pauseExpiresAt,
4169
4185
  partial: stream.partial,
4186
+ playbackRate: stream.playbackRate,
4170
4187
  reconnect: stream.reconnect,
4171
4188
  recordingError: null,
4172
4189
  sessionId: stream.sessionId,
@@ -4204,6 +4221,7 @@ var createVoiceController = (path, options = {}) => {
4204
4221
  paused: stream.paused,
4205
4222
  pauseExpiresAt: stream.pauseExpiresAt,
4206
4223
  partial: stream.partial,
4224
+ playbackRate: stream.playbackRate,
4207
4225
  reconnect: stream.reconnect,
4208
4226
  sessionId: stream.sessionId,
4209
4227
  sessionMetadata: stream.sessionMetadata,
@@ -4350,6 +4368,9 @@ var createVoiceController = (path, options = {}) => {
4350
4368
  get partial() {
4351
4369
  return state.partial;
4352
4370
  },
4371
+ get playbackRate() {
4372
+ return state.playbackRate;
4373
+ },
4353
4374
  get paused() {
4354
4375
  return state.paused;
4355
4376
  },
@@ -4459,10 +4480,18 @@ var createVoiceDuplexController = (path, options = {}) => {
4459
4480
  }
4460
4481
  });
4461
4482
  const audioPlayer = createVoiceAudioPlayer(controller, options.audioPlayer);
4483
+ let appliedPlaybackRate = controller.playbackRate;
4484
+ const unsubscribePlaybackRate = controller.subscribe(() => {
4485
+ if (controller.playbackRate !== null && controller.playbackRate !== appliedPlaybackRate) {
4486
+ appliedPlaybackRate = controller.playbackRate;
4487
+ audioPlayer.setPlaybackRate(controller.playbackRate);
4488
+ }
4489
+ });
4462
4490
  bargeInBinding = bindVoiceBargeIn(controller, audioPlayer, options.bargeIn);
4463
4491
  const close = () => {
4464
4492
  bargeInBinding?.close();
4465
4493
  bargeInBinding = null;
4494
+ unsubscribePlaybackRate();
4466
4495
  audioPlayer.close();
4467
4496
  controller.close();
4468
4497
  };
@@ -7250,6 +7279,11 @@ var createVoiceSession = (options) => {
7250
7279
  let lastSpeechEnergyAt = 0;
7251
7280
  let sttHealthPhaseStart = 0;
7252
7281
  let sttDeafConfirmedAt = 0;
7282
+ const resetSttHealthWindow = () => {
7283
+ lastSpeechEnergyAt = 0;
7284
+ sttHealthPhaseStart = 0;
7285
+ sttDeafConfirmedAt = 0;
7286
+ };
7253
7287
  let lastSttRecoverySpokenAt = 0;
7254
7288
  let activeTTSTurnId;
7255
7289
  let assistantSpeechEndsAt = 0;
@@ -9681,6 +9715,7 @@ var createVoiceSession = (options) => {
9681
9715
  markTurnCommitted(currentSession, finalText, transcripts);
9682
9716
  });
9683
9717
  speechDetected = false;
9718
+ resetSttHealthWindow();
9684
9719
  rewindFallbackTurnAudio();
9685
9720
  logger.info("voice turn committed", {
9686
9721
  reason,
@@ -10008,30 +10043,35 @@ var createVoiceSession = (options) => {
10008
10043
  clearSilenceTimer();
10009
10044
  }
10010
10045
  const nowMs = Date.now();
10011
- if (nowMs - lastSpeechEnergyAt > STT_HEALTH_SPEECH_GAP_MS) {
10012
- sttHealthPhaseStart = nowMs;
10013
- }
10014
- lastSpeechEnergyAt = nowMs;
10015
- const lastTranscriptAt = latest.currentTurn.lastTranscriptAt ?? 0;
10016
- if (!options.realtime && sttSession && lastTranscriptAt < sttHealthPhaseStart && nowMs - sttHealthPhaseStart >= STT_HEALTH_STALE_MS) {
10017
- sttDeafConfirmedAt = nowMs;
10018
- sttReconnectCount = nowMs - lastSttReconnectAt < STT_RECONNECT_FLAP_WINDOW_MS ? sttReconnectCount + 1 : 1;
10019
- lastSttReconnectAt = nowMs;
10020
- sttHealthPhaseStart = nowMs;
10021
- maybeSpeakSttRecovery(nowMs, latest);
10022
- if (sttReconnectCount <= MAX_STT_RECONNECTS_IN_FLAP_WINDOW) {
10023
- await appendTrace({
10024
- payload: {
10025
- action: "stt-health-reconnect",
10026
- attempt: sttReconnectCount,
10027
- classification: "stt-deaf",
10028
- reason: `no transcript for ${STT_HEALTH_STALE_MS}ms of continuous speech`
10029
- },
10030
- session: latest,
10031
- type: "session.error"
10032
- });
10033
- await closeAdapter("stt stale; health-reconnect");
10034
- return;
10046
+ if (assistantTurnInProgress) {
10047
+ resetSttHealthWindow();
10048
+ } else {
10049
+ if (nowMs - lastSpeechEnergyAt > STT_HEALTH_SPEECH_GAP_MS) {
10050
+ sttHealthPhaseStart = nowMs;
10051
+ }
10052
+ lastSpeechEnergyAt = nowMs;
10053
+ const lastTranscriptAt = latest.currentTurn.lastTranscriptAt ?? 0;
10054
+ if (!options.realtime && sttSession && lastTranscriptAt < sttHealthPhaseStart && nowMs - sttHealthPhaseStart >= STT_HEALTH_STALE_MS) {
10055
+ sttDeafConfirmedAt = nowMs;
10056
+ sttReconnectCount = nowMs - lastSttReconnectAt < STT_RECONNECT_FLAP_WINDOW_MS ? sttReconnectCount + 1 : 1;
10057
+ lastSttReconnectAt = nowMs;
10058
+ sttHealthPhaseStart = nowMs;
10059
+ maybeSpeakSttRecovery(nowMs, latest);
10060
+ if (sttReconnectCount <= MAX_STT_RECONNECTS_IN_FLAP_WINDOW) {
10061
+ await appendTrace({
10062
+ payload: {
10063
+ action: "stt-health-reconnect",
10064
+ attempt: sttReconnectCount,
10065
+ classification: "stt-deaf",
10066
+ recoverable: true,
10067
+ reason: `no transcript for ${STT_HEALTH_STALE_MS}ms of continuous speech`
10068
+ },
10069
+ session: latest,
10070
+ type: "session.error"
10071
+ });
10072
+ await closeAdapter("stt stale; health-reconnect");
10073
+ return;
10074
+ }
10035
10075
  }
10036
10076
  }
10037
10077
  } else if (speechDetected) {
@@ -10161,6 +10201,11 @@ var createVoiceSession = (options) => {
10161
10201
  speechThreshold: turnDetection.speechThreshold,
10162
10202
  transcriptStabilityMs: turnDetection.transcriptStabilityMs
10163
10203
  };
10204
+ }),
10205
+ setPlaybackRate: async (rate) => runSerial("api.setPlaybackRate", async () => {
10206
+ const clamped = Math.max(0.5, Math.min(2, Number(rate) || 1));
10207
+ await send({ rate: clamped, type: "playback_rate" });
10208
+ return clamped;
10164
10209
  })
10165
10210
  };
10166
10211
  return api;
package/dist/vue/index.js CHANGED
@@ -10720,6 +10720,11 @@ var serverMessageToAction = (message) => {
10720
10720
  transcript: message.transcript,
10721
10721
  type: "partial"
10722
10722
  };
10723
+ case "playback_rate":
10724
+ return {
10725
+ rate: message.rate,
10726
+ type: "playback_rate"
10727
+ };
10723
10728
  case "replay":
10724
10729
  return {
10725
10730
  assistantTexts: message.assistantTexts,
@@ -10902,6 +10907,7 @@ var isVoiceServerMessage = (value) => {
10902
10907
  case "error":
10903
10908
  case "final":
10904
10909
  case "partial":
10910
+ case "playback_rate":
10905
10911
  case "pong":
10906
10912
  case "replay":
10907
10913
  case "session":
@@ -11328,6 +11334,7 @@ var createInitialState = () => ({
11328
11334
  isConnected: false,
11329
11335
  paused: false,
11330
11336
  partial: "",
11337
+ playbackRate: null,
11331
11338
  reconnect: createInitialReconnectState(),
11332
11339
  scenarioId: null,
11333
11340
  sessionId: null,
@@ -11436,6 +11443,12 @@ var createVoiceStreamStore = () => {
11436
11443
  partial: joinPartial(turnFinalText, action.transcript.text)
11437
11444
  };
11438
11445
  break;
11446
+ case "playback_rate":
11447
+ state = {
11448
+ ...state,
11449
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
11450
+ };
11451
+ break;
11439
11452
  case "replay":
11440
11453
  turnFinalText = action.partial;
11441
11454
  state = {
@@ -11601,6 +11614,9 @@ var createVoiceStream = (path, options = {}) => {
11601
11614
  get partial() {
11602
11615
  return store.getSnapshot().partial;
11603
11616
  },
11617
+ get playbackRate() {
11618
+ return store.getSnapshot().playbackRate;
11619
+ },
11604
11620
  get paused() {
11605
11621
  return store.getSnapshot().paused;
11606
11622
  },
@@ -12226,6 +12242,7 @@ var createInitialState2 = (stream) => ({
12226
12242
  paused: stream.paused,
12227
12243
  pauseExpiresAt: stream.pauseExpiresAt,
12228
12244
  partial: stream.partial,
12245
+ playbackRate: stream.playbackRate,
12229
12246
  reconnect: stream.reconnect,
12230
12247
  recordingError: null,
12231
12248
  sessionId: stream.sessionId,
@@ -12263,6 +12280,7 @@ var createVoiceController = (path, options = {}) => {
12263
12280
  paused: stream.paused,
12264
12281
  pauseExpiresAt: stream.pauseExpiresAt,
12265
12282
  partial: stream.partial,
12283
+ playbackRate: stream.playbackRate,
12266
12284
  reconnect: stream.reconnect,
12267
12285
  sessionId: stream.sessionId,
12268
12286
  sessionMetadata: stream.sessionMetadata,
@@ -12409,6 +12427,9 @@ var createVoiceController = (path, options = {}) => {
12409
12427
  get partial() {
12410
12428
  return state.partial;
12411
12429
  },
12430
+ get playbackRate() {
12431
+ return state.playbackRate;
12432
+ },
12412
12433
  get paused() {
12413
12434
  return state.paused;
12414
12435
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.665",
3
+ "version": "0.0.22-beta.666",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",