@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.
- package/dist/angular/index.js +21 -0
- package/dist/client/actions.d.ts +37 -0
- package/dist/client/htmxBootstrap.js +21 -0
- package/dist/client/index.js +29 -0
- package/dist/core/types.d.ts +15 -1
- package/dist/embed/index.js +21 -0
- package/dist/embed/voice-widget.js +4 -4
- package/dist/index.js +41 -24
- package/dist/react/index.js +21 -0
- package/dist/react/useVoiceController.d.ts +1 -0
- package/dist/react/useVoiceStream.d.ts +1 -0
- package/dist/svelte/index.js +21 -0
- package/dist/testing/index.js +69 -24
- package/dist/vue/index.js +21 -0
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -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
|
},
|
package/dist/client/actions.d.ts
CHANGED
|
@@ -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
|
},
|
package/dist/client/index.js
CHANGED
|
@@ -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
|
};
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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
|
} | {
|
package/dist/embed/index.js
CHANGED
|
@@ -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("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var C0=(V)=>{if(!V.isConnected)return"idle";if(V.isPlaying)return"speaking";if(V.isRecording&&V.hasActivePartial)return"listening";if(V.isRecording)return"listening";if(V.lastTranscriptAt&&!V.lastAssistantAt)return"thinking";if(V.lastTranscriptAt&&V.lastAssistantAt&&V.lastTranscriptAt>V.lastAssistantAt)return"thinking";return"idle"};var e0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},VV={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},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("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var C0=(V)=>{if(!V.isConnected)return"idle";if(V.isPlaying)return"speaking";if(V.isRecording&&V.hasActivePartial)return"listening";if(V.isRecording)return"listening";if(V.lastTranscriptAt&&!V.lastAssistantAt)return"thinking";if(V.lastTranscriptAt&&V.lastAssistantAt&&V.lastTranscriptAt>V.lastAssistantAt)return"thinking";return"idle"};var e0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},VV={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},gV=(V,g)=>{switch(V){case"listening":return g.listening;case"speaking":return g.speaking;case"thinking":return g.thinking;case"idle":return g.idle}},_0=(V)=>{let g={...e0,...V.theme},C={...VV,...V.labels},A=V.state.assistantAudio.at(-1)?.receivedAt,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="${
|
|
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:${
|
|
10
|
-
</div>`};var CV=(V)=>{if(typeof V!=="string")return V;let
|
|
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 (
|
|
7060
|
-
|
|
7061
|
-
}
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
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
|
});
|
package/dist/react/index.js
CHANGED
|
@@ -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
|
},
|
package/dist/svelte/index.js
CHANGED
|
@@ -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
|
},
|
package/dist/testing/index.js
CHANGED
|
@@ -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 (
|
|
10012
|
-
|
|
10013
|
-
}
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
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
|
},
|