getpatter 0.6.1 → 0.6.2
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/chunk-CL2U3YET.mjs +1429 -0
- package/dist/{chunk-TEW3NAZJ.mjs → chunk-LE63CSOB.mjs} +371 -1486
- package/dist/{chunk-RV7APPYE.mjs → chunk-R2T4JABZ.mjs} +13 -0
- package/dist/cli.js +48 -23
- package/dist/dashboard/ui.html +8 -8
- package/dist/index.d.mts +452 -186
- package/dist/index.d.ts +452 -186
- package/dist/index.js +1485 -979
- package/dist/index.mjs +973 -790
- package/dist/openai-realtime-2-CNFARP25.mjs +8 -0
- package/dist/{silero-vad-NSEXI4XS.mjs → silero-vad-LNDFGIY7.mjs} +1 -1
- package/dist/{test-mode-WEKKNBLD.mjs → test-mode-RS57BDM6.mjs} +2 -1
- package/package.json +1 -1
- package/src/dashboard/ui.html +8 -8
package/dist/index.js
CHANGED
|
@@ -5,10 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __glob = (map) => (
|
|
9
|
-
var fn = map[
|
|
8
|
+
var __glob = (map) => (path6) => {
|
|
9
|
+
var fn = map[path6];
|
|
10
10
|
if (fn) return fn();
|
|
11
|
-
throw new Error("Module not found in bundle: " +
|
|
11
|
+
throw new Error("Module not found in bundle: " + path6);
|
|
12
12
|
};
|
|
13
13
|
var __esm = (fn, res) => function __init() {
|
|
14
14
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
@@ -303,11 +303,10 @@ var init_openai_realtime = __esm({
|
|
|
303
303
|
const url2 = `wss://api.openai.com/v1/realtime?model=${encodeURIComponent(this.model)}`;
|
|
304
304
|
let ws = null;
|
|
305
305
|
try {
|
|
306
|
-
ws = await new Promise((
|
|
306
|
+
ws = await new Promise((resolve2, reject) => {
|
|
307
307
|
const sock = new import_ws.default(url2, {
|
|
308
308
|
headers: {
|
|
309
|
-
Authorization: `Bearer ${this.apiKey}
|
|
310
|
-
"OpenAI-Beta": "realtime=v1"
|
|
309
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
311
310
|
}
|
|
312
311
|
});
|
|
313
312
|
const timer = setTimeout(() => {
|
|
@@ -319,22 +318,22 @@ var init_openai_realtime = __esm({
|
|
|
319
318
|
}, 5e3);
|
|
320
319
|
sock.once("open", () => {
|
|
321
320
|
clearTimeout(timer);
|
|
322
|
-
|
|
321
|
+
resolve2(sock);
|
|
323
322
|
});
|
|
324
323
|
sock.once("error", (err) => {
|
|
325
324
|
clearTimeout(timer);
|
|
326
325
|
reject(err);
|
|
327
326
|
});
|
|
328
327
|
});
|
|
329
|
-
const sessionCreated = await new Promise((
|
|
330
|
-
const timer = setTimeout(() =>
|
|
328
|
+
const sessionCreated = await new Promise((resolve2) => {
|
|
329
|
+
const timer = setTimeout(() => resolve2(false), 2e3);
|
|
331
330
|
const onMsg = (raw) => {
|
|
332
331
|
try {
|
|
333
332
|
const data = JSON.parse(raw.toString());
|
|
334
333
|
if (data.type === "session.created") {
|
|
335
334
|
clearTimeout(timer);
|
|
336
335
|
ws.off("message", onMsg);
|
|
337
|
-
|
|
336
|
+
resolve2(true);
|
|
338
337
|
}
|
|
339
338
|
} catch {
|
|
340
339
|
}
|
|
@@ -347,15 +346,15 @@ var init_openai_realtime = __esm({
|
|
|
347
346
|
} catch {
|
|
348
347
|
return;
|
|
349
348
|
}
|
|
350
|
-
await new Promise((
|
|
351
|
-
const timer = setTimeout(() =>
|
|
349
|
+
await new Promise((resolve2) => {
|
|
350
|
+
const timer = setTimeout(() => resolve2(), 1500);
|
|
352
351
|
const onMsg = (raw) => {
|
|
353
352
|
try {
|
|
354
353
|
const data = JSON.parse(raw.toString());
|
|
355
354
|
if (data.type === "session.updated") {
|
|
356
355
|
clearTimeout(timer);
|
|
357
356
|
ws.off("message", onMsg);
|
|
358
|
-
|
|
357
|
+
resolve2();
|
|
359
358
|
}
|
|
360
359
|
} catch {
|
|
361
360
|
}
|
|
@@ -378,11 +377,10 @@ var init_openai_realtime = __esm({
|
|
|
378
377
|
const url2 = `wss://api.openai.com/v1/realtime?model=${encodeURIComponent(this.model)}`;
|
|
379
378
|
this.ws = new import_ws.default(url2, {
|
|
380
379
|
headers: {
|
|
381
|
-
Authorization: `Bearer ${this.apiKey}
|
|
382
|
-
"OpenAI-Beta": "realtime=v1"
|
|
380
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
383
381
|
}
|
|
384
382
|
});
|
|
385
|
-
await new Promise((
|
|
383
|
+
await new Promise((resolve2, reject) => {
|
|
386
384
|
let sessionCreated = false;
|
|
387
385
|
let settled = false;
|
|
388
386
|
const ws = this.ws;
|
|
@@ -399,7 +397,7 @@ var init_openai_realtime = __esm({
|
|
|
399
397
|
ws.send(JSON.stringify({ type: "session.update", session: this.buildSessionConfig() }));
|
|
400
398
|
} else if (msg.type === "session.updated") {
|
|
401
399
|
cleanup();
|
|
402
|
-
|
|
400
|
+
resolve2();
|
|
403
401
|
}
|
|
404
402
|
};
|
|
405
403
|
const onSetupError = (err) => {
|
|
@@ -472,11 +470,10 @@ var init_openai_realtime = __esm({
|
|
|
472
470
|
const url2 = `wss://api.openai.com/v1/realtime?model=${encodeURIComponent(this.model)}`;
|
|
473
471
|
const ws = new import_ws.default(url2, {
|
|
474
472
|
headers: {
|
|
475
|
-
Authorization: `Bearer ${this.apiKey}
|
|
476
|
-
"OpenAI-Beta": "realtime=v1"
|
|
473
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
477
474
|
}
|
|
478
475
|
});
|
|
479
|
-
await new Promise((
|
|
476
|
+
await new Promise((resolve2, reject) => {
|
|
480
477
|
let sessionCreated = false;
|
|
481
478
|
let settled = false;
|
|
482
479
|
const onMessage = (raw) => {
|
|
@@ -496,7 +493,7 @@ var init_openai_realtime = __esm({
|
|
|
496
493
|
}
|
|
497
494
|
} else if (msg.type === "session.updated") {
|
|
498
495
|
cleanup();
|
|
499
|
-
|
|
496
|
+
resolve2();
|
|
500
497
|
}
|
|
501
498
|
};
|
|
502
499
|
const onError = (err) => {
|
|
@@ -621,22 +618,23 @@ var init_openai_realtime = __esm({
|
|
|
621
618
|
*/
|
|
622
619
|
cancelResponse() {
|
|
623
620
|
if (!this.ws) return;
|
|
624
|
-
if (this.currentResponseItemId) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
621
|
+
if (!this.currentResponseItemId) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
let audioEndMs = this.currentResponseAudioMs;
|
|
625
|
+
if (this.currentResponseFirstAudioAt !== null) {
|
|
626
|
+
const elapsedMs = Date.now() - this.currentResponseFirstAudioAt;
|
|
627
|
+
audioEndMs = Math.min(audioEndMs, Math.max(elapsedMs, 0));
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
this.ws.send(JSON.stringify({
|
|
631
|
+
type: "conversation.item.truncate",
|
|
632
|
+
item_id: this.currentResponseItemId,
|
|
633
|
+
content_index: 0,
|
|
634
|
+
audio_end_ms: audioEndMs
|
|
635
|
+
}));
|
|
636
|
+
} catch (err) {
|
|
637
|
+
getLogger().debug?.(`conversation.item.truncate failed: ${String(err)}`);
|
|
640
638
|
}
|
|
641
639
|
this.ws.send(JSON.stringify({ type: "response.cancel" }));
|
|
642
640
|
this.currentResponseItemId = null;
|
|
@@ -651,6 +649,19 @@ var init_openai_realtime = __esm({
|
|
|
651
649
|
}));
|
|
652
650
|
this.ws?.send(JSON.stringify({ type: "response.create" }));
|
|
653
651
|
}
|
|
652
|
+
/**
|
|
653
|
+
* Trigger `response.create` with no new user item.
|
|
654
|
+
*
|
|
655
|
+
* Used by the Realtime stream-handler to drive a response after the
|
|
656
|
+
* client-side hallucination filter accepts an
|
|
657
|
+
* `input_audio_transcription.completed` event. The server VAD config
|
|
658
|
+
* sets `create_response: false` so OpenAI no longer auto-creates a
|
|
659
|
+
* response on every `input_audio_buffer.committed`; Patter is now
|
|
660
|
+
* responsible for triggering it explicitly when a real user turn lands.
|
|
661
|
+
*/
|
|
662
|
+
async requestResponse() {
|
|
663
|
+
this.ws?.send(JSON.stringify({ type: "response.create" }));
|
|
664
|
+
}
|
|
654
665
|
/**
|
|
655
666
|
* Make the AI speak ``text`` as its opening line.
|
|
656
667
|
*
|
|
@@ -1103,6 +1114,10 @@ var init_transcoding = __esm({
|
|
|
1103
1114
|
});
|
|
1104
1115
|
|
|
1105
1116
|
// src/providers/openai-realtime-2.ts
|
|
1117
|
+
var openai_realtime_2_exports = {};
|
|
1118
|
+
__export(openai_realtime_2_exports, {
|
|
1119
|
+
OpenAIRealtime2Adapter: () => OpenAIRealtime2Adapter
|
|
1120
|
+
});
|
|
1106
1121
|
var import_ws2, GA_TO_V1_EVENT_NAMES, OpenAIRealtime2Adapter;
|
|
1107
1122
|
var init_openai_realtime_2 = __esm({
|
|
1108
1123
|
"src/providers/openai-realtime-2.ts"() {
|
|
@@ -1159,17 +1174,33 @@ var init_openai_realtime_2 = __esm({
|
|
|
1159
1174
|
transcription: {
|
|
1160
1175
|
model: opts.inputAudioTranscriptionModel ?? OpenAITranscriptionModel.WHISPER_1
|
|
1161
1176
|
},
|
|
1162
|
-
//
|
|
1163
|
-
//
|
|
1164
|
-
//
|
|
1165
|
-
//
|
|
1166
|
-
//
|
|
1167
|
-
//
|
|
1177
|
+
// VAD threshold raised back to the OpenAI default (0.5) on
|
|
1178
|
+
// 2026-05-22. The earlier 0.1 tuning (motivated by the
|
|
1179
|
+
// upsampled telephony-band loss in high frequencies) made the
|
|
1180
|
+
// server VAD trigger on the carrier-loopback echo of the
|
|
1181
|
+
// agent's OWN outbound audio in PSTN no-AEC scenarios.
|
|
1182
|
+
// Combined with the default ``turn_detection.create_response:
|
|
1183
|
+
// true``, every phantom ``speech_started`` ended a turn early
|
|
1184
|
+
// and auto-created a new response that the agent immediately
|
|
1185
|
+
// spoke over, leading to a runaway loop where the first
|
|
1186
|
+
// message was repeatedly cut and re-generated.
|
|
1168
1187
|
turn_detection: {
|
|
1169
1188
|
type: opts.vadType ?? OpenAIRealtimeVADType.SERVER_VAD,
|
|
1170
|
-
threshold: 0.
|
|
1189
|
+
threshold: 0.5,
|
|
1171
1190
|
prefix_padding_ms: 300,
|
|
1172
|
-
silence_duration_ms: opts.silenceDurationMs ?? 500
|
|
1191
|
+
silence_duration_ms: opts.silenceDurationMs ?? 500,
|
|
1192
|
+
// Defer ``response.create`` to the application: when OpenAI's
|
|
1193
|
+
// server VAD commits an ``input_audio_buffer.committed`` segment
|
|
1194
|
+
// that turns out to be a Whisper hallucination on silence/echo,
|
|
1195
|
+
// auto-creating a response would generate a phantom turn (the
|
|
1196
|
+
// model reads the hallucinated text as user input). Patter
|
|
1197
|
+
// triggers ``response.create`` explicitly in the Realtime
|
|
1198
|
+
// stream-handler AFTER validating ``transcript_input`` against
|
|
1199
|
+
// the hallucination filter. Pair with ``interrupt_response:
|
|
1200
|
+
// false`` so server VAD also leaves in-flight responses alone —
|
|
1201
|
+
// barge-in is gated client-side.
|
|
1202
|
+
create_response: false,
|
|
1203
|
+
interrupt_response: false
|
|
1173
1204
|
}
|
|
1174
1205
|
},
|
|
1175
1206
|
output: {
|
|
@@ -1244,7 +1275,7 @@ var init_openai_realtime_2 = __esm({
|
|
|
1244
1275
|
};
|
|
1245
1276
|
return originalOn(event, wrapped);
|
|
1246
1277
|
};
|
|
1247
|
-
await new Promise((
|
|
1278
|
+
await new Promise((resolve2, reject) => {
|
|
1248
1279
|
let sessionCreated = false;
|
|
1249
1280
|
let settled = false;
|
|
1250
1281
|
const ws = this.ws;
|
|
@@ -1261,7 +1292,7 @@ var init_openai_realtime_2 = __esm({
|
|
|
1261
1292
|
ws.send(JSON.stringify({ type: "session.update", session: this.buildGASessionConfig() }));
|
|
1262
1293
|
} else if (msg.type === "session.updated") {
|
|
1263
1294
|
cleanup();
|
|
1264
|
-
|
|
1295
|
+
resolve2();
|
|
1265
1296
|
} else if (msg.type === "error") {
|
|
1266
1297
|
cleanup();
|
|
1267
1298
|
try {
|
|
@@ -1299,6 +1330,131 @@ var init_openai_realtime_2 = __esm({
|
|
|
1299
1330
|
});
|
|
1300
1331
|
this.armHeartbeatAndListener();
|
|
1301
1332
|
}
|
|
1333
|
+
/**
|
|
1334
|
+
* GA-API variant of {@link OpenAIRealtimeAdapter.openParkedConnection}.
|
|
1335
|
+
* Opens a fresh Realtime WS against the GA endpoint, exchanges
|
|
1336
|
+
* `session.created` → GA-shape `session.update` → `session.updated`
|
|
1337
|
+
* so the upstream session is fully primed, and returns the OPEN
|
|
1338
|
+
* socket WITHOUT taking it on `this.ws` or arming the heartbeat /
|
|
1339
|
+
* message listener.
|
|
1340
|
+
*
|
|
1341
|
+
* Used by `Patter.parkProviderConnections` during the carrier
|
|
1342
|
+
* ringing window so the per-call `StreamHandler` can adopt the
|
|
1343
|
+
* primed socket at carrier `start` — eliminating the TCP + TLS +
|
|
1344
|
+
* HTTP-101 + `session.update` ack round-trip from the critical path.
|
|
1345
|
+
* Saves ~300-600 ms of first-audible-word latency.
|
|
1346
|
+
*
|
|
1347
|
+
* Bounded by 8 s. Throws on timeout / handshake failure / GA-side
|
|
1348
|
+
* rejection. Callers treat any error as a cache miss and fall
|
|
1349
|
+
* through to the cold {@link connect} path.
|
|
1350
|
+
*
|
|
1351
|
+
* Billing safety: confirmed by OpenAI's Managing Realtime Costs
|
|
1352
|
+
* guide — `session.update` does NOT invoke the model and bills no
|
|
1353
|
+
* tokens. An idle parked socket costs $0.
|
|
1354
|
+
*/
|
|
1355
|
+
async openParkedConnection() {
|
|
1356
|
+
const url2 = `wss://api.openai.com/v1/realtime?model=${encodeURIComponent(this.model)}`;
|
|
1357
|
+
const ws = new import_ws2.default(url2, {
|
|
1358
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
1359
|
+
});
|
|
1360
|
+
await new Promise((resolve2, reject) => {
|
|
1361
|
+
let sessionCreated = false;
|
|
1362
|
+
let settled = false;
|
|
1363
|
+
const onMessage = (raw) => {
|
|
1364
|
+
let msg;
|
|
1365
|
+
try {
|
|
1366
|
+
msg = JSON.parse(raw.toString());
|
|
1367
|
+
} catch {
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
if (msg.type === "session.created" && !sessionCreated) {
|
|
1371
|
+
sessionCreated = true;
|
|
1372
|
+
try {
|
|
1373
|
+
ws.send(JSON.stringify({ type: "session.update", session: this.buildGASessionConfig() }));
|
|
1374
|
+
} catch (err) {
|
|
1375
|
+
cleanup();
|
|
1376
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
1377
|
+
}
|
|
1378
|
+
} else if (msg.type === "session.updated") {
|
|
1379
|
+
cleanup();
|
|
1380
|
+
resolve2();
|
|
1381
|
+
} else if (msg.type === "error") {
|
|
1382
|
+
cleanup();
|
|
1383
|
+
reject(new Error(`OpenAI Realtime 2 parked-setup error: ${msg.error?.message ?? JSON.stringify(msg)}`));
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
const onError = (err) => {
|
|
1387
|
+
cleanup();
|
|
1388
|
+
reject(err);
|
|
1389
|
+
};
|
|
1390
|
+
const cleanup = () => {
|
|
1391
|
+
if (settled) return;
|
|
1392
|
+
settled = true;
|
|
1393
|
+
clearTimeout(timer);
|
|
1394
|
+
ws.off("message", onMessage);
|
|
1395
|
+
ws.off("error", onError);
|
|
1396
|
+
};
|
|
1397
|
+
const timer = setTimeout(() => {
|
|
1398
|
+
cleanup();
|
|
1399
|
+
reject(new Error("OpenAI Realtime 2 park connect timeout"));
|
|
1400
|
+
}, 8e3);
|
|
1401
|
+
ws.on("message", onMessage);
|
|
1402
|
+
ws.on("error", onError);
|
|
1403
|
+
});
|
|
1404
|
+
const keepalive = setInterval(() => {
|
|
1405
|
+
if (ws.readyState !== ws.OPEN) {
|
|
1406
|
+
clearInterval(keepalive);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
try {
|
|
1410
|
+
ws.send(JSON.stringify({ type: "session.update", session: this.buildGASessionConfig() }));
|
|
1411
|
+
} catch {
|
|
1412
|
+
clearInterval(keepalive);
|
|
1413
|
+
}
|
|
1414
|
+
}, 3e3);
|
|
1415
|
+
ws._parkedKeepalive = keepalive;
|
|
1416
|
+
return ws;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* GA-API variant of {@link OpenAIRealtimeAdapter.adoptWebSocket}. Takes
|
|
1420
|
+
* over a WS that {@link openParkedConnection} produced (already through
|
|
1421
|
+
* `session.created` + `session.update` + `session.updated`) and arms
|
|
1422
|
+
* the heartbeat + message listener so the GA event-translation shim
|
|
1423
|
+
* is wired up. Skips the cold-connect path — saves ~300-600 ms on
|
|
1424
|
+
* first audible word.
|
|
1425
|
+
*
|
|
1426
|
+
* Caller MUST verify `ws.readyState === OPEN` before calling. If the
|
|
1427
|
+
* parked WS died between park and adopt, fall back to {@link connect}.
|
|
1428
|
+
*/
|
|
1429
|
+
adoptWebSocket(ws) {
|
|
1430
|
+
const wsAny = ws;
|
|
1431
|
+
if (wsAny._parkedKeepalive) {
|
|
1432
|
+
clearInterval(wsAny._parkedKeepalive);
|
|
1433
|
+
delete wsAny._parkedKeepalive;
|
|
1434
|
+
}
|
|
1435
|
+
this.ws = ws;
|
|
1436
|
+
const wsRef = ws;
|
|
1437
|
+
const originalOn = wsRef.on.bind(ws);
|
|
1438
|
+
wsRef.on = (event, handler) => {
|
|
1439
|
+
if (event !== "message") return originalOn(event, handler);
|
|
1440
|
+
const wrapped = (raw, ...rest) => {
|
|
1441
|
+
try {
|
|
1442
|
+
const text = typeof raw === "string" ? raw : raw.toString();
|
|
1443
|
+
const parsed = JSON.parse(text);
|
|
1444
|
+
const t = parsed.type;
|
|
1445
|
+
if (t && Object.prototype.hasOwnProperty.call(GA_TO_V1_EVENT_NAMES, t)) {
|
|
1446
|
+
parsed.type = GA_TO_V1_EVENT_NAMES[t];
|
|
1447
|
+
handler(JSON.stringify(parsed), ...rest);
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
} catch {
|
|
1451
|
+
}
|
|
1452
|
+
handler(raw, ...rest);
|
|
1453
|
+
};
|
|
1454
|
+
return originalOn(event, wrapped);
|
|
1455
|
+
};
|
|
1456
|
+
this.armHeartbeatAndListener();
|
|
1457
|
+
}
|
|
1302
1458
|
/**
|
|
1303
1459
|
* GA-API variant of {@link OpenAIRealtimeAdapter.sendFirstMessage}. Two
|
|
1304
1460
|
* differences from the v1 path:
|
|
@@ -1392,15 +1548,15 @@ var init_openai_realtime_2 = __esm({
|
|
|
1392
1548
|
return pcm16ToMulaw(pcm8);
|
|
1393
1549
|
}
|
|
1394
1550
|
async sendFirstMessage(text) {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}));
|
|
1551
|
+
const responseBody = {
|
|
1552
|
+
output_modalities: ["audio"],
|
|
1553
|
+
audio: { output: { voice: this.voice } },
|
|
1554
|
+
instructions: `Say exactly the following sentence as your first turn and nothing else: "${text}"`
|
|
1555
|
+
};
|
|
1556
|
+
if (this.options.reasoningEffort !== void 0) {
|
|
1557
|
+
responseBody.reasoning = { effort: this.options.reasoningEffort };
|
|
1558
|
+
}
|
|
1559
|
+
this.ws?.send(JSON.stringify({ type: "response.create", response: responseBody }));
|
|
1404
1560
|
}
|
|
1405
1561
|
};
|
|
1406
1562
|
}
|
|
@@ -1536,7 +1692,7 @@ var init_elevenlabs_convai = __esm({
|
|
|
1536
1692
|
wsOptions = { headers: { "xi-api-key": this.apiKey } };
|
|
1537
1693
|
}
|
|
1538
1694
|
this.ws = new import_ws3.default(wsUrl, wsOptions);
|
|
1539
|
-
await new Promise((
|
|
1695
|
+
await new Promise((resolve2, reject) => {
|
|
1540
1696
|
const timeout = setTimeout(
|
|
1541
1697
|
() => reject(new Error("ElevenLabs ConvAI connect timeout")),
|
|
1542
1698
|
15e3
|
|
@@ -1560,7 +1716,7 @@ var init_elevenlabs_convai = __esm({
|
|
|
1560
1716
|
conversation_config_override: override
|
|
1561
1717
|
};
|
|
1562
1718
|
this.ws.send(JSON.stringify(config2));
|
|
1563
|
-
|
|
1719
|
+
resolve2();
|
|
1564
1720
|
});
|
|
1565
1721
|
this.ws.once("error", (err) => {
|
|
1566
1722
|
clearTimeout(timeout);
|
|
@@ -1717,20 +1873,20 @@ var init_elevenlabs_convai = __esm({
|
|
|
1717
1873
|
return;
|
|
1718
1874
|
}
|
|
1719
1875
|
const ws = this.ws;
|
|
1720
|
-
this.closePromise = new Promise((
|
|
1876
|
+
this.closePromise = new Promise((resolve2) => {
|
|
1721
1877
|
if (ws.readyState === import_ws3.default.CLOSED || ws.readyState === import_ws3.default.CLOSING) {
|
|
1722
|
-
|
|
1878
|
+
resolve2();
|
|
1723
1879
|
return;
|
|
1724
1880
|
}
|
|
1725
1881
|
const done = () => {
|
|
1726
|
-
|
|
1882
|
+
resolve2();
|
|
1727
1883
|
};
|
|
1728
1884
|
ws.once("close", done);
|
|
1729
1885
|
ws.once("error", done);
|
|
1730
1886
|
try {
|
|
1731
1887
|
ws.close();
|
|
1732
1888
|
} catch {
|
|
1733
|
-
|
|
1889
|
+
resolve2();
|
|
1734
1890
|
}
|
|
1735
1891
|
});
|
|
1736
1892
|
try {
|
|
@@ -1901,11 +2057,13 @@ function calculateTelephonyCost(provider2, durationSeconds, pricing) {
|
|
|
1901
2057
|
const minutes = provider2 === "twilio" ? Math.ceil(durationSeconds / 60) : durationSeconds / 60;
|
|
1902
2058
|
return minutes * (config2.price ?? 0);
|
|
1903
2059
|
}
|
|
1904
|
-
var PricingUnit, DEFAULT_PRICING, llmPricing;
|
|
2060
|
+
var PRICING_VERSION, PRICING_LAST_UPDATED, PricingUnit, DEFAULT_PRICING, llmPricing;
|
|
1905
2061
|
var init_pricing = __esm({
|
|
1906
2062
|
"src/pricing.ts"() {
|
|
1907
2063
|
"use strict";
|
|
1908
2064
|
init_cjs_shims();
|
|
2065
|
+
PRICING_VERSION = "2026.3";
|
|
2066
|
+
PRICING_LAST_UPDATED = "2026-05-08";
|
|
1909
2067
|
PricingUnit = {
|
|
1910
2068
|
MINUTE: "minute",
|
|
1911
2069
|
THOUSAND_CHARS: "1k_chars",
|
|
@@ -2213,7 +2371,31 @@ var init_pricing = __esm({
|
|
|
2213
2371
|
}
|
|
2214
2372
|
});
|
|
2215
2373
|
|
|
2374
|
+
// src/version.ts
|
|
2375
|
+
function readVersion() {
|
|
2376
|
+
try {
|
|
2377
|
+
const pkgPath = path.resolve(__dirname, "..", "package.json");
|
|
2378
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
2379
|
+
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "";
|
|
2380
|
+
} catch {
|
|
2381
|
+
return "";
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
var fs, path, VERSION;
|
|
2385
|
+
var init_version = __esm({
|
|
2386
|
+
"src/version.ts"() {
|
|
2387
|
+
"use strict";
|
|
2388
|
+
init_cjs_shims();
|
|
2389
|
+
fs = __toESM(require("fs"));
|
|
2390
|
+
path = __toESM(require("path"));
|
|
2391
|
+
VERSION = readVersion();
|
|
2392
|
+
}
|
|
2393
|
+
});
|
|
2394
|
+
|
|
2216
2395
|
// src/dashboard/store.ts
|
|
2396
|
+
function sdkVersion() {
|
|
2397
|
+
return VERSION;
|
|
2398
|
+
}
|
|
2217
2399
|
function metricsFromTopLevel(meta2) {
|
|
2218
2400
|
const cost = meta2.cost && typeof meta2.cost === "object" ? meta2.cost : null;
|
|
2219
2401
|
const latency = meta2.latency && typeof meta2.latency === "object" ? meta2.latency : null;
|
|
@@ -2268,8 +2450,8 @@ function metadataToCallRecord(callId, meta2) {
|
|
|
2268
2450
|
}
|
|
2269
2451
|
function loadTranscriptJsonl(filePath) {
|
|
2270
2452
|
try {
|
|
2271
|
-
if (!
|
|
2272
|
-
const raw =
|
|
2453
|
+
if (!fs2.existsSync(filePath)) return [];
|
|
2454
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
2273
2455
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
2274
2456
|
const out = [];
|
|
2275
2457
|
for (const line of lines) {
|
|
@@ -2306,15 +2488,16 @@ function parseTimestamp(raw) {
|
|
|
2306
2488
|
}
|
|
2307
2489
|
return null;
|
|
2308
2490
|
}
|
|
2309
|
-
var import_events,
|
|
2491
|
+
var import_events, fs2, path2, MetricsStore;
|
|
2310
2492
|
var init_store = __esm({
|
|
2311
2493
|
"src/dashboard/store.ts"() {
|
|
2312
2494
|
"use strict";
|
|
2313
2495
|
init_cjs_shims();
|
|
2314
2496
|
import_events = require("events");
|
|
2315
|
-
|
|
2316
|
-
|
|
2497
|
+
fs2 = __toESM(require("fs"));
|
|
2498
|
+
path2 = __toESM(require("path"));
|
|
2317
2499
|
init_logger();
|
|
2500
|
+
init_version();
|
|
2318
2501
|
MetricsStore = class extends import_events.EventEmitter {
|
|
2319
2502
|
maxCalls;
|
|
2320
2503
|
calls = [];
|
|
@@ -2597,15 +2780,15 @@ var init_store = __esm({
|
|
|
2597
2780
|
persistDeletedIds() {
|
|
2598
2781
|
if (this.deletedIdsPath === null) return;
|
|
2599
2782
|
try {
|
|
2600
|
-
const dir =
|
|
2601
|
-
|
|
2783
|
+
const dir = path2.dirname(this.deletedIdsPath);
|
|
2784
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
2602
2785
|
const tmp = this.deletedIdsPath + ".tmp";
|
|
2603
2786
|
const payload = {
|
|
2604
2787
|
version: 1,
|
|
2605
2788
|
deleted_call_ids: Array.from(this.deletedCallIds).sort()
|
|
2606
2789
|
};
|
|
2607
|
-
|
|
2608
|
-
|
|
2790
|
+
fs2.writeFileSync(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
2791
|
+
fs2.renameSync(tmp, this.deletedIdsPath);
|
|
2609
2792
|
} catch (err) {
|
|
2610
2793
|
getLogger().debug(
|
|
2611
2794
|
`MetricsStore.persistDeletedIds: ${String(err)}`
|
|
@@ -2638,7 +2821,8 @@ var init_store = __esm({
|
|
|
2638
2821
|
avg_duration: 0,
|
|
2639
2822
|
avg_latency_ms: 0,
|
|
2640
2823
|
cost_breakdown: { stt: 0, tts: 0, llm: 0, telephony: 0 },
|
|
2641
|
-
active_calls: this.activeCalls.size
|
|
2824
|
+
active_calls: this.activeCalls.size,
|
|
2825
|
+
sdk_version: sdkVersion()
|
|
2642
2826
|
};
|
|
2643
2827
|
}
|
|
2644
2828
|
let totalCost = 0;
|
|
@@ -2677,7 +2861,8 @@ var init_store = __esm({
|
|
|
2677
2861
|
llm: Math.round(costLlm * 1e6) / 1e6,
|
|
2678
2862
|
telephony: Math.round(costTel * 1e6) / 1e6
|
|
2679
2863
|
},
|
|
2680
|
-
active_calls: this.activeCalls.size
|
|
2864
|
+
active_calls: this.activeCalls.size,
|
|
2865
|
+
sdk_version: sdkVersion()
|
|
2681
2866
|
};
|
|
2682
2867
|
}
|
|
2683
2868
|
/**
|
|
@@ -2713,11 +2898,11 @@ var init_store = __esm({
|
|
|
2713
2898
|
*/
|
|
2714
2899
|
hydrate(logRoot) {
|
|
2715
2900
|
if (!logRoot) return 0;
|
|
2716
|
-
const deletedIdsPath =
|
|
2901
|
+
const deletedIdsPath = path2.join(logRoot, ".deleted_call_ids.json");
|
|
2717
2902
|
this.deletedIdsPath = deletedIdsPath;
|
|
2718
|
-
if (
|
|
2903
|
+
if (fs2.existsSync(deletedIdsPath)) {
|
|
2719
2904
|
try {
|
|
2720
|
-
const raw =
|
|
2905
|
+
const raw = fs2.readFileSync(deletedIdsPath, "utf8");
|
|
2721
2906
|
const payload = JSON.parse(raw);
|
|
2722
2907
|
const arr = Array.isArray(payload.deleted_call_ids) ? payload.deleted_call_ids : [];
|
|
2723
2908
|
for (const cid of arr) {
|
|
@@ -2731,19 +2916,19 @@ var init_store = __esm({
|
|
|
2731
2916
|
);
|
|
2732
2917
|
}
|
|
2733
2918
|
}
|
|
2734
|
-
const callsRoot =
|
|
2735
|
-
if (!
|
|
2919
|
+
const callsRoot = path2.join(logRoot, "calls");
|
|
2920
|
+
if (!fs2.existsSync(callsRoot)) return 0;
|
|
2736
2921
|
const collected = [];
|
|
2737
2922
|
const seen = new Set(this.calls.map((c) => c.call_id));
|
|
2738
2923
|
const walk = (dir, depth) => {
|
|
2739
2924
|
let entries;
|
|
2740
2925
|
try {
|
|
2741
|
-
entries =
|
|
2926
|
+
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
2742
2927
|
} catch {
|
|
2743
2928
|
return;
|
|
2744
2929
|
}
|
|
2745
2930
|
for (const entry of entries) {
|
|
2746
|
-
const childPath =
|
|
2931
|
+
const childPath = path2.join(dir, entry.name);
|
|
2747
2932
|
if (depth < 3) {
|
|
2748
2933
|
if (entry.isDirectory() && /^\d+$/.test(entry.name)) {
|
|
2749
2934
|
walk(childPath, depth + 1);
|
|
@@ -2751,10 +2936,10 @@ var init_store = __esm({
|
|
|
2751
2936
|
continue;
|
|
2752
2937
|
}
|
|
2753
2938
|
if (!entry.isDirectory()) continue;
|
|
2754
|
-
const metadataPath =
|
|
2755
|
-
if (!
|
|
2939
|
+
const metadataPath = path2.join(childPath, "metadata.json");
|
|
2940
|
+
if (!fs2.existsSync(metadataPath)) continue;
|
|
2756
2941
|
try {
|
|
2757
|
-
const raw =
|
|
2942
|
+
const raw = fs2.readFileSync(metadataPath, "utf8");
|
|
2758
2943
|
const meta2 = JSON.parse(raw);
|
|
2759
2944
|
const callId = meta2.call_id || entry.name;
|
|
2760
2945
|
if (!callId || seen.has(callId)) continue;
|
|
@@ -2767,7 +2952,7 @@ var init_store = __esm({
|
|
|
2767
2952
|
}
|
|
2768
2953
|
if (!record2.transcript || record2.transcript.length === 0) {
|
|
2769
2954
|
const fromJsonl = loadTranscriptJsonl(
|
|
2770
|
-
|
|
2955
|
+
path2.join(childPath, "transcript.jsonl")
|
|
2771
2956
|
);
|
|
2772
2957
|
if (fromJsonl.length > 0) record2.transcript = fromJsonl;
|
|
2773
2958
|
}
|
|
@@ -2905,9 +3090,9 @@ function loadDashboardHtml() {
|
|
|
2905
3090
|
(0, import_node_path.join)(here, "dashboard", "ui.html"),
|
|
2906
3091
|
(0, import_node_path.join)(here, "..", "dashboard", "ui.html")
|
|
2907
3092
|
];
|
|
2908
|
-
for (const
|
|
3093
|
+
for (const path6 of candidates) {
|
|
2909
3094
|
try {
|
|
2910
|
-
return (0, import_node_fs.readFileSync)(
|
|
3095
|
+
return (0, import_node_fs.readFileSync)(path6, "utf8");
|
|
2911
3096
|
} catch {
|
|
2912
3097
|
}
|
|
2913
3098
|
}
|
|
@@ -3298,10 +3483,10 @@ var init_remote_message = __esm({
|
|
|
3298
3483
|
}
|
|
3299
3484
|
});
|
|
3300
3485
|
try {
|
|
3301
|
-
await new Promise((
|
|
3486
|
+
await new Promise((resolve2, reject) => {
|
|
3302
3487
|
ws.on("open", () => {
|
|
3303
3488
|
ws.send(JSON.stringify(data));
|
|
3304
|
-
|
|
3489
|
+
resolve2();
|
|
3305
3490
|
});
|
|
3306
3491
|
ws.on("error", (err) => {
|
|
3307
3492
|
reject(err);
|
|
@@ -3311,11 +3496,11 @@ var init_remote_message = __esm({
|
|
|
3311
3496
|
yield chunks.shift();
|
|
3312
3497
|
}
|
|
3313
3498
|
while (!done && !error2) {
|
|
3314
|
-
const text = await new Promise((
|
|
3499
|
+
const text = await new Promise((resolve2) => {
|
|
3315
3500
|
if (chunks.length > 0) {
|
|
3316
|
-
|
|
3501
|
+
resolve2(chunks.shift());
|
|
3317
3502
|
} else {
|
|
3318
|
-
resolveNext =
|
|
3503
|
+
resolveNext = resolve2;
|
|
3319
3504
|
}
|
|
3320
3505
|
});
|
|
3321
3506
|
if (text === null) break;
|
|
@@ -3461,7 +3646,7 @@ var init_deepgram_stt = __esm({
|
|
|
3461
3646
|
const url2 = `${DEEPGRAM_WS_URL}?${params.toString()}`;
|
|
3462
3647
|
let ws = null;
|
|
3463
3648
|
try {
|
|
3464
|
-
ws = await new Promise((
|
|
3649
|
+
ws = await new Promise((resolve2, reject) => {
|
|
3465
3650
|
const sock = new import_ws4.default(url2, {
|
|
3466
3651
|
headers: { Authorization: `Token ${this.apiKey}` }
|
|
3467
3652
|
});
|
|
@@ -3474,7 +3659,7 @@ var init_deepgram_stt = __esm({
|
|
|
3474
3659
|
}, 5e3);
|
|
3475
3660
|
sock.once("open", () => {
|
|
3476
3661
|
clearTimeout(timer);
|
|
3477
|
-
|
|
3662
|
+
resolve2(sock);
|
|
3478
3663
|
});
|
|
3479
3664
|
sock.once("error", (err) => {
|
|
3480
3665
|
clearTimeout(timer);
|
|
@@ -3505,7 +3690,7 @@ var init_deepgram_stt = __esm({
|
|
|
3505
3690
|
headers: { Authorization: `Token ${this.apiKey}` }
|
|
3506
3691
|
});
|
|
3507
3692
|
this.ws = ws;
|
|
3508
|
-
await new Promise((
|
|
3693
|
+
await new Promise((resolve2, reject) => {
|
|
3509
3694
|
let settled = false;
|
|
3510
3695
|
const settle = (fn) => {
|
|
3511
3696
|
if (settled) return;
|
|
@@ -3517,7 +3702,7 @@ var init_deepgram_stt = __esm({
|
|
|
3517
3702
|
() => settle(() => reject(new PatterConnectionError("Deepgram connect timeout"))),
|
|
3518
3703
|
1e4
|
|
3519
3704
|
);
|
|
3520
|
-
ws.once("open", () => settle(
|
|
3705
|
+
ws.once("open", () => settle(resolve2));
|
|
3521
3706
|
ws.once("error", (err) => settle(() => reject(err)));
|
|
3522
3707
|
ws.once("unexpected-response", (_req, res) => {
|
|
3523
3708
|
const status = res?.statusCode ?? 0;
|
|
@@ -3817,6 +4002,21 @@ var init_metrics = __esm({
|
|
|
3817
4002
|
_bargeinStoppedAt = null;
|
|
3818
4003
|
_turnUserText = "";
|
|
3819
4004
|
_turnSttAudioSeconds = 0;
|
|
4005
|
+
/**
|
|
4006
|
+
* Guard against the recordTurnInterrupted / recordTurnComplete race.
|
|
4007
|
+
*
|
|
4008
|
+
* A VAD-path barge-in fires ``recordTurnInterrupted`` synchronously
|
|
4009
|
+
* inside ``handleAudioAsync`` while the in-flight pipeline LLM stream
|
|
4010
|
+
* keeps unwinding on its own task. When the LLM stream eventually
|
|
4011
|
+
* exits, the existing pipeline path falls through to
|
|
4012
|
+
* ``recordTurnComplete``, which would push a second turn for the same
|
|
4013
|
+
* logical exchange (this time carrying ``user_text=''`` because the
|
|
4014
|
+
* field was already reset). ``_turnAlreadyClosed`` is flipped by
|
|
4015
|
+
* ``recordTurnInterrupted`` and read by ``recordTurnComplete`` so the
|
|
4016
|
+
* late ``recordTurnComplete`` becomes a no-op until the next
|
|
4017
|
+
* ``startTurn`` re-arms the accumulator.
|
|
4018
|
+
*/
|
|
4019
|
+
_turnAlreadyClosed = false;
|
|
3820
4020
|
// Cumulative usage counters
|
|
3821
4021
|
_totalSttAudioSeconds = 0;
|
|
3822
4022
|
_totalTtsCharacters = 0;
|
|
@@ -3914,6 +4114,7 @@ var init_metrics = __esm({
|
|
|
3914
4114
|
this._bargeinStoppedAt = null;
|
|
3915
4115
|
this._turnUserText = "";
|
|
3916
4116
|
this._turnSttAudioSeconds = 0;
|
|
4117
|
+
this._turnAlreadyClosed = false;
|
|
3917
4118
|
this._vadStoppedAt = null;
|
|
3918
4119
|
this._sttFinalAt = null;
|
|
3919
4120
|
this._turnCommittedAt = null;
|
|
@@ -4070,8 +4271,18 @@ var init_metrics = __esm({
|
|
|
4070
4271
|
recordTtsStopped(ts) {
|
|
4071
4272
|
this._bargeinStoppedAt = ts ?? hrTimeMs();
|
|
4072
4273
|
}
|
|
4073
|
-
/**
|
|
4274
|
+
/**
|
|
4275
|
+
* Close the current turn cleanly and append a `TurnMetrics` record.
|
|
4276
|
+
*
|
|
4277
|
+
* Returns ``null`` when ``recordTurnInterrupted`` has already closed
|
|
4278
|
+
* the current turn — this protects against the VAD-barge-in /
|
|
4279
|
+
* pipeline-LLM race where both paths try to finalise the same logical
|
|
4280
|
+
* turn and the second would otherwise push a phantom entry with
|
|
4281
|
+
* ``user_text=''``. The caller treats ``null`` as "nothing to emit";
|
|
4282
|
+
* ``emitTurnMetrics`` is already null-safe.
|
|
4283
|
+
*/
|
|
4074
4284
|
recordTurnComplete(agentText) {
|
|
4285
|
+
if (this._turnAlreadyClosed) return null;
|
|
4075
4286
|
const latency = this._computeTurnLatency();
|
|
4076
4287
|
const turn = {
|
|
4077
4288
|
turn_index: this._turns.length,
|
|
@@ -4084,13 +4295,23 @@ var init_metrics = __esm({
|
|
|
4084
4295
|
};
|
|
4085
4296
|
this._turns.push(turn);
|
|
4086
4297
|
this._resetTurnState();
|
|
4298
|
+
this._turnAlreadyClosed = true;
|
|
4087
4299
|
this._eventBus?.emit("turn_ended", { callId: this.callId, turn });
|
|
4088
4300
|
this._eventBus?.emit("metrics_collected", { callId: this.callId, turn });
|
|
4089
4301
|
return turn;
|
|
4090
4302
|
}
|
|
4091
|
-
/**
|
|
4303
|
+
/**
|
|
4304
|
+
* Close the current turn as interrupted (barge-in) and return the
|
|
4305
|
+
* recorded metrics. Returns ``null`` when no turn is open, OR when
|
|
4306
|
+
* ``recordTurnComplete`` has already finalised the current turn —
|
|
4307
|
+
* bidirectional parity with the guard at the top of
|
|
4308
|
+
* ``recordTurnComplete``. Prevents an out-of-order interruption (e.g.
|
|
4309
|
+
* a future refactor that reorders the bargein + LLM-unwind paths)
|
|
4310
|
+
* from overwriting a turn that the complete path already emitted.
|
|
4311
|
+
*/
|
|
4092
4312
|
recordTurnInterrupted() {
|
|
4093
4313
|
if (this._turnStart === null) return null;
|
|
4314
|
+
if (this._turnAlreadyClosed) return null;
|
|
4094
4315
|
const latency = this._computeTurnLatency();
|
|
4095
4316
|
const turn = {
|
|
4096
4317
|
turn_index: this._turns.length,
|
|
@@ -4105,6 +4326,7 @@ var init_metrics = __esm({
|
|
|
4105
4326
|
this._eventBus?.emit("turn_ended", { callId: this.callId, turn });
|
|
4106
4327
|
this._eventBus?.emit("metrics_collected", { callId: this.callId, turn });
|
|
4107
4328
|
this._resetTurnState();
|
|
4329
|
+
this._turnAlreadyClosed = true;
|
|
4108
4330
|
this._turnCommittedMono = null;
|
|
4109
4331
|
this._endpointSignalAt = null;
|
|
4110
4332
|
return turn;
|
|
@@ -5615,10 +5837,10 @@ function mergeDefs(...defs) {
|
|
|
5615
5837
|
function cloneDef(schema) {
|
|
5616
5838
|
return mergeDefs(schema._zod.def);
|
|
5617
5839
|
}
|
|
5618
|
-
function getElementAtPath(obj,
|
|
5619
|
-
if (!
|
|
5840
|
+
function getElementAtPath(obj, path6) {
|
|
5841
|
+
if (!path6)
|
|
5620
5842
|
return obj;
|
|
5621
|
-
return
|
|
5843
|
+
return path6.reduce((acc, key) => acc?.[key], obj);
|
|
5622
5844
|
}
|
|
5623
5845
|
function promiseAllObject(promisesObj) {
|
|
5624
5846
|
const keys = Object.keys(promisesObj);
|
|
@@ -5946,11 +6168,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
5946
6168
|
}
|
|
5947
6169
|
return false;
|
|
5948
6170
|
}
|
|
5949
|
-
function prefixIssues(
|
|
6171
|
+
function prefixIssues(path6, issues) {
|
|
5950
6172
|
return issues.map((iss) => {
|
|
5951
6173
|
var _a3;
|
|
5952
6174
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
5953
|
-
iss.path.unshift(
|
|
6175
|
+
iss.path.unshift(path6);
|
|
5954
6176
|
return iss;
|
|
5955
6177
|
});
|
|
5956
6178
|
}
|
|
@@ -6169,16 +6391,16 @@ function flattenError(error2, mapper = (issue2) => issue2.message) {
|
|
|
6169
6391
|
}
|
|
6170
6392
|
function formatError(error2, mapper = (issue2) => issue2.message) {
|
|
6171
6393
|
const fieldErrors = { _errors: [] };
|
|
6172
|
-
const processError = (error3,
|
|
6394
|
+
const processError = (error3, path6 = []) => {
|
|
6173
6395
|
for (const issue2 of error3.issues) {
|
|
6174
6396
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
6175
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
6397
|
+
issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
|
|
6176
6398
|
} else if (issue2.code === "invalid_key") {
|
|
6177
|
-
processError({ issues: issue2.issues }, [...
|
|
6399
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
6178
6400
|
} else if (issue2.code === "invalid_element") {
|
|
6179
|
-
processError({ issues: issue2.issues }, [...
|
|
6401
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
6180
6402
|
} else {
|
|
6181
|
-
const fullpath = [...
|
|
6403
|
+
const fullpath = [...path6, ...issue2.path];
|
|
6182
6404
|
if (fullpath.length === 0) {
|
|
6183
6405
|
fieldErrors._errors.push(mapper(issue2));
|
|
6184
6406
|
} else {
|
|
@@ -13684,7 +13906,7 @@ var init_protocol = __esm({
|
|
|
13684
13906
|
return;
|
|
13685
13907
|
}
|
|
13686
13908
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
13687
|
-
await new Promise((
|
|
13909
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
13688
13910
|
options?.signal?.throwIfAborted();
|
|
13689
13911
|
}
|
|
13690
13912
|
} catch (error2) {
|
|
@@ -13701,7 +13923,7 @@ var init_protocol = __esm({
|
|
|
13701
13923
|
*/
|
|
13702
13924
|
request(request, resultSchema, options) {
|
|
13703
13925
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
13704
|
-
return new Promise((
|
|
13926
|
+
return new Promise((resolve2, reject) => {
|
|
13705
13927
|
const earlyReject = (error2) => {
|
|
13706
13928
|
reject(error2);
|
|
13707
13929
|
};
|
|
@@ -13779,7 +14001,7 @@ var init_protocol = __esm({
|
|
|
13779
14001
|
if (!parseResult.success) {
|
|
13780
14002
|
reject(parseResult.error);
|
|
13781
14003
|
} else {
|
|
13782
|
-
|
|
14004
|
+
resolve2(parseResult.data);
|
|
13783
14005
|
}
|
|
13784
14006
|
} catch (error2) {
|
|
13785
14007
|
reject(error2);
|
|
@@ -14040,12 +14262,12 @@ var init_protocol = __esm({
|
|
|
14040
14262
|
}
|
|
14041
14263
|
} catch {
|
|
14042
14264
|
}
|
|
14043
|
-
return new Promise((
|
|
14265
|
+
return new Promise((resolve2, reject) => {
|
|
14044
14266
|
if (signal.aborted) {
|
|
14045
14267
|
reject(new McpError(ErrorCode2.InvalidRequest, "Request cancelled"));
|
|
14046
14268
|
return;
|
|
14047
14269
|
}
|
|
14048
|
-
const timeoutId = setTimeout(
|
|
14270
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
14049
14271
|
signal.addEventListener("abort", () => {
|
|
14050
14272
|
clearTimeout(timeoutId);
|
|
14051
14273
|
reject(new McpError(ErrorCode2.InvalidRequest, "Request cancelled"));
|
|
@@ -17093,7 +17315,7 @@ var require_compile = __commonJS({
|
|
|
17093
17315
|
const schOrFunc = root.refs[ref];
|
|
17094
17316
|
if (schOrFunc)
|
|
17095
17317
|
return schOrFunc;
|
|
17096
|
-
let _sch =
|
|
17318
|
+
let _sch = resolve2.call(this, root, ref);
|
|
17097
17319
|
if (_sch === void 0) {
|
|
17098
17320
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
17099
17321
|
const { schemaId } = this.opts;
|
|
@@ -17120,7 +17342,7 @@ var require_compile = __commonJS({
|
|
|
17120
17342
|
function sameSchemaEnv(s1, s2) {
|
|
17121
17343
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
17122
17344
|
}
|
|
17123
|
-
function
|
|
17345
|
+
function resolve2(root, ref) {
|
|
17124
17346
|
let sch;
|
|
17125
17347
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
17126
17348
|
ref = sch;
|
|
@@ -17339,8 +17561,8 @@ var require_utils = __commonJS({
|
|
|
17339
17561
|
}
|
|
17340
17562
|
return ind;
|
|
17341
17563
|
}
|
|
17342
|
-
function removeDotSegments(
|
|
17343
|
-
let input =
|
|
17564
|
+
function removeDotSegments(path6) {
|
|
17565
|
+
let input = path6;
|
|
17344
17566
|
const output = [];
|
|
17345
17567
|
let nextSlash = -1;
|
|
17346
17568
|
let len = 0;
|
|
@@ -17593,8 +17815,8 @@ var require_schemes = __commonJS({
|
|
|
17593
17815
|
wsComponent.secure = void 0;
|
|
17594
17816
|
}
|
|
17595
17817
|
if (wsComponent.resourceName) {
|
|
17596
|
-
const [
|
|
17597
|
-
wsComponent.path =
|
|
17818
|
+
const [path6, query] = wsComponent.resourceName.split("?");
|
|
17819
|
+
wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
|
|
17598
17820
|
wsComponent.query = query;
|
|
17599
17821
|
wsComponent.resourceName = void 0;
|
|
17600
17822
|
}
|
|
@@ -17754,7 +17976,7 @@ var require_fast_uri = __commonJS({
|
|
|
17754
17976
|
}
|
|
17755
17977
|
return uri;
|
|
17756
17978
|
}
|
|
17757
|
-
function
|
|
17979
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
17758
17980
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
17759
17981
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
17760
17982
|
schemelessOptions.skipEscape = true;
|
|
@@ -18012,7 +18234,7 @@ var require_fast_uri = __commonJS({
|
|
|
18012
18234
|
var fastUri = {
|
|
18013
18235
|
SCHEMES,
|
|
18014
18236
|
normalize,
|
|
18015
|
-
resolve,
|
|
18237
|
+
resolve: resolve2,
|
|
18016
18238
|
resolveComponent,
|
|
18017
18239
|
equal,
|
|
18018
18240
|
serialize,
|
|
@@ -21033,12 +21255,12 @@ var require_dist = __commonJS({
|
|
|
21033
21255
|
throw new Error(`Unknown format "${name}"`);
|
|
21034
21256
|
return f;
|
|
21035
21257
|
};
|
|
21036
|
-
function addFormats(ajv, list,
|
|
21258
|
+
function addFormats(ajv, list, fs6, exportName) {
|
|
21037
21259
|
var _a3;
|
|
21038
21260
|
var _b;
|
|
21039
21261
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
21040
21262
|
for (const f of list)
|
|
21041
|
-
ajv.addFormat(f,
|
|
21263
|
+
ajv.addFormat(f, fs6[f]);
|
|
21042
21264
|
}
|
|
21043
21265
|
module2.exports = exports2 = formatsPlugin;
|
|
21044
21266
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -24732,7 +24954,7 @@ var require_tensor_factory_impl = __commonJS({
|
|
|
24732
24954
|
throw new Error("Can not access image data");
|
|
24733
24955
|
}
|
|
24734
24956
|
} else if (isString) {
|
|
24735
|
-
return new Promise((
|
|
24957
|
+
return new Promise((resolve2, reject) => {
|
|
24736
24958
|
const canvas = createCanvas();
|
|
24737
24959
|
const context = createCanvasContext(canvas);
|
|
24738
24960
|
if (!image || !context) {
|
|
@@ -24748,7 +24970,7 @@ var require_tensor_factory_impl = __commonJS({
|
|
|
24748
24970
|
const img = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
24749
24971
|
bufferToTensorOptions.height = canvas.height;
|
|
24750
24972
|
bufferToTensorOptions.width = canvas.width;
|
|
24751
|
-
|
|
24973
|
+
resolve2((0, exports2.bufferToTensor)(img.data, bufferToTensorOptions));
|
|
24752
24974
|
};
|
|
24753
24975
|
});
|
|
24754
24976
|
} else {
|
|
@@ -25776,10 +25998,10 @@ var require_backend2 = __commonJS({
|
|
|
25776
25998
|
endProfiling() {
|
|
25777
25999
|
}
|
|
25778
26000
|
async run(feeds, fetches, options) {
|
|
25779
|
-
return new Promise((
|
|
26001
|
+
return new Promise((resolve2, reject) => {
|
|
25780
26002
|
setImmediate(() => {
|
|
25781
26003
|
try {
|
|
25782
|
-
|
|
26004
|
+
resolve2(__classPrivateFieldGet(this, _OnnxruntimeSessionHandler_inferenceSession, "f").run(feeds, fetches, options));
|
|
25783
26005
|
} catch (e) {
|
|
25784
26006
|
reject(e);
|
|
25785
26007
|
}
|
|
@@ -25793,10 +26015,10 @@ var require_backend2 = __commonJS({
|
|
|
25793
26015
|
return Promise.resolve();
|
|
25794
26016
|
}
|
|
25795
26017
|
async createInferenceSessionHandler(pathOrBuffer, options) {
|
|
25796
|
-
return new Promise((
|
|
26018
|
+
return new Promise((resolve2, reject) => {
|
|
25797
26019
|
setImmediate(() => {
|
|
25798
26020
|
try {
|
|
25799
|
-
|
|
26021
|
+
resolve2(new OnnxruntimeSessionHandler(pathOrBuffer, options || {}));
|
|
25800
26022
|
} catch (e) {
|
|
25801
26023
|
reject(e);
|
|
25802
26024
|
}
|
|
@@ -25873,20 +26095,20 @@ function resolveModuleDirs() {
|
|
|
25873
26095
|
}
|
|
25874
26096
|
try {
|
|
25875
26097
|
const url2 = importMetaUrl;
|
|
25876
|
-
if (url2) candidates.push(
|
|
26098
|
+
if (url2) candidates.push(path3.dirname((0, import_node_url.fileURLToPath)(url2)));
|
|
25877
26099
|
} catch {
|
|
25878
26100
|
}
|
|
25879
26101
|
try {
|
|
25880
26102
|
const url2 = importMetaUrl;
|
|
25881
26103
|
if (url2) {
|
|
25882
26104
|
const req = (0, import_node_module.createRequire)(url2);
|
|
25883
|
-
candidates.push(
|
|
26105
|
+
candidates.push(path3.dirname(req.resolve("getpatter/package.json")));
|
|
25884
26106
|
}
|
|
25885
26107
|
} catch {
|
|
25886
26108
|
}
|
|
25887
26109
|
try {
|
|
25888
|
-
const req = (0, import_node_module.createRequire)(
|
|
25889
|
-
candidates.push(
|
|
26110
|
+
const req = (0, import_node_module.createRequire)(path3.join(process.cwd(), "package.json"));
|
|
26111
|
+
candidates.push(path3.dirname(req.resolve("getpatter/package.json")));
|
|
25890
26112
|
} catch {
|
|
25891
26113
|
}
|
|
25892
26114
|
candidates.push(process.cwd());
|
|
@@ -25895,13 +26117,13 @@ function resolveModuleDirs() {
|
|
|
25895
26117
|
function resolveDefaultModelPath() {
|
|
25896
26118
|
for (const dir of MODULE_DIRS) {
|
|
25897
26119
|
const candidates = [
|
|
25898
|
-
|
|
25899
|
-
|
|
25900
|
-
|
|
26120
|
+
path3.join(dir, "resources", "silero_vad.onnx"),
|
|
26121
|
+
path3.join(dir, "..", "resources", "silero_vad.onnx"),
|
|
26122
|
+
path3.join(dir, "dist", "resources", "silero_vad.onnx")
|
|
25901
26123
|
];
|
|
25902
|
-
for (const c of candidates) if (
|
|
26124
|
+
for (const c of candidates) if (fs3.existsSync(c)) return c;
|
|
25903
26125
|
}
|
|
25904
|
-
return
|
|
26126
|
+
return path3.join(MODULE_DIRS[0] ?? process.cwd(), "resources", "silero_vad.onnx");
|
|
25905
26127
|
}
|
|
25906
26128
|
function classifyOnnxError(err) {
|
|
25907
26129
|
const msg = err?.message ?? String(err);
|
|
@@ -25919,7 +26141,7 @@ async function loadOnnxRuntime() {
|
|
|
25919
26141
|
firstErr = e;
|
|
25920
26142
|
}
|
|
25921
26143
|
try {
|
|
25922
|
-
const req = (0, import_node_module.createRequire)(
|
|
26144
|
+
const req = (0, import_node_module.createRequire)(path3.join(process.cwd(), "package.json"));
|
|
25923
26145
|
return req("onnxruntime-node");
|
|
25924
26146
|
} catch (secondErr) {
|
|
25925
26147
|
const importClass = classifyOnnxError(firstErr);
|
|
@@ -25955,14 +26177,14 @@ ${remedy}
|
|
|
25955
26177
|
throw err;
|
|
25956
26178
|
}
|
|
25957
26179
|
}
|
|
25958
|
-
var import_node_module,
|
|
26180
|
+
var import_node_module, fs3, path3, import_node_url, SUPPORTED_SAMPLE_RATES, MODULE_DIRS, DEFAULT_MODEL_PATH, ExpFilter, OnnxModel, SileroVAD;
|
|
25959
26181
|
var init_silero_vad = __esm({
|
|
25960
26182
|
"src/providers/silero-vad.ts"() {
|
|
25961
26183
|
"use strict";
|
|
25962
26184
|
init_cjs_shims();
|
|
25963
26185
|
import_node_module = require("module");
|
|
25964
|
-
|
|
25965
|
-
|
|
26186
|
+
fs3 = __toESM(require("fs"));
|
|
26187
|
+
path3 = __toESM(require("path"));
|
|
25966
26188
|
import_node_url = require("url");
|
|
25967
26189
|
SUPPORTED_SAMPLE_RATES = [8e3, 16e3];
|
|
25968
26190
|
MODULE_DIRS = resolveModuleDirs();
|
|
@@ -26127,6 +26349,19 @@ var init_silero_vad = __esm({
|
|
|
26127
26349
|
static forPhoneCall(options = {}) {
|
|
26128
26350
|
return _SileroVAD.load({
|
|
26129
26351
|
sampleRate: 16e3,
|
|
26352
|
+
// Telephony bumps the activation threshold from the upstream
|
|
26353
|
+
// 0.5 → 0.8 (with deactivation 0.65) so background voices and
|
|
26354
|
+
// low-volume audio in the caller's room don't trip barge-in.
|
|
26355
|
+
// Near-mic speech typically scores 0.85-0.98 on Silero — above
|
|
26356
|
+
// 0.8 — while a distant second speaker through a phone's noise-
|
|
26357
|
+
// suppression pipeline lands around 0.4-0.6 and is now correctly
|
|
26358
|
+
// ignored. Bumped twice during 2026-05-20 acceptance: first 0.5
|
|
26359
|
+
// → 0.7 (still triggered on quiet voices), then 0.7 → 0.8.
|
|
26360
|
+
// Trade-off: a whispered legitimate input may not trigger;
|
|
26361
|
+
// typical phone-call speakers are unaffected. Pass an explicit
|
|
26362
|
+
// ``activationThreshold`` to override per call site.
|
|
26363
|
+
activationThreshold: 0.8,
|
|
26364
|
+
deactivationThreshold: 0.65,
|
|
26130
26365
|
...options
|
|
26131
26366
|
});
|
|
26132
26367
|
}
|
|
@@ -26587,7 +26822,23 @@ var init_stream_handler = __esm({
|
|
|
26587
26822
|
".",
|
|
26588
26823
|
"bye",
|
|
26589
26824
|
"right",
|
|
26590
|
-
"cool"
|
|
26825
|
+
"cool",
|
|
26826
|
+
// Whisper YouTube-caption hallucinations
|
|
26827
|
+
"thank you for watching",
|
|
26828
|
+
"thanks for watching",
|
|
26829
|
+
"thank you for watching!",
|
|
26830
|
+
"thanks for watching!",
|
|
26831
|
+
"thank you so much for watching",
|
|
26832
|
+
"thanks for listening",
|
|
26833
|
+
"please subscribe",
|
|
26834
|
+
"subscribe",
|
|
26835
|
+
"music",
|
|
26836
|
+
"[music]",
|
|
26837
|
+
"\u266A",
|
|
26838
|
+
"[no audio]",
|
|
26839
|
+
"[silence]",
|
|
26840
|
+
"[blank_audio]",
|
|
26841
|
+
"(silence)"
|
|
26591
26842
|
]);
|
|
26592
26843
|
StreamHandler = class _StreamHandler {
|
|
26593
26844
|
deps;
|
|
@@ -26713,13 +26964,17 @@ var init_stream_handler = __esm({
|
|
|
26713
26964
|
* Same as the AEC variant but for deployments where AEC is OFF
|
|
26714
26965
|
* (default on PSTN — Twilio/Telnyx). Without an adaptive filter to
|
|
26715
26966
|
* converge, the only justification for a gate is anti-flicker on
|
|
26716
|
-
* micro-events (cough, click). 100
|
|
26717
|
-
*
|
|
26718
|
-
* the
|
|
26719
|
-
*
|
|
26720
|
-
*
|
|
26721
|
-
|
|
26722
|
-
|
|
26967
|
+
* micro-events (cough, click). Raised 100 → 500 ms on 2026-05-19
|
|
26968
|
+
* after the 0.6.2 acceptance run showed a phantom VAD speech_start
|
|
26969
|
+
* firing on the very first inbound frame (~500 ms into the call,
|
|
26970
|
+
* which is past a 100 ms gate). The phantom barge-in cancelled the
|
|
26971
|
+
* prewarmed firstMessage, the user heard a clipped (graffiante)
|
|
26972
|
+
* audio fragment, and the SDK left ``_turnAlreadyClosed=true`` so
|
|
26973
|
+
* subsequent ``recordTurnComplete`` calls were no-ops. 500 ms
|
|
26974
|
+
* filters those phantoms while still letting a real interruption
|
|
26975
|
+
* land within half a second of agent onset.
|
|
26976
|
+
*/
|
|
26977
|
+
static MIN_AGENT_SPEAKING_MS_BEFORE_BARGE_IN_NO_AEC = 500;
|
|
26723
26978
|
/** Handle for the pending grace-period timer, so it can be cleared on cleanup. */
|
|
26724
26979
|
graceTimer = null;
|
|
26725
26980
|
/**
|
|
@@ -26759,30 +27014,12 @@ var init_stream_handler = __esm({
|
|
|
26759
27014
|
* coexist without name collisions even when firstMessage finishes while
|
|
26760
27015
|
* a Realtime turn is still streaming.
|
|
26761
27016
|
*/
|
|
26762
|
-
firstMessageMarkCounter
|
|
26763
|
-
|
|
26764
|
-
|
|
26765
|
-
|
|
26766
|
-
|
|
26767
|
-
|
|
26768
|
-
* — vs. ~2-5 s with the previous burst-send code, which was the
|
|
26769
|
-
* root cause of "firstMessage non interrompibile". Higher values
|
|
26770
|
-
* smooth playback under jittery RTT (each mark echo adds ~150-250 ms
|
|
26771
|
-
* RTT on PSTN) at the cost of longer barge-in latency; lower values
|
|
26772
|
-
* risk under-buffering. 3 hit the smallest barge-in cap without
|
|
26773
|
-
* audible gaps in 2026-05 acceptance.
|
|
26774
|
-
*/
|
|
26775
|
-
static FIRST_MESSAGE_MARK_WINDOW = 3;
|
|
26776
|
-
/**
|
|
26777
|
-
* Per-chunk soft timeout (ms) while awaiting a mark echo. Twilio's
|
|
26778
|
-
* mark echoes typically arrive within 100-250 ms of audio playback.
|
|
26779
|
-
* Capping at 500 ms guards against carriers (or test doubles) that
|
|
26780
|
-
* never echo — without it a stalled echo would deadlock the loop and
|
|
26781
|
-
* the agent would freeze mid-utterance. On timeout we drop the
|
|
26782
|
-
* waiter from the queue and continue: playout may glitch by one
|
|
26783
|
-
* chunk but the call stays alive.
|
|
26784
|
-
*/
|
|
26785
|
-
static MARK_AWAIT_TIMEOUT_MS = 500;
|
|
27017
|
+
// firstMessageMarkCounter / FIRST_MESSAGE_MARK_WINDOW /
|
|
27018
|
+
// MARK_AWAIT_TIMEOUT_MS were retired with the move to the Twilio-FIFO-
|
|
27019
|
+
// trusts model (sendPacedFirstMessageBytes no longer emits marks).
|
|
27020
|
+
// Marks are still consumed via ``onMark`` for any adapter that wants
|
|
27021
|
+
// to round-trip one, but the firstMessage path no longer back-pressures
|
|
27022
|
+
// on them.
|
|
26786
27023
|
/**
|
|
26787
27024
|
* Minimum drain window (ms) between a ``cancelSpeaking`` and the next
|
|
26788
27025
|
* ``beginSpeaking``. 150 ms covers a typical PSTN jitter buffer drain
|
|
@@ -26847,6 +27084,14 @@ var init_stream_handler = __esm({
|
|
|
26847
27084
|
} catch {
|
|
26848
27085
|
}
|
|
26849
27086
|
}
|
|
27087
|
+
const ttsCancelable = this.tts;
|
|
27088
|
+
if (typeof ttsCancelable?.cancelActiveStream === "function") {
|
|
27089
|
+
try {
|
|
27090
|
+
ttsCancelable.cancelActiveStream();
|
|
27091
|
+
} catch (err) {
|
|
27092
|
+
getLogger().debug(`TTS cancelActiveStream raised: ${String(err)}`);
|
|
27093
|
+
}
|
|
27094
|
+
}
|
|
26850
27095
|
}
|
|
26851
27096
|
/**
|
|
26852
27097
|
* Resolve every entry in ``pendingMarks`` and empty the queue. Idempotent
|
|
@@ -26863,56 +27108,19 @@ var init_stream_handler = __esm({
|
|
|
26863
27108
|
}
|
|
26864
27109
|
this.pendingMarks.length = 0;
|
|
26865
27110
|
}
|
|
27111
|
+
// Mark-based back-pressure (sendMarkAwaitable / waitForMarkWindow)
|
|
27112
|
+
// was removed when sendPacedFirstMessageBytes switched to the
|
|
27113
|
+
// Twilio-FIFO-trusts model — see that method's doc comment for
|
|
27114
|
+
// rationale. ``pendingMarks`` and ``onMark`` are still kept so an
|
|
27115
|
+
// adapter that wants to round-trip a mark for some other purpose can
|
|
27116
|
+
// still do so without breaking the firstMessage path.
|
|
26866
27117
|
/**
|
|
26867
|
-
*
|
|
26868
|
-
*
|
|
26869
|
-
*
|
|
26870
|
-
*
|
|
26871
|
-
*
|
|
26872
|
-
|
|
26873
|
-
sendMarkAwaitable() {
|
|
26874
|
-
if (this.deps.bridge.telephonyProvider !== "twilio") return null;
|
|
26875
|
-
this.firstMessageMarkCounter += 1;
|
|
26876
|
-
const markName = `fm_${this.firstMessageMarkCounter}`;
|
|
26877
|
-
let resolve;
|
|
26878
|
-
const promise = new Promise((r) => {
|
|
26879
|
-
resolve = r;
|
|
26880
|
-
});
|
|
26881
|
-
this.pendingMarks.push({ name: markName, resolve, promise });
|
|
26882
|
-
try {
|
|
26883
|
-
this.deps.bridge.sendMark(this.ws, markName, this.streamSid);
|
|
26884
|
-
} catch (err) {
|
|
26885
|
-
getLogger().debug(`sendMark failed (${markName}): ${String(err)}`);
|
|
26886
|
-
const idx = this.pendingMarks.findIndex((m) => m.name === markName);
|
|
26887
|
-
if (idx >= 0) this.pendingMarks.splice(idx, 1);
|
|
26888
|
-
return Promise.resolve();
|
|
26889
|
-
}
|
|
26890
|
-
return promise;
|
|
26891
|
-
}
|
|
26892
|
-
/**
|
|
26893
|
-
* If the in-flight mark queue is at or above ``FIRST_MESSAGE_MARK_WINDOW``
|
|
26894
|
-
* entries, wait for the oldest entry to clear (mark echoed, agent
|
|
26895
|
-
* cancelled, or per-mark timeout). Repeats until the queue depth is
|
|
26896
|
-
* within the window — under high RTT the carrier may have several
|
|
26897
|
-
* marks queued and we want every loop iteration to be naturally back-
|
|
26898
|
-
* pressured by playback.
|
|
26899
|
-
*/
|
|
26900
|
-
async waitForMarkWindow() {
|
|
26901
|
-
while (this.isSpeaking && this.pendingMarks.length >= _StreamHandler.FIRST_MESSAGE_MARK_WINDOW) {
|
|
26902
|
-
const oldest = this.pendingMarks[0];
|
|
26903
|
-
const timeout = new Promise(
|
|
26904
|
-
(resolve) => setTimeout(resolve, _StreamHandler.MARK_AWAIT_TIMEOUT_MS)
|
|
26905
|
-
);
|
|
26906
|
-
await Promise.race([oldest.promise, timeout]);
|
|
26907
|
-
if (this.pendingMarks[0] === oldest) {
|
|
26908
|
-
this.pendingMarks.shift();
|
|
26909
|
-
}
|
|
26910
|
-
}
|
|
26911
|
-
}
|
|
26912
|
-
/**
|
|
26913
|
-
* Bytes-per-millisecond for a 16 kHz PCM16 mono stream. Used by the
|
|
26914
|
-
* non-Twilio firstMessage pacing path to translate chunk size into a
|
|
26915
|
-
* playout-duration sleep. 16000 samples/sec × 2 bytes = 32 bytes/ms.
|
|
27118
|
+
* Bytes-per-millisecond for a 16 kHz PCM16 mono stream. Used by
|
|
27119
|
+
* ``sendPacedFirstMessageBytes`` to translate chunk size into a
|
|
27120
|
+
* playout-duration sleep so we never deliver faster than the carrier
|
|
27121
|
+
* can decode + play out (which manifested as severe crackling on the
|
|
27122
|
+
* HTTP-TTS path with client-side resampling). 16000 samples/sec × 2
|
|
27123
|
+
* bytes/sample = 32 bytes/ms.
|
|
26916
27124
|
*/
|
|
26917
27125
|
static PCM16_16K_BYTES_PER_MS = 32;
|
|
26918
27126
|
/** Cancel and clear the pending grace timer, if any. */
|
|
@@ -27350,7 +27558,7 @@ var init_stream_handler = __esm({
|
|
|
27350
27558
|
if (activeVad && !this.vadDisabled) {
|
|
27351
27559
|
try {
|
|
27352
27560
|
const vadPromise = activeVad.processFrame(pcm16k, 16e3);
|
|
27353
|
-
const timeoutPromise = new Promise((
|
|
27561
|
+
const timeoutPromise = new Promise((resolve2) => setTimeout(() => resolve2(null), 25));
|
|
27354
27562
|
const evt = await Promise.race([vadPromise, timeoutPromise]);
|
|
27355
27563
|
if (evt) {
|
|
27356
27564
|
getLogger().info(
|
|
@@ -27486,9 +27694,21 @@ var init_stream_handler = __esm({
|
|
|
27486
27694
|
/** Handle call stop / stream end. */
|
|
27487
27695
|
/** Handle a carrier-emitted `stop` event signalling the call has ended. */
|
|
27488
27696
|
async handleStop() {
|
|
27697
|
+
if (this.llmAbort !== null) {
|
|
27698
|
+
try {
|
|
27699
|
+
this.llmAbort.abort();
|
|
27700
|
+
} catch {
|
|
27701
|
+
}
|
|
27702
|
+
}
|
|
27703
|
+
const ttsCancelable = this.tts;
|
|
27704
|
+
if (typeof ttsCancelable?.cancelActiveStream === "function") {
|
|
27705
|
+
try {
|
|
27706
|
+
ttsCancelable.cancelActiveStream();
|
|
27707
|
+
} catch {
|
|
27708
|
+
}
|
|
27709
|
+
}
|
|
27489
27710
|
this.clearPendingBargeIn();
|
|
27490
27711
|
this.drainPendingMarks();
|
|
27491
|
-
this.firstMessageMarkCounter = 0;
|
|
27492
27712
|
this.clearGraceTimer();
|
|
27493
27713
|
this.flushResamplers();
|
|
27494
27714
|
await this.closeSttOnce();
|
|
@@ -27501,9 +27721,21 @@ var init_stream_handler = __esm({
|
|
|
27501
27721
|
/** Handle WebSocket close event. */
|
|
27502
27722
|
/** Tear down adapter, STT/TTS, and per-call state when the carrier WebSocket closes. */
|
|
27503
27723
|
async handleWsClose() {
|
|
27724
|
+
if (this.llmAbort !== null) {
|
|
27725
|
+
try {
|
|
27726
|
+
this.llmAbort.abort();
|
|
27727
|
+
} catch {
|
|
27728
|
+
}
|
|
27729
|
+
}
|
|
27730
|
+
const ttsCancelable = this.tts;
|
|
27731
|
+
if (typeof ttsCancelable?.cancelActiveStream === "function") {
|
|
27732
|
+
try {
|
|
27733
|
+
ttsCancelable.cancelActiveStream();
|
|
27734
|
+
} catch {
|
|
27735
|
+
}
|
|
27736
|
+
}
|
|
27504
27737
|
this.clearPendingBargeIn();
|
|
27505
27738
|
this.drainPendingMarks();
|
|
27506
|
-
this.firstMessageMarkCounter = 0;
|
|
27507
27739
|
this.clearGraceTimer();
|
|
27508
27740
|
this.flushResamplers();
|
|
27509
27741
|
await this.closeSttOnce();
|
|
@@ -27542,13 +27774,39 @@ var init_stream_handler = __esm({
|
|
|
27542
27774
|
* Maintains a 1-byte carry across calls so unaligned HTTP chunks from
|
|
27543
27775
|
* streaming TTS providers never byte-swap the PCM16 samples downstream.
|
|
27544
27776
|
*/
|
|
27545
|
-
encodePipelineAudio(
|
|
27546
|
-
|
|
27777
|
+
encodePipelineAudio(audioChunk) {
|
|
27778
|
+
if (this.ttsOutputFormatNativeForCarrier === true) {
|
|
27779
|
+
return audioChunk.toString("base64");
|
|
27780
|
+
}
|
|
27781
|
+
const aligned = this.alignPcm16(audioChunk);
|
|
27547
27782
|
if (aligned.length === 0) return "";
|
|
27548
27783
|
const pcm8k = this.outboundResampler.process(aligned);
|
|
27549
27784
|
const mulaw = pcm16ToMulaw(pcm8k);
|
|
27550
27785
|
return mulaw.toString("base64");
|
|
27551
27786
|
}
|
|
27787
|
+
/**
|
|
27788
|
+
* Cached result of ``isTtsOutputFormatNativeForCarrier()`` — settled
|
|
27789
|
+
* once at ``initPipeline`` time after ``setTelephonyCarrier`` has run
|
|
27790
|
+
* on the TTS adapter. Stable for the call lifetime: changes to the
|
|
27791
|
+
* adapter's output format mid-call would NOT flip this. ``true`` means
|
|
27792
|
+
* ``encodePipelineAudio`` can take the bypass path.
|
|
27793
|
+
*/
|
|
27794
|
+
ttsOutputFormatNativeForCarrier = false;
|
|
27795
|
+
/**
|
|
27796
|
+
* Probe whether the TTS adapter is configured to emit bytes already in
|
|
27797
|
+
* the carrier's wire codec. Currently: Twilio expects ``ulaw_8000``,
|
|
27798
|
+
* Telnyx expects ``pcm_16000`` (no client transcode in either case if
|
|
27799
|
+
* matched). Anything else takes the resample-and-encode path.
|
|
27800
|
+
*/
|
|
27801
|
+
isTtsOutputFormatNativeForCarrier() {
|
|
27802
|
+
if (!this.tts) return false;
|
|
27803
|
+
const fmt = this.tts.outputFormat;
|
|
27804
|
+
if (typeof fmt !== "string") return false;
|
|
27805
|
+
const carrier = this.deps.bridge.telephonyProvider;
|
|
27806
|
+
if (carrier === "twilio") return fmt === "ulaw_8000";
|
|
27807
|
+
if (carrier === "telnyx") return fmt === "pcm_16000";
|
|
27808
|
+
return false;
|
|
27809
|
+
}
|
|
27552
27810
|
/**
|
|
27553
27811
|
* Prepend any carry byte from the previous chunk, return the even-length
|
|
27554
27812
|
* portion, and stash the final odd byte (if any) for the next call.
|
|
@@ -27559,17 +27817,11 @@ var init_stream_handler = __esm({
|
|
|
27559
27817
|
this.ttsByteCarry = alignedLen < combined.length ? combined.subarray(alignedLen) : null;
|
|
27560
27818
|
return combined.subarray(0, alignedLen);
|
|
27561
27819
|
}
|
|
27562
|
-
/**
|
|
27563
|
-
* 40 ms @ 16 kHz mono PCM16 = 1280 bytes. Sized to mirror the smallest
|
|
27564
|
-
* live-TTS chunk boundary so cancel granularity (mark/clear bookkeeping)
|
|
27565
|
-
* is identical regardless of whether the firstMessage came from the
|
|
27566
|
-
* prewarm cache or a live ``tts.synthesizeStream`` stream.
|
|
27567
|
-
*/
|
|
27568
|
-
static PREWARM_CHUNK_BYTES = 1280;
|
|
27569
27820
|
/**
|
|
27570
27821
|
* Stream a cached firstMessage buffer in pacing-friendly chunks.
|
|
27571
27822
|
*
|
|
27572
|
-
* Splits ``prewarmBytes`` into
|
|
27823
|
+
* Splits ``prewarmBytes`` into 20 ms slices (matching Twilio's PSTN
|
|
27824
|
+
* frame quantum) and
|
|
27573
27825
|
* forwards each through ``deps.bridge.sendAudio`` exactly like the
|
|
27574
27826
|
* live TTS path does — preserving Twilio mark/clear granularity. A
|
|
27575
27827
|
* single multi-second sendAudio call would push the whole intro into
|
|
@@ -27585,7 +27837,7 @@ var init_stream_handler = __esm({
|
|
|
27585
27837
|
return this.sendPacedFirstMessageBytes(prewarmBytes);
|
|
27586
27838
|
}
|
|
27587
27839
|
/**
|
|
27588
|
-
* Iterate ``bytes``
|
|
27840
|
+
* Iterate ``bytes`` in 20 ms slices (Twilio PSTN frame quantum) and
|
|
27589
27841
|
* forward each via ``deps.bridge.sendAudio`` with mark-gated pacing
|
|
27590
27842
|
* (Twilio) or playout-time-based pacing (Telnyx). Caps the carrier-
|
|
27591
27843
|
* side buffer at ``FIRST_MESSAGE_MARK_WINDOW`` chunks so a barge-in's
|
|
@@ -27602,30 +27854,20 @@ var init_stream_handler = __esm({
|
|
|
27602
27854
|
*/
|
|
27603
27855
|
async sendPacedFirstMessageBytes(bytes) {
|
|
27604
27856
|
if (this.pendingMarks.length > 0) this.drainPendingMarks();
|
|
27605
|
-
this.firstMessageMarkCounter = 0;
|
|
27606
27857
|
let firstChunkSent = false;
|
|
27607
|
-
|
|
27608
|
-
|
|
27858
|
+
const PSTN_FRAME_MS = 20;
|
|
27859
|
+
const bytesPerMs = this.ttsOutputFormatNativeForCarrier ? 8 : _StreamHandler.PCM16_16K_BYTES_PER_MS;
|
|
27860
|
+
const sliceBytes = bytesPerMs * PSTN_FRAME_MS;
|
|
27861
|
+
for (let i = 0; i < bytes.length; i += sliceBytes) {
|
|
27609
27862
|
if (!this.isSpeaking) break;
|
|
27610
|
-
|
|
27611
|
-
if (!this.isSpeaking) break;
|
|
27612
|
-
const chunk = bytes.subarray(i, i + _StreamHandler.PREWARM_CHUNK_BYTES);
|
|
27863
|
+
const chunk = bytes.subarray(i, i + sliceBytes);
|
|
27613
27864
|
if (!firstChunkSent) firstChunkSent = true;
|
|
27614
|
-
if (this.aec
|
|
27865
|
+
if (this.aec && !this.ttsOutputFormatNativeForCarrier) {
|
|
27866
|
+
this.aec.pushFarEnd(chunk);
|
|
27867
|
+
}
|
|
27615
27868
|
const encoded = this.encodePipelineAudio(chunk);
|
|
27616
27869
|
this.deps.bridge.sendAudio(this.ws, encoded, this.streamSid);
|
|
27617
27870
|
this.markFirstAudioSent();
|
|
27618
|
-
const markPromise = this.sendMarkAwaitable();
|
|
27619
|
-
if (!initialFillComplete && this.pendingMarks.length >= _StreamHandler.FIRST_MESSAGE_MARK_WINDOW) {
|
|
27620
|
-
initialFillComplete = true;
|
|
27621
|
-
}
|
|
27622
|
-
if (markPromise === null || initialFillComplete) {
|
|
27623
|
-
const playoutMs = Math.max(
|
|
27624
|
-
1,
|
|
27625
|
-
Math.floor(chunk.length / _StreamHandler.PCM16_16K_BYTES_PER_MS)
|
|
27626
|
-
);
|
|
27627
|
-
await new Promise((resolve) => setTimeout(resolve, playoutMs));
|
|
27628
|
-
}
|
|
27629
27871
|
}
|
|
27630
27872
|
return firstChunkSent;
|
|
27631
27873
|
}
|
|
@@ -27645,6 +27887,12 @@ var init_stream_handler = __esm({
|
|
|
27645
27887
|
getLogger().debug(`TTS setTelephonyCarrier failed (${label}): ${String(e)}`);
|
|
27646
27888
|
}
|
|
27647
27889
|
}
|
|
27890
|
+
this.ttsOutputFormatNativeForCarrier = this.isTtsOutputFormatNativeForCarrier();
|
|
27891
|
+
if (this.ttsOutputFormatNativeForCarrier) {
|
|
27892
|
+
getLogger().debug(
|
|
27893
|
+
`TTS outputFormat matches ${this.deps.bridge.telephonyProvider} wire codec \u2014 bypassing client-side transcode`
|
|
27894
|
+
);
|
|
27895
|
+
}
|
|
27648
27896
|
}
|
|
27649
27897
|
if (!this.stt) {
|
|
27650
27898
|
getLogger().debug(`Pipeline mode (${label}): no STT configured`);
|
|
@@ -28347,16 +28595,49 @@ var init_stream_handler = __esm({
|
|
|
28347
28595
|
async initRealtimeAdapter(resolvedPrompt) {
|
|
28348
28596
|
const label = this.deps.bridge.label;
|
|
28349
28597
|
this.adapter = this.deps.buildAIAdapter(resolvedPrompt);
|
|
28350
|
-
|
|
28351
|
-
|
|
28352
|
-
getLogger().debug(`AI adapter connected (${label})`);
|
|
28353
|
-
} catch (e) {
|
|
28354
|
-
getLogger().error(`AI adapter connect FAILED (${label}):`, e);
|
|
28598
|
+
let parked;
|
|
28599
|
+
if (typeof this.deps.popPrewarmedConnections === "function") {
|
|
28355
28600
|
try {
|
|
28356
|
-
|
|
28357
|
-
} catch {
|
|
28601
|
+
parked = this.deps.popPrewarmedConnections(this.callId);
|
|
28602
|
+
} catch (err) {
|
|
28603
|
+
getLogger().debug(`popPrewarmedConnections raised: ${String(err)}`);
|
|
28604
|
+
}
|
|
28605
|
+
}
|
|
28606
|
+
const parkedRealtimeWs = parked?.openaiRealtime;
|
|
28607
|
+
let adoptOk = false;
|
|
28608
|
+
if (parkedRealtimeWs !== void 0) {
|
|
28609
|
+
const adapterAny = this.adapter;
|
|
28610
|
+
const wsAlive = parkedRealtimeWs.readyState === 1;
|
|
28611
|
+
if (typeof adapterAny?.adoptWebSocket === "function" && wsAlive) {
|
|
28612
|
+
try {
|
|
28613
|
+
adapterAny.adoptWebSocket(parkedRealtimeWs);
|
|
28614
|
+
getLogger().info(
|
|
28615
|
+
`[CONNECT] callId=${this.callId} provider=openai_realtime source=adopted ms=0`
|
|
28616
|
+
);
|
|
28617
|
+
adoptOk = true;
|
|
28618
|
+
} catch (err) {
|
|
28619
|
+
getLogger().debug(`Realtime adoptWebSocket failed: ${String(err)}; falling back`);
|
|
28620
|
+
}
|
|
28621
|
+
}
|
|
28622
|
+
if (!adoptOk) {
|
|
28623
|
+
try {
|
|
28624
|
+
parkedRealtimeWs.close();
|
|
28625
|
+
} catch {
|
|
28626
|
+
}
|
|
28627
|
+
}
|
|
28628
|
+
}
|
|
28629
|
+
if (!adoptOk) {
|
|
28630
|
+
try {
|
|
28631
|
+
await this.adapter.connect();
|
|
28632
|
+
getLogger().debug(`AI adapter connected (${label})`);
|
|
28633
|
+
} catch (e) {
|
|
28634
|
+
getLogger().error(`AI adapter connect FAILED (${label}):`, e);
|
|
28635
|
+
try {
|
|
28636
|
+
await this.deps.bridge.endCall(this.callId, this.ws);
|
|
28637
|
+
} catch {
|
|
28638
|
+
}
|
|
28639
|
+
return;
|
|
28358
28640
|
}
|
|
28359
|
-
return;
|
|
28360
28641
|
}
|
|
28361
28642
|
if (this.deps.agent.firstMessage) {
|
|
28362
28643
|
this.metricsAcc.startTurn();
|
|
@@ -28476,8 +28757,21 @@ var init_stream_handler = __esm({
|
|
|
28476
28757
|
await this.emitUserSpeechEnded();
|
|
28477
28758
|
}
|
|
28478
28759
|
async onAdapterTranscriptInput(inputText) {
|
|
28760
|
+
const stripped = inputText.trim().toLowerCase();
|
|
28761
|
+
if (HALLUCINATIONS.has(stripped) || stripped === "") {
|
|
28762
|
+
getLogger().debug(
|
|
28763
|
+
`Realtime transcript_input dropped (likely Whisper hallucination on silence/echo): ${sanitizeLogValue(inputText.slice(0, 60))}`
|
|
28764
|
+
);
|
|
28765
|
+
this.userTranscriptPending = false;
|
|
28766
|
+
return;
|
|
28767
|
+
}
|
|
28479
28768
|
getLogger().debug(`User (${this.deps.bridge.label}): ${sanitizeLogValue(inputText)}`);
|
|
28480
28769
|
this.history.push({ role: "user", text: inputText, timestamp: Date.now() });
|
|
28770
|
+
if (this.adapter instanceof OpenAIRealtimeAdapter) {
|
|
28771
|
+
void this.adapter.requestResponse().catch(
|
|
28772
|
+
(err) => getLogger().debug(`Realtime requestResponse failed: ${String(err)}`)
|
|
28773
|
+
);
|
|
28774
|
+
}
|
|
28481
28775
|
if (!this.metricsAcc.turnActive) {
|
|
28482
28776
|
this.metricsAcc.startTurn();
|
|
28483
28777
|
this.currentAgentText = "";
|
|
@@ -28629,6 +28923,18 @@ var init_stream_handler = __esm({
|
|
|
28629
28923
|
await this.flushAssistantTurn(text);
|
|
28630
28924
|
}
|
|
28631
28925
|
async onAdapterSpeechInterrupt() {
|
|
28926
|
+
if (this.adapter instanceof OpenAIRealtimeAdapter) {
|
|
28927
|
+
const startedAt = this.adapter.currentResponseFirstAudioAt;
|
|
28928
|
+
if (startedAt !== null) {
|
|
28929
|
+
const elapsedMs = Date.now() - startedAt;
|
|
28930
|
+
if (elapsedMs < _StreamHandler.MIN_AGENT_SPEAKING_MS_BEFORE_BARGE_IN_NO_AEC) {
|
|
28931
|
+
getLogger().info(
|
|
28932
|
+
`Realtime barge-in suppressed (response < gate, ${elapsedMs}ms)`
|
|
28933
|
+
);
|
|
28934
|
+
return;
|
|
28935
|
+
}
|
|
28936
|
+
}
|
|
28937
|
+
}
|
|
28632
28938
|
this.deps.bridge.sendClear(this.ws, this.streamSid);
|
|
28633
28939
|
if (this.adapter instanceof OpenAIRealtimeAdapter) this.adapter.cancelResponse();
|
|
28634
28940
|
this.metricsAcc.recordTurnInterrupted();
|
|
@@ -28835,24 +29141,24 @@ var init_stream_handler = __esm({
|
|
|
28835
29141
|
|
|
28836
29142
|
// src/services/call-log.ts
|
|
28837
29143
|
function xdgDataHome() {
|
|
28838
|
-
return process.env.XDG_DATA_HOME ||
|
|
29144
|
+
return process.env.XDG_DATA_HOME || path4.join(os.homedir(), ".local", "share");
|
|
28839
29145
|
}
|
|
28840
29146
|
function platformDefaultRoot() {
|
|
28841
29147
|
if (process.platform === "darwin") {
|
|
28842
|
-
return
|
|
29148
|
+
return path4.join(os.homedir(), "Library", "Application Support", "patter");
|
|
28843
29149
|
}
|
|
28844
29150
|
if (process.platform === "win32") {
|
|
28845
29151
|
const localAppData = process.env.LOCALAPPDATA;
|
|
28846
|
-
if (localAppData) return
|
|
28847
|
-
return
|
|
29152
|
+
if (localAppData) return path4.join(localAppData, "patter");
|
|
29153
|
+
return path4.join(os.homedir(), "AppData", "Local", "patter");
|
|
28848
29154
|
}
|
|
28849
|
-
return
|
|
29155
|
+
return path4.join(xdgDataHome(), "patter");
|
|
28850
29156
|
}
|
|
28851
29157
|
function resolveLogRoot(explicit) {
|
|
28852
29158
|
const value = explicit ?? process.env.PATTER_LOG_DIR;
|
|
28853
29159
|
if (!value) return null;
|
|
28854
29160
|
if (value.trim().toLowerCase() === "auto") return platformDefaultRoot();
|
|
28855
|
-
if (value.startsWith("~")) return
|
|
29161
|
+
if (value.startsWith("~")) return path4.join(os.homedir(), value.slice(1));
|
|
28856
29162
|
return value;
|
|
28857
29163
|
}
|
|
28858
29164
|
function retentionDays() {
|
|
@@ -28863,9 +29169,9 @@ function retentionDays() {
|
|
|
28863
29169
|
return Math.max(0, parsed);
|
|
28864
29170
|
}
|
|
28865
29171
|
function redactMode() {
|
|
28866
|
-
const raw = (process.env.PATTER_LOG_REDACT_PHONE || "
|
|
29172
|
+
const raw = (process.env.PATTER_LOG_REDACT_PHONE || "full").trim().toLowerCase();
|
|
28867
29173
|
if (raw === "full" || raw === "mask" || raw === "hash_only") return raw;
|
|
28868
|
-
return "
|
|
29174
|
+
return "full";
|
|
28869
29175
|
}
|
|
28870
29176
|
function redactPhone(raw) {
|
|
28871
29177
|
if (!raw) return "";
|
|
@@ -28881,9 +29187,9 @@ function utcIso(tsSeconds) {
|
|
|
28881
29187
|
return new Date(ms).toISOString();
|
|
28882
29188
|
}
|
|
28883
29189
|
async function atomicWriteJson(filePath, payload) {
|
|
28884
|
-
const dir =
|
|
29190
|
+
const dir = path4.dirname(filePath);
|
|
28885
29191
|
await import_node_fs2.promises.mkdir(dir, { recursive: true });
|
|
28886
|
-
const tmp =
|
|
29192
|
+
const tmp = path4.join(dir, `.tmp.${process.pid}.${crypto4.randomBytes(4).toString("hex")}.json`);
|
|
28887
29193
|
try {
|
|
28888
29194
|
const handle = await import_node_fs2.promises.open(tmp, "w");
|
|
28889
29195
|
try {
|
|
@@ -28902,37 +29208,37 @@ async function atomicWriteJson(filePath, payload) {
|
|
|
28902
29208
|
}
|
|
28903
29209
|
}
|
|
28904
29210
|
async function appendJsonl(filePath, record2) {
|
|
28905
|
-
await import_node_fs2.promises.mkdir(
|
|
29211
|
+
await import_node_fs2.promises.mkdir(path4.dirname(filePath), { recursive: true });
|
|
28906
29212
|
await import_node_fs2.promises.appendFile(filePath, JSON.stringify(record2) + "\n", { encoding: "utf8" });
|
|
28907
29213
|
}
|
|
28908
29214
|
function rmTree(target) {
|
|
28909
29215
|
try {
|
|
28910
|
-
for (const child of
|
|
28911
|
-
const childPath =
|
|
28912
|
-
const stat =
|
|
29216
|
+
for (const child of fs4.readdirSync(target)) {
|
|
29217
|
+
const childPath = path4.join(target, child);
|
|
29218
|
+
const stat = fs4.lstatSync(childPath);
|
|
28913
29219
|
if (stat.isDirectory()) {
|
|
28914
29220
|
rmTree(childPath);
|
|
28915
29221
|
} else {
|
|
28916
29222
|
try {
|
|
28917
|
-
|
|
29223
|
+
fs4.unlinkSync(childPath);
|
|
28918
29224
|
} catch {
|
|
28919
29225
|
}
|
|
28920
29226
|
}
|
|
28921
29227
|
}
|
|
28922
|
-
|
|
29228
|
+
fs4.rmdirSync(target);
|
|
28923
29229
|
} catch {
|
|
28924
29230
|
}
|
|
28925
29231
|
}
|
|
28926
|
-
var crypto4,
|
|
29232
|
+
var crypto4, fs4, import_node_fs2, os, path4, SCHEMA_VERSION, DEFAULT_RETENTION_DAYS, CallLogger;
|
|
28927
29233
|
var init_call_log = __esm({
|
|
28928
29234
|
"src/services/call-log.ts"() {
|
|
28929
29235
|
"use strict";
|
|
28930
29236
|
init_cjs_shims();
|
|
28931
29237
|
crypto4 = __toESM(require("crypto"));
|
|
28932
|
-
|
|
29238
|
+
fs4 = __toESM(require("fs"));
|
|
28933
29239
|
import_node_fs2 = require("fs");
|
|
28934
29240
|
os = __toESM(require("os"));
|
|
28935
|
-
|
|
29241
|
+
path4 = __toESM(require("path"));
|
|
28936
29242
|
init_logger();
|
|
28937
29243
|
init_stream_handler();
|
|
28938
29244
|
SCHEMA_VERSION = "1.0";
|
|
@@ -28944,9 +29250,9 @@ var init_call_log = __esm({
|
|
|
28944
29250
|
this.root = null;
|
|
28945
29251
|
return;
|
|
28946
29252
|
}
|
|
28947
|
-
const resolved = root.startsWith("~") ?
|
|
29253
|
+
const resolved = root.startsWith("~") ? path4.join(os.homedir(), root.slice(1)) : root;
|
|
28948
29254
|
try {
|
|
28949
|
-
|
|
29255
|
+
fs4.mkdirSync(resolved, { recursive: true });
|
|
28950
29256
|
this.root = resolved;
|
|
28951
29257
|
getLogger().info(`Call logs: ${resolved}`);
|
|
28952
29258
|
} catch (err) {
|
|
@@ -28968,7 +29274,7 @@ var init_call_log = __esm({
|
|
|
28968
29274
|
const month = String(dt.getUTCMonth() + 1).padStart(2, "0");
|
|
28969
29275
|
const day = String(dt.getUTCDate()).padStart(2, "0");
|
|
28970
29276
|
const safeId = sanitizeLogValue(callId, 64).replace(/\//g, "_") || "unknown";
|
|
28971
|
-
return
|
|
29277
|
+
return path4.join(this.root, "calls", year, month, day, safeId);
|
|
28972
29278
|
}
|
|
28973
29279
|
/** Write the initial `metadata.json` for a new call. */
|
|
28974
29280
|
async logCallStart(callId, input = {}) {
|
|
@@ -28986,6 +29292,7 @@ var init_call_log = __esm({
|
|
|
28986
29292
|
status: "in_progress",
|
|
28987
29293
|
caller: redactPhone(input.caller ?? ""),
|
|
28988
29294
|
callee: redactPhone(input.callee ?? ""),
|
|
29295
|
+
direction: input.direction || "inbound",
|
|
28989
29296
|
telephony_provider: input.telephonyProvider ?? "",
|
|
28990
29297
|
provider_mode: input.providerMode ?? "",
|
|
28991
29298
|
agent: input.agent ?? {},
|
|
@@ -28995,7 +29302,7 @@ var init_call_log = __esm({
|
|
|
28995
29302
|
error: null
|
|
28996
29303
|
};
|
|
28997
29304
|
try {
|
|
28998
|
-
await atomicWriteJson(
|
|
29305
|
+
await atomicWriteJson(path4.join(dir, "metadata.json"), metadata);
|
|
28999
29306
|
} catch (err) {
|
|
29000
29307
|
getLogger().warn(`call_log write failed (${sanitizeLogValue(callId)}): ${sanitizeLogValue(String(err))}`);
|
|
29001
29308
|
}
|
|
@@ -29014,7 +29321,7 @@ var init_call_log = __esm({
|
|
|
29014
29321
|
...turn
|
|
29015
29322
|
};
|
|
29016
29323
|
try {
|
|
29017
|
-
await appendJsonl(
|
|
29324
|
+
await appendJsonl(path4.join(dir, "transcript.jsonl"), record2);
|
|
29018
29325
|
} catch (err) {
|
|
29019
29326
|
getLogger().warn(
|
|
29020
29327
|
`call_log turn write failed (${sanitizeLogValue(callId)}): ${sanitizeLogValue(String(err))}`
|
|
@@ -29033,7 +29340,7 @@ var init_call_log = __esm({
|
|
|
29033
29340
|
data: payload
|
|
29034
29341
|
};
|
|
29035
29342
|
try {
|
|
29036
|
-
await appendJsonl(
|
|
29343
|
+
await appendJsonl(path4.join(dir, "events.jsonl"), record2);
|
|
29037
29344
|
} catch (err) {
|
|
29038
29345
|
getLogger().warn(
|
|
29039
29346
|
`call_log event write failed (${sanitizeLogValue(callId)}): ${sanitizeLogValue(String(err))}`
|
|
@@ -29045,7 +29352,7 @@ var init_call_log = __esm({
|
|
|
29045
29352
|
if (!this.enabled) return;
|
|
29046
29353
|
const dir = this.callDir(callId);
|
|
29047
29354
|
if (dir === null) return;
|
|
29048
|
-
const metadataPath =
|
|
29355
|
+
const metadataPath = path4.join(dir, "metadata.json");
|
|
29049
29356
|
let existing = {};
|
|
29050
29357
|
try {
|
|
29051
29358
|
existing = JSON.parse(await import_node_fs2.promises.readFile(metadataPath, "utf8"));
|
|
@@ -29080,20 +29387,20 @@ var init_call_log = __esm({
|
|
|
29080
29387
|
const days = retentionDays();
|
|
29081
29388
|
if (days === 0) return;
|
|
29082
29389
|
const cutoff = Date.now() / 1e3 - days * 86400;
|
|
29083
|
-
const callsRoot =
|
|
29084
|
-
if (!
|
|
29390
|
+
const callsRoot = path4.join(this.root, "calls");
|
|
29391
|
+
if (!fs4.existsSync(callsRoot)) return;
|
|
29085
29392
|
try {
|
|
29086
|
-
for (const yearName of
|
|
29393
|
+
for (const yearName of fs4.readdirSync(callsRoot)) {
|
|
29087
29394
|
if (!/^\d+$/.test(yearName)) continue;
|
|
29088
|
-
const yearDir =
|
|
29089
|
-
if (!
|
|
29090
|
-
for (const monthName of
|
|
29395
|
+
const yearDir = path4.join(callsRoot, yearName);
|
|
29396
|
+
if (!fs4.statSync(yearDir).isDirectory()) continue;
|
|
29397
|
+
for (const monthName of fs4.readdirSync(yearDir)) {
|
|
29091
29398
|
if (!/^\d+$/.test(monthName)) continue;
|
|
29092
|
-
const monthDir =
|
|
29093
|
-
if (!
|
|
29094
|
-
for (const dayName of
|
|
29399
|
+
const monthDir = path4.join(yearDir, monthName);
|
|
29400
|
+
if (!fs4.statSync(monthDir).isDirectory()) continue;
|
|
29401
|
+
for (const dayName of fs4.readdirSync(monthDir)) {
|
|
29095
29402
|
if (!/^\d+$/.test(dayName)) continue;
|
|
29096
|
-
const dayDir =
|
|
29403
|
+
const dayDir = path4.join(monthDir, dayName);
|
|
29097
29404
|
const y = Number.parseInt(yearName, 10);
|
|
29098
29405
|
const m = Number.parseInt(monthName, 10);
|
|
29099
29406
|
const d = Number.parseInt(dayName, 10);
|
|
@@ -29103,12 +29410,12 @@ var init_call_log = __esm({
|
|
|
29103
29410
|
}
|
|
29104
29411
|
}
|
|
29105
29412
|
try {
|
|
29106
|
-
if (
|
|
29413
|
+
if (fs4.readdirSync(monthDir).length === 0) fs4.rmdirSync(monthDir);
|
|
29107
29414
|
} catch {
|
|
29108
29415
|
}
|
|
29109
29416
|
}
|
|
29110
29417
|
try {
|
|
29111
|
-
if (
|
|
29418
|
+
if (fs4.readdirSync(yearDir).length === 0) fs4.rmdirSync(yearDir);
|
|
29112
29419
|
} catch {
|
|
29113
29420
|
}
|
|
29114
29421
|
}
|
|
@@ -29298,7 +29605,7 @@ function isValidTelnyxTransferTarget(target) {
|
|
|
29298
29605
|
}
|
|
29299
29606
|
async function sleep(ms) {
|
|
29300
29607
|
if (ms <= 0) return;
|
|
29301
|
-
await new Promise((
|
|
29608
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
29302
29609
|
}
|
|
29303
29610
|
var import_node_crypto3, import_express, import_http, import_ws5, TRANSFER_CALL_TOOL, END_CALL_TOOL, TwilioBridge, TELNYX_DTMF_ALLOWED, TELNYX_DTMF_DURATION_MS, TelnyxBridge, GRACEFUL_SHUTDOWN_TIMEOUT_MS, EmbeddedServer;
|
|
29304
29611
|
var init_server = __esm({
|
|
@@ -30035,7 +30342,7 @@ var init_server = __esm({
|
|
|
30035
30342
|
this.handleTwilioStream(ws, url2);
|
|
30036
30343
|
}
|
|
30037
30344
|
});
|
|
30038
|
-
await new Promise((
|
|
30345
|
+
await new Promise((resolve2) => {
|
|
30039
30346
|
const bindHost = process.env.PATTER_BIND_HOST ?? "127.0.0.1";
|
|
30040
30347
|
this.server.listen(port, bindHost, () => {
|
|
30041
30348
|
getLogger().info(`Server on port ${port}`);
|
|
@@ -30057,7 +30364,7 @@ var init_server = __esm({
|
|
|
30057
30364
|
}
|
|
30058
30365
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
30059
30366
|
}
|
|
30060
|
-
|
|
30367
|
+
resolve2();
|
|
30061
30368
|
});
|
|
30062
30369
|
});
|
|
30063
30370
|
}
|
|
@@ -30100,7 +30407,7 @@ var init_server = __esm({
|
|
|
30100
30407
|
`Telnyx voicemail speak failed: ${speakResp.status} ${(await speakResp.text()).slice(0, 200)}`
|
|
30101
30408
|
);
|
|
30102
30409
|
}
|
|
30103
|
-
await new Promise((
|
|
30410
|
+
await new Promise((resolve2) => setTimeout(resolve2, estimatedMs));
|
|
30104
30411
|
await fetch(`https://api.telnyx.com/v2/calls/${encoded}/actions/hangup`, {
|
|
30105
30412
|
method: "POST",
|
|
30106
30413
|
headers,
|
|
@@ -30171,9 +30478,11 @@ var init_server = __esm({
|
|
|
30171
30478
|
const active = callId ? store.getActive(callId) : void 0;
|
|
30172
30479
|
const resolvedCaller = dataCaller || active?.caller || "";
|
|
30173
30480
|
const resolvedCallee = dataCallee || active?.callee || "";
|
|
30481
|
+
const resolvedDirection = (typeof data.direction === "string" ? data.direction : "") || active?.direction || "inbound";
|
|
30174
30482
|
void logger2.logCallStart(callId, {
|
|
30175
30483
|
caller: resolvedCaller,
|
|
30176
30484
|
callee: resolvedCallee,
|
|
30485
|
+
direction: resolvedDirection,
|
|
30177
30486
|
telephonyProvider: bridge.telephonyProvider,
|
|
30178
30487
|
providerMode: agent.provider ?? "",
|
|
30179
30488
|
agent: agentSnapshot()
|
|
@@ -30331,8 +30640,8 @@ var init_server = __esm({
|
|
|
30331
30640
|
*/
|
|
30332
30641
|
async stop() {
|
|
30333
30642
|
if (!this.server) return;
|
|
30334
|
-
const httpClosePromise = new Promise((
|
|
30335
|
-
this.server.close(() =>
|
|
30643
|
+
const httpClosePromise = new Promise((resolve2) => {
|
|
30644
|
+
this.server.close(() => resolve2());
|
|
30336
30645
|
});
|
|
30337
30646
|
const isTelnyx = this.config.telephonyProvider === "telnyx";
|
|
30338
30647
|
for (const [ws, callId] of this.activeCallIds) {
|
|
@@ -30352,15 +30661,15 @@ var init_server = __esm({
|
|
|
30352
30661
|
if (this.activeConnections.size > 0) {
|
|
30353
30662
|
getLogger().info(`Waiting for ${this.activeConnections.size} active connection(s) to close...`);
|
|
30354
30663
|
await Promise.race([
|
|
30355
|
-
new Promise((
|
|
30664
|
+
new Promise((resolve2) => {
|
|
30356
30665
|
const checkInterval = setInterval(() => {
|
|
30357
30666
|
if (this.activeConnections.size === 0) {
|
|
30358
30667
|
clearInterval(checkInterval);
|
|
30359
|
-
|
|
30668
|
+
resolve2();
|
|
30360
30669
|
}
|
|
30361
30670
|
}, 100);
|
|
30362
30671
|
}),
|
|
30363
|
-
new Promise((
|
|
30672
|
+
new Promise((resolve2) => setTimeout(resolve2, GRACEFUL_SHUTDOWN_TIMEOUT_MS))
|
|
30364
30673
|
]);
|
|
30365
30674
|
}
|
|
30366
30675
|
if (this.activeConnections.size > 0) {
|
|
@@ -30453,8 +30762,8 @@ async function startTunnel(port, timeoutMs = 3e4) {
|
|
|
30453
30762
|
instance = result;
|
|
30454
30763
|
}
|
|
30455
30764
|
const tunnelUrl = await Promise.race([
|
|
30456
|
-
new Promise((
|
|
30457
|
-
instance.on("url", (url2) =>
|
|
30765
|
+
new Promise((resolve2) => {
|
|
30766
|
+
instance.on("url", (url2) => resolve2(url2));
|
|
30458
30767
|
}),
|
|
30459
30768
|
new Promise(
|
|
30460
30769
|
(_, reject) => setTimeout(() => reject(new Error(
|
|
@@ -30663,7 +30972,7 @@ var init_test_mode = __esm({
|
|
|
30663
30972
|
input: process.stdin,
|
|
30664
30973
|
output: process.stdout
|
|
30665
30974
|
});
|
|
30666
|
-
const askQuestion = (prompt) => new Promise((
|
|
30975
|
+
const askQuestion = (prompt) => new Promise((resolve2) => rl.question(prompt, resolve2));
|
|
30667
30976
|
try {
|
|
30668
30977
|
while (!ended) {
|
|
30669
30978
|
let userInput;
|
|
@@ -30852,11 +31161,11 @@ var require_tracked_promise = __commonJS({
|
|
|
30852
31161
|
value;
|
|
30853
31162
|
constructor(executor) {
|
|
30854
31163
|
this.state = "pending";
|
|
30855
|
-
this.promise = new Promise((
|
|
31164
|
+
this.promise = new Promise((resolve2, reject) => {
|
|
30856
31165
|
executor((value) => {
|
|
30857
31166
|
this.state = "fulfilled";
|
|
30858
31167
|
this.value = value;
|
|
30859
|
-
|
|
31168
|
+
resolve2(value);
|
|
30860
31169
|
}, (error2) => {
|
|
30861
31170
|
this.state = "rejected";
|
|
30862
31171
|
this.error = error2;
|
|
@@ -30961,7 +31270,7 @@ var require_runner = __commonJS({
|
|
|
30961
31270
|
}
|
|
30962
31271
|
};
|
|
30963
31272
|
const runTask = (date4) => {
|
|
30964
|
-
return new Promise(async (
|
|
31273
|
+
return new Promise(async (resolve2) => {
|
|
30965
31274
|
const execution = {
|
|
30966
31275
|
id: (0, create_id_1.createID)("exec"),
|
|
30967
31276
|
reason: "scheduled"
|
|
@@ -30986,18 +31295,18 @@ var require_runner = __commonJS({
|
|
|
30986
31295
|
execution.error = error2;
|
|
30987
31296
|
this.onError(date4, error2, execution);
|
|
30988
31297
|
}
|
|
30989
|
-
|
|
31298
|
+
resolve2(true);
|
|
30990
31299
|
}, randomDelay);
|
|
30991
31300
|
}
|
|
30992
31301
|
});
|
|
30993
31302
|
};
|
|
30994
31303
|
const checkAndRun = (date4) => {
|
|
30995
|
-
return new tracked_promise_1.TrackedPromise(async (
|
|
31304
|
+
return new tracked_promise_1.TrackedPromise(async (resolve2, reject) => {
|
|
30996
31305
|
try {
|
|
30997
31306
|
if (this.timeMatcher.match(date4)) {
|
|
30998
31307
|
await runTask(date4);
|
|
30999
31308
|
}
|
|
31000
|
-
|
|
31309
|
+
resolve2(true);
|
|
31001
31310
|
} catch (err) {
|
|
31002
31311
|
reject(err);
|
|
31003
31312
|
}
|
|
@@ -31672,14 +31981,14 @@ var require_inline_scheduled_task = __commonJS({
|
|
|
31672
31981
|
this.emitter.emit("task:destroyed", this.createContext(/* @__PURE__ */ new Date()));
|
|
31673
31982
|
}
|
|
31674
31983
|
execute() {
|
|
31675
|
-
return new Promise((
|
|
31984
|
+
return new Promise((resolve2, reject) => {
|
|
31676
31985
|
const onFail = (context) => {
|
|
31677
31986
|
this.off("execution:finished", onFail);
|
|
31678
31987
|
reject(context.execution?.error);
|
|
31679
31988
|
};
|
|
31680
31989
|
const onFinished = (context) => {
|
|
31681
31990
|
this.off("execution:failed", onFail);
|
|
31682
|
-
|
|
31991
|
+
resolve2(context.execution?.result);
|
|
31683
31992
|
};
|
|
31684
31993
|
this.once("execution:finished", onFinished);
|
|
31685
31994
|
this.once("execution:failed", onFail);
|
|
@@ -31873,9 +32182,9 @@ var require_background_scheduled_task = __commonJS({
|
|
|
31873
32182
|
return null;
|
|
31874
32183
|
}
|
|
31875
32184
|
start() {
|
|
31876
|
-
return new Promise((
|
|
32185
|
+
return new Promise((resolve2, reject) => {
|
|
31877
32186
|
if (this.forkProcess) {
|
|
31878
|
-
return
|
|
32187
|
+
return resolve2(void 0);
|
|
31879
32188
|
}
|
|
31880
32189
|
const timeout = setTimeout(() => {
|
|
31881
32190
|
reject(new Error("Start operation timed out"));
|
|
@@ -31914,7 +32223,7 @@ var require_background_scheduled_task = __commonJS({
|
|
|
31914
32223
|
this.once("task:started", () => {
|
|
31915
32224
|
this.stateMachine.changeState("idle");
|
|
31916
32225
|
clearTimeout(timeout);
|
|
31917
|
-
|
|
32226
|
+
resolve2(void 0);
|
|
31918
32227
|
});
|
|
31919
32228
|
this.forkProcess.send({
|
|
31920
32229
|
command: "task:start",
|
|
@@ -31928,9 +32237,9 @@ var require_background_scheduled_task = __commonJS({
|
|
|
31928
32237
|
});
|
|
31929
32238
|
}
|
|
31930
32239
|
stop() {
|
|
31931
|
-
return new Promise((
|
|
32240
|
+
return new Promise((resolve2, reject) => {
|
|
31932
32241
|
if (!this.forkProcess) {
|
|
31933
|
-
return
|
|
32242
|
+
return resolve2(void 0);
|
|
31934
32243
|
}
|
|
31935
32244
|
const timeoutId = setTimeout(() => {
|
|
31936
32245
|
clearTimeout(timeoutId);
|
|
@@ -31940,7 +32249,7 @@ var require_background_scheduled_task = __commonJS({
|
|
|
31940
32249
|
clearTimeout(timeoutId);
|
|
31941
32250
|
this.off("task:stopped", onStopped);
|
|
31942
32251
|
this.forkProcess = void 0;
|
|
31943
|
-
|
|
32252
|
+
resolve2(void 0);
|
|
31944
32253
|
};
|
|
31945
32254
|
const onStopped = () => {
|
|
31946
32255
|
cleanupAndResolve();
|
|
@@ -31955,9 +32264,9 @@ var require_background_scheduled_task = __commonJS({
|
|
|
31955
32264
|
return this.stateMachine.state;
|
|
31956
32265
|
}
|
|
31957
32266
|
destroy() {
|
|
31958
|
-
return new Promise((
|
|
32267
|
+
return new Promise((resolve2, reject) => {
|
|
31959
32268
|
if (!this.forkProcess) {
|
|
31960
|
-
return
|
|
32269
|
+
return resolve2(void 0);
|
|
31961
32270
|
}
|
|
31962
32271
|
const timeoutId = setTimeout(() => {
|
|
31963
32272
|
clearTimeout(timeoutId);
|
|
@@ -31966,7 +32275,7 @@ var require_background_scheduled_task = __commonJS({
|
|
|
31966
32275
|
const onDestroy = () => {
|
|
31967
32276
|
clearTimeout(timeoutId);
|
|
31968
32277
|
this.off("task:destroyed", onDestroy);
|
|
31969
|
-
|
|
32278
|
+
resolve2(void 0);
|
|
31970
32279
|
};
|
|
31971
32280
|
this.once("task:destroyed", onDestroy);
|
|
31972
32281
|
this.forkProcess.send({
|
|
@@ -31975,7 +32284,7 @@ var require_background_scheduled_task = __commonJS({
|
|
|
31975
32284
|
});
|
|
31976
32285
|
}
|
|
31977
32286
|
execute() {
|
|
31978
|
-
return new Promise((
|
|
32287
|
+
return new Promise((resolve2, reject) => {
|
|
31979
32288
|
if (!this.forkProcess) {
|
|
31980
32289
|
return reject(new Error("Cannot execute background task because it hasn't been started yet. Please initialize the task using the start() method before attempting to execute it."));
|
|
31981
32290
|
}
|
|
@@ -31990,7 +32299,7 @@ var require_background_scheduled_task = __commonJS({
|
|
|
31990
32299
|
};
|
|
31991
32300
|
const onFinished = (context) => {
|
|
31992
32301
|
cleanupListeners();
|
|
31993
|
-
|
|
32302
|
+
resolve2(context.execution?.result);
|
|
31994
32303
|
};
|
|
31995
32304
|
const onFail = (context) => {
|
|
31996
32305
|
cleanupListeners();
|
|
@@ -32131,6 +32440,8 @@ __export(index_exports, {
|
|
|
32131
32440
|
CallMetricsAccumulator: () => CallMetricsAccumulator,
|
|
32132
32441
|
CartesiaSTT: () => STT4,
|
|
32133
32442
|
CartesiaTTS: () => TTS4,
|
|
32443
|
+
CartesiaTTSModel: () => CartesiaTTSModel,
|
|
32444
|
+
CartesiaTTSVoiceMode: () => CartesiaTTSVoiceMode,
|
|
32134
32445
|
CerebrasLLM: () => LLM4,
|
|
32135
32446
|
ChatContext: () => ChatContext,
|
|
32136
32447
|
CloudflareTunnel: () => CloudflareTunnel,
|
|
@@ -32138,10 +32449,13 @@ __export(index_exports, {
|
|
|
32138
32449
|
DEFAULT_PRICING: () => DEFAULT_PRICING,
|
|
32139
32450
|
DTMF_EVENTS: () => DTMF_EVENTS,
|
|
32140
32451
|
DeepFilterNetFilter: () => DeepFilterNetFilter,
|
|
32452
|
+
DeepgramModel: () => DeepgramModel,
|
|
32141
32453
|
DeepgramSTT: () => STT,
|
|
32142
32454
|
DefaultToolExecutor: () => DefaultToolExecutor,
|
|
32143
32455
|
ElevenLabsConvAI: () => ConvAI,
|
|
32144
32456
|
ElevenLabsConvAIAdapter: () => ElevenLabsConvAIAdapter,
|
|
32457
|
+
ElevenLabsModel: () => ElevenLabsModel,
|
|
32458
|
+
ElevenLabsOutputFormat: () => ElevenLabsOutputFormat,
|
|
32145
32459
|
ElevenLabsRestTTS: () => ElevenLabsTTS,
|
|
32146
32460
|
ElevenLabsTTS: () => TTS,
|
|
32147
32461
|
ElevenLabsWebSocketTTS: () => TTS2,
|
|
@@ -32170,8 +32484,15 @@ __export(index_exports, {
|
|
|
32170
32484
|
OpenAIRealtime2: () => Realtime2,
|
|
32171
32485
|
OpenAIRealtime2Adapter: () => OpenAIRealtime2Adapter,
|
|
32172
32486
|
OpenAIRealtimeAdapter: () => OpenAIRealtimeAdapter,
|
|
32487
|
+
OpenAIRealtimeAudioFormat: () => OpenAIRealtimeAudioFormat,
|
|
32488
|
+
OpenAIRealtimeModel: () => OpenAIRealtimeModel,
|
|
32489
|
+
OpenAIRealtimeVADType: () => OpenAIRealtimeVADType,
|
|
32173
32490
|
OpenAITTS: () => TTS3,
|
|
32174
32491
|
OpenAITranscribeSTT: () => STT3,
|
|
32492
|
+
OpenAITranscriptionModel: () => OpenAITranscriptionModel,
|
|
32493
|
+
OpenAIVoice: () => OpenAIVoice,
|
|
32494
|
+
PRICING_LAST_UPDATED: () => PRICING_LAST_UPDATED,
|
|
32495
|
+
PRICING_VERSION: () => PRICING_VERSION,
|
|
32175
32496
|
PartialStreamError: () => PartialStreamError,
|
|
32176
32497
|
Patter: () => Patter,
|
|
32177
32498
|
PatterConnectionError: () => PatterConnectionError,
|
|
@@ -32179,9 +32500,12 @@ __export(index_exports, {
|
|
|
32179
32500
|
PatterTool: () => PatterTool,
|
|
32180
32501
|
PcmCarry: () => PcmCarry,
|
|
32181
32502
|
PipelineHookExecutor: () => PipelineHookExecutor,
|
|
32503
|
+
PricingUnit: () => PricingUnit,
|
|
32182
32504
|
ProvisionError: () => ProvisionError,
|
|
32183
32505
|
RateLimitError: () => RateLimitError,
|
|
32184
32506
|
RemoteMessageHandler: () => RemoteMessageHandler,
|
|
32507
|
+
RimeAudioFormat: () => RimeAudioFormat,
|
|
32508
|
+
RimeModel: () => RimeModel,
|
|
32185
32509
|
RimeTTS: () => TTS5,
|
|
32186
32510
|
SPAN_BARGEIN: () => SPAN_BARGEIN,
|
|
32187
32511
|
SPAN_CALL: () => SPAN_CALL,
|
|
@@ -32301,7 +32625,7 @@ var Realtime = class {
|
|
|
32301
32625
|
);
|
|
32302
32626
|
}
|
|
32303
32627
|
this.apiKey = key;
|
|
32304
|
-
this.model = opts.model ?? "gpt-
|
|
32628
|
+
this.model = opts.model ?? "gpt-realtime-mini";
|
|
32305
32629
|
this.voice = opts.voice ?? "alloy";
|
|
32306
32630
|
this.reasoningEffort = opts.reasoningEffort;
|
|
32307
32631
|
this.inputAudioTranscriptionModel = opts.inputAudioTranscriptionModel;
|
|
@@ -32760,7 +33084,9 @@ function resolvePersistRoot(persist) {
|
|
|
32760
33084
|
if (persist === false) return null;
|
|
32761
33085
|
if (persist === true) return resolveLogRoot("auto");
|
|
32762
33086
|
if (typeof persist === "string") return resolveLogRoot(persist);
|
|
32763
|
-
|
|
33087
|
+
const envRoot = resolveLogRoot();
|
|
33088
|
+
if (envRoot !== null) return envRoot;
|
|
33089
|
+
return resolveLogRoot("auto");
|
|
32764
33090
|
}
|
|
32765
33091
|
function closeParkedConnections(slot) {
|
|
32766
33092
|
if (slot.stt) {
|
|
@@ -32776,6 +33102,11 @@ function closeParkedConnections(slot) {
|
|
|
32776
33102
|
}
|
|
32777
33103
|
}
|
|
32778
33104
|
if (slot.openaiRealtime) {
|
|
33105
|
+
const wsAny = slot.openaiRealtime;
|
|
33106
|
+
if (wsAny._parkedKeepalive) {
|
|
33107
|
+
clearInterval(wsAny._parkedKeepalive);
|
|
33108
|
+
delete wsAny._parkedKeepalive;
|
|
33109
|
+
}
|
|
32779
33110
|
try {
|
|
32780
33111
|
slot.openaiRealtime.close();
|
|
32781
33112
|
} catch {
|
|
@@ -33007,8 +33338,8 @@ var Patter = class {
|
|
|
33007
33338
|
openaiKey: options.openaiKey,
|
|
33008
33339
|
persistRoot: resolvePersistRoot(options.persist)
|
|
33009
33340
|
};
|
|
33010
|
-
this._tunnelReady = new Promise((
|
|
33011
|
-
this._tunnelReadyResolve =
|
|
33341
|
+
this._tunnelReady = new Promise((resolve2, reject) => {
|
|
33342
|
+
this._tunnelReadyResolve = resolve2;
|
|
33012
33343
|
this._tunnelReadyReject = reject;
|
|
33013
33344
|
});
|
|
33014
33345
|
this._tunnelReady.catch(() => {
|
|
@@ -33016,8 +33347,8 @@ var Patter = class {
|
|
|
33016
33347
|
if (normalizedWebhook) {
|
|
33017
33348
|
this._tunnelReadyResolve(normalizedWebhook);
|
|
33018
33349
|
}
|
|
33019
|
-
this._ready = new Promise((
|
|
33020
|
-
this._readyResolve =
|
|
33350
|
+
this._ready = new Promise((resolve2, reject) => {
|
|
33351
|
+
this._readyResolve = resolve2;
|
|
33021
33352
|
this._readyReject = reject;
|
|
33022
33353
|
});
|
|
33023
33354
|
this._ready.catch(() => {
|
|
@@ -33347,7 +33678,9 @@ var Patter = class {
|
|
|
33347
33678
|
const tts = agent.tts;
|
|
33348
33679
|
const sttOpen = typeof stt?.openParkedConnection === "function" ? stt.openParkedConnection.bind(stt) : null;
|
|
33349
33680
|
const ttsOpen = typeof tts?.openParkedConnection === "function" ? tts.openParkedConnection.bind(tts) : null;
|
|
33350
|
-
|
|
33681
|
+
const providerStr = agent.provider ?? "";
|
|
33682
|
+
const wantsRealtimePark = providerStr === "openai_realtime" || providerStr === "openai_realtime_2";
|
|
33683
|
+
if (!sttOpen && !ttsOpen && !wantsRealtimePark) return;
|
|
33351
33684
|
const slot = {};
|
|
33352
33685
|
this.prewarmedConnections.set(callId, slot);
|
|
33353
33686
|
const startedAt = Date.now();
|
|
@@ -33392,6 +33725,43 @@ var Patter = class {
|
|
|
33392
33725
|
}
|
|
33393
33726
|
})());
|
|
33394
33727
|
}
|
|
33728
|
+
if (wantsRealtimePark) {
|
|
33729
|
+
tasks.push((async () => {
|
|
33730
|
+
const { OpenAIRealtime2Adapter: OpenAIRealtime2Adapter2 } = await Promise.resolve().then(() => (init_openai_realtime_2(), openai_realtime_2_exports));
|
|
33731
|
+
const apiKey = process.env.OPENAI_API_KEY ?? "";
|
|
33732
|
+
if (!apiKey) {
|
|
33733
|
+
getLogger().debug(`Park OpenAI Realtime skipped for ${callId}: no OPENAI_API_KEY`);
|
|
33734
|
+
return;
|
|
33735
|
+
}
|
|
33736
|
+
try {
|
|
33737
|
+
const tmpAdapter = new OpenAIRealtime2Adapter2(
|
|
33738
|
+
apiKey,
|
|
33739
|
+
agent.model ?? "gpt-realtime-mini",
|
|
33740
|
+
agent.voice ?? "alloy",
|
|
33741
|
+
agent.systemPrompt ?? "",
|
|
33742
|
+
[],
|
|
33743
|
+
// audioFormat — the GA adapter always emits audio/pcm@24000
|
|
33744
|
+
// internally regardless of this value, but it's a required
|
|
33745
|
+
// positional param. Default to g711_ulaw (Twilio wire format).
|
|
33746
|
+
void 0
|
|
33747
|
+
);
|
|
33748
|
+
const ws = await tmpAdapter.openParkedConnection();
|
|
33749
|
+
if (this.prewarmedConnections.get(callId) !== slot) {
|
|
33750
|
+
try {
|
|
33751
|
+
ws.close();
|
|
33752
|
+
} catch {
|
|
33753
|
+
}
|
|
33754
|
+
return;
|
|
33755
|
+
}
|
|
33756
|
+
slot.openaiRealtime = ws;
|
|
33757
|
+
getLogger().info(
|
|
33758
|
+
`[PREWARM] callId=${callId} provider=openai_realtime ms=${Date.now() - startedAt}`
|
|
33759
|
+
);
|
|
33760
|
+
} catch (err) {
|
|
33761
|
+
getLogger().debug(`Park OpenAI Realtime failed for ${callId}: ${String(err)}`);
|
|
33762
|
+
}
|
|
33763
|
+
})());
|
|
33764
|
+
}
|
|
33395
33765
|
const task = (async () => {
|
|
33396
33766
|
await Promise.allSettled(tasks);
|
|
33397
33767
|
})();
|
|
@@ -33469,7 +33839,7 @@ var Patter = class {
|
|
|
33469
33839
|
* with a warn when the cap is reached (the call still proceeds —
|
|
33470
33840
|
* StreamHandler falls back to live TTS).
|
|
33471
33841
|
*/
|
|
33472
|
-
spawnPrewarmFirstMessage(agent, callId, ringTimeout) {
|
|
33842
|
+
spawnPrewarmFirstMessage(agent, callId, ringTimeout, carrier) {
|
|
33473
33843
|
if (!agent.prewarmFirstMessage) return;
|
|
33474
33844
|
const providerMode = agent.provider ?? "openai_realtime";
|
|
33475
33845
|
if (providerMode !== "pipeline") {
|
|
@@ -33482,6 +33852,18 @@ var Patter = class {
|
|
|
33482
33852
|
const tts = agent.tts;
|
|
33483
33853
|
if (!firstMessage || !tts) return;
|
|
33484
33854
|
if (typeof tts.synthesizeStream !== "function") return;
|
|
33855
|
+
if (carrier) {
|
|
33856
|
+
const carrierAware = tts;
|
|
33857
|
+
if (typeof carrierAware.setTelephonyCarrier === "function") {
|
|
33858
|
+
try {
|
|
33859
|
+
carrierAware.setTelephonyCarrier(carrier);
|
|
33860
|
+
} catch (err) {
|
|
33861
|
+
getLogger().debug(
|
|
33862
|
+
`Prewarm TTS setTelephonyCarrier failed for ${callId}: ${String(err)}`
|
|
33863
|
+
);
|
|
33864
|
+
}
|
|
33865
|
+
}
|
|
33866
|
+
}
|
|
33485
33867
|
const inFlight = this.prewarmAudio.size + this.prewarmTasks.size;
|
|
33486
33868
|
if (inFlight >= PREWARM_CACHE_MAX) {
|
|
33487
33869
|
getLogger().warn(
|
|
@@ -33594,16 +33976,25 @@ var Patter = class {
|
|
|
33594
33976
|
telnyxCallId = body.data?.call_control_id;
|
|
33595
33977
|
} catch {
|
|
33596
33978
|
}
|
|
33597
|
-
if (
|
|
33598
|
-
|
|
33979
|
+
if (telnyxCallId) {
|
|
33980
|
+
const initiatedPayload = {
|
|
33599
33981
|
call_id: telnyxCallId,
|
|
33600
33982
|
caller: phoneNumber,
|
|
33601
33983
|
callee: options.to,
|
|
33602
|
-
direction: "outbound"
|
|
33603
|
-
|
|
33984
|
+
direction: "outbound",
|
|
33985
|
+
status: "initiated"
|
|
33986
|
+
};
|
|
33987
|
+
if (this.embeddedServer) {
|
|
33988
|
+
this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
|
|
33989
|
+
}
|
|
33990
|
+
try {
|
|
33991
|
+
const { notifyDashboard: notifyDashboard2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
|
|
33992
|
+
notifyDashboard2(initiatedPayload);
|
|
33993
|
+
} catch {
|
|
33994
|
+
}
|
|
33604
33995
|
}
|
|
33605
33996
|
if (telnyxCallId) {
|
|
33606
|
-
this.spawnPrewarmFirstMessage(options.agent, telnyxCallId, effectiveRingTimeout);
|
|
33997
|
+
this.spawnPrewarmFirstMessage(options.agent, telnyxCallId, effectiveRingTimeout, "telnyx");
|
|
33607
33998
|
if (options.agent.prewarm !== false) {
|
|
33608
33999
|
this.parkProviderConnections(options.agent, telnyxCallId);
|
|
33609
34000
|
}
|
|
@@ -33656,21 +34047,30 @@ var Patter = class {
|
|
|
33656
34047
|
twilioNotificationsPath = body.subresource_uris?.notifications;
|
|
33657
34048
|
} catch {
|
|
33658
34049
|
}
|
|
33659
|
-
if (
|
|
33660
|
-
|
|
34050
|
+
if (twilioCallSid) {
|
|
34051
|
+
const initiatedPayload = {
|
|
33661
34052
|
call_id: twilioCallSid,
|
|
33662
34053
|
caller: phoneNumber,
|
|
33663
34054
|
callee: options.to,
|
|
33664
|
-
direction: "outbound"
|
|
33665
|
-
|
|
33666
|
-
|
|
33667
|
-
|
|
33668
|
-
|
|
33669
|
-
)
|
|
34055
|
+
direction: "outbound",
|
|
34056
|
+
status: "initiated"
|
|
34057
|
+
};
|
|
34058
|
+
if (this.embeddedServer) {
|
|
34059
|
+
this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
|
|
34060
|
+
if (twilioNotificationsPath) {
|
|
34061
|
+
getLogger().info(
|
|
34062
|
+
`Outbound call ${twilioCallSid} placed. Twilio notifications: https://api.twilio.com${twilioNotificationsPath} (check here if the call drops with no audio).`
|
|
34063
|
+
);
|
|
34064
|
+
}
|
|
34065
|
+
}
|
|
34066
|
+
try {
|
|
34067
|
+
const { notifyDashboard: notifyDashboard2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
|
|
34068
|
+
notifyDashboard2(initiatedPayload);
|
|
34069
|
+
} catch {
|
|
33670
34070
|
}
|
|
33671
34071
|
}
|
|
33672
34072
|
if (twilioCallSid) {
|
|
33673
|
-
this.spawnPrewarmFirstMessage(options.agent, twilioCallSid, effectiveRingTimeout);
|
|
34073
|
+
this.spawnPrewarmFirstMessage(options.agent, twilioCallSid, effectiveRingTimeout, "twilio");
|
|
33674
34074
|
if (options.agent.prewarm !== false) {
|
|
33675
34075
|
this.parkProviderConnections(options.agent, twilioCallSid);
|
|
33676
34076
|
}
|
|
@@ -33695,7 +34095,7 @@ var Patter = class {
|
|
|
33695
34095
|
if (this.prewarmTasks.size > 0) {
|
|
33696
34096
|
const drain = Promise.allSettled(Array.from(this.prewarmTasks));
|
|
33697
34097
|
const timer = new Promise(
|
|
33698
|
-
(
|
|
34098
|
+
(resolve2) => setTimeout(resolve2, 1e3).unref?.()
|
|
33699
34099
|
);
|
|
33700
34100
|
await Promise.race([drain, timer]);
|
|
33701
34101
|
}
|
|
@@ -33722,8 +34122,8 @@ var Patter = class {
|
|
|
33722
34122
|
this.localConfig = { ...this.localConfig, webhookUrl: void 0 };
|
|
33723
34123
|
this.tunnelOwnsWebhookUrl = false;
|
|
33724
34124
|
}
|
|
33725
|
-
this._tunnelReady = new Promise((
|
|
33726
|
-
this._tunnelReadyResolve =
|
|
34125
|
+
this._tunnelReady = new Promise((resolve2, reject) => {
|
|
34126
|
+
this._tunnelReadyResolve = resolve2;
|
|
33727
34127
|
this._tunnelReadyReject = reject;
|
|
33728
34128
|
});
|
|
33729
34129
|
this._tunnelReady.catch(() => {
|
|
@@ -33731,8 +34131,8 @@ var Patter = class {
|
|
|
33731
34131
|
if (this.localConfig.webhookUrl) {
|
|
33732
34132
|
this._tunnelReadyResolve(this.localConfig.webhookUrl);
|
|
33733
34133
|
}
|
|
33734
|
-
this._ready = new Promise((
|
|
33735
|
-
this._readyResolve =
|
|
34134
|
+
this._ready = new Promise((resolve2, reject) => {
|
|
34135
|
+
this._readyResolve = resolve2;
|
|
33736
34136
|
this._readyReject = reject;
|
|
33737
34137
|
});
|
|
33738
34138
|
this._ready.catch(() => {
|
|
@@ -34382,13 +34782,13 @@ var PatterTool = class _PatterTool {
|
|
|
34382
34782
|
...args.first_message !== void 0 ? { firstMessage: args.first_message } : {}
|
|
34383
34783
|
});
|
|
34384
34784
|
const callId = await this.acquireCallId(args.to, overrideAgent);
|
|
34385
|
-
return new Promise((
|
|
34785
|
+
return new Promise((resolve2, reject) => {
|
|
34386
34786
|
const timer = setTimeout(() => {
|
|
34387
34787
|
this.pending.delete(callId);
|
|
34388
34788
|
reject(new Error(`PatterTool.execute: call ${callId} exceeded ${timeoutSec}s timeout`));
|
|
34389
34789
|
}, timeoutSec * 1e3);
|
|
34390
34790
|
this.pending.set(callId, {
|
|
34391
|
-
resolve,
|
|
34791
|
+
resolve: resolve2,
|
|
34392
34792
|
reject,
|
|
34393
34793
|
timer,
|
|
34394
34794
|
startedAt: Date.now() / 1e3
|
|
@@ -34406,8 +34806,8 @@ var PatterTool = class _PatterTool {
|
|
|
34406
34806
|
await previous;
|
|
34407
34807
|
let captureTimer = null;
|
|
34408
34808
|
try {
|
|
34409
|
-
const callIdPromise = new Promise((
|
|
34410
|
-
this.pendingDial =
|
|
34809
|
+
const callIdPromise = new Promise((resolve2, reject) => {
|
|
34810
|
+
this.pendingDial = resolve2;
|
|
34411
34811
|
captureTimer = setTimeout(() => {
|
|
34412
34812
|
this.pendingDial = null;
|
|
34413
34813
|
reject(
|
|
@@ -34759,11 +35159,11 @@ var UltravoxRealtimeAdapter = class {
|
|
|
34759
35159
|
const call = await resp.json();
|
|
34760
35160
|
if (!call.joinUrl) throw new Error("Ultravox response missing joinUrl");
|
|
34761
35161
|
this.ws = new import_ws6.default(call.joinUrl);
|
|
34762
|
-
await new Promise((
|
|
35162
|
+
await new Promise((resolve2, reject) => {
|
|
34763
35163
|
const ws = this.ws;
|
|
34764
35164
|
const onOpen = () => {
|
|
34765
35165
|
ws.off("error", onError);
|
|
34766
|
-
|
|
35166
|
+
resolve2();
|
|
34767
35167
|
};
|
|
34768
35168
|
const onError = (err) => {
|
|
34769
35169
|
ws.off("open", onOpen);
|
|
@@ -34995,6 +35395,601 @@ function scheduleInterval(intervalOrOpts, callback) {
|
|
|
34995
35395
|
};
|
|
34996
35396
|
}
|
|
34997
35397
|
|
|
35398
|
+
// src/index.ts
|
|
35399
|
+
init_openai_realtime();
|
|
35400
|
+
|
|
35401
|
+
// src/providers/elevenlabs-tts.ts
|
|
35402
|
+
init_cjs_shims();
|
|
35403
|
+
var ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
|
|
35404
|
+
var ELEVENLABS_VOICE_ID_BY_NAME = {
|
|
35405
|
+
rachel: "21m00Tcm4TlvDq8ikWAM",
|
|
35406
|
+
drew: "29vD33N1CtxCmqQRPOHJ",
|
|
35407
|
+
clyde: "2EiwWnXFnvU5JabPnv8n",
|
|
35408
|
+
paul: "5Q0t7uMcjvnagumLfvZi",
|
|
35409
|
+
domi: "AZnzlk1XvdvUeBnXmlld",
|
|
35410
|
+
dave: "CYw3kZ02Hs0563khs1Fj",
|
|
35411
|
+
fin: "D38z5RcWu1voky8WS1ja",
|
|
35412
|
+
bella: "EXAVITQu4vr4xnSDxMaL",
|
|
35413
|
+
antoni: "ErXwobaYiN019PkySvjV",
|
|
35414
|
+
thomas: "GBv7mTt0atIp3Br8iCZE",
|
|
35415
|
+
charlie: "IKne3meq5aSn9XLyUdCD",
|
|
35416
|
+
george: "JBFqnCBsd6RMkjVDRZzb",
|
|
35417
|
+
emily: "LcfcDJNUP1GQjkzn1xUU",
|
|
35418
|
+
elli: "MF3mGyEYCl7XYWbV9V6O",
|
|
35419
|
+
callum: "N2lVS1w4EtoT3dr4eOWO",
|
|
35420
|
+
patrick: "ODq5zmih8GrVes37Dizd",
|
|
35421
|
+
harry: "SOYHLrjzK2X1ezoPC6cr",
|
|
35422
|
+
liam: "TX3LPaxmHKxFdv7VOQHJ",
|
|
35423
|
+
dorothy: "ThT5KcBeYPX3keUQqHPh",
|
|
35424
|
+
josh: "TxGEqnHWrfWFTfGW9XjX",
|
|
35425
|
+
arnold: "VR6AewLTigWG4xSOukaG",
|
|
35426
|
+
charlotte: "XB0fDUnXU5powFXDhCwa",
|
|
35427
|
+
matilda: "XrExE9yKIg1WjnnlVkGX",
|
|
35428
|
+
matthew: "Yko7PKHZNXotIFUBG7I9",
|
|
35429
|
+
james: "ZQe5CZNOzWyzPSCn5a3c",
|
|
35430
|
+
joseph: "Zlb1dXrM653N07WRdFW3",
|
|
35431
|
+
jeremy: "bVMeCyTHy58xNoL34h3p",
|
|
35432
|
+
michael: "flq6f7yk4E4fJM5XTYuZ",
|
|
35433
|
+
ethan: "g5CIjZEefAph4nQFvHAz",
|
|
35434
|
+
gigi: "jBpfuIE2acCO8z3wKNLl",
|
|
35435
|
+
freya: "jsCqWAovK2LkecY7zXl4",
|
|
35436
|
+
brian: "nPczCjzI2devNBz1zQrb",
|
|
35437
|
+
grace: "oWAxZDx7w5VEj9dCyTzz",
|
|
35438
|
+
daniel: "onwK4e9ZLuTAKqWW03F9",
|
|
35439
|
+
lily: "pFZP5JQG7iQjIQuC4Bku",
|
|
35440
|
+
serena: "pMsXgVXv3BLzUgSXRplE",
|
|
35441
|
+
adam: "pNInz6obpgDQGcFmaJgB",
|
|
35442
|
+
nicole: "piTKgcLEGmPE4e6mEKli",
|
|
35443
|
+
bill: "pqHfZKP75CvOlQylNhV4",
|
|
35444
|
+
jessie: "t0jbNlBVZ17f02VDIeMI",
|
|
35445
|
+
ryan: "wViXBPUzp2ZZixB1xQuM",
|
|
35446
|
+
sam: "yoZ06aMxZJJ28mfd3POQ",
|
|
35447
|
+
glinda: "z9fAnlkpzviPz146aGWa",
|
|
35448
|
+
giovanni: "zcAOhNBS3c14rBihAFp1",
|
|
35449
|
+
mimi: "zrHiDhphv9ZnVXBqCLjz",
|
|
35450
|
+
sarah: "EXAVITQu4vr4xnSDxMaL",
|
|
35451
|
+
alloy: "EXAVITQu4vr4xnSDxMaL"
|
|
35452
|
+
};
|
|
35453
|
+
var VOICE_ID_PATTERN = /^[A-Za-z0-9]{20}$/;
|
|
35454
|
+
function resolveVoiceId(voice) {
|
|
35455
|
+
if (!voice) return voice;
|
|
35456
|
+
if (VOICE_ID_PATTERN.test(voice)) return voice;
|
|
35457
|
+
return ELEVENLABS_VOICE_ID_BY_NAME[voice.toLowerCase()] ?? voice;
|
|
35458
|
+
}
|
|
35459
|
+
var ElevenLabsModel = {
|
|
35460
|
+
V3: "eleven_v3",
|
|
35461
|
+
FLASH_V2_5: "eleven_flash_v2_5",
|
|
35462
|
+
TURBO_V2_5: "eleven_turbo_v2_5",
|
|
35463
|
+
MULTILINGUAL_V2: "eleven_multilingual_v2",
|
|
35464
|
+
MONOLINGUAL_V1: "eleven_monolingual_v1"
|
|
35465
|
+
};
|
|
35466
|
+
var ElevenLabsOutputFormat = {
|
|
35467
|
+
MP3_22050_32: "mp3_22050_32",
|
|
35468
|
+
MP3_44100_32: "mp3_44100_32",
|
|
35469
|
+
MP3_44100_64: "mp3_44100_64",
|
|
35470
|
+
MP3_44100_96: "mp3_44100_96",
|
|
35471
|
+
MP3_44100_128: "mp3_44100_128",
|
|
35472
|
+
MP3_44100_192: "mp3_44100_192",
|
|
35473
|
+
PCM_8000: "pcm_8000",
|
|
35474
|
+
PCM_16000: "pcm_16000",
|
|
35475
|
+
PCM_22050: "pcm_22050",
|
|
35476
|
+
PCM_24000: "pcm_24000",
|
|
35477
|
+
PCM_44100: "pcm_44100",
|
|
35478
|
+
ULAW_8000: "ulaw_8000"
|
|
35479
|
+
};
|
|
35480
|
+
var ElevenLabsTTS = class _ElevenLabsTTS {
|
|
35481
|
+
// Stable pricing/dashboard key — read by stream-handler / metrics via
|
|
35482
|
+
// ``(agent.tts.constructor as any).providerKey``. Without this the cost
|
|
35483
|
+
// calculator falls back to ``constructor.name`` ("ElevenLabsTTS") which
|
|
35484
|
+
// does NOT match the pricing table key "elevenlabs", silently zeroing
|
|
35485
|
+
// TTS cost for callers that construct the raw REST class directly
|
|
35486
|
+
// (exposed at top level as ``ElevenLabsRestTTS``).
|
|
35487
|
+
static providerKey = "elevenlabs";
|
|
35488
|
+
apiKey;
|
|
35489
|
+
voiceId;
|
|
35490
|
+
modelId;
|
|
35491
|
+
_outputFormat;
|
|
35492
|
+
_outputFormatExplicit;
|
|
35493
|
+
voiceSettings;
|
|
35494
|
+
languageCode;
|
|
35495
|
+
chunkSize;
|
|
35496
|
+
/**
|
|
35497
|
+
* Public view of the (possibly auto-flipped) wire format. Read by the
|
|
35498
|
+
* stream-handler to decide whether to skip the client-side resample +
|
|
35499
|
+
* mulaw encode when the bytes are already in the carrier's wire codec.
|
|
35500
|
+
*/
|
|
35501
|
+
get outputFormat() {
|
|
35502
|
+
return this._outputFormat;
|
|
35503
|
+
}
|
|
35504
|
+
constructor(apiKey, voiceIdOrOptions = "21m00Tcm4TlvDq8ikWAM", modelId = ElevenLabsModel.FLASH_V2_5, outputFormat = ElevenLabsOutputFormat.PCM_16000) {
|
|
35505
|
+
this.apiKey = apiKey;
|
|
35506
|
+
if (typeof voiceIdOrOptions === "object") {
|
|
35507
|
+
const o = voiceIdOrOptions;
|
|
35508
|
+
this.voiceId = resolveVoiceId(o.voiceId ?? "21m00Tcm4TlvDq8ikWAM");
|
|
35509
|
+
this.modelId = o.modelId ?? ElevenLabsModel.FLASH_V2_5;
|
|
35510
|
+
this._outputFormatExplicit = o.outputFormat !== void 0;
|
|
35511
|
+
this._outputFormat = o.outputFormat ?? ElevenLabsOutputFormat.PCM_16000;
|
|
35512
|
+
this.voiceSettings = o.voiceSettings;
|
|
35513
|
+
this.languageCode = o.languageCode;
|
|
35514
|
+
this.chunkSize = o.chunkSize ?? 4096;
|
|
35515
|
+
} else {
|
|
35516
|
+
this.voiceId = resolveVoiceId(voiceIdOrOptions);
|
|
35517
|
+
this.modelId = modelId;
|
|
35518
|
+
this._outputFormatExplicit = outputFormat !== ElevenLabsOutputFormat.PCM_16000;
|
|
35519
|
+
this._outputFormat = outputFormat;
|
|
35520
|
+
this.voiceSettings = void 0;
|
|
35521
|
+
this.languageCode = void 0;
|
|
35522
|
+
this.chunkSize = 4096;
|
|
35523
|
+
}
|
|
35524
|
+
}
|
|
35525
|
+
/**
|
|
35526
|
+
* Hook called by ``StreamHandler.initPipeline`` to advise the carrier
|
|
35527
|
+
* wire format. When the user did NOT pass an explicit ``outputFormat``,
|
|
35528
|
+
* auto-flip to the carrier's native codec so the audio bytes ElevenLabs
|
|
35529
|
+
* returns are already in Twilio/Telnyx wire format — eliminating the
|
|
35530
|
+
* client-side 16 kHz → 8 kHz resample and PCM → μ-law encode. The
|
|
35531
|
+
* resample/encode chain was a source of audible artifacts on the
|
|
35532
|
+
* prewarmed firstMessage (see 0.6.2 acceptance notes — burst delivery
|
|
35533
|
+
* of resampled audio crackled on the carrier-side jitter buffer).
|
|
35534
|
+
*
|
|
35535
|
+
* No-op when the caller passed an explicit ``outputFormat`` (incl. via
|
|
35536
|
+
* the ``forTwilio`` / ``forTelnyx`` factories) — user wins.
|
|
35537
|
+
*
|
|
35538
|
+
* Parity with {@link ElevenLabsWebSocketTTS.setTelephonyCarrier}.
|
|
35539
|
+
*/
|
|
35540
|
+
setTelephonyCarrier(carrier) {
|
|
35541
|
+
if (this._outputFormatExplicit) return;
|
|
35542
|
+
if (carrier === "twilio") {
|
|
35543
|
+
this._outputFormat = ElevenLabsOutputFormat.ULAW_8000;
|
|
35544
|
+
} else if (carrier === "telnyx") {
|
|
35545
|
+
this._outputFormat = ElevenLabsOutputFormat.PCM_16000;
|
|
35546
|
+
}
|
|
35547
|
+
}
|
|
35548
|
+
/**
|
|
35549
|
+
* Construct an instance pre-configured for Twilio Media Streams.
|
|
35550
|
+
*
|
|
35551
|
+
* Sets `outputFormat='ulaw_8000'` so ElevenLabs emits μ-law @ 8 kHz
|
|
35552
|
+
* directly — the exact wire format Twilio's media stream uses — letting
|
|
35553
|
+
* the SDK skip the 16 kHz→8 kHz resample and PCM→μ-law conversion in
|
|
35554
|
+
* `TwilioAudioSender`. Saves ~30–80 ms first-byte and per-frame CPU,
|
|
35555
|
+
* and removes a potential aliasing source.
|
|
35556
|
+
*
|
|
35557
|
+
* `voiceSettings` defaults to a low-bandwidth-friendly profile
|
|
35558
|
+
* (speaker boost off, modest stability) which sounds cleaner at 8 kHz
|
|
35559
|
+
* μ-law than the studio default. Pass an explicit object to override.
|
|
35560
|
+
*/
|
|
35561
|
+
static forTwilio(apiKey, options = {}) {
|
|
35562
|
+
const voiceSettings = options.voiceSettings ?? {
|
|
35563
|
+
// Speaker boost adds high-frequency emphasis that aliases ugly over an
|
|
35564
|
+
// 8 kHz μ-law line. Slightly higher stability tames the excursions
|
|
35565
|
+
// that compander quantization noise can amplify.
|
|
35566
|
+
stability: 0.6,
|
|
35567
|
+
similarity_boost: 0.75,
|
|
35568
|
+
use_speaker_boost: false
|
|
35569
|
+
};
|
|
35570
|
+
return new _ElevenLabsTTS(apiKey, {
|
|
35571
|
+
...options,
|
|
35572
|
+
voiceSettings,
|
|
35573
|
+
outputFormat: ElevenLabsOutputFormat.ULAW_8000
|
|
35574
|
+
});
|
|
35575
|
+
}
|
|
35576
|
+
/**
|
|
35577
|
+
* Construct an instance pre-configured for Telnyx bidirectional media.
|
|
35578
|
+
*
|
|
35579
|
+
* Telnyx's default media-streaming codec is L16 PCM @ 16 kHz, which
|
|
35580
|
+
* matches our default Telnyx handler. We pick `pcm_16000` so the audio
|
|
35581
|
+
* flows end-to-end with zero resampling or transcoding.
|
|
35582
|
+
*
|
|
35583
|
+
* Trade-off: if your Telnyx profile is pinned to PCMU/8000 (μ-law),
|
|
35584
|
+
* construct `ElevenLabsTTS` directly with `outputFormat: 'ulaw_8000'`
|
|
35585
|
+
* — Telnyx supports that natively too.
|
|
35586
|
+
*/
|
|
35587
|
+
static forTelnyx(apiKey, options = {}) {
|
|
35588
|
+
return new _ElevenLabsTTS(apiKey, {
|
|
35589
|
+
...options,
|
|
35590
|
+
outputFormat: ElevenLabsOutputFormat.PCM_16000
|
|
35591
|
+
});
|
|
35592
|
+
}
|
|
35593
|
+
/**
|
|
35594
|
+
* Synthesise text to speech and return the full audio as a single Buffer.
|
|
35595
|
+
*
|
|
35596
|
+
* For large chunks (or when latency matters) call `synthesizeStream` instead.
|
|
35597
|
+
*/
|
|
35598
|
+
async synthesize(text) {
|
|
35599
|
+
const chunks = [];
|
|
35600
|
+
for await (const chunk of this.synthesizeStream(text)) {
|
|
35601
|
+
chunks.push(chunk);
|
|
35602
|
+
}
|
|
35603
|
+
return Buffer.concat(chunks);
|
|
35604
|
+
}
|
|
35605
|
+
/**
|
|
35606
|
+
* Synthesise text and yield audio chunks as they arrive (streaming).
|
|
35607
|
+
*
|
|
35608
|
+
* The yielded buffers are raw PCM at 16 kHz (or whatever `outputFormat` is
|
|
35609
|
+
* configured to). `chunkSize` controls the maximum yield size — 512 is a
|
|
35610
|
+
* good choice for low-latency telephony.
|
|
35611
|
+
*/
|
|
35612
|
+
async *synthesizeStream(text) {
|
|
35613
|
+
const url2 = `${ELEVENLABS_BASE_URL}/text-to-speech/${encodeURIComponent(this.voiceId)}/stream?output_format=${encodeURIComponent(this._outputFormat)}`;
|
|
35614
|
+
const body = {
|
|
35615
|
+
text,
|
|
35616
|
+
model_id: this.modelId
|
|
35617
|
+
};
|
|
35618
|
+
if (this.voiceSettings) body["voice_settings"] = this.voiceSettings;
|
|
35619
|
+
if (this.languageCode) body["language_code"] = this.languageCode;
|
|
35620
|
+
const response = await fetch(url2, {
|
|
35621
|
+
method: "POST",
|
|
35622
|
+
headers: {
|
|
35623
|
+
"xi-api-key": this.apiKey,
|
|
35624
|
+
"Content-Type": "application/json"
|
|
35625
|
+
},
|
|
35626
|
+
body: JSON.stringify(body),
|
|
35627
|
+
signal: AbortSignal.timeout(3e4)
|
|
35628
|
+
});
|
|
35629
|
+
if (!response.ok) {
|
|
35630
|
+
const errBody = await response.text();
|
|
35631
|
+
throw new Error(`ElevenLabs TTS error ${response.status}: ${errBody}`);
|
|
35632
|
+
}
|
|
35633
|
+
if (!response.body) {
|
|
35634
|
+
throw new Error("ElevenLabs TTS: no response body");
|
|
35635
|
+
}
|
|
35636
|
+
const reader = response.body.getReader();
|
|
35637
|
+
try {
|
|
35638
|
+
while (true) {
|
|
35639
|
+
const { done, value } = await reader.read();
|
|
35640
|
+
if (done) break;
|
|
35641
|
+
if (!value || value.length === 0) continue;
|
|
35642
|
+
const buf = Buffer.from(value);
|
|
35643
|
+
for (let offset = 0; offset < buf.length; offset += this.chunkSize) {
|
|
35644
|
+
yield buf.subarray(offset, Math.min(offset + this.chunkSize, buf.length));
|
|
35645
|
+
}
|
|
35646
|
+
}
|
|
35647
|
+
} finally {
|
|
35648
|
+
if (typeof reader.cancel === "function") await reader.cancel().catch(() => {
|
|
35649
|
+
});
|
|
35650
|
+
reader.releaseLock();
|
|
35651
|
+
}
|
|
35652
|
+
}
|
|
35653
|
+
};
|
|
35654
|
+
|
|
35655
|
+
// src/index.ts
|
|
35656
|
+
init_deepgram_stt();
|
|
35657
|
+
|
|
35658
|
+
// src/providers/cartesia-tts.ts
|
|
35659
|
+
init_cjs_shims();
|
|
35660
|
+
init_logger();
|
|
35661
|
+
var CARTESIA_BASE_URL = "https://api.cartesia.ai";
|
|
35662
|
+
var CARTESIA_API_VERSION = "2025-04-16";
|
|
35663
|
+
var CARTESIA_DEFAULT_VOICE_ID = "f786b574-daa5-4673-aa0c-cbe3e8534c02";
|
|
35664
|
+
var CartesiaTTSModel = {
|
|
35665
|
+
SONIC_3: "sonic-3",
|
|
35666
|
+
SONIC_2: "sonic-2",
|
|
35667
|
+
SONIC: "sonic"
|
|
35668
|
+
};
|
|
35669
|
+
var CartesiaTTSContainer = {
|
|
35670
|
+
RAW: "raw",
|
|
35671
|
+
WAV: "wav",
|
|
35672
|
+
MP3: "mp3"
|
|
35673
|
+
};
|
|
35674
|
+
var CartesiaTTSEncoding = {
|
|
35675
|
+
PCM_S16LE: "pcm_s16le",
|
|
35676
|
+
PCM_F32LE: "pcm_f32le",
|
|
35677
|
+
PCM_MULAW: "pcm_mulaw",
|
|
35678
|
+
PCM_ALAW: "pcm_alaw"
|
|
35679
|
+
};
|
|
35680
|
+
var CartesiaTTSSampleRate = {
|
|
35681
|
+
HZ_8000: 8e3,
|
|
35682
|
+
HZ_16000: 16e3,
|
|
35683
|
+
HZ_22050: 22050,
|
|
35684
|
+
HZ_24000: 24e3,
|
|
35685
|
+
HZ_44100: 44100
|
|
35686
|
+
};
|
|
35687
|
+
var CartesiaTTSVoiceMode = {
|
|
35688
|
+
ID: "id",
|
|
35689
|
+
EMBEDDING: "embedding"
|
|
35690
|
+
};
|
|
35691
|
+
var CartesiaTTS = class _CartesiaTTS {
|
|
35692
|
+
/** Stable pricing/dashboard key — read by stream-handler/metrics. */
|
|
35693
|
+
static providerKey = "cartesia_tts";
|
|
35694
|
+
apiKey;
|
|
35695
|
+
model;
|
|
35696
|
+
voice;
|
|
35697
|
+
language;
|
|
35698
|
+
sampleRate;
|
|
35699
|
+
speed;
|
|
35700
|
+
emotion;
|
|
35701
|
+
volume;
|
|
35702
|
+
baseUrl;
|
|
35703
|
+
apiVersion;
|
|
35704
|
+
constructor(apiKey, opts = {}) {
|
|
35705
|
+
this.apiKey = apiKey;
|
|
35706
|
+
this.model = opts.model ?? CartesiaTTSModel.SONIC_3;
|
|
35707
|
+
this.voice = opts.voice ?? CARTESIA_DEFAULT_VOICE_ID;
|
|
35708
|
+
this.language = opts.language ?? "en";
|
|
35709
|
+
this.sampleRate = opts.sampleRate ?? CartesiaTTSSampleRate.HZ_16000;
|
|
35710
|
+
this.speed = opts.speed;
|
|
35711
|
+
this.emotion = typeof opts.emotion === "string" ? [opts.emotion] : opts.emotion;
|
|
35712
|
+
this.volume = opts.volume;
|
|
35713
|
+
this.baseUrl = opts.baseUrl ?? CARTESIA_BASE_URL;
|
|
35714
|
+
this.apiVersion = opts.apiVersion ?? CARTESIA_API_VERSION;
|
|
35715
|
+
}
|
|
35716
|
+
/**
|
|
35717
|
+
* Construct an instance pre-configured for Twilio Media Streams.
|
|
35718
|
+
*
|
|
35719
|
+
* Sets `sampleRate=8000` so Cartesia emits PCM_S16LE @ 8 kHz directly.
|
|
35720
|
+
* Twilio's media stream uses μ-law @ 8 kHz so the SDK still does the
|
|
35721
|
+
* PCM → μ-law transcode client-side, but the 16 kHz → 8 kHz resample
|
|
35722
|
+
* step is skipped. Saves ~10–30 ms first-byte plus per-frame CPU and
|
|
35723
|
+
* removes a potential aliasing source.
|
|
35724
|
+
*/
|
|
35725
|
+
static forTwilio(apiKey, options = {}) {
|
|
35726
|
+
return new _CartesiaTTS(apiKey, {
|
|
35727
|
+
...options,
|
|
35728
|
+
sampleRate: CartesiaTTSSampleRate.HZ_8000
|
|
35729
|
+
});
|
|
35730
|
+
}
|
|
35731
|
+
/**
|
|
35732
|
+
* Construct an instance pre-configured for Telnyx bidirectional media.
|
|
35733
|
+
*
|
|
35734
|
+
* Sets `sampleRate=16000` to match Telnyx's L16/16000 default codec —
|
|
35735
|
+
* audio flows end-to-end with zero resampling or transcoding. Same as
|
|
35736
|
+
* the bare-constructor default; exists for API symmetry with
|
|
35737
|
+
* {@link CartesiaTTS.forTwilio}.
|
|
35738
|
+
*/
|
|
35739
|
+
static forTelnyx(apiKey, options = {}) {
|
|
35740
|
+
return new _CartesiaTTS(apiKey, {
|
|
35741
|
+
...options,
|
|
35742
|
+
sampleRate: CartesiaTTSSampleRate.HZ_16000
|
|
35743
|
+
});
|
|
35744
|
+
}
|
|
35745
|
+
/** Build the JSON payload for the Cartesia bytes endpoint. */
|
|
35746
|
+
buildPayload(text) {
|
|
35747
|
+
const payload = {
|
|
35748
|
+
model_id: this.model,
|
|
35749
|
+
voice: { mode: CartesiaTTSVoiceMode.ID, id: this.voice },
|
|
35750
|
+
transcript: text,
|
|
35751
|
+
output_format: {
|
|
35752
|
+
container: CartesiaTTSContainer.RAW,
|
|
35753
|
+
encoding: CartesiaTTSEncoding.PCM_S16LE,
|
|
35754
|
+
sample_rate: this.sampleRate
|
|
35755
|
+
},
|
|
35756
|
+
language: this.language
|
|
35757
|
+
};
|
|
35758
|
+
const generationConfig = {};
|
|
35759
|
+
if (this.speed !== void 0) generationConfig.speed = this.speed;
|
|
35760
|
+
if (this.emotion && this.emotion.length > 0)
|
|
35761
|
+
generationConfig.emotion = this.emotion[0];
|
|
35762
|
+
if (this.volume !== void 0) generationConfig.volume = this.volume;
|
|
35763
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
35764
|
+
payload.generation_config = generationConfig;
|
|
35765
|
+
}
|
|
35766
|
+
return payload;
|
|
35767
|
+
}
|
|
35768
|
+
/**
|
|
35769
|
+
* Pre-call HTTP warmup for the Cartesia `/tts/bytes` endpoint.
|
|
35770
|
+
*
|
|
35771
|
+
* Issues a lightweight `GET <baseUrl>/voices` so DNS, TLS, and HTTP/2
|
|
35772
|
+
* are already up by the time the first `synthesizeStream()` POST
|
|
35773
|
+
* lands. Best-effort: 5 s timeout, all exceptions swallowed at
|
|
35774
|
+
* debug level.
|
|
35775
|
+
*
|
|
35776
|
+
* Billing safety: `GET /voices` is a free metadata read on
|
|
35777
|
+
* Cartesia's REST surface (per https://docs.cartesia.ai). It does
|
|
35778
|
+
* not consume synthesis credits. The actual synthesis is billed
|
|
35779
|
+
* only when `POST /tts/bytes` runs with a non-empty `transcript`.
|
|
35780
|
+
*
|
|
35781
|
+
* Note: Cartesia TTS uses the HTTP path (vs the WebSocket variant
|
|
35782
|
+
* Cartesia also exposes) — connection warmup is therefore HTTP-GET
|
|
35783
|
+
* based, not WebSocket pre-handshake. The latency win is smaller
|
|
35784
|
+
* (~50-150 ms vs the ~200-500 ms of a WS prewarm) but still real.
|
|
35785
|
+
*/
|
|
35786
|
+
async warmup() {
|
|
35787
|
+
try {
|
|
35788
|
+
await fetch(`${this.baseUrl}/voices`, {
|
|
35789
|
+
method: "GET",
|
|
35790
|
+
headers: {
|
|
35791
|
+
"X-API-Key": this.apiKey,
|
|
35792
|
+
"Cartesia-Version": this.apiVersion
|
|
35793
|
+
},
|
|
35794
|
+
signal: AbortSignal.timeout(5e3)
|
|
35795
|
+
});
|
|
35796
|
+
} catch (err) {
|
|
35797
|
+
getLogger().debug(`Cartesia TTS warmup failed (best-effort): ${String(err)}`);
|
|
35798
|
+
}
|
|
35799
|
+
}
|
|
35800
|
+
/** Synthesize text and return the concatenated audio buffer. */
|
|
35801
|
+
async synthesize(text) {
|
|
35802
|
+
const chunks = [];
|
|
35803
|
+
for await (const chunk of this.synthesizeStream(text)) {
|
|
35804
|
+
chunks.push(chunk);
|
|
35805
|
+
}
|
|
35806
|
+
return Buffer.concat(chunks);
|
|
35807
|
+
}
|
|
35808
|
+
/**
|
|
35809
|
+
* Synthesize text and yield raw PCM_S16LE chunks at the configured
|
|
35810
|
+
* `sampleRate` as they arrive from Cartesia.
|
|
35811
|
+
*/
|
|
35812
|
+
async *synthesizeStream(text) {
|
|
35813
|
+
const response = await fetch(`${this.baseUrl}/tts/bytes`, {
|
|
35814
|
+
method: "POST",
|
|
35815
|
+
headers: {
|
|
35816
|
+
"X-API-Key": this.apiKey,
|
|
35817
|
+
"Cartesia-Version": this.apiVersion,
|
|
35818
|
+
"Content-Type": "application/json"
|
|
35819
|
+
},
|
|
35820
|
+
body: JSON.stringify(this.buildPayload(text)),
|
|
35821
|
+
signal: AbortSignal.timeout(3e4)
|
|
35822
|
+
});
|
|
35823
|
+
if (!response.ok) {
|
|
35824
|
+
const body = await response.text();
|
|
35825
|
+
throw new Error(`Cartesia TTS error ${response.status}: ${body}`);
|
|
35826
|
+
}
|
|
35827
|
+
if (!response.body) {
|
|
35828
|
+
throw new Error("Cartesia TTS: no response body");
|
|
35829
|
+
}
|
|
35830
|
+
const reader = response.body.getReader();
|
|
35831
|
+
try {
|
|
35832
|
+
while (true) {
|
|
35833
|
+
const { done, value } = await reader.read();
|
|
35834
|
+
if (done) break;
|
|
35835
|
+
if (value && value.length > 0) {
|
|
35836
|
+
yield Buffer.from(value);
|
|
35837
|
+
}
|
|
35838
|
+
}
|
|
35839
|
+
} finally {
|
|
35840
|
+
if (typeof reader.cancel === "function")
|
|
35841
|
+
await reader.cancel().catch(() => {
|
|
35842
|
+
});
|
|
35843
|
+
reader.releaseLock();
|
|
35844
|
+
}
|
|
35845
|
+
}
|
|
35846
|
+
};
|
|
35847
|
+
|
|
35848
|
+
// src/providers/rime-tts.ts
|
|
35849
|
+
init_cjs_shims();
|
|
35850
|
+
var RIME_BASE_URL = "https://users.rime.ai/v1/rime-tts";
|
|
35851
|
+
var RimeModel = {
|
|
35852
|
+
ARCANA: "arcana",
|
|
35853
|
+
MIST: "mist",
|
|
35854
|
+
MIST_V2: "mistv2"
|
|
35855
|
+
};
|
|
35856
|
+
var RimeAudioFormat = {
|
|
35857
|
+
PCM: "audio/pcm",
|
|
35858
|
+
MP3: "audio/mp3",
|
|
35859
|
+
WAV: "audio/wav",
|
|
35860
|
+
MULAW: "audio/mulaw"
|
|
35861
|
+
};
|
|
35862
|
+
var ARCANA_MODEL_TIMEOUT_MS = 60 * 4 * 1e3;
|
|
35863
|
+
var MIST_MODEL_TIMEOUT_MS = 30 * 1e3;
|
|
35864
|
+
function isMistModel(model) {
|
|
35865
|
+
return model.includes(RimeModel.MIST);
|
|
35866
|
+
}
|
|
35867
|
+
function timeoutForModel(model) {
|
|
35868
|
+
if (model === RimeModel.ARCANA) return ARCANA_MODEL_TIMEOUT_MS;
|
|
35869
|
+
return MIST_MODEL_TIMEOUT_MS;
|
|
35870
|
+
}
|
|
35871
|
+
var RimeTTS = class {
|
|
35872
|
+
/** Stable pricing/dashboard key — read by stream-handler/metrics. */
|
|
35873
|
+
static providerKey = "rime";
|
|
35874
|
+
apiKey;
|
|
35875
|
+
model;
|
|
35876
|
+
speaker;
|
|
35877
|
+
lang;
|
|
35878
|
+
sampleRate;
|
|
35879
|
+
repetitionPenalty;
|
|
35880
|
+
temperature;
|
|
35881
|
+
topP;
|
|
35882
|
+
maxTokens;
|
|
35883
|
+
speedAlpha;
|
|
35884
|
+
reduceLatency;
|
|
35885
|
+
pauseBetweenBrackets;
|
|
35886
|
+
phonemizeBetweenBrackets;
|
|
35887
|
+
baseUrl;
|
|
35888
|
+
totalTimeoutMs;
|
|
35889
|
+
constructor(apiKey, opts = {}) {
|
|
35890
|
+
this.apiKey = apiKey;
|
|
35891
|
+
this.model = opts.model ?? RimeModel.ARCANA;
|
|
35892
|
+
const defaultSpeaker = isMistModel(this.model) ? "cove" : "astra";
|
|
35893
|
+
this.speaker = opts.speaker ?? defaultSpeaker;
|
|
35894
|
+
this.lang = opts.lang ?? "eng";
|
|
35895
|
+
this.sampleRate = opts.sampleRate ?? 16e3;
|
|
35896
|
+
this.repetitionPenalty = opts.repetitionPenalty;
|
|
35897
|
+
this.temperature = opts.temperature;
|
|
35898
|
+
this.topP = opts.topP;
|
|
35899
|
+
this.maxTokens = opts.maxTokens;
|
|
35900
|
+
this.speedAlpha = opts.speedAlpha;
|
|
35901
|
+
this.reduceLatency = opts.reduceLatency;
|
|
35902
|
+
this.pauseBetweenBrackets = opts.pauseBetweenBrackets;
|
|
35903
|
+
this.phonemizeBetweenBrackets = opts.phonemizeBetweenBrackets;
|
|
35904
|
+
this.baseUrl = opts.baseUrl ?? RIME_BASE_URL;
|
|
35905
|
+
this.totalTimeoutMs = timeoutForModel(this.model);
|
|
35906
|
+
}
|
|
35907
|
+
buildPayload(text) {
|
|
35908
|
+
const payload = {
|
|
35909
|
+
speaker: this.speaker,
|
|
35910
|
+
text,
|
|
35911
|
+
modelId: this.model
|
|
35912
|
+
};
|
|
35913
|
+
if (this.model === RimeModel.ARCANA) {
|
|
35914
|
+
if (this.repetitionPenalty !== void 0)
|
|
35915
|
+
payload.repetition_penalty = this.repetitionPenalty;
|
|
35916
|
+
if (this.temperature !== void 0) payload.temperature = this.temperature;
|
|
35917
|
+
if (this.topP !== void 0) payload.top_p = this.topP;
|
|
35918
|
+
if (this.maxTokens !== void 0) payload.max_tokens = this.maxTokens;
|
|
35919
|
+
payload.lang = this.lang;
|
|
35920
|
+
payload.samplingRate = this.sampleRate;
|
|
35921
|
+
} else if (isMistModel(this.model)) {
|
|
35922
|
+
payload.lang = this.lang;
|
|
35923
|
+
payload.samplingRate = this.sampleRate;
|
|
35924
|
+
if (this.speedAlpha !== void 0) payload.speedAlpha = this.speedAlpha;
|
|
35925
|
+
if (this.model === RimeModel.MIST_V2 && this.reduceLatency !== void 0) {
|
|
35926
|
+
payload.reduceLatency = this.reduceLatency;
|
|
35927
|
+
}
|
|
35928
|
+
if (this.pauseBetweenBrackets !== void 0) {
|
|
35929
|
+
payload.pauseBetweenBrackets = this.pauseBetweenBrackets;
|
|
35930
|
+
}
|
|
35931
|
+
if (this.phonemizeBetweenBrackets !== void 0) {
|
|
35932
|
+
payload.phonemizeBetweenBrackets = this.phonemizeBetweenBrackets;
|
|
35933
|
+
}
|
|
35934
|
+
}
|
|
35935
|
+
return payload;
|
|
35936
|
+
}
|
|
35937
|
+
/** Synthesize text and return the concatenated audio buffer. */
|
|
35938
|
+
async synthesize(text) {
|
|
35939
|
+
const chunks = [];
|
|
35940
|
+
for await (const chunk of this.synthesizeStream(text)) {
|
|
35941
|
+
chunks.push(chunk);
|
|
35942
|
+
}
|
|
35943
|
+
return Buffer.concat(chunks);
|
|
35944
|
+
}
|
|
35945
|
+
/**
|
|
35946
|
+
* Synthesize text and yield raw PCM_S16LE chunks at the configured
|
|
35947
|
+
* `sampleRate` as they stream in.
|
|
35948
|
+
*/
|
|
35949
|
+
async *synthesizeStream(text) {
|
|
35950
|
+
const response = await fetch(this.baseUrl, {
|
|
35951
|
+
method: "POST",
|
|
35952
|
+
headers: {
|
|
35953
|
+
accept: RimeAudioFormat.PCM,
|
|
35954
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
35955
|
+
"content-type": "application/json"
|
|
35956
|
+
},
|
|
35957
|
+
body: JSON.stringify(this.buildPayload(text)),
|
|
35958
|
+
signal: AbortSignal.timeout(this.totalTimeoutMs)
|
|
35959
|
+
});
|
|
35960
|
+
if (!response.ok) {
|
|
35961
|
+
const body = await response.text();
|
|
35962
|
+
throw new Error(`Rime TTS error ${response.status}: ${body}`);
|
|
35963
|
+
}
|
|
35964
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
35965
|
+
if (!contentType.startsWith("audio")) {
|
|
35966
|
+
const body = await response.text();
|
|
35967
|
+
throw new Error(`Rime returned non-audio response: ${body.slice(0, 500)}`);
|
|
35968
|
+
}
|
|
35969
|
+
if (!response.body) {
|
|
35970
|
+
throw new Error("Rime TTS: no response body");
|
|
35971
|
+
}
|
|
35972
|
+
const reader = response.body.getReader();
|
|
35973
|
+
try {
|
|
35974
|
+
while (true) {
|
|
35975
|
+
const { done, value } = await reader.read();
|
|
35976
|
+
if (done) break;
|
|
35977
|
+
if (value && value.length > 0) {
|
|
35978
|
+
yield Buffer.from(value);
|
|
35979
|
+
}
|
|
35980
|
+
}
|
|
35981
|
+
} finally {
|
|
35982
|
+
if (typeof reader.cancel === "function")
|
|
35983
|
+
await reader.cancel().catch(() => {
|
|
35984
|
+
});
|
|
35985
|
+
reader.releaseLock();
|
|
35986
|
+
}
|
|
35987
|
+
}
|
|
35988
|
+
};
|
|
35989
|
+
|
|
35990
|
+
// src/index.ts
|
|
35991
|
+
init_pricing();
|
|
35992
|
+
|
|
34998
35993
|
// src/stt/deepgram.ts
|
|
34999
35994
|
init_cjs_shims();
|
|
35000
35995
|
init_deepgram_stt();
|
|
@@ -35325,14 +36320,14 @@ var CartesiaSTT = class {
|
|
|
35325
36320
|
const ws = new import_ws7.default(url2, {
|
|
35326
36321
|
headers: { "User-Agent": USER_AGENT }
|
|
35327
36322
|
});
|
|
35328
|
-
await new Promise((
|
|
36323
|
+
await new Promise((resolve2, reject) => {
|
|
35329
36324
|
const timer = setTimeout(
|
|
35330
36325
|
() => reject(new Error("Cartesia STT park connect timeout")),
|
|
35331
36326
|
CONNECT_TIMEOUT_MS
|
|
35332
36327
|
);
|
|
35333
36328
|
ws.once("open", () => {
|
|
35334
36329
|
clearTimeout(timer);
|
|
35335
|
-
|
|
36330
|
+
resolve2();
|
|
35336
36331
|
});
|
|
35337
36332
|
ws.once("error", (err) => {
|
|
35338
36333
|
clearTimeout(timer);
|
|
@@ -35382,7 +36377,7 @@ var CartesiaSTT = class {
|
|
|
35382
36377
|
const url2 = this.buildWsUrl();
|
|
35383
36378
|
let ws = null;
|
|
35384
36379
|
try {
|
|
35385
|
-
ws = await new Promise((
|
|
36380
|
+
ws = await new Promise((resolve2, reject) => {
|
|
35386
36381
|
const sock = new import_ws7.default(url2, {
|
|
35387
36382
|
headers: { "User-Agent": USER_AGENT }
|
|
35388
36383
|
});
|
|
@@ -35395,7 +36390,7 @@ var CartesiaSTT = class {
|
|
|
35395
36390
|
}, 5e3);
|
|
35396
36391
|
sock.once("open", () => {
|
|
35397
36392
|
clearTimeout(timer);
|
|
35398
|
-
|
|
36393
|
+
resolve2(sock);
|
|
35399
36394
|
});
|
|
35400
36395
|
sock.once("error", (err) => {
|
|
35401
36396
|
clearTimeout(timer);
|
|
@@ -35422,14 +36417,14 @@ var CartesiaSTT = class {
|
|
|
35422
36417
|
this.ws = new import_ws7.default(url2, {
|
|
35423
36418
|
headers: { "User-Agent": USER_AGENT }
|
|
35424
36419
|
});
|
|
35425
|
-
await new Promise((
|
|
36420
|
+
await new Promise((resolve2, reject) => {
|
|
35426
36421
|
const timer = setTimeout(
|
|
35427
36422
|
() => reject(new Error("Cartesia STT connect timeout")),
|
|
35428
36423
|
CONNECT_TIMEOUT_MS
|
|
35429
36424
|
);
|
|
35430
36425
|
this.ws.once("open", () => {
|
|
35431
36426
|
clearTimeout(timer);
|
|
35432
|
-
|
|
36427
|
+
resolve2();
|
|
35433
36428
|
});
|
|
35434
36429
|
this.ws.once("error", (err) => {
|
|
35435
36430
|
clearTimeout(timer);
|
|
@@ -35517,12 +36512,12 @@ var CartesiaSTT = class {
|
|
|
35517
36512
|
*/
|
|
35518
36513
|
async finalize() {
|
|
35519
36514
|
if (!this.ws || this.ws.readyState !== import_ws7.default.OPEN) return;
|
|
35520
|
-
await new Promise((
|
|
36515
|
+
await new Promise((resolve2) => {
|
|
35521
36516
|
this.ws.send(CartesiaSTTClientFrame.FINALIZE, (err) => {
|
|
35522
36517
|
if (err) {
|
|
35523
36518
|
getLogger().debug(`Cartesia finalize send failed: ${String(err)}`);
|
|
35524
36519
|
}
|
|
35525
|
-
|
|
36520
|
+
resolve2();
|
|
35526
36521
|
});
|
|
35527
36522
|
});
|
|
35528
36523
|
}
|
|
@@ -35571,10 +36566,10 @@ var CartesiaSTT = class {
|
|
|
35571
36566
|
if (!ws) return;
|
|
35572
36567
|
if (ws.readyState === import_ws7.default.OPEN) {
|
|
35573
36568
|
try {
|
|
35574
|
-
await new Promise((
|
|
36569
|
+
await new Promise((resolve2) => {
|
|
35575
36570
|
ws.send(CartesiaSTTClientFrame.FINALIZE, (err) => {
|
|
35576
36571
|
if (err) getLogger().warn(`CartesiaSTT finalize send failed: ${String(err)}`);
|
|
35577
|
-
|
|
36572
|
+
resolve2();
|
|
35578
36573
|
});
|
|
35579
36574
|
});
|
|
35580
36575
|
} catch (err) {
|
|
@@ -35582,18 +36577,18 @@ var CartesiaSTT = class {
|
|
|
35582
36577
|
}
|
|
35583
36578
|
}
|
|
35584
36579
|
if (ws.readyState === import_ws7.default.OPEN || ws.readyState === import_ws7.default.CONNECTING) {
|
|
35585
|
-
await new Promise((
|
|
36580
|
+
await new Promise((resolve2) => {
|
|
35586
36581
|
const done = () => {
|
|
35587
36582
|
ws.off("close", done);
|
|
35588
36583
|
ws.off("error", done);
|
|
35589
|
-
|
|
36584
|
+
resolve2();
|
|
35590
36585
|
};
|
|
35591
36586
|
ws.once("close", done);
|
|
35592
36587
|
ws.once("error", done);
|
|
35593
36588
|
try {
|
|
35594
36589
|
ws.close();
|
|
35595
36590
|
} catch {
|
|
35596
|
-
|
|
36591
|
+
resolve2();
|
|
35597
36592
|
}
|
|
35598
36593
|
});
|
|
35599
36594
|
}
|
|
@@ -35758,11 +36753,11 @@ var SonioxSTT = class _SonioxSTT {
|
|
|
35758
36753
|
async connect() {
|
|
35759
36754
|
this.final.reset();
|
|
35760
36755
|
this.ws = new import_ws8.default(this.baseUrl);
|
|
35761
|
-
await new Promise((
|
|
36756
|
+
await new Promise((resolve2, reject) => {
|
|
35762
36757
|
const timer = setTimeout(() => reject(new Error("Soniox connect timeout")), 1e4);
|
|
35763
36758
|
this.ws.once("open", () => {
|
|
35764
36759
|
clearTimeout(timer);
|
|
35765
|
-
|
|
36760
|
+
resolve2();
|
|
35766
36761
|
});
|
|
35767
36762
|
this.ws.once("error", (err) => {
|
|
35768
36763
|
clearTimeout(timer);
|
|
@@ -36080,7 +37075,7 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
36080
37075
|
const headers = this.buildHeaders();
|
|
36081
37076
|
let ws = null;
|
|
36082
37077
|
try {
|
|
36083
|
-
ws = await new Promise((
|
|
37078
|
+
ws = await new Promise((resolve2, reject) => {
|
|
36084
37079
|
const sock = new import_ws9.default(url2, { headers });
|
|
36085
37080
|
const timer = setTimeout(() => {
|
|
36086
37081
|
try {
|
|
@@ -36091,7 +37086,7 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
36091
37086
|
}, 5e3);
|
|
36092
37087
|
sock.once("open", () => {
|
|
36093
37088
|
clearTimeout(timer);
|
|
36094
|
-
|
|
37089
|
+
resolve2(sock);
|
|
36095
37090
|
});
|
|
36096
37091
|
sock.once("error", (err) => {
|
|
36097
37092
|
clearTimeout(timer);
|
|
@@ -36125,14 +37120,14 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
36125
37120
|
this.attachHandlers(this.ws);
|
|
36126
37121
|
}
|
|
36127
37122
|
async awaitOpen(ws) {
|
|
36128
|
-
await new Promise((
|
|
37123
|
+
await new Promise((resolve2, reject) => {
|
|
36129
37124
|
const timer = setTimeout(
|
|
36130
37125
|
() => reject(new Error("AssemblyAI connect timeout")),
|
|
36131
37126
|
CONNECT_TIMEOUT_MS2
|
|
36132
37127
|
);
|
|
36133
37128
|
ws.once("open", () => {
|
|
36134
37129
|
clearTimeout(timer);
|
|
36135
|
-
|
|
37130
|
+
resolve2();
|
|
36136
37131
|
});
|
|
36137
37132
|
ws.once("error", (err) => {
|
|
36138
37133
|
clearTimeout(timer);
|
|
@@ -36319,14 +37314,14 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
36319
37314
|
this.ws.send(JSON.stringify({ type: AssemblyAIClientFrame.TERMINATE }));
|
|
36320
37315
|
} catch {
|
|
36321
37316
|
}
|
|
36322
|
-
await new Promise((
|
|
37317
|
+
await new Promise((resolve2) => {
|
|
36323
37318
|
const timer = setTimeout(() => {
|
|
36324
37319
|
this.terminationResolve = null;
|
|
36325
|
-
|
|
37320
|
+
resolve2();
|
|
36326
37321
|
}, TERMINATION_WAIT_TIMEOUT_MS);
|
|
36327
37322
|
this.terminationResolve = () => {
|
|
36328
37323
|
clearTimeout(timer);
|
|
36329
|
-
|
|
37324
|
+
resolve2();
|
|
36330
37325
|
};
|
|
36331
37326
|
});
|
|
36332
37327
|
try {
|
|
@@ -36513,7 +37508,7 @@ var SpeechmaticsSTT = class {
|
|
|
36513
37508
|
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
36514
37509
|
});
|
|
36515
37510
|
this.ws = ws;
|
|
36516
|
-
await new Promise((
|
|
37511
|
+
await new Promise((resolve2, reject) => {
|
|
36517
37512
|
let settled = false;
|
|
36518
37513
|
const settle = (fn) => {
|
|
36519
37514
|
if (settled) return;
|
|
@@ -36527,7 +37522,7 @@ var SpeechmaticsSTT = class {
|
|
|
36527
37522
|
),
|
|
36528
37523
|
CONNECT_TIMEOUT_MS3
|
|
36529
37524
|
);
|
|
36530
|
-
ws.once("open", () => settle(
|
|
37525
|
+
ws.once("open", () => settle(resolve2));
|
|
36531
37526
|
ws.once("error", (err) => settle(() => reject(err)));
|
|
36532
37527
|
ws.once("unexpected-response", (_req, res) => {
|
|
36533
37528
|
const status = res?.statusCode ?? 0;
|
|
@@ -36722,228 +37717,6 @@ var STT7 = class extends SpeechmaticsSTT {
|
|
|
36722
37717
|
|
|
36723
37718
|
// src/tts/elevenlabs.ts
|
|
36724
37719
|
init_cjs_shims();
|
|
36725
|
-
|
|
36726
|
-
// src/providers/elevenlabs-tts.ts
|
|
36727
|
-
init_cjs_shims();
|
|
36728
|
-
var ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
|
|
36729
|
-
var ELEVENLABS_VOICE_ID_BY_NAME = {
|
|
36730
|
-
rachel: "21m00Tcm4TlvDq8ikWAM",
|
|
36731
|
-
drew: "29vD33N1CtxCmqQRPOHJ",
|
|
36732
|
-
clyde: "2EiwWnXFnvU5JabPnv8n",
|
|
36733
|
-
paul: "5Q0t7uMcjvnagumLfvZi",
|
|
36734
|
-
domi: "AZnzlk1XvdvUeBnXmlld",
|
|
36735
|
-
dave: "CYw3kZ02Hs0563khs1Fj",
|
|
36736
|
-
fin: "D38z5RcWu1voky8WS1ja",
|
|
36737
|
-
bella: "EXAVITQu4vr4xnSDxMaL",
|
|
36738
|
-
antoni: "ErXwobaYiN019PkySvjV",
|
|
36739
|
-
thomas: "GBv7mTt0atIp3Br8iCZE",
|
|
36740
|
-
charlie: "IKne3meq5aSn9XLyUdCD",
|
|
36741
|
-
george: "JBFqnCBsd6RMkjVDRZzb",
|
|
36742
|
-
emily: "LcfcDJNUP1GQjkzn1xUU",
|
|
36743
|
-
elli: "MF3mGyEYCl7XYWbV9V6O",
|
|
36744
|
-
callum: "N2lVS1w4EtoT3dr4eOWO",
|
|
36745
|
-
patrick: "ODq5zmih8GrVes37Dizd",
|
|
36746
|
-
harry: "SOYHLrjzK2X1ezoPC6cr",
|
|
36747
|
-
liam: "TX3LPaxmHKxFdv7VOQHJ",
|
|
36748
|
-
dorothy: "ThT5KcBeYPX3keUQqHPh",
|
|
36749
|
-
josh: "TxGEqnHWrfWFTfGW9XjX",
|
|
36750
|
-
arnold: "VR6AewLTigWG4xSOukaG",
|
|
36751
|
-
charlotte: "XB0fDUnXU5powFXDhCwa",
|
|
36752
|
-
matilda: "XrExE9yKIg1WjnnlVkGX",
|
|
36753
|
-
matthew: "Yko7PKHZNXotIFUBG7I9",
|
|
36754
|
-
james: "ZQe5CZNOzWyzPSCn5a3c",
|
|
36755
|
-
joseph: "Zlb1dXrM653N07WRdFW3",
|
|
36756
|
-
jeremy: "bVMeCyTHy58xNoL34h3p",
|
|
36757
|
-
michael: "flq6f7yk4E4fJM5XTYuZ",
|
|
36758
|
-
ethan: "g5CIjZEefAph4nQFvHAz",
|
|
36759
|
-
gigi: "jBpfuIE2acCO8z3wKNLl",
|
|
36760
|
-
freya: "jsCqWAovK2LkecY7zXl4",
|
|
36761
|
-
brian: "nPczCjzI2devNBz1zQrb",
|
|
36762
|
-
grace: "oWAxZDx7w5VEj9dCyTzz",
|
|
36763
|
-
daniel: "onwK4e9ZLuTAKqWW03F9",
|
|
36764
|
-
lily: "pFZP5JQG7iQjIQuC4Bku",
|
|
36765
|
-
serena: "pMsXgVXv3BLzUgSXRplE",
|
|
36766
|
-
adam: "pNInz6obpgDQGcFmaJgB",
|
|
36767
|
-
nicole: "piTKgcLEGmPE4e6mEKli",
|
|
36768
|
-
bill: "pqHfZKP75CvOlQylNhV4",
|
|
36769
|
-
jessie: "t0jbNlBVZ17f02VDIeMI",
|
|
36770
|
-
ryan: "wViXBPUzp2ZZixB1xQuM",
|
|
36771
|
-
sam: "yoZ06aMxZJJ28mfd3POQ",
|
|
36772
|
-
glinda: "z9fAnlkpzviPz146aGWa",
|
|
36773
|
-
giovanni: "zcAOhNBS3c14rBihAFp1",
|
|
36774
|
-
mimi: "zrHiDhphv9ZnVXBqCLjz",
|
|
36775
|
-
sarah: "EXAVITQu4vr4xnSDxMaL",
|
|
36776
|
-
alloy: "EXAVITQu4vr4xnSDxMaL"
|
|
36777
|
-
};
|
|
36778
|
-
var VOICE_ID_PATTERN = /^[A-Za-z0-9]{20}$/;
|
|
36779
|
-
function resolveVoiceId(voice) {
|
|
36780
|
-
if (!voice) return voice;
|
|
36781
|
-
if (VOICE_ID_PATTERN.test(voice)) return voice;
|
|
36782
|
-
return ELEVENLABS_VOICE_ID_BY_NAME[voice.toLowerCase()] ?? voice;
|
|
36783
|
-
}
|
|
36784
|
-
var ElevenLabsModel = {
|
|
36785
|
-
V3: "eleven_v3",
|
|
36786
|
-
FLASH_V2_5: "eleven_flash_v2_5",
|
|
36787
|
-
TURBO_V2_5: "eleven_turbo_v2_5",
|
|
36788
|
-
MULTILINGUAL_V2: "eleven_multilingual_v2",
|
|
36789
|
-
MONOLINGUAL_V1: "eleven_monolingual_v1"
|
|
36790
|
-
};
|
|
36791
|
-
var ElevenLabsOutputFormat = {
|
|
36792
|
-
MP3_22050_32: "mp3_22050_32",
|
|
36793
|
-
MP3_44100_32: "mp3_44100_32",
|
|
36794
|
-
MP3_44100_64: "mp3_44100_64",
|
|
36795
|
-
MP3_44100_96: "mp3_44100_96",
|
|
36796
|
-
MP3_44100_128: "mp3_44100_128",
|
|
36797
|
-
MP3_44100_192: "mp3_44100_192",
|
|
36798
|
-
PCM_8000: "pcm_8000",
|
|
36799
|
-
PCM_16000: "pcm_16000",
|
|
36800
|
-
PCM_22050: "pcm_22050",
|
|
36801
|
-
PCM_24000: "pcm_24000",
|
|
36802
|
-
PCM_44100: "pcm_44100",
|
|
36803
|
-
ULAW_8000: "ulaw_8000"
|
|
36804
|
-
};
|
|
36805
|
-
var ElevenLabsTTS = class _ElevenLabsTTS {
|
|
36806
|
-
// Stable pricing/dashboard key — read by stream-handler / metrics via
|
|
36807
|
-
// ``(agent.tts.constructor as any).providerKey``. Without this the cost
|
|
36808
|
-
// calculator falls back to ``constructor.name`` ("ElevenLabsTTS") which
|
|
36809
|
-
// does NOT match the pricing table key "elevenlabs", silently zeroing
|
|
36810
|
-
// TTS cost for callers that construct the raw REST class directly
|
|
36811
|
-
// (exposed at top level as ``ElevenLabsRestTTS``).
|
|
36812
|
-
static providerKey = "elevenlabs";
|
|
36813
|
-
apiKey;
|
|
36814
|
-
voiceId;
|
|
36815
|
-
modelId;
|
|
36816
|
-
outputFormat;
|
|
36817
|
-
voiceSettings;
|
|
36818
|
-
languageCode;
|
|
36819
|
-
chunkSize;
|
|
36820
|
-
constructor(apiKey, voiceIdOrOptions = "21m00Tcm4TlvDq8ikWAM", modelId = ElevenLabsModel.FLASH_V2_5, outputFormat = ElevenLabsOutputFormat.PCM_16000) {
|
|
36821
|
-
this.apiKey = apiKey;
|
|
36822
|
-
if (typeof voiceIdOrOptions === "object") {
|
|
36823
|
-
const o = voiceIdOrOptions;
|
|
36824
|
-
this.voiceId = resolveVoiceId(o.voiceId ?? "21m00Tcm4TlvDq8ikWAM");
|
|
36825
|
-
this.modelId = o.modelId ?? ElevenLabsModel.FLASH_V2_5;
|
|
36826
|
-
this.outputFormat = o.outputFormat ?? ElevenLabsOutputFormat.PCM_16000;
|
|
36827
|
-
this.voiceSettings = o.voiceSettings;
|
|
36828
|
-
this.languageCode = o.languageCode;
|
|
36829
|
-
this.chunkSize = o.chunkSize ?? 4096;
|
|
36830
|
-
} else {
|
|
36831
|
-
this.voiceId = resolveVoiceId(voiceIdOrOptions);
|
|
36832
|
-
this.modelId = modelId;
|
|
36833
|
-
this.outputFormat = outputFormat;
|
|
36834
|
-
this.voiceSettings = void 0;
|
|
36835
|
-
this.languageCode = void 0;
|
|
36836
|
-
this.chunkSize = 4096;
|
|
36837
|
-
}
|
|
36838
|
-
}
|
|
36839
|
-
/**
|
|
36840
|
-
* Construct an instance pre-configured for Twilio Media Streams.
|
|
36841
|
-
*
|
|
36842
|
-
* Sets `outputFormat='ulaw_8000'` so ElevenLabs emits μ-law @ 8 kHz
|
|
36843
|
-
* directly — the exact wire format Twilio's media stream uses — letting
|
|
36844
|
-
* the SDK skip the 16 kHz→8 kHz resample and PCM→μ-law conversion in
|
|
36845
|
-
* `TwilioAudioSender`. Saves ~30–80 ms first-byte and per-frame CPU,
|
|
36846
|
-
* and removes a potential aliasing source.
|
|
36847
|
-
*
|
|
36848
|
-
* `voiceSettings` defaults to a low-bandwidth-friendly profile
|
|
36849
|
-
* (speaker boost off, modest stability) which sounds cleaner at 8 kHz
|
|
36850
|
-
* μ-law than the studio default. Pass an explicit object to override.
|
|
36851
|
-
*/
|
|
36852
|
-
static forTwilio(apiKey, options = {}) {
|
|
36853
|
-
const voiceSettings = options.voiceSettings ?? {
|
|
36854
|
-
// Speaker boost adds high-frequency emphasis that aliases ugly over an
|
|
36855
|
-
// 8 kHz μ-law line. Slightly higher stability tames the excursions
|
|
36856
|
-
// that compander quantization noise can amplify.
|
|
36857
|
-
stability: 0.6,
|
|
36858
|
-
similarity_boost: 0.75,
|
|
36859
|
-
use_speaker_boost: false
|
|
36860
|
-
};
|
|
36861
|
-
return new _ElevenLabsTTS(apiKey, {
|
|
36862
|
-
...options,
|
|
36863
|
-
voiceSettings,
|
|
36864
|
-
outputFormat: ElevenLabsOutputFormat.ULAW_8000
|
|
36865
|
-
});
|
|
36866
|
-
}
|
|
36867
|
-
/**
|
|
36868
|
-
* Construct an instance pre-configured for Telnyx bidirectional media.
|
|
36869
|
-
*
|
|
36870
|
-
* Telnyx's default media-streaming codec is L16 PCM @ 16 kHz, which
|
|
36871
|
-
* matches our default Telnyx handler. We pick `pcm_16000` so the audio
|
|
36872
|
-
* flows end-to-end with zero resampling or transcoding.
|
|
36873
|
-
*
|
|
36874
|
-
* Trade-off: if your Telnyx profile is pinned to PCMU/8000 (μ-law),
|
|
36875
|
-
* construct `ElevenLabsTTS` directly with `outputFormat: 'ulaw_8000'`
|
|
36876
|
-
* — Telnyx supports that natively too.
|
|
36877
|
-
*/
|
|
36878
|
-
static forTelnyx(apiKey, options = {}) {
|
|
36879
|
-
return new _ElevenLabsTTS(apiKey, {
|
|
36880
|
-
...options,
|
|
36881
|
-
outputFormat: ElevenLabsOutputFormat.PCM_16000
|
|
36882
|
-
});
|
|
36883
|
-
}
|
|
36884
|
-
/**
|
|
36885
|
-
* Synthesise text to speech and return the full audio as a single Buffer.
|
|
36886
|
-
*
|
|
36887
|
-
* For large chunks (or when latency matters) call `synthesizeStream` instead.
|
|
36888
|
-
*/
|
|
36889
|
-
async synthesize(text) {
|
|
36890
|
-
const chunks = [];
|
|
36891
|
-
for await (const chunk of this.synthesizeStream(text)) {
|
|
36892
|
-
chunks.push(chunk);
|
|
36893
|
-
}
|
|
36894
|
-
return Buffer.concat(chunks);
|
|
36895
|
-
}
|
|
36896
|
-
/**
|
|
36897
|
-
* Synthesise text and yield audio chunks as they arrive (streaming).
|
|
36898
|
-
*
|
|
36899
|
-
* The yielded buffers are raw PCM at 16 kHz (or whatever `outputFormat` is
|
|
36900
|
-
* configured to). `chunkSize` controls the maximum yield size — 512 is a
|
|
36901
|
-
* good choice for low-latency telephony.
|
|
36902
|
-
*/
|
|
36903
|
-
async *synthesizeStream(text) {
|
|
36904
|
-
const url2 = `${ELEVENLABS_BASE_URL}/text-to-speech/${encodeURIComponent(this.voiceId)}/stream?output_format=${encodeURIComponent(this.outputFormat)}`;
|
|
36905
|
-
const body = {
|
|
36906
|
-
text,
|
|
36907
|
-
model_id: this.modelId
|
|
36908
|
-
};
|
|
36909
|
-
if (this.voiceSettings) body["voice_settings"] = this.voiceSettings;
|
|
36910
|
-
if (this.languageCode) body["language_code"] = this.languageCode;
|
|
36911
|
-
const response = await fetch(url2, {
|
|
36912
|
-
method: "POST",
|
|
36913
|
-
headers: {
|
|
36914
|
-
"xi-api-key": this.apiKey,
|
|
36915
|
-
"Content-Type": "application/json"
|
|
36916
|
-
},
|
|
36917
|
-
body: JSON.stringify(body),
|
|
36918
|
-
signal: AbortSignal.timeout(3e4)
|
|
36919
|
-
});
|
|
36920
|
-
if (!response.ok) {
|
|
36921
|
-
const errBody = await response.text();
|
|
36922
|
-
throw new Error(`ElevenLabs TTS error ${response.status}: ${errBody}`);
|
|
36923
|
-
}
|
|
36924
|
-
if (!response.body) {
|
|
36925
|
-
throw new Error("ElevenLabs TTS: no response body");
|
|
36926
|
-
}
|
|
36927
|
-
const reader = response.body.getReader();
|
|
36928
|
-
try {
|
|
36929
|
-
while (true) {
|
|
36930
|
-
const { done, value } = await reader.read();
|
|
36931
|
-
if (done) break;
|
|
36932
|
-
if (!value || value.length === 0) continue;
|
|
36933
|
-
const buf = Buffer.from(value);
|
|
36934
|
-
for (let offset = 0; offset < buf.length; offset += this.chunkSize) {
|
|
36935
|
-
yield buf.subarray(offset, Math.min(offset + this.chunkSize, buf.length));
|
|
36936
|
-
}
|
|
36937
|
-
}
|
|
36938
|
-
} finally {
|
|
36939
|
-
if (typeof reader.cancel === "function") await reader.cancel().catch(() => {
|
|
36940
|
-
});
|
|
36941
|
-
reader.releaseLock();
|
|
36942
|
-
}
|
|
36943
|
-
}
|
|
36944
|
-
};
|
|
36945
|
-
|
|
36946
|
-
// src/tts/elevenlabs.ts
|
|
36947
37720
|
function resolveApiKey(apiKey) {
|
|
36948
37721
|
const key = apiKey ?? process.env.ELEVENLABS_API_KEY;
|
|
36949
37722
|
if (!key) {
|
|
@@ -36959,7 +37732,7 @@ var TTS = class _TTS extends ElevenLabsTTS {
|
|
|
36959
37732
|
super(resolveApiKey(opts.apiKey), {
|
|
36960
37733
|
voiceId: opts.voiceId ?? "EXAVITQu4vr4xnSDxMaL",
|
|
36961
37734
|
modelId: opts.modelId ?? "eleven_flash_v2_5",
|
|
36962
|
-
outputFormat: opts.outputFormat
|
|
37735
|
+
...opts.outputFormat !== void 0 ? { outputFormat: opts.outputFormat } : {},
|
|
36963
37736
|
languageCode: opts.languageCode,
|
|
36964
37737
|
voiceSettings: opts.voiceSettings
|
|
36965
37738
|
});
|
|
@@ -37031,6 +37804,20 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
37031
37804
|
* changes.
|
|
37032
37805
|
*/
|
|
37033
37806
|
adoptedConnection = null;
|
|
37807
|
+
/**
|
|
37808
|
+
* Active WS for the in-flight ``synthesizeStream`` call, if any. Set
|
|
37809
|
+
* when a stream starts, cleared in its ``finally`` block. The
|
|
37810
|
+
* stream-handler calls ``cancelActiveStream()`` from ``cancelSpeaking``
|
|
37811
|
+
* to unblock the generator's inner ``await Promise<frame>`` — without
|
|
37812
|
+
* it, a barge-in on the firstMessage live path leaves the for-await
|
|
37813
|
+
* stuck waiting for the next frame; ElevenLabs never sends
|
|
37814
|
+
* ``isFinal=true`` after the consumer breaks, the 30 s frame timeout
|
|
37815
|
+
* fires post-call, and meanwhile ``initPipeline`` never returns so
|
|
37816
|
+
* the STT ``onTranscript`` callback never registers and subsequent
|
|
37817
|
+
* user turns are silently dropped (root cause of the 2026-05-20
|
|
37818
|
+
* "first message OK, then no response" symptom).
|
|
37819
|
+
*/
|
|
37820
|
+
activeStreamWs = null;
|
|
37034
37821
|
/**
|
|
37035
37822
|
* The wire format requested over the ElevenLabs WS. Initially set from
|
|
37036
37823
|
* the constructor; ``setTelephonyCarrier`` may auto-flip it to the
|
|
@@ -37079,6 +37866,32 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
37079
37866
|
if (!native) return;
|
|
37080
37867
|
this._outputFormat = native;
|
|
37081
37868
|
}
|
|
37869
|
+
/**
|
|
37870
|
+
* Force-close the WebSocket of any in-flight ``synthesizeStream`` call.
|
|
37871
|
+
* Called by the stream-handler from ``cancelSpeaking`` (barge-in) so
|
|
37872
|
+
* the generator's inner ``await Promise<frame>`` loop unblocks cleanly
|
|
37873
|
+
* via the ``onClose`` handler — instead of waiting up to 30 s for the
|
|
37874
|
+
* ``FRAME_TIMEOUT_MS`` watchdog to fire. No-op when no stream is in
|
|
37875
|
+
* flight or when the WS is already closing.
|
|
37876
|
+
*
|
|
37877
|
+
* Without this, a barge-in during the firstMessage live path left the
|
|
37878
|
+
* for-await stuck (ElevenLabs never sends ``isFinal=true`` after the
|
|
37879
|
+
* consumer breaks), ``initPipeline`` never returned, the STT
|
|
37880
|
+
* ``onTranscript`` callback never registered, and the entire remainder
|
|
37881
|
+
* of the call was silent for the user. Surfaced during the 2026-05-20
|
|
37882
|
+
* acceptance run.
|
|
37883
|
+
*/
|
|
37884
|
+
cancelActiveStream() {
|
|
37885
|
+
const ws = this.activeStreamWs;
|
|
37886
|
+
if (!ws) return;
|
|
37887
|
+
this.activeStreamWs = null;
|
|
37888
|
+
try {
|
|
37889
|
+
if (ws.readyState === import_ws11.default.OPEN || ws.readyState === import_ws11.default.CONNECTING) {
|
|
37890
|
+
ws.close();
|
|
37891
|
+
}
|
|
37892
|
+
} catch {
|
|
37893
|
+
}
|
|
37894
|
+
}
|
|
37082
37895
|
/** Pre-configured for Twilio Media Streams (`ulaw_8000`). */
|
|
37083
37896
|
static forTwilio(opts) {
|
|
37084
37897
|
return new _ElevenLabsWebSocketTTS({
|
|
@@ -37164,6 +37977,7 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
37164
37977
|
headers: { "xi-api-key": this.apiKey }
|
|
37165
37978
|
});
|
|
37166
37979
|
}
|
|
37980
|
+
this.activeStreamWs = ws;
|
|
37167
37981
|
const queue = [];
|
|
37168
37982
|
let done = false;
|
|
37169
37983
|
let pendingError = null;
|
|
@@ -37234,7 +38048,7 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
37234
38048
|
ws.on("error", onError);
|
|
37235
38049
|
try {
|
|
37236
38050
|
if (!adopted) {
|
|
37237
|
-
await new Promise((
|
|
38051
|
+
await new Promise((resolve2, reject) => {
|
|
37238
38052
|
connectTimer = setTimeout(
|
|
37239
38053
|
() => reject(new Error("ElevenLabs WS connect timeout")),
|
|
37240
38054
|
CONNECT_TIMEOUT_MS4
|
|
@@ -37242,7 +38056,7 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
37242
38056
|
ws.once("open", () => {
|
|
37243
38057
|
if (connectTimer) clearTimeout(connectTimer);
|
|
37244
38058
|
connectTimer = void 0;
|
|
37245
|
-
|
|
38059
|
+
resolve2();
|
|
37246
38060
|
});
|
|
37247
38061
|
ws.once("error", (err) => {
|
|
37248
38062
|
if (connectTimer) clearTimeout(connectTimer);
|
|
@@ -37284,6 +38098,7 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
37284
38098
|
}
|
|
37285
38099
|
} finally {
|
|
37286
38100
|
if (connectTimer) clearTimeout(connectTimer);
|
|
38101
|
+
if (this.activeStreamWs === ws) this.activeStreamWs = null;
|
|
37287
38102
|
try {
|
|
37288
38103
|
if (ws.readyState === import_ws11.default.OPEN) {
|
|
37289
38104
|
ws.send(JSON.stringify({ text: "" }));
|
|
@@ -37326,14 +38141,14 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
37326
38141
|
headers: { "xi-api-key": this.apiKey }
|
|
37327
38142
|
});
|
|
37328
38143
|
try {
|
|
37329
|
-
await new Promise((
|
|
38144
|
+
await new Promise((resolve2, reject) => {
|
|
37330
38145
|
const timer = setTimeout(
|
|
37331
38146
|
() => reject(new Error("ElevenLabs WS TTS warmup connect timeout")),
|
|
37332
38147
|
CONNECT_TIMEOUT_MS4
|
|
37333
38148
|
);
|
|
37334
38149
|
ws.once("open", () => {
|
|
37335
38150
|
clearTimeout(timer);
|
|
37336
|
-
|
|
38151
|
+
resolve2();
|
|
37337
38152
|
});
|
|
37338
38153
|
ws.once("error", (err) => {
|
|
37339
38154
|
clearTimeout(timer);
|
|
@@ -37377,14 +38192,14 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
37377
38192
|
const ws = new import_ws11.default(this.buildUrl(), {
|
|
37378
38193
|
headers: { "xi-api-key": this.apiKey }
|
|
37379
38194
|
});
|
|
37380
|
-
await new Promise((
|
|
38195
|
+
await new Promise((resolve2, reject) => {
|
|
37381
38196
|
const timer = setTimeout(
|
|
37382
38197
|
() => reject(new Error("ElevenLabs WS park connect timeout")),
|
|
37383
38198
|
CONNECT_TIMEOUT_MS4
|
|
37384
38199
|
);
|
|
37385
38200
|
ws.once("open", () => {
|
|
37386
38201
|
clearTimeout(timer);
|
|
37387
|
-
|
|
38202
|
+
resolve2();
|
|
37388
38203
|
});
|
|
37389
38204
|
ws.once("error", (err) => {
|
|
37390
38205
|
clearTimeout(timer);
|
|
@@ -37456,9 +38271,9 @@ function buildOpts(opts) {
|
|
|
37456
38271
|
const out = {
|
|
37457
38272
|
apiKey: resolveApiKey2(opts.apiKey),
|
|
37458
38273
|
modelId: opts.modelId ?? "eleven_flash_v2_5",
|
|
37459
|
-
outputFormat: opts.outputFormat ?? "pcm_16000",
|
|
37460
38274
|
autoMode: opts.autoMode ?? true
|
|
37461
38275
|
};
|
|
38276
|
+
if (opts.outputFormat !== void 0) out.outputFormat = opts.outputFormat;
|
|
37462
38277
|
if (opts.voiceId !== void 0) out.voiceId = opts.voiceId;
|
|
37463
38278
|
if (opts.voiceSettings !== void 0) out.voiceSettings = opts.voiceSettings;
|
|
37464
38279
|
if (opts.languageCode !== void 0) out.languageCode = opts.languageCode;
|
|
@@ -37706,198 +38521,6 @@ var TTS3 = class extends OpenAITTS {
|
|
|
37706
38521
|
|
|
37707
38522
|
// src/tts/cartesia.ts
|
|
37708
38523
|
init_cjs_shims();
|
|
37709
|
-
|
|
37710
|
-
// src/providers/cartesia-tts.ts
|
|
37711
|
-
init_cjs_shims();
|
|
37712
|
-
init_logger();
|
|
37713
|
-
var CARTESIA_BASE_URL = "https://api.cartesia.ai";
|
|
37714
|
-
var CARTESIA_API_VERSION = "2025-04-16";
|
|
37715
|
-
var CARTESIA_DEFAULT_VOICE_ID = "f786b574-daa5-4673-aa0c-cbe3e8534c02";
|
|
37716
|
-
var CartesiaTTSModel = {
|
|
37717
|
-
SONIC_3: "sonic-3",
|
|
37718
|
-
SONIC_2: "sonic-2",
|
|
37719
|
-
SONIC: "sonic"
|
|
37720
|
-
};
|
|
37721
|
-
var CartesiaTTSContainer = {
|
|
37722
|
-
RAW: "raw",
|
|
37723
|
-
WAV: "wav",
|
|
37724
|
-
MP3: "mp3"
|
|
37725
|
-
};
|
|
37726
|
-
var CartesiaTTSEncoding = {
|
|
37727
|
-
PCM_S16LE: "pcm_s16le",
|
|
37728
|
-
PCM_F32LE: "pcm_f32le",
|
|
37729
|
-
PCM_MULAW: "pcm_mulaw",
|
|
37730
|
-
PCM_ALAW: "pcm_alaw"
|
|
37731
|
-
};
|
|
37732
|
-
var CartesiaTTSSampleRate = {
|
|
37733
|
-
HZ_8000: 8e3,
|
|
37734
|
-
HZ_16000: 16e3,
|
|
37735
|
-
HZ_22050: 22050,
|
|
37736
|
-
HZ_24000: 24e3,
|
|
37737
|
-
HZ_44100: 44100
|
|
37738
|
-
};
|
|
37739
|
-
var CartesiaTTSVoiceMode = {
|
|
37740
|
-
ID: "id",
|
|
37741
|
-
EMBEDDING: "embedding"
|
|
37742
|
-
};
|
|
37743
|
-
var CartesiaTTS = class _CartesiaTTS {
|
|
37744
|
-
/** Stable pricing/dashboard key — read by stream-handler/metrics. */
|
|
37745
|
-
static providerKey = "cartesia_tts";
|
|
37746
|
-
apiKey;
|
|
37747
|
-
model;
|
|
37748
|
-
voice;
|
|
37749
|
-
language;
|
|
37750
|
-
sampleRate;
|
|
37751
|
-
speed;
|
|
37752
|
-
emotion;
|
|
37753
|
-
volume;
|
|
37754
|
-
baseUrl;
|
|
37755
|
-
apiVersion;
|
|
37756
|
-
constructor(apiKey, opts = {}) {
|
|
37757
|
-
this.apiKey = apiKey;
|
|
37758
|
-
this.model = opts.model ?? CartesiaTTSModel.SONIC_3;
|
|
37759
|
-
this.voice = opts.voice ?? CARTESIA_DEFAULT_VOICE_ID;
|
|
37760
|
-
this.language = opts.language ?? "en";
|
|
37761
|
-
this.sampleRate = opts.sampleRate ?? CartesiaTTSSampleRate.HZ_16000;
|
|
37762
|
-
this.speed = opts.speed;
|
|
37763
|
-
this.emotion = typeof opts.emotion === "string" ? [opts.emotion] : opts.emotion;
|
|
37764
|
-
this.volume = opts.volume;
|
|
37765
|
-
this.baseUrl = opts.baseUrl ?? CARTESIA_BASE_URL;
|
|
37766
|
-
this.apiVersion = opts.apiVersion ?? CARTESIA_API_VERSION;
|
|
37767
|
-
}
|
|
37768
|
-
/**
|
|
37769
|
-
* Construct an instance pre-configured for Twilio Media Streams.
|
|
37770
|
-
*
|
|
37771
|
-
* Sets `sampleRate=8000` so Cartesia emits PCM_S16LE @ 8 kHz directly.
|
|
37772
|
-
* Twilio's media stream uses μ-law @ 8 kHz so the SDK still does the
|
|
37773
|
-
* PCM → μ-law transcode client-side, but the 16 kHz → 8 kHz resample
|
|
37774
|
-
* step is skipped. Saves ~10–30 ms first-byte plus per-frame CPU and
|
|
37775
|
-
* removes a potential aliasing source.
|
|
37776
|
-
*/
|
|
37777
|
-
static forTwilio(apiKey, options = {}) {
|
|
37778
|
-
return new _CartesiaTTS(apiKey, {
|
|
37779
|
-
...options,
|
|
37780
|
-
sampleRate: CartesiaTTSSampleRate.HZ_8000
|
|
37781
|
-
});
|
|
37782
|
-
}
|
|
37783
|
-
/**
|
|
37784
|
-
* Construct an instance pre-configured for Telnyx bidirectional media.
|
|
37785
|
-
*
|
|
37786
|
-
* Sets `sampleRate=16000` to match Telnyx's L16/16000 default codec —
|
|
37787
|
-
* audio flows end-to-end with zero resampling or transcoding. Same as
|
|
37788
|
-
* the bare-constructor default; exists for API symmetry with
|
|
37789
|
-
* {@link CartesiaTTS.forTwilio}.
|
|
37790
|
-
*/
|
|
37791
|
-
static forTelnyx(apiKey, options = {}) {
|
|
37792
|
-
return new _CartesiaTTS(apiKey, {
|
|
37793
|
-
...options,
|
|
37794
|
-
sampleRate: CartesiaTTSSampleRate.HZ_16000
|
|
37795
|
-
});
|
|
37796
|
-
}
|
|
37797
|
-
/** Build the JSON payload for the Cartesia bytes endpoint. */
|
|
37798
|
-
buildPayload(text) {
|
|
37799
|
-
const payload = {
|
|
37800
|
-
model_id: this.model,
|
|
37801
|
-
voice: { mode: CartesiaTTSVoiceMode.ID, id: this.voice },
|
|
37802
|
-
transcript: text,
|
|
37803
|
-
output_format: {
|
|
37804
|
-
container: CartesiaTTSContainer.RAW,
|
|
37805
|
-
encoding: CartesiaTTSEncoding.PCM_S16LE,
|
|
37806
|
-
sample_rate: this.sampleRate
|
|
37807
|
-
},
|
|
37808
|
-
language: this.language
|
|
37809
|
-
};
|
|
37810
|
-
const generationConfig = {};
|
|
37811
|
-
if (this.speed !== void 0) generationConfig.speed = this.speed;
|
|
37812
|
-
if (this.emotion && this.emotion.length > 0)
|
|
37813
|
-
generationConfig.emotion = this.emotion[0];
|
|
37814
|
-
if (this.volume !== void 0) generationConfig.volume = this.volume;
|
|
37815
|
-
if (Object.keys(generationConfig).length > 0) {
|
|
37816
|
-
payload.generation_config = generationConfig;
|
|
37817
|
-
}
|
|
37818
|
-
return payload;
|
|
37819
|
-
}
|
|
37820
|
-
/**
|
|
37821
|
-
* Pre-call HTTP warmup for the Cartesia `/tts/bytes` endpoint.
|
|
37822
|
-
*
|
|
37823
|
-
* Issues a lightweight `GET <baseUrl>/voices` so DNS, TLS, and HTTP/2
|
|
37824
|
-
* are already up by the time the first `synthesizeStream()` POST
|
|
37825
|
-
* lands. Best-effort: 5 s timeout, all exceptions swallowed at
|
|
37826
|
-
* debug level.
|
|
37827
|
-
*
|
|
37828
|
-
* Billing safety: `GET /voices` is a free metadata read on
|
|
37829
|
-
* Cartesia's REST surface (per https://docs.cartesia.ai). It does
|
|
37830
|
-
* not consume synthesis credits. The actual synthesis is billed
|
|
37831
|
-
* only when `POST /tts/bytes` runs with a non-empty `transcript`.
|
|
37832
|
-
*
|
|
37833
|
-
* Note: Cartesia TTS uses the HTTP path (vs the WebSocket variant
|
|
37834
|
-
* Cartesia also exposes) — connection warmup is therefore HTTP-GET
|
|
37835
|
-
* based, not WebSocket pre-handshake. The latency win is smaller
|
|
37836
|
-
* (~50-150 ms vs the ~200-500 ms of a WS prewarm) but still real.
|
|
37837
|
-
*/
|
|
37838
|
-
async warmup() {
|
|
37839
|
-
try {
|
|
37840
|
-
await fetch(`${this.baseUrl}/voices`, {
|
|
37841
|
-
method: "GET",
|
|
37842
|
-
headers: {
|
|
37843
|
-
"X-API-Key": this.apiKey,
|
|
37844
|
-
"Cartesia-Version": this.apiVersion
|
|
37845
|
-
},
|
|
37846
|
-
signal: AbortSignal.timeout(5e3)
|
|
37847
|
-
});
|
|
37848
|
-
} catch (err) {
|
|
37849
|
-
getLogger().debug(`Cartesia TTS warmup failed (best-effort): ${String(err)}`);
|
|
37850
|
-
}
|
|
37851
|
-
}
|
|
37852
|
-
/** Synthesize text and return the concatenated audio buffer. */
|
|
37853
|
-
async synthesize(text) {
|
|
37854
|
-
const chunks = [];
|
|
37855
|
-
for await (const chunk of this.synthesizeStream(text)) {
|
|
37856
|
-
chunks.push(chunk);
|
|
37857
|
-
}
|
|
37858
|
-
return Buffer.concat(chunks);
|
|
37859
|
-
}
|
|
37860
|
-
/**
|
|
37861
|
-
* Synthesize text and yield raw PCM_S16LE chunks at the configured
|
|
37862
|
-
* `sampleRate` as they arrive from Cartesia.
|
|
37863
|
-
*/
|
|
37864
|
-
async *synthesizeStream(text) {
|
|
37865
|
-
const response = await fetch(`${this.baseUrl}/tts/bytes`, {
|
|
37866
|
-
method: "POST",
|
|
37867
|
-
headers: {
|
|
37868
|
-
"X-API-Key": this.apiKey,
|
|
37869
|
-
"Cartesia-Version": this.apiVersion,
|
|
37870
|
-
"Content-Type": "application/json"
|
|
37871
|
-
},
|
|
37872
|
-
body: JSON.stringify(this.buildPayload(text)),
|
|
37873
|
-
signal: AbortSignal.timeout(3e4)
|
|
37874
|
-
});
|
|
37875
|
-
if (!response.ok) {
|
|
37876
|
-
const body = await response.text();
|
|
37877
|
-
throw new Error(`Cartesia TTS error ${response.status}: ${body}`);
|
|
37878
|
-
}
|
|
37879
|
-
if (!response.body) {
|
|
37880
|
-
throw new Error("Cartesia TTS: no response body");
|
|
37881
|
-
}
|
|
37882
|
-
const reader = response.body.getReader();
|
|
37883
|
-
try {
|
|
37884
|
-
while (true) {
|
|
37885
|
-
const { done, value } = await reader.read();
|
|
37886
|
-
if (done) break;
|
|
37887
|
-
if (value && value.length > 0) {
|
|
37888
|
-
yield Buffer.from(value);
|
|
37889
|
-
}
|
|
37890
|
-
}
|
|
37891
|
-
} finally {
|
|
37892
|
-
if (typeof reader.cancel === "function")
|
|
37893
|
-
await reader.cancel().catch(() => {
|
|
37894
|
-
});
|
|
37895
|
-
reader.releaseLock();
|
|
37896
|
-
}
|
|
37897
|
-
}
|
|
37898
|
-
};
|
|
37899
|
-
|
|
37900
|
-
// src/tts/cartesia.ts
|
|
37901
38524
|
function resolveApiKey3(apiKey) {
|
|
37902
38525
|
const key = apiKey ?? process.env.CARTESIA_API_KEY;
|
|
37903
38526
|
if (!key) {
|
|
@@ -37927,150 +38550,6 @@ var TTS4 = class _TTS extends CartesiaTTS {
|
|
|
37927
38550
|
|
|
37928
38551
|
// src/tts/rime.ts
|
|
37929
38552
|
init_cjs_shims();
|
|
37930
|
-
|
|
37931
|
-
// src/providers/rime-tts.ts
|
|
37932
|
-
init_cjs_shims();
|
|
37933
|
-
var RIME_BASE_URL = "https://users.rime.ai/v1/rime-tts";
|
|
37934
|
-
var RimeModel = {
|
|
37935
|
-
ARCANA: "arcana",
|
|
37936
|
-
MIST: "mist",
|
|
37937
|
-
MIST_V2: "mistv2"
|
|
37938
|
-
};
|
|
37939
|
-
var RimeAudioFormat = {
|
|
37940
|
-
PCM: "audio/pcm",
|
|
37941
|
-
MP3: "audio/mp3",
|
|
37942
|
-
WAV: "audio/wav",
|
|
37943
|
-
MULAW: "audio/mulaw"
|
|
37944
|
-
};
|
|
37945
|
-
var ARCANA_MODEL_TIMEOUT_MS = 60 * 4 * 1e3;
|
|
37946
|
-
var MIST_MODEL_TIMEOUT_MS = 30 * 1e3;
|
|
37947
|
-
function isMistModel(model) {
|
|
37948
|
-
return model.includes(RimeModel.MIST);
|
|
37949
|
-
}
|
|
37950
|
-
function timeoutForModel(model) {
|
|
37951
|
-
if (model === RimeModel.ARCANA) return ARCANA_MODEL_TIMEOUT_MS;
|
|
37952
|
-
return MIST_MODEL_TIMEOUT_MS;
|
|
37953
|
-
}
|
|
37954
|
-
var RimeTTS = class {
|
|
37955
|
-
/** Stable pricing/dashboard key — read by stream-handler/metrics. */
|
|
37956
|
-
static providerKey = "rime";
|
|
37957
|
-
apiKey;
|
|
37958
|
-
model;
|
|
37959
|
-
speaker;
|
|
37960
|
-
lang;
|
|
37961
|
-
sampleRate;
|
|
37962
|
-
repetitionPenalty;
|
|
37963
|
-
temperature;
|
|
37964
|
-
topP;
|
|
37965
|
-
maxTokens;
|
|
37966
|
-
speedAlpha;
|
|
37967
|
-
reduceLatency;
|
|
37968
|
-
pauseBetweenBrackets;
|
|
37969
|
-
phonemizeBetweenBrackets;
|
|
37970
|
-
baseUrl;
|
|
37971
|
-
totalTimeoutMs;
|
|
37972
|
-
constructor(apiKey, opts = {}) {
|
|
37973
|
-
this.apiKey = apiKey;
|
|
37974
|
-
this.model = opts.model ?? RimeModel.ARCANA;
|
|
37975
|
-
const defaultSpeaker = isMistModel(this.model) ? "cove" : "astra";
|
|
37976
|
-
this.speaker = opts.speaker ?? defaultSpeaker;
|
|
37977
|
-
this.lang = opts.lang ?? "eng";
|
|
37978
|
-
this.sampleRate = opts.sampleRate ?? 16e3;
|
|
37979
|
-
this.repetitionPenalty = opts.repetitionPenalty;
|
|
37980
|
-
this.temperature = opts.temperature;
|
|
37981
|
-
this.topP = opts.topP;
|
|
37982
|
-
this.maxTokens = opts.maxTokens;
|
|
37983
|
-
this.speedAlpha = opts.speedAlpha;
|
|
37984
|
-
this.reduceLatency = opts.reduceLatency;
|
|
37985
|
-
this.pauseBetweenBrackets = opts.pauseBetweenBrackets;
|
|
37986
|
-
this.phonemizeBetweenBrackets = opts.phonemizeBetweenBrackets;
|
|
37987
|
-
this.baseUrl = opts.baseUrl ?? RIME_BASE_URL;
|
|
37988
|
-
this.totalTimeoutMs = timeoutForModel(this.model);
|
|
37989
|
-
}
|
|
37990
|
-
buildPayload(text) {
|
|
37991
|
-
const payload = {
|
|
37992
|
-
speaker: this.speaker,
|
|
37993
|
-
text,
|
|
37994
|
-
modelId: this.model
|
|
37995
|
-
};
|
|
37996
|
-
if (this.model === RimeModel.ARCANA) {
|
|
37997
|
-
if (this.repetitionPenalty !== void 0)
|
|
37998
|
-
payload.repetition_penalty = this.repetitionPenalty;
|
|
37999
|
-
if (this.temperature !== void 0) payload.temperature = this.temperature;
|
|
38000
|
-
if (this.topP !== void 0) payload.top_p = this.topP;
|
|
38001
|
-
if (this.maxTokens !== void 0) payload.max_tokens = this.maxTokens;
|
|
38002
|
-
payload.lang = this.lang;
|
|
38003
|
-
payload.samplingRate = this.sampleRate;
|
|
38004
|
-
} else if (isMistModel(this.model)) {
|
|
38005
|
-
payload.lang = this.lang;
|
|
38006
|
-
payload.samplingRate = this.sampleRate;
|
|
38007
|
-
if (this.speedAlpha !== void 0) payload.speedAlpha = this.speedAlpha;
|
|
38008
|
-
if (this.model === RimeModel.MIST_V2 && this.reduceLatency !== void 0) {
|
|
38009
|
-
payload.reduceLatency = this.reduceLatency;
|
|
38010
|
-
}
|
|
38011
|
-
if (this.pauseBetweenBrackets !== void 0) {
|
|
38012
|
-
payload.pauseBetweenBrackets = this.pauseBetweenBrackets;
|
|
38013
|
-
}
|
|
38014
|
-
if (this.phonemizeBetweenBrackets !== void 0) {
|
|
38015
|
-
payload.phonemizeBetweenBrackets = this.phonemizeBetweenBrackets;
|
|
38016
|
-
}
|
|
38017
|
-
}
|
|
38018
|
-
return payload;
|
|
38019
|
-
}
|
|
38020
|
-
/** Synthesize text and return the concatenated audio buffer. */
|
|
38021
|
-
async synthesize(text) {
|
|
38022
|
-
const chunks = [];
|
|
38023
|
-
for await (const chunk of this.synthesizeStream(text)) {
|
|
38024
|
-
chunks.push(chunk);
|
|
38025
|
-
}
|
|
38026
|
-
return Buffer.concat(chunks);
|
|
38027
|
-
}
|
|
38028
|
-
/**
|
|
38029
|
-
* Synthesize text and yield raw PCM_S16LE chunks at the configured
|
|
38030
|
-
* `sampleRate` as they stream in.
|
|
38031
|
-
*/
|
|
38032
|
-
async *synthesizeStream(text) {
|
|
38033
|
-
const response = await fetch(this.baseUrl, {
|
|
38034
|
-
method: "POST",
|
|
38035
|
-
headers: {
|
|
38036
|
-
accept: RimeAudioFormat.PCM,
|
|
38037
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
38038
|
-
"content-type": "application/json"
|
|
38039
|
-
},
|
|
38040
|
-
body: JSON.stringify(this.buildPayload(text)),
|
|
38041
|
-
signal: AbortSignal.timeout(this.totalTimeoutMs)
|
|
38042
|
-
});
|
|
38043
|
-
if (!response.ok) {
|
|
38044
|
-
const body = await response.text();
|
|
38045
|
-
throw new Error(`Rime TTS error ${response.status}: ${body}`);
|
|
38046
|
-
}
|
|
38047
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
38048
|
-
if (!contentType.startsWith("audio")) {
|
|
38049
|
-
const body = await response.text();
|
|
38050
|
-
throw new Error(`Rime returned non-audio response: ${body.slice(0, 500)}`);
|
|
38051
|
-
}
|
|
38052
|
-
if (!response.body) {
|
|
38053
|
-
throw new Error("Rime TTS: no response body");
|
|
38054
|
-
}
|
|
38055
|
-
const reader = response.body.getReader();
|
|
38056
|
-
try {
|
|
38057
|
-
while (true) {
|
|
38058
|
-
const { done, value } = await reader.read();
|
|
38059
|
-
if (done) break;
|
|
38060
|
-
if (value && value.length > 0) {
|
|
38061
|
-
yield Buffer.from(value);
|
|
38062
|
-
}
|
|
38063
|
-
}
|
|
38064
|
-
} finally {
|
|
38065
|
-
if (typeof reader.cancel === "function")
|
|
38066
|
-
await reader.cancel().catch(() => {
|
|
38067
|
-
});
|
|
38068
|
-
reader.releaseLock();
|
|
38069
|
-
}
|
|
38070
|
-
}
|
|
38071
|
-
};
|
|
38072
|
-
|
|
38073
|
-
// src/tts/rime.ts
|
|
38074
38553
|
var TTS5 = class extends RimeTTS {
|
|
38075
38554
|
static providerKey = "rime";
|
|
38076
38555
|
constructor(opts = {}) {
|
|
@@ -38715,12 +39194,7 @@ init_cjs_shims();
|
|
|
38715
39194
|
init_cjs_shims();
|
|
38716
39195
|
init_llm_loop();
|
|
38717
39196
|
init_logger();
|
|
38718
|
-
|
|
38719
|
-
// src/version.ts
|
|
38720
|
-
init_cjs_shims();
|
|
38721
|
-
var VERSION = "0.5.5";
|
|
38722
|
-
|
|
38723
|
-
// src/providers/groq-llm.ts
|
|
39197
|
+
init_version();
|
|
38724
39198
|
var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
|
|
38725
39199
|
var GroqModel = {
|
|
38726
39200
|
LLAMA_3_3_70B_VERSATILE: "llama-3.3-70b-versatile",
|
|
@@ -38910,6 +39384,7 @@ init_cjs_shims();
|
|
|
38910
39384
|
init_llm_loop();
|
|
38911
39385
|
init_logger();
|
|
38912
39386
|
init_errors();
|
|
39387
|
+
init_version();
|
|
38913
39388
|
var CEREBRAS_BASE_URL = "https://api.cerebras.ai/v1";
|
|
38914
39389
|
var CerebrasModel = {
|
|
38915
39390
|
GPT_OSS_120B: "gpt-oss-120b",
|
|
@@ -40308,8 +40783,8 @@ var TwilioAdapter = class _TwilioAdapter {
|
|
|
40308
40783
|
this.baseUrl = opts.region ? `https://api.${opts.region}.twilio.com/2010-04-01` : TWILIO_API_BASE2;
|
|
40309
40784
|
this.authHeader = `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`;
|
|
40310
40785
|
}
|
|
40311
|
-
async request(method,
|
|
40312
|
-
const url2 = `${this.baseUrl}/Accounts/${encodeURIComponent(this.accountSid)}${
|
|
40786
|
+
async request(method, path6, body) {
|
|
40787
|
+
const url2 = `${this.baseUrl}/Accounts/${encodeURIComponent(this.accountSid)}${path6}`;
|
|
40313
40788
|
const headers = { Authorization: this.authHeader };
|
|
40314
40789
|
if (body) headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
40315
40790
|
const response = await fetch(url2, {
|
|
@@ -40320,7 +40795,7 @@ var TwilioAdapter = class _TwilioAdapter {
|
|
|
40320
40795
|
});
|
|
40321
40796
|
const text = await response.text();
|
|
40322
40797
|
if (!response.ok) {
|
|
40323
|
-
throw new Error(`Twilio ${method} ${
|
|
40798
|
+
throw new Error(`Twilio ${method} ${path6} failed: ${response.status} ${text}`);
|
|
40324
40799
|
}
|
|
40325
40800
|
if (!text) return {};
|
|
40326
40801
|
try {
|
|
@@ -40338,8 +40813,8 @@ var TwilioAdapter = class _TwilioAdapter {
|
|
|
40338
40813
|
const country = encodeURIComponent(opts.countryCode);
|
|
40339
40814
|
const queryParts = ["PageSize=1"];
|
|
40340
40815
|
if (opts.areaCode) queryParts.push(`AreaCode=${encodeURIComponent(opts.areaCode)}`);
|
|
40341
|
-
const
|
|
40342
|
-
const available = await this.request("GET",
|
|
40816
|
+
const path6 = `/AvailablePhoneNumbers/${country}/Local.json?${queryParts.join("&")}`;
|
|
40817
|
+
const available = await this.request("GET", path6);
|
|
40343
40818
|
const first = available.available_phone_numbers?.[0]?.phone_number;
|
|
40344
40819
|
if (!first) {
|
|
40345
40820
|
throw new Error(`TwilioAdapter: no numbers available for country ${opts.countryCode}`);
|
|
@@ -40397,12 +40872,28 @@ var TwilioAdapter = class _TwilioAdapter {
|
|
|
40397
40872
|
return { callSid: call.sid };
|
|
40398
40873
|
}
|
|
40399
40874
|
/**
|
|
40400
|
-
* Build a
|
|
40401
|
-
*
|
|
40875
|
+
* Build a ``<Response><Connect><Stream url="...">`` TwiML document.
|
|
40876
|
+
*
|
|
40877
|
+
* ``parameters`` is forwarded as ``<Parameter name="..." value="..."/>``
|
|
40878
|
+
* children of ``<Stream>``. Twilio Media Streams strips query-string params
|
|
40879
|
+
* from the ``<Stream url=...>`` before the WS handshake, so
|
|
40880
|
+
* ``<Parameter>`` tags are the supported way to pre-populate
|
|
40881
|
+
* ``start.customParameters`` on the WS ``start`` frame. Used by the
|
|
40882
|
+
* inbound path to carry caller / callee through to the bridge.
|
|
40883
|
+
*
|
|
40884
|
+
* Mirrors the Python adapter's ``generate_stream_twiml``.
|
|
40402
40885
|
*/
|
|
40403
|
-
static generateStreamTwiml(streamUrl) {
|
|
40404
|
-
const
|
|
40405
|
-
|
|
40886
|
+
static generateStreamTwiml(streamUrl, parameters) {
|
|
40887
|
+
const esc2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
40888
|
+
const escapedUrl = esc2(streamUrl);
|
|
40889
|
+
let paramTags = "";
|
|
40890
|
+
if (parameters) {
|
|
40891
|
+
for (const [name, value] of Object.entries(parameters)) {
|
|
40892
|
+
if (value == null) continue;
|
|
40893
|
+
paramTags += `<Parameter name="${esc2(name)}" value="${esc2(String(value))}"/>`;
|
|
40894
|
+
}
|
|
40895
|
+
}
|
|
40896
|
+
return `<?xml version="1.0" encoding="UTF-8"?><Response><Connect><Stream url="${escapedUrl}">${paramTags}</Stream></Connect></Response>`;
|
|
40406
40897
|
}
|
|
40407
40898
|
/** Force-complete an in-progress call. */
|
|
40408
40899
|
async endCall(callSid) {
|
|
@@ -40435,8 +40926,8 @@ var TelnyxAdapter = class {
|
|
|
40435
40926
|
this.apiKey = apiKey;
|
|
40436
40927
|
this.connectionId = connectionId;
|
|
40437
40928
|
}
|
|
40438
|
-
async request(method,
|
|
40439
|
-
const url2 = `${this.baseUrl}${
|
|
40929
|
+
async request(method, path6, body) {
|
|
40930
|
+
const url2 = `${this.baseUrl}${path6}`;
|
|
40440
40931
|
const headers = {
|
|
40441
40932
|
Authorization: `Bearer ${this.apiKey}`
|
|
40442
40933
|
};
|
|
@@ -40449,7 +40940,7 @@ var TelnyxAdapter = class {
|
|
|
40449
40940
|
});
|
|
40450
40941
|
const text = await response.text();
|
|
40451
40942
|
if (!response.ok) {
|
|
40452
|
-
throw new Error(`Telnyx ${method} ${
|
|
40943
|
+
throw new Error(`Telnyx ${method} ${path6} failed: ${response.status} ${text}`);
|
|
40453
40944
|
}
|
|
40454
40945
|
if (!text) return {};
|
|
40455
40946
|
try {
|
|
@@ -40610,11 +41101,11 @@ var TelnyxSTT = class {
|
|
|
40610
41101
|
this.ws = new import_ws12.default(url2, {
|
|
40611
41102
|
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
40612
41103
|
});
|
|
40613
|
-
await new Promise((
|
|
41104
|
+
await new Promise((resolve2, reject) => {
|
|
40614
41105
|
const timer = setTimeout(() => reject(new Error("Telnyx STT connect timeout")), 1e4);
|
|
40615
41106
|
this.ws.once("open", () => {
|
|
40616
41107
|
clearTimeout(timer);
|
|
40617
|
-
|
|
41108
|
+
resolve2();
|
|
40618
41109
|
});
|
|
40619
41110
|
this.ws.once("error", (err) => {
|
|
40620
41111
|
clearTimeout(timer);
|
|
@@ -40722,11 +41213,11 @@ var TelnyxTTS = class {
|
|
|
40722
41213
|
const ws = new import_ws13.default(url2, {
|
|
40723
41214
|
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
40724
41215
|
});
|
|
40725
|
-
await new Promise((
|
|
41216
|
+
await new Promise((resolve2, reject) => {
|
|
40726
41217
|
const timer = setTimeout(() => reject(new Error("Telnyx TTS connect timeout")), 1e4);
|
|
40727
41218
|
ws.once("open", () => {
|
|
40728
41219
|
clearTimeout(timer);
|
|
40729
|
-
|
|
41220
|
+
resolve2();
|
|
40730
41221
|
});
|
|
40731
41222
|
ws.once("error", (err) => {
|
|
40732
41223
|
clearTimeout(timer);
|
|
@@ -40772,7 +41263,7 @@ var TelnyxTTS = class {
|
|
|
40772
41263
|
ws.send(JSON.stringify({ text: "" }));
|
|
40773
41264
|
try {
|
|
40774
41265
|
while (true) {
|
|
40775
|
-
const item = queue.length > 0 ? queue.shift() : await new Promise((
|
|
41266
|
+
const item = queue.length > 0 ? queue.shift() : await new Promise((resolve2) => waiters.push(resolve2));
|
|
40776
41267
|
if (item === null) return;
|
|
40777
41268
|
if (typeof item === "object" && "error" in item) throw item.error;
|
|
40778
41269
|
yield item;
|
|
@@ -40801,6 +41292,8 @@ init_event_bus();
|
|
|
40801
41292
|
CallMetricsAccumulator,
|
|
40802
41293
|
CartesiaSTT,
|
|
40803
41294
|
CartesiaTTS,
|
|
41295
|
+
CartesiaTTSModel,
|
|
41296
|
+
CartesiaTTSVoiceMode,
|
|
40804
41297
|
CerebrasLLM,
|
|
40805
41298
|
ChatContext,
|
|
40806
41299
|
CloudflareTunnel,
|
|
@@ -40808,10 +41301,13 @@ init_event_bus();
|
|
|
40808
41301
|
DEFAULT_PRICING,
|
|
40809
41302
|
DTMF_EVENTS,
|
|
40810
41303
|
DeepFilterNetFilter,
|
|
41304
|
+
DeepgramModel,
|
|
40811
41305
|
DeepgramSTT,
|
|
40812
41306
|
DefaultToolExecutor,
|
|
40813
41307
|
ElevenLabsConvAI,
|
|
40814
41308
|
ElevenLabsConvAIAdapter,
|
|
41309
|
+
ElevenLabsModel,
|
|
41310
|
+
ElevenLabsOutputFormat,
|
|
40815
41311
|
ElevenLabsRestTTS,
|
|
40816
41312
|
ElevenLabsTTS,
|
|
40817
41313
|
ElevenLabsWebSocketTTS,
|
|
@@ -40840,8 +41336,15 @@ init_event_bus();
|
|
|
40840
41336
|
OpenAIRealtime2,
|
|
40841
41337
|
OpenAIRealtime2Adapter,
|
|
40842
41338
|
OpenAIRealtimeAdapter,
|
|
41339
|
+
OpenAIRealtimeAudioFormat,
|
|
41340
|
+
OpenAIRealtimeModel,
|
|
41341
|
+
OpenAIRealtimeVADType,
|
|
40843
41342
|
OpenAITTS,
|
|
40844
41343
|
OpenAITranscribeSTT,
|
|
41344
|
+
OpenAITranscriptionModel,
|
|
41345
|
+
OpenAIVoice,
|
|
41346
|
+
PRICING_LAST_UPDATED,
|
|
41347
|
+
PRICING_VERSION,
|
|
40845
41348
|
PartialStreamError,
|
|
40846
41349
|
Patter,
|
|
40847
41350
|
PatterConnectionError,
|
|
@@ -40849,9 +41352,12 @@ init_event_bus();
|
|
|
40849
41352
|
PatterTool,
|
|
40850
41353
|
PcmCarry,
|
|
40851
41354
|
PipelineHookExecutor,
|
|
41355
|
+
PricingUnit,
|
|
40852
41356
|
ProvisionError,
|
|
40853
41357
|
RateLimitError,
|
|
40854
41358
|
RemoteMessageHandler,
|
|
41359
|
+
RimeAudioFormat,
|
|
41360
|
+
RimeModel,
|
|
40855
41361
|
RimeTTS,
|
|
40856
41362
|
SPAN_BARGEIN,
|
|
40857
41363
|
SPAN_CALL,
|