getpatter 0.4.4 → 0.5.1
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/README.md +156 -158
- package/dist/carrier-config-CPG5CROM.mjs +84 -0
- package/dist/{chunk-JO5C35FM.mjs → chunk-AKQFOFLG.mjs} +1 -1
- package/dist/{chunk-O3RQG3NL.mjs → chunk-B6C3KIBG.mjs} +177 -567
- package/dist/index.d.mts +1163 -377
- package/dist/index.d.ts +1163 -377
- package/dist/index.js +2028 -1835
- package/dist/index.mjs +1644 -329
- package/dist/{test-mode-ASSLSQU2.mjs → test-mode-JZMYE5HY.mjs} +1 -1
- package/dist/{tunnel-BL7A7GXW.mjs → tunnel-O7ICMSTP.mjs} +1 -1
- package/package.json +1 -1
- package/dist/lib-4WCAS54J.mjs +0 -830
package/dist/index.js
CHANGED
|
@@ -305,254 +305,16 @@ var init_elevenlabs_convai = __esm({
|
|
|
305
305
|
}
|
|
306
306
|
});
|
|
307
307
|
|
|
308
|
-
// src/
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
"src/providers/deepgram-stt.ts"() {
|
|
312
|
-
"use strict";
|
|
313
|
-
import_ws4 = __toESM(require("ws"));
|
|
314
|
-
init_logger();
|
|
315
|
-
DEEPGRAM_WS_URL = "wss://api.deepgram.com/v1/listen";
|
|
316
|
-
DeepgramSTT = class _DeepgramSTT {
|
|
317
|
-
ws = null;
|
|
318
|
-
callbacks = [];
|
|
319
|
-
/** Request ID from Deepgram — used to query actual cost post-call. */
|
|
320
|
-
requestId = "";
|
|
321
|
-
apiKey;
|
|
322
|
-
language;
|
|
323
|
-
model;
|
|
324
|
-
encoding;
|
|
325
|
-
sampleRate;
|
|
326
|
-
endpointingMs;
|
|
327
|
-
utteranceEndMs;
|
|
328
|
-
smartFormat;
|
|
329
|
-
interimResults;
|
|
330
|
-
vadEvents;
|
|
331
|
-
constructor(apiKey, languageOrOptions, model, encoding, sampleRate, options) {
|
|
332
|
-
this.apiKey = apiKey;
|
|
333
|
-
const opts = typeof languageOrOptions === "object" && languageOrOptions !== null ? languageOrOptions : options ?? {};
|
|
334
|
-
this.language = (typeof languageOrOptions === "string" ? languageOrOptions : opts.language) ?? "en";
|
|
335
|
-
this.model = model ?? opts.model ?? "nova-3";
|
|
336
|
-
this.encoding = encoding ?? opts.encoding ?? "linear16";
|
|
337
|
-
this.sampleRate = sampleRate ?? opts.sampleRate ?? 16e3;
|
|
338
|
-
this.endpointingMs = opts.endpointingMs ?? 150;
|
|
339
|
-
this.utteranceEndMs = opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3;
|
|
340
|
-
this.smartFormat = opts.smartFormat ?? true;
|
|
341
|
-
this.interimResults = opts.interimResults ?? true;
|
|
342
|
-
this.vadEvents = opts.vadEvents ?? true;
|
|
343
|
-
}
|
|
344
|
-
/** Factory for Twilio calls — mulaw 8 kHz. Forwards tuning options through. */
|
|
345
|
-
static forTwilio(apiKey, language = "en", model = "nova-3", options = {}) {
|
|
346
|
-
return new _DeepgramSTT(apiKey, language, model, "mulaw", 8e3, options);
|
|
347
|
-
}
|
|
348
|
-
async connect() {
|
|
349
|
-
const params = new URLSearchParams({
|
|
350
|
-
model: this.model,
|
|
351
|
-
language: this.language,
|
|
352
|
-
encoding: this.encoding,
|
|
353
|
-
sample_rate: String(this.sampleRate),
|
|
354
|
-
channels: "1",
|
|
355
|
-
interim_results: this.interimResults ? "true" : "false",
|
|
356
|
-
endpointing: String(this.endpointingMs),
|
|
357
|
-
smart_format: this.smartFormat ? "true" : "false",
|
|
358
|
-
vad_events: this.vadEvents ? "true" : "false",
|
|
359
|
-
no_delay: "true"
|
|
360
|
-
});
|
|
361
|
-
if (this.utteranceEndMs !== null) {
|
|
362
|
-
params.set("utterance_end_ms", String(Math.max(this.utteranceEndMs, 1e3)));
|
|
363
|
-
}
|
|
364
|
-
const url = `${DEEPGRAM_WS_URL}?${params.toString()}`;
|
|
365
|
-
this.ws = new import_ws4.default(url, {
|
|
366
|
-
headers: { Authorization: `Token ${this.apiKey}` }
|
|
367
|
-
});
|
|
368
|
-
await new Promise((resolve, reject) => {
|
|
369
|
-
const timer = setTimeout(() => reject(new Error("Deepgram connect timeout")), 1e4);
|
|
370
|
-
this.ws.once("open", () => {
|
|
371
|
-
clearTimeout(timer);
|
|
372
|
-
resolve();
|
|
373
|
-
});
|
|
374
|
-
this.ws.once("error", (err) => {
|
|
375
|
-
clearTimeout(timer);
|
|
376
|
-
reject(err);
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
this.ws.on("message", (raw) => {
|
|
380
|
-
let data;
|
|
381
|
-
try {
|
|
382
|
-
data = JSON.parse(raw.toString());
|
|
383
|
-
} catch {
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
if (data.type === "Metadata" && data.request_id) {
|
|
387
|
-
this.requestId = data.request_id;
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (data.type !== "Results") return;
|
|
391
|
-
const alternatives = data.channel?.alternatives ?? [];
|
|
392
|
-
if (!alternatives.length) return;
|
|
393
|
-
const best = alternatives[0];
|
|
394
|
-
const text = (best.transcript ?? "").trim();
|
|
395
|
-
if (!text) return;
|
|
396
|
-
const transcript = {
|
|
397
|
-
text,
|
|
398
|
-
isFinal: Boolean(data.is_final) || Boolean(data.speech_final),
|
|
399
|
-
confidence: best.confidence ?? 0
|
|
400
|
-
};
|
|
401
|
-
for (const cb of this.callbacks) {
|
|
402
|
-
cb(transcript);
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
sendAudio(audio) {
|
|
407
|
-
if (!this.ws || this.ws.readyState !== import_ws4.default.OPEN) return;
|
|
408
|
-
this.ws.send(audio);
|
|
409
|
-
}
|
|
410
|
-
onTranscript(callback) {
|
|
411
|
-
if (this.callbacks.length >= 10) {
|
|
412
|
-
getLogger().warn("DeepgramSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback.");
|
|
413
|
-
this.callbacks[this.callbacks.length - 1] = callback;
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
this.callbacks.push(callback);
|
|
417
|
-
}
|
|
418
|
-
close() {
|
|
419
|
-
if (this.ws) {
|
|
420
|
-
try {
|
|
421
|
-
this.ws.send(JSON.stringify({ type: "CloseStream" }));
|
|
422
|
-
} catch {
|
|
423
|
-
}
|
|
424
|
-
this.ws.close();
|
|
425
|
-
this.ws = null;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// src/providers/whisper-stt.ts
|
|
433
|
-
function wrapPcmInWav(pcm, sampleRate = 16e3, channels = 1, bitsPerSample = 16) {
|
|
434
|
-
const dataSize = pcm.length;
|
|
435
|
-
const header = Buffer.alloc(44);
|
|
436
|
-
header.write("RIFF", 0);
|
|
437
|
-
header.writeUInt32LE(36 + dataSize, 4);
|
|
438
|
-
header.write("WAVE", 8);
|
|
439
|
-
header.write("fmt ", 12);
|
|
440
|
-
header.writeUInt32LE(16, 16);
|
|
441
|
-
header.writeUInt16LE(1, 20);
|
|
442
|
-
header.writeUInt16LE(channels, 22);
|
|
443
|
-
header.writeUInt32LE(sampleRate, 24);
|
|
444
|
-
header.writeUInt32LE(sampleRate * channels * (bitsPerSample / 8), 28);
|
|
445
|
-
header.writeUInt16LE(channels * (bitsPerSample / 8), 32);
|
|
446
|
-
header.writeUInt16LE(bitsPerSample, 34);
|
|
447
|
-
header.write("data", 36);
|
|
448
|
-
header.writeUInt32LE(dataSize, 40);
|
|
449
|
-
return Buffer.concat([header, pcm]);
|
|
308
|
+
// src/provider-factory.ts
|
|
309
|
+
async function createSTT(agent) {
|
|
310
|
+
return agent.stt ?? null;
|
|
450
311
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
312
|
+
async function createTTS(agent) {
|
|
313
|
+
return agent.tts ?? null;
|
|
314
|
+
}
|
|
315
|
+
var init_provider_factory = __esm({
|
|
316
|
+
"src/provider-factory.ts"() {
|
|
454
317
|
"use strict";
|
|
455
|
-
init_logger();
|
|
456
|
-
OPENAI_TRANSCRIPTION_URL = "https://api.openai.com/v1/audio/transcriptions";
|
|
457
|
-
DEFAULT_BUFFER_SIZE = 16e3 * 2;
|
|
458
|
-
WhisperSTT = class _WhisperSTT {
|
|
459
|
-
apiKey;
|
|
460
|
-
model;
|
|
461
|
-
language;
|
|
462
|
-
bufferSize;
|
|
463
|
-
buffer = Buffer.alloc(0);
|
|
464
|
-
callbacks = [];
|
|
465
|
-
running = false;
|
|
466
|
-
pendingTranscriptions = [];
|
|
467
|
-
constructor(apiKey, model = "whisper-1", language, bufferSize = DEFAULT_BUFFER_SIZE) {
|
|
468
|
-
this.apiKey = apiKey;
|
|
469
|
-
this.model = model;
|
|
470
|
-
this.language = language;
|
|
471
|
-
this.bufferSize = bufferSize;
|
|
472
|
-
}
|
|
473
|
-
/** Factory for Twilio calls — mulaw 8 kHz is transcoded upstream, so we still receive PCM 16-bit. */
|
|
474
|
-
static forTwilio(apiKey, language = "en", model = "whisper-1") {
|
|
475
|
-
return new _WhisperSTT(apiKey, model, language);
|
|
476
|
-
}
|
|
477
|
-
async connect() {
|
|
478
|
-
this.running = true;
|
|
479
|
-
this.buffer = Buffer.alloc(0);
|
|
480
|
-
}
|
|
481
|
-
sendAudio(audio) {
|
|
482
|
-
if (!this.running) return;
|
|
483
|
-
this.buffer = Buffer.concat([this.buffer, audio]);
|
|
484
|
-
if (this.buffer.length >= this.bufferSize) {
|
|
485
|
-
const pcm = this.buffer;
|
|
486
|
-
this.buffer = Buffer.alloc(0);
|
|
487
|
-
this.trackTranscription(this.transcribeBuffer(pcm));
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
trackTranscription(promise) {
|
|
491
|
-
const wrapped = promise.finally(() => {
|
|
492
|
-
const idx = this.pendingTranscriptions.indexOf(wrapped);
|
|
493
|
-
if (idx !== -1) this.pendingTranscriptions.splice(idx, 1);
|
|
494
|
-
});
|
|
495
|
-
this.pendingTranscriptions.push(wrapped);
|
|
496
|
-
}
|
|
497
|
-
onTranscript(callback) {
|
|
498
|
-
if (this.callbacks.length >= 10) {
|
|
499
|
-
getLogger().warn("WhisperSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback.");
|
|
500
|
-
this.callbacks[this.callbacks.length - 1] = callback;
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
this.callbacks.push(callback);
|
|
504
|
-
}
|
|
505
|
-
async close() {
|
|
506
|
-
this.running = false;
|
|
507
|
-
if (this.buffer.length >= this.bufferSize / 4) {
|
|
508
|
-
const pcm = this.buffer;
|
|
509
|
-
this.buffer = Buffer.alloc(0);
|
|
510
|
-
this.trackTranscription(this.transcribeBuffer(pcm));
|
|
511
|
-
} else {
|
|
512
|
-
this.buffer = Buffer.alloc(0);
|
|
513
|
-
}
|
|
514
|
-
await Promise.allSettled(this.pendingTranscriptions);
|
|
515
|
-
this.callbacks = [];
|
|
516
|
-
}
|
|
517
|
-
// ------------------------------------------------------------------
|
|
518
|
-
// Private
|
|
519
|
-
// ------------------------------------------------------------------
|
|
520
|
-
async transcribeBuffer(pcm) {
|
|
521
|
-
const wav = wrapPcmInWav(pcm);
|
|
522
|
-
const formData = new FormData();
|
|
523
|
-
formData.append("file", new Blob([wav.buffer.slice(wav.byteOffset, wav.byteOffset + wav.byteLength)], { type: "audio/wav" }), "audio.wav");
|
|
524
|
-
formData.append("model", this.model);
|
|
525
|
-
if (this.language) {
|
|
526
|
-
formData.append("language", this.language);
|
|
527
|
-
}
|
|
528
|
-
try {
|
|
529
|
-
const resp = await fetch(OPENAI_TRANSCRIPTION_URL, {
|
|
530
|
-
method: "POST",
|
|
531
|
-
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
532
|
-
body: formData,
|
|
533
|
-
signal: AbortSignal.timeout(15e3)
|
|
534
|
-
});
|
|
535
|
-
if (!resp.ok) {
|
|
536
|
-
const body = await resp.text();
|
|
537
|
-
getLogger().error(`WhisperSTT transcription error: ${resp.status} ${body}`);
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
const json = await resp.json();
|
|
541
|
-
const text = (json.text ?? "").trim();
|
|
542
|
-
if (!text) return;
|
|
543
|
-
const transcript = {
|
|
544
|
-
text,
|
|
545
|
-
isFinal: true,
|
|
546
|
-
confidence: 1
|
|
547
|
-
};
|
|
548
|
-
for (const cb of this.callbacks) {
|
|
549
|
-
cb(transcript);
|
|
550
|
-
}
|
|
551
|
-
} catch (err) {
|
|
552
|
-
getLogger().error(`WhisperSTT transcription error: ${String(err)}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
318
|
}
|
|
557
319
|
});
|
|
558
320
|
|
|
@@ -1972,258 +1734,125 @@ var init_remote_message = __esm({
|
|
|
1972
1734
|
}
|
|
1973
1735
|
});
|
|
1974
1736
|
|
|
1975
|
-
// src/providers/
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
return ELEVENLABS_VOICE_ID_BY_NAME[voice.toLowerCase()] ?? voice;
|
|
1980
|
-
}
|
|
1981
|
-
var ELEVENLABS_BASE_URL, ELEVENLABS_VOICE_ID_BY_NAME, VOICE_ID_PATTERN, ElevenLabsTTS;
|
|
1982
|
-
var init_elevenlabs_tts = __esm({
|
|
1983
|
-
"src/providers/elevenlabs-tts.ts"() {
|
|
1737
|
+
// src/providers/deepgram-stt.ts
|
|
1738
|
+
var import_ws4, DEEPGRAM_WS_URL, DeepgramSTT;
|
|
1739
|
+
var init_deepgram_stt = __esm({
|
|
1740
|
+
"src/providers/deepgram-stt.ts"() {
|
|
1984
1741
|
"use strict";
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
liam: "TX3LPaxmHKxFdv7VOQHJ",
|
|
2005
|
-
dorothy: "ThT5KcBeYPX3keUQqHPh",
|
|
2006
|
-
josh: "TxGEqnHWrfWFTfGW9XjX",
|
|
2007
|
-
arnold: "VR6AewLTigWG4xSOukaG",
|
|
2008
|
-
charlotte: "XB0fDUnXU5powFXDhCwa",
|
|
2009
|
-
matilda: "XrExE9yKIg1WjnnlVkGX",
|
|
2010
|
-
matthew: "Yko7PKHZNXotIFUBG7I9",
|
|
2011
|
-
james: "ZQe5CZNOzWyzPSCn5a3c",
|
|
2012
|
-
joseph: "Zlb1dXrM653N07WRdFW3",
|
|
2013
|
-
jeremy: "bVMeCyTHy58xNoL34h3p",
|
|
2014
|
-
michael: "flq6f7yk4E4fJM5XTYuZ",
|
|
2015
|
-
ethan: "g5CIjZEefAph4nQFvHAz",
|
|
2016
|
-
gigi: "jBpfuIE2acCO8z3wKNLl",
|
|
2017
|
-
freya: "jsCqWAovK2LkecY7zXl4",
|
|
2018
|
-
brian: "nPczCjzI2devNBz1zQrb",
|
|
2019
|
-
grace: "oWAxZDx7w5VEj9dCyTzz",
|
|
2020
|
-
daniel: "onwK4e9ZLuTAKqWW03F9",
|
|
2021
|
-
lily: "pFZP5JQG7iQjIQuC4Bku",
|
|
2022
|
-
serena: "pMsXgVXv3BLzUgSXRplE",
|
|
2023
|
-
adam: "pNInz6obpgDQGcFmaJgB",
|
|
2024
|
-
nicole: "piTKgcLEGmPE4e6mEKli",
|
|
2025
|
-
bill: "pqHfZKP75CvOlQylNhV4",
|
|
2026
|
-
jessie: "t0jbNlBVZ17f02VDIeMI",
|
|
2027
|
-
ryan: "wViXBPUzp2ZZixB1xQuM",
|
|
2028
|
-
sam: "yoZ06aMxZJJ28mfd3POQ",
|
|
2029
|
-
glinda: "z9fAnlkpzviPz146aGWa",
|
|
2030
|
-
giovanni: "zcAOhNBS3c14rBihAFp1",
|
|
2031
|
-
mimi: "zrHiDhphv9ZnVXBqCLjz",
|
|
2032
|
-
alloy: "21m00Tcm4TlvDq8ikWAM"
|
|
2033
|
-
};
|
|
2034
|
-
VOICE_ID_PATTERN = /^[A-Za-z0-9]{20}$/;
|
|
2035
|
-
ElevenLabsTTS = class {
|
|
2036
|
-
constructor(apiKey, voiceId = "21m00Tcm4TlvDq8ikWAM", modelId = "eleven_turbo_v2_5", outputFormat = "pcm_16000") {
|
|
1742
|
+
import_ws4 = __toESM(require("ws"));
|
|
1743
|
+
init_logger();
|
|
1744
|
+
DEEPGRAM_WS_URL = "wss://api.deepgram.com/v1/listen";
|
|
1745
|
+
DeepgramSTT = class _DeepgramSTT {
|
|
1746
|
+
ws = null;
|
|
1747
|
+
callbacks = [];
|
|
1748
|
+
/** Request ID from Deepgram — used to query actual cost post-call. */
|
|
1749
|
+
requestId = "";
|
|
1750
|
+
apiKey;
|
|
1751
|
+
language;
|
|
1752
|
+
model;
|
|
1753
|
+
encoding;
|
|
1754
|
+
sampleRate;
|
|
1755
|
+
endpointingMs;
|
|
1756
|
+
utteranceEndMs;
|
|
1757
|
+
smartFormat;
|
|
1758
|
+
interimResults;
|
|
1759
|
+
vadEvents;
|
|
1760
|
+
constructor(apiKey, languageOrOptions, model, encoding, sampleRate, options) {
|
|
2037
1761
|
this.apiKey = apiKey;
|
|
2038
|
-
|
|
2039
|
-
this.
|
|
2040
|
-
this.
|
|
1762
|
+
const opts = typeof languageOrOptions === "object" && languageOrOptions !== null ? languageOrOptions : options ?? {};
|
|
1763
|
+
this.language = (typeof languageOrOptions === "string" ? languageOrOptions : opts.language) ?? "en";
|
|
1764
|
+
this.model = model ?? opts.model ?? "nova-3";
|
|
1765
|
+
this.encoding = encoding ?? opts.encoding ?? "linear16";
|
|
1766
|
+
this.sampleRate = sampleRate ?? opts.sampleRate ?? 16e3;
|
|
1767
|
+
this.endpointingMs = opts.endpointingMs ?? 150;
|
|
1768
|
+
this.utteranceEndMs = opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3;
|
|
1769
|
+
this.smartFormat = opts.smartFormat ?? true;
|
|
1770
|
+
this.interimResults = opts.interimResults ?? true;
|
|
1771
|
+
this.vadEvents = opts.vadEvents ?? true;
|
|
2041
1772
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
*
|
|
2046
|
-
* For large chunks (or when latency matters) call `synthesizeStream` instead.
|
|
2047
|
-
*/
|
|
2048
|
-
async synthesize(text) {
|
|
2049
|
-
const chunks = [];
|
|
2050
|
-
for await (const chunk of this.synthesizeStream(text)) {
|
|
2051
|
-
chunks.push(chunk);
|
|
2052
|
-
}
|
|
2053
|
-
return Buffer.concat(chunks);
|
|
1773
|
+
/** Factory for Twilio calls — mulaw 8 kHz. Forwards tuning options through. */
|
|
1774
|
+
static forTwilio(apiKey, language = "en", model = "nova-3", options = {}) {
|
|
1775
|
+
return new _DeepgramSTT(apiKey, language, model, "mulaw", 8e3, options);
|
|
2054
1776
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
"Content-Type": "application/json"
|
|
2068
|
-
},
|
|
2069
|
-
body: JSON.stringify({ text, model_id: this.modelId }),
|
|
2070
|
-
signal: AbortSignal.timeout(3e4)
|
|
1777
|
+
async connect() {
|
|
1778
|
+
const params = new URLSearchParams({
|
|
1779
|
+
model: this.model,
|
|
1780
|
+
language: this.language,
|
|
1781
|
+
encoding: this.encoding,
|
|
1782
|
+
sample_rate: String(this.sampleRate),
|
|
1783
|
+
channels: "1",
|
|
1784
|
+
interim_results: this.interimResults ? "true" : "false",
|
|
1785
|
+
endpointing: String(this.endpointingMs),
|
|
1786
|
+
smart_format: this.smartFormat ? "true" : "false",
|
|
1787
|
+
vad_events: this.vadEvents ? "true" : "false",
|
|
1788
|
+
no_delay: "true"
|
|
2071
1789
|
});
|
|
2072
|
-
if (
|
|
2073
|
-
|
|
2074
|
-
throw new Error(`ElevenLabs TTS error ${response.status}: ${body}`);
|
|
2075
|
-
}
|
|
2076
|
-
if (!response.body) {
|
|
2077
|
-
throw new Error("ElevenLabs TTS: no response body");
|
|
1790
|
+
if (this.utteranceEndMs !== null) {
|
|
1791
|
+
params.set("utterance_end_ms", String(Math.max(this.utteranceEndMs, 1e3)));
|
|
2078
1792
|
}
|
|
2079
|
-
const
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
1793
|
+
const url = `${DEEPGRAM_WS_URL}?${params.toString()}`;
|
|
1794
|
+
this.ws = new import_ws4.default(url, {
|
|
1795
|
+
headers: { Authorization: `Token ${this.apiKey}` }
|
|
1796
|
+
});
|
|
1797
|
+
await new Promise((resolve, reject) => {
|
|
1798
|
+
const timer = setTimeout(() => reject(new Error("Deepgram connect timeout")), 1e4);
|
|
1799
|
+
this.ws.once("open", () => {
|
|
1800
|
+
clearTimeout(timer);
|
|
1801
|
+
resolve();
|
|
1802
|
+
});
|
|
1803
|
+
this.ws.once("error", (err) => {
|
|
1804
|
+
clearTimeout(timer);
|
|
1805
|
+
reject(err);
|
|
2090
1806
|
});
|
|
2091
|
-
reader.releaseLock();
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
});
|
|
2097
|
-
|
|
2098
|
-
// src/providers/openai-tts.ts
|
|
2099
|
-
var OPENAI_TTS_URL, OpenAITTS;
|
|
2100
|
-
var init_openai_tts = __esm({
|
|
2101
|
-
"src/providers/openai-tts.ts"() {
|
|
2102
|
-
"use strict";
|
|
2103
|
-
OPENAI_TTS_URL = "https://api.openai.com/v1/audio/speech";
|
|
2104
|
-
OpenAITTS = class _OpenAITTS {
|
|
2105
|
-
constructor(apiKey, voice = "alloy", model = "tts-1") {
|
|
2106
|
-
this.apiKey = apiKey;
|
|
2107
|
-
this.voice = voice;
|
|
2108
|
-
this.model = model;
|
|
2109
|
-
}
|
|
2110
|
-
/**
|
|
2111
|
-
* Synthesise text to speech and return the full audio as a single Buffer.
|
|
2112
|
-
*
|
|
2113
|
-
* For large chunks (or when latency matters) call `synthesizeStream` instead.
|
|
2114
|
-
*/
|
|
2115
|
-
async synthesize(text) {
|
|
2116
|
-
const chunks = [];
|
|
2117
|
-
for await (const chunk of this.synthesizeStream(text)) {
|
|
2118
|
-
chunks.push(chunk);
|
|
2119
|
-
}
|
|
2120
|
-
return Buffer.concat(chunks);
|
|
2121
|
-
}
|
|
2122
|
-
/**
|
|
2123
|
-
* Synthesise text and yield audio chunks as they arrive (streaming).
|
|
2124
|
-
*
|
|
2125
|
-
* OpenAI returns 24 kHz PCM16; each chunk is resampled to 16 kHz before
|
|
2126
|
-
* yielding so the output is ready for telephony pipelines.
|
|
2127
|
-
*
|
|
2128
|
-
* The resampler carries state (buffered samples + odd trailing byte)
|
|
2129
|
-
* between chunks — without that state cross-chunk sample alignment drifts
|
|
2130
|
-
* and the caller hears pops / dropped audio (BUG #23, mirror of the
|
|
2131
|
-
* Python `audioop.ratecv` fix).
|
|
2132
|
-
*/
|
|
2133
|
-
async *synthesizeStream(text) {
|
|
2134
|
-
const response = await fetch(OPENAI_TTS_URL, {
|
|
2135
|
-
method: "POST",
|
|
2136
|
-
headers: {
|
|
2137
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
2138
|
-
"Content-Type": "application/json"
|
|
2139
|
-
},
|
|
2140
|
-
body: JSON.stringify({
|
|
2141
|
-
model: this.model,
|
|
2142
|
-
input: text,
|
|
2143
|
-
voice: this.voice,
|
|
2144
|
-
response_format: "pcm"
|
|
2145
|
-
}),
|
|
2146
|
-
signal: AbortSignal.timeout(3e4)
|
|
2147
1807
|
});
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
}
|
|
2155
|
-
const ctx = { carryByte: null, leftover: [] };
|
|
2156
|
-
const reader = response.body.getReader();
|
|
2157
|
-
try {
|
|
2158
|
-
while (true) {
|
|
2159
|
-
const { done, value } = await reader.read();
|
|
2160
|
-
if (done) break;
|
|
2161
|
-
if (value && value.length > 0) {
|
|
2162
|
-
const out = _OpenAITTS.resampleStreaming(Buffer.from(value), ctx);
|
|
2163
|
-
if (out.length > 0) yield out;
|
|
2164
|
-
}
|
|
1808
|
+
this.ws.on("message", (raw) => {
|
|
1809
|
+
let data;
|
|
1810
|
+
try {
|
|
1811
|
+
data = JSON.parse(raw.toString());
|
|
1812
|
+
} catch {
|
|
1813
|
+
return;
|
|
2165
1814
|
}
|
|
2166
|
-
if (
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
tail.writeInt16LE(ctx.leftover[i], i * 2);
|
|
2170
|
-
}
|
|
2171
|
-
yield tail;
|
|
1815
|
+
if (data.type === "Metadata" && data.request_id) {
|
|
1816
|
+
this.requestId = data.request_id;
|
|
1817
|
+
return;
|
|
2172
1818
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
1819
|
+
if (data.type !== "Results") return;
|
|
1820
|
+
const alternatives = data.channel?.alternatives ?? [];
|
|
1821
|
+
if (!alternatives.length) return;
|
|
1822
|
+
const best = alternatives[0];
|
|
1823
|
+
const text = (best.transcript ?? "").trim();
|
|
1824
|
+
if (!text) return;
|
|
1825
|
+
const transcript = {
|
|
1826
|
+
text,
|
|
1827
|
+
isFinal: Boolean(data.is_final) || Boolean(data.speech_final),
|
|
1828
|
+
confidence: best.confidence ?? 0
|
|
1829
|
+
};
|
|
1830
|
+
for (const cb of this.callbacks) {
|
|
1831
|
+
cb(transcript);
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
sendAudio(audio) {
|
|
1836
|
+
if (!this.ws || this.ws.readyState !== import_ws4.default.OPEN) return;
|
|
1837
|
+
this.ws.send(audio);
|
|
1838
|
+
}
|
|
1839
|
+
onTranscript(callback) {
|
|
1840
|
+
if (this.callbacks.length >= 10) {
|
|
1841
|
+
getLogger().warn("DeepgramSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback.");
|
|
1842
|
+
this.callbacks[this.callbacks.length - 1] = callback;
|
|
1843
|
+
return;
|
|
2177
1844
|
}
|
|
1845
|
+
this.callbacks.push(callback);
|
|
2178
1846
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
ctx.carryByte = null;
|
|
2188
|
-
} else {
|
|
2189
|
-
buf = audio;
|
|
2190
|
-
}
|
|
2191
|
-
if (buf.length % 2 === 1) {
|
|
2192
|
-
ctx.carryByte = buf[buf.length - 1];
|
|
2193
|
-
buf = buf.subarray(0, buf.length - 1);
|
|
2194
|
-
}
|
|
2195
|
-
if (buf.length === 0 && ctx.leftover.length === 0) {
|
|
2196
|
-
return Buffer.alloc(0);
|
|
2197
|
-
}
|
|
2198
|
-
const sampleCount = buf.length / 2;
|
|
2199
|
-
const samples = ctx.leftover.slice();
|
|
2200
|
-
for (let i2 = 0; i2 < sampleCount; i2++) {
|
|
2201
|
-
samples.push(buf.readInt16LE(i2 * 2));
|
|
2202
|
-
}
|
|
2203
|
-
const out = [];
|
|
2204
|
-
let i = 0;
|
|
2205
|
-
while (i + 2 < samples.length) {
|
|
2206
|
-
out.push(samples[i]);
|
|
2207
|
-
out.push(Math.trunc((samples[i + 1] + samples[i + 2]) / 2));
|
|
2208
|
-
i += 3;
|
|
2209
|
-
}
|
|
2210
|
-
ctx.leftover = samples.slice(i);
|
|
2211
|
-
const buffer = Buffer.alloc(out.length * 2);
|
|
2212
|
-
for (let j = 0; j < out.length; j++) {
|
|
2213
|
-
buffer.writeInt16LE(out[j], j * 2);
|
|
2214
|
-
}
|
|
2215
|
-
return buffer;
|
|
2216
|
-
}
|
|
2217
|
-
/** @deprecated use {@link resampleStreaming} with persistent state. */
|
|
2218
|
-
static resample24kTo16k(audio) {
|
|
2219
|
-
const ctx = { carryByte: null, leftover: [] };
|
|
2220
|
-
const out = _OpenAITTS.resampleStreaming(audio, ctx);
|
|
2221
|
-
if (ctx.leftover.length === 0) return out;
|
|
2222
|
-
const tail = Buffer.alloc(ctx.leftover.length * 2);
|
|
2223
|
-
for (let i = 0; i < ctx.leftover.length; i++) {
|
|
2224
|
-
tail.writeInt16LE(ctx.leftover[i], i * 2);
|
|
1847
|
+
close() {
|
|
1848
|
+
if (this.ws) {
|
|
1849
|
+
try {
|
|
1850
|
+
this.ws.send(JSON.stringify({ type: "CloseStream" }));
|
|
1851
|
+
} catch {
|
|
1852
|
+
}
|
|
1853
|
+
this.ws.close();
|
|
1854
|
+
this.ws = null;
|
|
2225
1855
|
}
|
|
2226
|
-
return Buffer.concat([out, tail]);
|
|
2227
1856
|
}
|
|
2228
1857
|
};
|
|
2229
1858
|
}
|
|
@@ -3119,7 +2748,7 @@ async function queryDeepgramCost(metricsAcc, deepgramKey, deepgramRequestId) {
|
|
|
3119
2748
|
const usd = reqData.response?.details?.usd;
|
|
3120
2749
|
if (usd != null) {
|
|
3121
2750
|
metricsAcc.setActualSttCost(usd);
|
|
3122
|
-
getLogger().
|
|
2751
|
+
getLogger().debug(`Deepgram actual cost: $${usd}`);
|
|
3123
2752
|
}
|
|
3124
2753
|
}
|
|
3125
2754
|
}
|
|
@@ -3133,8 +2762,8 @@ var init_stream_handler = __esm({
|
|
|
3133
2762
|
"use strict";
|
|
3134
2763
|
init_openai_realtime();
|
|
3135
2764
|
init_elevenlabs_convai();
|
|
3136
|
-
|
|
3137
|
-
|
|
2765
|
+
init_deepgram_stt();
|
|
2766
|
+
init_provider_factory();
|
|
3138
2767
|
init_metrics();
|
|
3139
2768
|
init_transcoding();
|
|
3140
2769
|
init_llm_loop();
|
|
@@ -3176,8 +2805,8 @@ var init_stream_handler = __esm({
|
|
|
3176
2805
|
this.caller = caller;
|
|
3177
2806
|
this.callee = callee;
|
|
3178
2807
|
this.history = createHistoryManager(200);
|
|
3179
|
-
const sttProviderName = deps.agent.stt
|
|
3180
|
-
const ttsProviderName = deps.agent.tts
|
|
2808
|
+
const sttProviderName = deps.agent.stt ? deps.agent.stt.constructor?.name ?? "custom" : void 0;
|
|
2809
|
+
const ttsProviderName = deps.agent.tts ? deps.agent.tts.constructor?.name ?? "custom" : void 0;
|
|
3181
2810
|
const providerMode = deps.agent.provider ?? "openai_realtime";
|
|
3182
2811
|
this.metricsAcc = new CallMetricsAccumulator({
|
|
3183
2812
|
callId: "",
|
|
@@ -3187,7 +2816,7 @@ var init_stream_handler = __esm({
|
|
|
3187
2816
|
ttsProvider: ttsProviderName,
|
|
3188
2817
|
pricing: deps.pricing
|
|
3189
2818
|
});
|
|
3190
|
-
getLogger().
|
|
2819
|
+
getLogger().debug(`WebSocket connection opened (${deps.bridge.label})`);
|
|
3191
2820
|
}
|
|
3192
2821
|
// ---------------------------------------------------------------------------
|
|
3193
2822
|
// Public: called by the provider-specific parsers in server.ts
|
|
@@ -3203,9 +2832,12 @@ var init_stream_handler = __esm({
|
|
|
3203
2832
|
this.metricsAcc.callId = callId;
|
|
3204
2833
|
if (customParams.caller && !this.caller) this.caller = customParams.caller;
|
|
3205
2834
|
if (customParams.callee && !this.callee) this.callee = customParams.callee;
|
|
3206
|
-
|
|
2835
|
+
const mode = this.deps.agent.engine ? `engine=${this.deps.agent.engine.kind ?? "unknown"}` : "pipeline";
|
|
2836
|
+
getLogger().info(
|
|
2837
|
+
`Call started: ${callId} (${this.deps.bridge.label}, ${mode}, ${sanitizeLogValue(this.caller || "?")} \u2192 ${sanitizeLogValue(this.callee || "?")})`
|
|
2838
|
+
);
|
|
3207
2839
|
if (Object.keys(customParams).length > 0) {
|
|
3208
|
-
getLogger().
|
|
2840
|
+
getLogger().debug(`Custom params: ${sanitizeLogValue(JSON.stringify(customParams))}`);
|
|
3209
2841
|
}
|
|
3210
2842
|
this.deps.metricsStore.recordCallStart({
|
|
3211
2843
|
call_id: callId,
|
|
@@ -3253,7 +2885,7 @@ var init_stream_handler = __esm({
|
|
|
3253
2885
|
}
|
|
3254
2886
|
});
|
|
3255
2887
|
if (recResp.ok) {
|
|
3256
|
-
getLogger().
|
|
2888
|
+
getLogger().debug(`Recording started for ${callId}`);
|
|
3257
2889
|
} else {
|
|
3258
2890
|
getLogger().warn(`could not start recording: ${await recResp.text()}`);
|
|
3259
2891
|
}
|
|
@@ -3308,7 +2940,7 @@ var init_stream_handler = __esm({
|
|
|
3308
2940
|
}
|
|
3309
2941
|
/** Handle a DTMF keypress event (Twilio only). */
|
|
3310
2942
|
async handleDtmf(digit) {
|
|
3311
|
-
getLogger().
|
|
2943
|
+
getLogger().debug(`DTMF: ${digit}`);
|
|
3312
2944
|
if (this.adapter instanceof OpenAIRealtimeAdapter) {
|
|
3313
2945
|
await this.adapter.sendText(`The user pressed key ${digit} on their phone keypad.`);
|
|
3314
2946
|
}
|
|
@@ -3367,26 +2999,17 @@ var init_stream_handler = __esm({
|
|
|
3367
2999
|
// ---------------------------------------------------------------------------
|
|
3368
3000
|
async initPipeline(resolvedPrompt) {
|
|
3369
3001
|
const label = this.deps.bridge.label;
|
|
3370
|
-
this.stt = this.deps.bridge.createStt(this.deps.agent);
|
|
3371
|
-
|
|
3372
|
-
if (this.deps.agent.tts.provider === "elevenlabs") {
|
|
3373
|
-
this.tts = new ElevenLabsTTS(this.deps.agent.tts.apiKey, this.deps.agent.tts.voice ?? "21m00Tcm4TlvDq8ikWAM");
|
|
3374
|
-
}
|
|
3375
|
-
if (this.deps.agent.tts.provider === "openai") {
|
|
3376
|
-
this.tts = new OpenAITTS(this.deps.agent.tts.apiKey, this.deps.agent.tts.voice ?? "alloy");
|
|
3377
|
-
}
|
|
3378
|
-
} else if (this.deps.agent.elevenlabsKey) {
|
|
3379
|
-
this.tts = new ElevenLabsTTS(this.deps.agent.elevenlabsKey, this.deps.agent.voice || "rachel");
|
|
3380
|
-
}
|
|
3002
|
+
this.stt = await this.deps.bridge.createStt(this.deps.agent);
|
|
3003
|
+
this.tts = await createTTS(this.deps.agent);
|
|
3381
3004
|
if (!this.stt) {
|
|
3382
|
-
getLogger().
|
|
3005
|
+
getLogger().debug(`Pipeline mode (${label}): no STT configured`);
|
|
3383
3006
|
}
|
|
3384
3007
|
if (!this.tts) {
|
|
3385
|
-
getLogger().
|
|
3008
|
+
getLogger().debug(`Pipeline mode (${label}): no TTS configured`);
|
|
3386
3009
|
}
|
|
3387
3010
|
try {
|
|
3388
3011
|
if (this.stt) await this.stt.connect();
|
|
3389
|
-
getLogger().
|
|
3012
|
+
getLogger().debug(`Pipeline mode (${label}): STT + TTS connected`);
|
|
3390
3013
|
} catch (e) {
|
|
3391
3014
|
getLogger().error(`Pipeline connect FAILED (${label}):`, e);
|
|
3392
3015
|
try {
|
|
@@ -3419,7 +3042,24 @@ var init_stream_handler = __esm({
|
|
|
3419
3042
|
this.history.push({ role: "assistant", text: this.deps.agent.firstMessage, timestamp: Date.now() });
|
|
3420
3043
|
}
|
|
3421
3044
|
}
|
|
3422
|
-
if (
|
|
3045
|
+
if (this.deps.agent.llm) {
|
|
3046
|
+
if (this.deps.onMessage) {
|
|
3047
|
+
throw new Error(
|
|
3048
|
+
"Cannot pass both agent({ llm }) and serve({ onMessage }). Pick one \u2014 `llm` for built-in LLMs, `onMessage` for custom logic."
|
|
3049
|
+
);
|
|
3050
|
+
}
|
|
3051
|
+
this.llmLoop = new LLMLoop(
|
|
3052
|
+
"",
|
|
3053
|
+
// apiKey unused when llmProvider is supplied
|
|
3054
|
+
"",
|
|
3055
|
+
// model unused when llmProvider is supplied
|
|
3056
|
+
resolvedPrompt,
|
|
3057
|
+
this.deps.agent.tools,
|
|
3058
|
+
this.deps.agent.llm
|
|
3059
|
+
);
|
|
3060
|
+
const llmLabel = this.deps.agent.llm.constructor?.name ?? "custom";
|
|
3061
|
+
getLogger().debug(`Built-in LLM loop active (pipeline, ${label}, llm=${llmLabel})`);
|
|
3062
|
+
} else if (!this.deps.onMessage && this.deps.config.openaiKey) {
|
|
3423
3063
|
let llmModel = this.deps.agent.model || "gpt-4o-mini";
|
|
3424
3064
|
if (llmModel.includes("realtime")) llmModel = "gpt-4o-mini";
|
|
3425
3065
|
this.llmLoop = new LLMLoop(
|
|
@@ -3428,7 +3068,7 @@ var init_stream_handler = __esm({
|
|
|
3428
3068
|
resolvedPrompt,
|
|
3429
3069
|
this.deps.agent.tools
|
|
3430
3070
|
);
|
|
3431
|
-
getLogger().
|
|
3071
|
+
getLogger().debug(`Built-in LLM loop active (pipeline, ${label})`);
|
|
3432
3072
|
}
|
|
3433
3073
|
if (this.stt) {
|
|
3434
3074
|
this.stt.onTranscript(async (transcript) => {
|
|
@@ -3489,7 +3129,7 @@ var init_stream_handler = __esm({
|
|
|
3489
3129
|
}
|
|
3490
3130
|
async processTranscript(transcript) {
|
|
3491
3131
|
if (transcript.text && this.isSpeaking) {
|
|
3492
|
-
getLogger().
|
|
3132
|
+
getLogger().debug(
|
|
3493
3133
|
`Barge-in: caller spoke over agent (${sanitizeLogValue(transcript.text.slice(0, 40))})`
|
|
3494
3134
|
);
|
|
3495
3135
|
this.isSpeaking = false;
|
|
@@ -3524,17 +3164,17 @@ var init_stream_handler = __esm({
|
|
|
3524
3164
|
"cool"
|
|
3525
3165
|
]);
|
|
3526
3166
|
if (HALLUCINATIONS.has(stripped) || stripped === "") {
|
|
3527
|
-
getLogger().
|
|
3167
|
+
getLogger().debug(`Dropped likely STT hallucination: ${sanitizeLogValue(normalised.slice(0, 40))}`);
|
|
3528
3168
|
return;
|
|
3529
3169
|
}
|
|
3530
3170
|
if (sinceLastMs < 2e3 && normalised === this.lastCommitText) {
|
|
3531
|
-
getLogger().
|
|
3171
|
+
getLogger().debug(
|
|
3532
3172
|
`Dropped duplicate final transcript (${(sinceLastMs / 1e3).toFixed(1)}s since last): ${sanitizeLogValue(normalised.slice(0, 40))}`
|
|
3533
3173
|
);
|
|
3534
3174
|
return;
|
|
3535
3175
|
}
|
|
3536
3176
|
if (sinceLastMs < 500) {
|
|
3537
|
-
getLogger().
|
|
3177
|
+
getLogger().debug(
|
|
3538
3178
|
`Dropped back-to-back final transcript (${(sinceLastMs / 1e3).toFixed(2)}s since last): ${sanitizeLogValue(normalised.slice(0, 40))}`
|
|
3539
3179
|
);
|
|
3540
3180
|
return;
|
|
@@ -3542,7 +3182,7 @@ var init_stream_handler = __esm({
|
|
|
3542
3182
|
this.lastCommitText = normalised;
|
|
3543
3183
|
this.lastCommitAt = now;
|
|
3544
3184
|
const label = this.deps.bridge.label;
|
|
3545
|
-
getLogger().
|
|
3185
|
+
getLogger().debug(`User (${label} pipeline): ${sanitizeLogValue(transcript.text)}`);
|
|
3546
3186
|
this.metricsAcc.startTurn();
|
|
3547
3187
|
this.metricsAcc.recordSttComplete(transcript.text);
|
|
3548
3188
|
if (this.deps.onTranscript) {
|
|
@@ -3557,7 +3197,7 @@ var init_stream_handler = __esm({
|
|
|
3557
3197
|
const hookCtx = this.buildHookContext();
|
|
3558
3198
|
const filteredTranscript = await hookExecutor.runAfterTranscribe(transcript.text, hookCtx);
|
|
3559
3199
|
if (filteredTranscript === null) {
|
|
3560
|
-
getLogger().
|
|
3200
|
+
getLogger().debug(`afterTranscribe hook vetoed turn (${label})`);
|
|
3561
3201
|
this.metricsAcc.recordTurnInterrupted();
|
|
3562
3202
|
return;
|
|
3563
3203
|
}
|
|
@@ -3645,7 +3285,7 @@ var init_stream_handler = __esm({
|
|
|
3645
3285
|
if (!this.llmLoop) {
|
|
3646
3286
|
const guard = checkGuardrails(responseText, this.deps.agent.guardrails);
|
|
3647
3287
|
if (guard) {
|
|
3648
|
-
getLogger().
|
|
3288
|
+
getLogger().debug(`Guardrail '${guard.name}' triggered (pipeline)`);
|
|
3649
3289
|
responseText = guard.replacement ?? "I'm sorry, I can't respond to that.";
|
|
3650
3290
|
}
|
|
3651
3291
|
this.metricsAcc.recordLlmComplete();
|
|
@@ -3723,7 +3363,7 @@ var init_stream_handler = __esm({
|
|
|
3723
3363
|
this.adapter = this.deps.buildAIAdapter(resolvedPrompt);
|
|
3724
3364
|
try {
|
|
3725
3365
|
await this.adapter.connect();
|
|
3726
|
-
getLogger().
|
|
3366
|
+
getLogger().debug(`AI adapter connected (${label})`);
|
|
3727
3367
|
} catch (e) {
|
|
3728
3368
|
getLogger().error(`AI adapter connect FAILED (${label}):`, e);
|
|
3729
3369
|
try {
|
|
@@ -3765,7 +3405,7 @@ var init_stream_handler = __esm({
|
|
|
3765
3405
|
this.deps.bridge.sendMark(this.ws, `audio_${this.chunkCount}`, this.streamSid);
|
|
3766
3406
|
} else if (type === "transcript_input") {
|
|
3767
3407
|
const inputText = eventData;
|
|
3768
|
-
getLogger().
|
|
3408
|
+
getLogger().debug(`User (${this.deps.bridge.label}): ${sanitizeLogValue(inputText)}`);
|
|
3769
3409
|
this.history.push({ role: "user", text: inputText, timestamp: Date.now() });
|
|
3770
3410
|
this.metricsAcc.startTurn();
|
|
3771
3411
|
this.currentAgentText = "";
|
|
@@ -3783,7 +3423,7 @@ var init_stream_handler = __esm({
|
|
|
3783
3423
|
if (outputText) {
|
|
3784
3424
|
const triggered = checkGuardrails(outputText, this.deps.agent.guardrails);
|
|
3785
3425
|
if (triggered) {
|
|
3786
|
-
getLogger().
|
|
3426
|
+
getLogger().debug(`Guardrail '${triggered.name}' triggered`);
|
|
3787
3427
|
if (this.adapter instanceof OpenAIRealtimeAdapter) {
|
|
3788
3428
|
this.adapter.cancelResponse();
|
|
3789
3429
|
await this.adapter.sendText(triggered.replacement ?? "I'm sorry, I can't respond to that.");
|
|
@@ -3842,7 +3482,7 @@ var init_stream_handler = __esm({
|
|
|
3842
3482
|
await adapter.sendFunctionResult(fc.call_id, JSON.stringify({ error: "Invalid phone number format", status: "rejected" }));
|
|
3843
3483
|
return;
|
|
3844
3484
|
}
|
|
3845
|
-
getLogger().
|
|
3485
|
+
getLogger().debug(`Transferring call to ${transferTo}`);
|
|
3846
3486
|
await adapter.sendFunctionResult(fc.call_id, JSON.stringify({ status: "transferring", to: transferTo }));
|
|
3847
3487
|
await this.deps.bridge.transferCall(this.callId, transferTo);
|
|
3848
3488
|
if (this.deps.onTranscript) {
|
|
@@ -3858,7 +3498,7 @@ var init_stream_handler = __esm({
|
|
|
3858
3498
|
endArgs = {};
|
|
3859
3499
|
}
|
|
3860
3500
|
const reason = endArgs.reason ?? "conversation_complete";
|
|
3861
|
-
getLogger().
|
|
3501
|
+
getLogger().debug(`Ending call (${this.deps.bridge.label}): ${reason}`);
|
|
3862
3502
|
await adapter.sendFunctionResult(fc.call_id, JSON.stringify({ status: "ending", reason }));
|
|
3863
3503
|
await this.deps.bridge.endCall(this.callId, this.ws);
|
|
3864
3504
|
if (this.deps.onTranscript) {
|
|
@@ -3895,10 +3535,11 @@ var init_stream_handler = __esm({
|
|
|
3895
3535
|
this.maxDurationTimer = null;
|
|
3896
3536
|
}
|
|
3897
3537
|
await this.deps.bridge.queryTelephonyCost(this.metricsAcc, this.callId);
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3538
|
+
if (this.stt instanceof DeepgramSTT && this.stt.requestId) {
|
|
3539
|
+
const dgKey = this.stt.apiKey;
|
|
3540
|
+
if (dgKey) {
|
|
3541
|
+
await queryDeepgramCost(this.metricsAcc, dgKey, this.stt.requestId);
|
|
3542
|
+
}
|
|
3902
3543
|
}
|
|
3903
3544
|
const finalMetrics = this.metricsAcc.endCall();
|
|
3904
3545
|
const callEndData = {
|
|
@@ -3909,6 +3550,11 @@ var init_stream_handler = __esm({
|
|
|
3909
3550
|
transcript: [...this.history.entries],
|
|
3910
3551
|
metrics: finalMetrics
|
|
3911
3552
|
};
|
|
3553
|
+
const cost = finalMetrics.cost?.total ?? 0;
|
|
3554
|
+
const latencyP95 = finalMetrics.latency_p95?.total_ms ?? 0;
|
|
3555
|
+
getLogger().info(
|
|
3556
|
+
`Call ended: ${this.callId} (${finalMetrics.duration_seconds.toFixed(1)}s, ${finalMetrics.turns.length} turns, cost=$${cost.toFixed(4)}, p95=${Math.round(latencyP95)}ms)`
|
|
3557
|
+
);
|
|
3912
3558
|
this.deps.metricsStore.recordCallEnd(
|
|
3913
3559
|
callEndData,
|
|
3914
3560
|
finalMetrics
|
|
@@ -4000,11 +3646,16 @@ function resolveVariables(template, variables) {
|
|
|
4000
3646
|
return result;
|
|
4001
3647
|
}
|
|
4002
3648
|
function buildAIAdapter(config, agent, resolvedPrompt) {
|
|
3649
|
+
const engine = agent.engine;
|
|
4003
3650
|
if (agent.provider === "elevenlabs_convai") {
|
|
4004
|
-
|
|
3651
|
+
if (!engine || engine.kind !== "elevenlabs_convai") {
|
|
3652
|
+
throw new Error(
|
|
3653
|
+
"ElevenLabs ConvAI mode requires `agent.engine = new ElevenLabsConvAI({...})`."
|
|
3654
|
+
);
|
|
3655
|
+
}
|
|
4005
3656
|
return new ElevenLabsConvAIAdapter(
|
|
4006
|
-
|
|
4007
|
-
|
|
3657
|
+
engine.apiKey,
|
|
3658
|
+
engine.agentId,
|
|
4008
3659
|
agent.voice ?? "21m00Tcm4TlvDq8ikWAM",
|
|
4009
3660
|
"eleven_turbo_v2_5",
|
|
4010
3661
|
agent.language ?? "en",
|
|
@@ -4017,33 +3668,15 @@ function buildAIAdapter(config, agent, resolvedPrompt) {
|
|
|
4017
3668
|
parameters: t.parameters
|
|
4018
3669
|
})) ?? [];
|
|
4019
3670
|
const tools = [...agentTools, TRANSFER_CALL_TOOL, END_CALL_TOOL];
|
|
3671
|
+
const openaiKey = engine && engine.kind === "openai_realtime" ? engine.apiKey : config.openaiKey ?? "";
|
|
4020
3672
|
return new OpenAIRealtimeAdapter(
|
|
4021
|
-
|
|
3673
|
+
openaiKey,
|
|
4022
3674
|
agent.model,
|
|
4023
3675
|
agent.voice,
|
|
4024
3676
|
resolvedPrompt ?? agent.systemPrompt,
|
|
4025
3677
|
tools
|
|
4026
3678
|
);
|
|
4027
3679
|
}
|
|
4028
|
-
function extractDeepgramOptions(options) {
|
|
4029
|
-
if (!options) return {};
|
|
4030
|
-
const get = (snake, camel) => options[snake] ?? options[camel];
|
|
4031
|
-
const out = {};
|
|
4032
|
-
const model = get("model", "model");
|
|
4033
|
-
if (typeof model === "string") out.model = model;
|
|
4034
|
-
const endpointing = get("endpointing_ms", "endpointingMs");
|
|
4035
|
-
if (typeof endpointing === "number") out.endpointingMs = endpointing;
|
|
4036
|
-
const utteranceEnd = get("utterance_end_ms", "utteranceEndMs");
|
|
4037
|
-
if (utteranceEnd === null) out.utteranceEndMs = null;
|
|
4038
|
-
else if (typeof utteranceEnd === "number") out.utteranceEndMs = utteranceEnd;
|
|
4039
|
-
const smart = get("smart_format", "smartFormat");
|
|
4040
|
-
if (typeof smart === "boolean") out.smartFormat = smart;
|
|
4041
|
-
const interim = get("interim_results", "interimResults");
|
|
4042
|
-
if (typeof interim === "boolean") out.interimResults = interim;
|
|
4043
|
-
const vad = get("vad_events", "vadEvents");
|
|
4044
|
-
if (typeof vad === "boolean") out.vadEvents = vad;
|
|
4045
|
-
return out;
|
|
4046
|
-
}
|
|
4047
3680
|
function isValidTelnyxTransferTarget(target) {
|
|
4048
3681
|
if (typeof target !== "string" || !target) return false;
|
|
4049
3682
|
if (/^\+[1-9]\d{6,14}$/.test(target)) return true;
|
|
@@ -4063,8 +3696,7 @@ var init_server = __esm({
|
|
|
4063
3696
|
import_ws5 = require("ws");
|
|
4064
3697
|
init_openai_realtime();
|
|
4065
3698
|
init_elevenlabs_convai();
|
|
4066
|
-
|
|
4067
|
-
init_whisper_stt();
|
|
3699
|
+
init_provider_factory();
|
|
4068
3700
|
init_pricing();
|
|
4069
3701
|
init_store();
|
|
4070
3702
|
init_routes();
|
|
@@ -4149,24 +3781,7 @@ var init_server = __esm({
|
|
|
4149
3781
|
}
|
|
4150
3782
|
}
|
|
4151
3783
|
createStt(agent) {
|
|
4152
|
-
|
|
4153
|
-
if (agent.stt) {
|
|
4154
|
-
if (agent.stt.provider === "deepgram") {
|
|
4155
|
-
const dgOptions = extractDeepgramOptions(agent.stt.options);
|
|
4156
|
-
if (isPipeline) {
|
|
4157
|
-
return new DeepgramSTT(agent.stt.apiKey, agent.stt.language ?? "en", dgOptions.model, "linear16", 16e3, dgOptions);
|
|
4158
|
-
}
|
|
4159
|
-
return DeepgramSTT.forTwilio(agent.stt.apiKey, agent.stt.language ?? "en", dgOptions.model, dgOptions);
|
|
4160
|
-
} else if (agent.stt.provider === "whisper") {
|
|
4161
|
-
return isPipeline ? new WhisperSTT(agent.stt.apiKey, "whisper-1", agent.stt.language ?? "en") : WhisperSTT.forTwilio(agent.stt.apiKey, agent.stt.language ?? "en");
|
|
4162
|
-
}
|
|
4163
|
-
} else if (agent.deepgramKey) {
|
|
4164
|
-
if (isPipeline) {
|
|
4165
|
-
return new DeepgramSTT(agent.deepgramKey, agent.language ?? "en", "nova-3", "linear16", 16e3);
|
|
4166
|
-
}
|
|
4167
|
-
return DeepgramSTT.forTwilio(agent.deepgramKey, agent.language ?? "en");
|
|
4168
|
-
}
|
|
4169
|
-
return null;
|
|
3784
|
+
return createSTT(agent);
|
|
4170
3785
|
}
|
|
4171
3786
|
async queryTelephonyCost(metricsAcc, callId) {
|
|
4172
3787
|
if (this.config.twilioSid && this.config.twilioToken && callId) {
|
|
@@ -4304,24 +3919,7 @@ var init_server = __esm({
|
|
|
4304
3919
|
ws.close();
|
|
4305
3920
|
}
|
|
4306
3921
|
createStt(agent) {
|
|
4307
|
-
|
|
4308
|
-
if (agent.stt.provider === "deepgram") {
|
|
4309
|
-
const dgOptions = extractDeepgramOptions(agent.stt.options);
|
|
4310
|
-
return new DeepgramSTT(
|
|
4311
|
-
agent.stt.apiKey,
|
|
4312
|
-
agent.stt.language ?? "en",
|
|
4313
|
-
dgOptions.model ?? "nova-3",
|
|
4314
|
-
"linear16",
|
|
4315
|
-
16e3,
|
|
4316
|
-
dgOptions
|
|
4317
|
-
);
|
|
4318
|
-
} else if (agent.stt.provider === "whisper") {
|
|
4319
|
-
return new WhisperSTT(agent.stt.apiKey, "whisper-1", agent.stt.language ?? "en");
|
|
4320
|
-
}
|
|
4321
|
-
} else if (agent.deepgramKey) {
|
|
4322
|
-
return new DeepgramSTT(agent.deepgramKey, agent.language ?? "en", "nova-3", "linear16", 16e3);
|
|
4323
|
-
}
|
|
4324
|
-
return null;
|
|
3922
|
+
return createSTT(agent);
|
|
4325
3923
|
}
|
|
4326
3924
|
async queryTelephonyCost(metricsAcc, callId) {
|
|
4327
3925
|
if (this.config.telnyxKey && callId) {
|
|
@@ -4865,909 +4463,171 @@ Connect AI agents to phone numbers in 4 lines of code
|
|
|
4865
4463
|
}
|
|
4866
4464
|
});
|
|
4867
4465
|
|
|
4868
|
-
//
|
|
4869
|
-
var
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
4881
|
-
};
|
|
4882
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
4883
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
4884
|
-
for (let key of __getOwnPropNames2(from))
|
|
4885
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
4886
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
4887
|
-
}
|
|
4888
|
-
return to;
|
|
4889
|
-
};
|
|
4890
|
-
var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps2(
|
|
4891
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
4892
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
4893
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
4894
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
4895
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
|
|
4896
|
-
mod
|
|
4897
|
-
));
|
|
4898
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
4899
|
-
var constants_exports = {};
|
|
4900
|
-
__export2(constants_exports, {
|
|
4901
|
-
CLOUDFLARED_VERSION: () => CLOUDFLARED_VERSION,
|
|
4902
|
-
DEFAULT_CLOUDFLARED_BIN: () => DEFAULT_CLOUDFLARED_BIN,
|
|
4903
|
-
RELEASE_BASE: () => RELEASE_BASE,
|
|
4904
|
-
bin: () => bin,
|
|
4905
|
-
use: () => use
|
|
4906
|
-
});
|
|
4907
|
-
module2.exports = __toCommonJS2(constants_exports);
|
|
4908
|
-
var import_node_path = __toESM2(require("path"));
|
|
4909
|
-
var DEFAULT_CLOUDFLARED_BIN = import_node_path.default.join(
|
|
4910
|
-
__dirname,
|
|
4911
|
-
"..",
|
|
4912
|
-
"bin",
|
|
4913
|
-
process.platform === "win32" ? "cloudflared.exe" : "cloudflared"
|
|
4466
|
+
// src/tunnel.ts
|
|
4467
|
+
var tunnel_exports = {};
|
|
4468
|
+
__export(tunnel_exports, {
|
|
4469
|
+
startTunnel: () => startTunnel
|
|
4470
|
+
});
|
|
4471
|
+
async function startTunnel(port, timeoutMs = 3e4) {
|
|
4472
|
+
let tunnelMod;
|
|
4473
|
+
try {
|
|
4474
|
+
tunnelMod = await import("cloudflared");
|
|
4475
|
+
} catch {
|
|
4476
|
+
throw new Error(
|
|
4477
|
+
'Built-in tunnel requires the "cloudflared" package. Install it with:\n\n npm install cloudflared\n\nOr provide your own webhookUrl instead of using tunnel: true.'
|
|
4914
4478
|
);
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4479
|
+
}
|
|
4480
|
+
log.info("Starting tunnel to localhost:%d ...", port);
|
|
4481
|
+
const TunnelClass = tunnelMod.Tunnel;
|
|
4482
|
+
const hasQuick = TunnelClass && typeof TunnelClass.quick === "function";
|
|
4483
|
+
let instance;
|
|
4484
|
+
if (hasQuick) {
|
|
4485
|
+
instance = TunnelClass.quick(`http://localhost:${port}`);
|
|
4486
|
+
} else {
|
|
4487
|
+
const result = tunnelMod.tunnel({ "--url": `http://localhost:${port}` });
|
|
4488
|
+
if (result.url && typeof result.url.then === "function") {
|
|
4489
|
+
const tunnelUrl2 = await Promise.race([
|
|
4490
|
+
result.url,
|
|
4491
|
+
new Promise(
|
|
4492
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
4493
|
+
`Tunnel failed to start within ${timeoutMs / 1e3}s. Check your internet connection or provide webhookUrl manually.`
|
|
4494
|
+
)), timeoutMs)
|
|
4495
|
+
)
|
|
4496
|
+
]);
|
|
4497
|
+
const hostname2 = tunnelUrl2.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
4498
|
+
log.info("Tunnel ready: https://%s", hostname2);
|
|
4499
|
+
return { hostname: hostname2, stop: () => {
|
|
4500
|
+
log.info("Stopping tunnel...");
|
|
4501
|
+
result.stop();
|
|
4502
|
+
} };
|
|
4503
|
+
}
|
|
4504
|
+
instance = result;
|
|
4505
|
+
}
|
|
4506
|
+
const tunnelUrl = await Promise.race([
|
|
4507
|
+
new Promise((resolve) => {
|
|
4508
|
+
instance.on("url", (url) => resolve(url));
|
|
4509
|
+
}),
|
|
4510
|
+
new Promise(
|
|
4511
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
4512
|
+
`Tunnel failed to start within ${timeoutMs / 1e3}s. Check your internet connection or provide webhookUrl manually.`
|
|
4513
|
+
)), timeoutMs)
|
|
4514
|
+
)
|
|
4515
|
+
]);
|
|
4516
|
+
const hostname = tunnelUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
4517
|
+
log.info("Tunnel ready: https://%s", hostname);
|
|
4518
|
+
return {
|
|
4519
|
+
hostname,
|
|
4520
|
+
stop: () => {
|
|
4521
|
+
log.info("Stopping tunnel...");
|
|
4522
|
+
instance.stop();
|
|
4918
4523
|
}
|
|
4919
|
-
|
|
4920
|
-
|
|
4524
|
+
};
|
|
4525
|
+
}
|
|
4526
|
+
var log;
|
|
4527
|
+
var init_tunnel = __esm({
|
|
4528
|
+
"src/tunnel.ts"() {
|
|
4529
|
+
"use strict";
|
|
4530
|
+
init_logger();
|
|
4531
|
+
log = getLogger();
|
|
4921
4532
|
}
|
|
4922
4533
|
});
|
|
4923
4534
|
|
|
4924
|
-
//
|
|
4925
|
-
var
|
|
4926
|
-
|
|
4535
|
+
// src/carrier-config.ts
|
|
4536
|
+
var carrier_config_exports = {};
|
|
4537
|
+
__export(carrier_config_exports, {
|
|
4538
|
+
autoConfigureCarrier: () => autoConfigureCarrier,
|
|
4539
|
+
configureTelnyxNumber: () => configureTelnyxNumber,
|
|
4540
|
+
configureTwilioNumber: () => configureTwilioNumber
|
|
4541
|
+
});
|
|
4542
|
+
async function configureTwilioNumber(accountSid, authToken, phoneNumber, voiceUrl) {
|
|
4543
|
+
const auth = `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`;
|
|
4544
|
+
const listUrl = `${TWILIO_API_BASE}/Accounts/${accountSid}/IncomingPhoneNumbers.json?PhoneNumber=${encodeURIComponent(phoneNumber)}`;
|
|
4545
|
+
const listResp = await fetch(listUrl, {
|
|
4546
|
+
method: "GET",
|
|
4547
|
+
headers: { Authorization: auth }
|
|
4548
|
+
});
|
|
4549
|
+
if (!listResp.ok) {
|
|
4550
|
+
throw new Error(
|
|
4551
|
+
`Twilio IncomingPhoneNumbers.list failed: ${listResp.status} ${await listResp.text()}`
|
|
4552
|
+
);
|
|
4553
|
+
}
|
|
4554
|
+
const body = await listResp.json();
|
|
4555
|
+
const match = body.incoming_phone_numbers?.[0];
|
|
4556
|
+
if (!match) {
|
|
4557
|
+
throw new Error(`Twilio number ${phoneNumber} not found on account ${accountSid}`);
|
|
4558
|
+
}
|
|
4559
|
+
const updateUrl = `${TWILIO_API_BASE}/Accounts/${accountSid}/IncomingPhoneNumbers/${match.sid}.json`;
|
|
4560
|
+
const form = new URLSearchParams({ VoiceUrl: voiceUrl, VoiceMethod: "POST" });
|
|
4561
|
+
const updateResp = await fetch(updateUrl, {
|
|
4562
|
+
method: "POST",
|
|
4563
|
+
headers: {
|
|
4564
|
+
Authorization: auth,
|
|
4565
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
4566
|
+
},
|
|
4567
|
+
body: form.toString()
|
|
4568
|
+
});
|
|
4569
|
+
if (!updateResp.ok) {
|
|
4570
|
+
throw new Error(
|
|
4571
|
+
`Twilio IncomingPhoneNumbers.update failed: ${updateResp.status} ${await updateResp.text()}`
|
|
4572
|
+
);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
async function configureTelnyxNumber(apiKey, connectionId, phoneNumber) {
|
|
4576
|
+
const resp = await fetch(`${TELNYX_API_BASE}/phone_numbers/${encodeURIComponent(phoneNumber)}`, {
|
|
4577
|
+
method: "PATCH",
|
|
4578
|
+
headers: {
|
|
4579
|
+
Authorization: `Bearer ${apiKey}`,
|
|
4580
|
+
"Content-Type": "application/json"
|
|
4581
|
+
},
|
|
4582
|
+
body: JSON.stringify({ connection_id: connectionId })
|
|
4583
|
+
});
|
|
4584
|
+
if (!resp.ok) {
|
|
4585
|
+
throw new Error(
|
|
4586
|
+
`Telnyx PATCH /phone_numbers/${phoneNumber} failed: ${resp.status} ${await resp.text()}`
|
|
4587
|
+
);
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
async function autoConfigureCarrier(params) {
|
|
4591
|
+
const log2 = getLogger();
|
|
4592
|
+
const provider = params.telephonyProvider ?? (params.twilioSid ? "twilio" : "telnyx");
|
|
4593
|
+
if (provider === "twilio" && params.twilioSid && params.twilioToken) {
|
|
4594
|
+
const voiceUrl = `https://${params.webhookHost}/webhooks/twilio/voice`;
|
|
4595
|
+
try {
|
|
4596
|
+
await configureTwilioNumber(params.twilioSid, params.twilioToken, params.phoneNumber, voiceUrl);
|
|
4597
|
+
log2.info("Twilio webhook set to %s", voiceUrl);
|
|
4598
|
+
} catch (err) {
|
|
4599
|
+
log2.warn("Could not auto-configure Twilio webhook: %s", err instanceof Error ? err.message : String(err));
|
|
4600
|
+
log2.info("Set webhook manually to: %s", voiceUrl);
|
|
4601
|
+
}
|
|
4602
|
+
return;
|
|
4603
|
+
}
|
|
4604
|
+
if (provider === "telnyx" && params.telnyxKey && params.telnyxConnectionId) {
|
|
4605
|
+
try {
|
|
4606
|
+
await configureTelnyxNumber(params.telnyxKey, params.telnyxConnectionId, params.phoneNumber);
|
|
4607
|
+
log2.info("Telnyx number %s associated with connection %s", params.phoneNumber, params.telnyxConnectionId);
|
|
4608
|
+
} catch (err) {
|
|
4609
|
+
log2.warn("Could not auto-configure Telnyx number: %s", err instanceof Error ? err.message : String(err));
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
var TWILIO_API_BASE, TELNYX_API_BASE;
|
|
4614
|
+
var init_carrier_config = __esm({
|
|
4615
|
+
"src/carrier-config.ts"() {
|
|
4927
4616
|
"use strict";
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
4932
|
-
var __export2 = (target, all) => {
|
|
4933
|
-
for (var name in all)
|
|
4934
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
4935
|
-
};
|
|
4936
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
4937
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
4938
|
-
for (let key of __getOwnPropNames2(from))
|
|
4939
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
4940
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
4941
|
-
}
|
|
4942
|
-
return to;
|
|
4943
|
-
};
|
|
4944
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
4945
|
-
var error_exports = {};
|
|
4946
|
-
__export2(error_exports, {
|
|
4947
|
-
UnsupportedError: () => UnsupportedError
|
|
4948
|
-
});
|
|
4949
|
-
module2.exports = __toCommonJS2(error_exports);
|
|
4950
|
-
var UnsupportedError = class extends Error {
|
|
4951
|
-
constructor(message) {
|
|
4952
|
-
super(message);
|
|
4953
|
-
}
|
|
4954
|
-
};
|
|
4617
|
+
init_logger();
|
|
4618
|
+
TWILIO_API_BASE = "https://api.twilio.com/2010-04-01";
|
|
4619
|
+
TELNYX_API_BASE = "https://api.telnyx.com/v2";
|
|
4955
4620
|
}
|
|
4956
4621
|
});
|
|
4957
4622
|
|
|
4958
|
-
//
|
|
4959
|
-
var
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
var __getProtoOf2 = Object.getPrototypeOf;
|
|
4967
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
4968
|
-
var __export2 = (target, all) => {
|
|
4969
|
-
for (var name in all)
|
|
4970
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
4971
|
-
};
|
|
4972
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
4973
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
4974
|
-
for (let key of __getOwnPropNames2(from))
|
|
4975
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
4976
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
4977
|
-
}
|
|
4978
|
-
return to;
|
|
4979
|
-
};
|
|
4980
|
-
var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps2(
|
|
4981
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
4982
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
4983
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
4984
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
4985
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
|
|
4986
|
-
mod
|
|
4987
|
-
));
|
|
4988
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
4989
|
-
var install_exports = {};
|
|
4990
|
-
__export2(install_exports, {
|
|
4991
|
-
install: () => install,
|
|
4992
|
-
install_linux: () => install_linux,
|
|
4993
|
-
install_macos: () => install_macos,
|
|
4994
|
-
install_windows: () => install_windows
|
|
4995
|
-
});
|
|
4996
|
-
module2.exports = __toCommonJS2(install_exports);
|
|
4997
|
-
var import_node_fs = __toESM2(require("fs"));
|
|
4998
|
-
var import_node_path = __toESM2(require("path"));
|
|
4999
|
-
var import_node_https = __toESM2(require("https"));
|
|
5000
|
-
var import_node_child_process = require("child_process");
|
|
5001
|
-
var import_constants = require_constants();
|
|
5002
|
-
var import_error = require_error();
|
|
5003
|
-
var LINUX_URL = {
|
|
5004
|
-
arm64: "cloudflared-linux-arm64",
|
|
5005
|
-
arm: "cloudflared-linux-arm",
|
|
5006
|
-
x64: "cloudflared-linux-amd64",
|
|
5007
|
-
ia32: "cloudflared-linux-386"
|
|
5008
|
-
};
|
|
5009
|
-
var MACOS_URL = {
|
|
5010
|
-
arm64: "cloudflared-darwin-arm64.tgz",
|
|
5011
|
-
x64: "cloudflared-darwin-amd64.tgz"
|
|
5012
|
-
};
|
|
5013
|
-
var WINDOWS_URL = {
|
|
5014
|
-
x64: "cloudflared-windows-amd64.exe",
|
|
5015
|
-
ia32: "cloudflared-windows-386.exe"
|
|
5016
|
-
};
|
|
5017
|
-
function resolve_base(version2) {
|
|
5018
|
-
if (version2 === "latest") {
|
|
5019
|
-
return `${import_constants.RELEASE_BASE}latest/download/`;
|
|
5020
|
-
}
|
|
5021
|
-
return `${import_constants.RELEASE_BASE}download/${version2}/`;
|
|
5022
|
-
}
|
|
5023
|
-
async function install(to, version2 = import_constants.CLOUDFLARED_VERSION) {
|
|
5024
|
-
if (process.platform === "linux") {
|
|
5025
|
-
return install_linux(to, version2);
|
|
5026
|
-
} else if (process.platform === "darwin") {
|
|
5027
|
-
return install_macos(to, version2);
|
|
5028
|
-
} else if (process.platform === "win32") {
|
|
5029
|
-
return install_windows(to, version2);
|
|
5030
|
-
} else {
|
|
5031
|
-
throw new import_error.UnsupportedError("Unsupported platform: " + process.platform);
|
|
5032
|
-
}
|
|
5033
|
-
}
|
|
5034
|
-
async function install_linux(to, version2 = import_constants.CLOUDFLARED_VERSION) {
|
|
5035
|
-
const file = LINUX_URL[process.arch];
|
|
5036
|
-
if (file === void 0) {
|
|
5037
|
-
throw new import_error.UnsupportedError("Unsupported architecture: " + process.arch);
|
|
5038
|
-
}
|
|
5039
|
-
await download(resolve_base(version2) + file, to);
|
|
5040
|
-
import_node_fs.default.chmodSync(to, "755");
|
|
5041
|
-
return to;
|
|
5042
|
-
}
|
|
5043
|
-
async function install_macos(to, version2 = import_constants.CLOUDFLARED_VERSION) {
|
|
5044
|
-
let arch = process.arch;
|
|
5045
|
-
if (version2 !== "latest" && version_number(version2) < 20240802) {
|
|
5046
|
-
arch = "x64";
|
|
5047
|
-
}
|
|
5048
|
-
const file = MACOS_URL[arch];
|
|
5049
|
-
if (file === void 0) {
|
|
5050
|
-
throw new import_error.UnsupportedError("Unsupported architecture: " + arch);
|
|
5051
|
-
}
|
|
5052
|
-
await download(resolve_base(version2) + file, `${to}.tgz`);
|
|
5053
|
-
process.env.VERBOSE && console.log(`Extracting to ${to}`);
|
|
5054
|
-
(0, import_node_child_process.execSync)(`tar -xzf ${import_node_path.default.basename(`${to}.tgz`)}`, { cwd: import_node_path.default.dirname(to) });
|
|
5055
|
-
import_node_fs.default.unlinkSync(`${to}.tgz`);
|
|
5056
|
-
import_node_fs.default.renameSync(`${import_node_path.default.dirname(to)}/cloudflared`, to);
|
|
5057
|
-
return to;
|
|
5058
|
-
}
|
|
5059
|
-
async function install_windows(to, version2 = import_constants.CLOUDFLARED_VERSION) {
|
|
5060
|
-
const file = WINDOWS_URL[process.arch];
|
|
5061
|
-
if (file === void 0) {
|
|
5062
|
-
throw new import_error.UnsupportedError("Unsupported architecture: " + process.arch);
|
|
5063
|
-
}
|
|
5064
|
-
await download(resolve_base(version2) + file, to);
|
|
5065
|
-
return to;
|
|
5066
|
-
}
|
|
5067
|
-
function download(url, to, redirect = 0) {
|
|
5068
|
-
if (redirect === 0) {
|
|
5069
|
-
process.env.VERBOSE && console.log(`Downloading ${url} to ${to}`);
|
|
5070
|
-
} else {
|
|
5071
|
-
process.env.VERBOSE && console.log(`Redirecting to ${url}`);
|
|
5072
|
-
}
|
|
5073
|
-
if (!import_node_fs.default.existsSync(import_node_path.default.dirname(to))) {
|
|
5074
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(to), { recursive: true });
|
|
5075
|
-
}
|
|
5076
|
-
return new Promise((resolve, reject) => {
|
|
5077
|
-
const request = import_node_https.default.get(url, (res) => {
|
|
5078
|
-
const redirect_code = [301, 302, 303, 307, 308];
|
|
5079
|
-
if (redirect_code.includes(res.statusCode) && res.headers.location !== void 0) {
|
|
5080
|
-
request.destroy();
|
|
5081
|
-
const redirection = res.headers.location;
|
|
5082
|
-
resolve(download(redirection, to, redirect + 1));
|
|
5083
|
-
return;
|
|
5084
|
-
}
|
|
5085
|
-
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
5086
|
-
const file = import_node_fs.default.createWriteStream(to);
|
|
5087
|
-
file.on("finish", () => {
|
|
5088
|
-
file.close(() => resolve(to));
|
|
5089
|
-
});
|
|
5090
|
-
file.on("error", (err) => {
|
|
5091
|
-
import_node_fs.default.unlink(to, () => reject(err));
|
|
5092
|
-
});
|
|
5093
|
-
res.pipe(file);
|
|
5094
|
-
} else {
|
|
5095
|
-
request.destroy();
|
|
5096
|
-
reject(new Error(`HTTP response with status code: ${res.statusCode}`));
|
|
5097
|
-
}
|
|
5098
|
-
});
|
|
5099
|
-
request.on("error", (err) => {
|
|
5100
|
-
reject(err);
|
|
5101
|
-
});
|
|
5102
|
-
request.end();
|
|
5103
|
-
});
|
|
5104
|
-
}
|
|
5105
|
-
function version_number(semver) {
|
|
5106
|
-
const [major, minor, patch] = semver.split(".").map(Number);
|
|
5107
|
-
return major * 1e4 + minor * 100 + patch;
|
|
5108
|
-
}
|
|
5109
|
-
}
|
|
5110
|
-
});
|
|
5111
|
-
|
|
5112
|
-
// node_modules/cloudflared/lib/regex.js
|
|
5113
|
-
var require_regex = __commonJS({
|
|
5114
|
-
"node_modules/cloudflared/lib/regex.js"(exports2, module2) {
|
|
5115
|
-
"use strict";
|
|
5116
|
-
var __defProp2 = Object.defineProperty;
|
|
5117
|
-
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5118
|
-
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5119
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5120
|
-
var __export2 = (target, all) => {
|
|
5121
|
-
for (var name in all)
|
|
5122
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5123
|
-
};
|
|
5124
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
5125
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
5126
|
-
for (let key of __getOwnPropNames2(from))
|
|
5127
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5128
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5129
|
-
}
|
|
5130
|
-
return to;
|
|
5131
|
-
};
|
|
5132
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
5133
|
-
var regex_exports = {};
|
|
5134
|
-
__export2(regex_exports, {
|
|
5135
|
-
config_regex: () => config_regex,
|
|
5136
|
-
conn_regex: () => conn_regex,
|
|
5137
|
-
connectorID_regex: () => connectorID_regex,
|
|
5138
|
-
disconnect_regex: () => disconnect_regex,
|
|
5139
|
-
index_regex: () => index_regex,
|
|
5140
|
-
ip_regex: () => ip_regex,
|
|
5141
|
-
location_regex: () => location_regex,
|
|
5142
|
-
metrics_regex: () => metrics_regex,
|
|
5143
|
-
tunnelID_regex: () => tunnelID_regex
|
|
5144
|
-
});
|
|
5145
|
-
module2.exports = __toCommonJS2(regex_exports);
|
|
5146
|
-
var conn_regex = /connection[= ]([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
5147
|
-
var ip_regex = /ip=([0-9.]+)/;
|
|
5148
|
-
var location_regex = /location=([A-Za-z0-9]+)/;
|
|
5149
|
-
var index_regex = /connIndex=(\d)/;
|
|
5150
|
-
var disconnect_regex = /Unregistered tunnel connection connIndex=(\d)/i;
|
|
5151
|
-
var tunnelID_regex = /tunnelID=([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
5152
|
-
var connectorID_regex = /Connector ID: ([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
5153
|
-
var metrics_regex = /metrics server on ([0-9.:]+\/metrics)/;
|
|
5154
|
-
var config_regex = /config="(.+[^\\])"/;
|
|
5155
|
-
}
|
|
5156
|
-
});
|
|
5157
|
-
|
|
5158
|
-
// node_modules/cloudflared/lib/handler.js
|
|
5159
|
-
var require_handler = __commonJS({
|
|
5160
|
-
"node_modules/cloudflared/lib/handler.js"(exports2, module2) {
|
|
5161
|
-
"use strict";
|
|
5162
|
-
var __defProp2 = Object.defineProperty;
|
|
5163
|
-
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5164
|
-
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5165
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5166
|
-
var __export2 = (target, all) => {
|
|
5167
|
-
for (var name in all)
|
|
5168
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5169
|
-
};
|
|
5170
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
5171
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
5172
|
-
for (let key of __getOwnPropNames2(from))
|
|
5173
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5174
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5175
|
-
}
|
|
5176
|
-
return to;
|
|
5177
|
-
};
|
|
5178
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
5179
|
-
var handler_exports = {};
|
|
5180
|
-
__export2(handler_exports, {
|
|
5181
|
-
ConfigHandler: () => ConfigHandler,
|
|
5182
|
-
ConnectionHandler: () => ConnectionHandler,
|
|
5183
|
-
TryCloudflareHandler: () => TryCloudflareHandler
|
|
5184
|
-
});
|
|
5185
|
-
module2.exports = __toCommonJS2(handler_exports);
|
|
5186
|
-
var import_node_stream = require("stream");
|
|
5187
|
-
var import_regex2 = require_regex();
|
|
5188
|
-
var ConnectionHandler = class {
|
|
5189
|
-
constructor(tunnel) {
|
|
5190
|
-
this.connections = [];
|
|
5191
|
-
this.connected_handler = (output, tunnel2) => {
|
|
5192
|
-
const conn_match = output.match(import_regex2.conn_regex);
|
|
5193
|
-
const ip_match = output.match(import_regex2.ip_regex);
|
|
5194
|
-
const location_match = output.match(import_regex2.location_regex);
|
|
5195
|
-
const index_match = output.match(import_regex2.index_regex);
|
|
5196
|
-
if (conn_match && ip_match && location_match && index_match) {
|
|
5197
|
-
const connection = {
|
|
5198
|
-
id: conn_match[1],
|
|
5199
|
-
ip: ip_match[1],
|
|
5200
|
-
location: location_match[1]
|
|
5201
|
-
};
|
|
5202
|
-
this.connections[Number(index_match[1])] = connection;
|
|
5203
|
-
tunnel2.emit("connected", connection);
|
|
5204
|
-
}
|
|
5205
|
-
};
|
|
5206
|
-
this.disconnected_handler = (output, tunnel2) => {
|
|
5207
|
-
const index_match = output.includes("terminated") ? output.match(import_regex2.index_regex) : null;
|
|
5208
|
-
if (index_match) {
|
|
5209
|
-
const index = Number(index_match[1]);
|
|
5210
|
-
if (this.connections[index]) {
|
|
5211
|
-
tunnel2.emit("disconnected", this.connections[index]);
|
|
5212
|
-
this.connections[index] = void 0;
|
|
5213
|
-
}
|
|
5214
|
-
}
|
|
5215
|
-
};
|
|
5216
|
-
tunnel.addHandler(this.connected_handler.bind(this));
|
|
5217
|
-
tunnel.addHandler(this.disconnected_handler.bind(this));
|
|
5218
|
-
}
|
|
5219
|
-
};
|
|
5220
|
-
var TryCloudflareHandler = class {
|
|
5221
|
-
constructor(tunnel) {
|
|
5222
|
-
this.url_handler = (output, tunnel2) => {
|
|
5223
|
-
const url_match = output.match(/https:\/\/([a-z0-9-]+)\.trycloudflare\.com/);
|
|
5224
|
-
if (url_match) {
|
|
5225
|
-
tunnel2.emit("url", url_match[0]);
|
|
5226
|
-
}
|
|
5227
|
-
};
|
|
5228
|
-
tunnel.addHandler(this.url_handler.bind(this));
|
|
5229
|
-
}
|
|
5230
|
-
};
|
|
5231
|
-
var ConfigHandler = class extends import_node_stream.EventEmitter {
|
|
5232
|
-
constructor(tunnel) {
|
|
5233
|
-
super();
|
|
5234
|
-
this.config_handler = (output, tunnel2) => {
|
|
5235
|
-
const config_match = output.match(/\bconfig="(.+?)" version=(\d+)/);
|
|
5236
|
-
if (config_match) {
|
|
5237
|
-
try {
|
|
5238
|
-
const config_str = config_match[1].replace(/\\"/g, '"');
|
|
5239
|
-
const config = JSON.parse(config_str);
|
|
5240
|
-
const version2 = parseInt(config_match[2], 10);
|
|
5241
|
-
this.emit("config", {
|
|
5242
|
-
config,
|
|
5243
|
-
version: version2
|
|
5244
|
-
});
|
|
5245
|
-
if (config && typeof config === "object" && "ingress" in config && Array.isArray(config.ingress)) {
|
|
5246
|
-
for (const ingress of config.ingress) {
|
|
5247
|
-
if ("hostname" in ingress) {
|
|
5248
|
-
tunnel2.emit("url", ingress.hostname);
|
|
5249
|
-
}
|
|
5250
|
-
}
|
|
5251
|
-
}
|
|
5252
|
-
} catch (error) {
|
|
5253
|
-
this.emit("error", new Error(`Failed to parse config: ${error}`));
|
|
5254
|
-
}
|
|
5255
|
-
}
|
|
5256
|
-
};
|
|
5257
|
-
tunnel.addHandler(this.config_handler.bind(this));
|
|
5258
|
-
}
|
|
5259
|
-
on(event, listener) {
|
|
5260
|
-
return super.on(event, listener);
|
|
5261
|
-
}
|
|
5262
|
-
once(event, listener) {
|
|
5263
|
-
return super.once(event, listener);
|
|
5264
|
-
}
|
|
5265
|
-
off(event, listener) {
|
|
5266
|
-
return super.off(event, listener);
|
|
5267
|
-
}
|
|
5268
|
-
emit(event, ...args) {
|
|
5269
|
-
return super.emit(event, ...args);
|
|
5270
|
-
}
|
|
5271
|
-
};
|
|
5272
|
-
}
|
|
5273
|
-
});
|
|
5274
|
-
|
|
5275
|
-
// node_modules/cloudflared/lib/tunnel.js
|
|
5276
|
-
var require_tunnel = __commonJS({
|
|
5277
|
-
"node_modules/cloudflared/lib/tunnel.js"(exports2, module2) {
|
|
5278
|
-
"use strict";
|
|
5279
|
-
var __defProp2 = Object.defineProperty;
|
|
5280
|
-
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5281
|
-
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5282
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5283
|
-
var __export2 = (target, all) => {
|
|
5284
|
-
for (var name in all)
|
|
5285
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5286
|
-
};
|
|
5287
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
5288
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
5289
|
-
for (let key of __getOwnPropNames2(from))
|
|
5290
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5291
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5292
|
-
}
|
|
5293
|
-
return to;
|
|
5294
|
-
};
|
|
5295
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
5296
|
-
var tunnel_exports2 = {};
|
|
5297
|
-
__export2(tunnel_exports2, {
|
|
5298
|
-
Tunnel: () => Tunnel,
|
|
5299
|
-
build_args: () => build_args,
|
|
5300
|
-
build_options: () => build_options,
|
|
5301
|
-
tunnel: () => tunnel
|
|
5302
|
-
});
|
|
5303
|
-
module2.exports = __toCommonJS2(tunnel_exports2);
|
|
5304
|
-
var import_node_child_process = require("child_process");
|
|
5305
|
-
var import_node_events = require("events");
|
|
5306
|
-
var import_constants = require_constants();
|
|
5307
|
-
var import_handler = require_handler();
|
|
5308
|
-
var Tunnel = class _Tunnel extends import_node_events.EventEmitter {
|
|
5309
|
-
constructor(options = ["tunnel", "--hello-world"]) {
|
|
5310
|
-
super();
|
|
5311
|
-
this.outputHandlers = [];
|
|
5312
|
-
this.stop = this._stop.bind(this);
|
|
5313
|
-
this.setupDefaultHandlers();
|
|
5314
|
-
const args = Array.isArray(options) ? options : build_args(options);
|
|
5315
|
-
this._process = this.createProcess(args);
|
|
5316
|
-
this.setupEventHandlers();
|
|
5317
|
-
}
|
|
5318
|
-
get process() {
|
|
5319
|
-
return this._process;
|
|
5320
|
-
}
|
|
5321
|
-
setupDefaultHandlers() {
|
|
5322
|
-
new import_handler.ConnectionHandler(this);
|
|
5323
|
-
new import_handler.TryCloudflareHandler(this);
|
|
5324
|
-
}
|
|
5325
|
-
/**
|
|
5326
|
-
* Add a custom output handler
|
|
5327
|
-
* @param handler Function to handle cloudflared output
|
|
5328
|
-
*/
|
|
5329
|
-
addHandler(handler) {
|
|
5330
|
-
this.outputHandlers.push(handler);
|
|
5331
|
-
}
|
|
5332
|
-
/**
|
|
5333
|
-
* Remove a previously added output handler
|
|
5334
|
-
* @param handler The handler to remove
|
|
5335
|
-
*/
|
|
5336
|
-
removeHandler(handler) {
|
|
5337
|
-
const index = this.outputHandlers.indexOf(handler);
|
|
5338
|
-
if (index !== -1) {
|
|
5339
|
-
this.outputHandlers.splice(index, 1);
|
|
5340
|
-
}
|
|
5341
|
-
}
|
|
5342
|
-
processOutput(output) {
|
|
5343
|
-
for (const handler of this.outputHandlers) {
|
|
5344
|
-
try {
|
|
5345
|
-
handler(output, this);
|
|
5346
|
-
} catch (error) {
|
|
5347
|
-
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
5348
|
-
}
|
|
5349
|
-
}
|
|
5350
|
-
}
|
|
5351
|
-
setupEventHandlers() {
|
|
5352
|
-
this.on("stdout", (output) => {
|
|
5353
|
-
this.processOutput(output);
|
|
5354
|
-
});
|
|
5355
|
-
this.on("stderr", (output) => {
|
|
5356
|
-
this.processOutput(output);
|
|
5357
|
-
});
|
|
5358
|
-
}
|
|
5359
|
-
createProcess(args) {
|
|
5360
|
-
var _a, _b;
|
|
5361
|
-
const child = (0, import_node_child_process.spawn)(import_constants.bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
5362
|
-
child.on("error", (error) => this.emit("error", error));
|
|
5363
|
-
child.on("exit", (code, signal) => this.emit("exit", code, signal));
|
|
5364
|
-
if (process.env.VERBOSE) {
|
|
5365
|
-
child.stdout.pipe(process.stdout);
|
|
5366
|
-
child.stderr.pipe(process.stderr);
|
|
5367
|
-
}
|
|
5368
|
-
(_a = child.stdout) == null ? void 0 : _a.on("data", (data) => this.emit("stdout", data.toString()));
|
|
5369
|
-
(_b = child.stderr) == null ? void 0 : _b.on("data", (data) => this.emit("stderr", data.toString()));
|
|
5370
|
-
return child;
|
|
5371
|
-
}
|
|
5372
|
-
_stop() {
|
|
5373
|
-
return this.process.kill("SIGINT");
|
|
5374
|
-
}
|
|
5375
|
-
on(event, listener) {
|
|
5376
|
-
return super.on(event, listener);
|
|
5377
|
-
}
|
|
5378
|
-
once(event, listener) {
|
|
5379
|
-
return super.once(event, listener);
|
|
5380
|
-
}
|
|
5381
|
-
off(event, listener) {
|
|
5382
|
-
return super.off(event, listener);
|
|
5383
|
-
}
|
|
5384
|
-
emit(event, ...args) {
|
|
5385
|
-
return super.emit(event, ...args);
|
|
5386
|
-
}
|
|
5387
|
-
/**
|
|
5388
|
-
* Create a quick tunnel without a Cloudflare account.
|
|
5389
|
-
* @param url The local service URL to connect to. If not provided, the hello world mode will be used.
|
|
5390
|
-
* @param options The options to pass to cloudflared.
|
|
5391
|
-
*/
|
|
5392
|
-
static quick(url, options = {}) {
|
|
5393
|
-
const args = ["tunnel"];
|
|
5394
|
-
if (url) {
|
|
5395
|
-
args.push("--url", url);
|
|
5396
|
-
} else {
|
|
5397
|
-
args.push("--hello-world");
|
|
5398
|
-
}
|
|
5399
|
-
args.push(...build_options(options));
|
|
5400
|
-
return new _Tunnel(args);
|
|
5401
|
-
}
|
|
5402
|
-
/**
|
|
5403
|
-
* Create a tunnel with a Cloudflare account.
|
|
5404
|
-
* @param token The Cloudflare Tunnel token.
|
|
5405
|
-
* @param options The options to pass to cloudflared.
|
|
5406
|
-
*/
|
|
5407
|
-
static withToken(token, options = {}) {
|
|
5408
|
-
options["--token"] = token;
|
|
5409
|
-
return new _Tunnel(build_args(options));
|
|
5410
|
-
}
|
|
5411
|
-
};
|
|
5412
|
-
function tunnel(options = {}) {
|
|
5413
|
-
return new Tunnel(options);
|
|
5414
|
-
}
|
|
5415
|
-
function build_args(options) {
|
|
5416
|
-
const args = "--hello-world" in options ? ["tunnel"] : ["tunnel", "run"];
|
|
5417
|
-
args.push(...build_options(options));
|
|
5418
|
-
return args;
|
|
5419
|
-
}
|
|
5420
|
-
function build_options(options) {
|
|
5421
|
-
const opts = [];
|
|
5422
|
-
for (const [key, value] of Object.entries(options)) {
|
|
5423
|
-
if (typeof value === "string") {
|
|
5424
|
-
opts.push(`${key}`, value);
|
|
5425
|
-
} else if (typeof value === "number") {
|
|
5426
|
-
opts.push(`${key}`, value.toString());
|
|
5427
|
-
} else if (typeof value === "boolean") {
|
|
5428
|
-
if (value === true) {
|
|
5429
|
-
opts.push(`${key}`);
|
|
5430
|
-
}
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
return opts;
|
|
5434
|
-
}
|
|
5435
|
-
}
|
|
5436
|
-
});
|
|
5437
|
-
|
|
5438
|
-
// node_modules/cloudflared/lib/service.js
|
|
5439
|
-
var require_service = __commonJS({
|
|
5440
|
-
"node_modules/cloudflared/lib/service.js"(exports2, module2) {
|
|
5441
|
-
"use strict";
|
|
5442
|
-
var __create2 = Object.create;
|
|
5443
|
-
var __defProp2 = Object.defineProperty;
|
|
5444
|
-
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5445
|
-
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5446
|
-
var __getProtoOf2 = Object.getPrototypeOf;
|
|
5447
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5448
|
-
var __export2 = (target, all) => {
|
|
5449
|
-
for (var name in all)
|
|
5450
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5451
|
-
};
|
|
5452
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
5453
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
5454
|
-
for (let key of __getOwnPropNames2(from))
|
|
5455
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5456
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5457
|
-
}
|
|
5458
|
-
return to;
|
|
5459
|
-
};
|
|
5460
|
-
var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps2(
|
|
5461
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
5462
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
5463
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
5464
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
5465
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
|
|
5466
|
-
mod
|
|
5467
|
-
));
|
|
5468
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
5469
|
-
var service_exports = {};
|
|
5470
|
-
__export2(service_exports, {
|
|
5471
|
-
AlreadyInstalledError: () => AlreadyInstalledError2,
|
|
5472
|
-
LINUX_SERVICE_PATH: () => LINUX_SERVICE_PATH,
|
|
5473
|
-
MACOS_SERVICE_PATH: () => MACOS_SERVICE_PATH2,
|
|
5474
|
-
NotInstalledError: () => NotInstalledError2,
|
|
5475
|
-
clean: () => clean,
|
|
5476
|
-
current: () => current,
|
|
5477
|
-
err: () => err,
|
|
5478
|
-
exists: () => exists,
|
|
5479
|
-
identifier: () => identifier2,
|
|
5480
|
-
install: () => install,
|
|
5481
|
-
journal: () => journal,
|
|
5482
|
-
log: () => log2,
|
|
5483
|
-
service: () => service2,
|
|
5484
|
-
service_name: () => service_name,
|
|
5485
|
-
uninstall: () => uninstall
|
|
5486
|
-
});
|
|
5487
|
-
module2.exports = __toCommonJS2(service_exports);
|
|
5488
|
-
var import_node_os = __toESM2(require("os"));
|
|
5489
|
-
var import_node_fs = __toESM2(require("fs"));
|
|
5490
|
-
var import_node_child_process = require("child_process");
|
|
5491
|
-
var import_constants = require_constants();
|
|
5492
|
-
var import_regex2 = require_regex();
|
|
5493
|
-
var identifier2 = "com.cloudflare.cloudflared";
|
|
5494
|
-
var service_name = "cloudflared.service";
|
|
5495
|
-
var MACOS_SERVICE_PATH2 = {
|
|
5496
|
-
PLIST: is_root() ? `/Library/LaunchDaemons/${identifier2}.plist` : `${import_node_os.default.homedir()}/Library/LaunchAgents/${identifier2}.plist`,
|
|
5497
|
-
OUT: is_root() ? `/Library/Logs/${identifier2}.out.log` : `${import_node_os.default.homedir()}/Library/Logs/${identifier2}.out.log`,
|
|
5498
|
-
ERR: is_root() ? `/Library/Logs/${identifier2}.err.log` : `${import_node_os.default.homedir()}/Library/Logs/${identifier2}.err.log`
|
|
5499
|
-
};
|
|
5500
|
-
var LINUX_SERVICE_PATH = {
|
|
5501
|
-
SYSTEMD: `/etc/systemd/system/${service_name}`,
|
|
5502
|
-
SERVICE: "/etc/init.d/cloudflared",
|
|
5503
|
-
SERVICE_OUT: "/var/log/cloudflared.log",
|
|
5504
|
-
SERVICE_ERR: "/var/log/cloudflared.err"
|
|
5505
|
-
};
|
|
5506
|
-
var service2 = { install, uninstall, exists, log: log2, err, current, clean, journal };
|
|
5507
|
-
var AlreadyInstalledError2 = class extends Error {
|
|
5508
|
-
constructor() {
|
|
5509
|
-
super("service is already installed");
|
|
5510
|
-
}
|
|
5511
|
-
};
|
|
5512
|
-
var NotInstalledError2 = class extends Error {
|
|
5513
|
-
constructor() {
|
|
5514
|
-
super("service is not installed");
|
|
5515
|
-
}
|
|
5516
|
-
};
|
|
5517
|
-
function install(token) {
|
|
5518
|
-
if (!["darwin", "linux"].includes(process.platform)) {
|
|
5519
|
-
throw new Error(`Not Implemented on platform ${process.platform}`);
|
|
5520
|
-
}
|
|
5521
|
-
if (exists()) {
|
|
5522
|
-
throw new AlreadyInstalledError2();
|
|
5523
|
-
}
|
|
5524
|
-
const args = ["service", "install"];
|
|
5525
|
-
if (token) {
|
|
5526
|
-
args.push(token);
|
|
5527
|
-
}
|
|
5528
|
-
const result = (0, import_node_child_process.spawnSync)(import_constants.bin, args);
|
|
5529
|
-
if (result.status !== 0) {
|
|
5530
|
-
throw new Error(`service install failed: ${result.stderr.toString()}`);
|
|
5531
|
-
}
|
|
5532
|
-
}
|
|
5533
|
-
function uninstall() {
|
|
5534
|
-
if (!["darwin", "linux"].includes(process.platform)) {
|
|
5535
|
-
throw new Error(`Not Implemented on platform ${process.platform}`);
|
|
5536
|
-
}
|
|
5537
|
-
if (!exists()) {
|
|
5538
|
-
throw new NotInstalledError2();
|
|
5539
|
-
}
|
|
5540
|
-
const result = (0, import_node_child_process.spawnSync)(import_constants.bin, ["service", "uninstall"]);
|
|
5541
|
-
if (result.status !== 0) {
|
|
5542
|
-
throw new Error(`service uninstall failed: ${result.stderr.toString()}`);
|
|
5543
|
-
}
|
|
5544
|
-
if (process.platform === "darwin") {
|
|
5545
|
-
import_node_fs.default.rmSync(MACOS_SERVICE_PATH2.OUT);
|
|
5546
|
-
import_node_fs.default.rmSync(MACOS_SERVICE_PATH2.ERR);
|
|
5547
|
-
} else if (process.platform === "linux" && !is_systemd()) {
|
|
5548
|
-
import_node_fs.default.rmSync(LINUX_SERVICE_PATH.SERVICE_OUT);
|
|
5549
|
-
import_node_fs.default.rmSync(LINUX_SERVICE_PATH.SERVICE_ERR);
|
|
5550
|
-
}
|
|
5551
|
-
}
|
|
5552
|
-
function log2() {
|
|
5553
|
-
if (!exists()) {
|
|
5554
|
-
throw new NotInstalledError2();
|
|
5555
|
-
}
|
|
5556
|
-
if (process.platform === "darwin") {
|
|
5557
|
-
return import_node_fs.default.readFileSync(MACOS_SERVICE_PATH2.OUT, "utf8");
|
|
5558
|
-
}
|
|
5559
|
-
if (process.platform === "linux" && !is_systemd()) {
|
|
5560
|
-
return import_node_fs.default.readFileSync(LINUX_SERVICE_PATH.SERVICE_OUT, "utf8");
|
|
5561
|
-
}
|
|
5562
|
-
throw new Error(`Not Implemented on platform ${process.platform}`);
|
|
5563
|
-
}
|
|
5564
|
-
function err() {
|
|
5565
|
-
if (!exists()) {
|
|
5566
|
-
throw new NotInstalledError2();
|
|
5567
|
-
}
|
|
5568
|
-
if (process.platform === "darwin") {
|
|
5569
|
-
return import_node_fs.default.readFileSync(MACOS_SERVICE_PATH2.ERR, "utf8");
|
|
5570
|
-
}
|
|
5571
|
-
if (process.platform === "linux" && !is_systemd()) {
|
|
5572
|
-
return import_node_fs.default.readFileSync(LINUX_SERVICE_PATH.SERVICE_ERR, "utf8");
|
|
5573
|
-
}
|
|
5574
|
-
throw new Error(`Not Implemented on platform ${process.platform}`);
|
|
5575
|
-
}
|
|
5576
|
-
function journal(n = 300) {
|
|
5577
|
-
if (process.platform === "linux" && is_systemd()) {
|
|
5578
|
-
const args = ["-u", service_name, "-o", "cat", "-n", n.toString()];
|
|
5579
|
-
return (0, import_node_child_process.spawnSync)("journalctl", args).stdout.toString();
|
|
5580
|
-
}
|
|
5581
|
-
throw new Error(`Not Implemented on platform ${process.platform}`);
|
|
5582
|
-
}
|
|
5583
|
-
function current() {
|
|
5584
|
-
var _a, _b, _c, _d;
|
|
5585
|
-
if (!["darwin", "linux"].includes(process.platform)) {
|
|
5586
|
-
throw new Error(`Not Implemented on platform ${process.platform}`);
|
|
5587
|
-
}
|
|
5588
|
-
if (!exists()) {
|
|
5589
|
-
throw new NotInstalledError2();
|
|
5590
|
-
}
|
|
5591
|
-
const log22 = is_systemd() ? journal() : err();
|
|
5592
|
-
let tunnelID = "";
|
|
5593
|
-
let connectorID = "";
|
|
5594
|
-
const connections = [];
|
|
5595
|
-
let metrics = "";
|
|
5596
|
-
let config = {};
|
|
5597
|
-
for (const line of log22.split("\n")) {
|
|
5598
|
-
try {
|
|
5599
|
-
if (line.match(import_regex2.tunnelID_regex)) {
|
|
5600
|
-
tunnelID = ((_a = line.match(import_regex2.tunnelID_regex)) == null ? void 0 : _a[1]) ?? "";
|
|
5601
|
-
} else if (line.match(import_regex2.connectorID_regex)) {
|
|
5602
|
-
connectorID = ((_b = line.match(import_regex2.connectorID_regex)) == null ? void 0 : _b[1]) ?? "";
|
|
5603
|
-
} else if (line.match(import_regex2.conn_regex) && line.match(import_regex2.location_regex) && line.match(import_regex2.ip_regex) && line.match(import_regex2.index_regex)) {
|
|
5604
|
-
const [, id] = line.match(import_regex2.conn_regex) ?? [];
|
|
5605
|
-
const [, location] = line.match(import_regex2.location_regex) ?? [];
|
|
5606
|
-
const [, ip] = line.match(import_regex2.ip_regex) ?? [];
|
|
5607
|
-
const [, idx] = line.match(import_regex2.index_regex) ?? [];
|
|
5608
|
-
connections[parseInt(idx)] = { id, ip, location };
|
|
5609
|
-
} else if (line.match(import_regex2.disconnect_regex)) {
|
|
5610
|
-
const [, idx] = line.match(import_regex2.disconnect_regex) ?? [];
|
|
5611
|
-
if (parseInt(idx) in connections) {
|
|
5612
|
-
connections[parseInt(idx)] = { id: "", ip: "", location: "" };
|
|
5613
|
-
}
|
|
5614
|
-
} else if (line.match(import_regex2.metrics_regex)) {
|
|
5615
|
-
metrics = ((_c = line.match(import_regex2.metrics_regex)) == null ? void 0 : _c[1]) ?? "";
|
|
5616
|
-
} else if (line.match(import_regex2.config_regex)) {
|
|
5617
|
-
config = JSON.parse(((_d = line.match(import_regex2.config_regex)) == null ? void 0 : _d[1].replace(/\\/g, "")) ?? "{}");
|
|
5618
|
-
}
|
|
5619
|
-
} catch (err2) {
|
|
5620
|
-
if (process.env.VERBOSE) {
|
|
5621
|
-
console.error("log parsing failed", err2);
|
|
5622
|
-
}
|
|
5623
|
-
}
|
|
5624
|
-
}
|
|
5625
|
-
return { tunnelID, connectorID, connections, metrics, config };
|
|
5626
|
-
}
|
|
5627
|
-
function clean() {
|
|
5628
|
-
if (process.platform !== "darwin") {
|
|
5629
|
-
throw new Error(`Not Implemented on platform ${process.platform}`);
|
|
5630
|
-
}
|
|
5631
|
-
if (exists()) {
|
|
5632
|
-
throw new AlreadyInstalledError2();
|
|
5633
|
-
}
|
|
5634
|
-
import_node_fs.default.rmSync(MACOS_SERVICE_PATH2.OUT, { force: true });
|
|
5635
|
-
import_node_fs.default.rmSync(MACOS_SERVICE_PATH2.ERR, { force: true });
|
|
5636
|
-
}
|
|
5637
|
-
function exists() {
|
|
5638
|
-
if (process.platform === "darwin") {
|
|
5639
|
-
return import_node_fs.default.existsSync(MACOS_SERVICE_PATH2.PLIST);
|
|
5640
|
-
} else if (process.platform === "linux") {
|
|
5641
|
-
return is_systemd() ? import_node_fs.default.existsSync(LINUX_SERVICE_PATH.SYSTEMD) : import_node_fs.default.existsSync(LINUX_SERVICE_PATH.SERVICE);
|
|
5642
|
-
}
|
|
5643
|
-
throw new Error(`Not Implemented on platform ${process.platform}`);
|
|
5644
|
-
}
|
|
5645
|
-
function is_root() {
|
|
5646
|
-
var _a;
|
|
5647
|
-
return ((_a = process.getuid) == null ? void 0 : _a.call(process)) === 0;
|
|
5648
|
-
}
|
|
5649
|
-
function is_systemd() {
|
|
5650
|
-
return process.platform === "linux" && import_node_fs.default.existsSync("/run/systemd/system");
|
|
5651
|
-
}
|
|
5652
|
-
}
|
|
5653
|
-
});
|
|
5654
|
-
|
|
5655
|
-
// node_modules/cloudflared/lib/lib.js
|
|
5656
|
-
var require_lib = __commonJS({
|
|
5657
|
-
"node_modules/cloudflared/lib/lib.js"(exports2, module2) {
|
|
5658
|
-
"use strict";
|
|
5659
|
-
var __defProp2 = Object.defineProperty;
|
|
5660
|
-
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
5661
|
-
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
5662
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
5663
|
-
var __export2 = (target, all) => {
|
|
5664
|
-
for (var name in all)
|
|
5665
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
5666
|
-
};
|
|
5667
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
5668
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
5669
|
-
for (let key of __getOwnPropNames2(from))
|
|
5670
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
5671
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
5672
|
-
}
|
|
5673
|
-
return to;
|
|
5674
|
-
};
|
|
5675
|
-
var __reExport = (target, mod, secondTarget) => (__copyProps2(target, mod, "default"), secondTarget && __copyProps2(secondTarget, mod, "default"));
|
|
5676
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
5677
|
-
var lib_exports = {};
|
|
5678
|
-
__export2(lib_exports, {
|
|
5679
|
-
AlreadyInstalledError: () => import_service.AlreadyInstalledError,
|
|
5680
|
-
MACOS_SERVICE_PATH: () => import_service.MACOS_SERVICE_PATH,
|
|
5681
|
-
NotInstalledError: () => import_service.NotInstalledError,
|
|
5682
|
-
identifier: () => import_service.identifier,
|
|
5683
|
-
service: () => import_service.service
|
|
5684
|
-
});
|
|
5685
|
-
module2.exports = __toCommonJS2(lib_exports);
|
|
5686
|
-
__reExport(lib_exports, require_constants(), module2.exports);
|
|
5687
|
-
__reExport(lib_exports, require_install(), module2.exports);
|
|
5688
|
-
__reExport(lib_exports, require_tunnel(), module2.exports);
|
|
5689
|
-
var import_service = require_service();
|
|
5690
|
-
__reExport(lib_exports, require_handler(), module2.exports);
|
|
5691
|
-
}
|
|
5692
|
-
});
|
|
5693
|
-
|
|
5694
|
-
// src/tunnel.ts
|
|
5695
|
-
var tunnel_exports = {};
|
|
5696
|
-
__export(tunnel_exports, {
|
|
5697
|
-
startTunnel: () => startTunnel
|
|
5698
|
-
});
|
|
5699
|
-
async function startTunnel(port, timeoutMs = 3e4) {
|
|
5700
|
-
let tunnelMod;
|
|
5701
|
-
try {
|
|
5702
|
-
tunnelMod = await Promise.resolve().then(() => __toESM(require_lib()));
|
|
5703
|
-
} catch {
|
|
5704
|
-
throw new Error(
|
|
5705
|
-
'Built-in tunnel requires the "cloudflared" package. Install it with:\n\n npm install cloudflared\n\nOr provide your own webhookUrl instead of using tunnel: true.'
|
|
5706
|
-
);
|
|
5707
|
-
}
|
|
5708
|
-
log.info("Starting tunnel to localhost:%d ...", port);
|
|
5709
|
-
const TunnelClass = tunnelMod.Tunnel;
|
|
5710
|
-
const hasQuick = TunnelClass && typeof TunnelClass.quick === "function";
|
|
5711
|
-
let instance;
|
|
5712
|
-
if (hasQuick) {
|
|
5713
|
-
instance = TunnelClass.quick(`http://localhost:${port}`);
|
|
5714
|
-
} else {
|
|
5715
|
-
const result = tunnelMod.tunnel({ "--url": `http://localhost:${port}` });
|
|
5716
|
-
if (result.url && typeof result.url.then === "function") {
|
|
5717
|
-
const tunnelUrl2 = await Promise.race([
|
|
5718
|
-
result.url,
|
|
5719
|
-
new Promise(
|
|
5720
|
-
(_, reject) => setTimeout(() => reject(new Error(
|
|
5721
|
-
`Tunnel failed to start within ${timeoutMs / 1e3}s. Check your internet connection or provide webhookUrl manually.`
|
|
5722
|
-
)), timeoutMs)
|
|
5723
|
-
)
|
|
5724
|
-
]);
|
|
5725
|
-
const hostname2 = tunnelUrl2.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
5726
|
-
log.info("Tunnel ready: https://%s", hostname2);
|
|
5727
|
-
return { hostname: hostname2, stop: () => {
|
|
5728
|
-
log.info("Stopping tunnel...");
|
|
5729
|
-
result.stop();
|
|
5730
|
-
} };
|
|
5731
|
-
}
|
|
5732
|
-
instance = result;
|
|
5733
|
-
}
|
|
5734
|
-
const tunnelUrl = await Promise.race([
|
|
5735
|
-
new Promise((resolve) => {
|
|
5736
|
-
instance.on("url", (url) => resolve(url));
|
|
5737
|
-
}),
|
|
5738
|
-
new Promise(
|
|
5739
|
-
(_, reject) => setTimeout(() => reject(new Error(
|
|
5740
|
-
`Tunnel failed to start within ${timeoutMs / 1e3}s. Check your internet connection or provide webhookUrl manually.`
|
|
5741
|
-
)), timeoutMs)
|
|
5742
|
-
)
|
|
5743
|
-
]);
|
|
5744
|
-
const hostname = tunnelUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
5745
|
-
log.info("Tunnel ready: https://%s", hostname);
|
|
5746
|
-
return {
|
|
5747
|
-
hostname,
|
|
5748
|
-
stop: () => {
|
|
5749
|
-
log.info("Stopping tunnel...");
|
|
5750
|
-
instance.stop();
|
|
5751
|
-
}
|
|
5752
|
-
};
|
|
5753
|
-
}
|
|
5754
|
-
var log;
|
|
5755
|
-
var init_tunnel = __esm({
|
|
5756
|
-
"src/tunnel.ts"() {
|
|
5757
|
-
"use strict";
|
|
5758
|
-
init_logger();
|
|
5759
|
-
log = getLogger();
|
|
5760
|
-
}
|
|
5761
|
-
});
|
|
5762
|
-
|
|
5763
|
-
// src/test-mode.ts
|
|
5764
|
-
var test_mode_exports = {};
|
|
5765
|
-
__export(test_mode_exports, {
|
|
5766
|
-
TestSession: () => TestSession
|
|
5767
|
-
});
|
|
5768
|
-
var import_readline, TestSession;
|
|
5769
|
-
var init_test_mode = __esm({
|
|
5770
|
-
"src/test-mode.ts"() {
|
|
4623
|
+
// src/test-mode.ts
|
|
4624
|
+
var test_mode_exports = {};
|
|
4625
|
+
__export(test_mode_exports, {
|
|
4626
|
+
TestSession: () => TestSession
|
|
4627
|
+
});
|
|
4628
|
+
var import_readline, TestSession;
|
|
4629
|
+
var init_test_mode = __esm({
|
|
4630
|
+
"src/test-mode.ts"() {
|
|
5771
4631
|
"use strict";
|
|
5772
4632
|
import_readline = require("readline");
|
|
5773
4633
|
init_llm_loop();
|
|
@@ -6874,31 +5734,40 @@ var require_node_cron = __commonJS({
|
|
|
6874
5734
|
var index_exports = {};
|
|
6875
5735
|
__export(index_exports, {
|
|
6876
5736
|
AllProvidersFailedError: () => AllProvidersFailedError,
|
|
6877
|
-
|
|
5737
|
+
AnthropicLLM: () => LLM2,
|
|
5738
|
+
AssemblyAISTT: () => STT5,
|
|
6878
5739
|
AuthenticationError: () => AuthenticationError,
|
|
6879
5740
|
BackgroundAudioPlayer: () => BackgroundAudioPlayer,
|
|
6880
5741
|
BuiltinAudioClip: () => BuiltinAudioClip,
|
|
6881
5742
|
CallMetricsAccumulator: () => CallMetricsAccumulator,
|
|
6882
|
-
CartesiaSTT: () =>
|
|
6883
|
-
CartesiaTTS: () =>
|
|
5743
|
+
CartesiaSTT: () => STT3,
|
|
5744
|
+
CartesiaTTS: () => TTS3,
|
|
5745
|
+
CerebrasLLM: () => LLM4,
|
|
6884
5746
|
ChatContext: () => ChatContext,
|
|
5747
|
+
CloudflareTunnel: () => CloudflareTunnel,
|
|
6885
5748
|
DEFAULT_MIN_SENTENCE_LEN: () => DEFAULT_MIN_SENTENCE_LEN,
|
|
6886
5749
|
DEFAULT_PRICING: () => DEFAULT_PRICING,
|
|
6887
5750
|
DTMF_EVENTS: () => DTMF_EVENTS,
|
|
6888
|
-
DeepgramSTT: () =>
|
|
5751
|
+
DeepgramSTT: () => STT,
|
|
5752
|
+
ElevenLabsConvAI: () => ConvAI,
|
|
6889
5753
|
ElevenLabsConvAIAdapter: () => ElevenLabsConvAIAdapter,
|
|
6890
|
-
ElevenLabsTTS: () =>
|
|
5754
|
+
ElevenLabsTTS: () => TTS,
|
|
6891
5755
|
FallbackLLMProvider: () => FallbackLLMProvider,
|
|
6892
5756
|
GEMINI_DEFAULT_INPUT_SR: () => GEMINI_DEFAULT_INPUT_SR,
|
|
6893
5757
|
GEMINI_DEFAULT_OUTPUT_SR: () => GEMINI_DEFAULT_OUTPUT_SR,
|
|
6894
5758
|
GeminiLiveAdapter: () => GeminiLiveAdapter,
|
|
5759
|
+
GoogleLLM: () => LLM5,
|
|
5760
|
+
GroqLLM: () => LLM3,
|
|
5761
|
+
Guardrail: () => Guardrail,
|
|
6895
5762
|
IVRActivity: () => IVRActivity,
|
|
6896
5763
|
LLMLoop: () => LLMLoop,
|
|
6897
|
-
LMNTTTS: () =>
|
|
5764
|
+
LMNTTTS: () => TTS5,
|
|
6898
5765
|
MetricsStore: () => MetricsStore,
|
|
5766
|
+
OpenAILLM: () => LLM,
|
|
6899
5767
|
OpenAILLMProvider: () => OpenAILLMProvider,
|
|
5768
|
+
OpenAIRealtime: () => Realtime,
|
|
6900
5769
|
OpenAIRealtimeAdapter: () => OpenAIRealtimeAdapter,
|
|
6901
|
-
OpenAITTS: () =>
|
|
5770
|
+
OpenAITTS: () => TTS2,
|
|
6902
5771
|
PartialStreamError: () => PartialStreamError,
|
|
6903
5772
|
Patter: () => Patter,
|
|
6904
5773
|
PatterConnectionError: () => PatterConnectionError,
|
|
@@ -6906,15 +5775,19 @@ __export(index_exports, {
|
|
|
6906
5775
|
PipelineHookExecutor: () => PipelineHookExecutor,
|
|
6907
5776
|
ProvisionError: () => ProvisionError,
|
|
6908
5777
|
RemoteMessageHandler: () => RemoteMessageHandler,
|
|
6909
|
-
RimeTTS: () =>
|
|
5778
|
+
RimeTTS: () => TTS4,
|
|
6910
5779
|
SentenceChunker: () => SentenceChunker,
|
|
6911
|
-
SonioxSTT: () =>
|
|
5780
|
+
SonioxSTT: () => STT4,
|
|
5781
|
+
StaticTunnel: () => Static,
|
|
5782
|
+
Telnyx: () => Carrier2,
|
|
6912
5783
|
TestSession: () => TestSession,
|
|
6913
5784
|
TfidfLoopDetector: () => TfidfLoopDetector,
|
|
5785
|
+
Tool: () => Tool,
|
|
5786
|
+
Twilio: () => Carrier,
|
|
6914
5787
|
ULTRAVOX_DEFAULT_API_BASE: () => ULTRAVOX_DEFAULT_API_BASE,
|
|
6915
5788
|
ULTRAVOX_DEFAULT_SR: () => ULTRAVOX_DEFAULT_SR,
|
|
6916
5789
|
UltravoxRealtimeAdapter: () => UltravoxRealtimeAdapter,
|
|
6917
|
-
WhisperSTT: () =>
|
|
5790
|
+
WhisperSTT: () => STT2,
|
|
6918
5791
|
builtinClipPath: () => builtinClipPath,
|
|
6919
5792
|
calculateRealtimeCost: () => calculateRealtimeCost,
|
|
6920
5793
|
calculateSttCost: () => calculateSttCost,
|
|
@@ -6930,6 +5803,7 @@ __export(index_exports, {
|
|
|
6930
5803
|
filterMarkdown: () => filterMarkdown,
|
|
6931
5804
|
formatDtmf: () => formatDtmf,
|
|
6932
5805
|
getLogger: () => getLogger,
|
|
5806
|
+
guardrail: () => guardrail,
|
|
6933
5807
|
isRemoteUrl: () => isRemoteUrl,
|
|
6934
5808
|
isWebSocketUrl: () => isWebSocketUrl,
|
|
6935
5809
|
makeAuthMiddleware: () => makeAuthMiddleware,
|
|
@@ -6951,6 +5825,7 @@ __export(index_exports, {
|
|
|
6951
5825
|
selectSoundFromList: () => selectSoundFromList,
|
|
6952
5826
|
setLogger: () => setLogger,
|
|
6953
5827
|
startTunnel: () => startTunnel,
|
|
5828
|
+
tool: () => tool,
|
|
6954
5829
|
whisper: () => whisper
|
|
6955
5830
|
});
|
|
6956
5831
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -7096,77 +5971,70 @@ var PatterConnection = class {
|
|
|
7096
5971
|
}
|
|
7097
5972
|
};
|
|
7098
5973
|
|
|
7099
|
-
// src/
|
|
7100
|
-
|
|
7101
|
-
|
|
5974
|
+
// src/client.ts
|
|
5975
|
+
init_server();
|
|
5976
|
+
|
|
5977
|
+
// src/engines/openai.ts
|
|
5978
|
+
var Realtime = class {
|
|
5979
|
+
kind = "openai_realtime";
|
|
7102
5980
|
apiKey;
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
constructor(
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
language: this.language
|
|
7116
|
-
};
|
|
7117
|
-
if (this.options) out.options = { ...this.options };
|
|
7118
|
-
return out;
|
|
5981
|
+
model;
|
|
5982
|
+
voice;
|
|
5983
|
+
constructor(opts = {}) {
|
|
5984
|
+
const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
|
|
5985
|
+
if (!key) {
|
|
5986
|
+
throw new Error(
|
|
5987
|
+
"OpenAI Realtime requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
|
|
5988
|
+
);
|
|
5989
|
+
}
|
|
5990
|
+
this.apiKey = key;
|
|
5991
|
+
this.model = opts.model ?? "gpt-4o-mini-realtime-preview";
|
|
5992
|
+
this.voice = opts.voice ?? "alloy";
|
|
7119
5993
|
}
|
|
7120
5994
|
};
|
|
7121
|
-
|
|
7122
|
-
|
|
5995
|
+
|
|
5996
|
+
// src/engines/elevenlabs.ts
|
|
5997
|
+
var ConvAI = class {
|
|
5998
|
+
kind = "elevenlabs_convai";
|
|
7123
5999
|
apiKey;
|
|
6000
|
+
agentId;
|
|
7124
6001
|
voice;
|
|
7125
|
-
constructor(
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
6002
|
+
constructor(opts = {}) {
|
|
6003
|
+
const key = opts.apiKey ?? process.env.ELEVENLABS_API_KEY;
|
|
6004
|
+
const agent = opts.agentId ?? process.env.ELEVENLABS_AGENT_ID;
|
|
6005
|
+
if (!key) {
|
|
6006
|
+
throw new Error(
|
|
6007
|
+
"ElevenLabs ConvAI requires an apiKey. Pass { apiKey: '...' } or set ELEVENLABS_API_KEY in the environment."
|
|
6008
|
+
);
|
|
6009
|
+
}
|
|
6010
|
+
if (!agent) {
|
|
6011
|
+
throw new Error(
|
|
6012
|
+
"ElevenLabs ConvAI requires an agentId. Pass { agentId: 'agent_...' } or set ELEVENLABS_AGENT_ID in the environment."
|
|
6013
|
+
);
|
|
6014
|
+
}
|
|
6015
|
+
this.apiKey = key;
|
|
6016
|
+
this.agentId = agent;
|
|
6017
|
+
this.voice = opts.voice;
|
|
7129
6018
|
}
|
|
7130
|
-
|
|
7131
|
-
|
|
6019
|
+
};
|
|
6020
|
+
|
|
6021
|
+
// src/tunnels/index.ts
|
|
6022
|
+
var CloudflareTunnel = class {
|
|
6023
|
+
kind = "cloudflare";
|
|
6024
|
+
};
|
|
6025
|
+
var Static = class {
|
|
6026
|
+
kind = "static";
|
|
6027
|
+
hostname;
|
|
6028
|
+
constructor(opts) {
|
|
6029
|
+
if (!opts.hostname) {
|
|
6030
|
+
throw new Error("Static tunnel requires a non-empty hostname.");
|
|
6031
|
+
}
|
|
6032
|
+
this.hostname = opts.hostname;
|
|
7132
6033
|
}
|
|
7133
6034
|
};
|
|
7134
|
-
function deepgram(opts) {
|
|
7135
|
-
const options = {
|
|
7136
|
-
model: opts.model ?? "nova-3",
|
|
7137
|
-
endpointing_ms: opts.endpointingMs ?? 150,
|
|
7138
|
-
utterance_end_ms: opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3,
|
|
7139
|
-
smart_format: opts.smartFormat ?? true,
|
|
7140
|
-
interim_results: opts.interimResults ?? true
|
|
7141
|
-
};
|
|
7142
|
-
if (opts.vadEvents !== void 0) options.vad_events = opts.vadEvents;
|
|
7143
|
-
return new STTConfigImpl("deepgram", opts.apiKey, opts.language ?? "en", options);
|
|
7144
|
-
}
|
|
7145
|
-
function whisper(opts) {
|
|
7146
|
-
return new STTConfigImpl("whisper", opts.apiKey, opts.language ?? "en");
|
|
7147
|
-
}
|
|
7148
|
-
function elevenlabs(opts) {
|
|
7149
|
-
return new TTSConfigImpl("elevenlabs", opts.apiKey, opts.voice ?? "rachel");
|
|
7150
|
-
}
|
|
7151
|
-
function openaiTts(opts) {
|
|
7152
|
-
return new TTSConfigImpl("openai", opts.apiKey, opts.voice ?? "alloy");
|
|
7153
|
-
}
|
|
7154
|
-
function cartesia(opts) {
|
|
7155
|
-
return new TTSConfigImpl(
|
|
7156
|
-
"cartesia",
|
|
7157
|
-
opts.apiKey,
|
|
7158
|
-
opts.voice ?? "f786b574-daa5-4673-aa0c-cbe3e8534c02"
|
|
7159
|
-
);
|
|
7160
|
-
}
|
|
7161
|
-
function rime(opts) {
|
|
7162
|
-
return new TTSConfigImpl("rime", opts.apiKey, opts.voice ?? "astra");
|
|
7163
|
-
}
|
|
7164
|
-
function lmnt(opts) {
|
|
7165
|
-
return new TTSConfigImpl("lmnt", opts.apiKey, opts.voice ?? "leah");
|
|
7166
|
-
}
|
|
7167
6035
|
|
|
7168
6036
|
// src/client.ts
|
|
7169
|
-
|
|
6037
|
+
init_logger();
|
|
7170
6038
|
var DEFAULT_BACKEND_URL2 = "wss://api.getpatter.com";
|
|
7171
6039
|
var DEFAULT_REST_URL = "https://api.getpatter.com";
|
|
7172
6040
|
function sttConfigToDict(cfg) {
|
|
@@ -7197,21 +6065,39 @@ var Patter = class {
|
|
|
7197
6065
|
embeddedServer = null;
|
|
7198
6066
|
tunnelHandle = null;
|
|
7199
6067
|
constructor(options) {
|
|
7200
|
-
const
|
|
6068
|
+
const hasCarrier = "carrier" in options && options.carrier !== void 0;
|
|
6069
|
+
const isLocal = "mode" in options && options.mode === "local" || hasCarrier;
|
|
7201
6070
|
if (isLocal) {
|
|
7202
6071
|
const local = options;
|
|
7203
6072
|
if (!local.phoneNumber) {
|
|
7204
6073
|
throw new Error("Local mode requires phoneNumber");
|
|
7205
6074
|
}
|
|
7206
|
-
if (!local.
|
|
7207
|
-
throw new Error(
|
|
6075
|
+
if (!local.carrier) {
|
|
6076
|
+
throw new Error(
|
|
6077
|
+
"Local mode requires a `carrier` instance. Pass `carrier: new Twilio({...})` or `carrier: new Telnyx({...})`."
|
|
6078
|
+
);
|
|
7208
6079
|
}
|
|
7209
|
-
|
|
7210
|
-
|
|
6080
|
+
const carrier = local.carrier;
|
|
6081
|
+
const tunnel = local.tunnel;
|
|
6082
|
+
let tunnelWebhookUrl;
|
|
6083
|
+
if (tunnel instanceof Static) {
|
|
6084
|
+
if (local.webhookUrl) {
|
|
6085
|
+
throw new Error(
|
|
6086
|
+
"Cannot use both `tunnel: new StaticTunnel(...)` and `webhookUrl`. Pick one."
|
|
6087
|
+
);
|
|
6088
|
+
}
|
|
6089
|
+
tunnelWebhookUrl = tunnel.hostname;
|
|
7211
6090
|
}
|
|
7212
6091
|
this.mode = "local";
|
|
7213
|
-
const
|
|
7214
|
-
|
|
6092
|
+
const rawWebhook = tunnelWebhookUrl ?? local.webhookUrl;
|
|
6093
|
+
const normalizedWebhook = rawWebhook ? rawWebhook.replace(/^https?:\/\//, "").replace(/\/$/, "") : void 0;
|
|
6094
|
+
this.localConfig = {
|
|
6095
|
+
carrier,
|
|
6096
|
+
phoneNumber: local.phoneNumber,
|
|
6097
|
+
webhookUrl: normalizedWebhook,
|
|
6098
|
+
tunnel: local.tunnel,
|
|
6099
|
+
openaiKey: local.openaiKey
|
|
6100
|
+
};
|
|
7215
6101
|
this.apiKey = "";
|
|
7216
6102
|
this.backendUrl = DEFAULT_BACKEND_URL2;
|
|
7217
6103
|
this.restUrl = DEFAULT_REST_URL;
|
|
@@ -7228,25 +6114,70 @@ var Patter = class {
|
|
|
7228
6114
|
}
|
|
7229
6115
|
// === Local mode ===
|
|
7230
6116
|
agent(opts) {
|
|
7231
|
-
|
|
6117
|
+
let working = { ...opts };
|
|
6118
|
+
if (opts.engine) {
|
|
6119
|
+
if (opts.provider) {
|
|
6120
|
+
throw new Error(
|
|
6121
|
+
"Cannot pass both `engine:` and `provider:`. Use one (engine is preferred)."
|
|
6122
|
+
);
|
|
6123
|
+
}
|
|
6124
|
+
const engine = opts.engine;
|
|
6125
|
+
if (engine instanceof Realtime) {
|
|
6126
|
+
working = {
|
|
6127
|
+
...working,
|
|
6128
|
+
provider: "openai_realtime",
|
|
6129
|
+
model: working.model ?? engine.model,
|
|
6130
|
+
voice: working.voice ?? engine.voice
|
|
6131
|
+
};
|
|
6132
|
+
if (this.localConfig && !this.localConfig.openaiKey) {
|
|
6133
|
+
this.localConfig = { ...this.localConfig, openaiKey: engine.apiKey };
|
|
6134
|
+
}
|
|
6135
|
+
} else if (engine instanceof ConvAI) {
|
|
6136
|
+
working = {
|
|
6137
|
+
...working,
|
|
6138
|
+
provider: "elevenlabs_convai",
|
|
6139
|
+
voice: working.voice ?? engine.voice
|
|
6140
|
+
};
|
|
6141
|
+
} else {
|
|
6142
|
+
throw new Error(
|
|
6143
|
+
"Unknown engine. Expected OpenAIRealtime or ElevenLabsConvAI instance."
|
|
6144
|
+
);
|
|
6145
|
+
}
|
|
6146
|
+
} else if (!working.provider && (working.stt !== void 0 || working.tts !== void 0 || working.llm !== void 0)) {
|
|
6147
|
+
working = { ...working, provider: "pipeline" };
|
|
6148
|
+
}
|
|
6149
|
+
if (working.provider) {
|
|
7232
6150
|
const valid = ["openai_realtime", "elevenlabs_convai", "pipeline"];
|
|
7233
|
-
if (!valid.includes(
|
|
7234
|
-
throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${
|
|
6151
|
+
if (!valid.includes(working.provider)) {
|
|
6152
|
+
throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${working.provider}'`);
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
if (working.llm !== void 0) {
|
|
6156
|
+
const llm = working.llm;
|
|
6157
|
+
if (!llm || typeof llm.stream !== "function") {
|
|
6158
|
+
throw new Error(
|
|
6159
|
+
"`llm` must be an LLMProvider instance (e.g. new AnthropicLLM()). Got a value without a `.stream` method."
|
|
6160
|
+
);
|
|
6161
|
+
}
|
|
6162
|
+
if (working.engine) {
|
|
6163
|
+
getLogger().warn(
|
|
6164
|
+
"agent({ engine, llm }): `llm` is ignored when `engine` is set \u2014 realtime/ConvAI engines run their own model. Remove `llm` or switch to pipeline mode (stt + tts + llm) to silence this warning."
|
|
6165
|
+
);
|
|
7235
6166
|
}
|
|
7236
6167
|
}
|
|
7237
|
-
if (
|
|
7238
|
-
if (!Array.isArray(
|
|
6168
|
+
if (working.tools) {
|
|
6169
|
+
if (!Array.isArray(working.tools)) {
|
|
7239
6170
|
throw new TypeError("tools must be an array");
|
|
7240
6171
|
}
|
|
7241
|
-
|
|
7242
|
-
if (!
|
|
7243
|
-
if (!
|
|
6172
|
+
working.tools.forEach((tool2, i) => {
|
|
6173
|
+
if (!tool2.name) throw new Error(`tools[${i}] missing required 'name' field`);
|
|
6174
|
+
if (!tool2.webhookUrl && !tool2.handler) throw new Error(`tools[${i}] requires either 'webhookUrl' or 'handler'`);
|
|
7244
6175
|
});
|
|
7245
6176
|
}
|
|
7246
|
-
if (
|
|
6177
|
+
if (working.variables !== void 0 && (typeof working.variables !== "object" || Array.isArray(working.variables))) {
|
|
7247
6178
|
throw new TypeError("variables must be an object");
|
|
7248
6179
|
}
|
|
7249
|
-
return
|
|
6180
|
+
return working;
|
|
7250
6181
|
}
|
|
7251
6182
|
async serve(opts) {
|
|
7252
6183
|
if (this.mode !== "local" || !this.localConfig) {
|
|
@@ -7269,10 +6200,14 @@ var Patter = class {
|
|
|
7269
6200
|
}
|
|
7270
6201
|
let webhookUrl = this.localConfig.webhookUrl ?? "";
|
|
7271
6202
|
const port = opts.port ?? 8e3;
|
|
7272
|
-
|
|
6203
|
+
const ctorTunnel = this.localConfig.tunnel;
|
|
6204
|
+
const wantsCloudflaredFromServe = opts.tunnel === true;
|
|
6205
|
+
const wantsCloudflaredFromCtor = ctorTunnel === true || ctorTunnel instanceof CloudflareTunnel;
|
|
6206
|
+
const wantsCloudflared = wantsCloudflaredFromServe || wantsCloudflaredFromCtor;
|
|
6207
|
+
if (wantsCloudflared && webhookUrl) {
|
|
7273
6208
|
throw new Error("Cannot use both tunnel: true and webhookUrl. Pick one.");
|
|
7274
6209
|
}
|
|
7275
|
-
if (
|
|
6210
|
+
if (wantsCloudflared) {
|
|
7276
6211
|
const { startTunnel: startTunnel2 } = await Promise.resolve().then(() => (init_tunnel(), tunnel_exports));
|
|
7277
6212
|
this.tunnelHandle = await startTunnel2(port);
|
|
7278
6213
|
webhookUrl = this.tunnelHandle.hostname;
|
|
@@ -7282,17 +6217,29 @@ var Patter = class {
|
|
|
7282
6217
|
"No webhookUrl configured. Either:\n - Pass webhookUrl in the Patter constructor\n - Use tunnel: true in serve() to auto-create a tunnel"
|
|
7283
6218
|
);
|
|
7284
6219
|
}
|
|
6220
|
+
const carrier = this.localConfig.carrier;
|
|
6221
|
+
const telephonyProvider = carrier.kind === "twilio" ? "twilio" : "telnyx";
|
|
6222
|
+
const { autoConfigureCarrier: autoConfigureCarrier2 } = await Promise.resolve().then(() => (init_carrier_config(), carrier_config_exports));
|
|
6223
|
+
await autoConfigureCarrier2({
|
|
6224
|
+
telephonyProvider,
|
|
6225
|
+
twilioSid: carrier.kind === "twilio" ? carrier.accountSid : void 0,
|
|
6226
|
+
twilioToken: carrier.kind === "twilio" ? carrier.authToken : void 0,
|
|
6227
|
+
telnyxKey: carrier.kind === "telnyx" ? carrier.apiKey : void 0,
|
|
6228
|
+
telnyxConnectionId: carrier.kind === "telnyx" ? carrier.connectionId : void 0,
|
|
6229
|
+
phoneNumber: this.localConfig.phoneNumber,
|
|
6230
|
+
webhookHost: webhookUrl
|
|
6231
|
+
});
|
|
7285
6232
|
this.embeddedServer = new EmbeddedServer(
|
|
7286
6233
|
{
|
|
7287
|
-
twilioSid:
|
|
7288
|
-
twilioToken:
|
|
6234
|
+
twilioSid: carrier.kind === "twilio" ? carrier.accountSid : void 0,
|
|
6235
|
+
twilioToken: carrier.kind === "twilio" ? carrier.authToken : void 0,
|
|
7289
6236
|
openaiKey: this.localConfig.openaiKey,
|
|
7290
6237
|
phoneNumber: this.localConfig.phoneNumber,
|
|
7291
6238
|
webhookUrl,
|
|
7292
|
-
telephonyProvider
|
|
7293
|
-
telnyxKey:
|
|
7294
|
-
telnyxConnectionId:
|
|
7295
|
-
telnyxPublicKey:
|
|
6239
|
+
telephonyProvider,
|
|
6240
|
+
telnyxKey: carrier.kind === "telnyx" ? carrier.apiKey : void 0,
|
|
6241
|
+
telnyxConnectionId: carrier.kind === "telnyx" ? carrier.connectionId : void 0,
|
|
6242
|
+
telnyxPublicKey: carrier.kind === "telnyx" ? carrier.publicKey : void 0
|
|
7296
6243
|
},
|
|
7297
6244
|
opts.agent,
|
|
7298
6245
|
opts.onCallStart,
|
|
@@ -7353,10 +6300,10 @@ var Patter = class {
|
|
|
7353
6300
|
if (!this.localConfig) {
|
|
7354
6301
|
throw new Error("local config missing");
|
|
7355
6302
|
}
|
|
7356
|
-
const { phoneNumber, webhookUrl,
|
|
7357
|
-
if (
|
|
7358
|
-
const telnyxKey =
|
|
7359
|
-
const connectionId =
|
|
6303
|
+
const { phoneNumber, webhookUrl, carrier } = this.localConfig;
|
|
6304
|
+
if (carrier.kind === "telnyx") {
|
|
6305
|
+
const telnyxKey = carrier.apiKey;
|
|
6306
|
+
const connectionId = carrier.connectionId;
|
|
7360
6307
|
const streamUrl = `wss://${webhookUrl}/ws/stream/${encodeURIComponent(localOpts.to)}?caller=${encodeURIComponent(phoneNumber)}&callee=${encodeURIComponent(localOpts.to)}`;
|
|
7361
6308
|
const telnyxPayload = {
|
|
7362
6309
|
connection_id: connectionId,
|
|
@@ -7396,8 +6343,8 @@ var Patter = class {
|
|
|
7396
6343
|
}
|
|
7397
6344
|
return;
|
|
7398
6345
|
}
|
|
7399
|
-
const twilioSid =
|
|
7400
|
-
const twilioToken =
|
|
6346
|
+
const twilioSid = carrier.accountSid;
|
|
6347
|
+
const twilioToken = carrier.authToken;
|
|
7401
6348
|
const statusCallbackUrl = `https://${webhookUrl}/webhooks/twilio/status`;
|
|
7402
6349
|
const url = `https://api.twilio.com/2010-04-01/Accounts/${twilioSid}/Calls.json`;
|
|
7403
6350
|
const params = new URLSearchParams({
|
|
@@ -7529,65 +6476,6 @@ var Patter = class {
|
|
|
7529
6476
|
const data = await response.json();
|
|
7530
6477
|
return data.map((c) => ({ id: c.id, direction: c.direction, caller: c.caller, callee: c.callee, startedAt: c.started_at, endedAt: c.ended_at, durationSeconds: c.duration_seconds, status: c.status, transcript: c.transcript }));
|
|
7531
6478
|
}
|
|
7532
|
-
// Provider helpers — mirror the Python classmethod factories so callers can
|
|
7533
|
-
// write ``Patter.deepgram({ apiKey })`` without importing the top-level.
|
|
7534
|
-
static deepgram = deepgram;
|
|
7535
|
-
static whisper = whisper;
|
|
7536
|
-
static elevenlabs = elevenlabs;
|
|
7537
|
-
static openaiTts = openaiTts;
|
|
7538
|
-
static cartesia = cartesia;
|
|
7539
|
-
static rime = rime;
|
|
7540
|
-
static lmnt = lmnt;
|
|
7541
|
-
static guardrail(opts) {
|
|
7542
|
-
return {
|
|
7543
|
-
name: opts.name,
|
|
7544
|
-
blockedTerms: opts.blockedTerms,
|
|
7545
|
-
check: opts.check,
|
|
7546
|
-
replacement: opts.replacement ?? "I'm sorry, I can't respond to that."
|
|
7547
|
-
};
|
|
7548
|
-
}
|
|
7549
|
-
/**
|
|
7550
|
-
* Create a tool definition for use with `agent({ tools: [...] })`.
|
|
7551
|
-
*
|
|
7552
|
-
* Either `handler` (a function) or `webhookUrl` must be provided.
|
|
7553
|
-
*
|
|
7554
|
-
* @param opts.name - Tool name (visible to the LLM).
|
|
7555
|
-
* @param opts.description - What the tool does (visible to the LLM).
|
|
7556
|
-
* @param opts.parameters - JSON Schema for tool arguments.
|
|
7557
|
-
* @param opts.handler - Async function called in-process when the LLM invokes the tool.
|
|
7558
|
-
* @param opts.webhookUrl - URL to POST to when the LLM invokes the tool.
|
|
7559
|
-
*
|
|
7560
|
-
* @example
|
|
7561
|
-
* ```ts
|
|
7562
|
-
* phone.agent({
|
|
7563
|
-
* systemPrompt: 'You are a pizza bot.',
|
|
7564
|
-
* tools: [
|
|
7565
|
-
* Patter.tool({
|
|
7566
|
-
* name: 'check_menu',
|
|
7567
|
-
* description: 'Check available menu items',
|
|
7568
|
-
* handler: async (args) => JSON.stringify({ items: ['margherita'] }),
|
|
7569
|
-
* }),
|
|
7570
|
-
* ],
|
|
7571
|
-
* });
|
|
7572
|
-
* ```
|
|
7573
|
-
*/
|
|
7574
|
-
static tool(opts) {
|
|
7575
|
-
if (!opts.handler && !opts.webhookUrl) {
|
|
7576
|
-
throw new Error("tool() requires either handler or webhookUrl");
|
|
7577
|
-
}
|
|
7578
|
-
const t = {
|
|
7579
|
-
name: opts.name,
|
|
7580
|
-
description: opts.description ?? "",
|
|
7581
|
-
parameters: opts.parameters ?? { type: "object", properties: {} }
|
|
7582
|
-
};
|
|
7583
|
-
if (opts.handler) {
|
|
7584
|
-
t.handler = opts.handler;
|
|
7585
|
-
}
|
|
7586
|
-
if (opts.webhookUrl) {
|
|
7587
|
-
t.webhookUrl = opts.webhookUrl;
|
|
7588
|
-
}
|
|
7589
|
-
return t;
|
|
7590
|
-
}
|
|
7591
6479
|
// Internal
|
|
7592
6480
|
async registerNumber(provider, providerKey, number, providerSecret, country = "US", stt, tts) {
|
|
7593
6481
|
const credentials = { api_key: providerKey };
|
|
@@ -7682,6 +6570,62 @@ function filterForTTS(text) {
|
|
|
7682
6570
|
return filterEmoji(filterMarkdown(text));
|
|
7683
6571
|
}
|
|
7684
6572
|
|
|
6573
|
+
// src/providers.ts
|
|
6574
|
+
var STTConfigImpl = class {
|
|
6575
|
+
provider;
|
|
6576
|
+
apiKey;
|
|
6577
|
+
language;
|
|
6578
|
+
options;
|
|
6579
|
+
constructor(provider, apiKey, language = "en", options) {
|
|
6580
|
+
this.provider = provider;
|
|
6581
|
+
this.apiKey = apiKey;
|
|
6582
|
+
this.language = language;
|
|
6583
|
+
if (options) this.options = options;
|
|
6584
|
+
}
|
|
6585
|
+
toDict() {
|
|
6586
|
+
const out = {
|
|
6587
|
+
provider: this.provider,
|
|
6588
|
+
api_key: this.apiKey,
|
|
6589
|
+
language: this.language
|
|
6590
|
+
};
|
|
6591
|
+
if (this.options) out.options = { ...this.options };
|
|
6592
|
+
return out;
|
|
6593
|
+
}
|
|
6594
|
+
};
|
|
6595
|
+
var TTSConfigImpl = class {
|
|
6596
|
+
provider;
|
|
6597
|
+
apiKey;
|
|
6598
|
+
voice;
|
|
6599
|
+
constructor(provider, apiKey, voice = "alloy") {
|
|
6600
|
+
this.provider = provider;
|
|
6601
|
+
this.apiKey = apiKey;
|
|
6602
|
+
this.voice = voice;
|
|
6603
|
+
}
|
|
6604
|
+
toDict() {
|
|
6605
|
+
return { provider: this.provider, api_key: this.apiKey, voice: this.voice };
|
|
6606
|
+
}
|
|
6607
|
+
};
|
|
6608
|
+
function deepgram(opts) {
|
|
6609
|
+
const options = {
|
|
6610
|
+
model: opts.model ?? "nova-3",
|
|
6611
|
+
endpointing_ms: opts.endpointingMs ?? 150,
|
|
6612
|
+
utterance_end_ms: opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3,
|
|
6613
|
+
smart_format: opts.smartFormat ?? true,
|
|
6614
|
+
interim_results: opts.interimResults ?? true
|
|
6615
|
+
};
|
|
6616
|
+
if (opts.vadEvents !== void 0) options.vad_events = opts.vadEvents;
|
|
6617
|
+
return new STTConfigImpl("deepgram", opts.apiKey, opts.language ?? "en", options);
|
|
6618
|
+
}
|
|
6619
|
+
function whisper(opts) {
|
|
6620
|
+
return new STTConfigImpl("whisper", opts.apiKey, opts.language ?? "en");
|
|
6621
|
+
}
|
|
6622
|
+
function elevenlabs(opts) {
|
|
6623
|
+
return new TTSConfigImpl("elevenlabs", opts.apiKey, opts.voice ?? "rachel");
|
|
6624
|
+
}
|
|
6625
|
+
function openaiTts(opts) {
|
|
6626
|
+
return new TTSConfigImpl("openai", opts.apiKey, opts.voice ?? "alloy");
|
|
6627
|
+
}
|
|
6628
|
+
|
|
7685
6629
|
// src/index.ts
|
|
7686
6630
|
init_pricing();
|
|
7687
6631
|
init_metrics();
|
|
@@ -8372,73 +7316,387 @@ function scheduleInterval(intervalOrOpts, callback) {
|
|
|
8372
7316
|
};
|
|
8373
7317
|
}
|
|
8374
7318
|
|
|
8375
|
-
// src/
|
|
7319
|
+
// src/stt/deepgram.ts
|
|
8376
7320
|
init_deepgram_stt();
|
|
7321
|
+
var STT = class extends DeepgramSTT {
|
|
7322
|
+
constructor(opts = {}) {
|
|
7323
|
+
const key = opts.apiKey ?? process.env.DEEPGRAM_API_KEY;
|
|
7324
|
+
if (!key) {
|
|
7325
|
+
throw new Error(
|
|
7326
|
+
"Deepgram STT requires an apiKey. Pass { apiKey: 'dg_...' } or set DEEPGRAM_API_KEY in the environment."
|
|
7327
|
+
);
|
|
7328
|
+
}
|
|
7329
|
+
super(
|
|
7330
|
+
key,
|
|
7331
|
+
opts.language ?? "en",
|
|
7332
|
+
opts.model ?? "nova-3",
|
|
7333
|
+
opts.encoding ?? "linear16",
|
|
7334
|
+
opts.sampleRate ?? 16e3,
|
|
7335
|
+
{
|
|
7336
|
+
endpointingMs: opts.endpointingMs ?? 150,
|
|
7337
|
+
utteranceEndMs: opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3,
|
|
7338
|
+
smartFormat: opts.smartFormat ?? true,
|
|
7339
|
+
interimResults: opts.interimResults ?? true,
|
|
7340
|
+
...opts.vadEvents !== void 0 ? { vadEvents: opts.vadEvents } : {}
|
|
7341
|
+
}
|
|
7342
|
+
);
|
|
7343
|
+
}
|
|
7344
|
+
};
|
|
8377
7345
|
|
|
8378
|
-
// src/providers/
|
|
8379
|
-
var import_ws7 = __toESM(require("ws"));
|
|
7346
|
+
// src/providers/whisper-stt.ts
|
|
8380
7347
|
init_logger();
|
|
8381
|
-
var
|
|
8382
|
-
var
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
7348
|
+
var OPENAI_TRANSCRIPTION_URL = "https://api.openai.com/v1/audio/transcriptions";
|
|
7349
|
+
var DEFAULT_BUFFER_SIZE = 16e3 * 2;
|
|
7350
|
+
function wrapPcmInWav(pcm, sampleRate = 16e3, channels = 1, bitsPerSample = 16) {
|
|
7351
|
+
const dataSize = pcm.length;
|
|
7352
|
+
const header = Buffer.alloc(44);
|
|
7353
|
+
header.write("RIFF", 0);
|
|
7354
|
+
header.writeUInt32LE(36 + dataSize, 4);
|
|
7355
|
+
header.write("WAVE", 8);
|
|
7356
|
+
header.write("fmt ", 12);
|
|
7357
|
+
header.writeUInt32LE(16, 16);
|
|
7358
|
+
header.writeUInt16LE(1, 20);
|
|
7359
|
+
header.writeUInt16LE(channels, 22);
|
|
7360
|
+
header.writeUInt32LE(sampleRate, 24);
|
|
7361
|
+
header.writeUInt32LE(sampleRate * channels * (bitsPerSample / 8), 28);
|
|
7362
|
+
header.writeUInt16LE(channels * (bitsPerSample / 8), 32);
|
|
7363
|
+
header.writeUInt16LE(bitsPerSample, 34);
|
|
7364
|
+
header.write("data", 36);
|
|
7365
|
+
header.writeUInt32LE(dataSize, 40);
|
|
7366
|
+
return Buffer.concat([header, pcm]);
|
|
8388
7367
|
}
|
|
8389
|
-
var
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
7368
|
+
var WhisperSTT = class _WhisperSTT {
|
|
7369
|
+
apiKey;
|
|
7370
|
+
model;
|
|
7371
|
+
language;
|
|
7372
|
+
bufferSize;
|
|
7373
|
+
buffer = Buffer.alloc(0);
|
|
7374
|
+
callbacks = [];
|
|
7375
|
+
running = false;
|
|
7376
|
+
pendingTranscriptions = [];
|
|
7377
|
+
constructor(apiKey, model = "whisper-1", language, bufferSize = DEFAULT_BUFFER_SIZE) {
|
|
7378
|
+
this.apiKey = apiKey;
|
|
7379
|
+
this.model = model;
|
|
7380
|
+
this.language = language;
|
|
7381
|
+
this.bufferSize = bufferSize;
|
|
8401
7382
|
}
|
|
8402
|
-
|
|
8403
|
-
|
|
7383
|
+
/** Factory for Twilio calls — mulaw 8 kHz is transcoded upstream, so we still receive PCM 16-bit. */
|
|
7384
|
+
static forTwilio(apiKey, language = "en", model = "whisper-1") {
|
|
7385
|
+
return new _WhisperSTT(apiKey, model, language);
|
|
8404
7386
|
}
|
|
8405
|
-
|
|
8406
|
-
this.
|
|
8407
|
-
this.
|
|
8408
|
-
this.confCount = 0;
|
|
7387
|
+
async connect() {
|
|
7388
|
+
this.running = true;
|
|
7389
|
+
this.buffer = Buffer.alloc(0);
|
|
8409
7390
|
}
|
|
8410
|
-
|
|
8411
|
-
|
|
7391
|
+
sendAudio(audio) {
|
|
7392
|
+
if (!this.running) return;
|
|
7393
|
+
this.buffer = Buffer.concat([this.buffer, audio]);
|
|
7394
|
+
if (this.buffer.length >= this.bufferSize) {
|
|
7395
|
+
const pcm = this.buffer;
|
|
7396
|
+
this.buffer = Buffer.alloc(0);
|
|
7397
|
+
this.trackTranscription(this.transcribeBuffer(pcm));
|
|
7398
|
+
}
|
|
7399
|
+
}
|
|
7400
|
+
trackTranscription(promise) {
|
|
7401
|
+
const wrapped = promise.finally(() => {
|
|
7402
|
+
const idx = this.pendingTranscriptions.indexOf(wrapped);
|
|
7403
|
+
if (idx !== -1) this.pendingTranscriptions.splice(idx, 1);
|
|
7404
|
+
});
|
|
7405
|
+
this.pendingTranscriptions.push(wrapped);
|
|
8412
7406
|
}
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
keepaliveTimer = null;
|
|
8419
|
-
apiKey;
|
|
8420
|
-
model;
|
|
8421
|
-
languageHints;
|
|
8422
|
-
languageHintsStrict;
|
|
8423
|
-
sampleRate;
|
|
8424
|
-
numChannels;
|
|
8425
|
-
enableSpeakerDiarization;
|
|
8426
|
-
enableLanguageIdentification;
|
|
8427
|
-
maxEndpointDelayMs;
|
|
8428
|
-
clientReferenceId;
|
|
8429
|
-
baseUrl;
|
|
8430
|
-
constructor(apiKey, options = {}) {
|
|
8431
|
-
if (!apiKey) {
|
|
8432
|
-
throw new Error("Soniox apiKey is required");
|
|
7407
|
+
onTranscript(callback) {
|
|
7408
|
+
if (this.callbacks.length >= 10) {
|
|
7409
|
+
getLogger().warn("WhisperSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback.");
|
|
7410
|
+
this.callbacks[this.callbacks.length - 1] = callback;
|
|
7411
|
+
return;
|
|
8433
7412
|
}
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
7413
|
+
this.callbacks.push(callback);
|
|
7414
|
+
}
|
|
7415
|
+
async close() {
|
|
7416
|
+
this.running = false;
|
|
7417
|
+
if (this.buffer.length >= this.bufferSize / 4) {
|
|
7418
|
+
const pcm = this.buffer;
|
|
7419
|
+
this.buffer = Buffer.alloc(0);
|
|
7420
|
+
this.trackTranscription(this.transcribeBuffer(pcm));
|
|
7421
|
+
} else {
|
|
7422
|
+
this.buffer = Buffer.alloc(0);
|
|
7423
|
+
}
|
|
7424
|
+
await Promise.allSettled(this.pendingTranscriptions);
|
|
7425
|
+
this.callbacks = [];
|
|
7426
|
+
}
|
|
7427
|
+
// ------------------------------------------------------------------
|
|
7428
|
+
// Private
|
|
7429
|
+
// ------------------------------------------------------------------
|
|
7430
|
+
async transcribeBuffer(pcm) {
|
|
7431
|
+
const wav = wrapPcmInWav(pcm);
|
|
7432
|
+
const formData = new FormData();
|
|
7433
|
+
formData.append("file", new Blob([wav.buffer.slice(wav.byteOffset, wav.byteOffset + wav.byteLength)], { type: "audio/wav" }), "audio.wav");
|
|
7434
|
+
formData.append("model", this.model);
|
|
7435
|
+
if (this.language) {
|
|
7436
|
+
formData.append("language", this.language);
|
|
8437
7437
|
}
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
7438
|
+
try {
|
|
7439
|
+
const resp = await fetch(OPENAI_TRANSCRIPTION_URL, {
|
|
7440
|
+
method: "POST",
|
|
7441
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
7442
|
+
body: formData,
|
|
7443
|
+
signal: AbortSignal.timeout(15e3)
|
|
7444
|
+
});
|
|
7445
|
+
if (!resp.ok) {
|
|
7446
|
+
const body = await resp.text();
|
|
7447
|
+
getLogger().error(`WhisperSTT transcription error: ${resp.status} ${body}`);
|
|
7448
|
+
return;
|
|
7449
|
+
}
|
|
7450
|
+
const json = await resp.json();
|
|
7451
|
+
const text = (json.text ?? "").trim();
|
|
7452
|
+
if (!text) return;
|
|
7453
|
+
const transcript = {
|
|
7454
|
+
text,
|
|
7455
|
+
isFinal: true,
|
|
7456
|
+
confidence: 1
|
|
7457
|
+
};
|
|
7458
|
+
for (const cb of this.callbacks) {
|
|
7459
|
+
cb(transcript);
|
|
7460
|
+
}
|
|
7461
|
+
} catch (err) {
|
|
7462
|
+
getLogger().error(`WhisperSTT transcription error: ${String(err)}`);
|
|
7463
|
+
}
|
|
7464
|
+
}
|
|
7465
|
+
};
|
|
7466
|
+
|
|
7467
|
+
// src/stt/whisper.ts
|
|
7468
|
+
var STT2 = class extends WhisperSTT {
|
|
7469
|
+
constructor(opts = {}) {
|
|
7470
|
+
const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
|
|
7471
|
+
if (!key) {
|
|
7472
|
+
throw new Error(
|
|
7473
|
+
"Whisper STT requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
|
|
7474
|
+
);
|
|
7475
|
+
}
|
|
7476
|
+
super(key, opts.model ?? "whisper-1", opts.language, opts.bufferSize);
|
|
7477
|
+
}
|
|
7478
|
+
};
|
|
7479
|
+
|
|
7480
|
+
// src/providers/cartesia-stt.ts
|
|
7481
|
+
var import_ws7 = __toESM(require("ws"));
|
|
7482
|
+
init_logger();
|
|
7483
|
+
var DEFAULT_BASE_URL = "https://api.cartesia.ai";
|
|
7484
|
+
var API_VERSION = "2025-04-16";
|
|
7485
|
+
var USER_AGENT = "Patter/1.0 (integration=LiveKit-port; provider=Cartesia)";
|
|
7486
|
+
var KEEPALIVE_INTERVAL_MS = 3e4;
|
|
7487
|
+
var CONNECT_TIMEOUT_MS = 1e4;
|
|
7488
|
+
var MAX_CALLBACKS = 10;
|
|
7489
|
+
var CartesiaSTT = class {
|
|
7490
|
+
constructor(apiKey, options = {}) {
|
|
7491
|
+
this.apiKey = apiKey;
|
|
7492
|
+
this.options = options;
|
|
7493
|
+
if (!apiKey) {
|
|
7494
|
+
throw new Error("CartesiaSTT requires a non-empty apiKey");
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
ws = null;
|
|
7498
|
+
callbacks = [];
|
|
7499
|
+
keepaliveTimer = null;
|
|
7500
|
+
/** Cartesia request id — set from the server transcript events. */
|
|
7501
|
+
requestId = "";
|
|
7502
|
+
buildWsUrl() {
|
|
7503
|
+
const opts = this.options;
|
|
7504
|
+
const rawBase = opts.baseUrl ?? DEFAULT_BASE_URL;
|
|
7505
|
+
let base;
|
|
7506
|
+
if (rawBase.startsWith("http://")) {
|
|
7507
|
+
base = `ws://${rawBase.slice("http://".length)}`;
|
|
7508
|
+
} else if (rawBase.startsWith("https://")) {
|
|
7509
|
+
base = `wss://${rawBase.slice("https://".length)}`;
|
|
7510
|
+
} else if (rawBase.startsWith("ws://") || rawBase.startsWith("wss://")) {
|
|
7511
|
+
base = rawBase;
|
|
7512
|
+
} else {
|
|
7513
|
+
base = `wss://${rawBase}`;
|
|
7514
|
+
}
|
|
7515
|
+
const language = opts.language ?? "en";
|
|
7516
|
+
const params = new URLSearchParams({
|
|
7517
|
+
model: opts.model ?? "ink-whisper",
|
|
7518
|
+
sample_rate: String(opts.sampleRate ?? 16e3),
|
|
7519
|
+
encoding: opts.encoding ?? "pcm_s16le",
|
|
7520
|
+
cartesia_version: API_VERSION,
|
|
7521
|
+
api_key: this.apiKey,
|
|
7522
|
+
language
|
|
7523
|
+
});
|
|
7524
|
+
return `${base}/stt/websocket?${params.toString()}`;
|
|
7525
|
+
}
|
|
7526
|
+
async connect() {
|
|
7527
|
+
const url = this.buildWsUrl();
|
|
7528
|
+
this.ws = new import_ws7.default(url, {
|
|
7529
|
+
headers: { "User-Agent": USER_AGENT }
|
|
7530
|
+
});
|
|
7531
|
+
await new Promise((resolve, reject) => {
|
|
7532
|
+
const timer = setTimeout(
|
|
7533
|
+
() => reject(new Error("Cartesia STT connect timeout")),
|
|
7534
|
+
CONNECT_TIMEOUT_MS
|
|
7535
|
+
);
|
|
7536
|
+
this.ws.once("open", () => {
|
|
7537
|
+
clearTimeout(timer);
|
|
7538
|
+
resolve();
|
|
7539
|
+
});
|
|
7540
|
+
this.ws.once("error", (err) => {
|
|
7541
|
+
clearTimeout(timer);
|
|
7542
|
+
reject(err);
|
|
7543
|
+
});
|
|
7544
|
+
});
|
|
7545
|
+
this.ws.on("message", (raw) => {
|
|
7546
|
+
let event;
|
|
7547
|
+
try {
|
|
7548
|
+
event = JSON.parse(raw.toString());
|
|
7549
|
+
} catch {
|
|
7550
|
+
return;
|
|
7551
|
+
}
|
|
7552
|
+
this.handleEvent(event);
|
|
7553
|
+
});
|
|
7554
|
+
this.keepaliveTimer = setInterval(() => {
|
|
7555
|
+
if (this.ws && this.ws.readyState === import_ws7.default.OPEN) {
|
|
7556
|
+
try {
|
|
7557
|
+
this.ws.ping();
|
|
7558
|
+
} catch {
|
|
7559
|
+
}
|
|
7560
|
+
}
|
|
7561
|
+
}, KEEPALIVE_INTERVAL_MS);
|
|
7562
|
+
}
|
|
7563
|
+
handleEvent(event) {
|
|
7564
|
+
const type = event.type;
|
|
7565
|
+
if (type === "transcript") {
|
|
7566
|
+
const text = (event.text ?? "").trim();
|
|
7567
|
+
const isFinal = Boolean(event.is_final);
|
|
7568
|
+
if (!text && !isFinal) return;
|
|
7569
|
+
if (event.request_id) {
|
|
7570
|
+
this.requestId = event.request_id;
|
|
7571
|
+
}
|
|
7572
|
+
if (!text) return;
|
|
7573
|
+
const confidence = Number(event.probability ?? 1);
|
|
7574
|
+
this.emit({ text, isFinal, confidence });
|
|
7575
|
+
return;
|
|
7576
|
+
}
|
|
7577
|
+
if (type === "error") {
|
|
7578
|
+
getLogger().error(`Cartesia STT error: ${event.message ?? "unknown"}`);
|
|
7579
|
+
return;
|
|
7580
|
+
}
|
|
7581
|
+
}
|
|
7582
|
+
emit(transcript) {
|
|
7583
|
+
for (const cb of this.callbacks) {
|
|
7584
|
+
cb(transcript);
|
|
7585
|
+
}
|
|
7586
|
+
}
|
|
7587
|
+
sendAudio(audio) {
|
|
7588
|
+
if (!this.ws || this.ws.readyState !== import_ws7.default.OPEN) return;
|
|
7589
|
+
this.ws.send(audio);
|
|
7590
|
+
}
|
|
7591
|
+
onTranscript(callback) {
|
|
7592
|
+
if (this.callbacks.length >= MAX_CALLBACKS) {
|
|
7593
|
+
getLogger().warn(
|
|
7594
|
+
"CartesiaSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback."
|
|
7595
|
+
);
|
|
7596
|
+
this.callbacks[this.callbacks.length - 1] = callback;
|
|
7597
|
+
return;
|
|
7598
|
+
}
|
|
7599
|
+
this.callbacks.push(callback);
|
|
7600
|
+
}
|
|
7601
|
+
close() {
|
|
7602
|
+
if (this.keepaliveTimer) {
|
|
7603
|
+
clearInterval(this.keepaliveTimer);
|
|
7604
|
+
this.keepaliveTimer = null;
|
|
7605
|
+
}
|
|
7606
|
+
if (this.ws) {
|
|
7607
|
+
try {
|
|
7608
|
+
this.ws.send("finalize");
|
|
7609
|
+
} catch {
|
|
7610
|
+
}
|
|
7611
|
+
this.ws.close();
|
|
7612
|
+
this.ws = null;
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
7615
|
+
};
|
|
7616
|
+
|
|
7617
|
+
// src/stt/cartesia.ts
|
|
7618
|
+
var STT3 = class extends CartesiaSTT {
|
|
7619
|
+
constructor(opts = {}) {
|
|
7620
|
+
const key = opts.apiKey ?? process.env.CARTESIA_API_KEY;
|
|
7621
|
+
if (!key) {
|
|
7622
|
+
throw new Error(
|
|
7623
|
+
"Cartesia STT requires an apiKey. Pass { apiKey: '...' } or set CARTESIA_API_KEY in the environment."
|
|
7624
|
+
);
|
|
7625
|
+
}
|
|
7626
|
+
super(key, {
|
|
7627
|
+
model: opts.model,
|
|
7628
|
+
language: opts.language,
|
|
7629
|
+
encoding: opts.encoding,
|
|
7630
|
+
sampleRate: opts.sampleRate,
|
|
7631
|
+
baseUrl: opts.baseUrl
|
|
7632
|
+
});
|
|
7633
|
+
}
|
|
7634
|
+
};
|
|
7635
|
+
|
|
7636
|
+
// src/providers/soniox-stt.ts
|
|
7637
|
+
var import_ws8 = __toESM(require("ws"));
|
|
7638
|
+
init_logger();
|
|
7639
|
+
var SONIOX_WS_URL = "wss://stt-rt.soniox.com/transcribe-websocket";
|
|
7640
|
+
var KEEPALIVE_MESSAGE = '{"type": "keepalive"}';
|
|
7641
|
+
var END_TOKEN = "<end>";
|
|
7642
|
+
var FINALIZED_TOKEN = "<fin>";
|
|
7643
|
+
var KEEPALIVE_INTERVAL_MS2 = 5e3;
|
|
7644
|
+
function isEndToken(token) {
|
|
7645
|
+
return token.text === END_TOKEN || token.text === FINALIZED_TOKEN;
|
|
7646
|
+
}
|
|
7647
|
+
var TokenAccumulator = class {
|
|
7648
|
+
text = "";
|
|
7649
|
+
confSum = 0;
|
|
7650
|
+
confCount = 0;
|
|
7651
|
+
update(token) {
|
|
7652
|
+
if (token.text) {
|
|
7653
|
+
this.text += token.text;
|
|
7654
|
+
}
|
|
7655
|
+
if (typeof token.confidence === "number") {
|
|
7656
|
+
this.confSum += token.confidence;
|
|
7657
|
+
this.confCount += 1;
|
|
7658
|
+
}
|
|
7659
|
+
}
|
|
7660
|
+
get confidence() {
|
|
7661
|
+
return this.confCount === 0 ? 0 : this.confSum / this.confCount;
|
|
7662
|
+
}
|
|
7663
|
+
reset() {
|
|
7664
|
+
this.text = "";
|
|
7665
|
+
this.confSum = 0;
|
|
7666
|
+
this.confCount = 0;
|
|
7667
|
+
}
|
|
7668
|
+
get raw() {
|
|
7669
|
+
return { sum: this.confSum, count: this.confCount };
|
|
7670
|
+
}
|
|
7671
|
+
};
|
|
7672
|
+
var SonioxSTT = class _SonioxSTT {
|
|
7673
|
+
ws = null;
|
|
7674
|
+
callbacks = [];
|
|
7675
|
+
final = new TokenAccumulator();
|
|
7676
|
+
keepaliveTimer = null;
|
|
7677
|
+
apiKey;
|
|
7678
|
+
model;
|
|
7679
|
+
languageHints;
|
|
7680
|
+
languageHintsStrict;
|
|
7681
|
+
sampleRate;
|
|
7682
|
+
numChannels;
|
|
7683
|
+
enableSpeakerDiarization;
|
|
7684
|
+
enableLanguageIdentification;
|
|
7685
|
+
maxEndpointDelayMs;
|
|
7686
|
+
clientReferenceId;
|
|
7687
|
+
baseUrl;
|
|
7688
|
+
constructor(apiKey, options = {}) {
|
|
7689
|
+
if (!apiKey) {
|
|
7690
|
+
throw new Error("Soniox apiKey is required");
|
|
7691
|
+
}
|
|
7692
|
+
const maxEndpointDelayMs = options.maxEndpointDelayMs ?? 500;
|
|
7693
|
+
if (maxEndpointDelayMs < 500 || maxEndpointDelayMs > 3e3) {
|
|
7694
|
+
throw new Error("maxEndpointDelayMs must be between 500 and 3000");
|
|
7695
|
+
}
|
|
7696
|
+
this.apiKey = apiKey;
|
|
7697
|
+
this.model = options.model ?? "stt-rt-v4";
|
|
7698
|
+
this.languageHints = options.languageHints;
|
|
7699
|
+
this.languageHintsStrict = options.languageHintsStrict ?? false;
|
|
8442
7700
|
this.sampleRate = options.sampleRate ?? 16e3;
|
|
8443
7701
|
this.numChannels = options.numChannels ?? 1;
|
|
8444
7702
|
this.enableSpeakerDiarization = options.enableSpeakerDiarization ?? false;
|
|
@@ -8473,7 +7731,7 @@ var SonioxSTT = class _SonioxSTT {
|
|
|
8473
7731
|
return config;
|
|
8474
7732
|
}
|
|
8475
7733
|
async connect() {
|
|
8476
|
-
this.ws = new
|
|
7734
|
+
this.ws = new import_ws8.default(this.baseUrl);
|
|
8477
7735
|
await new Promise((resolve, reject) => {
|
|
8478
7736
|
const timer = setTimeout(() => reject(new Error("Soniox connect timeout")), 1e4);
|
|
8479
7737
|
this.ws.once("open", () => {
|
|
@@ -8492,13 +7750,13 @@ var SonioxSTT = class _SonioxSTT {
|
|
|
8492
7750
|
getLogger().error(`SonioxSTT WebSocket error: ${String(err)}`);
|
|
8493
7751
|
});
|
|
8494
7752
|
this.keepaliveTimer = setInterval(() => {
|
|
8495
|
-
if (this.ws && this.ws.readyState ===
|
|
7753
|
+
if (this.ws && this.ws.readyState === import_ws8.default.OPEN) {
|
|
8496
7754
|
try {
|
|
8497
7755
|
this.ws.send(KEEPALIVE_MESSAGE);
|
|
8498
7756
|
} catch {
|
|
8499
7757
|
}
|
|
8500
7758
|
}
|
|
8501
|
-
},
|
|
7759
|
+
}, KEEPALIVE_INTERVAL_MS2);
|
|
8502
7760
|
}
|
|
8503
7761
|
clearKeepalive() {
|
|
8504
7762
|
if (this.keepaliveTimer) {
|
|
@@ -8565,7 +7823,7 @@ var SonioxSTT = class _SonioxSTT {
|
|
|
8565
7823
|
}
|
|
8566
7824
|
}
|
|
8567
7825
|
sendAudio(audio) {
|
|
8568
|
-
if (!this.ws || this.ws.readyState !==
|
|
7826
|
+
if (!this.ws || this.ws.readyState !== import_ws8.default.OPEN) return;
|
|
8569
7827
|
if (audio.length === 0) return;
|
|
8570
7828
|
this.ws.send(audio);
|
|
8571
7829
|
}
|
|
@@ -8595,16 +7853,28 @@ var SonioxSTT = class _SonioxSTT {
|
|
|
8595
7853
|
}
|
|
8596
7854
|
};
|
|
8597
7855
|
|
|
8598
|
-
// src/
|
|
8599
|
-
|
|
7856
|
+
// src/stt/soniox.ts
|
|
7857
|
+
var STT4 = class extends SonioxSTT {
|
|
7858
|
+
constructor(opts = {}) {
|
|
7859
|
+
const key = opts.apiKey ?? process.env.SONIOX_API_KEY;
|
|
7860
|
+
if (!key) {
|
|
7861
|
+
throw new Error(
|
|
7862
|
+
"Soniox STT requires an apiKey. Pass { apiKey: '...' } or set SONIOX_API_KEY in the environment."
|
|
7863
|
+
);
|
|
7864
|
+
}
|
|
7865
|
+
const { apiKey: _ignored, ...rest } = opts;
|
|
7866
|
+
void _ignored;
|
|
7867
|
+
super(key, rest);
|
|
7868
|
+
}
|
|
7869
|
+
};
|
|
8600
7870
|
|
|
8601
7871
|
// src/providers/assemblyai-stt.ts
|
|
8602
|
-
var
|
|
7872
|
+
var import_ws9 = __toESM(require("ws"));
|
|
8603
7873
|
init_logger();
|
|
8604
|
-
var
|
|
7874
|
+
var DEFAULT_BASE_URL2 = "wss://streaming.assemblyai.com";
|
|
8605
7875
|
var DEFAULT_MIN_TURN_SILENCE_MS = 100;
|
|
8606
|
-
var
|
|
8607
|
-
var
|
|
7876
|
+
var CONNECT_TIMEOUT_MS2 = 1e4;
|
|
7877
|
+
var MAX_CALLBACKS2 = 10;
|
|
8608
7878
|
var AssemblyAISTT = class _AssemblyAISTT {
|
|
8609
7879
|
constructor(apiKey, options = {}) {
|
|
8610
7880
|
this.apiKey = apiKey;
|
|
@@ -8667,12 +7937,12 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
8667
7937
|
params.set(key, String(value));
|
|
8668
7938
|
}
|
|
8669
7939
|
}
|
|
8670
|
-
const base = opts.baseUrl ??
|
|
7940
|
+
const base = opts.baseUrl ?? DEFAULT_BASE_URL2;
|
|
8671
7941
|
return `${base}/v3/ws?${params.toString()}`;
|
|
8672
7942
|
}
|
|
8673
7943
|
async connect() {
|
|
8674
7944
|
const url = this.buildUrl();
|
|
8675
|
-
this.ws = new
|
|
7945
|
+
this.ws = new import_ws9.default(url, {
|
|
8676
7946
|
headers: {
|
|
8677
7947
|
Authorization: this.apiKey,
|
|
8678
7948
|
"Content-Type": "application/json",
|
|
@@ -8682,7 +7952,7 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
8682
7952
|
await new Promise((resolve, reject) => {
|
|
8683
7953
|
const timer = setTimeout(
|
|
8684
7954
|
() => reject(new Error("AssemblyAI connect timeout")),
|
|
8685
|
-
|
|
7955
|
+
CONNECT_TIMEOUT_MS2
|
|
8686
7956
|
);
|
|
8687
7957
|
this.ws.once("open", () => {
|
|
8688
7958
|
clearTimeout(timer);
|
|
@@ -8742,11 +8012,11 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
8742
8012
|
}
|
|
8743
8013
|
}
|
|
8744
8014
|
sendAudio(audio) {
|
|
8745
|
-
if (!this.ws || this.ws.readyState !==
|
|
8015
|
+
if (!this.ws || this.ws.readyState !== import_ws9.default.OPEN) return;
|
|
8746
8016
|
this.ws.send(audio);
|
|
8747
8017
|
}
|
|
8748
8018
|
onTranscript(callback) {
|
|
8749
|
-
if (this.callbacks.length >=
|
|
8019
|
+
if (this.callbacks.length >= MAX_CALLBACKS2) {
|
|
8750
8020
|
getLogger().warn(
|
|
8751
8021
|
"AssemblyAISTT: maximum of 10 onTranscript callbacks reached; replacing the last callback."
|
|
8752
8022
|
);
|
|
@@ -8775,146 +8045,296 @@ function averageConfidence(words) {
|
|
|
8775
8045
|
return total / words.length;
|
|
8776
8046
|
}
|
|
8777
8047
|
|
|
8778
|
-
// src/
|
|
8779
|
-
var
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
var MAX_CALLBACKS2 = 10;
|
|
8787
|
-
var CartesiaSTT = class {
|
|
8788
|
-
constructor(apiKey, options = {}) {
|
|
8789
|
-
this.apiKey = apiKey;
|
|
8790
|
-
this.options = options;
|
|
8791
|
-
if (!apiKey) {
|
|
8792
|
-
throw new Error("CartesiaSTT requires a non-empty apiKey");
|
|
8048
|
+
// src/stt/assemblyai.ts
|
|
8049
|
+
var STT5 = class extends AssemblyAISTT {
|
|
8050
|
+
constructor(opts = {}) {
|
|
8051
|
+
const key = opts.apiKey ?? process.env.ASSEMBLYAI_API_KEY;
|
|
8052
|
+
if (!key) {
|
|
8053
|
+
throw new Error(
|
|
8054
|
+
"AssemblyAI STT requires an apiKey. Pass { apiKey: '...' } or set ASSEMBLYAI_API_KEY in the environment."
|
|
8055
|
+
);
|
|
8793
8056
|
}
|
|
8057
|
+
const { apiKey: _ignored, ...rest } = opts;
|
|
8058
|
+
void _ignored;
|
|
8059
|
+
super(key, rest);
|
|
8794
8060
|
}
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8061
|
+
};
|
|
8062
|
+
|
|
8063
|
+
// src/providers/elevenlabs-tts.ts
|
|
8064
|
+
var ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
|
|
8065
|
+
var ELEVENLABS_VOICE_ID_BY_NAME = {
|
|
8066
|
+
rachel: "21m00Tcm4TlvDq8ikWAM",
|
|
8067
|
+
drew: "29vD33N1CtxCmqQRPOHJ",
|
|
8068
|
+
clyde: "2EiwWnXFnvU5JabPnv8n",
|
|
8069
|
+
paul: "5Q0t7uMcjvnagumLfvZi",
|
|
8070
|
+
domi: "AZnzlk1XvdvUeBnXmlld",
|
|
8071
|
+
dave: "CYw3kZ02Hs0563khs1Fj",
|
|
8072
|
+
fin: "D38z5RcWu1voky8WS1ja",
|
|
8073
|
+
bella: "EXAVITQu4vr4xnSDxMaL",
|
|
8074
|
+
antoni: "ErXwobaYiN019PkySvjV",
|
|
8075
|
+
thomas: "GBv7mTt0atIp3Br8iCZE",
|
|
8076
|
+
charlie: "IKne3meq5aSn9XLyUdCD",
|
|
8077
|
+
george: "JBFqnCBsd6RMkjVDRZzb",
|
|
8078
|
+
emily: "LcfcDJNUP1GQjkzn1xUU",
|
|
8079
|
+
elli: "MF3mGyEYCl7XYWbV9V6O",
|
|
8080
|
+
callum: "N2lVS1w4EtoT3dr4eOWO",
|
|
8081
|
+
patrick: "ODq5zmih8GrVes37Dizd",
|
|
8082
|
+
harry: "SOYHLrjzK2X1ezoPC6cr",
|
|
8083
|
+
liam: "TX3LPaxmHKxFdv7VOQHJ",
|
|
8084
|
+
dorothy: "ThT5KcBeYPX3keUQqHPh",
|
|
8085
|
+
josh: "TxGEqnHWrfWFTfGW9XjX",
|
|
8086
|
+
arnold: "VR6AewLTigWG4xSOukaG",
|
|
8087
|
+
charlotte: "XB0fDUnXU5powFXDhCwa",
|
|
8088
|
+
matilda: "XrExE9yKIg1WjnnlVkGX",
|
|
8089
|
+
matthew: "Yko7PKHZNXotIFUBG7I9",
|
|
8090
|
+
james: "ZQe5CZNOzWyzPSCn5a3c",
|
|
8091
|
+
joseph: "Zlb1dXrM653N07WRdFW3",
|
|
8092
|
+
jeremy: "bVMeCyTHy58xNoL34h3p",
|
|
8093
|
+
michael: "flq6f7yk4E4fJM5XTYuZ",
|
|
8094
|
+
ethan: "g5CIjZEefAph4nQFvHAz",
|
|
8095
|
+
gigi: "jBpfuIE2acCO8z3wKNLl",
|
|
8096
|
+
freya: "jsCqWAovK2LkecY7zXl4",
|
|
8097
|
+
brian: "nPczCjzI2devNBz1zQrb",
|
|
8098
|
+
grace: "oWAxZDx7w5VEj9dCyTzz",
|
|
8099
|
+
daniel: "onwK4e9ZLuTAKqWW03F9",
|
|
8100
|
+
lily: "pFZP5JQG7iQjIQuC4Bku",
|
|
8101
|
+
serena: "pMsXgVXv3BLzUgSXRplE",
|
|
8102
|
+
adam: "pNInz6obpgDQGcFmaJgB",
|
|
8103
|
+
nicole: "piTKgcLEGmPE4e6mEKli",
|
|
8104
|
+
bill: "pqHfZKP75CvOlQylNhV4",
|
|
8105
|
+
jessie: "t0jbNlBVZ17f02VDIeMI",
|
|
8106
|
+
ryan: "wViXBPUzp2ZZixB1xQuM",
|
|
8107
|
+
sam: "yoZ06aMxZJJ28mfd3POQ",
|
|
8108
|
+
glinda: "z9fAnlkpzviPz146aGWa",
|
|
8109
|
+
giovanni: "zcAOhNBS3c14rBihAFp1",
|
|
8110
|
+
mimi: "zrHiDhphv9ZnVXBqCLjz",
|
|
8111
|
+
alloy: "21m00Tcm4TlvDq8ikWAM"
|
|
8112
|
+
};
|
|
8113
|
+
var VOICE_ID_PATTERN = /^[A-Za-z0-9]{20}$/;
|
|
8114
|
+
function resolveVoiceId(voice) {
|
|
8115
|
+
if (!voice) return voice;
|
|
8116
|
+
if (VOICE_ID_PATTERN.test(voice)) return voice;
|
|
8117
|
+
return ELEVENLABS_VOICE_ID_BY_NAME[voice.toLowerCase()] ?? voice;
|
|
8118
|
+
}
|
|
8119
|
+
var ElevenLabsTTS = class {
|
|
8120
|
+
constructor(apiKey, voiceId = "21m00Tcm4TlvDq8ikWAM", modelId = "eleven_turbo_v2_5", outputFormat = "pcm_16000") {
|
|
8121
|
+
this.apiKey = apiKey;
|
|
8122
|
+
this.modelId = modelId;
|
|
8123
|
+
this.outputFormat = outputFormat;
|
|
8124
|
+
this.voiceId = resolveVoiceId(voiceId);
|
|
8125
|
+
}
|
|
8126
|
+
voiceId;
|
|
8127
|
+
/**
|
|
8128
|
+
* Synthesise text to speech and return the full audio as a single Buffer.
|
|
8129
|
+
*
|
|
8130
|
+
* For large chunks (or when latency matters) call `synthesizeStream` instead.
|
|
8131
|
+
*/
|
|
8132
|
+
async synthesize(text) {
|
|
8133
|
+
const chunks = [];
|
|
8134
|
+
for await (const chunk of this.synthesizeStream(text)) {
|
|
8135
|
+
chunks.push(chunk);
|
|
8812
8136
|
}
|
|
8813
|
-
|
|
8814
|
-
const params = new URLSearchParams({
|
|
8815
|
-
model: opts.model ?? "ink-whisper",
|
|
8816
|
-
sample_rate: String(opts.sampleRate ?? 16e3),
|
|
8817
|
-
encoding: opts.encoding ?? "pcm_s16le",
|
|
8818
|
-
cartesia_version: API_VERSION,
|
|
8819
|
-
api_key: this.apiKey,
|
|
8820
|
-
language
|
|
8821
|
-
});
|
|
8822
|
-
return `${base}/stt/websocket?${params.toString()}`;
|
|
8137
|
+
return Buffer.concat(chunks);
|
|
8823
8138
|
}
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
}
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
reject(err);
|
|
8841
|
-
});
|
|
8842
|
-
});
|
|
8843
|
-
this.ws.on("message", (raw) => {
|
|
8844
|
-
let event;
|
|
8845
|
-
try {
|
|
8846
|
-
event = JSON.parse(raw.toString());
|
|
8847
|
-
} catch {
|
|
8848
|
-
return;
|
|
8849
|
-
}
|
|
8850
|
-
this.handleEvent(event);
|
|
8139
|
+
/**
|
|
8140
|
+
* Synthesise text and yield audio chunks as they arrive (streaming).
|
|
8141
|
+
*
|
|
8142
|
+
* The yielded buffers are raw PCM at 16 kHz (or whatever `outputFormat` is
|
|
8143
|
+
* configured to).
|
|
8144
|
+
*/
|
|
8145
|
+
async *synthesizeStream(text) {
|
|
8146
|
+
const url = `${ELEVENLABS_BASE_URL}/text-to-speech/${encodeURIComponent(this.voiceId)}/stream?output_format=${encodeURIComponent(this.outputFormat)}`;
|
|
8147
|
+
const response = await fetch(url, {
|
|
8148
|
+
method: "POST",
|
|
8149
|
+
headers: {
|
|
8150
|
+
"xi-api-key": this.apiKey,
|
|
8151
|
+
"Content-Type": "application/json"
|
|
8152
|
+
},
|
|
8153
|
+
body: JSON.stringify({ text, model_id: this.modelId }),
|
|
8154
|
+
signal: AbortSignal.timeout(3e4)
|
|
8851
8155
|
});
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8156
|
+
if (!response.ok) {
|
|
8157
|
+
const body = await response.text();
|
|
8158
|
+
throw new Error(`ElevenLabs TTS error ${response.status}: ${body}`);
|
|
8159
|
+
}
|
|
8160
|
+
if (!response.body) {
|
|
8161
|
+
throw new Error("ElevenLabs TTS: no response body");
|
|
8162
|
+
}
|
|
8163
|
+
const reader = response.body.getReader();
|
|
8164
|
+
try {
|
|
8165
|
+
while (true) {
|
|
8166
|
+
const { done, value } = await reader.read();
|
|
8167
|
+
if (done) break;
|
|
8168
|
+
if (value && value.length > 0) {
|
|
8169
|
+
yield Buffer.from(value);
|
|
8857
8170
|
}
|
|
8858
8171
|
}
|
|
8859
|
-
}
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
if (type === "transcript") {
|
|
8864
|
-
const text = (event.text ?? "").trim();
|
|
8865
|
-
const isFinal = Boolean(event.is_final);
|
|
8866
|
-
if (!text && !isFinal) return;
|
|
8867
|
-
if (event.request_id) {
|
|
8868
|
-
this.requestId = event.request_id;
|
|
8869
|
-
}
|
|
8870
|
-
if (!text) return;
|
|
8871
|
-
const confidence = Number(event.probability ?? 1);
|
|
8872
|
-
this.emit({ text, isFinal, confidence });
|
|
8873
|
-
return;
|
|
8874
|
-
}
|
|
8875
|
-
if (type === "error") {
|
|
8876
|
-
getLogger().error(`Cartesia STT error: ${event.message ?? "unknown"}`);
|
|
8877
|
-
return;
|
|
8172
|
+
} finally {
|
|
8173
|
+
if (typeof reader.cancel === "function") await reader.cancel().catch(() => {
|
|
8174
|
+
});
|
|
8175
|
+
reader.releaseLock();
|
|
8878
8176
|
}
|
|
8879
8177
|
}
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8178
|
+
};
|
|
8179
|
+
|
|
8180
|
+
// src/tts/elevenlabs.ts
|
|
8181
|
+
var TTS = class extends ElevenLabsTTS {
|
|
8182
|
+
constructor(opts = {}) {
|
|
8183
|
+
const key = opts.apiKey ?? process.env.ELEVENLABS_API_KEY;
|
|
8184
|
+
if (!key) {
|
|
8185
|
+
throw new Error(
|
|
8186
|
+
"ElevenLabs TTS requires an apiKey. Pass { apiKey: '...' } or set ELEVENLABS_API_KEY in the environment."
|
|
8187
|
+
);
|
|
8883
8188
|
}
|
|
8189
|
+
super(
|
|
8190
|
+
key,
|
|
8191
|
+
opts.voiceId ?? "21m00Tcm4TlvDq8ikWAM",
|
|
8192
|
+
opts.modelId ?? "eleven_turbo_v2_5",
|
|
8193
|
+
opts.outputFormat ?? "pcm_16000"
|
|
8194
|
+
);
|
|
8884
8195
|
}
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8196
|
+
};
|
|
8197
|
+
|
|
8198
|
+
// src/providers/openai-tts.ts
|
|
8199
|
+
var OPENAI_TTS_URL = "https://api.openai.com/v1/audio/speech";
|
|
8200
|
+
var OpenAITTS = class _OpenAITTS {
|
|
8201
|
+
constructor(apiKey, voice = "alloy", model = "tts-1") {
|
|
8202
|
+
this.apiKey = apiKey;
|
|
8203
|
+
this.voice = voice;
|
|
8204
|
+
this.model = model;
|
|
8888
8205
|
}
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8206
|
+
/**
|
|
8207
|
+
* Synthesise text to speech and return the full audio as a single Buffer.
|
|
8208
|
+
*
|
|
8209
|
+
* For large chunks (or when latency matters) call `synthesizeStream` instead.
|
|
8210
|
+
*/
|
|
8211
|
+
async synthesize(text) {
|
|
8212
|
+
const chunks = [];
|
|
8213
|
+
for await (const chunk of this.synthesizeStream(text)) {
|
|
8214
|
+
chunks.push(chunk);
|
|
8896
8215
|
}
|
|
8897
|
-
|
|
8216
|
+
return Buffer.concat(chunks);
|
|
8898
8217
|
}
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8218
|
+
/**
|
|
8219
|
+
* Synthesise text and yield audio chunks as they arrive (streaming).
|
|
8220
|
+
*
|
|
8221
|
+
* OpenAI returns 24 kHz PCM16; each chunk is resampled to 16 kHz before
|
|
8222
|
+
* yielding so the output is ready for telephony pipelines.
|
|
8223
|
+
*
|
|
8224
|
+
* The resampler carries state (buffered samples + odd trailing byte)
|
|
8225
|
+
* between chunks — without that state cross-chunk sample alignment drifts
|
|
8226
|
+
* and the caller hears pops / dropped audio (BUG #23, mirror of the
|
|
8227
|
+
* Python `audioop.ratecv` fix).
|
|
8228
|
+
*/
|
|
8229
|
+
async *synthesizeStream(text) {
|
|
8230
|
+
const response = await fetch(OPENAI_TTS_URL, {
|
|
8231
|
+
method: "POST",
|
|
8232
|
+
headers: {
|
|
8233
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
8234
|
+
"Content-Type": "application/json"
|
|
8235
|
+
},
|
|
8236
|
+
body: JSON.stringify({
|
|
8237
|
+
model: this.model,
|
|
8238
|
+
input: text,
|
|
8239
|
+
voice: this.voice,
|
|
8240
|
+
response_format: "pcm"
|
|
8241
|
+
}),
|
|
8242
|
+
signal: AbortSignal.timeout(3e4)
|
|
8243
|
+
});
|
|
8244
|
+
if (!response.ok) {
|
|
8245
|
+
const body = await response.text();
|
|
8246
|
+
throw new Error(`OpenAI TTS error ${response.status}: ${body}`);
|
|
8903
8247
|
}
|
|
8904
|
-
if (
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8248
|
+
if (!response.body) {
|
|
8249
|
+
throw new Error("OpenAI TTS: no response body");
|
|
8250
|
+
}
|
|
8251
|
+
const ctx = { carryByte: null, leftover: [] };
|
|
8252
|
+
const reader = response.body.getReader();
|
|
8253
|
+
try {
|
|
8254
|
+
while (true) {
|
|
8255
|
+
const { done, value } = await reader.read();
|
|
8256
|
+
if (done) break;
|
|
8257
|
+
if (value && value.length > 0) {
|
|
8258
|
+
const out = _OpenAITTS.resampleStreaming(Buffer.from(value), ctx);
|
|
8259
|
+
if (out.length > 0) yield out;
|
|
8260
|
+
}
|
|
8908
8261
|
}
|
|
8909
|
-
|
|
8910
|
-
|
|
8262
|
+
if (ctx.leftover.length > 0) {
|
|
8263
|
+
const tail = Buffer.alloc(ctx.leftover.length * 2);
|
|
8264
|
+
for (let i = 0; i < ctx.leftover.length; i++) {
|
|
8265
|
+
tail.writeInt16LE(ctx.leftover[i], i * 2);
|
|
8266
|
+
}
|
|
8267
|
+
yield tail;
|
|
8268
|
+
}
|
|
8269
|
+
} finally {
|
|
8270
|
+
if (typeof reader.cancel === "function") await reader.cancel().catch(() => {
|
|
8271
|
+
});
|
|
8272
|
+
reader.releaseLock();
|
|
8273
|
+
}
|
|
8274
|
+
}
|
|
8275
|
+
/**
|
|
8276
|
+
* Streaming 24 kHz → 16 kHz resampler (PCM16-LE). Maintains cross-chunk
|
|
8277
|
+
* state so the 3:2 pattern doesn't reset at every network read.
|
|
8278
|
+
*/
|
|
8279
|
+
static resampleStreaming(audio, ctx) {
|
|
8280
|
+
let buf;
|
|
8281
|
+
if (ctx.carryByte !== null) {
|
|
8282
|
+
buf = Buffer.concat([Buffer.from([ctx.carryByte]), audio]);
|
|
8283
|
+
ctx.carryByte = null;
|
|
8284
|
+
} else {
|
|
8285
|
+
buf = audio;
|
|
8286
|
+
}
|
|
8287
|
+
if (buf.length % 2 === 1) {
|
|
8288
|
+
ctx.carryByte = buf[buf.length - 1];
|
|
8289
|
+
buf = buf.subarray(0, buf.length - 1);
|
|
8290
|
+
}
|
|
8291
|
+
if (buf.length === 0 && ctx.leftover.length === 0) {
|
|
8292
|
+
return Buffer.alloc(0);
|
|
8911
8293
|
}
|
|
8294
|
+
const sampleCount = buf.length / 2;
|
|
8295
|
+
const samples = ctx.leftover.slice();
|
|
8296
|
+
for (let i2 = 0; i2 < sampleCount; i2++) {
|
|
8297
|
+
samples.push(buf.readInt16LE(i2 * 2));
|
|
8298
|
+
}
|
|
8299
|
+
const out = [];
|
|
8300
|
+
let i = 0;
|
|
8301
|
+
while (i + 2 < samples.length) {
|
|
8302
|
+
out.push(samples[i]);
|
|
8303
|
+
out.push(Math.trunc((samples[i + 1] + samples[i + 2]) / 2));
|
|
8304
|
+
i += 3;
|
|
8305
|
+
}
|
|
8306
|
+
ctx.leftover = samples.slice(i);
|
|
8307
|
+
const buffer = Buffer.alloc(out.length * 2);
|
|
8308
|
+
for (let j = 0; j < out.length; j++) {
|
|
8309
|
+
buffer.writeInt16LE(out[j], j * 2);
|
|
8310
|
+
}
|
|
8311
|
+
return buffer;
|
|
8312
|
+
}
|
|
8313
|
+
/** @deprecated use {@link resampleStreaming} with persistent state. */
|
|
8314
|
+
static resample24kTo16k(audio) {
|
|
8315
|
+
const ctx = { carryByte: null, leftover: [] };
|
|
8316
|
+
const out = _OpenAITTS.resampleStreaming(audio, ctx);
|
|
8317
|
+
if (ctx.leftover.length === 0) return out;
|
|
8318
|
+
const tail = Buffer.alloc(ctx.leftover.length * 2);
|
|
8319
|
+
for (let i = 0; i < ctx.leftover.length; i++) {
|
|
8320
|
+
tail.writeInt16LE(ctx.leftover[i], i * 2);
|
|
8321
|
+
}
|
|
8322
|
+
return Buffer.concat([out, tail]);
|
|
8912
8323
|
}
|
|
8913
8324
|
};
|
|
8914
8325
|
|
|
8915
|
-
// src/
|
|
8916
|
-
|
|
8917
|
-
|
|
8326
|
+
// src/tts/openai.ts
|
|
8327
|
+
var TTS2 = class extends OpenAITTS {
|
|
8328
|
+
constructor(opts = {}) {
|
|
8329
|
+
const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
|
|
8330
|
+
if (!key) {
|
|
8331
|
+
throw new Error(
|
|
8332
|
+
"OpenAI TTS requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
|
|
8333
|
+
);
|
|
8334
|
+
}
|
|
8335
|
+
super(key, opts.voice ?? "alloy", opts.model ?? "tts-1");
|
|
8336
|
+
}
|
|
8337
|
+
};
|
|
8918
8338
|
|
|
8919
8339
|
// src/providers/cartesia-tts.ts
|
|
8920
8340
|
var CARTESIA_BASE_URL = "https://api.cartesia.ai";
|
|
@@ -9014,6 +8434,21 @@ var CartesiaTTS = class {
|
|
|
9014
8434
|
}
|
|
9015
8435
|
};
|
|
9016
8436
|
|
|
8437
|
+
// src/tts/cartesia.ts
|
|
8438
|
+
var TTS3 = class extends CartesiaTTS {
|
|
8439
|
+
constructor(opts = {}) {
|
|
8440
|
+
const key = opts.apiKey ?? process.env.CARTESIA_API_KEY;
|
|
8441
|
+
if (!key) {
|
|
8442
|
+
throw new Error(
|
|
8443
|
+
"Cartesia TTS requires an apiKey. Pass { apiKey: '...' } or set CARTESIA_API_KEY in the environment."
|
|
8444
|
+
);
|
|
8445
|
+
}
|
|
8446
|
+
const { apiKey: _ignored, ...rest } = opts;
|
|
8447
|
+
void _ignored;
|
|
8448
|
+
super(key, rest);
|
|
8449
|
+
}
|
|
8450
|
+
};
|
|
8451
|
+
|
|
9017
8452
|
// src/providers/rime-tts.ts
|
|
9018
8453
|
var RIME_BASE_URL = "https://users.rime.ai/v1/rime-tts";
|
|
9019
8454
|
var ARCANA_MODEL_TIMEOUT_MS = 60 * 4 * 1e3;
|
|
@@ -9141,6 +8576,21 @@ var RimeTTS = class {
|
|
|
9141
8576
|
}
|
|
9142
8577
|
};
|
|
9143
8578
|
|
|
8579
|
+
// src/tts/rime.ts
|
|
8580
|
+
var TTS4 = class extends RimeTTS {
|
|
8581
|
+
constructor(opts = {}) {
|
|
8582
|
+
const key = opts.apiKey ?? process.env.RIME_API_KEY;
|
|
8583
|
+
if (!key) {
|
|
8584
|
+
throw new Error(
|
|
8585
|
+
"Rime TTS requires an apiKey. Pass { apiKey: '...' } or set RIME_API_KEY in the environment."
|
|
8586
|
+
);
|
|
8587
|
+
}
|
|
8588
|
+
const { apiKey: _ignored, ...rest } = opts;
|
|
8589
|
+
void _ignored;
|
|
8590
|
+
super(key, rest);
|
|
8591
|
+
}
|
|
8592
|
+
};
|
|
8593
|
+
|
|
9144
8594
|
// src/providers/lmnt-tts.ts
|
|
9145
8595
|
var LMNT_BASE_URL = "https://api.lmnt.com/v1/ai/speech/bytes";
|
|
9146
8596
|
var LMNTTTS = class {
|
|
@@ -9219,6 +8669,734 @@ var LMNTTTS = class {
|
|
|
9219
8669
|
}
|
|
9220
8670
|
};
|
|
9221
8671
|
|
|
8672
|
+
// src/tts/lmnt.ts
|
|
8673
|
+
var TTS5 = class extends LMNTTTS {
|
|
8674
|
+
constructor(opts = {}) {
|
|
8675
|
+
const key = opts.apiKey ?? process.env.LMNT_API_KEY;
|
|
8676
|
+
if (!key) {
|
|
8677
|
+
throw new Error(
|
|
8678
|
+
"LMNT TTS requires an apiKey. Pass { apiKey: '...' } or set LMNT_API_KEY in the environment."
|
|
8679
|
+
);
|
|
8680
|
+
}
|
|
8681
|
+
const { apiKey: _ignored, ...rest } = opts;
|
|
8682
|
+
void _ignored;
|
|
8683
|
+
super(key, rest);
|
|
8684
|
+
}
|
|
8685
|
+
};
|
|
8686
|
+
|
|
8687
|
+
// src/llm/openai.ts
|
|
8688
|
+
init_llm_loop();
|
|
8689
|
+
var LLM = class extends OpenAILLMProvider {
|
|
8690
|
+
constructor(opts = {}) {
|
|
8691
|
+
const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
|
|
8692
|
+
if (!key) {
|
|
8693
|
+
throw new Error(
|
|
8694
|
+
"OpenAI LLM requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY."
|
|
8695
|
+
);
|
|
8696
|
+
}
|
|
8697
|
+
super(key, opts.model ?? "gpt-4o-mini");
|
|
8698
|
+
}
|
|
8699
|
+
};
|
|
8700
|
+
|
|
8701
|
+
// src/providers/anthropic-llm.ts
|
|
8702
|
+
init_logger();
|
|
8703
|
+
var DEFAULT_ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
|
|
8704
|
+
var DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
8705
|
+
var DEFAULT_MODEL = "claude-3-5-sonnet-20241022";
|
|
8706
|
+
var DEFAULT_MAX_TOKENS = 1024;
|
|
8707
|
+
var AnthropicLLMProvider = class {
|
|
8708
|
+
apiKey;
|
|
8709
|
+
model;
|
|
8710
|
+
maxTokens;
|
|
8711
|
+
temperature;
|
|
8712
|
+
url;
|
|
8713
|
+
anthropicVersion;
|
|
8714
|
+
constructor(options) {
|
|
8715
|
+
if (!options.apiKey) {
|
|
8716
|
+
throw new Error(
|
|
8717
|
+
"Anthropic API key is required. Pass it via { apiKey } or set the ANTHROPIC_API_KEY environment variable before constructing the provider."
|
|
8718
|
+
);
|
|
8719
|
+
}
|
|
8720
|
+
this.apiKey = options.apiKey;
|
|
8721
|
+
this.model = options.model ?? DEFAULT_MODEL;
|
|
8722
|
+
this.maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
8723
|
+
this.temperature = options.temperature;
|
|
8724
|
+
this.url = options.baseUrl ?? DEFAULT_ANTHROPIC_URL;
|
|
8725
|
+
this.anthropicVersion = options.anthropicVersion ?? DEFAULT_ANTHROPIC_VERSION;
|
|
8726
|
+
}
|
|
8727
|
+
async *stream(messages, tools) {
|
|
8728
|
+
const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
|
|
8729
|
+
const anthropicTools = tools ? toAnthropicTools(tools) : null;
|
|
8730
|
+
const body = {
|
|
8731
|
+
model: this.model,
|
|
8732
|
+
messages: anthropicMessages,
|
|
8733
|
+
max_tokens: this.maxTokens,
|
|
8734
|
+
stream: true
|
|
8735
|
+
};
|
|
8736
|
+
if (system) body.system = system;
|
|
8737
|
+
if (anthropicTools && anthropicTools.length > 0) body.tools = anthropicTools;
|
|
8738
|
+
if (this.temperature !== void 0) body.temperature = this.temperature;
|
|
8739
|
+
const response = await fetch(this.url, {
|
|
8740
|
+
method: "POST",
|
|
8741
|
+
headers: {
|
|
8742
|
+
"Content-Type": "application/json",
|
|
8743
|
+
"x-api-key": this.apiKey,
|
|
8744
|
+
"anthropic-version": this.anthropicVersion
|
|
8745
|
+
},
|
|
8746
|
+
body: JSON.stringify(body),
|
|
8747
|
+
signal: AbortSignal.timeout(3e4)
|
|
8748
|
+
});
|
|
8749
|
+
if (!response.ok) {
|
|
8750
|
+
const errText = await response.text();
|
|
8751
|
+
getLogger().error(`Anthropic API error: ${response.status} ${errText}`);
|
|
8752
|
+
return;
|
|
8753
|
+
}
|
|
8754
|
+
const reader = response.body?.getReader();
|
|
8755
|
+
if (!reader) return;
|
|
8756
|
+
const decoder = new TextDecoder();
|
|
8757
|
+
let buffer = "";
|
|
8758
|
+
const toolIndexByBlock = /* @__PURE__ */ new Map();
|
|
8759
|
+
const toolIdByBlock = /* @__PURE__ */ new Map();
|
|
8760
|
+
let nextIndex = 0;
|
|
8761
|
+
while (true) {
|
|
8762
|
+
const { done, value } = await reader.read();
|
|
8763
|
+
if (done) break;
|
|
8764
|
+
buffer += decoder.decode(value, { stream: true });
|
|
8765
|
+
const lines = buffer.split("\n");
|
|
8766
|
+
buffer = lines.pop() || "";
|
|
8767
|
+
for (const line of lines) {
|
|
8768
|
+
const trimmed = line.trim();
|
|
8769
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
8770
|
+
const data = trimmed.slice(6);
|
|
8771
|
+
if (!data || data === "[DONE]") continue;
|
|
8772
|
+
let event;
|
|
8773
|
+
try {
|
|
8774
|
+
event = JSON.parse(data);
|
|
8775
|
+
} catch {
|
|
8776
|
+
continue;
|
|
8777
|
+
}
|
|
8778
|
+
if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
8779
|
+
const blockIdx = event.index ?? 0;
|
|
8780
|
+
const toolId = event.content_block.id ?? "";
|
|
8781
|
+
const toolName = event.content_block.name ?? "";
|
|
8782
|
+
const patterIndex = nextIndex++;
|
|
8783
|
+
toolIndexByBlock.set(blockIdx, patterIndex);
|
|
8784
|
+
toolIdByBlock.set(blockIdx, toolId);
|
|
8785
|
+
yield {
|
|
8786
|
+
type: "tool_call",
|
|
8787
|
+
index: patterIndex,
|
|
8788
|
+
id: toolId,
|
|
8789
|
+
name: toolName,
|
|
8790
|
+
arguments: ""
|
|
8791
|
+
};
|
|
8792
|
+
continue;
|
|
8793
|
+
}
|
|
8794
|
+
if (event.type === "content_block_delta") {
|
|
8795
|
+
if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
8796
|
+
yield { type: "text", content: event.delta.text };
|
|
8797
|
+
continue;
|
|
8798
|
+
}
|
|
8799
|
+
if (event.delta?.type === "input_json_delta" && event.delta.partial_json) {
|
|
8800
|
+
const blockIdx = event.index ?? 0;
|
|
8801
|
+
const patterIndex = toolIndexByBlock.get(blockIdx);
|
|
8802
|
+
if (patterIndex !== void 0) {
|
|
8803
|
+
yield {
|
|
8804
|
+
type: "tool_call",
|
|
8805
|
+
index: patterIndex,
|
|
8806
|
+
id: toolIdByBlock.get(blockIdx),
|
|
8807
|
+
arguments: event.delta.partial_json
|
|
8808
|
+
};
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
yield { type: "done" };
|
|
8815
|
+
}
|
|
8816
|
+
};
|
|
8817
|
+
function toAnthropicTools(tools) {
|
|
8818
|
+
return tools.map((t) => {
|
|
8819
|
+
const fn = t.function ?? t;
|
|
8820
|
+
return {
|
|
8821
|
+
name: String(fn.name ?? ""),
|
|
8822
|
+
description: String(fn.description ?? ""),
|
|
8823
|
+
input_schema: fn.parameters ?? { type: "object", properties: {} }
|
|
8824
|
+
};
|
|
8825
|
+
});
|
|
8826
|
+
}
|
|
8827
|
+
function toAnthropicMessages(messages) {
|
|
8828
|
+
const systemParts = [];
|
|
8829
|
+
const out = [];
|
|
8830
|
+
for (const rawMsg of messages) {
|
|
8831
|
+
const role = rawMsg.role;
|
|
8832
|
+
if (role === "system") {
|
|
8833
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
8834
|
+
systemParts.push(rawMsg.content);
|
|
8835
|
+
}
|
|
8836
|
+
continue;
|
|
8837
|
+
}
|
|
8838
|
+
if (role === "user") {
|
|
8839
|
+
if (typeof rawMsg.content === "string") {
|
|
8840
|
+
out.push({ role: "user", content: rawMsg.content });
|
|
8841
|
+
} else if (rawMsg.content) {
|
|
8842
|
+
out.push({ role: "user", content: rawMsg.content });
|
|
8843
|
+
}
|
|
8844
|
+
continue;
|
|
8845
|
+
}
|
|
8846
|
+
if (role === "assistant") {
|
|
8847
|
+
const blocks = [];
|
|
8848
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
8849
|
+
blocks.push({ type: "text", text: rawMsg.content });
|
|
8850
|
+
}
|
|
8851
|
+
for (const tc of rawMsg.tool_calls ?? []) {
|
|
8852
|
+
let args = {};
|
|
8853
|
+
try {
|
|
8854
|
+
args = JSON.parse(tc.function?.arguments ?? "{}");
|
|
8855
|
+
} catch {
|
|
8856
|
+
args = {};
|
|
8857
|
+
}
|
|
8858
|
+
blocks.push({
|
|
8859
|
+
type: "tool_use",
|
|
8860
|
+
id: tc.id ?? "",
|
|
8861
|
+
name: tc.function?.name ?? "",
|
|
8862
|
+
input: args
|
|
8863
|
+
});
|
|
8864
|
+
}
|
|
8865
|
+
if (blocks.length > 0) {
|
|
8866
|
+
out.push({ role: "assistant", content: blocks });
|
|
8867
|
+
}
|
|
8868
|
+
continue;
|
|
8869
|
+
}
|
|
8870
|
+
if (role === "tool") {
|
|
8871
|
+
const contentStr = typeof rawMsg.content === "string" ? rawMsg.content : JSON.stringify(rawMsg.content);
|
|
8872
|
+
out.push({
|
|
8873
|
+
role: "user",
|
|
8874
|
+
content: [
|
|
8875
|
+
{
|
|
8876
|
+
type: "tool_result",
|
|
8877
|
+
tool_use_id: rawMsg.tool_call_id ?? "",
|
|
8878
|
+
content: contentStr
|
|
8879
|
+
}
|
|
8880
|
+
]
|
|
8881
|
+
});
|
|
8882
|
+
continue;
|
|
8883
|
+
}
|
|
8884
|
+
}
|
|
8885
|
+
return { system: systemParts.join("\n\n"), messages: out };
|
|
8886
|
+
}
|
|
8887
|
+
|
|
8888
|
+
// src/llm/anthropic.ts
|
|
8889
|
+
var LLM2 = class extends AnthropicLLMProvider {
|
|
8890
|
+
constructor(opts = {}) {
|
|
8891
|
+
const key = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
8892
|
+
if (!key) {
|
|
8893
|
+
throw new Error(
|
|
8894
|
+
"Anthropic LLM requires an apiKey. Pass { apiKey: 'sk-ant-...' } or set ANTHROPIC_API_KEY."
|
|
8895
|
+
);
|
|
8896
|
+
}
|
|
8897
|
+
super({
|
|
8898
|
+
apiKey: key,
|
|
8899
|
+
model: opts.model,
|
|
8900
|
+
maxTokens: opts.maxTokens,
|
|
8901
|
+
temperature: opts.temperature,
|
|
8902
|
+
baseUrl: opts.baseUrl,
|
|
8903
|
+
anthropicVersion: opts.anthropicVersion
|
|
8904
|
+
});
|
|
8905
|
+
}
|
|
8906
|
+
};
|
|
8907
|
+
|
|
8908
|
+
// src/providers/groq-llm.ts
|
|
8909
|
+
init_logger();
|
|
8910
|
+
var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
|
|
8911
|
+
var DEFAULT_MODEL2 = "llama-3.3-70b-versatile";
|
|
8912
|
+
var GroqLLMProvider = class {
|
|
8913
|
+
apiKey;
|
|
8914
|
+
model;
|
|
8915
|
+
baseUrl;
|
|
8916
|
+
constructor(options) {
|
|
8917
|
+
if (!options.apiKey) {
|
|
8918
|
+
throw new Error(
|
|
8919
|
+
"Groq API key is required. Pass it via { apiKey } or read GROQ_API_KEY from the environment."
|
|
8920
|
+
);
|
|
8921
|
+
}
|
|
8922
|
+
this.apiKey = options.apiKey;
|
|
8923
|
+
this.model = options.model ?? DEFAULT_MODEL2;
|
|
8924
|
+
this.baseUrl = options.baseUrl ?? GROQ_BASE_URL;
|
|
8925
|
+
}
|
|
8926
|
+
async *stream(messages, tools) {
|
|
8927
|
+
const body = {
|
|
8928
|
+
model: this.model,
|
|
8929
|
+
messages,
|
|
8930
|
+
stream: true
|
|
8931
|
+
};
|
|
8932
|
+
if (tools) body.tools = tools;
|
|
8933
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
8934
|
+
method: "POST",
|
|
8935
|
+
headers: {
|
|
8936
|
+
"Content-Type": "application/json",
|
|
8937
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
8938
|
+
},
|
|
8939
|
+
body: JSON.stringify(body),
|
|
8940
|
+
signal: AbortSignal.timeout(3e4)
|
|
8941
|
+
});
|
|
8942
|
+
if (!response.ok) {
|
|
8943
|
+
const errText = await response.text();
|
|
8944
|
+
getLogger().error(`Groq API error: ${response.status} ${errText}`);
|
|
8945
|
+
return;
|
|
8946
|
+
}
|
|
8947
|
+
yield* parseOpenAISseStream(response);
|
|
8948
|
+
}
|
|
8949
|
+
};
|
|
8950
|
+
async function* parseOpenAISseStream(response) {
|
|
8951
|
+
const reader = response.body?.getReader();
|
|
8952
|
+
if (!reader) return;
|
|
8953
|
+
const decoder = new TextDecoder();
|
|
8954
|
+
let buffer = "";
|
|
8955
|
+
while (true) {
|
|
8956
|
+
const { done, value } = await reader.read();
|
|
8957
|
+
if (done) break;
|
|
8958
|
+
buffer += decoder.decode(value, { stream: true });
|
|
8959
|
+
const lines = buffer.split("\n");
|
|
8960
|
+
buffer = lines.pop() || "";
|
|
8961
|
+
for (const line of lines) {
|
|
8962
|
+
const trimmed = line.trim();
|
|
8963
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
8964
|
+
const data = trimmed.slice(6);
|
|
8965
|
+
if (data === "[DONE]") continue;
|
|
8966
|
+
let chunk;
|
|
8967
|
+
try {
|
|
8968
|
+
chunk = JSON.parse(data);
|
|
8969
|
+
} catch {
|
|
8970
|
+
continue;
|
|
8971
|
+
}
|
|
8972
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
8973
|
+
if (!delta) continue;
|
|
8974
|
+
if (delta.content) {
|
|
8975
|
+
yield { type: "text", content: delta.content };
|
|
8976
|
+
}
|
|
8977
|
+
if (delta.tool_calls) {
|
|
8978
|
+
for (const tc of delta.tool_calls) {
|
|
8979
|
+
yield {
|
|
8980
|
+
type: "tool_call",
|
|
8981
|
+
index: tc.index,
|
|
8982
|
+
id: tc.id,
|
|
8983
|
+
name: tc.function?.name,
|
|
8984
|
+
arguments: tc.function?.arguments
|
|
8985
|
+
};
|
|
8986
|
+
}
|
|
8987
|
+
}
|
|
8988
|
+
}
|
|
8989
|
+
}
|
|
8990
|
+
}
|
|
8991
|
+
|
|
8992
|
+
// src/llm/groq.ts
|
|
8993
|
+
var LLM3 = class extends GroqLLMProvider {
|
|
8994
|
+
constructor(opts = {}) {
|
|
8995
|
+
const key = opts.apiKey ?? process.env.GROQ_API_KEY;
|
|
8996
|
+
if (!key) {
|
|
8997
|
+
throw new Error(
|
|
8998
|
+
"Groq LLM requires an apiKey. Pass { apiKey: 'gsk_...' } or set GROQ_API_KEY."
|
|
8999
|
+
);
|
|
9000
|
+
}
|
|
9001
|
+
super({
|
|
9002
|
+
apiKey: key,
|
|
9003
|
+
model: opts.model,
|
|
9004
|
+
baseUrl: opts.baseUrl
|
|
9005
|
+
});
|
|
9006
|
+
}
|
|
9007
|
+
};
|
|
9008
|
+
|
|
9009
|
+
// src/providers/cerebras-llm.ts
|
|
9010
|
+
init_logger();
|
|
9011
|
+
var CEREBRAS_BASE_URL = "https://api.cerebras.ai/v1";
|
|
9012
|
+
var DEFAULT_MODEL3 = "llama3.1-8b";
|
|
9013
|
+
var CerebrasLLMProvider = class {
|
|
9014
|
+
apiKey;
|
|
9015
|
+
model;
|
|
9016
|
+
baseUrl;
|
|
9017
|
+
gzipCompression;
|
|
9018
|
+
constructor(options) {
|
|
9019
|
+
if (!options.apiKey) {
|
|
9020
|
+
throw new Error(
|
|
9021
|
+
"Cerebras API key is required. Pass it via { apiKey } or read CEREBRAS_API_KEY from the environment."
|
|
9022
|
+
);
|
|
9023
|
+
}
|
|
9024
|
+
this.apiKey = options.apiKey;
|
|
9025
|
+
this.model = options.model ?? DEFAULT_MODEL3;
|
|
9026
|
+
this.baseUrl = options.baseUrl ?? CEREBRAS_BASE_URL;
|
|
9027
|
+
this.gzipCompression = options.gzipCompression ?? false;
|
|
9028
|
+
}
|
|
9029
|
+
async *stream(messages, tools) {
|
|
9030
|
+
const body = {
|
|
9031
|
+
model: this.model,
|
|
9032
|
+
messages,
|
|
9033
|
+
stream: true
|
|
9034
|
+
};
|
|
9035
|
+
if (tools) body.tools = tools;
|
|
9036
|
+
const headers = {
|
|
9037
|
+
"Content-Type": "application/json",
|
|
9038
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
9039
|
+
};
|
|
9040
|
+
let payload = JSON.stringify(body);
|
|
9041
|
+
if (this.gzipCompression) {
|
|
9042
|
+
const compressed = await gzipEncode(payload);
|
|
9043
|
+
if (compressed) {
|
|
9044
|
+
payload = compressed;
|
|
9045
|
+
headers["Content-Encoding"] = "gzip";
|
|
9046
|
+
}
|
|
9047
|
+
}
|
|
9048
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
9049
|
+
method: "POST",
|
|
9050
|
+
headers,
|
|
9051
|
+
body: payload,
|
|
9052
|
+
signal: AbortSignal.timeout(3e4)
|
|
9053
|
+
});
|
|
9054
|
+
if (!response.ok) {
|
|
9055
|
+
const errText = await response.text();
|
|
9056
|
+
getLogger().error(`Cerebras API error: ${response.status} ${errText}`);
|
|
9057
|
+
return;
|
|
9058
|
+
}
|
|
9059
|
+
yield* parseOpenAISseStream(response);
|
|
9060
|
+
}
|
|
9061
|
+
};
|
|
9062
|
+
async function gzipEncode(data) {
|
|
9063
|
+
const CompressionCtor = globalThis.CompressionStream;
|
|
9064
|
+
if (!CompressionCtor) return null;
|
|
9065
|
+
const stream = new CompressionCtor("gzip");
|
|
9066
|
+
const writer = stream.writable.getWriter();
|
|
9067
|
+
const encoder = new TextEncoder();
|
|
9068
|
+
await writer.write(encoder.encode(data));
|
|
9069
|
+
await writer.close();
|
|
9070
|
+
const chunks = [];
|
|
9071
|
+
const reader = stream.readable.getReader();
|
|
9072
|
+
while (true) {
|
|
9073
|
+
const { done, value } = await reader.read();
|
|
9074
|
+
if (done) break;
|
|
9075
|
+
if (value) chunks.push(value);
|
|
9076
|
+
}
|
|
9077
|
+
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
9078
|
+
const out = new Uint8Array(total);
|
|
9079
|
+
let offset = 0;
|
|
9080
|
+
for (const c of chunks) {
|
|
9081
|
+
out.set(c, offset);
|
|
9082
|
+
offset += c.length;
|
|
9083
|
+
}
|
|
9084
|
+
return out;
|
|
9085
|
+
}
|
|
9086
|
+
|
|
9087
|
+
// src/llm/cerebras.ts
|
|
9088
|
+
var LLM4 = class extends CerebrasLLMProvider {
|
|
9089
|
+
constructor(opts = {}) {
|
|
9090
|
+
const key = opts.apiKey ?? process.env.CEREBRAS_API_KEY;
|
|
9091
|
+
if (!key) {
|
|
9092
|
+
throw new Error(
|
|
9093
|
+
"Cerebras LLM requires an apiKey. Pass { apiKey: 'csk-...' } or set CEREBRAS_API_KEY."
|
|
9094
|
+
);
|
|
9095
|
+
}
|
|
9096
|
+
super({
|
|
9097
|
+
apiKey: key,
|
|
9098
|
+
model: opts.model,
|
|
9099
|
+
baseUrl: opts.baseUrl,
|
|
9100
|
+
gzipCompression: opts.gzipCompression
|
|
9101
|
+
});
|
|
9102
|
+
}
|
|
9103
|
+
};
|
|
9104
|
+
|
|
9105
|
+
// src/providers/google-llm.ts
|
|
9106
|
+
init_logger();
|
|
9107
|
+
var DEFAULT_MODEL4 = "gemini-2.5-flash";
|
|
9108
|
+
var DEFAULT_BASE_URL3 = "https://generativelanguage.googleapis.com/v1beta";
|
|
9109
|
+
var GoogleLLMProvider = class {
|
|
9110
|
+
apiKey;
|
|
9111
|
+
model;
|
|
9112
|
+
baseUrl;
|
|
9113
|
+
temperature;
|
|
9114
|
+
maxOutputTokens;
|
|
9115
|
+
constructor(options) {
|
|
9116
|
+
if (!options.apiKey) {
|
|
9117
|
+
throw new Error(
|
|
9118
|
+
"Google API key is required. Pass it via { apiKey } or read GOOGLE_API_KEY from the environment."
|
|
9119
|
+
);
|
|
9120
|
+
}
|
|
9121
|
+
this.apiKey = options.apiKey;
|
|
9122
|
+
this.model = options.model ?? DEFAULT_MODEL4;
|
|
9123
|
+
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL3;
|
|
9124
|
+
this.temperature = options.temperature;
|
|
9125
|
+
this.maxOutputTokens = options.maxOutputTokens;
|
|
9126
|
+
}
|
|
9127
|
+
async *stream(messages, tools) {
|
|
9128
|
+
const { systemInstruction, contents } = toGeminiContents(messages);
|
|
9129
|
+
const geminiTools = tools ? toGeminiTools(tools) : null;
|
|
9130
|
+
const body = { contents };
|
|
9131
|
+
if (systemInstruction) {
|
|
9132
|
+
body.systemInstruction = { role: "system", parts: [{ text: systemInstruction }] };
|
|
9133
|
+
}
|
|
9134
|
+
if (geminiTools) body.tools = geminiTools;
|
|
9135
|
+
const generationConfig = {};
|
|
9136
|
+
if (this.temperature !== void 0) generationConfig.temperature = this.temperature;
|
|
9137
|
+
if (this.maxOutputTokens !== void 0)
|
|
9138
|
+
generationConfig.maxOutputTokens = this.maxOutputTokens;
|
|
9139
|
+
if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
|
|
9140
|
+
const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:streamGenerateContent?alt=sse&key=${encodeURIComponent(this.apiKey)}`;
|
|
9141
|
+
const response = await fetch(url, {
|
|
9142
|
+
method: "POST",
|
|
9143
|
+
headers: { "Content-Type": "application/json" },
|
|
9144
|
+
body: JSON.stringify(body),
|
|
9145
|
+
signal: AbortSignal.timeout(3e4)
|
|
9146
|
+
});
|
|
9147
|
+
if (!response.ok) {
|
|
9148
|
+
const errText = await response.text();
|
|
9149
|
+
getLogger().error(`Gemini API error: ${response.status} ${errText}`);
|
|
9150
|
+
return;
|
|
9151
|
+
}
|
|
9152
|
+
const reader = response.body?.getReader();
|
|
9153
|
+
if (!reader) return;
|
|
9154
|
+
const decoder = new TextDecoder();
|
|
9155
|
+
let buffer = "";
|
|
9156
|
+
let nextIndex = 0;
|
|
9157
|
+
while (true) {
|
|
9158
|
+
const { done, value } = await reader.read();
|
|
9159
|
+
if (done) break;
|
|
9160
|
+
buffer += decoder.decode(value, { stream: true });
|
|
9161
|
+
const lines = buffer.split("\n");
|
|
9162
|
+
buffer = lines.pop() || "";
|
|
9163
|
+
for (const line of lines) {
|
|
9164
|
+
const trimmed = line.trim();
|
|
9165
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
9166
|
+
const data = trimmed.slice(6);
|
|
9167
|
+
if (!data) continue;
|
|
9168
|
+
let payload;
|
|
9169
|
+
try {
|
|
9170
|
+
payload = JSON.parse(data);
|
|
9171
|
+
} catch {
|
|
9172
|
+
continue;
|
|
9173
|
+
}
|
|
9174
|
+
const candidate = payload.candidates?.[0];
|
|
9175
|
+
const parts = candidate?.content?.parts ?? [];
|
|
9176
|
+
for (const part of parts) {
|
|
9177
|
+
if (part.functionCall) {
|
|
9178
|
+
const args = part.functionCall.args ?? {};
|
|
9179
|
+
const callId = part.functionCall.id ?? `gemini_call_${nextIndex}`;
|
|
9180
|
+
yield {
|
|
9181
|
+
type: "tool_call",
|
|
9182
|
+
index: nextIndex,
|
|
9183
|
+
id: callId,
|
|
9184
|
+
name: part.functionCall.name ?? "",
|
|
9185
|
+
arguments: JSON.stringify(args)
|
|
9186
|
+
};
|
|
9187
|
+
nextIndex++;
|
|
9188
|
+
continue;
|
|
9189
|
+
}
|
|
9190
|
+
if (part.text) {
|
|
9191
|
+
yield { type: "text", content: part.text };
|
|
9192
|
+
}
|
|
9193
|
+
}
|
|
9194
|
+
}
|
|
9195
|
+
}
|
|
9196
|
+
yield { type: "done" };
|
|
9197
|
+
}
|
|
9198
|
+
};
|
|
9199
|
+
function toGeminiTools(tools) {
|
|
9200
|
+
const functionDeclarations = tools.map((t) => {
|
|
9201
|
+
const fn = t.function ?? t;
|
|
9202
|
+
return {
|
|
9203
|
+
name: String(fn.name ?? ""),
|
|
9204
|
+
description: String(fn.description ?? ""),
|
|
9205
|
+
parameters: fn.parameters ?? { type: "object", properties: {} }
|
|
9206
|
+
};
|
|
9207
|
+
});
|
|
9208
|
+
if (functionDeclarations.length === 0) return [];
|
|
9209
|
+
return [{ functionDeclarations }];
|
|
9210
|
+
}
|
|
9211
|
+
function toGeminiContents(messages) {
|
|
9212
|
+
const systemParts = [];
|
|
9213
|
+
const contents = [];
|
|
9214
|
+
for (const rawMsg of messages) {
|
|
9215
|
+
const role = rawMsg.role;
|
|
9216
|
+
if (role === "system") {
|
|
9217
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
9218
|
+
systemParts.push(rawMsg.content);
|
|
9219
|
+
}
|
|
9220
|
+
continue;
|
|
9221
|
+
}
|
|
9222
|
+
if (role === "user") {
|
|
9223
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
9224
|
+
contents.push({ role: "user", parts: [{ text: rawMsg.content }] });
|
|
9225
|
+
}
|
|
9226
|
+
continue;
|
|
9227
|
+
}
|
|
9228
|
+
if (role === "assistant") {
|
|
9229
|
+
const parts = [];
|
|
9230
|
+
if (typeof rawMsg.content === "string" && rawMsg.content) {
|
|
9231
|
+
parts.push({ text: rawMsg.content });
|
|
9232
|
+
}
|
|
9233
|
+
for (const tc of rawMsg.tool_calls ?? []) {
|
|
9234
|
+
let args = {};
|
|
9235
|
+
try {
|
|
9236
|
+
const parsed = JSON.parse(tc.function?.arguments ?? "{}");
|
|
9237
|
+
if (parsed && typeof parsed === "object") args = parsed;
|
|
9238
|
+
} catch {
|
|
9239
|
+
args = {};
|
|
9240
|
+
}
|
|
9241
|
+
parts.push({
|
|
9242
|
+
functionCall: {
|
|
9243
|
+
name: tc.function?.name ?? "",
|
|
9244
|
+
args,
|
|
9245
|
+
id: tc.id
|
|
9246
|
+
}
|
|
9247
|
+
});
|
|
9248
|
+
}
|
|
9249
|
+
if (parts.length > 0) contents.push({ role: "model", parts });
|
|
9250
|
+
continue;
|
|
9251
|
+
}
|
|
9252
|
+
if (role === "tool") {
|
|
9253
|
+
const raw = rawMsg.content;
|
|
9254
|
+
let response;
|
|
9255
|
+
if (typeof raw === "string") {
|
|
9256
|
+
try {
|
|
9257
|
+
const parsed = JSON.parse(raw);
|
|
9258
|
+
response = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: parsed };
|
|
9259
|
+
} catch {
|
|
9260
|
+
response = { result: raw };
|
|
9261
|
+
}
|
|
9262
|
+
} else {
|
|
9263
|
+
response = raw ?? {};
|
|
9264
|
+
}
|
|
9265
|
+
contents.push({
|
|
9266
|
+
role: "user",
|
|
9267
|
+
parts: [
|
|
9268
|
+
{
|
|
9269
|
+
functionResponse: {
|
|
9270
|
+
name: rawMsg.name ?? rawMsg.tool_call_id ?? "",
|
|
9271
|
+
response,
|
|
9272
|
+
id: rawMsg.tool_call_id
|
|
9273
|
+
}
|
|
9274
|
+
}
|
|
9275
|
+
]
|
|
9276
|
+
});
|
|
9277
|
+
continue;
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
return { systemInstruction: systemParts.join("\n\n"), contents };
|
|
9281
|
+
}
|
|
9282
|
+
|
|
9283
|
+
// src/llm/google.ts
|
|
9284
|
+
var LLM5 = class extends GoogleLLMProvider {
|
|
9285
|
+
constructor(opts = {}) {
|
|
9286
|
+
const key = opts.apiKey ?? process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
|
|
9287
|
+
if (!key) {
|
|
9288
|
+
throw new Error(
|
|
9289
|
+
"Google LLM requires an apiKey. Pass { apiKey: 'AIza...' } or set GEMINI_API_KEY (or GOOGLE_API_KEY)."
|
|
9290
|
+
);
|
|
9291
|
+
}
|
|
9292
|
+
super({
|
|
9293
|
+
apiKey: key,
|
|
9294
|
+
model: opts.model,
|
|
9295
|
+
baseUrl: opts.baseUrl,
|
|
9296
|
+
temperature: opts.temperature,
|
|
9297
|
+
maxOutputTokens: opts.maxOutputTokens
|
|
9298
|
+
});
|
|
9299
|
+
}
|
|
9300
|
+
};
|
|
9301
|
+
|
|
9302
|
+
// src/carriers/twilio.ts
|
|
9303
|
+
var Carrier = class {
|
|
9304
|
+
kind = "twilio";
|
|
9305
|
+
accountSid;
|
|
9306
|
+
authToken;
|
|
9307
|
+
constructor(opts = {}) {
|
|
9308
|
+
const sid = opts.accountSid ?? process.env.TWILIO_ACCOUNT_SID;
|
|
9309
|
+
const tok = opts.authToken ?? process.env.TWILIO_AUTH_TOKEN;
|
|
9310
|
+
if (!sid) {
|
|
9311
|
+
throw new Error(
|
|
9312
|
+
"Twilio carrier requires accountSid. Pass { accountSid: 'AC...' } or set TWILIO_ACCOUNT_SID in the environment."
|
|
9313
|
+
);
|
|
9314
|
+
}
|
|
9315
|
+
if (!tok) {
|
|
9316
|
+
throw new Error(
|
|
9317
|
+
"Twilio carrier requires authToken. Pass { authToken: '...' } or set TWILIO_AUTH_TOKEN in the environment."
|
|
9318
|
+
);
|
|
9319
|
+
}
|
|
9320
|
+
this.accountSid = sid;
|
|
9321
|
+
this.authToken = tok;
|
|
9322
|
+
}
|
|
9323
|
+
};
|
|
9324
|
+
|
|
9325
|
+
// src/carriers/telnyx.ts
|
|
9326
|
+
var Carrier2 = class {
|
|
9327
|
+
kind = "telnyx";
|
|
9328
|
+
apiKey;
|
|
9329
|
+
connectionId;
|
|
9330
|
+
publicKey;
|
|
9331
|
+
constructor(opts = {}) {
|
|
9332
|
+
const key = opts.apiKey ?? process.env.TELNYX_API_KEY;
|
|
9333
|
+
const conn = opts.connectionId ?? process.env.TELNYX_CONNECTION_ID;
|
|
9334
|
+
const pub = opts.publicKey ?? process.env.TELNYX_PUBLIC_KEY;
|
|
9335
|
+
if (!key) {
|
|
9336
|
+
throw new Error(
|
|
9337
|
+
"Telnyx carrier requires apiKey. Pass { apiKey: '...' } or set TELNYX_API_KEY in the environment."
|
|
9338
|
+
);
|
|
9339
|
+
}
|
|
9340
|
+
if (!conn) {
|
|
9341
|
+
throw new Error(
|
|
9342
|
+
"Telnyx carrier requires connectionId. Pass { connectionId: '...' } or set TELNYX_CONNECTION_ID in the environment."
|
|
9343
|
+
);
|
|
9344
|
+
}
|
|
9345
|
+
this.apiKey = key;
|
|
9346
|
+
this.connectionId = conn;
|
|
9347
|
+
this.publicKey = pub;
|
|
9348
|
+
}
|
|
9349
|
+
};
|
|
9350
|
+
|
|
9351
|
+
// src/public-api.ts
|
|
9352
|
+
var DEFAULT_GUARDRAIL_REPLACEMENT = "I'm sorry, I can't respond to that.";
|
|
9353
|
+
var Guardrail = class {
|
|
9354
|
+
name;
|
|
9355
|
+
blockedTerms;
|
|
9356
|
+
check;
|
|
9357
|
+
replacement;
|
|
9358
|
+
constructor(opts) {
|
|
9359
|
+
if (!opts.name) {
|
|
9360
|
+
throw new Error("Guardrail requires a non-empty name.");
|
|
9361
|
+
}
|
|
9362
|
+
this.name = opts.name;
|
|
9363
|
+
if (opts.blockedTerms) this.blockedTerms = opts.blockedTerms;
|
|
9364
|
+
if (opts.check) this.check = opts.check;
|
|
9365
|
+
this.replacement = opts.replacement ?? DEFAULT_GUARDRAIL_REPLACEMENT;
|
|
9366
|
+
}
|
|
9367
|
+
};
|
|
9368
|
+
function guardrail(opts) {
|
|
9369
|
+
return new Guardrail(opts);
|
|
9370
|
+
}
|
|
9371
|
+
var Tool = class {
|
|
9372
|
+
name;
|
|
9373
|
+
description;
|
|
9374
|
+
parameters;
|
|
9375
|
+
handler;
|
|
9376
|
+
webhookUrl;
|
|
9377
|
+
constructor(opts) {
|
|
9378
|
+
if (!opts.name) {
|
|
9379
|
+
throw new Error("Tool requires a non-empty name.");
|
|
9380
|
+
}
|
|
9381
|
+
const hasHandler = typeof opts.handler === "function";
|
|
9382
|
+
const hasWebhook = typeof opts.webhookUrl === "string" && opts.webhookUrl.length > 0;
|
|
9383
|
+
if (!hasHandler && !hasWebhook) {
|
|
9384
|
+
throw new Error("Tool requires either handler or webhookUrl.");
|
|
9385
|
+
}
|
|
9386
|
+
if (hasHandler && hasWebhook) {
|
|
9387
|
+
throw new Error("Tool accepts handler OR webhookUrl, not both.");
|
|
9388
|
+
}
|
|
9389
|
+
this.name = opts.name;
|
|
9390
|
+
this.description = opts.description ?? "";
|
|
9391
|
+
this.parameters = opts.parameters ?? { type: "object", properties: {} };
|
|
9392
|
+
if (hasHandler) this.handler = opts.handler;
|
|
9393
|
+
if (hasWebhook) this.webhookUrl = opts.webhookUrl;
|
|
9394
|
+
}
|
|
9395
|
+
};
|
|
9396
|
+
function tool(opts) {
|
|
9397
|
+
return new Tool(opts);
|
|
9398
|
+
}
|
|
9399
|
+
|
|
9222
9400
|
// src/index.ts
|
|
9223
9401
|
init_transcoding();
|
|
9224
9402
|
init_tunnel();
|
|
@@ -9835,6 +10013,7 @@ function isAudioConfig(value) {
|
|
|
9835
10013
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9836
10014
|
0 && (module.exports = {
|
|
9837
10015
|
AllProvidersFailedError,
|
|
10016
|
+
AnthropicLLM,
|
|
9838
10017
|
AssemblyAISTT,
|
|
9839
10018
|
AuthenticationError,
|
|
9840
10019
|
BackgroundAudioPlayer,
|
|
@@ -9842,22 +10021,30 @@ function isAudioConfig(value) {
|
|
|
9842
10021
|
CallMetricsAccumulator,
|
|
9843
10022
|
CartesiaSTT,
|
|
9844
10023
|
CartesiaTTS,
|
|
10024
|
+
CerebrasLLM,
|
|
9845
10025
|
ChatContext,
|
|
10026
|
+
CloudflareTunnel,
|
|
9846
10027
|
DEFAULT_MIN_SENTENCE_LEN,
|
|
9847
10028
|
DEFAULT_PRICING,
|
|
9848
10029
|
DTMF_EVENTS,
|
|
9849
10030
|
DeepgramSTT,
|
|
10031
|
+
ElevenLabsConvAI,
|
|
9850
10032
|
ElevenLabsConvAIAdapter,
|
|
9851
10033
|
ElevenLabsTTS,
|
|
9852
10034
|
FallbackLLMProvider,
|
|
9853
10035
|
GEMINI_DEFAULT_INPUT_SR,
|
|
9854
10036
|
GEMINI_DEFAULT_OUTPUT_SR,
|
|
9855
10037
|
GeminiLiveAdapter,
|
|
10038
|
+
GoogleLLM,
|
|
10039
|
+
GroqLLM,
|
|
10040
|
+
Guardrail,
|
|
9856
10041
|
IVRActivity,
|
|
9857
10042
|
LLMLoop,
|
|
9858
10043
|
LMNTTTS,
|
|
9859
10044
|
MetricsStore,
|
|
10045
|
+
OpenAILLM,
|
|
9860
10046
|
OpenAILLMProvider,
|
|
10047
|
+
OpenAIRealtime,
|
|
9861
10048
|
OpenAIRealtimeAdapter,
|
|
9862
10049
|
OpenAITTS,
|
|
9863
10050
|
PartialStreamError,
|
|
@@ -9870,8 +10057,12 @@ function isAudioConfig(value) {
|
|
|
9870
10057
|
RimeTTS,
|
|
9871
10058
|
SentenceChunker,
|
|
9872
10059
|
SonioxSTT,
|
|
10060
|
+
StaticTunnel,
|
|
10061
|
+
Telnyx,
|
|
9873
10062
|
TestSession,
|
|
9874
10063
|
TfidfLoopDetector,
|
|
10064
|
+
Tool,
|
|
10065
|
+
Twilio,
|
|
9875
10066
|
ULTRAVOX_DEFAULT_API_BASE,
|
|
9876
10067
|
ULTRAVOX_DEFAULT_SR,
|
|
9877
10068
|
UltravoxRealtimeAdapter,
|
|
@@ -9891,6 +10082,7 @@ function isAudioConfig(value) {
|
|
|
9891
10082
|
filterMarkdown,
|
|
9892
10083
|
formatDtmf,
|
|
9893
10084
|
getLogger,
|
|
10085
|
+
guardrail,
|
|
9894
10086
|
isRemoteUrl,
|
|
9895
10087
|
isWebSocketUrl,
|
|
9896
10088
|
makeAuthMiddleware,
|
|
@@ -9912,5 +10104,6 @@ function isAudioConfig(value) {
|
|
|
9912
10104
|
selectSoundFromList,
|
|
9913
10105
|
setLogger,
|
|
9914
10106
|
startTunnel,
|
|
10107
|
+
tool,
|
|
9915
10108
|
whisper
|
|
9916
10109
|
});
|