getpatter 0.4.4 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -158
- package/dist/carrier-config-CPG5CROM.mjs +84 -0
- package/dist/{chunk-JO5C35FM.mjs → chunk-AKQFOFLG.mjs} +1 -1
- package/dist/{chunk-O3RQG3NL.mjs → chunk-B6C3KIBG.mjs} +177 -567
- package/dist/index.d.mts +1163 -377
- package/dist/index.d.ts +1163 -377
- package/dist/index.js +2028 -1835
- package/dist/index.mjs +1644 -329
- package/dist/{test-mode-ASSLSQU2.mjs → test-mode-JZMYE5HY.mjs} +1 -1
- package/dist/{tunnel-BL7A7GXW.mjs → tunnel-O7ICMSTP.mjs} +1 -1
- package/package.json +1 -1
- package/dist/lib-4WCAS54J.mjs +0 -830
package/dist/index.mjs
CHANGED
|
@@ -3,25 +3,22 @@ import {
|
|
|
3
3
|
} from "./chunk-AFUYSNDH.mjs";
|
|
4
4
|
import {
|
|
5
5
|
startTunnel
|
|
6
|
-
} from "./chunk-
|
|
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-
|
|
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/
|
|
190
|
-
var
|
|
191
|
-
|
|
186
|
+
// src/engines/openai.ts
|
|
187
|
+
var Realtime = class {
|
|
188
|
+
kind = "openai_realtime";
|
|
192
189
|
apiKey;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
constructor(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
212
|
-
|
|
204
|
+
|
|
205
|
+
// src/engines/elevenlabs.ts
|
|
206
|
+
var ConvAI = class {
|
|
207
|
+
kind = "elevenlabs_convai";
|
|
213
208
|
apiKey;
|
|
209
|
+
agentId;
|
|
214
210
|
voice;
|
|
215
|
-
constructor(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
|
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.
|
|
296
|
-
throw new Error(
|
|
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
|
-
|
|
299
|
-
|
|
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
|
|
303
|
-
|
|
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
|
-
|
|
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(
|
|
323
|
-
throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${
|
|
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 (
|
|
327
|
-
if (!Array.isArray(
|
|
376
|
+
if (working.tools) {
|
|
377
|
+
if (!Array.isArray(working.tools)) {
|
|
328
378
|
throw new TypeError("tools must be an array");
|
|
329
379
|
}
|
|
330
|
-
|
|
331
|
-
if (!
|
|
332
|
-
if (!
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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 (
|
|
365
|
-
const { startTunnel: startTunnel2 } = await import("./tunnel-
|
|
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:
|
|
377
|
-
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
|
|
382
|
-
telnyxKey:
|
|
383
|
-
telnyxConnectionId:
|
|
384
|
-
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-
|
|
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,
|
|
446
|
-
if (
|
|
447
|
-
const telnyxKey =
|
|
448
|
-
const connectionId =
|
|
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 =
|
|
489
|
-
const 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/
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
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
|
-
|
|
1466
|
-
|
|
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
|
-
|
|
1469
|
-
this.
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
-
|
|
1474
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
|
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 ===
|
|
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
|
-
},
|
|
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 !==
|
|
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
|
|
1663
|
-
var
|
|
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
|
|
1666
|
-
var
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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 !==
|
|
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 >=
|
|
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/
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
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
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
|
|
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
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
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
|
-
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
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
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
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
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
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
|
-
|
|
2397
|
+
return Buffer.concat(chunks);
|
|
1956
2398
|
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
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 (
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
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
|
-
|
|
1968
|
-
|
|
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
|
-
|
|
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
|
};
|