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/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-LE63CSOB.mjs";
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
- } from "./chunk-CL2U3YET.mjs";
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-R2T4JABZ.mjs";
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 resolveLogRoot("auto");
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 Telnyx({...})`."
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 === "twilio" ? "twilio" : "telnyx";
1020
+ const telephonyProvider = carrier.kind;
981
1021
  const wantsCarrierManagement = opts.manageWebhook !== false || wantsCloudflared;
982
1022
  if (wantsCarrierManagement) {
983
- const { autoConfigureCarrier } = await import("./carrier-config-4ZKVYAWV.mjs");
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-RS57BDM6.mjs");
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-CNFARP25.mjs");
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
- /** Place an outbound call via the configured carrier. */
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 (!options.to.startsWith("+")) {
1422
- throw new Error(`'to' must be in E.164 format (e.g., '+1234567890'). Got: '${options.to}'`);
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 _PatterTool {
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
- /** Resolver for the next `call_initiated` SSE event. Only set inside the
2113
- * dial mutex (`dialQueue`), so two parallel `execute()` calls never share
2114
- * it and never lose a dispatch. */
2115
- pendingDial = null;
2116
- /** Mutex that serializes the dial → call_id capture critical section.
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
- /** Start the underlying Patter server. Idempotent. */
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
- /** Stop the underlying Patter server (and reject any pending calls). */
2417
+ /** Best-effort shutdown — tear the Patter server down via `disconnect()`. */
2210
2418
  async stop() {
2211
2419
  if (!this.started) return;
2212
- if (this.metricsStoreRef && this.sseListener) {
2213
- this.metricsStoreRef.off("sse", this.sseListener);
2214
- }
2215
- this.sseListener = null;
2216
- this.metricsStoreRef = null;
2217
- this.pendingDial = null;
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
- /** Place an outbound call and resolve once it ends with the transcript and metrics. */
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
- const callId = await this.acquireCallId(args.to, overrideAgent);
2247
- return new Promise((resolve, reject) => {
2248
- const timer = setTimeout(() => {
2249
- this.pending.delete(callId);
2250
- reject(new Error(`PatterTool.execute: call ${callId} exceeded ${timeoutSec}s timeout`));
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
- this.pending.set(callId, {
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
- const previous = this.dialQueue;
2267
- this.dialQueue = previous.then(() => slot);
2268
- await previous;
2269
- let captureTimer = null;
2465
+ let result;
2270
2466
  try {
2271
- const callIdPromise = new Promise((resolve, reject) => {
2272
- this.pendingDial = resolve;
2273
- captureTimer = setTimeout(() => {
2274
- this.pendingDial = null;
2275
- reject(
2276
- new Error(
2277
- `PatterTool.execute: did not observe call_initiated within ${_PatterTool.DIAL_CAPTURE_TIMEOUT_MS}ms`
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 (captureTimer) clearTimeout(captureTimer);
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
- if (carrier === "twilio") {
2997
- this._outputFormat = ElevenLabsOutputFormat.ULAW_8000;
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 ?? true,
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 (max 10 concurrent listeners). */
4508
+ /** Register a transcript listener. */
4296
4509
  onTranscript(callback) {
4297
- if (this.callbacks.length >= 10) {
4298
- getLogger().warn(
4299
- "SonioxSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback."
4300
- );
4301
- this.callbacks[this.callbacks.length - 1] = callback;
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
- throw new AssemblyAISTTNotConnectedError(
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
- throw new AssemblyAISTTNotConnectedError(
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 CARRIER_NATIVE_FORMAT = {
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 = CARRIER_NATIVE_FORMAT[carrier];
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 ?? false
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
- await fetch(INWORLD_VOICES_URL, {
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
- while (true) {
6477
- const { done, value } = await reader.read();
6478
- if (done) break;
6479
- buffer += decoder.decode(value, { stream: true });
6480
- const lines = buffer.split("\n");
6481
- buffer = lines.pop() || "";
6482
- for (const line of lines) {
6483
- const trimmed = line.trim();
6484
- if (!trimmed.startsWith("data: ")) continue;
6485
- const data = trimmed.slice(6);
6486
- if (!data || data === "[DONE]") continue;
6487
- let event;
6488
- try {
6489
- event = JSON.parse(data);
6490
- } catch {
6491
- continue;
6492
- }
6493
- if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
6494
- const blockIdx = event.index ?? 0;
6495
- const toolId = event.content_block.id ?? "";
6496
- const toolName = event.content_block.name ?? "";
6497
- const patterIndex = nextIndex++;
6498
- toolIndexByBlock.set(blockIdx, patterIndex);
6499
- toolIdByBlock.set(blockIdx, toolId);
6500
- yield {
6501
- type: "tool_call",
6502
- index: patterIndex,
6503
- id: toolId,
6504
- name: toolName,
6505
- arguments: ""
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.delta?.type === "input_json_delta" && event.delta.partial_json) {
6725
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
6515
6726
  const blockIdx = event.index ?? 0;
6516
- const patterIndex = toolIndexByBlock.get(blockIdx);
6517
- if (patterIndex !== void 0) {
6518
- yield {
6519
- type: "tool_call",
6520
- index: patterIndex,
6521
- id: toolIdByBlock.get(blockIdx),
6522
- arguments: event.delta.partial_json
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
- out.push({
6588
- role: "user",
6589
- content: [
6590
- {
6591
- type: "tool_result",
6592
- tool_use_id: rawMsg.tool_call_id ?? "",
6593
- content: contentStr
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
- while (true) {
6737
- const { done, value } = await reader.read();
6738
- if (done) break;
6739
- buffer += decoder.decode(value, { stream: true });
6740
- const lines = buffer.split("\n");
6741
- buffer = lines.pop() || "";
6742
- for (const line of lines) {
6743
- const trimmed = line.trim();
6744
- if (!trimmed || !trimmed.startsWith("data: ")) continue;
6745
- const data = trimmed.slice(6);
6746
- if (data === "[DONE]") continue;
6747
- let chunk;
6748
- try {
6749
- chunk = JSON.parse(data);
6750
- } catch {
6751
- continue;
6752
- }
6753
- const usage = chunk.usage ?? chunk.x_groq?.usage;
6754
- if (usage) {
6755
- const cached = chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0;
6756
- yield {
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: "tool_call",
6772
- index: tc.index,
6773
- id: tc.id,
6774
- name: tc.function?.name,
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((r) => setTimeout(r, delayMs));
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
- while (true) {
7110
- const { done, value } = await reader.read();
7111
- if (done) break;
7112
- buffer += decoder.decode(value, { stream: true });
7113
- const lines = buffer.split("\n");
7114
- buffer = lines.pop() || "";
7115
- for (const line of lines) {
7116
- const trimmed = line.trim();
7117
- if (!trimmed.startsWith("data: ")) continue;
7118
- const data = trimmed.slice(6);
7119
- if (!data) continue;
7120
- let payload;
7121
- try {
7122
- payload = JSON.parse(data);
7123
- } catch {
7124
- continue;
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 (part.text) {
7146
- yield { type: "text", content: part.text };
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
- return { systemInstruction: systemParts.join("\n\n"), contents };
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
- // Fix 5: stateful resamplers for src_sr↔48k conversions so chunk-boundary
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 StatefulResampler({ srcRate: sampleRate, dstRate: DEEPFILTERNET_SR });
7364
- this._downsamplerInst = new StatefulResampler({ srcRate: DEEPFILTERNET_SR, dstRate: sampleRate });
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 Carrier = class {
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 Carrier2 = class {
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
- await this.request(
8393
- "PATCH",
8394
- `/phone_numbers/${encodeURIComponent(phoneNumber)}/voice`,
8395
- { connection_id: opts.connectionId, tech_prefix_enabled: false }
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 (max 10 concurrent listeners). */
8904
+ /** Register a transcript listener. */
8557
8905
  onTranscript(callback) {
8558
- if (this.callbacks.length >= 10) {
8559
- getLogger().warn("TelnyxSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback.");
8560
- this.callbacks[this.callbacks.length - 1] = callback;
8561
- return;
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
- const ws = new WebSocket8(url, {
8622
- headers: { Authorization: `Bearer ${this.apiKey}` }
8623
- });
8624
- await new Promise((resolve, reject) => {
8625
- const timer = setTimeout(() => reject(new Error("Telnyx TTS connect timeout")), 1e4);
8626
- ws.once("open", () => {
8627
- clearTimeout(timer);
8628
- resolve();
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
- ws.once("error", (err) => {
8631
- clearTimeout(timer);
8632
- reject(err);
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
- const queue = [];
8636
- const waiters = [];
8637
- function push(item) {
8638
- const w = waiters.shift();
8639
- if (w) {
8640
- w(item);
8641
- } else {
8642
- queue.push(item);
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
- } catch {
8661
- }
8662
- });
8663
- ws.on("close", () => {
8664
- push(null);
8665
- });
8666
- ws.on("error", (err) => {
8667
- push({ error: err instanceof Error ? err : new Error(String(err)) });
8668
- });
8669
- ws.send(JSON.stringify({ text: " " }));
8670
- ws.send(JSON.stringify({ text }));
8671
- ws.send(JSON.stringify({ text: "" }));
8672
- try {
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
- const item = queue.length > 0 ? queue.shift() : await new Promise((resolve) => waiters.push(resolve));
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.close();
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
- Carrier2 as Telnyx,
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
- Carrier as Twilio,
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,