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.mjs CHANGED
@@ -3,25 +3,22 @@ import {
3
3
  } from "./chunk-AFUYSNDH.mjs";
4
4
  import {
5
5
  startTunnel
6
- } from "./chunk-JO5C35FM.mjs";
6
+ } from "./chunk-AKQFOFLG.mjs";
7
7
  import {
8
8
  CallMetricsAccumulator,
9
9
  DEFAULT_MIN_SENTENCE_LEN,
10
10
  DEFAULT_PRICING,
11
11
  DeepgramSTT,
12
12
  ElevenLabsConvAIAdapter,
13
- ElevenLabsTTS,
14
13
  EmbeddedServer,
15
14
  LLMLoop,
16
15
  MetricsStore,
17
16
  OpenAILLMProvider,
18
17
  OpenAIRealtimeAdapter,
19
- OpenAITTS,
20
18
  PipelineHookExecutor,
21
19
  RemoteMessageHandler,
22
20
  SentenceChunker,
23
21
  TestSession,
24
- WhisperSTT,
25
22
  calculateRealtimeCost,
26
23
  calculateSttCost,
27
24
  calculateTelephonyCost,
@@ -39,7 +36,7 @@ import {
39
36
  resample16kTo8k,
40
37
  resample24kTo16k,
41
38
  resample8kTo16k
42
- } from "./chunk-O3RQG3NL.mjs";
39
+ } from "./chunk-B6C3KIBG.mjs";
43
40
  import {
44
41
  getLogger,
45
42
  setLogger
@@ -186,74 +183,64 @@ var PatterConnection = class {
186
183
  }
187
184
  };
188
185
 
189
- // src/providers.ts
190
- var STTConfigImpl = class {
191
- provider;
186
+ // src/engines/openai.ts
187
+ var Realtime = class {
188
+ kind = "openai_realtime";
192
189
  apiKey;
193
- language;
194
- options;
195
- constructor(provider, apiKey, language = "en", options) {
196
- this.provider = provider;
197
- this.apiKey = apiKey;
198
- this.language = language;
199
- if (options) this.options = options;
200
- }
201
- toDict() {
202
- const out = {
203
- provider: this.provider,
204
- api_key: this.apiKey,
205
- language: this.language
206
- };
207
- if (this.options) out.options = { ...this.options };
208
- return out;
190
+ model;
191
+ voice;
192
+ constructor(opts = {}) {
193
+ const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
194
+ if (!key) {
195
+ throw new Error(
196
+ "OpenAI Realtime requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
197
+ );
198
+ }
199
+ this.apiKey = key;
200
+ this.model = opts.model ?? "gpt-4o-mini-realtime-preview";
201
+ this.voice = opts.voice ?? "alloy";
209
202
  }
210
203
  };
211
- var TTSConfigImpl = class {
212
- provider;
204
+
205
+ // src/engines/elevenlabs.ts
206
+ var ConvAI = class {
207
+ kind = "elevenlabs_convai";
213
208
  apiKey;
209
+ agentId;
214
210
  voice;
215
- constructor(provider, apiKey, voice = "alloy") {
216
- this.provider = provider;
217
- this.apiKey = apiKey;
218
- this.voice = voice;
211
+ constructor(opts = {}) {
212
+ const key = opts.apiKey ?? process.env.ELEVENLABS_API_KEY;
213
+ const agent = opts.agentId ?? process.env.ELEVENLABS_AGENT_ID;
214
+ if (!key) {
215
+ throw new Error(
216
+ "ElevenLabs ConvAI requires an apiKey. Pass { apiKey: '...' } or set ELEVENLABS_API_KEY in the environment."
217
+ );
218
+ }
219
+ if (!agent) {
220
+ throw new Error(
221
+ "ElevenLabs ConvAI requires an agentId. Pass { agentId: 'agent_...' } or set ELEVENLABS_AGENT_ID in the environment."
222
+ );
223
+ }
224
+ this.apiKey = key;
225
+ this.agentId = agent;
226
+ this.voice = opts.voice;
219
227
  }
220
- toDict() {
221
- return { provider: this.provider, api_key: this.apiKey, voice: this.voice };
228
+ };
229
+
230
+ // src/tunnels/index.ts
231
+ var CloudflareTunnel = class {
232
+ kind = "cloudflare";
233
+ };
234
+ var Static = class {
235
+ kind = "static";
236
+ hostname;
237
+ constructor(opts) {
238
+ if (!opts.hostname) {
239
+ throw new Error("Static tunnel requires a non-empty hostname.");
240
+ }
241
+ this.hostname = opts.hostname;
222
242
  }
223
243
  };
224
- function deepgram(opts) {
225
- const options = {
226
- model: opts.model ?? "nova-3",
227
- endpointing_ms: opts.endpointingMs ?? 150,
228
- utterance_end_ms: opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3,
229
- smart_format: opts.smartFormat ?? true,
230
- interim_results: opts.interimResults ?? true
231
- };
232
- if (opts.vadEvents !== void 0) options.vad_events = opts.vadEvents;
233
- return new STTConfigImpl("deepgram", opts.apiKey, opts.language ?? "en", options);
234
- }
235
- function whisper(opts) {
236
- return new STTConfigImpl("whisper", opts.apiKey, opts.language ?? "en");
237
- }
238
- function elevenlabs(opts) {
239
- return new TTSConfigImpl("elevenlabs", opts.apiKey, opts.voice ?? "rachel");
240
- }
241
- function openaiTts(opts) {
242
- return new TTSConfigImpl("openai", opts.apiKey, opts.voice ?? "alloy");
243
- }
244
- function cartesia(opts) {
245
- return new TTSConfigImpl(
246
- "cartesia",
247
- opts.apiKey,
248
- opts.voice ?? "f786b574-daa5-4673-aa0c-cbe3e8534c02"
249
- );
250
- }
251
- function rime(opts) {
252
- return new TTSConfigImpl("rime", opts.apiKey, opts.voice ?? "astra");
253
- }
254
- function lmnt(opts) {
255
- return new TTSConfigImpl("lmnt", opts.apiKey, opts.voice ?? "leah");
256
- }
257
244
 
258
245
  // src/client.ts
259
246
  var DEFAULT_BACKEND_URL2 = "wss://api.getpatter.com";
@@ -286,21 +273,39 @@ var Patter = class {
286
273
  embeddedServer = null;
287
274
  tunnelHandle = null;
288
275
  constructor(options) {
289
- const isLocal = "mode" in options && options.mode === "local" || !("apiKey" in options) && ("twilioSid" in options && options.twilioSid || "telnyxKey" in options && options.telnyxKey);
276
+ const hasCarrier = "carrier" in options && options.carrier !== void 0;
277
+ const isLocal = "mode" in options && options.mode === "local" || hasCarrier;
290
278
  if (isLocal) {
291
279
  const local = options;
292
280
  if (!local.phoneNumber) {
293
281
  throw new Error("Local mode requires phoneNumber");
294
282
  }
295
- if (!local.twilioSid && !local.telnyxKey) {
296
- throw new Error("Local mode requires twilioSid or telnyxKey");
283
+ if (!local.carrier) {
284
+ throw new Error(
285
+ "Local mode requires a `carrier` instance. Pass `carrier: new Twilio({...})` or `carrier: new Telnyx({...})`."
286
+ );
297
287
  }
298
- if (local.twilioSid && !local.twilioToken) {
299
- throw new Error("twilioToken is required when using twilioSid");
288
+ const carrier = local.carrier;
289
+ const tunnel = local.tunnel;
290
+ let tunnelWebhookUrl;
291
+ if (tunnel instanceof Static) {
292
+ if (local.webhookUrl) {
293
+ throw new Error(
294
+ "Cannot use both `tunnel: new StaticTunnel(...)` and `webhookUrl`. Pick one."
295
+ );
296
+ }
297
+ tunnelWebhookUrl = tunnel.hostname;
300
298
  }
301
299
  this.mode = "local";
302
- const normalizedLocal = local.webhookUrl ? { ...local, webhookUrl: local.webhookUrl.replace(/^https?:\/\//, "").replace(/\/$/, "") } : local;
303
- this.localConfig = normalizedLocal;
300
+ const rawWebhook = tunnelWebhookUrl ?? local.webhookUrl;
301
+ const normalizedWebhook = rawWebhook ? rawWebhook.replace(/^https?:\/\//, "").replace(/\/$/, "") : void 0;
302
+ this.localConfig = {
303
+ carrier,
304
+ phoneNumber: local.phoneNumber,
305
+ webhookUrl: normalizedWebhook,
306
+ tunnel: local.tunnel,
307
+ openaiKey: local.openaiKey
308
+ };
304
309
  this.apiKey = "";
305
310
  this.backendUrl = DEFAULT_BACKEND_URL2;
306
311
  this.restUrl = DEFAULT_REST_URL;
@@ -317,25 +322,70 @@ var Patter = class {
317
322
  }
318
323
  // === Local mode ===
319
324
  agent(opts) {
320
- if (opts.provider) {
325
+ let working = { ...opts };
326
+ if (opts.engine) {
327
+ if (opts.provider) {
328
+ throw new Error(
329
+ "Cannot pass both `engine:` and `provider:`. Use one (engine is preferred)."
330
+ );
331
+ }
332
+ const engine = opts.engine;
333
+ if (engine instanceof Realtime) {
334
+ working = {
335
+ ...working,
336
+ provider: "openai_realtime",
337
+ model: working.model ?? engine.model,
338
+ voice: working.voice ?? engine.voice
339
+ };
340
+ if (this.localConfig && !this.localConfig.openaiKey) {
341
+ this.localConfig = { ...this.localConfig, openaiKey: engine.apiKey };
342
+ }
343
+ } else if (engine instanceof ConvAI) {
344
+ working = {
345
+ ...working,
346
+ provider: "elevenlabs_convai",
347
+ voice: working.voice ?? engine.voice
348
+ };
349
+ } else {
350
+ throw new Error(
351
+ "Unknown engine. Expected OpenAIRealtime or ElevenLabsConvAI instance."
352
+ );
353
+ }
354
+ } else if (!working.provider && (working.stt !== void 0 || working.tts !== void 0 || working.llm !== void 0)) {
355
+ working = { ...working, provider: "pipeline" };
356
+ }
357
+ if (working.provider) {
321
358
  const valid = ["openai_realtime", "elevenlabs_convai", "pipeline"];
322
- if (!valid.includes(opts.provider)) {
323
- throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${opts.provider}'`);
359
+ if (!valid.includes(working.provider)) {
360
+ throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${working.provider}'`);
361
+ }
362
+ }
363
+ if (working.llm !== void 0) {
364
+ const llm = working.llm;
365
+ if (!llm || typeof llm.stream !== "function") {
366
+ throw new Error(
367
+ "`llm` must be an LLMProvider instance (e.g. new AnthropicLLM()). Got a value without a `.stream` method."
368
+ );
369
+ }
370
+ if (working.engine) {
371
+ getLogger().warn(
372
+ "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."
373
+ );
324
374
  }
325
375
  }
326
- if (opts.tools) {
327
- if (!Array.isArray(opts.tools)) {
376
+ if (working.tools) {
377
+ if (!Array.isArray(working.tools)) {
328
378
  throw new TypeError("tools must be an array");
329
379
  }
330
- opts.tools.forEach((tool, i) => {
331
- if (!tool.name) throw new Error(`tools[${i}] missing required 'name' field`);
332
- if (!tool.webhookUrl && !tool.handler) throw new Error(`tools[${i}] requires either 'webhookUrl' or 'handler'`);
380
+ working.tools.forEach((tool2, i) => {
381
+ if (!tool2.name) throw new Error(`tools[${i}] missing required 'name' field`);
382
+ if (!tool2.webhookUrl && !tool2.handler) throw new Error(`tools[${i}] requires either 'webhookUrl' or 'handler'`);
333
383
  });
334
384
  }
335
- if (opts.variables !== void 0 && (typeof opts.variables !== "object" || Array.isArray(opts.variables))) {
385
+ if (working.variables !== void 0 && (typeof working.variables !== "object" || Array.isArray(working.variables))) {
336
386
  throw new TypeError("variables must be an object");
337
387
  }
338
- return { ...opts };
388
+ return working;
339
389
  }
340
390
  async serve(opts) {
341
391
  if (this.mode !== "local" || !this.localConfig) {
@@ -358,11 +408,15 @@ var Patter = class {
358
408
  }
359
409
  let webhookUrl = this.localConfig.webhookUrl ?? "";
360
410
  const port = opts.port ?? 8e3;
361
- if (opts.tunnel && webhookUrl) {
411
+ const ctorTunnel = this.localConfig.tunnel;
412
+ const wantsCloudflaredFromServe = opts.tunnel === true;
413
+ const wantsCloudflaredFromCtor = ctorTunnel === true || ctorTunnel instanceof CloudflareTunnel;
414
+ const wantsCloudflared = wantsCloudflaredFromServe || wantsCloudflaredFromCtor;
415
+ if (wantsCloudflared && webhookUrl) {
362
416
  throw new Error("Cannot use both tunnel: true and webhookUrl. Pick one.");
363
417
  }
364
- if (opts.tunnel) {
365
- const { startTunnel: startTunnel2 } = await import("./tunnel-BL7A7GXW.mjs");
418
+ if (wantsCloudflared) {
419
+ const { startTunnel: startTunnel2 } = await import("./tunnel-O7ICMSTP.mjs");
366
420
  this.tunnelHandle = await startTunnel2(port);
367
421
  webhookUrl = this.tunnelHandle.hostname;
368
422
  }
@@ -371,17 +425,29 @@ var Patter = class {
371
425
  "No webhookUrl configured. Either:\n - Pass webhookUrl in the Patter constructor\n - Use tunnel: true in serve() to auto-create a tunnel"
372
426
  );
373
427
  }
428
+ const carrier = this.localConfig.carrier;
429
+ const telephonyProvider = carrier.kind === "twilio" ? "twilio" : "telnyx";
430
+ const { autoConfigureCarrier } = await import("./carrier-config-CPG5CROM.mjs");
431
+ await autoConfigureCarrier({
432
+ telephonyProvider,
433
+ twilioSid: carrier.kind === "twilio" ? carrier.accountSid : void 0,
434
+ twilioToken: carrier.kind === "twilio" ? carrier.authToken : void 0,
435
+ telnyxKey: carrier.kind === "telnyx" ? carrier.apiKey : void 0,
436
+ telnyxConnectionId: carrier.kind === "telnyx" ? carrier.connectionId : void 0,
437
+ phoneNumber: this.localConfig.phoneNumber,
438
+ webhookHost: webhookUrl
439
+ });
374
440
  this.embeddedServer = new EmbeddedServer(
375
441
  {
376
- twilioSid: this.localConfig.twilioSid,
377
- twilioToken: this.localConfig.twilioToken,
442
+ twilioSid: carrier.kind === "twilio" ? carrier.accountSid : void 0,
443
+ twilioToken: carrier.kind === "twilio" ? carrier.authToken : void 0,
378
444
  openaiKey: this.localConfig.openaiKey,
379
445
  phoneNumber: this.localConfig.phoneNumber,
380
446
  webhookUrl,
381
- telephonyProvider: this.localConfig.telephonyProvider,
382
- telnyxKey: this.localConfig.telnyxKey,
383
- telnyxConnectionId: this.localConfig.telnyxConnectionId,
384
- telnyxPublicKey: this.localConfig.telnyxPublicKey
447
+ telephonyProvider,
448
+ telnyxKey: carrier.kind === "telnyx" ? carrier.apiKey : void 0,
449
+ telnyxConnectionId: carrier.kind === "telnyx" ? carrier.connectionId : void 0,
450
+ telnyxPublicKey: carrier.kind === "telnyx" ? carrier.publicKey : void 0
385
451
  },
386
452
  opts.agent,
387
453
  opts.onCallStart,
@@ -401,7 +467,7 @@ var Patter = class {
401
467
  if (this.mode !== "local") {
402
468
  throw new Error("test() is only available in local mode");
403
469
  }
404
- const { TestSession: TestSession2 } = await import("./test-mode-ASSLSQU2.mjs");
470
+ const { TestSession: TestSession2 } = await import("./test-mode-JZMYE5HY.mjs");
405
471
  const session = new TestSession2();
406
472
  await session.run({
407
473
  agent: opts.agent,
@@ -442,10 +508,10 @@ var Patter = class {
442
508
  if (!this.localConfig) {
443
509
  throw new Error("local config missing");
444
510
  }
445
- const { phoneNumber, webhookUrl, telephonyProvider } = this.localConfig;
446
- if (telephonyProvider === "telnyx") {
447
- const telnyxKey = this.localConfig.telnyxKey ?? "";
448
- const connectionId = this.localConfig.telnyxConnectionId ?? "";
511
+ const { phoneNumber, webhookUrl, carrier } = this.localConfig;
512
+ if (carrier.kind === "telnyx") {
513
+ const telnyxKey = carrier.apiKey;
514
+ const connectionId = carrier.connectionId;
449
515
  const streamUrl = `wss://${webhookUrl}/ws/stream/${encodeURIComponent(localOpts.to)}?caller=${encodeURIComponent(phoneNumber)}&callee=${encodeURIComponent(localOpts.to)}`;
450
516
  const telnyxPayload = {
451
517
  connection_id: connectionId,
@@ -485,8 +551,8 @@ var Patter = class {
485
551
  }
486
552
  return;
487
553
  }
488
- const twilioSid = this.localConfig.twilioSid ?? "";
489
- const twilioToken = this.localConfig.twilioToken ?? "";
554
+ const twilioSid = carrier.accountSid;
555
+ const twilioToken = carrier.authToken;
490
556
  const statusCallbackUrl = `https://${webhookUrl}/webhooks/twilio/status`;
491
557
  const url = `https://api.twilio.com/2010-04-01/Accounts/${twilioSid}/Calls.json`;
492
558
  const params = new URLSearchParams({
@@ -618,65 +684,6 @@ var Patter = class {
618
684
  const data = await response.json();
619
685
  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 }));
620
686
  }
621
- // Provider helpers — mirror the Python classmethod factories so callers can
622
- // write ``Patter.deepgram({ apiKey })`` without importing the top-level.
623
- static deepgram = deepgram;
624
- static whisper = whisper;
625
- static elevenlabs = elevenlabs;
626
- static openaiTts = openaiTts;
627
- static cartesia = cartesia;
628
- static rime = rime;
629
- static lmnt = lmnt;
630
- static guardrail(opts) {
631
- return {
632
- name: opts.name,
633
- blockedTerms: opts.blockedTerms,
634
- check: opts.check,
635
- replacement: opts.replacement ?? "I'm sorry, I can't respond to that."
636
- };
637
- }
638
- /**
639
- * Create a tool definition for use with `agent({ tools: [...] })`.
640
- *
641
- * Either `handler` (a function) or `webhookUrl` must be provided.
642
- *
643
- * @param opts.name - Tool name (visible to the LLM).
644
- * @param opts.description - What the tool does (visible to the LLM).
645
- * @param opts.parameters - JSON Schema for tool arguments.
646
- * @param opts.handler - Async function called in-process when the LLM invokes the tool.
647
- * @param opts.webhookUrl - URL to POST to when the LLM invokes the tool.
648
- *
649
- * @example
650
- * ```ts
651
- * phone.agent({
652
- * systemPrompt: 'You are a pizza bot.',
653
- * tools: [
654
- * Patter.tool({
655
- * name: 'check_menu',
656
- * description: 'Check available menu items',
657
- * handler: async (args) => JSON.stringify({ items: ['margherita'] }),
658
- * }),
659
- * ],
660
- * });
661
- * ```
662
- */
663
- static tool(opts) {
664
- if (!opts.handler && !opts.webhookUrl) {
665
- throw new Error("tool() requires either handler or webhookUrl");
666
- }
667
- const t = {
668
- name: opts.name,
669
- description: opts.description ?? "",
670
- parameters: opts.parameters ?? { type: "object", properties: {} }
671
- };
672
- if (opts.handler) {
673
- t.handler = opts.handler;
674
- }
675
- if (opts.webhookUrl) {
676
- t.webhookUrl = opts.webhookUrl;
677
- }
678
- return t;
679
- }
680
687
  // Internal
681
688
  async registerNumber(provider, providerKey, number, providerSecret, country = "US", stt, tts) {
682
689
  const credentials = { api_key: providerKey };
@@ -766,6 +773,62 @@ function filterForTTS(text) {
766
773
  return filterEmoji(filterMarkdown(text));
767
774
  }
768
775
 
776
+ // src/providers.ts
777
+ var STTConfigImpl = class {
778
+ provider;
779
+ apiKey;
780
+ language;
781
+ options;
782
+ constructor(provider, apiKey, language = "en", options) {
783
+ this.provider = provider;
784
+ this.apiKey = apiKey;
785
+ this.language = language;
786
+ if (options) this.options = options;
787
+ }
788
+ toDict() {
789
+ const out = {
790
+ provider: this.provider,
791
+ api_key: this.apiKey,
792
+ language: this.language
793
+ };
794
+ if (this.options) out.options = { ...this.options };
795
+ return out;
796
+ }
797
+ };
798
+ var TTSConfigImpl = class {
799
+ provider;
800
+ apiKey;
801
+ voice;
802
+ constructor(provider, apiKey, voice = "alloy") {
803
+ this.provider = provider;
804
+ this.apiKey = apiKey;
805
+ this.voice = voice;
806
+ }
807
+ toDict() {
808
+ return { provider: this.provider, api_key: this.apiKey, voice: this.voice };
809
+ }
810
+ };
811
+ function deepgram(opts) {
812
+ const options = {
813
+ model: opts.model ?? "nova-3",
814
+ endpointing_ms: opts.endpointingMs ?? 150,
815
+ utterance_end_ms: opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3,
816
+ smart_format: opts.smartFormat ?? true,
817
+ interim_results: opts.interimResults ?? true
818
+ };
819
+ if (opts.vadEvents !== void 0) options.vad_events = opts.vadEvents;
820
+ return new STTConfigImpl("deepgram", opts.apiKey, opts.language ?? "en", options);
821
+ }
822
+ function whisper(opts) {
823
+ return new STTConfigImpl("whisper", opts.apiKey, opts.language ?? "en");
824
+ }
825
+ function elevenlabs(opts) {
826
+ return new TTSConfigImpl("elevenlabs", opts.apiKey, opts.voice ?? "rachel");
827
+ }
828
+ function openaiTts(opts) {
829
+ return new TTSConfigImpl("openai", opts.apiKey, opts.voice ?? "alloy");
830
+ }
831
+
769
832
  // src/fallback-provider.ts
770
833
  var AllProvidersFailedError = class extends Error {
771
834
  constructor(message) {
@@ -1439,49 +1502,363 @@ function scheduleInterval(intervalOrOpts, callback) {
1439
1502
  };
1440
1503
  }
1441
1504
 
1442
- // src/providers/soniox-stt.ts
1443
- import WebSocket3 from "ws";
1444
- var SONIOX_WS_URL = "wss://stt-rt.soniox.com/transcribe-websocket";
1445
- var KEEPALIVE_MESSAGE = '{"type": "keepalive"}';
1446
- var END_TOKEN = "<end>";
1447
- var FINALIZED_TOKEN = "<fin>";
1448
- var KEEPALIVE_INTERVAL_MS = 5e3;
1449
- function isEndToken(token) {
1450
- return token.text === END_TOKEN || token.text === FINALIZED_TOKEN;
1451
- }
1452
- var TokenAccumulator = class {
1453
- text = "";
1454
- confSum = 0;
1455
- confCount = 0;
1456
- update(token) {
1457
- if (token.text) {
1458
- this.text += token.text;
1505
+ // src/stt/deepgram.ts
1506
+ var STT = class extends DeepgramSTT {
1507
+ constructor(opts = {}) {
1508
+ const key = opts.apiKey ?? process.env.DEEPGRAM_API_KEY;
1509
+ if (!key) {
1510
+ throw new Error(
1511
+ "Deepgram STT requires an apiKey. Pass { apiKey: 'dg_...' } or set DEEPGRAM_API_KEY in the environment."
1512
+ );
1459
1513
  }
1460
- if (typeof token.confidence === "number") {
1461
- this.confSum += token.confidence;
1462
- this.confCount += 1;
1514
+ super(
1515
+ key,
1516
+ opts.language ?? "en",
1517
+ opts.model ?? "nova-3",
1518
+ opts.encoding ?? "linear16",
1519
+ opts.sampleRate ?? 16e3,
1520
+ {
1521
+ endpointingMs: opts.endpointingMs ?? 150,
1522
+ utteranceEndMs: opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3,
1523
+ smartFormat: opts.smartFormat ?? true,
1524
+ interimResults: opts.interimResults ?? true,
1525
+ ...opts.vadEvents !== void 0 ? { vadEvents: opts.vadEvents } : {}
1526
+ }
1527
+ );
1528
+ }
1529
+ };
1530
+
1531
+ // src/providers/whisper-stt.ts
1532
+ var OPENAI_TRANSCRIPTION_URL = "https://api.openai.com/v1/audio/transcriptions";
1533
+ var DEFAULT_BUFFER_SIZE = 16e3 * 2;
1534
+ function wrapPcmInWav(pcm, sampleRate = 16e3, channels = 1, bitsPerSample = 16) {
1535
+ const dataSize = pcm.length;
1536
+ const header = Buffer.alloc(44);
1537
+ header.write("RIFF", 0);
1538
+ header.writeUInt32LE(36 + dataSize, 4);
1539
+ header.write("WAVE", 8);
1540
+ header.write("fmt ", 12);
1541
+ header.writeUInt32LE(16, 16);
1542
+ header.writeUInt16LE(1, 20);
1543
+ header.writeUInt16LE(channels, 22);
1544
+ header.writeUInt32LE(sampleRate, 24);
1545
+ header.writeUInt32LE(sampleRate * channels * (bitsPerSample / 8), 28);
1546
+ header.writeUInt16LE(channels * (bitsPerSample / 8), 32);
1547
+ header.writeUInt16LE(bitsPerSample, 34);
1548
+ header.write("data", 36);
1549
+ header.writeUInt32LE(dataSize, 40);
1550
+ return Buffer.concat([header, pcm]);
1551
+ }
1552
+ var WhisperSTT = class _WhisperSTT {
1553
+ apiKey;
1554
+ model;
1555
+ language;
1556
+ bufferSize;
1557
+ buffer = Buffer.alloc(0);
1558
+ callbacks = [];
1559
+ running = false;
1560
+ pendingTranscriptions = [];
1561
+ constructor(apiKey, model = "whisper-1", language, bufferSize = DEFAULT_BUFFER_SIZE) {
1562
+ this.apiKey = apiKey;
1563
+ this.model = model;
1564
+ this.language = language;
1565
+ this.bufferSize = bufferSize;
1566
+ }
1567
+ /** Factory for Twilio calls — mulaw 8 kHz is transcoded upstream, so we still receive PCM 16-bit. */
1568
+ static forTwilio(apiKey, language = "en", model = "whisper-1") {
1569
+ return new _WhisperSTT(apiKey, model, language);
1570
+ }
1571
+ async connect() {
1572
+ this.running = true;
1573
+ this.buffer = Buffer.alloc(0);
1574
+ }
1575
+ sendAudio(audio) {
1576
+ if (!this.running) return;
1577
+ this.buffer = Buffer.concat([this.buffer, audio]);
1578
+ if (this.buffer.length >= this.bufferSize) {
1579
+ const pcm = this.buffer;
1580
+ this.buffer = Buffer.alloc(0);
1581
+ this.trackTranscription(this.transcribeBuffer(pcm));
1463
1582
  }
1464
1583
  }
1465
- get confidence() {
1466
- return this.confCount === 0 ? 0 : this.confSum / this.confCount;
1584
+ trackTranscription(promise) {
1585
+ const wrapped = promise.finally(() => {
1586
+ const idx = this.pendingTranscriptions.indexOf(wrapped);
1587
+ if (idx !== -1) this.pendingTranscriptions.splice(idx, 1);
1588
+ });
1589
+ this.pendingTranscriptions.push(wrapped);
1467
1590
  }
1468
- reset() {
1469
- this.text = "";
1470
- this.confSum = 0;
1471
- this.confCount = 0;
1591
+ onTranscript(callback) {
1592
+ if (this.callbacks.length >= 10) {
1593
+ getLogger().warn("WhisperSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback.");
1594
+ this.callbacks[this.callbacks.length - 1] = callback;
1595
+ return;
1596
+ }
1597
+ this.callbacks.push(callback);
1472
1598
  }
1473
- get raw() {
1474
- return { sum: this.confSum, count: this.confCount };
1599
+ async close() {
1600
+ this.running = false;
1601
+ if (this.buffer.length >= this.bufferSize / 4) {
1602
+ const pcm = this.buffer;
1603
+ this.buffer = Buffer.alloc(0);
1604
+ this.trackTranscription(this.transcribeBuffer(pcm));
1605
+ } else {
1606
+ this.buffer = Buffer.alloc(0);
1607
+ }
1608
+ await Promise.allSettled(this.pendingTranscriptions);
1609
+ this.callbacks = [];
1610
+ }
1611
+ // ------------------------------------------------------------------
1612
+ // Private
1613
+ // ------------------------------------------------------------------
1614
+ async transcribeBuffer(pcm) {
1615
+ const wav = wrapPcmInWav(pcm);
1616
+ const formData = new FormData();
1617
+ formData.append("file", new Blob([wav.buffer.slice(wav.byteOffset, wav.byteOffset + wav.byteLength)], { type: "audio/wav" }), "audio.wav");
1618
+ formData.append("model", this.model);
1619
+ if (this.language) {
1620
+ formData.append("language", this.language);
1621
+ }
1622
+ try {
1623
+ const resp = await fetch(OPENAI_TRANSCRIPTION_URL, {
1624
+ method: "POST",
1625
+ headers: { Authorization: `Bearer ${this.apiKey}` },
1626
+ body: formData,
1627
+ signal: AbortSignal.timeout(15e3)
1628
+ });
1629
+ if (!resp.ok) {
1630
+ const body = await resp.text();
1631
+ getLogger().error(`WhisperSTT transcription error: ${resp.status} ${body}`);
1632
+ return;
1633
+ }
1634
+ const json = await resp.json();
1635
+ const text = (json.text ?? "").trim();
1636
+ if (!text) return;
1637
+ const transcript = {
1638
+ text,
1639
+ isFinal: true,
1640
+ confidence: 1
1641
+ };
1642
+ for (const cb of this.callbacks) {
1643
+ cb(transcript);
1644
+ }
1645
+ } catch (err) {
1646
+ getLogger().error(`WhisperSTT transcription error: ${String(err)}`);
1647
+ }
1475
1648
  }
1476
1649
  };
1477
- var SonioxSTT = class _SonioxSTT {
1650
+
1651
+ // src/stt/whisper.ts
1652
+ var STT2 = class extends WhisperSTT {
1653
+ constructor(opts = {}) {
1654
+ const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
1655
+ if (!key) {
1656
+ throw new Error(
1657
+ "Whisper STT requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
1658
+ );
1659
+ }
1660
+ super(key, opts.model ?? "whisper-1", opts.language, opts.bufferSize);
1661
+ }
1662
+ };
1663
+
1664
+ // src/providers/cartesia-stt.ts
1665
+ import WebSocket3 from "ws";
1666
+ var DEFAULT_BASE_URL = "https://api.cartesia.ai";
1667
+ var API_VERSION = "2025-04-16";
1668
+ var USER_AGENT = "Patter/1.0 (integration=LiveKit-port; provider=Cartesia)";
1669
+ var KEEPALIVE_INTERVAL_MS = 3e4;
1670
+ var CONNECT_TIMEOUT_MS = 1e4;
1671
+ var MAX_CALLBACKS = 10;
1672
+ var CartesiaSTT = class {
1673
+ constructor(apiKey, options = {}) {
1674
+ this.apiKey = apiKey;
1675
+ this.options = options;
1676
+ if (!apiKey) {
1677
+ throw new Error("CartesiaSTT requires a non-empty apiKey");
1678
+ }
1679
+ }
1478
1680
  ws = null;
1479
1681
  callbacks = [];
1480
- final = new TokenAccumulator();
1481
1682
  keepaliveTimer = null;
1482
- apiKey;
1483
- model;
1484
- languageHints;
1683
+ /** Cartesia request id — set from the server transcript events. */
1684
+ requestId = "";
1685
+ buildWsUrl() {
1686
+ const opts = this.options;
1687
+ const rawBase = opts.baseUrl ?? DEFAULT_BASE_URL;
1688
+ let base;
1689
+ if (rawBase.startsWith("http://")) {
1690
+ base = `ws://${rawBase.slice("http://".length)}`;
1691
+ } else if (rawBase.startsWith("https://")) {
1692
+ base = `wss://${rawBase.slice("https://".length)}`;
1693
+ } else if (rawBase.startsWith("ws://") || rawBase.startsWith("wss://")) {
1694
+ base = rawBase;
1695
+ } else {
1696
+ base = `wss://${rawBase}`;
1697
+ }
1698
+ const language = opts.language ?? "en";
1699
+ const params = new URLSearchParams({
1700
+ model: opts.model ?? "ink-whisper",
1701
+ sample_rate: String(opts.sampleRate ?? 16e3),
1702
+ encoding: opts.encoding ?? "pcm_s16le",
1703
+ cartesia_version: API_VERSION,
1704
+ api_key: this.apiKey,
1705
+ language
1706
+ });
1707
+ return `${base}/stt/websocket?${params.toString()}`;
1708
+ }
1709
+ async connect() {
1710
+ const url = this.buildWsUrl();
1711
+ this.ws = new WebSocket3(url, {
1712
+ headers: { "User-Agent": USER_AGENT }
1713
+ });
1714
+ await new Promise((resolve, reject) => {
1715
+ const timer = setTimeout(
1716
+ () => reject(new Error("Cartesia STT connect timeout")),
1717
+ CONNECT_TIMEOUT_MS
1718
+ );
1719
+ this.ws.once("open", () => {
1720
+ clearTimeout(timer);
1721
+ resolve();
1722
+ });
1723
+ this.ws.once("error", (err) => {
1724
+ clearTimeout(timer);
1725
+ reject(err);
1726
+ });
1727
+ });
1728
+ this.ws.on("message", (raw) => {
1729
+ let event;
1730
+ try {
1731
+ event = JSON.parse(raw.toString());
1732
+ } catch {
1733
+ return;
1734
+ }
1735
+ this.handleEvent(event);
1736
+ });
1737
+ this.keepaliveTimer = setInterval(() => {
1738
+ if (this.ws && this.ws.readyState === WebSocket3.OPEN) {
1739
+ try {
1740
+ this.ws.ping();
1741
+ } catch {
1742
+ }
1743
+ }
1744
+ }, KEEPALIVE_INTERVAL_MS);
1745
+ }
1746
+ handleEvent(event) {
1747
+ const type = event.type;
1748
+ if (type === "transcript") {
1749
+ const text = (event.text ?? "").trim();
1750
+ const isFinal = Boolean(event.is_final);
1751
+ if (!text && !isFinal) return;
1752
+ if (event.request_id) {
1753
+ this.requestId = event.request_id;
1754
+ }
1755
+ if (!text) return;
1756
+ const confidence = Number(event.probability ?? 1);
1757
+ this.emit({ text, isFinal, confidence });
1758
+ return;
1759
+ }
1760
+ if (type === "error") {
1761
+ getLogger().error(`Cartesia STT error: ${event.message ?? "unknown"}`);
1762
+ return;
1763
+ }
1764
+ }
1765
+ emit(transcript) {
1766
+ for (const cb of this.callbacks) {
1767
+ cb(transcript);
1768
+ }
1769
+ }
1770
+ sendAudio(audio) {
1771
+ if (!this.ws || this.ws.readyState !== WebSocket3.OPEN) return;
1772
+ this.ws.send(audio);
1773
+ }
1774
+ onTranscript(callback) {
1775
+ if (this.callbacks.length >= MAX_CALLBACKS) {
1776
+ getLogger().warn(
1777
+ "CartesiaSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback."
1778
+ );
1779
+ this.callbacks[this.callbacks.length - 1] = callback;
1780
+ return;
1781
+ }
1782
+ this.callbacks.push(callback);
1783
+ }
1784
+ close() {
1785
+ if (this.keepaliveTimer) {
1786
+ clearInterval(this.keepaliveTimer);
1787
+ this.keepaliveTimer = null;
1788
+ }
1789
+ if (this.ws) {
1790
+ try {
1791
+ this.ws.send("finalize");
1792
+ } catch {
1793
+ }
1794
+ this.ws.close();
1795
+ this.ws = null;
1796
+ }
1797
+ }
1798
+ };
1799
+
1800
+ // src/stt/cartesia.ts
1801
+ var STT3 = class extends CartesiaSTT {
1802
+ constructor(opts = {}) {
1803
+ const key = opts.apiKey ?? process.env.CARTESIA_API_KEY;
1804
+ if (!key) {
1805
+ throw new Error(
1806
+ "Cartesia STT requires an apiKey. Pass { apiKey: '...' } or set CARTESIA_API_KEY in the environment."
1807
+ );
1808
+ }
1809
+ super(key, {
1810
+ model: opts.model,
1811
+ language: opts.language,
1812
+ encoding: opts.encoding,
1813
+ sampleRate: opts.sampleRate,
1814
+ baseUrl: opts.baseUrl
1815
+ });
1816
+ }
1817
+ };
1818
+
1819
+ // src/providers/soniox-stt.ts
1820
+ import WebSocket4 from "ws";
1821
+ var SONIOX_WS_URL = "wss://stt-rt.soniox.com/transcribe-websocket";
1822
+ var KEEPALIVE_MESSAGE = '{"type": "keepalive"}';
1823
+ var END_TOKEN = "<end>";
1824
+ var FINALIZED_TOKEN = "<fin>";
1825
+ var KEEPALIVE_INTERVAL_MS2 = 5e3;
1826
+ function isEndToken(token) {
1827
+ return token.text === END_TOKEN || token.text === FINALIZED_TOKEN;
1828
+ }
1829
+ var TokenAccumulator = class {
1830
+ text = "";
1831
+ confSum = 0;
1832
+ confCount = 0;
1833
+ update(token) {
1834
+ if (token.text) {
1835
+ this.text += token.text;
1836
+ }
1837
+ if (typeof token.confidence === "number") {
1838
+ this.confSum += token.confidence;
1839
+ this.confCount += 1;
1840
+ }
1841
+ }
1842
+ get confidence() {
1843
+ return this.confCount === 0 ? 0 : this.confSum / this.confCount;
1844
+ }
1845
+ reset() {
1846
+ this.text = "";
1847
+ this.confSum = 0;
1848
+ this.confCount = 0;
1849
+ }
1850
+ get raw() {
1851
+ return { sum: this.confSum, count: this.confCount };
1852
+ }
1853
+ };
1854
+ var SonioxSTT = class _SonioxSTT {
1855
+ ws = null;
1856
+ callbacks = [];
1857
+ final = new TokenAccumulator();
1858
+ keepaliveTimer = null;
1859
+ apiKey;
1860
+ model;
1861
+ languageHints;
1485
1862
  languageHintsStrict;
1486
1863
  sampleRate;
1487
1864
  numChannels;
@@ -1536,7 +1913,7 @@ var SonioxSTT = class _SonioxSTT {
1536
1913
  return config;
1537
1914
  }
1538
1915
  async connect() {
1539
- this.ws = new WebSocket3(this.baseUrl);
1916
+ this.ws = new WebSocket4(this.baseUrl);
1540
1917
  await new Promise((resolve, reject) => {
1541
1918
  const timer = setTimeout(() => reject(new Error("Soniox connect timeout")), 1e4);
1542
1919
  this.ws.once("open", () => {
@@ -1555,13 +1932,13 @@ var SonioxSTT = class _SonioxSTT {
1555
1932
  getLogger().error(`SonioxSTT WebSocket error: ${String(err)}`);
1556
1933
  });
1557
1934
  this.keepaliveTimer = setInterval(() => {
1558
- if (this.ws && this.ws.readyState === WebSocket3.OPEN) {
1935
+ if (this.ws && this.ws.readyState === WebSocket4.OPEN) {
1559
1936
  try {
1560
1937
  this.ws.send(KEEPALIVE_MESSAGE);
1561
1938
  } catch {
1562
1939
  }
1563
1940
  }
1564
- }, KEEPALIVE_INTERVAL_MS);
1941
+ }, KEEPALIVE_INTERVAL_MS2);
1565
1942
  }
1566
1943
  clearKeepalive() {
1567
1944
  if (this.keepaliveTimer) {
@@ -1628,7 +2005,7 @@ var SonioxSTT = class _SonioxSTT {
1628
2005
  }
1629
2006
  }
1630
2007
  sendAudio(audio) {
1631
- if (!this.ws || this.ws.readyState !== WebSocket3.OPEN) return;
2008
+ if (!this.ws || this.ws.readyState !== WebSocket4.OPEN) return;
1632
2009
  if (audio.length === 0) return;
1633
2010
  this.ws.send(audio);
1634
2011
  }
@@ -1658,12 +2035,27 @@ var SonioxSTT = class _SonioxSTT {
1658
2035
  }
1659
2036
  };
1660
2037
 
2038
+ // src/stt/soniox.ts
2039
+ var STT4 = class extends SonioxSTT {
2040
+ constructor(opts = {}) {
2041
+ const key = opts.apiKey ?? process.env.SONIOX_API_KEY;
2042
+ if (!key) {
2043
+ throw new Error(
2044
+ "Soniox STT requires an apiKey. Pass { apiKey: '...' } or set SONIOX_API_KEY in the environment."
2045
+ );
2046
+ }
2047
+ const { apiKey: _ignored, ...rest } = opts;
2048
+ void _ignored;
2049
+ super(key, rest);
2050
+ }
2051
+ };
2052
+
1661
2053
  // src/providers/assemblyai-stt.ts
1662
- import WebSocket4 from "ws";
1663
- var DEFAULT_BASE_URL = "wss://streaming.assemblyai.com";
2054
+ import WebSocket5 from "ws";
2055
+ var DEFAULT_BASE_URL2 = "wss://streaming.assemblyai.com";
1664
2056
  var DEFAULT_MIN_TURN_SILENCE_MS = 100;
1665
- var CONNECT_TIMEOUT_MS = 1e4;
1666
- var MAX_CALLBACKS = 10;
2057
+ var CONNECT_TIMEOUT_MS2 = 1e4;
2058
+ var MAX_CALLBACKS2 = 10;
1667
2059
  var AssemblyAISTT = class _AssemblyAISTT {
1668
2060
  constructor(apiKey, options = {}) {
1669
2061
  this.apiKey = apiKey;
@@ -1726,12 +2118,12 @@ var AssemblyAISTT = class _AssemblyAISTT {
1726
2118
  params.set(key, String(value));
1727
2119
  }
1728
2120
  }
1729
- const base = opts.baseUrl ?? DEFAULT_BASE_URL;
2121
+ const base = opts.baseUrl ?? DEFAULT_BASE_URL2;
1730
2122
  return `${base}/v3/ws?${params.toString()}`;
1731
2123
  }
1732
2124
  async connect() {
1733
2125
  const url = this.buildUrl();
1734
- this.ws = new WebSocket4(url, {
2126
+ this.ws = new WebSocket5(url, {
1735
2127
  headers: {
1736
2128
  Authorization: this.apiKey,
1737
2129
  "Content-Type": "application/json",
@@ -1741,7 +2133,7 @@ var AssemblyAISTT = class _AssemblyAISTT {
1741
2133
  await new Promise((resolve, reject) => {
1742
2134
  const timer = setTimeout(
1743
2135
  () => reject(new Error("AssemblyAI connect timeout")),
1744
- CONNECT_TIMEOUT_MS
2136
+ CONNECT_TIMEOUT_MS2
1745
2137
  );
1746
2138
  this.ws.once("open", () => {
1747
2139
  clearTimeout(timer);
@@ -1801,11 +2193,11 @@ var AssemblyAISTT = class _AssemblyAISTT {
1801
2193
  }
1802
2194
  }
1803
2195
  sendAudio(audio) {
1804
- if (!this.ws || this.ws.readyState !== WebSocket4.OPEN) return;
2196
+ if (!this.ws || this.ws.readyState !== WebSocket5.OPEN) return;
1805
2197
  this.ws.send(audio);
1806
2198
  }
1807
2199
  onTranscript(callback) {
1808
- if (this.callbacks.length >= MAX_CALLBACKS) {
2200
+ if (this.callbacks.length >= MAX_CALLBACKS2) {
1809
2201
  getLogger().warn(
1810
2202
  "AssemblyAISTT: maximum of 10 onTranscript callbacks reached; replacing the last callback."
1811
2203
  );
@@ -1834,139 +2226,294 @@ function averageConfidence(words) {
1834
2226
  return total / words.length;
1835
2227
  }
1836
2228
 
1837
- // src/providers/cartesia-stt.ts
1838
- import WebSocket5 from "ws";
1839
- var DEFAULT_BASE_URL2 = "https://api.cartesia.ai";
1840
- var API_VERSION = "2025-04-16";
1841
- var USER_AGENT = "Patter/1.0 (integration=LiveKit-port; provider=Cartesia)";
1842
- var KEEPALIVE_INTERVAL_MS2 = 3e4;
1843
- var CONNECT_TIMEOUT_MS2 = 1e4;
1844
- var MAX_CALLBACKS2 = 10;
1845
- var CartesiaSTT = class {
1846
- constructor(apiKey, options = {}) {
1847
- this.apiKey = apiKey;
1848
- this.options = options;
1849
- if (!apiKey) {
1850
- throw new Error("CartesiaSTT requires a non-empty apiKey");
2229
+ // src/stt/assemblyai.ts
2230
+ var STT5 = class extends AssemblyAISTT {
2231
+ constructor(opts = {}) {
2232
+ const key = opts.apiKey ?? process.env.ASSEMBLYAI_API_KEY;
2233
+ if (!key) {
2234
+ throw new Error(
2235
+ "AssemblyAI STT requires an apiKey. Pass { apiKey: '...' } or set ASSEMBLYAI_API_KEY in the environment."
2236
+ );
1851
2237
  }
2238
+ const { apiKey: _ignored, ...rest } = opts;
2239
+ void _ignored;
2240
+ super(key, rest);
1852
2241
  }
1853
- ws = null;
1854
- callbacks = [];
1855
- keepaliveTimer = null;
1856
- /** Cartesia request id — set from the server transcript events. */
1857
- requestId = "";
1858
- buildWsUrl() {
1859
- const opts = this.options;
1860
- const rawBase = opts.baseUrl ?? DEFAULT_BASE_URL2;
1861
- let base;
1862
- if (rawBase.startsWith("http://")) {
1863
- base = `ws://${rawBase.slice("http://".length)}`;
1864
- } else if (rawBase.startsWith("https://")) {
1865
- base = `wss://${rawBase.slice("https://".length)}`;
1866
- } else if (rawBase.startsWith("ws://") || rawBase.startsWith("wss://")) {
1867
- base = rawBase;
1868
- } else {
1869
- base = `wss://${rawBase}`;
2242
+ };
2243
+
2244
+ // src/providers/elevenlabs-tts.ts
2245
+ var ELEVENLABS_BASE_URL = "https://api.elevenlabs.io/v1";
2246
+ var ELEVENLABS_VOICE_ID_BY_NAME = {
2247
+ rachel: "21m00Tcm4TlvDq8ikWAM",
2248
+ drew: "29vD33N1CtxCmqQRPOHJ",
2249
+ clyde: "2EiwWnXFnvU5JabPnv8n",
2250
+ paul: "5Q0t7uMcjvnagumLfvZi",
2251
+ domi: "AZnzlk1XvdvUeBnXmlld",
2252
+ dave: "CYw3kZ02Hs0563khs1Fj",
2253
+ fin: "D38z5RcWu1voky8WS1ja",
2254
+ bella: "EXAVITQu4vr4xnSDxMaL",
2255
+ antoni: "ErXwobaYiN019PkySvjV",
2256
+ thomas: "GBv7mTt0atIp3Br8iCZE",
2257
+ charlie: "IKne3meq5aSn9XLyUdCD",
2258
+ george: "JBFqnCBsd6RMkjVDRZzb",
2259
+ emily: "LcfcDJNUP1GQjkzn1xUU",
2260
+ elli: "MF3mGyEYCl7XYWbV9V6O",
2261
+ callum: "N2lVS1w4EtoT3dr4eOWO",
2262
+ patrick: "ODq5zmih8GrVes37Dizd",
2263
+ harry: "SOYHLrjzK2X1ezoPC6cr",
2264
+ liam: "TX3LPaxmHKxFdv7VOQHJ",
2265
+ dorothy: "ThT5KcBeYPX3keUQqHPh",
2266
+ josh: "TxGEqnHWrfWFTfGW9XjX",
2267
+ arnold: "VR6AewLTigWG4xSOukaG",
2268
+ charlotte: "XB0fDUnXU5powFXDhCwa",
2269
+ matilda: "XrExE9yKIg1WjnnlVkGX",
2270
+ matthew: "Yko7PKHZNXotIFUBG7I9",
2271
+ james: "ZQe5CZNOzWyzPSCn5a3c",
2272
+ joseph: "Zlb1dXrM653N07WRdFW3",
2273
+ jeremy: "bVMeCyTHy58xNoL34h3p",
2274
+ michael: "flq6f7yk4E4fJM5XTYuZ",
2275
+ ethan: "g5CIjZEefAph4nQFvHAz",
2276
+ gigi: "jBpfuIE2acCO8z3wKNLl",
2277
+ freya: "jsCqWAovK2LkecY7zXl4",
2278
+ brian: "nPczCjzI2devNBz1zQrb",
2279
+ grace: "oWAxZDx7w5VEj9dCyTzz",
2280
+ daniel: "onwK4e9ZLuTAKqWW03F9",
2281
+ lily: "pFZP5JQG7iQjIQuC4Bku",
2282
+ serena: "pMsXgVXv3BLzUgSXRplE",
2283
+ adam: "pNInz6obpgDQGcFmaJgB",
2284
+ nicole: "piTKgcLEGmPE4e6mEKli",
2285
+ bill: "pqHfZKP75CvOlQylNhV4",
2286
+ jessie: "t0jbNlBVZ17f02VDIeMI",
2287
+ ryan: "wViXBPUzp2ZZixB1xQuM",
2288
+ sam: "yoZ06aMxZJJ28mfd3POQ",
2289
+ glinda: "z9fAnlkpzviPz146aGWa",
2290
+ giovanni: "zcAOhNBS3c14rBihAFp1",
2291
+ mimi: "zrHiDhphv9ZnVXBqCLjz",
2292
+ alloy: "21m00Tcm4TlvDq8ikWAM"
2293
+ };
2294
+ var VOICE_ID_PATTERN = /^[A-Za-z0-9]{20}$/;
2295
+ function resolveVoiceId(voice) {
2296
+ if (!voice) return voice;
2297
+ if (VOICE_ID_PATTERN.test(voice)) return voice;
2298
+ return ELEVENLABS_VOICE_ID_BY_NAME[voice.toLowerCase()] ?? voice;
2299
+ }
2300
+ var ElevenLabsTTS = class {
2301
+ constructor(apiKey, voiceId = "21m00Tcm4TlvDq8ikWAM", modelId = "eleven_turbo_v2_5", outputFormat = "pcm_16000") {
2302
+ this.apiKey = apiKey;
2303
+ this.modelId = modelId;
2304
+ this.outputFormat = outputFormat;
2305
+ this.voiceId = resolveVoiceId(voiceId);
2306
+ }
2307
+ voiceId;
2308
+ /**
2309
+ * Synthesise text to speech and return the full audio as a single Buffer.
2310
+ *
2311
+ * For large chunks (or when latency matters) call `synthesizeStream` instead.
2312
+ */
2313
+ async synthesize(text) {
2314
+ const chunks = [];
2315
+ for await (const chunk of this.synthesizeStream(text)) {
2316
+ chunks.push(chunk);
1870
2317
  }
1871
- const language = opts.language ?? "en";
1872
- const params = new URLSearchParams({
1873
- model: opts.model ?? "ink-whisper",
1874
- sample_rate: String(opts.sampleRate ?? 16e3),
1875
- encoding: opts.encoding ?? "pcm_s16le",
1876
- cartesia_version: API_VERSION,
1877
- api_key: this.apiKey,
1878
- language
1879
- });
1880
- return `${base}/stt/websocket?${params.toString()}`;
2318
+ return Buffer.concat(chunks);
1881
2319
  }
1882
- async connect() {
1883
- const url = this.buildWsUrl();
1884
- this.ws = new WebSocket5(url, {
1885
- headers: { "User-Agent": USER_AGENT }
1886
- });
1887
- await new Promise((resolve, reject) => {
1888
- const timer = setTimeout(
1889
- () => reject(new Error("Cartesia STT connect timeout")),
1890
- CONNECT_TIMEOUT_MS2
1891
- );
1892
- this.ws.once("open", () => {
1893
- clearTimeout(timer);
1894
- resolve();
1895
- });
1896
- this.ws.once("error", (err) => {
1897
- clearTimeout(timer);
1898
- reject(err);
1899
- });
1900
- });
1901
- this.ws.on("message", (raw) => {
1902
- let event;
1903
- try {
1904
- event = JSON.parse(raw.toString());
1905
- } catch {
1906
- return;
1907
- }
1908
- this.handleEvent(event);
2320
+ /**
2321
+ * Synthesise text and yield audio chunks as they arrive (streaming).
2322
+ *
2323
+ * The yielded buffers are raw PCM at 16 kHz (or whatever `outputFormat` is
2324
+ * configured to).
2325
+ */
2326
+ async *synthesizeStream(text) {
2327
+ const url = `${ELEVENLABS_BASE_URL}/text-to-speech/${encodeURIComponent(this.voiceId)}/stream?output_format=${encodeURIComponent(this.outputFormat)}`;
2328
+ const response = await fetch(url, {
2329
+ method: "POST",
2330
+ headers: {
2331
+ "xi-api-key": this.apiKey,
2332
+ "Content-Type": "application/json"
2333
+ },
2334
+ body: JSON.stringify({ text, model_id: this.modelId }),
2335
+ signal: AbortSignal.timeout(3e4)
1909
2336
  });
1910
- this.keepaliveTimer = setInterval(() => {
1911
- if (this.ws && this.ws.readyState === WebSocket5.OPEN) {
1912
- try {
1913
- this.ws.ping();
1914
- } catch {
2337
+ if (!response.ok) {
2338
+ const body = await response.text();
2339
+ throw new Error(`ElevenLabs TTS error ${response.status}: ${body}`);
2340
+ }
2341
+ if (!response.body) {
2342
+ throw new Error("ElevenLabs TTS: no response body");
2343
+ }
2344
+ const reader = response.body.getReader();
2345
+ try {
2346
+ while (true) {
2347
+ const { done, value } = await reader.read();
2348
+ if (done) break;
2349
+ if (value && value.length > 0) {
2350
+ yield Buffer.from(value);
1915
2351
  }
1916
2352
  }
1917
- }, KEEPALIVE_INTERVAL_MS2);
1918
- }
1919
- handleEvent(event) {
1920
- const type = event.type;
1921
- if (type === "transcript") {
1922
- const text = (event.text ?? "").trim();
1923
- const isFinal = Boolean(event.is_final);
1924
- if (!text && !isFinal) return;
1925
- if (event.request_id) {
1926
- this.requestId = event.request_id;
1927
- }
1928
- if (!text) return;
1929
- const confidence = Number(event.probability ?? 1);
1930
- this.emit({ text, isFinal, confidence });
1931
- return;
1932
- }
1933
- if (type === "error") {
1934
- getLogger().error(`Cartesia STT error: ${event.message ?? "unknown"}`);
1935
- return;
2353
+ } finally {
2354
+ if (typeof reader.cancel === "function") await reader.cancel().catch(() => {
2355
+ });
2356
+ reader.releaseLock();
1936
2357
  }
1937
2358
  }
1938
- emit(transcript) {
1939
- for (const cb of this.callbacks) {
1940
- cb(transcript);
2359
+ };
2360
+
2361
+ // src/tts/elevenlabs.ts
2362
+ var TTS = class extends ElevenLabsTTS {
2363
+ constructor(opts = {}) {
2364
+ const key = opts.apiKey ?? process.env.ELEVENLABS_API_KEY;
2365
+ if (!key) {
2366
+ throw new Error(
2367
+ "ElevenLabs TTS requires an apiKey. Pass { apiKey: '...' } or set ELEVENLABS_API_KEY in the environment."
2368
+ );
1941
2369
  }
2370
+ super(
2371
+ key,
2372
+ opts.voiceId ?? "21m00Tcm4TlvDq8ikWAM",
2373
+ opts.modelId ?? "eleven_turbo_v2_5",
2374
+ opts.outputFormat ?? "pcm_16000"
2375
+ );
1942
2376
  }
1943
- sendAudio(audio) {
1944
- if (!this.ws || this.ws.readyState !== WebSocket5.OPEN) return;
1945
- this.ws.send(audio);
2377
+ };
2378
+
2379
+ // src/providers/openai-tts.ts
2380
+ var OPENAI_TTS_URL = "https://api.openai.com/v1/audio/speech";
2381
+ var OpenAITTS = class _OpenAITTS {
2382
+ constructor(apiKey, voice = "alloy", model = "tts-1") {
2383
+ this.apiKey = apiKey;
2384
+ this.voice = voice;
2385
+ this.model = model;
1946
2386
  }
1947
- onTranscript(callback) {
1948
- if (this.callbacks.length >= MAX_CALLBACKS2) {
1949
- getLogger().warn(
1950
- "CartesiaSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback."
1951
- );
1952
- this.callbacks[this.callbacks.length - 1] = callback;
1953
- return;
2387
+ /**
2388
+ * Synthesise text to speech and return the full audio as a single Buffer.
2389
+ *
2390
+ * For large chunks (or when latency matters) call `synthesizeStream` instead.
2391
+ */
2392
+ async synthesize(text) {
2393
+ const chunks = [];
2394
+ for await (const chunk of this.synthesizeStream(text)) {
2395
+ chunks.push(chunk);
1954
2396
  }
1955
- this.callbacks.push(callback);
2397
+ return Buffer.concat(chunks);
1956
2398
  }
1957
- close() {
1958
- if (this.keepaliveTimer) {
1959
- clearInterval(this.keepaliveTimer);
1960
- this.keepaliveTimer = null;
2399
+ /**
2400
+ * Synthesise text and yield audio chunks as they arrive (streaming).
2401
+ *
2402
+ * OpenAI returns 24 kHz PCM16; each chunk is resampled to 16 kHz before
2403
+ * yielding so the output is ready for telephony pipelines.
2404
+ *
2405
+ * The resampler carries state (buffered samples + odd trailing byte)
2406
+ * between chunks — without that state cross-chunk sample alignment drifts
2407
+ * and the caller hears pops / dropped audio (BUG #23, mirror of the
2408
+ * Python `audioop.ratecv` fix).
2409
+ */
2410
+ async *synthesizeStream(text) {
2411
+ const response = await fetch(OPENAI_TTS_URL, {
2412
+ method: "POST",
2413
+ headers: {
2414
+ "Authorization": `Bearer ${this.apiKey}`,
2415
+ "Content-Type": "application/json"
2416
+ },
2417
+ body: JSON.stringify({
2418
+ model: this.model,
2419
+ input: text,
2420
+ voice: this.voice,
2421
+ response_format: "pcm"
2422
+ }),
2423
+ signal: AbortSignal.timeout(3e4)
2424
+ });
2425
+ if (!response.ok) {
2426
+ const body = await response.text();
2427
+ throw new Error(`OpenAI TTS error ${response.status}: ${body}`);
1961
2428
  }
1962
- if (this.ws) {
1963
- try {
1964
- this.ws.send("finalize");
1965
- } catch {
2429
+ if (!response.body) {
2430
+ throw new Error("OpenAI TTS: no response body");
2431
+ }
2432
+ const ctx = { carryByte: null, leftover: [] };
2433
+ const reader = response.body.getReader();
2434
+ try {
2435
+ while (true) {
2436
+ const { done, value } = await reader.read();
2437
+ if (done) break;
2438
+ if (value && value.length > 0) {
2439
+ const out = _OpenAITTS.resampleStreaming(Buffer.from(value), ctx);
2440
+ if (out.length > 0) yield out;
2441
+ }
1966
2442
  }
1967
- this.ws.close();
1968
- this.ws = null;
2443
+ if (ctx.leftover.length > 0) {
2444
+ const tail = Buffer.alloc(ctx.leftover.length * 2);
2445
+ for (let i = 0; i < ctx.leftover.length; i++) {
2446
+ tail.writeInt16LE(ctx.leftover[i], i * 2);
2447
+ }
2448
+ yield tail;
2449
+ }
2450
+ } finally {
2451
+ if (typeof reader.cancel === "function") await reader.cancel().catch(() => {
2452
+ });
2453
+ reader.releaseLock();
2454
+ }
2455
+ }
2456
+ /**
2457
+ * Streaming 24 kHz → 16 kHz resampler (PCM16-LE). Maintains cross-chunk
2458
+ * state so the 3:2 pattern doesn't reset at every network read.
2459
+ */
2460
+ static resampleStreaming(audio, ctx) {
2461
+ let buf;
2462
+ if (ctx.carryByte !== null) {
2463
+ buf = Buffer.concat([Buffer.from([ctx.carryByte]), audio]);
2464
+ ctx.carryByte = null;
2465
+ } else {
2466
+ buf = audio;
2467
+ }
2468
+ if (buf.length % 2 === 1) {
2469
+ ctx.carryByte = buf[buf.length - 1];
2470
+ buf = buf.subarray(0, buf.length - 1);
2471
+ }
2472
+ if (buf.length === 0 && ctx.leftover.length === 0) {
2473
+ return Buffer.alloc(0);
2474
+ }
2475
+ const sampleCount = buf.length / 2;
2476
+ const samples = ctx.leftover.slice();
2477
+ for (let i2 = 0; i2 < sampleCount; i2++) {
2478
+ samples.push(buf.readInt16LE(i2 * 2));
2479
+ }
2480
+ const out = [];
2481
+ let i = 0;
2482
+ while (i + 2 < samples.length) {
2483
+ out.push(samples[i]);
2484
+ out.push(Math.trunc((samples[i + 1] + samples[i + 2]) / 2));
2485
+ i += 3;
2486
+ }
2487
+ ctx.leftover = samples.slice(i);
2488
+ const buffer = Buffer.alloc(out.length * 2);
2489
+ for (let j = 0; j < out.length; j++) {
2490
+ buffer.writeInt16LE(out[j], j * 2);
2491
+ }
2492
+ return buffer;
2493
+ }
2494
+ /** @deprecated use {@link resampleStreaming} with persistent state. */
2495
+ static resample24kTo16k(audio) {
2496
+ const ctx = { carryByte: null, leftover: [] };
2497
+ const out = _OpenAITTS.resampleStreaming(audio, ctx);
2498
+ if (ctx.leftover.length === 0) return out;
2499
+ const tail = Buffer.alloc(ctx.leftover.length * 2);
2500
+ for (let i = 0; i < ctx.leftover.length; i++) {
2501
+ tail.writeInt16LE(ctx.leftover[i], i * 2);
2502
+ }
2503
+ return Buffer.concat([out, tail]);
2504
+ }
2505
+ };
2506
+
2507
+ // src/tts/openai.ts
2508
+ var TTS2 = class extends OpenAITTS {
2509
+ constructor(opts = {}) {
2510
+ const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
2511
+ if (!key) {
2512
+ throw new Error(
2513
+ "OpenAI TTS requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
2514
+ );
1969
2515
  }
2516
+ super(key, opts.voice ?? "alloy", opts.model ?? "tts-1");
1970
2517
  }
1971
2518
  };
1972
2519
 
@@ -2068,6 +2615,21 @@ var CartesiaTTS = class {
2068
2615
  }
2069
2616
  };
2070
2617
 
2618
+ // src/tts/cartesia.ts
2619
+ var TTS3 = class extends CartesiaTTS {
2620
+ constructor(opts = {}) {
2621
+ const key = opts.apiKey ?? process.env.CARTESIA_API_KEY;
2622
+ if (!key) {
2623
+ throw new Error(
2624
+ "Cartesia TTS requires an apiKey. Pass { apiKey: '...' } or set CARTESIA_API_KEY in the environment."
2625
+ );
2626
+ }
2627
+ const { apiKey: _ignored, ...rest } = opts;
2628
+ void _ignored;
2629
+ super(key, rest);
2630
+ }
2631
+ };
2632
+
2071
2633
  // src/providers/rime-tts.ts
2072
2634
  var RIME_BASE_URL = "https://users.rime.ai/v1/rime-tts";
2073
2635
  var ARCANA_MODEL_TIMEOUT_MS = 60 * 4 * 1e3;
@@ -2195,6 +2757,21 @@ var RimeTTS = class {
2195
2757
  }
2196
2758
  };
2197
2759
 
2760
+ // src/tts/rime.ts
2761
+ var TTS4 = class extends RimeTTS {
2762
+ constructor(opts = {}) {
2763
+ const key = opts.apiKey ?? process.env.RIME_API_KEY;
2764
+ if (!key) {
2765
+ throw new Error(
2766
+ "Rime TTS requires an apiKey. Pass { apiKey: '...' } or set RIME_API_KEY in the environment."
2767
+ );
2768
+ }
2769
+ const { apiKey: _ignored, ...rest } = opts;
2770
+ void _ignored;
2771
+ super(key, rest);
2772
+ }
2773
+ };
2774
+
2198
2775
  // src/providers/lmnt-tts.ts
2199
2776
  var LMNT_BASE_URL = "https://api.lmnt.com/v1/ai/speech/bytes";
2200
2777
  var LMNTTTS = class {
@@ -2273,6 +2850,729 @@ var LMNTTTS = class {
2273
2850
  }
2274
2851
  };
2275
2852
 
2853
+ // src/tts/lmnt.ts
2854
+ var TTS5 = class extends LMNTTTS {
2855
+ constructor(opts = {}) {
2856
+ const key = opts.apiKey ?? process.env.LMNT_API_KEY;
2857
+ if (!key) {
2858
+ throw new Error(
2859
+ "LMNT TTS requires an apiKey. Pass { apiKey: '...' } or set LMNT_API_KEY in the environment."
2860
+ );
2861
+ }
2862
+ const { apiKey: _ignored, ...rest } = opts;
2863
+ void _ignored;
2864
+ super(key, rest);
2865
+ }
2866
+ };
2867
+
2868
+ // src/llm/openai.ts
2869
+ var LLM = class extends OpenAILLMProvider {
2870
+ constructor(opts = {}) {
2871
+ const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
2872
+ if (!key) {
2873
+ throw new Error(
2874
+ "OpenAI LLM requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY."
2875
+ );
2876
+ }
2877
+ super(key, opts.model ?? "gpt-4o-mini");
2878
+ }
2879
+ };
2880
+
2881
+ // src/providers/anthropic-llm.ts
2882
+ var DEFAULT_ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
2883
+ var DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
2884
+ var DEFAULT_MODEL = "claude-3-5-sonnet-20241022";
2885
+ var DEFAULT_MAX_TOKENS = 1024;
2886
+ var AnthropicLLMProvider = class {
2887
+ apiKey;
2888
+ model;
2889
+ maxTokens;
2890
+ temperature;
2891
+ url;
2892
+ anthropicVersion;
2893
+ constructor(options) {
2894
+ if (!options.apiKey) {
2895
+ throw new Error(
2896
+ "Anthropic API key is required. Pass it via { apiKey } or set the ANTHROPIC_API_KEY environment variable before constructing the provider."
2897
+ );
2898
+ }
2899
+ this.apiKey = options.apiKey;
2900
+ this.model = options.model ?? DEFAULT_MODEL;
2901
+ this.maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
2902
+ this.temperature = options.temperature;
2903
+ this.url = options.baseUrl ?? DEFAULT_ANTHROPIC_URL;
2904
+ this.anthropicVersion = options.anthropicVersion ?? DEFAULT_ANTHROPIC_VERSION;
2905
+ }
2906
+ async *stream(messages, tools) {
2907
+ const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
2908
+ const anthropicTools = tools ? toAnthropicTools(tools) : null;
2909
+ const body = {
2910
+ model: this.model,
2911
+ messages: anthropicMessages,
2912
+ max_tokens: this.maxTokens,
2913
+ stream: true
2914
+ };
2915
+ if (system) body.system = system;
2916
+ if (anthropicTools && anthropicTools.length > 0) body.tools = anthropicTools;
2917
+ if (this.temperature !== void 0) body.temperature = this.temperature;
2918
+ const response = await fetch(this.url, {
2919
+ method: "POST",
2920
+ headers: {
2921
+ "Content-Type": "application/json",
2922
+ "x-api-key": this.apiKey,
2923
+ "anthropic-version": this.anthropicVersion
2924
+ },
2925
+ body: JSON.stringify(body),
2926
+ signal: AbortSignal.timeout(3e4)
2927
+ });
2928
+ if (!response.ok) {
2929
+ const errText = await response.text();
2930
+ getLogger().error(`Anthropic API error: ${response.status} ${errText}`);
2931
+ return;
2932
+ }
2933
+ const reader = response.body?.getReader();
2934
+ if (!reader) return;
2935
+ const decoder = new TextDecoder();
2936
+ let buffer = "";
2937
+ const toolIndexByBlock = /* @__PURE__ */ new Map();
2938
+ const toolIdByBlock = /* @__PURE__ */ new Map();
2939
+ let nextIndex = 0;
2940
+ while (true) {
2941
+ const { done, value } = await reader.read();
2942
+ if (done) break;
2943
+ buffer += decoder.decode(value, { stream: true });
2944
+ const lines = buffer.split("\n");
2945
+ buffer = lines.pop() || "";
2946
+ for (const line of lines) {
2947
+ const trimmed = line.trim();
2948
+ if (!trimmed.startsWith("data: ")) continue;
2949
+ const data = trimmed.slice(6);
2950
+ if (!data || data === "[DONE]") continue;
2951
+ let event;
2952
+ try {
2953
+ event = JSON.parse(data);
2954
+ } catch {
2955
+ continue;
2956
+ }
2957
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
2958
+ const blockIdx = event.index ?? 0;
2959
+ const toolId = event.content_block.id ?? "";
2960
+ const toolName = event.content_block.name ?? "";
2961
+ const patterIndex = nextIndex++;
2962
+ toolIndexByBlock.set(blockIdx, patterIndex);
2963
+ toolIdByBlock.set(blockIdx, toolId);
2964
+ yield {
2965
+ type: "tool_call",
2966
+ index: patterIndex,
2967
+ id: toolId,
2968
+ name: toolName,
2969
+ arguments: ""
2970
+ };
2971
+ continue;
2972
+ }
2973
+ if (event.type === "content_block_delta") {
2974
+ if (event.delta?.type === "text_delta" && event.delta.text) {
2975
+ yield { type: "text", content: event.delta.text };
2976
+ continue;
2977
+ }
2978
+ if (event.delta?.type === "input_json_delta" && event.delta.partial_json) {
2979
+ const blockIdx = event.index ?? 0;
2980
+ const patterIndex = toolIndexByBlock.get(blockIdx);
2981
+ if (patterIndex !== void 0) {
2982
+ yield {
2983
+ type: "tool_call",
2984
+ index: patterIndex,
2985
+ id: toolIdByBlock.get(blockIdx),
2986
+ arguments: event.delta.partial_json
2987
+ };
2988
+ }
2989
+ }
2990
+ }
2991
+ }
2992
+ }
2993
+ yield { type: "done" };
2994
+ }
2995
+ };
2996
+ function toAnthropicTools(tools) {
2997
+ return tools.map((t) => {
2998
+ const fn = t.function ?? t;
2999
+ return {
3000
+ name: String(fn.name ?? ""),
3001
+ description: String(fn.description ?? ""),
3002
+ input_schema: fn.parameters ?? { type: "object", properties: {} }
3003
+ };
3004
+ });
3005
+ }
3006
+ function toAnthropicMessages(messages) {
3007
+ const systemParts = [];
3008
+ const out = [];
3009
+ for (const rawMsg of messages) {
3010
+ const role = rawMsg.role;
3011
+ if (role === "system") {
3012
+ if (typeof rawMsg.content === "string" && rawMsg.content) {
3013
+ systemParts.push(rawMsg.content);
3014
+ }
3015
+ continue;
3016
+ }
3017
+ if (role === "user") {
3018
+ if (typeof rawMsg.content === "string") {
3019
+ out.push({ role: "user", content: rawMsg.content });
3020
+ } else if (rawMsg.content) {
3021
+ out.push({ role: "user", content: rawMsg.content });
3022
+ }
3023
+ continue;
3024
+ }
3025
+ if (role === "assistant") {
3026
+ const blocks = [];
3027
+ if (typeof rawMsg.content === "string" && rawMsg.content) {
3028
+ blocks.push({ type: "text", text: rawMsg.content });
3029
+ }
3030
+ for (const tc of rawMsg.tool_calls ?? []) {
3031
+ let args = {};
3032
+ try {
3033
+ args = JSON.parse(tc.function?.arguments ?? "{}");
3034
+ } catch {
3035
+ args = {};
3036
+ }
3037
+ blocks.push({
3038
+ type: "tool_use",
3039
+ id: tc.id ?? "",
3040
+ name: tc.function?.name ?? "",
3041
+ input: args
3042
+ });
3043
+ }
3044
+ if (blocks.length > 0) {
3045
+ out.push({ role: "assistant", content: blocks });
3046
+ }
3047
+ continue;
3048
+ }
3049
+ if (role === "tool") {
3050
+ const contentStr = typeof rawMsg.content === "string" ? rawMsg.content : JSON.stringify(rawMsg.content);
3051
+ out.push({
3052
+ role: "user",
3053
+ content: [
3054
+ {
3055
+ type: "tool_result",
3056
+ tool_use_id: rawMsg.tool_call_id ?? "",
3057
+ content: contentStr
3058
+ }
3059
+ ]
3060
+ });
3061
+ continue;
3062
+ }
3063
+ }
3064
+ return { system: systemParts.join("\n\n"), messages: out };
3065
+ }
3066
+
3067
+ // src/llm/anthropic.ts
3068
+ var LLM2 = class extends AnthropicLLMProvider {
3069
+ constructor(opts = {}) {
3070
+ const key = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
3071
+ if (!key) {
3072
+ throw new Error(
3073
+ "Anthropic LLM requires an apiKey. Pass { apiKey: 'sk-ant-...' } or set ANTHROPIC_API_KEY."
3074
+ );
3075
+ }
3076
+ super({
3077
+ apiKey: key,
3078
+ model: opts.model,
3079
+ maxTokens: opts.maxTokens,
3080
+ temperature: opts.temperature,
3081
+ baseUrl: opts.baseUrl,
3082
+ anthropicVersion: opts.anthropicVersion
3083
+ });
3084
+ }
3085
+ };
3086
+
3087
+ // src/providers/groq-llm.ts
3088
+ var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
3089
+ var DEFAULT_MODEL2 = "llama-3.3-70b-versatile";
3090
+ var GroqLLMProvider = class {
3091
+ apiKey;
3092
+ model;
3093
+ baseUrl;
3094
+ constructor(options) {
3095
+ if (!options.apiKey) {
3096
+ throw new Error(
3097
+ "Groq API key is required. Pass it via { apiKey } or read GROQ_API_KEY from the environment."
3098
+ );
3099
+ }
3100
+ this.apiKey = options.apiKey;
3101
+ this.model = options.model ?? DEFAULT_MODEL2;
3102
+ this.baseUrl = options.baseUrl ?? GROQ_BASE_URL;
3103
+ }
3104
+ async *stream(messages, tools) {
3105
+ const body = {
3106
+ model: this.model,
3107
+ messages,
3108
+ stream: true
3109
+ };
3110
+ if (tools) body.tools = tools;
3111
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
3112
+ method: "POST",
3113
+ headers: {
3114
+ "Content-Type": "application/json",
3115
+ Authorization: `Bearer ${this.apiKey}`
3116
+ },
3117
+ body: JSON.stringify(body),
3118
+ signal: AbortSignal.timeout(3e4)
3119
+ });
3120
+ if (!response.ok) {
3121
+ const errText = await response.text();
3122
+ getLogger().error(`Groq API error: ${response.status} ${errText}`);
3123
+ return;
3124
+ }
3125
+ yield* parseOpenAISseStream(response);
3126
+ }
3127
+ };
3128
+ async function* parseOpenAISseStream(response) {
3129
+ const reader = response.body?.getReader();
3130
+ if (!reader) return;
3131
+ const decoder = new TextDecoder();
3132
+ let buffer = "";
3133
+ while (true) {
3134
+ const { done, value } = await reader.read();
3135
+ if (done) break;
3136
+ buffer += decoder.decode(value, { stream: true });
3137
+ const lines = buffer.split("\n");
3138
+ buffer = lines.pop() || "";
3139
+ for (const line of lines) {
3140
+ const trimmed = line.trim();
3141
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
3142
+ const data = trimmed.slice(6);
3143
+ if (data === "[DONE]") continue;
3144
+ let chunk;
3145
+ try {
3146
+ chunk = JSON.parse(data);
3147
+ } catch {
3148
+ continue;
3149
+ }
3150
+ const delta = chunk.choices?.[0]?.delta;
3151
+ if (!delta) continue;
3152
+ if (delta.content) {
3153
+ yield { type: "text", content: delta.content };
3154
+ }
3155
+ if (delta.tool_calls) {
3156
+ for (const tc of delta.tool_calls) {
3157
+ yield {
3158
+ type: "tool_call",
3159
+ index: tc.index,
3160
+ id: tc.id,
3161
+ name: tc.function?.name,
3162
+ arguments: tc.function?.arguments
3163
+ };
3164
+ }
3165
+ }
3166
+ }
3167
+ }
3168
+ }
3169
+
3170
+ // src/llm/groq.ts
3171
+ var LLM3 = class extends GroqLLMProvider {
3172
+ constructor(opts = {}) {
3173
+ const key = opts.apiKey ?? process.env.GROQ_API_KEY;
3174
+ if (!key) {
3175
+ throw new Error(
3176
+ "Groq LLM requires an apiKey. Pass { apiKey: 'gsk_...' } or set GROQ_API_KEY."
3177
+ );
3178
+ }
3179
+ super({
3180
+ apiKey: key,
3181
+ model: opts.model,
3182
+ baseUrl: opts.baseUrl
3183
+ });
3184
+ }
3185
+ };
3186
+
3187
+ // src/providers/cerebras-llm.ts
3188
+ var CEREBRAS_BASE_URL = "https://api.cerebras.ai/v1";
3189
+ var DEFAULT_MODEL3 = "llama3.1-8b";
3190
+ var CerebrasLLMProvider = class {
3191
+ apiKey;
3192
+ model;
3193
+ baseUrl;
3194
+ gzipCompression;
3195
+ constructor(options) {
3196
+ if (!options.apiKey) {
3197
+ throw new Error(
3198
+ "Cerebras API key is required. Pass it via { apiKey } or read CEREBRAS_API_KEY from the environment."
3199
+ );
3200
+ }
3201
+ this.apiKey = options.apiKey;
3202
+ this.model = options.model ?? DEFAULT_MODEL3;
3203
+ this.baseUrl = options.baseUrl ?? CEREBRAS_BASE_URL;
3204
+ this.gzipCompression = options.gzipCompression ?? false;
3205
+ }
3206
+ async *stream(messages, tools) {
3207
+ const body = {
3208
+ model: this.model,
3209
+ messages,
3210
+ stream: true
3211
+ };
3212
+ if (tools) body.tools = tools;
3213
+ const headers = {
3214
+ "Content-Type": "application/json",
3215
+ Authorization: `Bearer ${this.apiKey}`
3216
+ };
3217
+ let payload = JSON.stringify(body);
3218
+ if (this.gzipCompression) {
3219
+ const compressed = await gzipEncode(payload);
3220
+ if (compressed) {
3221
+ payload = compressed;
3222
+ headers["Content-Encoding"] = "gzip";
3223
+ }
3224
+ }
3225
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
3226
+ method: "POST",
3227
+ headers,
3228
+ body: payload,
3229
+ signal: AbortSignal.timeout(3e4)
3230
+ });
3231
+ if (!response.ok) {
3232
+ const errText = await response.text();
3233
+ getLogger().error(`Cerebras API error: ${response.status} ${errText}`);
3234
+ return;
3235
+ }
3236
+ yield* parseOpenAISseStream(response);
3237
+ }
3238
+ };
3239
+ async function gzipEncode(data) {
3240
+ const CompressionCtor = globalThis.CompressionStream;
3241
+ if (!CompressionCtor) return null;
3242
+ const stream = new CompressionCtor("gzip");
3243
+ const writer = stream.writable.getWriter();
3244
+ const encoder = new TextEncoder();
3245
+ await writer.write(encoder.encode(data));
3246
+ await writer.close();
3247
+ const chunks = [];
3248
+ const reader = stream.readable.getReader();
3249
+ while (true) {
3250
+ const { done, value } = await reader.read();
3251
+ if (done) break;
3252
+ if (value) chunks.push(value);
3253
+ }
3254
+ const total = chunks.reduce((n, c) => n + c.length, 0);
3255
+ const out = new Uint8Array(total);
3256
+ let offset = 0;
3257
+ for (const c of chunks) {
3258
+ out.set(c, offset);
3259
+ offset += c.length;
3260
+ }
3261
+ return out;
3262
+ }
3263
+
3264
+ // src/llm/cerebras.ts
3265
+ var LLM4 = class extends CerebrasLLMProvider {
3266
+ constructor(opts = {}) {
3267
+ const key = opts.apiKey ?? process.env.CEREBRAS_API_KEY;
3268
+ if (!key) {
3269
+ throw new Error(
3270
+ "Cerebras LLM requires an apiKey. Pass { apiKey: 'csk-...' } or set CEREBRAS_API_KEY."
3271
+ );
3272
+ }
3273
+ super({
3274
+ apiKey: key,
3275
+ model: opts.model,
3276
+ baseUrl: opts.baseUrl,
3277
+ gzipCompression: opts.gzipCompression
3278
+ });
3279
+ }
3280
+ };
3281
+
3282
+ // src/providers/google-llm.ts
3283
+ var DEFAULT_MODEL4 = "gemini-2.5-flash";
3284
+ var DEFAULT_BASE_URL3 = "https://generativelanguage.googleapis.com/v1beta";
3285
+ var GoogleLLMProvider = class {
3286
+ apiKey;
3287
+ model;
3288
+ baseUrl;
3289
+ temperature;
3290
+ maxOutputTokens;
3291
+ constructor(options) {
3292
+ if (!options.apiKey) {
3293
+ throw new Error(
3294
+ "Google API key is required. Pass it via { apiKey } or read GOOGLE_API_KEY from the environment."
3295
+ );
3296
+ }
3297
+ this.apiKey = options.apiKey;
3298
+ this.model = options.model ?? DEFAULT_MODEL4;
3299
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL3;
3300
+ this.temperature = options.temperature;
3301
+ this.maxOutputTokens = options.maxOutputTokens;
3302
+ }
3303
+ async *stream(messages, tools) {
3304
+ const { systemInstruction, contents } = toGeminiContents(messages);
3305
+ const geminiTools = tools ? toGeminiTools(tools) : null;
3306
+ const body = { contents };
3307
+ if (systemInstruction) {
3308
+ body.systemInstruction = { role: "system", parts: [{ text: systemInstruction }] };
3309
+ }
3310
+ if (geminiTools) body.tools = geminiTools;
3311
+ const generationConfig = {};
3312
+ if (this.temperature !== void 0) generationConfig.temperature = this.temperature;
3313
+ if (this.maxOutputTokens !== void 0)
3314
+ generationConfig.maxOutputTokens = this.maxOutputTokens;
3315
+ if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
3316
+ const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:streamGenerateContent?alt=sse&key=${encodeURIComponent(this.apiKey)}`;
3317
+ const response = await fetch(url, {
3318
+ method: "POST",
3319
+ headers: { "Content-Type": "application/json" },
3320
+ body: JSON.stringify(body),
3321
+ signal: AbortSignal.timeout(3e4)
3322
+ });
3323
+ if (!response.ok) {
3324
+ const errText = await response.text();
3325
+ getLogger().error(`Gemini API error: ${response.status} ${errText}`);
3326
+ return;
3327
+ }
3328
+ const reader = response.body?.getReader();
3329
+ if (!reader) return;
3330
+ const decoder = new TextDecoder();
3331
+ let buffer = "";
3332
+ let nextIndex = 0;
3333
+ while (true) {
3334
+ const { done, value } = await reader.read();
3335
+ if (done) break;
3336
+ buffer += decoder.decode(value, { stream: true });
3337
+ const lines = buffer.split("\n");
3338
+ buffer = lines.pop() || "";
3339
+ for (const line of lines) {
3340
+ const trimmed = line.trim();
3341
+ if (!trimmed.startsWith("data: ")) continue;
3342
+ const data = trimmed.slice(6);
3343
+ if (!data) continue;
3344
+ let payload;
3345
+ try {
3346
+ payload = JSON.parse(data);
3347
+ } catch {
3348
+ continue;
3349
+ }
3350
+ const candidate = payload.candidates?.[0];
3351
+ const parts = candidate?.content?.parts ?? [];
3352
+ for (const part of parts) {
3353
+ if (part.functionCall) {
3354
+ const args = part.functionCall.args ?? {};
3355
+ const callId = part.functionCall.id ?? `gemini_call_${nextIndex}`;
3356
+ yield {
3357
+ type: "tool_call",
3358
+ index: nextIndex,
3359
+ id: callId,
3360
+ name: part.functionCall.name ?? "",
3361
+ arguments: JSON.stringify(args)
3362
+ };
3363
+ nextIndex++;
3364
+ continue;
3365
+ }
3366
+ if (part.text) {
3367
+ yield { type: "text", content: part.text };
3368
+ }
3369
+ }
3370
+ }
3371
+ }
3372
+ yield { type: "done" };
3373
+ }
3374
+ };
3375
+ function toGeminiTools(tools) {
3376
+ const functionDeclarations = tools.map((t) => {
3377
+ const fn = t.function ?? t;
3378
+ return {
3379
+ name: String(fn.name ?? ""),
3380
+ description: String(fn.description ?? ""),
3381
+ parameters: fn.parameters ?? { type: "object", properties: {} }
3382
+ };
3383
+ });
3384
+ if (functionDeclarations.length === 0) return [];
3385
+ return [{ functionDeclarations }];
3386
+ }
3387
+ function toGeminiContents(messages) {
3388
+ const systemParts = [];
3389
+ const contents = [];
3390
+ for (const rawMsg of messages) {
3391
+ const role = rawMsg.role;
3392
+ if (role === "system") {
3393
+ if (typeof rawMsg.content === "string" && rawMsg.content) {
3394
+ systemParts.push(rawMsg.content);
3395
+ }
3396
+ continue;
3397
+ }
3398
+ if (role === "user") {
3399
+ if (typeof rawMsg.content === "string" && rawMsg.content) {
3400
+ contents.push({ role: "user", parts: [{ text: rawMsg.content }] });
3401
+ }
3402
+ continue;
3403
+ }
3404
+ if (role === "assistant") {
3405
+ const parts = [];
3406
+ if (typeof rawMsg.content === "string" && rawMsg.content) {
3407
+ parts.push({ text: rawMsg.content });
3408
+ }
3409
+ for (const tc of rawMsg.tool_calls ?? []) {
3410
+ let args = {};
3411
+ try {
3412
+ const parsed = JSON.parse(tc.function?.arguments ?? "{}");
3413
+ if (parsed && typeof parsed === "object") args = parsed;
3414
+ } catch {
3415
+ args = {};
3416
+ }
3417
+ parts.push({
3418
+ functionCall: {
3419
+ name: tc.function?.name ?? "",
3420
+ args,
3421
+ id: tc.id
3422
+ }
3423
+ });
3424
+ }
3425
+ if (parts.length > 0) contents.push({ role: "model", parts });
3426
+ continue;
3427
+ }
3428
+ if (role === "tool") {
3429
+ const raw = rawMsg.content;
3430
+ let response;
3431
+ if (typeof raw === "string") {
3432
+ try {
3433
+ const parsed = JSON.parse(raw);
3434
+ response = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: parsed };
3435
+ } catch {
3436
+ response = { result: raw };
3437
+ }
3438
+ } else {
3439
+ response = raw ?? {};
3440
+ }
3441
+ contents.push({
3442
+ role: "user",
3443
+ parts: [
3444
+ {
3445
+ functionResponse: {
3446
+ name: rawMsg.name ?? rawMsg.tool_call_id ?? "",
3447
+ response,
3448
+ id: rawMsg.tool_call_id
3449
+ }
3450
+ }
3451
+ ]
3452
+ });
3453
+ continue;
3454
+ }
3455
+ }
3456
+ return { systemInstruction: systemParts.join("\n\n"), contents };
3457
+ }
3458
+
3459
+ // src/llm/google.ts
3460
+ var LLM5 = class extends GoogleLLMProvider {
3461
+ constructor(opts = {}) {
3462
+ const key = opts.apiKey ?? process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
3463
+ if (!key) {
3464
+ throw new Error(
3465
+ "Google LLM requires an apiKey. Pass { apiKey: 'AIza...' } or set GEMINI_API_KEY (or GOOGLE_API_KEY)."
3466
+ );
3467
+ }
3468
+ super({
3469
+ apiKey: key,
3470
+ model: opts.model,
3471
+ baseUrl: opts.baseUrl,
3472
+ temperature: opts.temperature,
3473
+ maxOutputTokens: opts.maxOutputTokens
3474
+ });
3475
+ }
3476
+ };
3477
+
3478
+ // src/carriers/twilio.ts
3479
+ var Carrier = class {
3480
+ kind = "twilio";
3481
+ accountSid;
3482
+ authToken;
3483
+ constructor(opts = {}) {
3484
+ const sid = opts.accountSid ?? process.env.TWILIO_ACCOUNT_SID;
3485
+ const tok = opts.authToken ?? process.env.TWILIO_AUTH_TOKEN;
3486
+ if (!sid) {
3487
+ throw new Error(
3488
+ "Twilio carrier requires accountSid. Pass { accountSid: 'AC...' } or set TWILIO_ACCOUNT_SID in the environment."
3489
+ );
3490
+ }
3491
+ if (!tok) {
3492
+ throw new Error(
3493
+ "Twilio carrier requires authToken. Pass { authToken: '...' } or set TWILIO_AUTH_TOKEN in the environment."
3494
+ );
3495
+ }
3496
+ this.accountSid = sid;
3497
+ this.authToken = tok;
3498
+ }
3499
+ };
3500
+
3501
+ // src/carriers/telnyx.ts
3502
+ var Carrier2 = class {
3503
+ kind = "telnyx";
3504
+ apiKey;
3505
+ connectionId;
3506
+ publicKey;
3507
+ constructor(opts = {}) {
3508
+ const key = opts.apiKey ?? process.env.TELNYX_API_KEY;
3509
+ const conn = opts.connectionId ?? process.env.TELNYX_CONNECTION_ID;
3510
+ const pub = opts.publicKey ?? process.env.TELNYX_PUBLIC_KEY;
3511
+ if (!key) {
3512
+ throw new Error(
3513
+ "Telnyx carrier requires apiKey. Pass { apiKey: '...' } or set TELNYX_API_KEY in the environment."
3514
+ );
3515
+ }
3516
+ if (!conn) {
3517
+ throw new Error(
3518
+ "Telnyx carrier requires connectionId. Pass { connectionId: '...' } or set TELNYX_CONNECTION_ID in the environment."
3519
+ );
3520
+ }
3521
+ this.apiKey = key;
3522
+ this.connectionId = conn;
3523
+ this.publicKey = pub;
3524
+ }
3525
+ };
3526
+
3527
+ // src/public-api.ts
3528
+ var DEFAULT_GUARDRAIL_REPLACEMENT = "I'm sorry, I can't respond to that.";
3529
+ var Guardrail = class {
3530
+ name;
3531
+ blockedTerms;
3532
+ check;
3533
+ replacement;
3534
+ constructor(opts) {
3535
+ if (!opts.name) {
3536
+ throw new Error("Guardrail requires a non-empty name.");
3537
+ }
3538
+ this.name = opts.name;
3539
+ if (opts.blockedTerms) this.blockedTerms = opts.blockedTerms;
3540
+ if (opts.check) this.check = opts.check;
3541
+ this.replacement = opts.replacement ?? DEFAULT_GUARDRAIL_REPLACEMENT;
3542
+ }
3543
+ };
3544
+ function guardrail(opts) {
3545
+ return new Guardrail(opts);
3546
+ }
3547
+ var Tool = class {
3548
+ name;
3549
+ description;
3550
+ parameters;
3551
+ handler;
3552
+ webhookUrl;
3553
+ constructor(opts) {
3554
+ if (!opts.name) {
3555
+ throw new Error("Tool requires a non-empty name.");
3556
+ }
3557
+ const hasHandler = typeof opts.handler === "function";
3558
+ const hasWebhook = typeof opts.webhookUrl === "string" && opts.webhookUrl.length > 0;
3559
+ if (!hasHandler && !hasWebhook) {
3560
+ throw new Error("Tool requires either handler or webhookUrl.");
3561
+ }
3562
+ if (hasHandler && hasWebhook) {
3563
+ throw new Error("Tool accepts handler OR webhookUrl, not both.");
3564
+ }
3565
+ this.name = opts.name;
3566
+ this.description = opts.description ?? "";
3567
+ this.parameters = opts.parameters ?? { type: "object", properties: {} };
3568
+ if (hasHandler) this.handler = opts.handler;
3569
+ if (hasWebhook) this.webhookUrl = opts.webhookUrl;
3570
+ }
3571
+ };
3572
+ function tool(opts) {
3573
+ return new Tool(opts);
3574
+ }
3575
+
2276
3576
  // src/chat-context.ts
2277
3577
  import { randomUUID } from "crypto";
2278
3578
  function generateId() {
@@ -2883,31 +4183,40 @@ function isAudioConfig(value) {
2883
4183
  }
2884
4184
  export {
2885
4185
  AllProvidersFailedError,
2886
- AssemblyAISTT,
4186
+ LLM2 as AnthropicLLM,
4187
+ STT5 as AssemblyAISTT,
2887
4188
  AuthenticationError,
2888
4189
  BackgroundAudioPlayer,
2889
4190
  BuiltinAudioClip,
2890
4191
  CallMetricsAccumulator,
2891
- CartesiaSTT,
2892
- CartesiaTTS,
4192
+ STT3 as CartesiaSTT,
4193
+ TTS3 as CartesiaTTS,
4194
+ LLM4 as CerebrasLLM,
2893
4195
  ChatContext,
4196
+ CloudflareTunnel,
2894
4197
  DEFAULT_MIN_SENTENCE_LEN,
2895
4198
  DEFAULT_PRICING,
2896
4199
  DTMF_EVENTS,
2897
- DeepgramSTT,
4200
+ STT as DeepgramSTT,
4201
+ ConvAI as ElevenLabsConvAI,
2898
4202
  ElevenLabsConvAIAdapter,
2899
- ElevenLabsTTS,
4203
+ TTS as ElevenLabsTTS,
2900
4204
  FallbackLLMProvider,
2901
4205
  GEMINI_DEFAULT_INPUT_SR,
2902
4206
  GEMINI_DEFAULT_OUTPUT_SR,
2903
4207
  GeminiLiveAdapter,
4208
+ LLM5 as GoogleLLM,
4209
+ LLM3 as GroqLLM,
4210
+ Guardrail,
2904
4211
  IVRActivity,
2905
4212
  LLMLoop,
2906
- LMNTTTS,
4213
+ TTS5 as LMNTTTS,
2907
4214
  MetricsStore,
4215
+ LLM as OpenAILLM,
2908
4216
  OpenAILLMProvider,
4217
+ Realtime as OpenAIRealtime,
2909
4218
  OpenAIRealtimeAdapter,
2910
- OpenAITTS,
4219
+ TTS2 as OpenAITTS,
2911
4220
  PartialStreamError,
2912
4221
  Patter,
2913
4222
  PatterConnectionError,
@@ -2915,15 +4224,19 @@ export {
2915
4224
  PipelineHookExecutor,
2916
4225
  ProvisionError,
2917
4226
  RemoteMessageHandler,
2918
- RimeTTS,
4227
+ TTS4 as RimeTTS,
2919
4228
  SentenceChunker,
2920
- SonioxSTT,
4229
+ STT4 as SonioxSTT,
4230
+ Static as StaticTunnel,
4231
+ Carrier2 as Telnyx,
2921
4232
  TestSession,
2922
4233
  TfidfLoopDetector,
4234
+ Tool,
4235
+ Carrier as Twilio,
2923
4236
  ULTRAVOX_DEFAULT_API_BASE,
2924
4237
  ULTRAVOX_DEFAULT_SR,
2925
4238
  UltravoxRealtimeAdapter,
2926
- WhisperSTT,
4239
+ STT2 as WhisperSTT,
2927
4240
  builtinClipPath,
2928
4241
  calculateRealtimeCost,
2929
4242
  calculateSttCost,
@@ -2939,6 +4252,7 @@ export {
2939
4252
  filterMarkdown,
2940
4253
  formatDtmf,
2941
4254
  getLogger,
4255
+ guardrail,
2942
4256
  isRemoteUrl,
2943
4257
  isWebSocketUrl,
2944
4258
  makeAuthMiddleware,
@@ -2960,5 +4274,6 @@ export {
2960
4274
  selectSoundFromList,
2961
4275
  setLogger,
2962
4276
  startTunnel,
4277
+ tool,
2963
4278
  whisper
2964
4279
  };