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/dist/index.js CHANGED
@@ -305,254 +305,16 @@ var init_elevenlabs_convai = __esm({
305
305
  }
306
306
  });
307
307
 
308
- // src/providers/deepgram-stt.ts
309
- var import_ws4, DEEPGRAM_WS_URL, DeepgramSTT;
310
- var init_deepgram_stt = __esm({
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
- var OPENAI_TRANSCRIPTION_URL, DEFAULT_BUFFER_SIZE, WhisperSTT;
452
- var init_whisper_stt = __esm({
453
- "src/providers/whisper-stt.ts"() {
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/elevenlabs-tts.ts
1976
- function resolveVoiceId(voice) {
1977
- if (!voice) return voice;
1978
- if (VOICE_ID_PATTERN.test(voice)) return voice;
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
- ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
1986
- ELEVENLABS_VOICE_ID_BY_NAME = {
1987
- rachel: "21m00Tcm4TlvDq8ikWAM",
1988
- drew: "29vD33N1CtxCmqQRPOHJ",
1989
- clyde: "2EiwWnXFnvU5JabPnv8n",
1990
- paul: "5Q0t7uMcjvnagumLfvZi",
1991
- domi: "AZnzlk1XvdvUeBnXmlld",
1992
- dave: "CYw3kZ02Hs0563khs1Fj",
1993
- fin: "D38z5RcWu1voky8WS1ja",
1994
- bella: "EXAVITQu4vr4xnSDxMaL",
1995
- antoni: "ErXwobaYiN019PkySvjV",
1996
- thomas: "GBv7mTt0atIp3Br8iCZE",
1997
- charlie: "IKne3meq5aSn9XLyUdCD",
1998
- george: "JBFqnCBsd6RMkjVDRZzb",
1999
- emily: "LcfcDJNUP1GQjkzn1xUU",
2000
- elli: "MF3mGyEYCl7XYWbV9V6O",
2001
- callum: "N2lVS1w4EtoT3dr4eOWO",
2002
- patrick: "ODq5zmih8GrVes37Dizd",
2003
- harry: "SOYHLrjzK2X1ezoPC6cr",
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
- this.modelId = modelId;
2039
- this.outputFormat = outputFormat;
2040
- this.voiceId = resolveVoiceId(voiceId);
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
- voiceId;
2043
- /**
2044
- * Synthesise text to speech and return the full audio as a single Buffer.
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
- * Synthesise text and yield audio chunks as they arrive (streaming).
2057
- *
2058
- * The yielded buffers are raw PCM at 16 kHz (or whatever `outputFormat` is
2059
- * configured to).
2060
- */
2061
- async *synthesizeStream(text) {
2062
- const url = `${ELEVENLABS_BASE_URL}/text-to-speech/${encodeURIComponent(this.voiceId)}/stream?output_format=${encodeURIComponent(this.outputFormat)}`;
2063
- const response = await fetch(url, {
2064
- method: "POST",
2065
- headers: {
2066
- "xi-api-key": this.apiKey,
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 (!response.ok) {
2073
- const body = await response.text();
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 reader = response.body.getReader();
2080
- try {
2081
- while (true) {
2082
- const { done, value } = await reader.read();
2083
- if (done) break;
2084
- if (value && value.length > 0) {
2085
- yield Buffer.from(value);
2086
- }
2087
- }
2088
- } finally {
2089
- if (typeof reader.cancel === "function") await reader.cancel().catch(() => {
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
- if (!response.ok) {
2149
- const body = await response.text();
2150
- throw new Error(`OpenAI TTS error ${response.status}: ${body}`);
2151
- }
2152
- if (!response.body) {
2153
- throw new Error("OpenAI TTS: no response body");
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 (ctx.leftover.length > 0) {
2167
- const tail = Buffer.alloc(ctx.leftover.length * 2);
2168
- for (let i = 0; i < ctx.leftover.length; i++) {
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
- } finally {
2174
- if (typeof reader.cancel === "function") await reader.cancel().catch(() => {
2175
- });
2176
- reader.releaseLock();
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
- * Streaming 24 kHz → 16 kHz resampler (PCM16-LE). Maintains cross-chunk
2181
- * state so the 3:2 pattern doesn't reset at every network read.
2182
- */
2183
- static resampleStreaming(audio, ctx) {
2184
- let buf;
2185
- if (ctx.carryByte !== null) {
2186
- buf = Buffer.concat([Buffer.from([ctx.carryByte]), audio]);
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().info(`Deepgram actual cost: $${usd}`);
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
- init_elevenlabs_tts();
3137
- init_openai_tts();
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?.provider || (deps.agent.deepgramKey ? "deepgram" : void 0);
3180
- const ttsProviderName = deps.agent.tts?.provider === "elevenlabs" ? "elevenlabs" : deps.agent.tts?.provider === "openai" ? "openai_tts" : deps.agent.elevenlabsKey ? "elevenlabs" : void 0;
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().info(`WebSocket connection opened (${deps.bridge.label})`);
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
- getLogger().info(`Call started: ${callId}`);
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().info(`Custom params: ${sanitizeLogValue(JSON.stringify(customParams))}`);
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().info(`Recording started for ${callId}`);
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().info(`DTMF: ${digit}`);
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
- if (this.deps.agent.tts) {
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().info(`Pipeline mode (${label}): no STT configured`);
3005
+ getLogger().debug(`Pipeline mode (${label}): no STT configured`);
3383
3006
  }
3384
3007
  if (!this.tts) {
3385
- getLogger().info(`Pipeline mode (${label}): no TTS configured`);
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().info(`Pipeline mode (${label}): STT + TTS connected`);
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 (!this.deps.onMessage && this.deps.config.openaiKey) {
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().info(`Built-in LLM loop active (pipeline, ${label})`);
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().info(
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().info(`Dropped likely STT hallucination: ${sanitizeLogValue(normalised.slice(0, 40))}`);
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().info(
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().info(
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().info(`User (${label} pipeline): ${sanitizeLogValue(transcript.text)}`);
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().info(`afterTranscribe hook vetoed turn (${label})`);
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().info(`Guardrail '${guard.name}' triggered (pipeline)`);
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().info(`AI adapter connected (${label})`);
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().info(`User (${this.deps.bridge.label}): ${sanitizeLogValue(inputText)}`);
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().info(`Guardrail '${triggered.name}' triggered`);
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().info(`Transferring call to ${transferTo}`);
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().info(`Ending call (${this.deps.bridge.label}): ${reason}`);
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
- const deepgramKey = this.deps.agent.deepgramKey;
3899
- const deepgramRequestId = this.stt?.requestId;
3900
- if (deepgramKey && deepgramRequestId) {
3901
- await queryDeepgramCost(this.metricsAcc, deepgramKey, deepgramRequestId);
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
- const key = agent.elevenlabsKey ?? "";
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
- key,
4007
- agent.elevenlabsAgentId ?? "",
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
- config.openaiKey ?? "",
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
- init_deepgram_stt();
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
- const isPipeline = agent.provider === "pipeline";
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
- if (agent.stt) {
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
- // node_modules/cloudflared/lib/constants.js
4869
- var require_constants = __commonJS({
4870
- "node_modules/cloudflared/lib/constants.js"(exports2, module2) {
4871
- "use strict";
4872
- var __create2 = Object.create;
4873
- var __defProp2 = Object.defineProperty;
4874
- var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
4875
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
4876
- var __getProtoOf2 = Object.getPrototypeOf;
4877
- var __hasOwnProp2 = Object.prototype.hasOwnProperty;
4878
- var __export2 = (target, all) => {
4879
- for (var name in all)
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
- var bin = process.env.CLOUDFLARED_BIN || DEFAULT_CLOUDFLARED_BIN;
4916
- function use(executable) {
4917
- bin = executable;
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
- var CLOUDFLARED_VERSION = process.env.CLOUDFLARED_VERSION || "latest";
4920
- var RELEASE_BASE = "https://github.com/cloudflare/cloudflared/releases/";
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
- // node_modules/cloudflared/lib/error.js
4925
- var require_error = __commonJS({
4926
- "node_modules/cloudflared/lib/error.js"(exports2, module2) {
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
- var __defProp2 = Object.defineProperty;
4929
- var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
4930
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
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
- // node_modules/cloudflared/lib/install.js
4959
- var require_install = __commonJS({
4960
- "node_modules/cloudflared/lib/install.js"(exports2, module2) {
4961
- "use strict";
4962
- var __create2 = Object.create;
4963
- var __defProp2 = Object.defineProperty;
4964
- var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
4965
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
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
- AssemblyAISTT: () => AssemblyAISTT,
5737
+ AnthropicLLM: () => LLM2,
5738
+ AssemblyAISTT: () => STT5,
6878
5739
  AuthenticationError: () => AuthenticationError,
6879
5740
  BackgroundAudioPlayer: () => BackgroundAudioPlayer,
6880
5741
  BuiltinAudioClip: () => BuiltinAudioClip,
6881
5742
  CallMetricsAccumulator: () => CallMetricsAccumulator,
6882
- CartesiaSTT: () => CartesiaSTT,
6883
- CartesiaTTS: () => 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: () => DeepgramSTT,
5751
+ DeepgramSTT: () => STT,
5752
+ ElevenLabsConvAI: () => ConvAI,
6889
5753
  ElevenLabsConvAIAdapter: () => ElevenLabsConvAIAdapter,
6890
- ElevenLabsTTS: () => 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: () => LMNTTTS,
5764
+ LMNTTTS: () => TTS5,
6898
5765
  MetricsStore: () => MetricsStore,
5766
+ OpenAILLM: () => LLM,
6899
5767
  OpenAILLMProvider: () => OpenAILLMProvider,
5768
+ OpenAIRealtime: () => Realtime,
6900
5769
  OpenAIRealtimeAdapter: () => OpenAIRealtimeAdapter,
6901
- OpenAITTS: () => 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: () => RimeTTS,
5778
+ RimeTTS: () => TTS4,
6910
5779
  SentenceChunker: () => SentenceChunker,
6911
- SonioxSTT: () => 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: () => 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/providers.ts
7100
- var STTConfigImpl = class {
7101
- provider;
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
- language;
7104
- options;
7105
- constructor(provider, apiKey, language = "en", options) {
7106
- this.provider = provider;
7107
- this.apiKey = apiKey;
7108
- this.language = language;
7109
- if (options) this.options = options;
7110
- }
7111
- toDict() {
7112
- const out = {
7113
- provider: this.provider,
7114
- api_key: this.apiKey,
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
- var TTSConfigImpl = class {
7122
- provider;
5995
+
5996
+ // src/engines/elevenlabs.ts
5997
+ var ConvAI = class {
5998
+ kind = "elevenlabs_convai";
7123
5999
  apiKey;
6000
+ agentId;
7124
6001
  voice;
7125
- constructor(provider, apiKey, voice = "alloy") {
7126
- this.provider = provider;
7127
- this.apiKey = apiKey;
7128
- this.voice = voice;
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
- toDict() {
7131
- return { provider: this.provider, api_key: this.apiKey, voice: this.voice };
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
- init_server();
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 isLocal = "mode" in options && options.mode === "local" || !("apiKey" in options) && ("twilioSid" in options && options.twilioSid || "telnyxKey" in options && options.telnyxKey);
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.twilioSid && !local.telnyxKey) {
7207
- throw new Error("Local mode requires twilioSid or telnyxKey");
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
- if (local.twilioSid && !local.twilioToken) {
7210
- throw new Error("twilioToken is required when using twilioSid");
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 normalizedLocal = local.webhookUrl ? { ...local, webhookUrl: local.webhookUrl.replace(/^https?:\/\//, "").replace(/\/$/, "") } : local;
7214
- this.localConfig = normalizedLocal;
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
- if (opts.provider) {
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(opts.provider)) {
7234
- throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${opts.provider}'`);
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 (opts.tools) {
7238
- if (!Array.isArray(opts.tools)) {
6168
+ if (working.tools) {
6169
+ if (!Array.isArray(working.tools)) {
7239
6170
  throw new TypeError("tools must be an array");
7240
6171
  }
7241
- opts.tools.forEach((tool, i) => {
7242
- if (!tool.name) throw new Error(`tools[${i}] missing required 'name' field`);
7243
- if (!tool.webhookUrl && !tool.handler) throw new Error(`tools[${i}] requires either 'webhookUrl' or 'handler'`);
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 (opts.variables !== void 0 && (typeof opts.variables !== "object" || Array.isArray(opts.variables))) {
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 { ...opts };
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
- if (opts.tunnel && webhookUrl) {
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 (opts.tunnel) {
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: this.localConfig.twilioSid,
7288
- twilioToken: this.localConfig.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: this.localConfig.telephonyProvider,
7293
- telnyxKey: this.localConfig.telnyxKey,
7294
- telnyxConnectionId: this.localConfig.telnyxConnectionId,
7295
- telnyxPublicKey: this.localConfig.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, telephonyProvider } = this.localConfig;
7357
- if (telephonyProvider === "telnyx") {
7358
- const telnyxKey = this.localConfig.telnyxKey ?? "";
7359
- const connectionId = this.localConfig.telnyxConnectionId ?? "";
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 = this.localConfig.twilioSid ?? "";
7400
- const twilioToken = this.localConfig.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/index.ts
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/soniox-stt.ts
8379
- var import_ws7 = __toESM(require("ws"));
7346
+ // src/providers/whisper-stt.ts
8380
7347
  init_logger();
8381
- var SONIOX_WS_URL = "wss://stt-rt.soniox.com/transcribe-websocket";
8382
- var KEEPALIVE_MESSAGE = '{"type": "keepalive"}';
8383
- var END_TOKEN = "<end>";
8384
- var FINALIZED_TOKEN = "<fin>";
8385
- var KEEPALIVE_INTERVAL_MS = 5e3;
8386
- function isEndToken(token) {
8387
- return token.text === END_TOKEN || token.text === FINALIZED_TOKEN;
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 TokenAccumulator = class {
8390
- text = "";
8391
- confSum = 0;
8392
- confCount = 0;
8393
- update(token) {
8394
- if (token.text) {
8395
- this.text += token.text;
8396
- }
8397
- if (typeof token.confidence === "number") {
8398
- this.confSum += token.confidence;
8399
- this.confCount += 1;
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
- get confidence() {
8403
- return this.confCount === 0 ? 0 : this.confSum / this.confCount;
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
- reset() {
8406
- this.text = "";
8407
- this.confSum = 0;
8408
- this.confCount = 0;
7387
+ async connect() {
7388
+ this.running = true;
7389
+ this.buffer = Buffer.alloc(0);
8409
7390
  }
8410
- get raw() {
8411
- return { sum: this.confSum, count: this.confCount };
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
- var SonioxSTT = class _SonioxSTT {
8415
- ws = null;
8416
- callbacks = [];
8417
- final = new TokenAccumulator();
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
- const maxEndpointDelayMs = options.maxEndpointDelayMs ?? 500;
8435
- if (maxEndpointDelayMs < 500 || maxEndpointDelayMs > 3e3) {
8436
- throw new Error("maxEndpointDelayMs must be between 500 and 3000");
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
- this.apiKey = apiKey;
8439
- this.model = options.model ?? "stt-rt-v4";
8440
- this.languageHints = options.languageHints;
8441
- this.languageHintsStrict = options.languageHintsStrict ?? false;
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 import_ws7.default(this.baseUrl);
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 === import_ws7.default.OPEN) {
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
- }, KEEPALIVE_INTERVAL_MS);
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 !== import_ws7.default.OPEN) return;
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/index.ts
8599
- init_whisper_stt();
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 import_ws8 = __toESM(require("ws"));
7872
+ var import_ws9 = __toESM(require("ws"));
8603
7873
  init_logger();
8604
- var DEFAULT_BASE_URL = "wss://streaming.assemblyai.com";
7874
+ var DEFAULT_BASE_URL2 = "wss://streaming.assemblyai.com";
8605
7875
  var DEFAULT_MIN_TURN_SILENCE_MS = 100;
8606
- var CONNECT_TIMEOUT_MS = 1e4;
8607
- var MAX_CALLBACKS = 10;
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 ?? DEFAULT_BASE_URL;
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 import_ws8.default(url, {
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
- CONNECT_TIMEOUT_MS
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 !== import_ws8.default.OPEN) return;
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 >= MAX_CALLBACKS) {
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/providers/cartesia-stt.ts
8779
- var import_ws9 = __toESM(require("ws"));
8780
- init_logger();
8781
- var DEFAULT_BASE_URL2 = "https://api.cartesia.ai";
8782
- var API_VERSION = "2025-04-16";
8783
- var USER_AGENT = "Patter/1.0 (integration=LiveKit-port; provider=Cartesia)";
8784
- var KEEPALIVE_INTERVAL_MS2 = 3e4;
8785
- var CONNECT_TIMEOUT_MS2 = 1e4;
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
- ws = null;
8796
- callbacks = [];
8797
- keepaliveTimer = null;
8798
- /** Cartesia request id — set from the server transcript events. */
8799
- requestId = "";
8800
- buildWsUrl() {
8801
- const opts = this.options;
8802
- const rawBase = opts.baseUrl ?? DEFAULT_BASE_URL2;
8803
- let base;
8804
- if (rawBase.startsWith("http://")) {
8805
- base = `ws://${rawBase.slice("http://".length)}`;
8806
- } else if (rawBase.startsWith("https://")) {
8807
- base = `wss://${rawBase.slice("https://".length)}`;
8808
- } else if (rawBase.startsWith("ws://") || rawBase.startsWith("wss://")) {
8809
- base = rawBase;
8810
- } else {
8811
- base = `wss://${rawBase}`;
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
- const language = opts.language ?? "en";
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
- async connect() {
8825
- const url = this.buildWsUrl();
8826
- this.ws = new import_ws9.default(url, {
8827
- headers: { "User-Agent": USER_AGENT }
8828
- });
8829
- await new Promise((resolve, reject) => {
8830
- const timer = setTimeout(
8831
- () => reject(new Error("Cartesia STT connect timeout")),
8832
- CONNECT_TIMEOUT_MS2
8833
- );
8834
- this.ws.once("open", () => {
8835
- clearTimeout(timer);
8836
- resolve();
8837
- });
8838
- this.ws.once("error", (err) => {
8839
- clearTimeout(timer);
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
- this.keepaliveTimer = setInterval(() => {
8853
- if (this.ws && this.ws.readyState === import_ws9.default.OPEN) {
8854
- try {
8855
- this.ws.ping();
8856
- } catch {
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
- }, KEEPALIVE_INTERVAL_MS2);
8860
- }
8861
- handleEvent(event) {
8862
- const type = event.type;
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
- emit(transcript) {
8881
- for (const cb of this.callbacks) {
8882
- cb(transcript);
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
- sendAudio(audio) {
8886
- if (!this.ws || this.ws.readyState !== import_ws9.default.OPEN) return;
8887
- this.ws.send(audio);
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
- onTranscript(callback) {
8890
- if (this.callbacks.length >= MAX_CALLBACKS2) {
8891
- getLogger().warn(
8892
- "CartesiaSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback."
8893
- );
8894
- this.callbacks[this.callbacks.length - 1] = callback;
8895
- return;
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
- this.callbacks.push(callback);
8216
+ return Buffer.concat(chunks);
8898
8217
  }
8899
- close() {
8900
- if (this.keepaliveTimer) {
8901
- clearInterval(this.keepaliveTimer);
8902
- this.keepaliveTimer = null;
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 (this.ws) {
8905
- try {
8906
- this.ws.send("finalize");
8907
- } catch {
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
- this.ws.close();
8910
- this.ws = null;
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/index.ts
8916
- init_elevenlabs_tts();
8917
- init_openai_tts();
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
  });