getpatter 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-35EVXMGB.mjs → chunk-O3RQG3NL.mjs} +453 -100
- package/dist/cli.js +92 -5
- package/dist/index.d.mts +245 -64
- package/dist/index.d.ts +245 -64
- package/dist/index.js +636 -147
- package/dist/index.mjs +183 -47
- package/dist/{test-mode-RH65MMSP.mjs → test-mode-ASSLSQU2.mjs} +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
resample16kTo8k,
|
|
40
40
|
resample24kTo16k,
|
|
41
41
|
resample8kTo16k
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-O3RQG3NL.mjs";
|
|
43
43
|
import {
|
|
44
44
|
getLogger,
|
|
45
45
|
setLogger
|
|
@@ -191,13 +191,21 @@ var STTConfigImpl = class {
|
|
|
191
191
|
provider;
|
|
192
192
|
apiKey;
|
|
193
193
|
language;
|
|
194
|
-
|
|
194
|
+
options;
|
|
195
|
+
constructor(provider, apiKey, language = "en", options) {
|
|
195
196
|
this.provider = provider;
|
|
196
197
|
this.apiKey = apiKey;
|
|
197
198
|
this.language = language;
|
|
199
|
+
if (options) this.options = options;
|
|
198
200
|
}
|
|
199
201
|
toDict() {
|
|
200
|
-
|
|
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;
|
|
201
209
|
}
|
|
202
210
|
};
|
|
203
211
|
var TTSConfigImpl = class {
|
|
@@ -214,7 +222,15 @@ var TTSConfigImpl = class {
|
|
|
214
222
|
}
|
|
215
223
|
};
|
|
216
224
|
function deepgram(opts) {
|
|
217
|
-
|
|
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);
|
|
218
234
|
}
|
|
219
235
|
function whisper(opts) {
|
|
220
236
|
return new STTConfigImpl("whisper", opts.apiKey, opts.language ?? "en");
|
|
@@ -225,10 +241,41 @@ function elevenlabs(opts) {
|
|
|
225
241
|
function openaiTts(opts) {
|
|
226
242
|
return new TTSConfigImpl("openai", opts.apiKey, opts.voice ?? "alloy");
|
|
227
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
|
+
}
|
|
228
257
|
|
|
229
258
|
// src/client.ts
|
|
230
259
|
var DEFAULT_BACKEND_URL2 = "wss://api.getpatter.com";
|
|
231
260
|
var DEFAULT_REST_URL = "https://api.getpatter.com";
|
|
261
|
+
function sttConfigToDict(cfg) {
|
|
262
|
+
const out = {
|
|
263
|
+
provider: cfg.provider,
|
|
264
|
+
api_key: cfg.apiKey,
|
|
265
|
+
language: cfg.language
|
|
266
|
+
};
|
|
267
|
+
if (cfg.options) out.options = { ...cfg.options };
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
function ttsConfigToDict(cfg) {
|
|
271
|
+
const out = {
|
|
272
|
+
provider: cfg.provider,
|
|
273
|
+
api_key: cfg.apiKey,
|
|
274
|
+
voice: cfg.voice
|
|
275
|
+
};
|
|
276
|
+
if (cfg.options) out.options = { ...cfg.options };
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
232
279
|
var Patter = class {
|
|
233
280
|
apiKey;
|
|
234
281
|
backendUrl;
|
|
@@ -239,7 +286,8 @@ var Patter = class {
|
|
|
239
286
|
embeddedServer = null;
|
|
240
287
|
tunnelHandle = null;
|
|
241
288
|
constructor(options) {
|
|
242
|
-
|
|
289
|
+
const isLocal = "mode" in options && options.mode === "local" || !("apiKey" in options) && ("twilioSid" in options && options.twilioSid || "telnyxKey" in options && options.telnyxKey);
|
|
290
|
+
if (isLocal) {
|
|
243
291
|
const local = options;
|
|
244
292
|
if (!local.phoneNumber) {
|
|
245
293
|
throw new Error("Local mode requires phoneNumber");
|
|
@@ -353,7 +401,7 @@ var Patter = class {
|
|
|
353
401
|
if (this.mode !== "local") {
|
|
354
402
|
throw new Error("test() is only available in local mode");
|
|
355
403
|
}
|
|
356
|
-
const { TestSession: TestSession2 } = await import("./test-mode-
|
|
404
|
+
const { TestSession: TestSession2 } = await import("./test-mode-ASSLSQU2.mjs");
|
|
357
405
|
const session = new TestSession2();
|
|
358
406
|
await session.run({
|
|
359
407
|
agent: opts.agent,
|
|
@@ -399,23 +447,42 @@ var Patter = class {
|
|
|
399
447
|
const telnyxKey = this.localConfig.telnyxKey ?? "";
|
|
400
448
|
const connectionId = this.localConfig.telnyxConnectionId ?? "";
|
|
401
449
|
const streamUrl = `wss://${webhookUrl}/ws/stream/${encodeURIComponent(localOpts.to)}?caller=${encodeURIComponent(phoneNumber)}&callee=${encodeURIComponent(localOpts.to)}`;
|
|
450
|
+
const telnyxPayload = {
|
|
451
|
+
connection_id: connectionId,
|
|
452
|
+
from: phoneNumber,
|
|
453
|
+
to: localOpts.to,
|
|
454
|
+
stream_url: streamUrl,
|
|
455
|
+
stream_track: "both_tracks"
|
|
456
|
+
};
|
|
457
|
+
if (localOpts.ringTimeout !== void 0) {
|
|
458
|
+
telnyxPayload.timeout_secs = Math.max(1, Math.floor(localOpts.ringTimeout));
|
|
459
|
+
}
|
|
402
460
|
const response2 = await fetch("https://api.telnyx.com/v2/calls", {
|
|
403
461
|
method: "POST",
|
|
404
462
|
headers: {
|
|
405
463
|
"Content-Type": "application/json",
|
|
406
464
|
Authorization: `Bearer ${telnyxKey}`
|
|
407
465
|
},
|
|
408
|
-
body: JSON.stringify(
|
|
409
|
-
connection_id: connectionId,
|
|
410
|
-
from: phoneNumber,
|
|
411
|
-
to: localOpts.to,
|
|
412
|
-
stream_url: streamUrl,
|
|
413
|
-
stream_track: "both_tracks"
|
|
414
|
-
})
|
|
466
|
+
body: JSON.stringify(telnyxPayload)
|
|
415
467
|
});
|
|
416
468
|
if (!response2.ok) {
|
|
417
469
|
throw new ProvisionError(`Failed to initiate Telnyx call: ${await response2.text()}`);
|
|
418
470
|
}
|
|
471
|
+
if (this.embeddedServer) {
|
|
472
|
+
try {
|
|
473
|
+
const body = await response2.clone().json();
|
|
474
|
+
const callId = body.data?.call_control_id;
|
|
475
|
+
if (callId) {
|
|
476
|
+
this.embeddedServer.metricsStore.recordCallInitiated({
|
|
477
|
+
call_id: callId,
|
|
478
|
+
caller: phoneNumber,
|
|
479
|
+
callee: localOpts.to,
|
|
480
|
+
direction: "outbound"
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
}
|
|
419
486
|
return;
|
|
420
487
|
}
|
|
421
488
|
const twilioSid = this.localConfig.twilioSid ?? "";
|
|
@@ -427,13 +494,19 @@ var Patter = class {
|
|
|
427
494
|
From: phoneNumber,
|
|
428
495
|
Url: `https://${webhookUrl}/webhooks/twilio/voice`,
|
|
429
496
|
StatusCallback: statusCallbackUrl,
|
|
430
|
-
StatusCallbackMethod: "POST"
|
|
497
|
+
StatusCallbackMethod: "POST",
|
|
498
|
+
// Full lifecycle so the dashboard sees ringing/no-answer/busy/failed
|
|
499
|
+
// transitions even when media never arrives.
|
|
500
|
+
StatusCallbackEvent: "initiated ringing answered completed"
|
|
431
501
|
});
|
|
432
502
|
if (localOpts.machineDetection) {
|
|
433
503
|
params.append("MachineDetection", "DetectMessageEnd");
|
|
434
504
|
params.append("AsyncAmd", "true");
|
|
435
505
|
params.append("AsyncAmdStatusCallback", `https://${webhookUrl}/webhooks/twilio/amd`);
|
|
436
506
|
}
|
|
507
|
+
if (localOpts.ringTimeout !== void 0) {
|
|
508
|
+
params.append("Timeout", String(Math.max(1, Math.floor(localOpts.ringTimeout))));
|
|
509
|
+
}
|
|
437
510
|
if (localOpts.voicemailMessage && this.embeddedServer) {
|
|
438
511
|
this.embeddedServer.voicemailMessage = localOpts.voicemailMessage;
|
|
439
512
|
}
|
|
@@ -448,6 +521,21 @@ var Patter = class {
|
|
|
448
521
|
if (!response.ok) {
|
|
449
522
|
throw new ProvisionError(`Failed to initiate call: ${await response.text()}`);
|
|
450
523
|
}
|
|
524
|
+
if (this.embeddedServer) {
|
|
525
|
+
try {
|
|
526
|
+
const body = await response.clone().json();
|
|
527
|
+
const callSid = body.sid;
|
|
528
|
+
if (callSid) {
|
|
529
|
+
this.embeddedServer.metricsStore.recordCallInitiated({
|
|
530
|
+
call_id: callSid,
|
|
531
|
+
caller: phoneNumber,
|
|
532
|
+
callee: localOpts.to,
|
|
533
|
+
direction: "outbound"
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
}
|
|
451
539
|
return;
|
|
452
540
|
}
|
|
453
541
|
const cloudOpts = options;
|
|
@@ -530,11 +618,15 @@ var Patter = class {
|
|
|
530
618
|
const data = await response.json();
|
|
531
619
|
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 }));
|
|
532
620
|
}
|
|
533
|
-
// Provider helpers
|
|
621
|
+
// Provider helpers — mirror the Python classmethod factories so callers can
|
|
622
|
+
// write ``Patter.deepgram({ apiKey })`` without importing the top-level.
|
|
534
623
|
static deepgram = deepgram;
|
|
535
624
|
static whisper = whisper;
|
|
536
625
|
static elevenlabs = elevenlabs;
|
|
537
626
|
static openaiTts = openaiTts;
|
|
627
|
+
static cartesia = cartesia;
|
|
628
|
+
static rime = rime;
|
|
629
|
+
static lmnt = lmnt;
|
|
538
630
|
static guardrail(opts) {
|
|
539
631
|
return {
|
|
540
632
|
name: opts.name,
|
|
@@ -600,8 +692,8 @@ var Patter = class {
|
|
|
600
692
|
provider,
|
|
601
693
|
provider_credentials: credentials,
|
|
602
694
|
country,
|
|
603
|
-
stt_config: stt?.
|
|
604
|
-
tts_config: tts?.
|
|
695
|
+
stt_config: stt ? stt.toDict?.() ?? sttConfigToDict(stt) : null,
|
|
696
|
+
tts_config: tts ? tts.toDict?.() ?? ttsConfigToDict(tts) : null
|
|
605
697
|
})
|
|
606
698
|
});
|
|
607
699
|
if (response.status === 409) return;
|
|
@@ -719,6 +811,37 @@ var FallbackLLMProvider = class {
|
|
|
719
811
|
}
|
|
720
812
|
}
|
|
721
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Async-friendly disposer. Parity with Python's ``FallbackLLMProvider.aclose()``
|
|
816
|
+
* — safe to call multiple times, returns a resolved Promise once all probe
|
|
817
|
+
* timers are cleared. Prefer this in async contexts so awaiting the
|
|
818
|
+
* shutdown integrates naturally with the owning lifecycle.
|
|
819
|
+
*/
|
|
820
|
+
async aclose() {
|
|
821
|
+
this.destroy();
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Explicit-resource-management hook so callers can write
|
|
825
|
+
* ``await using fallback = new FallbackLLMProvider([...])`` and have
|
|
826
|
+
* background probe timers cleared automatically when the block exits.
|
|
827
|
+
* Mirrors Python's ``async with FallbackLLMProvider(...)``.
|
|
828
|
+
*/
|
|
829
|
+
async [Symbol.asyncDispose]() {
|
|
830
|
+
await this.aclose();
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Stream only the text deltas, flattening the chunk envelope. Parity with
|
|
834
|
+
* Python's ``FallbackLLMProvider.complete_stream``. Tool-call and done
|
|
835
|
+
* markers are filtered out so callers can concatenate the yielded strings
|
|
836
|
+
* directly.
|
|
837
|
+
*/
|
|
838
|
+
async *completeStream(messages, tools) {
|
|
839
|
+
for await (const chunk of this.stream(messages, tools)) {
|
|
840
|
+
if (chunk.type === "text") {
|
|
841
|
+
yield chunk.content ?? "";
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
722
845
|
// -----------------------------------------------------------------------
|
|
723
846
|
// LLMProvider implementation
|
|
724
847
|
// -----------------------------------------------------------------------
|
|
@@ -1235,13 +1358,37 @@ function wrapCallback(cb) {
|
|
|
1235
1358
|
}
|
|
1236
1359
|
};
|
|
1237
1360
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1361
|
+
function scheduleCron(cron, callback) {
|
|
1362
|
+
let cancelled = false;
|
|
1363
|
+
let task = null;
|
|
1364
|
+
const jobId = `cron-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1365
|
+
loadCron().then((cm) => {
|
|
1366
|
+
if (cancelled) return;
|
|
1367
|
+
if (!cm.validate(cron)) {
|
|
1368
|
+
throw new Error(`Invalid cron expression: ${cron}`);
|
|
1369
|
+
}
|
|
1370
|
+
task = cm.schedule(cron, wrapCallback(callback));
|
|
1371
|
+
}).catch((err) => getLogger().error(`scheduleCron failed: ${String(err)}`));
|
|
1372
|
+
return {
|
|
1373
|
+
jobId,
|
|
1374
|
+
cancel() {
|
|
1375
|
+
if (cancelled) return;
|
|
1376
|
+
cancelled = true;
|
|
1377
|
+
if (task) {
|
|
1378
|
+
try {
|
|
1379
|
+
task.stop();
|
|
1380
|
+
} catch {
|
|
1381
|
+
}
|
|
1382
|
+
try {
|
|
1383
|
+
task.destroy?.();
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
},
|
|
1388
|
+
get pending() {
|
|
1389
|
+
return !cancelled;
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1245
1392
|
}
|
|
1246
1393
|
function scheduleOnce(at, callback) {
|
|
1247
1394
|
const delayMs = at.getTime() - Date.now();
|
|
@@ -1263,8 +1410,18 @@ function scheduleOnce(at, callback) {
|
|
|
1263
1410
|
}
|
|
1264
1411
|
};
|
|
1265
1412
|
}
|
|
1266
|
-
function scheduleInterval(
|
|
1267
|
-
|
|
1413
|
+
function scheduleInterval(intervalOrOpts, callback) {
|
|
1414
|
+
let intervalMs;
|
|
1415
|
+
if (typeof intervalOrOpts === "number") {
|
|
1416
|
+
intervalMs = intervalOrOpts;
|
|
1417
|
+
} else if (intervalOrOpts.intervalMs !== void 0) {
|
|
1418
|
+
intervalMs = intervalOrOpts.intervalMs;
|
|
1419
|
+
} else if (intervalOrOpts.seconds !== void 0) {
|
|
1420
|
+
intervalMs = intervalOrOpts.seconds * 1e3;
|
|
1421
|
+
} else {
|
|
1422
|
+
throw new Error("scheduleInterval requires seconds or intervalMs");
|
|
1423
|
+
}
|
|
1424
|
+
if (intervalMs <= 0) throw new Error("interval must be positive");
|
|
1268
1425
|
let cancelled = false;
|
|
1269
1426
|
const wrapped = wrapCallback(callback);
|
|
1270
1427
|
const timer = setInterval(() => {
|
|
@@ -1281,27 +1438,6 @@ function scheduleInterval(intervalMs, callback) {
|
|
|
1281
1438
|
}
|
|
1282
1439
|
};
|
|
1283
1440
|
}
|
|
1284
|
-
function makeHandle(jobId, task) {
|
|
1285
|
-
let cancelled = false;
|
|
1286
|
-
return {
|
|
1287
|
-
jobId,
|
|
1288
|
-
cancel() {
|
|
1289
|
-
if (cancelled) return;
|
|
1290
|
-
cancelled = true;
|
|
1291
|
-
try {
|
|
1292
|
-
task.stop();
|
|
1293
|
-
} catch {
|
|
1294
|
-
}
|
|
1295
|
-
try {
|
|
1296
|
-
task.destroy?.();
|
|
1297
|
-
} catch {
|
|
1298
|
-
}
|
|
1299
|
-
},
|
|
1300
|
-
get pending() {
|
|
1301
|
-
return !cancelled;
|
|
1302
|
-
}
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
1441
|
|
|
1306
1442
|
// src/providers/soniox-stt.ts
|
|
1307
1443
|
import WebSocket3 from "ws";
|