getpatter 0.6.2 → 0.6.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/README.md +5 -4
- package/dist/{carrier-config-4ZKVYAWV.mjs → carrier-config-7YGNRBPO.mjs} +60 -12
- package/dist/{chunk-R2T4JABZ.mjs → chunk-3VVATR6A.mjs} +8 -6
- package/dist/{chunk-LE63CSOB.mjs → chunk-7IIV3BY4.mjs} +1679 -228
- package/dist/{chunk-CL2U3YET.mjs → chunk-BO227NTF.mjs} +271 -54
- package/dist/cli.js +63 -20
- package/dist/dashboard/ui.html +10 -10
- package/dist/index.d.mts +4217 -3319
- package/dist/index.d.ts +4217 -3319
- package/dist/index.js +2815 -705
- package/dist/index.mjs +760 -392
- package/dist/{openai-realtime-2-CNFARP25.mjs → openai-realtime-2-L5EKAAUH.mjs} +1 -1
- package/dist/{silero-vad-LNDFGIY7.mjs → silero-vad-RGF5HCIR.mjs} +1 -1
- package/dist/{test-mode-RS57BDM6.mjs → test-mode-4QLLWYVV.mjs} +2 -2
- package/package.json +2 -1
- package/src/dashboard/ui.html +10 -10
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
AuthenticationError,
|
|
6
6
|
CallMetricsAccumulator,
|
|
7
|
+
Carrier,
|
|
7
8
|
DEFAULT_MIN_SENTENCE_LEN,
|
|
8
9
|
DEFAULT_PRICING,
|
|
9
10
|
DeepgramModel,
|
|
@@ -18,9 +19,11 @@ import {
|
|
|
18
19
|
OpenAILLMProvider,
|
|
19
20
|
PRICING_LAST_UPDATED,
|
|
20
21
|
PRICING_VERSION,
|
|
22
|
+
PatterConfigError,
|
|
21
23
|
PatterConnectionError,
|
|
22
24
|
PatterError,
|
|
23
25
|
PipelineHookExecutor,
|
|
26
|
+
PlivoAdapter,
|
|
24
27
|
PricingUnit,
|
|
25
28
|
ProvisionError,
|
|
26
29
|
RateLimitError,
|
|
@@ -50,9 +53,11 @@ import {
|
|
|
50
53
|
mergePricing,
|
|
51
54
|
mountApi,
|
|
52
55
|
mountDashboard,
|
|
56
|
+
openclawConsult,
|
|
57
|
+
openclawPostCallNotifier,
|
|
53
58
|
resolveLogRoot,
|
|
54
59
|
startSpan
|
|
55
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-7IIV3BY4.mjs";
|
|
56
61
|
import {
|
|
57
62
|
OpenAIRealtime2Adapter,
|
|
58
63
|
OpenAIRealtimeAdapter,
|
|
@@ -71,8 +76,9 @@ import {
|
|
|
71
76
|
pcm16ToMulaw,
|
|
72
77
|
resample16kTo8k,
|
|
73
78
|
resample24kTo16k,
|
|
74
|
-
resample8kTo16k
|
|
75
|
-
|
|
79
|
+
resample8kTo16k,
|
|
80
|
+
validateRealtimeTurnDetection
|
|
81
|
+
} from "./chunk-BO227NTF.mjs";
|
|
76
82
|
import {
|
|
77
83
|
MinWordsStrategy,
|
|
78
84
|
evaluateStrategies,
|
|
@@ -87,7 +93,7 @@ import {
|
|
|
87
93
|
} from "./chunk-6GR5MHHQ.mjs";
|
|
88
94
|
import {
|
|
89
95
|
SileroVAD
|
|
90
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-3VVATR6A.mjs";
|
|
91
97
|
import {
|
|
92
98
|
__dirname,
|
|
93
99
|
__require,
|
|
@@ -109,6 +115,9 @@ var Realtime = class {
|
|
|
109
115
|
voice;
|
|
110
116
|
reasoningEffort;
|
|
111
117
|
inputAudioTranscriptionModel;
|
|
118
|
+
noiseReduction;
|
|
119
|
+
turnDetection;
|
|
120
|
+
gateResponseOnTranscript;
|
|
112
121
|
constructor(opts = {}) {
|
|
113
122
|
const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
|
|
114
123
|
if (!key) {
|
|
@@ -116,11 +125,20 @@ var Realtime = class {
|
|
|
116
125
|
"OpenAI Realtime requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
|
|
117
126
|
);
|
|
118
127
|
}
|
|
128
|
+
if (opts.noiseReduction !== void 0 && opts.noiseReduction !== "near_field" && opts.noiseReduction !== "far_field") {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`noiseReduction must be 'near_field' or 'far_field', got ${JSON.stringify(opts.noiseReduction)}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
validateRealtimeTurnDetection(opts.turnDetection);
|
|
119
134
|
this.apiKey = key;
|
|
120
135
|
this.model = opts.model ?? "gpt-realtime-mini";
|
|
121
136
|
this.voice = opts.voice ?? "alloy";
|
|
122
137
|
this.reasoningEffort = opts.reasoningEffort;
|
|
123
138
|
this.inputAudioTranscriptionModel = opts.inputAudioTranscriptionModel;
|
|
139
|
+
this.noiseReduction = opts.noiseReduction;
|
|
140
|
+
this.turnDetection = opts.turnDetection;
|
|
141
|
+
this.gateResponseOnTranscript = opts.gateResponseOnTranscript;
|
|
124
142
|
}
|
|
125
143
|
};
|
|
126
144
|
|
|
@@ -133,6 +151,9 @@ var Realtime2 = class {
|
|
|
133
151
|
voice;
|
|
134
152
|
reasoningEffort;
|
|
135
153
|
inputAudioTranscriptionModel;
|
|
154
|
+
noiseReduction;
|
|
155
|
+
turnDetection;
|
|
156
|
+
gateResponseOnTranscript;
|
|
136
157
|
constructor(opts = {}) {
|
|
137
158
|
const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
|
|
138
159
|
if (!key) {
|
|
@@ -140,11 +161,20 @@ var Realtime2 = class {
|
|
|
140
161
|
"OpenAI Realtime 2 requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
|
|
141
162
|
);
|
|
142
163
|
}
|
|
164
|
+
if (opts.noiseReduction !== void 0 && opts.noiseReduction !== "near_field" && opts.noiseReduction !== "far_field") {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`noiseReduction must be 'near_field' or 'far_field', got ${JSON.stringify(opts.noiseReduction)}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
validateRealtimeTurnDetection(opts.turnDetection);
|
|
143
170
|
this.apiKey = key;
|
|
144
171
|
this.model = opts.model ?? "gpt-realtime-2";
|
|
145
172
|
this.voice = opts.voice ?? "alloy";
|
|
146
173
|
this.reasoningEffort = opts.reasoningEffort;
|
|
147
174
|
this.inputAudioTranscriptionModel = opts.inputAudioTranscriptionModel;
|
|
175
|
+
this.noiseReduction = opts.noiseReduction;
|
|
176
|
+
this.turnDetection = opts.turnDetection;
|
|
177
|
+
this.gateResponseOnTranscript = opts.gateResponseOnTranscript;
|
|
148
178
|
}
|
|
149
179
|
};
|
|
150
180
|
|
|
@@ -571,7 +601,7 @@ function resolvePersistRoot(persist) {
|
|
|
571
601
|
if (typeof persist === "string") return resolveLogRoot(persist);
|
|
572
602
|
const envRoot = resolveLogRoot();
|
|
573
603
|
if (envRoot !== null) return envRoot;
|
|
574
|
-
return
|
|
604
|
+
return null;
|
|
575
605
|
}
|
|
576
606
|
function closeParkedConnections(slot) {
|
|
577
607
|
if (slot.stt) {
|
|
@@ -799,7 +829,7 @@ var Patter = class {
|
|
|
799
829
|
}
|
|
800
830
|
if (!options.carrier) {
|
|
801
831
|
throw new Error(
|
|
802
|
-
"Local mode requires a `carrier` instance. Pass `carrier: new Twilio({...})` or `carrier: new
|
|
832
|
+
"Local mode requires a `carrier` instance. Pass `carrier: new Twilio({...})`, `carrier: new Telnyx({...})` or `carrier: new Plivo({...})`."
|
|
803
833
|
);
|
|
804
834
|
}
|
|
805
835
|
const carrier = options.carrier;
|
|
@@ -855,7 +885,12 @@ var Patter = class {
|
|
|
855
885
|
...working,
|
|
856
886
|
provider: "openai_realtime",
|
|
857
887
|
model: working.model ?? engine.model,
|
|
858
|
-
voice: working.voice ?? engine.voice
|
|
888
|
+
voice: working.voice ?? engine.voice,
|
|
889
|
+
// Explicit agent() kwargs win over the engine marker value
|
|
890
|
+
// (same precedence as Python: explicit kwarg > engine > default).
|
|
891
|
+
openaiRealtimeNoiseReduction: working.openaiRealtimeNoiseReduction ?? engine.noiseReduction,
|
|
892
|
+
realtimeTurnDetection: working.realtimeTurnDetection ?? engine.turnDetection,
|
|
893
|
+
openaiRealtimeGateResponseOnTranscript: working.openaiRealtimeGateResponseOnTranscript ?? engine.gateResponseOnTranscript
|
|
859
894
|
};
|
|
860
895
|
if (!this.localConfig.openaiKey) {
|
|
861
896
|
this.localConfig = { ...this.localConfig, openaiKey: engine.apiKey };
|
|
@@ -880,6 +915,11 @@ var Patter = class {
|
|
|
880
915
|
throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${working.provider}'`);
|
|
881
916
|
}
|
|
882
917
|
}
|
|
918
|
+
if (working.consult && working.provider === "elevenlabs_convai") {
|
|
919
|
+
getLogger().warn(
|
|
920
|
+
"consult is set but provider is ElevenLabs ConvAI; the consult tool is only injected in Realtime and Pipeline modes and will be ignored for this agent."
|
|
921
|
+
);
|
|
922
|
+
}
|
|
883
923
|
if (working.llm !== void 0) {
|
|
884
924
|
const llm = working.llm;
|
|
885
925
|
if (!llm || typeof llm.stream !== "function") {
|
|
@@ -977,16 +1017,18 @@ var Patter = class {
|
|
|
977
1017
|
throw err;
|
|
978
1018
|
}
|
|
979
1019
|
const carrier = this.localConfig.carrier;
|
|
980
|
-
const telephonyProvider = carrier.kind
|
|
1020
|
+
const telephonyProvider = carrier.kind;
|
|
981
1021
|
const wantsCarrierManagement = opts.manageWebhook !== false || wantsCloudflared;
|
|
982
1022
|
if (wantsCarrierManagement) {
|
|
983
|
-
const { autoConfigureCarrier } = await import("./carrier-config-
|
|
1023
|
+
const { autoConfigureCarrier } = await import("./carrier-config-7YGNRBPO.mjs");
|
|
984
1024
|
await autoConfigureCarrier({
|
|
985
1025
|
telephonyProvider,
|
|
986
1026
|
twilioSid: carrier.kind === "twilio" ? carrier.accountSid : void 0,
|
|
987
1027
|
twilioToken: carrier.kind === "twilio" ? carrier.authToken : void 0,
|
|
988
1028
|
telnyxKey: carrier.kind === "telnyx" ? carrier.apiKey : void 0,
|
|
989
1029
|
telnyxConnectionId: carrier.kind === "telnyx" ? carrier.connectionId : void 0,
|
|
1030
|
+
plivoAuthId: carrier.kind === "plivo" ? carrier.authId : void 0,
|
|
1031
|
+
plivoAuthToken: carrier.kind === "plivo" ? carrier.authToken : void 0,
|
|
990
1032
|
phoneNumber: this.localConfig.phoneNumber,
|
|
991
1033
|
webhookHost: webhookUrl
|
|
992
1034
|
});
|
|
@@ -1002,6 +1044,8 @@ var Patter = class {
|
|
|
1002
1044
|
telnyxKey: carrier.kind === "telnyx" ? carrier.apiKey : void 0,
|
|
1003
1045
|
telnyxConnectionId: carrier.kind === "telnyx" ? carrier.connectionId : void 0,
|
|
1004
1046
|
telnyxPublicKey: carrier.kind === "telnyx" ? carrier.publicKey : void 0,
|
|
1047
|
+
plivoAuthId: carrier.kind === "plivo" ? carrier.authId : void 0,
|
|
1048
|
+
plivoAuthToken: carrier.kind === "plivo" ? carrier.authToken : void 0,
|
|
1005
1049
|
persistRoot: this.localConfig.persistRoot
|
|
1006
1050
|
},
|
|
1007
1051
|
opts.agent,
|
|
@@ -1014,7 +1058,8 @@ var Patter = class {
|
|
|
1014
1058
|
opts.onMetrics,
|
|
1015
1059
|
opts.pricing,
|
|
1016
1060
|
opts.dashboard ?? true,
|
|
1017
|
-
opts.dashboardToken ?? ""
|
|
1061
|
+
opts.dashboardToken ?? "",
|
|
1062
|
+
opts.allowInsecureDashboard ?? false
|
|
1018
1063
|
);
|
|
1019
1064
|
this.embeddedServer.popPrewarmAudio = this.popPrewarmAudio;
|
|
1020
1065
|
this.embeddedServer.popPrewarmedConnections = this.popPrewarmedConnections;
|
|
@@ -1033,7 +1078,7 @@ var Patter = class {
|
|
|
1033
1078
|
}
|
|
1034
1079
|
/** Run the agent in interactive terminal-test mode (no real telephony). */
|
|
1035
1080
|
async test(opts) {
|
|
1036
|
-
const { TestSession: TestSession2 } = await import("./test-mode-
|
|
1081
|
+
const { TestSession: TestSession2 } = await import("./test-mode-4QLLWYVV.mjs");
|
|
1037
1082
|
const session = new TestSession2();
|
|
1038
1083
|
await session.run({
|
|
1039
1084
|
agent: opts.agent,
|
|
@@ -1212,7 +1257,7 @@ var Patter = class {
|
|
|
1212
1257
|
}
|
|
1213
1258
|
if (wantsRealtimePark) {
|
|
1214
1259
|
tasks.push((async () => {
|
|
1215
|
-
const { OpenAIRealtime2Adapter: OpenAIRealtime2Adapter2 } = await import("./openai-realtime-2-
|
|
1260
|
+
const { OpenAIRealtime2Adapter: OpenAIRealtime2Adapter2 } = await import("./openai-realtime-2-L5EKAAUH.mjs");
|
|
1216
1261
|
const apiKey = process.env.OPENAI_API_KEY ?? "";
|
|
1217
1262
|
if (!apiKey) {
|
|
1218
1263
|
getLogger().debug(`Park OpenAI Realtime skipped for ${callId}: no OPENAI_API_KEY`);
|
|
@@ -1413,20 +1458,31 @@ var Patter = class {
|
|
|
1413
1458
|
this.prewarmTtlTimers.set(callId, handle);
|
|
1414
1459
|
});
|
|
1415
1460
|
}
|
|
1416
|
-
/**
|
|
1461
|
+
/**
|
|
1462
|
+
* Place an outbound call via the configured carrier.
|
|
1463
|
+
*
|
|
1464
|
+
* With `wait: false` (default) this resolves to `void` the instant the
|
|
1465
|
+
* carrier accepts the dial (fire-and-forget). With `wait: true` it blocks
|
|
1466
|
+
* until the call reaches a terminal state and resolves to a
|
|
1467
|
+
* {@link CallResult} — see {@link LocalCallOptions.wait}. Mirrors Python's
|
|
1468
|
+
* `Patter.call(..., wait=False)`.
|
|
1469
|
+
*/
|
|
1417
1470
|
async call(options) {
|
|
1418
1471
|
if (!options.to) {
|
|
1419
1472
|
throw new Error("'to' phone number is required");
|
|
1420
1473
|
}
|
|
1421
|
-
if (
|
|
1422
|
-
throw new Error(
|
|
1474
|
+
if (!/^\+[1-9]\d{6,14}$/.test(options.to)) {
|
|
1475
|
+
throw new Error("'to' must be E.164 format (+<country><digits>). Got value with invalid format.");
|
|
1476
|
+
}
|
|
1477
|
+
if (options.wait && !this.embeddedServer) {
|
|
1478
|
+
throw new PatterConnectionError(
|
|
1479
|
+
"call({ wait: true }) requires an active server to receive the carrier completion webhooks. Call `await phone.serve(...)` first, or use `await using phone = new Patter(...)` (and serve inside the block) which keeps the server up for the duration of the block."
|
|
1480
|
+
);
|
|
1423
1481
|
}
|
|
1424
1482
|
const { phoneNumber, webhookUrl, carrier } = this.localConfig;
|
|
1483
|
+
let callId = "";
|
|
1425
1484
|
const effectiveRingTimeout = options.ringTimeout === void 0 ? 25 : options.ringTimeout;
|
|
1426
1485
|
const wantsAmd = options.machineDetection !== false || Boolean(options.voicemailMessage);
|
|
1427
|
-
if (this.embeddedServer) {
|
|
1428
|
-
this.embeddedServer.onMachineDetection = options.onMachineDetection;
|
|
1429
|
-
}
|
|
1430
1486
|
if (options.agent.prewarm !== false) {
|
|
1431
1487
|
this.spawnProviderWarmup(options.agent);
|
|
1432
1488
|
}
|
|
@@ -1471,6 +1527,12 @@ var Patter = class {
|
|
|
1471
1527
|
};
|
|
1472
1528
|
if (this.embeddedServer) {
|
|
1473
1529
|
this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
|
|
1530
|
+
if (options.onMachineDetection) {
|
|
1531
|
+
this.embeddedServer.onMachineDetectionByCallSid.set(
|
|
1532
|
+
telnyxCallId,
|
|
1533
|
+
options.onMachineDetection
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1474
1536
|
}
|
|
1475
1537
|
try {
|
|
1476
1538
|
const { notifyDashboard: notifyDashboard2 } = await import("./persistence-LVIAHESK.mjs");
|
|
@@ -1479,11 +1541,80 @@ var Patter = class {
|
|
|
1479
1541
|
}
|
|
1480
1542
|
}
|
|
1481
1543
|
if (telnyxCallId) {
|
|
1544
|
+
callId = telnyxCallId;
|
|
1482
1545
|
this.spawnPrewarmFirstMessage(options.agent, telnyxCallId, effectiveRingTimeout, "telnyx");
|
|
1483
1546
|
if (options.agent.prewarm !== false) {
|
|
1484
1547
|
this.parkProviderConnections(options.agent, telnyxCallId);
|
|
1485
1548
|
}
|
|
1486
1549
|
}
|
|
1550
|
+
return this.maybeAwaitCompletion(options, callId, effectiveRingTimeout);
|
|
1551
|
+
}
|
|
1552
|
+
if (carrier.kind === "plivo") {
|
|
1553
|
+
const auth = `Basic ${Buffer.from(`${carrier.authId}:${carrier.authToken}`).toString("base64")}`;
|
|
1554
|
+
const plivoPayload = {
|
|
1555
|
+
from: phoneNumber,
|
|
1556
|
+
to: options.to,
|
|
1557
|
+
answer_url: `https://${webhookUrl}/webhooks/plivo/voice`,
|
|
1558
|
+
answer_method: "POST",
|
|
1559
|
+
// hangup_url is Plivo's StatusCallback analogue — without it the
|
|
1560
|
+
// /webhooks/plivo/status route never fires for outbound calls and
|
|
1561
|
+
// the dashboard misses no-answer / busy / failed.
|
|
1562
|
+
hangup_url: `https://${webhookUrl}/webhooks/plivo/status`,
|
|
1563
|
+
hangup_method: "POST"
|
|
1564
|
+
};
|
|
1565
|
+
if (effectiveRingTimeout !== null && effectiveRingTimeout !== void 0) {
|
|
1566
|
+
plivoPayload.ring_timeout = Math.max(1, Math.floor(effectiveRingTimeout));
|
|
1567
|
+
}
|
|
1568
|
+
if (wantsAmd) {
|
|
1569
|
+
plivoPayload.machine_detection = "true";
|
|
1570
|
+
plivoPayload.machine_detection_time = 5e3;
|
|
1571
|
+
plivoPayload.machine_detection_url = `https://${webhookUrl}/webhooks/plivo/amd`;
|
|
1572
|
+
plivoPayload.machine_detection_method = "POST";
|
|
1573
|
+
}
|
|
1574
|
+
if (options.voicemailMessage && this.embeddedServer) {
|
|
1575
|
+
this.embeddedServer.voicemailMessage = options.voicemailMessage;
|
|
1576
|
+
}
|
|
1577
|
+
const response2 = await fetch(`https://api.plivo.com/v1/Account/${carrier.authId}/Call/`, {
|
|
1578
|
+
method: "POST",
|
|
1579
|
+
headers: { "Content-Type": "application/json", Authorization: auth },
|
|
1580
|
+
body: JSON.stringify(plivoPayload)
|
|
1581
|
+
});
|
|
1582
|
+
if (!response2.ok) {
|
|
1583
|
+
throw new ProvisionError(`Failed to initiate Plivo call: ${await response2.text()}`);
|
|
1584
|
+
}
|
|
1585
|
+
let plivoCallId;
|
|
1586
|
+
try {
|
|
1587
|
+
const body = await response2.clone().json();
|
|
1588
|
+
plivoCallId = body.request_uuid;
|
|
1589
|
+
} catch {
|
|
1590
|
+
}
|
|
1591
|
+
if (plivoCallId) {
|
|
1592
|
+
const initiatedPayload = {
|
|
1593
|
+
call_id: plivoCallId,
|
|
1594
|
+
caller: phoneNumber,
|
|
1595
|
+
callee: options.to,
|
|
1596
|
+
direction: "outbound",
|
|
1597
|
+
status: "initiated"
|
|
1598
|
+
};
|
|
1599
|
+
if (this.embeddedServer) {
|
|
1600
|
+
this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
|
|
1601
|
+
if (options.onMachineDetection) {
|
|
1602
|
+
this.embeddedServer.onMachineDetectionByCallSid.set(
|
|
1603
|
+
plivoCallId,
|
|
1604
|
+
options.onMachineDetection
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
try {
|
|
1609
|
+
const { notifyDashboard: notifyDashboard2 } = await import("./persistence-LVIAHESK.mjs");
|
|
1610
|
+
notifyDashboard2(initiatedPayload);
|
|
1611
|
+
} catch {
|
|
1612
|
+
}
|
|
1613
|
+
this.spawnPrewarmFirstMessage(options.agent, plivoCallId, effectiveRingTimeout, "plivo");
|
|
1614
|
+
if (options.agent.prewarm !== false) {
|
|
1615
|
+
this.parkProviderConnections(options.agent, plivoCallId);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1487
1618
|
return;
|
|
1488
1619
|
}
|
|
1489
1620
|
const twilioSid = carrier.accountSid;
|
|
@@ -1542,6 +1673,12 @@ var Patter = class {
|
|
|
1542
1673
|
};
|
|
1543
1674
|
if (this.embeddedServer) {
|
|
1544
1675
|
this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
|
|
1676
|
+
if (options.onMachineDetection) {
|
|
1677
|
+
this.embeddedServer.onMachineDetectionByCallSid.set(
|
|
1678
|
+
twilioCallSid,
|
|
1679
|
+
options.onMachineDetection
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1545
1682
|
if (twilioNotificationsPath) {
|
|
1546
1683
|
getLogger().info(
|
|
1547
1684
|
`Outbound call ${twilioCallSid} placed. Twilio notifications: https://api.twilio.com${twilioNotificationsPath} (check here if the call drops with no audio).`
|
|
@@ -1555,11 +1692,53 @@ var Patter = class {
|
|
|
1555
1692
|
}
|
|
1556
1693
|
}
|
|
1557
1694
|
if (twilioCallSid) {
|
|
1695
|
+
callId = twilioCallSid;
|
|
1558
1696
|
this.spawnPrewarmFirstMessage(options.agent, twilioCallSid, effectiveRingTimeout, "twilio");
|
|
1559
1697
|
if (options.agent.prewarm !== false) {
|
|
1560
1698
|
this.parkProviderConnections(options.agent, twilioCallSid);
|
|
1561
1699
|
}
|
|
1562
1700
|
}
|
|
1701
|
+
return this.maybeAwaitCompletion(options, callId, effectiveRingTimeout);
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* When `options.wait` is set, register a completion promise keyed by the
|
|
1705
|
+
* carrier-issued `callId` and await it (bounded by a backstop timeout).
|
|
1706
|
+
* Otherwise resolve to `void` immediately (fire-and-forget).
|
|
1707
|
+
*
|
|
1708
|
+
* The registration happens here — after the carrier accepted the dial and
|
|
1709
|
+
* issued the id — so the future correlates to the right call. The race
|
|
1710
|
+
* window between `initiateCall` returning and this registration is
|
|
1711
|
+
* harmless: the callee is still ringing, so no terminal signal can fire
|
|
1712
|
+
* before we register. Mirrors the Python `call(wait=True)` tail block.
|
|
1713
|
+
*/
|
|
1714
|
+
async maybeAwaitCompletion(options, callId, ringTimeout) {
|
|
1715
|
+
if (!options.wait) return;
|
|
1716
|
+
const server = this.embeddedServer;
|
|
1717
|
+
if (!server || !callId) {
|
|
1718
|
+
throw new PatterConnectionError(
|
|
1719
|
+
"call({ wait: true }): no active server or carrier call id."
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
const completion = server.registerCompletion(callId);
|
|
1723
|
+
const backstopMs = ((ringTimeout ?? 25) + 1800) * 1e3;
|
|
1724
|
+
let timer;
|
|
1725
|
+
const backstop = new Promise((_resolve, reject) => {
|
|
1726
|
+
timer = setTimeout(() => {
|
|
1727
|
+
server.deleteCompletion(callId);
|
|
1728
|
+
reject(
|
|
1729
|
+
new PatterConnectionError(
|
|
1730
|
+
`call({ wait: true }): no terminal signal for call ${callId} within ${(backstopMs / 1e3).toFixed(0)}s`,
|
|
1731
|
+
{ code: ErrorCode.TIMEOUT }
|
|
1732
|
+
)
|
|
1733
|
+
);
|
|
1734
|
+
}, backstopMs);
|
|
1735
|
+
timer.unref?.();
|
|
1736
|
+
});
|
|
1737
|
+
try {
|
|
1738
|
+
return await Promise.race([completion, backstop]);
|
|
1739
|
+
} finally {
|
|
1740
|
+
if (timer) clearTimeout(timer);
|
|
1741
|
+
}
|
|
1563
1742
|
}
|
|
1564
1743
|
/**
|
|
1565
1744
|
* Stop the embedded server and any running tunnel. Safe to call multiple
|
|
@@ -1600,6 +1779,11 @@ var Patter = class {
|
|
|
1600
1779
|
this.tunnelHandle = null;
|
|
1601
1780
|
}
|
|
1602
1781
|
if (this.embeddedServer) {
|
|
1782
|
+
this.embeddedServer.failPendingCompletions(
|
|
1783
|
+
new PatterConnectionError(
|
|
1784
|
+
"Patter.disconnect() called while a call({ wait: true }) was still in flight."
|
|
1785
|
+
)
|
|
1786
|
+
);
|
|
1603
1787
|
await this.embeddedServer.stop();
|
|
1604
1788
|
this.embeddedServer = null;
|
|
1605
1789
|
}
|
|
@@ -1623,6 +1807,30 @@ var Patter = class {
|
|
|
1623
1807
|
this._ready.catch(() => {
|
|
1624
1808
|
});
|
|
1625
1809
|
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Explicit-resource-management disposer so callers can write
|
|
1812
|
+
* ``await using phone = new Patter(...)`` and have {@link disconnect} run
|
|
1813
|
+
* automatically when the block exits — on the normal path AND when the
|
|
1814
|
+
* body throws. This guarantees the embedded server, any auto-started
|
|
1815
|
+
* tunnel, and in-flight prewarm/TTS work are torn down so a still-running
|
|
1816
|
+
* TTS WebSocket cannot keep the user billed after the block ends, and any
|
|
1817
|
+
* in-flight ``call({ wait: true })`` awaiter is failed rather than left
|
|
1818
|
+
* hanging. ``disconnect()`` is idempotent, so an explicit ``disconnect()``
|
|
1819
|
+
* inside the block is still safe. Mirrors Python's ``async with Patter(...)``.
|
|
1820
|
+
*
|
|
1821
|
+
* Note: this does NOT start the server (``serve()`` blocks until shutdown,
|
|
1822
|
+
* so it cannot run from a disposer) — call ``serve(...)`` inside the block:
|
|
1823
|
+
*
|
|
1824
|
+
* ```ts
|
|
1825
|
+
* await using phone = new Patter({ carrier: new Twilio(), phoneNumber: "+1555..." });
|
|
1826
|
+
* await phone.serve({ agent }); // inbound, or
|
|
1827
|
+
* const result = await phone.call({ to: "+1555...", agent, wait: true });
|
|
1828
|
+
* // disconnect() has run here — nothing left running.
|
|
1829
|
+
* ```
|
|
1830
|
+
*/
|
|
1831
|
+
async [Symbol.asyncDispose]() {
|
|
1832
|
+
await this.disconnect();
|
|
1833
|
+
}
|
|
1626
1834
|
/**
|
|
1627
1835
|
* Terminate an active call on the configured carrier.
|
|
1628
1836
|
*
|
|
@@ -1677,6 +1885,17 @@ var Patter = class {
|
|
|
1677
1885
|
}
|
|
1678
1886
|
return;
|
|
1679
1887
|
}
|
|
1888
|
+
if (carrier.kind === "plivo") {
|
|
1889
|
+
const auth = Buffer.from(`${carrier.authId}:${carrier.authToken}`).toString("base64");
|
|
1890
|
+
const res = await fetch(
|
|
1891
|
+
`https://api.plivo.com/v1/Account/${carrier.authId}/Call/${encodeURIComponent(callSid)}/`,
|
|
1892
|
+
{ method: "DELETE", headers: { Authorization: `Basic ${auth}` } }
|
|
1893
|
+
);
|
|
1894
|
+
if (!res.ok && res.status !== 404) {
|
|
1895
|
+
throw new Error(`Plivo hangup failed: ${res.status} ${await res.text()}`);
|
|
1896
|
+
}
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1680
1899
|
throw new Error(`endCall() requires a configured carrier; got kind=${carrier.kind}`);
|
|
1681
1900
|
}
|
|
1682
1901
|
};
|
|
@@ -1940,8 +2159,8 @@ var FallbackLLMProvider = class {
|
|
|
1940
2159
|
* markers are filtered out so callers can concatenate the yielded strings
|
|
1941
2160
|
* directly.
|
|
1942
2161
|
*/
|
|
1943
|
-
async *completeStream(messages, tools) {
|
|
1944
|
-
for await (const chunk of this.stream(messages, tools)) {
|
|
2162
|
+
async *completeStream(messages, tools, opts) {
|
|
2163
|
+
for await (const chunk of this.stream(messages, tools, opts)) {
|
|
1945
2164
|
if (chunk.type === "text") {
|
|
1946
2165
|
yield chunk.content ?? "";
|
|
1947
2166
|
}
|
|
@@ -1951,14 +2170,15 @@ var FallbackLLMProvider = class {
|
|
|
1951
2170
|
// LLMProvider implementation
|
|
1952
2171
|
// -----------------------------------------------------------------------
|
|
1953
2172
|
/** Streaming entry point — yields chunks from the first provider that succeeds. */
|
|
1954
|
-
async *stream(messages, tools) {
|
|
2173
|
+
async *stream(messages, tools, opts) {
|
|
1955
2174
|
const errors = [];
|
|
1956
2175
|
const result = yield* this.tryProviders(
|
|
1957
2176
|
messages,
|
|
1958
2177
|
tools,
|
|
1959
2178
|
/* availableOnly */
|
|
1960
2179
|
true,
|
|
1961
|
-
errors
|
|
2180
|
+
errors,
|
|
2181
|
+
opts
|
|
1962
2182
|
);
|
|
1963
2183
|
if (result === "done") return;
|
|
1964
2184
|
getLogger().warn(
|
|
@@ -1969,7 +2189,8 @@ var FallbackLLMProvider = class {
|
|
|
1969
2189
|
tools,
|
|
1970
2190
|
/* availableOnly */
|
|
1971
2191
|
false,
|
|
1972
|
-
errors
|
|
2192
|
+
errors,
|
|
2193
|
+
opts
|
|
1973
2194
|
);
|
|
1974
2195
|
if (retryResult === "done") return;
|
|
1975
2196
|
throw new AllProvidersFailedError(
|
|
@@ -1979,7 +2200,7 @@ var FallbackLLMProvider = class {
|
|
|
1979
2200
|
// -----------------------------------------------------------------------
|
|
1980
2201
|
// Internals
|
|
1981
2202
|
// -----------------------------------------------------------------------
|
|
1982
|
-
async *tryProviders(messages, tools, availableOnly, errors) {
|
|
2203
|
+
async *tryProviders(messages, tools, availableOnly, errors, opts) {
|
|
1983
2204
|
for (let i = 0; i < this.providers.length; i++) {
|
|
1984
2205
|
if (availableOnly && !this.availability[i]) continue;
|
|
1985
2206
|
for (let attempt = 0; attempt < this.maxRetryPerProvider; attempt++) {
|
|
@@ -1988,7 +2209,7 @@ var FallbackLLMProvider = class {
|
|
|
1988
2209
|
`FallbackLLMProvider: trying provider ${i}${attempt > 0 ? ` (retry ${attempt})` : ""}`
|
|
1989
2210
|
);
|
|
1990
2211
|
let yieldedTokens = false;
|
|
1991
|
-
const gen = this.providers[i].stream(messages, tools);
|
|
2212
|
+
const gen = this.providers[i].stream(messages, tools, opts);
|
|
1992
2213
|
while (true) {
|
|
1993
2214
|
let iterResult;
|
|
1994
2215
|
try {
|
|
@@ -2074,7 +2295,6 @@ init_esm_shims();
|
|
|
2074
2295
|
|
|
2075
2296
|
// src/integrations/patter-tool.ts
|
|
2076
2297
|
init_esm_shims();
|
|
2077
|
-
import { EventEmitter } from "events";
|
|
2078
2298
|
var PARAMETERS_SCHEMA = {
|
|
2079
2299
|
type: "object",
|
|
2080
2300
|
properties: {
|
|
@@ -2101,7 +2321,7 @@ var PARAMETERS_SCHEMA = {
|
|
|
2101
2321
|
};
|
|
2102
2322
|
var DEFAULT_NAME = "make_phone_call";
|
|
2103
2323
|
var DEFAULT_DESCRIPTION = "Place a real outbound phone call. Returns a JSON object with the full transcript, call status, duration in seconds, and cost. Use this when the user asks you to call someone, schedule appointments by phone, or otherwise reach a human via voice.";
|
|
2104
|
-
var PatterTool = class
|
|
2324
|
+
var PatterTool = class {
|
|
2105
2325
|
name;
|
|
2106
2326
|
description;
|
|
2107
2327
|
phone;
|
|
@@ -2109,24 +2329,11 @@ var PatterTool = class _PatterTool {
|
|
|
2109
2329
|
maxDurationSec;
|
|
2110
2330
|
recording;
|
|
2111
2331
|
started = false;
|
|
2112
|
-
/**
|
|
2113
|
-
*
|
|
2114
|
-
*
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
* Each `execute()` chains a continuation onto this promise so the
|
|
2118
|
-
* `pendingDial` slot is owned by exactly one caller at a time. */
|
|
2119
|
-
dialQueue = Promise.resolve();
|
|
2120
|
-
/** Captured SSE listener so `stop()` can detach it (prevents leaks when
|
|
2121
|
-
* the underlying Patter instance outlives this tool). */
|
|
2122
|
-
sseListener = null;
|
|
2123
|
-
/** Captured Patter metrics store, for cleanup in `stop()`. */
|
|
2124
|
-
metricsStoreRef = null;
|
|
2125
|
-
/** call_id → pending promise machinery. */
|
|
2126
|
-
pending = /* @__PURE__ */ new Map();
|
|
2127
|
-
bus = new EventEmitter();
|
|
2128
|
-
/** How long to wait for the `call_initiated` SSE before failing the dial. */
|
|
2129
|
-
static DIAL_CAPTURE_TIMEOUT_MS = 1e4;
|
|
2332
|
+
/** Cached in-progress (or completed) start promise so concurrent execute()
|
|
2333
|
+
* callers all await the same boot sequence instead of each racing into
|
|
2334
|
+
* phone.serve(). Reset to null on failure so callers can retry after a
|
|
2335
|
+
* transient error. */
|
|
2336
|
+
startPromise = null;
|
|
2130
2337
|
constructor(opts) {
|
|
2131
2338
|
if (!opts.phone) {
|
|
2132
2339
|
throw new Error("PatterTool: `phone` (a Patter instance) is required.");
|
|
@@ -2170,8 +2377,29 @@ var PatterTool = class _PatterTool {
|
|
|
2170
2377
|
};
|
|
2171
2378
|
}
|
|
2172
2379
|
// --- Lifecycle ----------------------------------------------------------
|
|
2173
|
-
/**
|
|
2380
|
+
/**
|
|
2381
|
+
* Start the underlying Patter server. Idempotent.
|
|
2382
|
+
*
|
|
2383
|
+
* `execute()` relies on `Patter.call({ wait: true })`, which requires an
|
|
2384
|
+
* active server to receive the carrier completion webhooks — that's what
|
|
2385
|
+
* `serve()` provides here. No `onCallEnd` callback is wired: the SDK's own
|
|
2386
|
+
* per-callId completion registry resolves the result, so the user's
|
|
2387
|
+
* `onCallEnd` slot is left free.
|
|
2388
|
+
*
|
|
2389
|
+
* Idempotent and concurrency-safe: concurrent callers all await the same
|
|
2390
|
+
* in-progress boot instead of each racing into `phone.serve()`.
|
|
2391
|
+
*/
|
|
2174
2392
|
async start() {
|
|
2393
|
+
if (this.startPromise) return this.startPromise;
|
|
2394
|
+
this.startPromise = this._doStart();
|
|
2395
|
+
try {
|
|
2396
|
+
await this.startPromise;
|
|
2397
|
+
} catch (err) {
|
|
2398
|
+
this.startPromise = null;
|
|
2399
|
+
throw err;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
async _doStart() {
|
|
2175
2403
|
if (this.started) return;
|
|
2176
2404
|
if (!this.agent) {
|
|
2177
2405
|
throw new Error(
|
|
@@ -2182,52 +2410,32 @@ var PatterTool = class _PatterTool {
|
|
|
2182
2410
|
await this.phone.serve({
|
|
2183
2411
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2184
2412
|
agent: builtAgent,
|
|
2185
|
-
recording: this.recording
|
|
2186
|
-
onCallEnd: this.onCallEndHandler.bind(this)
|
|
2413
|
+
recording: this.recording
|
|
2187
2414
|
});
|
|
2188
|
-
const store = this.phone.metricsStore;
|
|
2189
|
-
if (!store) {
|
|
2190
|
-
throw new Error(
|
|
2191
|
-
"PatterTool.start: phone.metricsStore is null after serve() \u2014 is the dashboard disabled?"
|
|
2192
|
-
);
|
|
2193
|
-
}
|
|
2194
|
-
const listener = (event) => {
|
|
2195
|
-
if (event.type === "call_initiated" && this.pendingDial) {
|
|
2196
|
-
const callId = event.data.call_id || "";
|
|
2197
|
-
if (callId) {
|
|
2198
|
-
const dispatch = this.pendingDial;
|
|
2199
|
-
this.pendingDial = null;
|
|
2200
|
-
dispatch(callId);
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
};
|
|
2204
|
-
store.on("sse", listener);
|
|
2205
|
-
this.sseListener = listener;
|
|
2206
|
-
this.metricsStoreRef = store;
|
|
2207
2415
|
this.started = true;
|
|
2208
2416
|
}
|
|
2209
|
-
/**
|
|
2417
|
+
/** Best-effort shutdown — tear the Patter server down via `disconnect()`. */
|
|
2210
2418
|
async stop() {
|
|
2211
2419
|
if (!this.started) return;
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
for (const [, p] of this.pending) {
|
|
2219
|
-
clearTimeout(p.timer);
|
|
2220
|
-
p.reject(new Error("PatterTool: shutdown while call pending"));
|
|
2221
|
-
}
|
|
2222
|
-
this.pending.clear();
|
|
2223
|
-
const stoppable = this.phone;
|
|
2224
|
-
if (typeof stoppable.stop === "function") {
|
|
2225
|
-
await stoppable.stop();
|
|
2420
|
+
const disconnectable = this.phone;
|
|
2421
|
+
if (typeof disconnectable.disconnect === "function") {
|
|
2422
|
+
try {
|
|
2423
|
+
await disconnectable.disconnect();
|
|
2424
|
+
} catch {
|
|
2425
|
+
}
|
|
2226
2426
|
}
|
|
2227
2427
|
this.started = false;
|
|
2428
|
+
this.startPromise = null;
|
|
2228
2429
|
}
|
|
2229
2430
|
// --- Execution ----------------------------------------------------------
|
|
2230
|
-
/**
|
|
2431
|
+
/**
|
|
2432
|
+
* Dial outbound, wait for the call to end, return a structured result.
|
|
2433
|
+
*
|
|
2434
|
+
* Thin wrapper over `Patter.call({ wait: true })`: the SDK now owns the
|
|
2435
|
+
* dial → callId → terminal-signal correlation, so this just bounds the wait
|
|
2436
|
+
* with `max_duration_sec` and maps the {@link CallResult} into the tool's
|
|
2437
|
+
* public envelope. Mirrors Python's `PatterTool.execute`.
|
|
2438
|
+
*/
|
|
2231
2439
|
async execute(args) {
|
|
2232
2440
|
if (!this.started) await this.start();
|
|
2233
2441
|
if (!args || typeof args.to !== "string" || !args.to.startsWith("+")) {
|
|
@@ -2243,55 +2451,32 @@ var PatterTool = class _PatterTool {
|
|
|
2243
2451
|
...args.goal !== void 0 ? { systemPrompt: args.goal } : {},
|
|
2244
2452
|
...args.first_message !== void 0 ? { firstMessage: args.first_message } : {}
|
|
2245
2453
|
});
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2454
|
+
let timer;
|
|
2455
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
2456
|
+
timer = setTimeout(() => {
|
|
2457
|
+
reject(
|
|
2458
|
+
new Error(
|
|
2459
|
+
`PatterTool.execute: call to ${args.to} exceeded ${timeoutSec}s timeout`
|
|
2460
|
+
)
|
|
2461
|
+
);
|
|
2251
2462
|
}, timeoutSec * 1e3);
|
|
2252
|
-
|
|
2253
|
-
resolve,
|
|
2254
|
-
reject,
|
|
2255
|
-
timer,
|
|
2256
|
-
startedAt: Date.now() / 1e3
|
|
2257
|
-
});
|
|
2258
|
-
});
|
|
2259
|
-
}
|
|
2260
|
-
/** Issue the outbound dial under the mutex and return its assigned call_id. */
|
|
2261
|
-
async acquireCallId(to, agent) {
|
|
2262
|
-
let release;
|
|
2263
|
-
const slot = new Promise((r) => {
|
|
2264
|
-
release = r;
|
|
2463
|
+
timer.unref?.();
|
|
2265
2464
|
});
|
|
2266
|
-
|
|
2267
|
-
this.dialQueue = previous.then(() => slot);
|
|
2268
|
-
await previous;
|
|
2269
|
-
let captureTimer = null;
|
|
2465
|
+
let result;
|
|
2270
2466
|
try {
|
|
2271
|
-
|
|
2272
|
-
this.
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
}, _PatterTool.DIAL_CAPTURE_TIMEOUT_MS);
|
|
2281
|
-
});
|
|
2282
|
-
await this.phone.call({
|
|
2283
|
-
to,
|
|
2284
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2285
|
-
agent
|
|
2286
|
-
});
|
|
2287
|
-
const callId = await callIdPromise;
|
|
2288
|
-
if (captureTimer) clearTimeout(captureTimer);
|
|
2289
|
-
return callId;
|
|
2467
|
+
result = await Promise.race([
|
|
2468
|
+
this.phone.call({
|
|
2469
|
+
to: args.to,
|
|
2470
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2471
|
+
agent: overrideAgent,
|
|
2472
|
+
wait: true
|
|
2473
|
+
}),
|
|
2474
|
+
timeout
|
|
2475
|
+
]);
|
|
2290
2476
|
} finally {
|
|
2291
|
-
if (
|
|
2292
|
-
this.pendingDial = null;
|
|
2293
|
-
release();
|
|
2477
|
+
if (timer) clearTimeout(timer);
|
|
2294
2478
|
}
|
|
2479
|
+
return resultFromCallResult(result);
|
|
2295
2480
|
}
|
|
2296
2481
|
/**
|
|
2297
2482
|
* Hermes-style handler: `(args, kwargs) => Promise<string>` returning a JSON
|
|
@@ -2309,32 +2494,32 @@ var PatterTool = class _PatterTool {
|
|
|
2309
2494
|
}
|
|
2310
2495
|
};
|
|
2311
2496
|
}
|
|
2312
|
-
// --- Internal: onCallEnd dispatcher -------------------------------------
|
|
2313
|
-
async onCallEndHandler(data) {
|
|
2314
|
-
const callId = data.call_id || "";
|
|
2315
|
-
if (!callId) return;
|
|
2316
|
-
const pending = this.pending.get(callId);
|
|
2317
|
-
if (!pending) {
|
|
2318
|
-
this.bus.emit("orphan_end", { call_id: callId, data });
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
clearTimeout(pending.timer);
|
|
2322
|
-
this.pending.delete(callId);
|
|
2323
|
-
const metrics = data.metrics && typeof data.metrics === "object" ? data.metrics : null;
|
|
2324
|
-
const cost = metrics && typeof metrics.cost === "object" && metrics.cost && typeof metrics.cost.total === "number" ? metrics.cost.total : void 0;
|
|
2325
|
-
const duration = typeof metrics?.duration_seconds === "number" ? metrics?.duration_seconds : Math.max(0, Date.now() / 1e3 - pending.startedAt);
|
|
2326
|
-
const transcript = Array.isArray(data.transcript) ? data.transcript : [];
|
|
2327
|
-
const status = data.status || "completed";
|
|
2328
|
-
pending.resolve({
|
|
2329
|
-
call_id: callId,
|
|
2330
|
-
status,
|
|
2331
|
-
duration_seconds: duration,
|
|
2332
|
-
cost_usd: cost,
|
|
2333
|
-
transcript,
|
|
2334
|
-
metrics
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
2497
|
};
|
|
2498
|
+
function resultFromCallResult(result) {
|
|
2499
|
+
if (!result) {
|
|
2500
|
+
return {
|
|
2501
|
+
call_id: "",
|
|
2502
|
+
status: "completed",
|
|
2503
|
+
outcome: "",
|
|
2504
|
+
duration_seconds: 0,
|
|
2505
|
+
cost_usd: void 0,
|
|
2506
|
+
transcript: [],
|
|
2507
|
+
metrics: null
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
const costTotal = result.cost?.total;
|
|
2511
|
+
const costUsd = typeof costTotal === "number" ? costTotal : void 0;
|
|
2512
|
+
const metrics = result.metrics ? result.metrics : null;
|
|
2513
|
+
return {
|
|
2514
|
+
call_id: result.callId || "",
|
|
2515
|
+
status: result.status || "completed",
|
|
2516
|
+
outcome: result.outcome || "",
|
|
2517
|
+
duration_seconds: typeof result.durationSeconds === "number" ? result.durationSeconds : 0,
|
|
2518
|
+
cost_usd: costUsd,
|
|
2519
|
+
transcript: result.transcript ? [...result.transcript] : [],
|
|
2520
|
+
metrics
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2338
2523
|
|
|
2339
2524
|
// src/providers/gemini-live.ts
|
|
2340
2525
|
init_esm_shims();
|
|
@@ -2605,7 +2790,8 @@ var UltravoxRealtimeAdapter = class {
|
|
|
2605
2790
|
"X-API-Key": this.apiKey,
|
|
2606
2791
|
"Content-Type": "application/json"
|
|
2607
2792
|
},
|
|
2608
|
-
body: JSON.stringify(body)
|
|
2793
|
+
body: JSON.stringify(body),
|
|
2794
|
+
signal: AbortSignal.timeout(15e3)
|
|
2609
2795
|
});
|
|
2610
2796
|
if (!resp.ok) {
|
|
2611
2797
|
const text = await resp.text().catch(() => "");
|
|
@@ -2616,12 +2802,36 @@ var UltravoxRealtimeAdapter = class {
|
|
|
2616
2802
|
this.ws = new WebSocket(call.joinUrl);
|
|
2617
2803
|
await new Promise((resolve, reject) => {
|
|
2618
2804
|
const ws = this.ws;
|
|
2805
|
+
let settled = false;
|
|
2806
|
+
const timer = setTimeout(() => {
|
|
2807
|
+
if (settled) return;
|
|
2808
|
+
settled = true;
|
|
2809
|
+
ws.off("open", onOpen);
|
|
2810
|
+
ws.off("error", onError);
|
|
2811
|
+
this.ws = null;
|
|
2812
|
+
try {
|
|
2813
|
+
ws.close();
|
|
2814
|
+
} catch {
|
|
2815
|
+
}
|
|
2816
|
+
reject(new Error("Ultravox WS connect timeout"));
|
|
2817
|
+
}, 15e3);
|
|
2619
2818
|
const onOpen = () => {
|
|
2819
|
+
if (settled) return;
|
|
2820
|
+
settled = true;
|
|
2821
|
+
clearTimeout(timer);
|
|
2620
2822
|
ws.off("error", onError);
|
|
2621
2823
|
resolve();
|
|
2622
2824
|
};
|
|
2623
2825
|
const onError = (err) => {
|
|
2826
|
+
if (settled) return;
|
|
2827
|
+
settled = true;
|
|
2828
|
+
clearTimeout(timer);
|
|
2624
2829
|
ws.off("open", onOpen);
|
|
2830
|
+
this.ws = null;
|
|
2831
|
+
try {
|
|
2832
|
+
ws.close();
|
|
2833
|
+
} catch {
|
|
2834
|
+
}
|
|
2625
2835
|
reject(err);
|
|
2626
2836
|
};
|
|
2627
2837
|
ws.once("open", onOpen);
|
|
@@ -2905,6 +3115,12 @@ var ELEVENLABS_VOICE_ID_BY_NAME = {
|
|
|
2905
3115
|
alloy: "EXAVITQu4vr4xnSDxMaL"
|
|
2906
3116
|
};
|
|
2907
3117
|
var VOICE_ID_PATTERN = /^[A-Za-z0-9]{20}$/;
|
|
3118
|
+
var CARRIER_NATIVE_FORMAT = {
|
|
3119
|
+
twilio: "ulaw_8000",
|
|
3120
|
+
telnyx: "pcm_16000",
|
|
3121
|
+
// Plivo streams mulaw 8 kHz (we pin contentType in the answer XML).
|
|
3122
|
+
plivo: "ulaw_8000"
|
|
3123
|
+
};
|
|
2908
3124
|
function resolveVoiceId(voice) {
|
|
2909
3125
|
if (!voice) return voice;
|
|
2910
3126
|
if (VOICE_ID_PATTERN.test(voice)) return voice;
|
|
@@ -2993,11 +3209,8 @@ var ElevenLabsTTS = class _ElevenLabsTTS {
|
|
|
2993
3209
|
*/
|
|
2994
3210
|
setTelephonyCarrier(carrier) {
|
|
2995
3211
|
if (this._outputFormatExplicit) return;
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
} else if (carrier === "telnyx") {
|
|
2999
|
-
this._outputFormat = ElevenLabsOutputFormat.PCM_16000;
|
|
3000
|
-
}
|
|
3212
|
+
const native = CARRIER_NATIVE_FORMAT[carrier];
|
|
3213
|
+
if (native !== void 0) this._outputFormat = native;
|
|
3001
3214
|
}
|
|
3002
3215
|
/**
|
|
3003
3216
|
* Construct an instance pre-configured for Twilio Media Streams.
|
|
@@ -3457,7 +3670,7 @@ var STT = class extends DeepgramSTT {
|
|
|
3457
3670
|
{
|
|
3458
3671
|
endpointingMs: opts.endpointingMs ?? 150,
|
|
3459
3672
|
utteranceEndMs: opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3,
|
|
3460
|
-
smartFormat: opts.smartFormat ??
|
|
3673
|
+
smartFormat: opts.smartFormat ?? false,
|
|
3461
3674
|
interimResults: opts.interimResults ?? true,
|
|
3462
3675
|
...opts.vadEvents !== void 0 ? { vadEvents: opts.vadEvents } : {}
|
|
3463
3676
|
}
|
|
@@ -3775,7 +3988,7 @@ var CartesiaSTT = class {
|
|
|
3775
3988
|
});
|
|
3776
3989
|
ws.once("error", (err) => {
|
|
3777
3990
|
clearTimeout(timer);
|
|
3778
|
-
reject(err);
|
|
3991
|
+
reject(new Error(`Cartesia STT park connect failed: ${describeWarmupError(err)}`));
|
|
3779
3992
|
});
|
|
3780
3993
|
});
|
|
3781
3994
|
return ws;
|
|
@@ -4130,7 +4343,7 @@ var SonioxSTT = class _SonioxSTT {
|
|
|
4130
4343
|
/** Stable pricing/dashboard key — read by stream-handler/metrics. */
|
|
4131
4344
|
static providerKey = "soniox";
|
|
4132
4345
|
ws = null;
|
|
4133
|
-
callbacks =
|
|
4346
|
+
callbacks = /* @__PURE__ */ new Set();
|
|
4134
4347
|
final = new TokenAccumulator();
|
|
4135
4348
|
keepaliveTimer = null;
|
|
4136
4349
|
apiKey;
|
|
@@ -4292,16 +4505,13 @@ var SonioxSTT = class _SonioxSTT {
|
|
|
4292
4505
|
if (audio.length === 0) return;
|
|
4293
4506
|
this.ws.send(audio);
|
|
4294
4507
|
}
|
|
4295
|
-
/** Register a transcript listener
|
|
4508
|
+
/** Register a transcript listener. */
|
|
4296
4509
|
onTranscript(callback) {
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
return;
|
|
4303
|
-
}
|
|
4304
|
-
this.callbacks.push(callback);
|
|
4510
|
+
this.callbacks.add(callback);
|
|
4511
|
+
}
|
|
4512
|
+
/** Unregister a previously registered transcript listener. */
|
|
4513
|
+
offTranscript(callback) {
|
|
4514
|
+
this.callbacks.delete(callback);
|
|
4305
4515
|
}
|
|
4306
4516
|
/** Send the empty-frame stream terminator and close the WebSocket. */
|
|
4307
4517
|
close() {
|
|
@@ -4382,12 +4592,6 @@ var VALID_DOMAINS = /* @__PURE__ */ new Set([
|
|
|
4382
4592
|
AssemblyAIDomain.GENERAL,
|
|
4383
4593
|
AssemblyAIDomain.MEDICAL_V1
|
|
4384
4594
|
]);
|
|
4385
|
-
var AssemblyAISTTNotConnectedError = class extends Error {
|
|
4386
|
-
constructor(message = "AssemblyAISTT is not connected") {
|
|
4387
|
-
super(message);
|
|
4388
|
-
this.name = "AssemblyAISTTNotConnectedError";
|
|
4389
|
-
}
|
|
4390
|
-
};
|
|
4391
4595
|
var AssemblyAISTT = class _AssemblyAISTT {
|
|
4392
4596
|
constructor(apiKey, options = {}) {
|
|
4393
4597
|
this.apiKey = apiKey;
|
|
@@ -4711,9 +4915,10 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
4711
4915
|
*/
|
|
4712
4916
|
updateConfiguration(params) {
|
|
4713
4917
|
if (!this.ws || this.ws.readyState !== WebSocket4.OPEN) {
|
|
4714
|
-
|
|
4715
|
-
"AssemblyAISTT.updateConfiguration: WebSocket is not open"
|
|
4918
|
+
getLogger().debug(
|
|
4919
|
+
"AssemblyAISTT.updateConfiguration: WebSocket is not open \u2014 dropping update (call teardown)."
|
|
4716
4920
|
);
|
|
4921
|
+
return;
|
|
4717
4922
|
}
|
|
4718
4923
|
const payload = {
|
|
4719
4924
|
type: AssemblyAIClientFrame.UPDATE_CONFIGURATION
|
|
@@ -4735,9 +4940,10 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
4735
4940
|
/** Force the server to finalize the current turn (for barge-in). */
|
|
4736
4941
|
forceEndpoint() {
|
|
4737
4942
|
if (!this.ws || this.ws.readyState !== WebSocket4.OPEN) {
|
|
4738
|
-
|
|
4739
|
-
"AssemblyAISTT.forceEndpoint: WebSocket is not open"
|
|
4943
|
+
getLogger().debug(
|
|
4944
|
+
"AssemblyAISTT.forceEndpoint: WebSocket is not open \u2014 dropping request (call teardown)."
|
|
4740
4945
|
);
|
|
4946
|
+
return;
|
|
4741
4947
|
}
|
|
4742
4948
|
this.ws.send(JSON.stringify({ type: AssemblyAIClientFrame.FORCE_ENDPOINT }));
|
|
4743
4949
|
}
|
|
@@ -4752,6 +4958,14 @@ var AssemblyAISTT = class _AssemblyAISTT {
|
|
|
4752
4958
|
async close() {
|
|
4753
4959
|
this.closing = true;
|
|
4754
4960
|
if (!this.ws) return;
|
|
4961
|
+
if (this.chunkBufferBytes > 0 && this.ws.readyState === WebSocket4.OPEN) {
|
|
4962
|
+
try {
|
|
4963
|
+
this.ws.send(Buffer.concat(this.chunkBuffer, this.chunkBufferBytes));
|
|
4964
|
+
} catch {
|
|
4965
|
+
}
|
|
4966
|
+
this.chunkBuffer = [];
|
|
4967
|
+
this.chunkBufferBytes = 0;
|
|
4968
|
+
}
|
|
4755
4969
|
try {
|
|
4756
4970
|
this.ws.send(JSON.stringify({ type: AssemblyAIClientFrame.TERMINATE }));
|
|
4757
4971
|
} catch {
|
|
@@ -5215,9 +5429,11 @@ var PLAN_REQUIRED_MSG = "ElevenLabs WS streaming requires a Pro plan or higher (
|
|
|
5215
5429
|
function sanitiseLogStr(value, limit = 200) {
|
|
5216
5430
|
return String(value).replace(/[\r\n\x00]/g, " ").slice(0, limit);
|
|
5217
5431
|
}
|
|
5218
|
-
var
|
|
5432
|
+
var CARRIER_NATIVE_FORMAT2 = {
|
|
5219
5433
|
twilio: "ulaw_8000",
|
|
5220
|
-
telnyx: "pcm_16000"
|
|
5434
|
+
telnyx: "pcm_16000",
|
|
5435
|
+
// Plivo streams mulaw 8 kHz (we pin contentType in the answer XML).
|
|
5436
|
+
plivo: "ulaw_8000"
|
|
5221
5437
|
};
|
|
5222
5438
|
var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
5223
5439
|
static providerKey = "elevenlabs_ws";
|
|
@@ -5301,7 +5517,7 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
5301
5517
|
*/
|
|
5302
5518
|
setTelephonyCarrier(carrier) {
|
|
5303
5519
|
if (this._outputFormatExplicit) return;
|
|
5304
|
-
const native =
|
|
5520
|
+
const native = CARRIER_NATIVE_FORMAT2[carrier];
|
|
5305
5521
|
if (!native) return;
|
|
5306
5522
|
this._outputFormat = native;
|
|
5307
5523
|
}
|
|
@@ -5953,7 +6169,7 @@ var TTS3 = class extends OpenAITTS {
|
|
|
5953
6169
|
opts.model ?? "gpt-4o-mini-tts",
|
|
5954
6170
|
opts.instructions ?? null,
|
|
5955
6171
|
opts.speed ?? null,
|
|
5956
|
-
opts.antiAlias ??
|
|
6172
|
+
opts.antiAlias ?? true
|
|
5957
6173
|
);
|
|
5958
6174
|
}
|
|
5959
6175
|
};
|
|
@@ -6127,7 +6343,6 @@ init_esm_shims();
|
|
|
6127
6343
|
// src/providers/inworld-tts.ts
|
|
6128
6344
|
init_esm_shims();
|
|
6129
6345
|
var INWORLD_BASE_URL = "https://api.inworld.ai/tts/v1/voice:stream";
|
|
6130
|
-
var INWORLD_VOICES_URL = "https://api.inworld.ai/tts/v1/voices";
|
|
6131
6346
|
var InworldModel = {
|
|
6132
6347
|
TTS_2: "inworld-tts-2",
|
|
6133
6348
|
TTS_1_5_MAX: "inworld-tts-1.5-max",
|
|
@@ -6216,7 +6431,8 @@ var InworldTTS = class {
|
|
|
6216
6431
|
*/
|
|
6217
6432
|
async warmup() {
|
|
6218
6433
|
try {
|
|
6219
|
-
|
|
6434
|
+
const voicesUrl = new URL(this.baseUrl).origin + "/tts/v1/voices";
|
|
6435
|
+
await fetch(voicesUrl, {
|
|
6220
6436
|
method: "GET",
|
|
6221
6437
|
headers: {
|
|
6222
6438
|
Authorization: `Basic ${this.authToken}`
|
|
@@ -6473,58 +6689,87 @@ var AnthropicLLMProvider = class {
|
|
|
6473
6689
|
const toolIndexByBlock = /* @__PURE__ */ new Map();
|
|
6474
6690
|
const toolIdByBlock = /* @__PURE__ */ new Map();
|
|
6475
6691
|
let nextIndex = 0;
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
const
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
continue;
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
continue;
|
|
6508
|
-
}
|
|
6509
|
-
if (event.type === "content_block_delta") {
|
|
6510
|
-
if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
6511
|
-
yield { type: "text", content: event.delta.text };
|
|
6692
|
+
let inputTokens = 0;
|
|
6693
|
+
let outputTokens = 0;
|
|
6694
|
+
let cacheReadTokens = 0;
|
|
6695
|
+
let cacheWriteTokens = 0;
|
|
6696
|
+
try {
|
|
6697
|
+
while (true) {
|
|
6698
|
+
const { done, value } = await reader.read();
|
|
6699
|
+
if (done) break;
|
|
6700
|
+
buffer += decoder.decode(value, { stream: true });
|
|
6701
|
+
const lines = buffer.split("\n");
|
|
6702
|
+
buffer = lines.pop() || "";
|
|
6703
|
+
for (const line of lines) {
|
|
6704
|
+
const trimmed = line.trim();
|
|
6705
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
6706
|
+
const data = trimmed.slice(6);
|
|
6707
|
+
if (!data || data === "[DONE]") continue;
|
|
6708
|
+
let event;
|
|
6709
|
+
try {
|
|
6710
|
+
event = JSON.parse(data);
|
|
6711
|
+
} catch {
|
|
6712
|
+
continue;
|
|
6713
|
+
}
|
|
6714
|
+
if (event.type === "message_start" && event.message?.usage) {
|
|
6715
|
+
const u = event.message.usage;
|
|
6716
|
+
if (u.input_tokens) inputTokens = u.input_tokens;
|
|
6717
|
+
if (u.cache_creation_input_tokens) cacheWriteTokens = u.cache_creation_input_tokens;
|
|
6718
|
+
if (u.cache_read_input_tokens) cacheReadTokens = u.cache_read_input_tokens;
|
|
6719
|
+
continue;
|
|
6720
|
+
}
|
|
6721
|
+
if (event.type === "message_delta" && event.usage?.output_tokens) {
|
|
6722
|
+
outputTokens = event.usage.output_tokens;
|
|
6512
6723
|
continue;
|
|
6513
6724
|
}
|
|
6514
|
-
if (event.
|
|
6725
|
+
if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
6515
6726
|
const blockIdx = event.index ?? 0;
|
|
6516
|
-
const
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6727
|
+
const toolId = event.content_block.id ?? "";
|
|
6728
|
+
const toolName = event.content_block.name ?? "";
|
|
6729
|
+
const patterIndex = nextIndex++;
|
|
6730
|
+
toolIndexByBlock.set(blockIdx, patterIndex);
|
|
6731
|
+
toolIdByBlock.set(blockIdx, toolId);
|
|
6732
|
+
yield {
|
|
6733
|
+
type: "tool_call",
|
|
6734
|
+
index: patterIndex,
|
|
6735
|
+
id: toolId,
|
|
6736
|
+
name: toolName,
|
|
6737
|
+
arguments: ""
|
|
6738
|
+
};
|
|
6739
|
+
continue;
|
|
6740
|
+
}
|
|
6741
|
+
if (event.type === "content_block_delta") {
|
|
6742
|
+
if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
6743
|
+
yield { type: "text", content: event.delta.text };
|
|
6744
|
+
continue;
|
|
6745
|
+
}
|
|
6746
|
+
if (event.delta?.type === "input_json_delta" && event.delta.partial_json) {
|
|
6747
|
+
const blockIdx = event.index ?? 0;
|
|
6748
|
+
const patterIndex = toolIndexByBlock.get(blockIdx);
|
|
6749
|
+
if (patterIndex !== void 0) {
|
|
6750
|
+
yield {
|
|
6751
|
+
type: "tool_call",
|
|
6752
|
+
index: patterIndex,
|
|
6753
|
+
id: toolIdByBlock.get(blockIdx),
|
|
6754
|
+
arguments: event.delta.partial_json
|
|
6755
|
+
};
|
|
6756
|
+
}
|
|
6524
6757
|
}
|
|
6525
6758
|
}
|
|
6526
6759
|
}
|
|
6527
6760
|
}
|
|
6761
|
+
} finally {
|
|
6762
|
+
reader.cancel().catch(() => {
|
|
6763
|
+
});
|
|
6764
|
+
}
|
|
6765
|
+
if (inputTokens > 0 || outputTokens > 0 || cacheReadTokens > 0 || cacheWriteTokens > 0) {
|
|
6766
|
+
yield {
|
|
6767
|
+
type: "usage",
|
|
6768
|
+
inputTokens,
|
|
6769
|
+
outputTokens,
|
|
6770
|
+
cacheReadInputTokens: cacheReadTokens,
|
|
6771
|
+
cacheWriteInputTokens: cacheWriteTokens
|
|
6772
|
+
};
|
|
6528
6773
|
}
|
|
6529
6774
|
yield { type: "done" };
|
|
6530
6775
|
}
|
|
@@ -6584,16 +6829,17 @@ function toAnthropicMessages(messages) {
|
|
|
6584
6829
|
}
|
|
6585
6830
|
if (role === "tool") {
|
|
6586
6831
|
const contentStr = typeof rawMsg.content === "string" ? rawMsg.content : JSON.stringify(rawMsg.content);
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6832
|
+
const toolResultBlock = {
|
|
6833
|
+
type: "tool_result",
|
|
6834
|
+
tool_use_id: rawMsg.tool_call_id ?? "",
|
|
6835
|
+
content: contentStr
|
|
6836
|
+
};
|
|
6837
|
+
const prev = out.length > 0 ? out[out.length - 1] : void 0;
|
|
6838
|
+
if (prev && prev.role === "user" && Array.isArray(prev.content) && prev.content.length > 0 && prev.content.every((b) => b["type"] === "tool_result")) {
|
|
6839
|
+
prev.content.push(toolResultBlock);
|
|
6840
|
+
} else {
|
|
6841
|
+
out.push({ role: "user", content: [toolResultBlock] });
|
|
6842
|
+
}
|
|
6597
6843
|
continue;
|
|
6598
6844
|
}
|
|
6599
6845
|
}
|
|
@@ -6733,50 +6979,55 @@ async function* parseOpenAISseStream(response) {
|
|
|
6733
6979
|
if (!reader) return;
|
|
6734
6980
|
const decoder = new TextDecoder();
|
|
6735
6981
|
let buffer = "";
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
const
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
type: "usage",
|
|
6758
|
-
inputTokens: usage.prompt_tokens,
|
|
6759
|
-
outputTokens: usage.completion_tokens,
|
|
6760
|
-
cacheReadInputTokens: cached
|
|
6761
|
-
};
|
|
6762
|
-
}
|
|
6763
|
-
const delta = chunk.choices?.[0]?.delta;
|
|
6764
|
-
if (!delta) continue;
|
|
6765
|
-
if (delta.content) {
|
|
6766
|
-
yield { type: "text", content: delta.content };
|
|
6767
|
-
}
|
|
6768
|
-
if (delta.tool_calls) {
|
|
6769
|
-
for (const tc of delta.tool_calls) {
|
|
6982
|
+
try {
|
|
6983
|
+
while (true) {
|
|
6984
|
+
const { done, value } = await reader.read();
|
|
6985
|
+
if (done) break;
|
|
6986
|
+
buffer += decoder.decode(value, { stream: true });
|
|
6987
|
+
const lines = buffer.split("\n");
|
|
6988
|
+
buffer = lines.pop() || "";
|
|
6989
|
+
for (const line of lines) {
|
|
6990
|
+
const trimmed = line.trim();
|
|
6991
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
6992
|
+
const data = trimmed.slice(6);
|
|
6993
|
+
if (data === "[DONE]") continue;
|
|
6994
|
+
let chunk;
|
|
6995
|
+
try {
|
|
6996
|
+
chunk = JSON.parse(data);
|
|
6997
|
+
} catch {
|
|
6998
|
+
continue;
|
|
6999
|
+
}
|
|
7000
|
+
const usage = chunk.usage ?? chunk.x_groq?.usage;
|
|
7001
|
+
if (usage) {
|
|
7002
|
+
const cached = chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0;
|
|
6770
7003
|
yield {
|
|
6771
|
-
type: "
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
arguments: tc.function?.arguments
|
|
7004
|
+
type: "usage",
|
|
7005
|
+
inputTokens: usage.prompt_tokens,
|
|
7006
|
+
outputTokens: usage.completion_tokens,
|
|
7007
|
+
cacheReadInputTokens: cached
|
|
6776
7008
|
};
|
|
6777
7009
|
}
|
|
7010
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
7011
|
+
if (!delta) continue;
|
|
7012
|
+
if (delta.content) {
|
|
7013
|
+
yield { type: "text", content: delta.content };
|
|
7014
|
+
}
|
|
7015
|
+
if (delta.tool_calls) {
|
|
7016
|
+
for (const tc of delta.tool_calls) {
|
|
7017
|
+
yield {
|
|
7018
|
+
type: "tool_call",
|
|
7019
|
+
index: tc.index,
|
|
7020
|
+
id: tc.id,
|
|
7021
|
+
name: tc.function?.name,
|
|
7022
|
+
arguments: tc.function?.arguments
|
|
7023
|
+
};
|
|
7024
|
+
}
|
|
7025
|
+
}
|
|
6778
7026
|
}
|
|
6779
7027
|
}
|
|
7028
|
+
} finally {
|
|
7029
|
+
reader.cancel().catch(() => {
|
|
7030
|
+
});
|
|
6780
7031
|
}
|
|
6781
7032
|
}
|
|
6782
7033
|
|
|
@@ -6941,11 +7192,21 @@ var CerebrasLLMProvider = class {
|
|
|
6941
7192
|
}
|
|
6942
7193
|
const advisoryMs = parseRateLimitResetMs(response.headers);
|
|
6943
7194
|
const exponentialMs = RETRY_BACKOFF_BASE_MS * Math.pow(2, attempt);
|
|
6944
|
-
const delayMs = Math.max(advisoryMs, exponentialMs);
|
|
7195
|
+
const delayMs = Math.min(5e3, Math.max(advisoryMs, exponentialMs));
|
|
6945
7196
|
getLogger().warn(
|
|
6946
7197
|
`Cerebras API ${response.status} (attempt ${attempt + 1}/${maxAttempts}); retrying after ${delayMs}ms`
|
|
6947
7198
|
);
|
|
6948
|
-
await new Promise((
|
|
7199
|
+
await new Promise((resolve, reject) => {
|
|
7200
|
+
const t = setTimeout(resolve, delayMs);
|
|
7201
|
+
opts?.signal?.addEventListener(
|
|
7202
|
+
"abort",
|
|
7203
|
+
() => {
|
|
7204
|
+
clearTimeout(t);
|
|
7205
|
+
reject(opts.signal.reason);
|
|
7206
|
+
},
|
|
7207
|
+
{ once: true }
|
|
7208
|
+
);
|
|
7209
|
+
});
|
|
6949
7210
|
}
|
|
6950
7211
|
throw new PatterError(`Cerebras API error ${lastStatus}: ${lastErrText || "request failed"}`);
|
|
6951
7212
|
}
|
|
@@ -7106,47 +7367,52 @@ var GoogleLLMProvider = class {
|
|
|
7106
7367
|
let buffer = "";
|
|
7107
7368
|
let nextIndex = 0;
|
|
7108
7369
|
let lastUsage;
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
const
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
}
|
|
7126
|
-
if (payload.usageMetadata) {
|
|
7127
|
-
lastUsage = payload.usageMetadata;
|
|
7128
|
-
}
|
|
7129
|
-
const candidate = payload.candidates?.[0];
|
|
7130
|
-
const parts = candidate?.content?.parts ?? [];
|
|
7131
|
-
for (const part of parts) {
|
|
7132
|
-
if (part.functionCall) {
|
|
7133
|
-
const args = part.functionCall.args ?? {};
|
|
7134
|
-
const callId = part.functionCall.id ?? `gemini_call_${nextIndex}`;
|
|
7135
|
-
yield {
|
|
7136
|
-
type: "tool_call",
|
|
7137
|
-
index: nextIndex,
|
|
7138
|
-
id: callId,
|
|
7139
|
-
name: part.functionCall.name ?? "",
|
|
7140
|
-
arguments: JSON.stringify(args)
|
|
7141
|
-
};
|
|
7142
|
-
nextIndex++;
|
|
7370
|
+
try {
|
|
7371
|
+
while (true) {
|
|
7372
|
+
const { done, value } = await reader.read();
|
|
7373
|
+
if (done) break;
|
|
7374
|
+
buffer += decoder.decode(value, { stream: true });
|
|
7375
|
+
const lines = buffer.split("\n");
|
|
7376
|
+
buffer = lines.pop() || "";
|
|
7377
|
+
for (const line of lines) {
|
|
7378
|
+
const trimmed = line.trim();
|
|
7379
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
7380
|
+
const data = trimmed.slice(6);
|
|
7381
|
+
if (!data) continue;
|
|
7382
|
+
let payload;
|
|
7383
|
+
try {
|
|
7384
|
+
payload = JSON.parse(data);
|
|
7385
|
+
} catch {
|
|
7143
7386
|
continue;
|
|
7144
7387
|
}
|
|
7145
|
-
if (
|
|
7146
|
-
|
|
7388
|
+
if (payload.usageMetadata) {
|
|
7389
|
+
lastUsage = payload.usageMetadata;
|
|
7390
|
+
}
|
|
7391
|
+
const candidate = payload.candidates?.[0];
|
|
7392
|
+
const parts = candidate?.content?.parts ?? [];
|
|
7393
|
+
for (const part of parts) {
|
|
7394
|
+
if (part.functionCall) {
|
|
7395
|
+
const args = part.functionCall.args ?? {};
|
|
7396
|
+
const callId = part.functionCall.id ?? `gemini_call_${nextIndex}`;
|
|
7397
|
+
yield {
|
|
7398
|
+
type: "tool_call",
|
|
7399
|
+
index: nextIndex,
|
|
7400
|
+
id: callId,
|
|
7401
|
+
name: part.functionCall.name ?? "",
|
|
7402
|
+
arguments: JSON.stringify(args)
|
|
7403
|
+
};
|
|
7404
|
+
nextIndex++;
|
|
7405
|
+
continue;
|
|
7406
|
+
}
|
|
7407
|
+
if (part.text) {
|
|
7408
|
+
yield { type: "text", content: part.text };
|
|
7409
|
+
}
|
|
7147
7410
|
}
|
|
7148
7411
|
}
|
|
7149
7412
|
}
|
|
7413
|
+
} finally {
|
|
7414
|
+
reader.cancel().catch(() => {
|
|
7415
|
+
});
|
|
7150
7416
|
}
|
|
7151
7417
|
if (lastUsage) {
|
|
7152
7418
|
yield {
|
|
@@ -7240,7 +7506,17 @@ function toGeminiContents(messages) {
|
|
|
7240
7506
|
continue;
|
|
7241
7507
|
}
|
|
7242
7508
|
}
|
|
7243
|
-
|
|
7509
|
+
const merged = [];
|
|
7510
|
+
for (const entry of contents) {
|
|
7511
|
+
const prev = merged[merged.length - 1];
|
|
7512
|
+
const isFunctionResponseOnly = (c) => c.role === "user" && c.parts.every((p) => p.functionResponse !== void 0);
|
|
7513
|
+
if (prev && isFunctionResponseOnly(prev) && isFunctionResponseOnly(entry)) {
|
|
7514
|
+
prev.parts.push(...entry.parts);
|
|
7515
|
+
} else {
|
|
7516
|
+
merged.push(entry);
|
|
7517
|
+
}
|
|
7518
|
+
}
|
|
7519
|
+
return { systemInstruction: systemParts.join("\n\n"), contents: merged };
|
|
7244
7520
|
}
|
|
7245
7521
|
|
|
7246
7522
|
// src/llm/google.ts
|
|
@@ -7294,6 +7570,57 @@ function float32ToPcm16(samples) {
|
|
|
7294
7570
|
}
|
|
7295
7571
|
return out;
|
|
7296
7572
|
}
|
|
7573
|
+
var ArbitraryResampler = class {
|
|
7574
|
+
srcRate;
|
|
7575
|
+
dstRate;
|
|
7576
|
+
phase = 0;
|
|
7577
|
+
// fractional position into the current chunk
|
|
7578
|
+
lastSample = 0;
|
|
7579
|
+
// last input sample from the previous chunk
|
|
7580
|
+
hasHistory = false;
|
|
7581
|
+
constructor(srcRate, dstRate) {
|
|
7582
|
+
this.srcRate = srcRate;
|
|
7583
|
+
this.dstRate = dstRate;
|
|
7584
|
+
}
|
|
7585
|
+
/** Process a chunk of PCM16-LE mono audio and return resampled PCM16-LE. */
|
|
7586
|
+
process(pcm) {
|
|
7587
|
+
const sampleCount = Math.floor(pcm.length / 2);
|
|
7588
|
+
if (sampleCount === 0) return Buffer.alloc(0);
|
|
7589
|
+
const step = this.srcRate / this.dstRate;
|
|
7590
|
+
const outArr = [];
|
|
7591
|
+
let phase = this.phase;
|
|
7592
|
+
while (true) {
|
|
7593
|
+
const idx = Math.floor(phase);
|
|
7594
|
+
if (idx >= sampleCount) break;
|
|
7595
|
+
const frac = phase - idx;
|
|
7596
|
+
let s0;
|
|
7597
|
+
let s1;
|
|
7598
|
+
if (idx < 0) {
|
|
7599
|
+
s0 = this.hasHistory ? this.lastSample : 0;
|
|
7600
|
+
s1 = pcm.readInt16LE(0);
|
|
7601
|
+
} else {
|
|
7602
|
+
s0 = pcm.readInt16LE(idx * 2);
|
|
7603
|
+
s1 = idx + 1 < sampleCount ? pcm.readInt16LE((idx + 1) * 2) : s0;
|
|
7604
|
+
}
|
|
7605
|
+
const interp = Math.round(s0 + (s1 - s0) * frac);
|
|
7606
|
+
outArr.push(Math.max(-32768, Math.min(32767, interp)));
|
|
7607
|
+
phase += step;
|
|
7608
|
+
}
|
|
7609
|
+
this.lastSample = pcm.readInt16LE((sampleCount - 1) * 2);
|
|
7610
|
+
this.hasHistory = true;
|
|
7611
|
+
this.phase = phase - sampleCount;
|
|
7612
|
+
const out = Buffer.alloc(outArr.length * 2);
|
|
7613
|
+
for (let j = 0; j < outArr.length; j++) out.writeInt16LE(outArr[j], j * 2);
|
|
7614
|
+
return out;
|
|
7615
|
+
}
|
|
7616
|
+
/** Flush any buffered state and reset. Returns any remaining tail output. */
|
|
7617
|
+
flush() {
|
|
7618
|
+
this.phase = 0;
|
|
7619
|
+
this.lastSample = 0;
|
|
7620
|
+
this.hasHistory = false;
|
|
7621
|
+
return Buffer.alloc(0);
|
|
7622
|
+
}
|
|
7623
|
+
};
|
|
7297
7624
|
var DeepFilterNetFilter = class {
|
|
7298
7625
|
modelPath;
|
|
7299
7626
|
silenceWarnings;
|
|
@@ -7301,8 +7628,9 @@ var DeepFilterNetFilter = class {
|
|
|
7301
7628
|
ort = null;
|
|
7302
7629
|
warned = false;
|
|
7303
7630
|
closed = false;
|
|
7304
|
-
//
|
|
7631
|
+
// Stateful resamplers for src_sr↔48k conversions so chunk-boundary
|
|
7305
7632
|
// samples are not discarded. Lazy-created and torn down on rate change.
|
|
7633
|
+
// Uses ArbitraryResampler which supports any integer rate pair.
|
|
7306
7634
|
_resamplerSrcRate = null;
|
|
7307
7635
|
_upsamplerInst = null;
|
|
7308
7636
|
_downsamplerInst = null;
|
|
@@ -7360,8 +7688,8 @@ var DeepFilterNetFilter = class {
|
|
|
7360
7688
|
try {
|
|
7361
7689
|
if (this._resamplerSrcRate !== sampleRate) {
|
|
7362
7690
|
this._resamplerSrcRate = sampleRate;
|
|
7363
|
-
this._upsamplerInst = new
|
|
7364
|
-
this._downsamplerInst = new
|
|
7691
|
+
this._upsamplerInst = new ArbitraryResampler(sampleRate, DEEPFILTERNET_SR);
|
|
7692
|
+
this._downsamplerInst = new ArbitraryResampler(DEEPFILTERNET_SR, sampleRate);
|
|
7365
7693
|
}
|
|
7366
7694
|
const samples = pcm16ToFloat32(pcmChunk);
|
|
7367
7695
|
const pcm16Up = this._upsamplerInst.process(float32ToPcm16(new Float32Array(samples)));
|
|
@@ -7445,7 +7773,7 @@ var KrispVivaFilter = class {
|
|
|
7445
7773
|
|
|
7446
7774
|
// src/telephony/twilio.ts
|
|
7447
7775
|
init_esm_shims();
|
|
7448
|
-
var
|
|
7776
|
+
var Carrier2 = class {
|
|
7449
7777
|
kind = "twilio";
|
|
7450
7778
|
accountSid;
|
|
7451
7779
|
authToken;
|
|
@@ -7469,7 +7797,7 @@ var Carrier = class {
|
|
|
7469
7797
|
|
|
7470
7798
|
// src/telephony/telnyx.ts
|
|
7471
7799
|
init_esm_shims();
|
|
7472
|
-
var
|
|
7800
|
+
var Carrier3 = class {
|
|
7473
7801
|
kind = "telnyx";
|
|
7474
7802
|
apiKey;
|
|
7475
7803
|
connectionId;
|
|
@@ -7521,6 +7849,17 @@ var Tool = class {
|
|
|
7521
7849
|
parameters;
|
|
7522
7850
|
handler;
|
|
7523
7851
|
webhookUrl;
|
|
7852
|
+
reassurance;
|
|
7853
|
+
/**
|
|
7854
|
+
* Per-tool execution timeout in milliseconds. `undefined` uses the
|
|
7855
|
+
* executor default (10 000 ms). Mirrors Python `timeout_s`.
|
|
7856
|
+
*/
|
|
7857
|
+
timeoutMs;
|
|
7858
|
+
/**
|
|
7859
|
+
* Enable OpenAI strict mode for this tool's function schema. Off by
|
|
7860
|
+
* default. Mirrors Python `strict` on `Tool`.
|
|
7861
|
+
*/
|
|
7862
|
+
strict;
|
|
7524
7863
|
constructor(opts) {
|
|
7525
7864
|
if (!opts.name) {
|
|
7526
7865
|
throw new Error("Tool requires a non-empty name.");
|
|
@@ -7538,6 +7877,9 @@ var Tool = class {
|
|
|
7538
7877
|
this.parameters = opts.parameters ?? { type: "object", properties: {} };
|
|
7539
7878
|
if (hasHandler) this.handler = opts.handler;
|
|
7540
7879
|
if (hasWebhook) this.webhookUrl = opts.webhookUrl;
|
|
7880
|
+
if (opts.reassurance !== void 0) this.reassurance = opts.reassurance;
|
|
7881
|
+
if (opts.timeoutMs !== void 0) this.timeoutMs = opts.timeoutMs;
|
|
7882
|
+
if (opts.strict !== void 0) this.strict = opts.strict;
|
|
7541
7883
|
}
|
|
7542
7884
|
};
|
|
7543
7885
|
function tool(opts) {
|
|
@@ -7696,7 +8038,6 @@ var ChatContext = class _ChatContext {
|
|
|
7696
8038
|
// src/services/ivr.ts
|
|
7697
8039
|
init_esm_shims();
|
|
7698
8040
|
var DTMF_EVENTS = [
|
|
7699
|
-
"0",
|
|
7700
8041
|
"1",
|
|
7701
8042
|
"2",
|
|
7702
8043
|
"3",
|
|
@@ -7706,6 +8047,7 @@ var DTMF_EVENTS = [
|
|
|
7706
8047
|
"7",
|
|
7707
8048
|
"8",
|
|
7708
8049
|
"9",
|
|
8050
|
+
"0",
|
|
7709
8051
|
"*",
|
|
7710
8052
|
"#",
|
|
7711
8053
|
"A",
|
|
@@ -8382,18 +8724,24 @@ var TelnyxAdapter = class {
|
|
|
8382
8724
|
"/number_orders",
|
|
8383
8725
|
orderBody
|
|
8384
8726
|
);
|
|
8385
|
-
const orderId = order.data?.id
|
|
8727
|
+
const orderId = order.data?.id;
|
|
8728
|
+
if (!orderId) throw new Error("TelnyxAdapter: /number_orders returned no order id");
|
|
8386
8729
|
return { phoneNumber: chosen, orderId };
|
|
8387
8730
|
}
|
|
8388
8731
|
/** Attach a number to a Call Control Application. */
|
|
8389
8732
|
async configureNumber(phoneNumber, opts) {
|
|
8390
8733
|
if (!phoneNumber) throw new Error("TelnyxAdapter: phoneNumber is required");
|
|
8391
8734
|
if (!opts.connectionId) throw new Error("TelnyxAdapter: connectionId is required");
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8735
|
+
try {
|
|
8736
|
+
await this.request(
|
|
8737
|
+
"PATCH",
|
|
8738
|
+
`/phone_numbers/${encodeURIComponent(phoneNumber)}/voice`,
|
|
8739
|
+
{ connection_id: opts.connectionId, tech_prefix_enabled: false }
|
|
8740
|
+
);
|
|
8741
|
+
} catch (err) {
|
|
8742
|
+
const status = err instanceof Error ? err.message.replace(/\+\d{7,15}/g, "[REDACTED]") : String(err);
|
|
8743
|
+
throw new Error(`TelnyxAdapter: configureNumber failed: ${status}`);
|
|
8744
|
+
}
|
|
8397
8745
|
}
|
|
8398
8746
|
/**
|
|
8399
8747
|
* Place an outbound call on the Call Control Application.
|
|
@@ -8497,7 +8845,7 @@ var TelnyxSTT = class {
|
|
|
8497
8845
|
/** Stable pricing/dashboard key — read by stream-handler/metrics. */
|
|
8498
8846
|
static providerKey = "telnyx_stt";
|
|
8499
8847
|
ws = null;
|
|
8500
|
-
callbacks =
|
|
8848
|
+
callbacks = /* @__PURE__ */ new Set();
|
|
8501
8849
|
headerSent = false;
|
|
8502
8850
|
/** Open the streaming WebSocket and arm message handlers. */
|
|
8503
8851
|
async connect() {
|
|
@@ -8553,14 +8901,13 @@ var TelnyxSTT = class {
|
|
|
8553
8901
|
}
|
|
8554
8902
|
this.ws.send(audio);
|
|
8555
8903
|
}
|
|
8556
|
-
/** Register a transcript listener
|
|
8904
|
+
/** Register a transcript listener. */
|
|
8557
8905
|
onTranscript(callback) {
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
this.callbacks.push(callback);
|
|
8906
|
+
this.callbacks.add(callback);
|
|
8907
|
+
}
|
|
8908
|
+
/** Unregister a previously-registered transcript listener. */
|
|
8909
|
+
offTranscript(callback) {
|
|
8910
|
+
this.callbacks.delete(callback);
|
|
8564
8911
|
}
|
|
8565
8912
|
/** Close the streaming WebSocket. */
|
|
8566
8913
|
close() {
|
|
@@ -8571,6 +8918,7 @@ var TelnyxSTT = class {
|
|
|
8571
8918
|
}
|
|
8572
8919
|
this.ws = null;
|
|
8573
8920
|
}
|
|
8921
|
+
this.headerSent = false;
|
|
8574
8922
|
}
|
|
8575
8923
|
};
|
|
8576
8924
|
|
|
@@ -8591,6 +8939,7 @@ var TelnyxTTSSampleRate = {
|
|
|
8591
8939
|
HZ_24000: 24e3
|
|
8592
8940
|
};
|
|
8593
8941
|
var DEFAULT_VOICE = TelnyxTTSVoice.NATURAL_HD_ASTRA;
|
|
8942
|
+
var FRAME_TIMEOUT_MS2 = 3e4;
|
|
8594
8943
|
var TelnyxTTS = class {
|
|
8595
8944
|
constructor(apiKey, voice = DEFAULT_VOICE, baseUrl = TELNYX_TTS_WS_URL) {
|
|
8596
8945
|
this.apiKey = apiKey;
|
|
@@ -8618,69 +8967,83 @@ var TelnyxTTS = class {
|
|
|
8618
8967
|
*/
|
|
8619
8968
|
async *synthesizeStream(text) {
|
|
8620
8969
|
const url = `${this.baseUrl}?voice=${encodeURIComponent(this.voice)}`;
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8970
|
+
let ws = null;
|
|
8971
|
+
try {
|
|
8972
|
+
let push2 = function(item) {
|
|
8973
|
+
const w = waiters.shift();
|
|
8974
|
+
if (w) {
|
|
8975
|
+
w(item);
|
|
8976
|
+
} else {
|
|
8977
|
+
queue.push(item);
|
|
8978
|
+
}
|
|
8979
|
+
};
|
|
8980
|
+
var push = push2;
|
|
8981
|
+
ws = new WebSocket8(url, {
|
|
8982
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
8629
8983
|
});
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8984
|
+
await new Promise((resolve, reject) => {
|
|
8985
|
+
const timer = setTimeout(() => reject(new Error("Telnyx TTS connect timeout")), 1e4);
|
|
8986
|
+
ws.once("open", () => {
|
|
8987
|
+
clearTimeout(timer);
|
|
8988
|
+
resolve();
|
|
8989
|
+
});
|
|
8990
|
+
ws.once("error", (err) => {
|
|
8991
|
+
clearTimeout(timer);
|
|
8992
|
+
reject(err);
|
|
8993
|
+
});
|
|
8633
8994
|
});
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
}
|
|
8644
|
-
}
|
|
8645
|
-
ws.on("message", (raw) => {
|
|
8646
|
-
let data;
|
|
8647
|
-
try {
|
|
8648
|
-
data = JSON.parse(raw.toString());
|
|
8649
|
-
} catch {
|
|
8650
|
-
getLogger().warn("TelnyxTTS: received invalid JSON");
|
|
8651
|
-
return;
|
|
8652
|
-
}
|
|
8653
|
-
const audioB64 = data.audio;
|
|
8654
|
-
if (!audioB64) return;
|
|
8655
|
-
try {
|
|
8656
|
-
const audioBytes = Buffer.from(audioB64, "base64");
|
|
8657
|
-
if (audioBytes.length > 0) {
|
|
8658
|
-
push(audioBytes);
|
|
8995
|
+
const queue = [];
|
|
8996
|
+
const waiters = [];
|
|
8997
|
+
ws.on("message", (raw) => {
|
|
8998
|
+
let data;
|
|
8999
|
+
try {
|
|
9000
|
+
data = JSON.parse(raw.toString());
|
|
9001
|
+
} catch {
|
|
9002
|
+
getLogger().warn("TelnyxTTS: received invalid JSON");
|
|
9003
|
+
return;
|
|
8659
9004
|
}
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
9005
|
+
const audioB64 = data.audio;
|
|
9006
|
+
if (!audioB64) return;
|
|
9007
|
+
try {
|
|
9008
|
+
const audioBytes = Buffer.from(audioB64, "base64");
|
|
9009
|
+
if (audioBytes.length > 0) {
|
|
9010
|
+
push2(audioBytes);
|
|
9011
|
+
}
|
|
9012
|
+
} catch {
|
|
9013
|
+
}
|
|
9014
|
+
});
|
|
9015
|
+
ws.on("close", () => {
|
|
9016
|
+
push2(null);
|
|
9017
|
+
});
|
|
9018
|
+
ws.on("error", (err) => {
|
|
9019
|
+
push2({ error: err instanceof Error ? err : new Error(String(err)) });
|
|
9020
|
+
});
|
|
9021
|
+
ws.send(JSON.stringify({ text: " " }));
|
|
9022
|
+
ws.send(JSON.stringify({ text }));
|
|
9023
|
+
ws.send(JSON.stringify({ text: "" }));
|
|
8673
9024
|
while (true) {
|
|
8674
|
-
|
|
9025
|
+
let frameTimer;
|
|
9026
|
+
const item = queue.length > 0 ? queue.shift() : await Promise.race([
|
|
9027
|
+
new Promise((resolve) => waiters.push(resolve)),
|
|
9028
|
+
new Promise((_, reject) => {
|
|
9029
|
+
frameTimer = setTimeout(
|
|
9030
|
+
() => reject(new Error("Telnyx TTS frame timeout")),
|
|
9031
|
+
FRAME_TIMEOUT_MS2
|
|
9032
|
+
);
|
|
9033
|
+
})
|
|
9034
|
+
]).finally(() => {
|
|
9035
|
+
if (frameTimer !== void 0) clearTimeout(frameTimer);
|
|
9036
|
+
});
|
|
8675
9037
|
if (item === null) return;
|
|
8676
9038
|
if (typeof item === "object" && "error" in item) throw item.error;
|
|
8677
9039
|
yield item;
|
|
8678
9040
|
}
|
|
8679
9041
|
} finally {
|
|
8680
9042
|
try {
|
|
8681
|
-
ws
|
|
9043
|
+
ws?.close();
|
|
8682
9044
|
} catch {
|
|
8683
9045
|
}
|
|
9046
|
+
ws?.removeAllListeners();
|
|
8684
9047
|
}
|
|
8685
9048
|
}
|
|
8686
9049
|
};
|
|
@@ -8752,11 +9115,14 @@ export {
|
|
|
8752
9115
|
PRICING_VERSION,
|
|
8753
9116
|
PartialStreamError,
|
|
8754
9117
|
Patter,
|
|
9118
|
+
PatterConfigError,
|
|
8755
9119
|
PatterConnectionError,
|
|
8756
9120
|
PatterError,
|
|
8757
9121
|
PatterTool,
|
|
8758
9122
|
PcmCarry,
|
|
8759
9123
|
PipelineHookExecutor,
|
|
9124
|
+
Carrier as Plivo,
|
|
9125
|
+
PlivoAdapter,
|
|
8760
9126
|
PricingUnit,
|
|
8761
9127
|
ProvisionError,
|
|
8762
9128
|
RateLimitError,
|
|
@@ -8783,7 +9149,7 @@ export {
|
|
|
8783
9149
|
TurnDetectionMode as SpeechmaticsTurnDetectionMode,
|
|
8784
9150
|
StatefulResampler,
|
|
8785
9151
|
Static as StaticTunnel,
|
|
8786
|
-
|
|
9152
|
+
Carrier3 as Telnyx,
|
|
8787
9153
|
TelnyxAdapter,
|
|
8788
9154
|
TelnyxSTT,
|
|
8789
9155
|
TelnyxSTTInputFormat,
|
|
@@ -8794,7 +9160,7 @@ export {
|
|
|
8794
9160
|
TestSession,
|
|
8795
9161
|
TfidfLoopDetector,
|
|
8796
9162
|
Tool,
|
|
8797
|
-
|
|
9163
|
+
Carrier2 as Twilio,
|
|
8798
9164
|
TwilioAdapter,
|
|
8799
9165
|
ULTRAVOX_DEFAULT_API_BASE,
|
|
8800
9166
|
ULTRAVOX_DEFAULT_SR,
|
|
@@ -8837,6 +9203,8 @@ export {
|
|
|
8837
9203
|
mulawToPcm16,
|
|
8838
9204
|
notifyDashboard,
|
|
8839
9205
|
openaiTts,
|
|
9206
|
+
openclawConsult,
|
|
9207
|
+
openclawPostCallNotifier,
|
|
8840
9208
|
pcm16ToMulaw,
|
|
8841
9209
|
resample16kTo8k,
|
|
8842
9210
|
resample24kTo16k,
|