getpatter 0.6.3 → 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
@@ -19,6 +19,7 @@ import {
19
19
  OpenAILLMProvider,
20
20
  PRICING_LAST_UPDATED,
21
21
  PRICING_VERSION,
22
+ PatterConfigError,
22
23
  PatterConnectionError,
23
24
  PatterError,
24
25
  PipelineHookExecutor,
@@ -52,9 +53,11 @@ import {
52
53
  mergePricing,
53
54
  mountApi,
54
55
  mountDashboard,
56
+ openclawConsult,
57
+ openclawPostCallNotifier,
55
58
  resolveLogRoot,
56
59
  startSpan
57
- } from "./chunk-Z6W5XFWS.mjs";
60
+ } from "./chunk-7IIV3BY4.mjs";
58
61
  import {
59
62
  OpenAIRealtime2Adapter,
60
63
  OpenAIRealtimeAdapter,
@@ -73,8 +76,9 @@ import {
73
76
  pcm16ToMulaw,
74
77
  resample16kTo8k,
75
78
  resample24kTo16k,
76
- resample8kTo16k
77
- } from "./chunk-CL2U3YET.mjs";
79
+ resample8kTo16k,
80
+ validateRealtimeTurnDetection
81
+ } from "./chunk-BO227NTF.mjs";
78
82
  import {
79
83
  MinWordsStrategy,
80
84
  evaluateStrategies,
@@ -89,7 +93,7 @@ import {
89
93
  } from "./chunk-6GR5MHHQ.mjs";
90
94
  import {
91
95
  SileroVAD
92
- } from "./chunk-R2T4JABZ.mjs";
96
+ } from "./chunk-3VVATR6A.mjs";
93
97
  import {
94
98
  __dirname,
95
99
  __require,
@@ -111,6 +115,9 @@ var Realtime = class {
111
115
  voice;
112
116
  reasoningEffort;
113
117
  inputAudioTranscriptionModel;
118
+ noiseReduction;
119
+ turnDetection;
120
+ gateResponseOnTranscript;
114
121
  constructor(opts = {}) {
115
122
  const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
116
123
  if (!key) {
@@ -118,11 +125,20 @@ var Realtime = class {
118
125
  "OpenAI Realtime requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
119
126
  );
120
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);
121
134
  this.apiKey = key;
122
135
  this.model = opts.model ?? "gpt-realtime-mini";
123
136
  this.voice = opts.voice ?? "alloy";
124
137
  this.reasoningEffort = opts.reasoningEffort;
125
138
  this.inputAudioTranscriptionModel = opts.inputAudioTranscriptionModel;
139
+ this.noiseReduction = opts.noiseReduction;
140
+ this.turnDetection = opts.turnDetection;
141
+ this.gateResponseOnTranscript = opts.gateResponseOnTranscript;
126
142
  }
127
143
  };
128
144
 
@@ -135,6 +151,9 @@ var Realtime2 = class {
135
151
  voice;
136
152
  reasoningEffort;
137
153
  inputAudioTranscriptionModel;
154
+ noiseReduction;
155
+ turnDetection;
156
+ gateResponseOnTranscript;
138
157
  constructor(opts = {}) {
139
158
  const key = opts.apiKey ?? process.env.OPENAI_API_KEY;
140
159
  if (!key) {
@@ -142,11 +161,20 @@ var Realtime2 = class {
142
161
  "OpenAI Realtime 2 requires an apiKey. Pass { apiKey: 'sk-...' } or set OPENAI_API_KEY in the environment."
143
162
  );
144
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);
145
170
  this.apiKey = key;
146
171
  this.model = opts.model ?? "gpt-realtime-2";
147
172
  this.voice = opts.voice ?? "alloy";
148
173
  this.reasoningEffort = opts.reasoningEffort;
149
174
  this.inputAudioTranscriptionModel = opts.inputAudioTranscriptionModel;
175
+ this.noiseReduction = opts.noiseReduction;
176
+ this.turnDetection = opts.turnDetection;
177
+ this.gateResponseOnTranscript = opts.gateResponseOnTranscript;
150
178
  }
151
179
  };
152
180
 
@@ -573,7 +601,7 @@ function resolvePersistRoot(persist) {
573
601
  if (typeof persist === "string") return resolveLogRoot(persist);
574
602
  const envRoot = resolveLogRoot();
575
603
  if (envRoot !== null) return envRoot;
576
- return resolveLogRoot("auto");
604
+ return null;
577
605
  }
578
606
  function closeParkedConnections(slot) {
579
607
  if (slot.stt) {
@@ -857,7 +885,12 @@ var Patter = class {
857
885
  ...working,
858
886
  provider: "openai_realtime",
859
887
  model: working.model ?? engine.model,
860
- 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
861
894
  };
862
895
  if (!this.localConfig.openaiKey) {
863
896
  this.localConfig = { ...this.localConfig, openaiKey: engine.apiKey };
@@ -882,6 +915,11 @@ var Patter = class {
882
915
  throw new Error(`provider must be one of: ${valid.join(", ")}. Got: '${working.provider}'`);
883
916
  }
884
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
+ }
885
923
  if (working.llm !== void 0) {
886
924
  const llm = working.llm;
887
925
  if (!llm || typeof llm.stream !== "function") {
@@ -982,7 +1020,7 @@ var Patter = class {
982
1020
  const telephonyProvider = carrier.kind;
983
1021
  const wantsCarrierManagement = opts.manageWebhook !== false || wantsCloudflared;
984
1022
  if (wantsCarrierManagement) {
985
- const { autoConfigureCarrier } = await import("./carrier-config-3WDQXP5J.mjs");
1023
+ const { autoConfigureCarrier } = await import("./carrier-config-7YGNRBPO.mjs");
986
1024
  await autoConfigureCarrier({
987
1025
  telephonyProvider,
988
1026
  twilioSid: carrier.kind === "twilio" ? carrier.accountSid : void 0,
@@ -1020,7 +1058,8 @@ var Patter = class {
1020
1058
  opts.onMetrics,
1021
1059
  opts.pricing,
1022
1060
  opts.dashboard ?? true,
1023
- opts.dashboardToken ?? ""
1061
+ opts.dashboardToken ?? "",
1062
+ opts.allowInsecureDashboard ?? false
1024
1063
  );
1025
1064
  this.embeddedServer.popPrewarmAudio = this.popPrewarmAudio;
1026
1065
  this.embeddedServer.popPrewarmedConnections = this.popPrewarmedConnections;
@@ -1039,7 +1078,7 @@ var Patter = class {
1039
1078
  }
1040
1079
  /** Run the agent in interactive terminal-test mode (no real telephony). */
1041
1080
  async test(opts) {
1042
- const { TestSession: TestSession2 } = await import("./test-mode-MDBQ4ECE.mjs");
1081
+ const { TestSession: TestSession2 } = await import("./test-mode-4QLLWYVV.mjs");
1043
1082
  const session = new TestSession2();
1044
1083
  await session.run({
1045
1084
  agent: opts.agent,
@@ -1218,7 +1257,7 @@ var Patter = class {
1218
1257
  }
1219
1258
  if (wantsRealtimePark) {
1220
1259
  tasks.push((async () => {
1221
- const { OpenAIRealtime2Adapter: OpenAIRealtime2Adapter2 } = await import("./openai-realtime-2-CNFARP25.mjs");
1260
+ const { OpenAIRealtime2Adapter: OpenAIRealtime2Adapter2 } = await import("./openai-realtime-2-L5EKAAUH.mjs");
1222
1261
  const apiKey = process.env.OPENAI_API_KEY ?? "";
1223
1262
  if (!apiKey) {
1224
1263
  getLogger().debug(`Park OpenAI Realtime skipped for ${callId}: no OPENAI_API_KEY`);
@@ -1432,8 +1471,8 @@ var Patter = class {
1432
1471
  if (!options.to) {
1433
1472
  throw new Error("'to' phone number is required");
1434
1473
  }
1435
- if (!options.to.startsWith("+")) {
1436
- 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.");
1437
1476
  }
1438
1477
  if (options.wait && !this.embeddedServer) {
1439
1478
  throw new PatterConnectionError(
@@ -1444,9 +1483,6 @@ var Patter = class {
1444
1483
  let callId = "";
1445
1484
  const effectiveRingTimeout = options.ringTimeout === void 0 ? 25 : options.ringTimeout;
1446
1485
  const wantsAmd = options.machineDetection !== false || Boolean(options.voicemailMessage);
1447
- if (this.embeddedServer) {
1448
- this.embeddedServer.onMachineDetection = options.onMachineDetection;
1449
- }
1450
1486
  if (options.agent.prewarm !== false) {
1451
1487
  this.spawnProviderWarmup(options.agent);
1452
1488
  }
@@ -1491,6 +1527,12 @@ var Patter = class {
1491
1527
  };
1492
1528
  if (this.embeddedServer) {
1493
1529
  this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
1530
+ if (options.onMachineDetection) {
1531
+ this.embeddedServer.onMachineDetectionByCallSid.set(
1532
+ telnyxCallId,
1533
+ options.onMachineDetection
1534
+ );
1535
+ }
1494
1536
  }
1495
1537
  try {
1496
1538
  const { notifyDashboard: notifyDashboard2 } = await import("./persistence-LVIAHESK.mjs");
@@ -1556,6 +1598,12 @@ var Patter = class {
1556
1598
  };
1557
1599
  if (this.embeddedServer) {
1558
1600
  this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
1601
+ if (options.onMachineDetection) {
1602
+ this.embeddedServer.onMachineDetectionByCallSid.set(
1603
+ plivoCallId,
1604
+ options.onMachineDetection
1605
+ );
1606
+ }
1559
1607
  }
1560
1608
  try {
1561
1609
  const { notifyDashboard: notifyDashboard2 } = await import("./persistence-LVIAHESK.mjs");
@@ -1625,6 +1673,12 @@ var Patter = class {
1625
1673
  };
1626
1674
  if (this.embeddedServer) {
1627
1675
  this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
1676
+ if (options.onMachineDetection) {
1677
+ this.embeddedServer.onMachineDetectionByCallSid.set(
1678
+ twilioCallSid,
1679
+ options.onMachineDetection
1680
+ );
1681
+ }
1628
1682
  if (twilioNotificationsPath) {
1629
1683
  getLogger().info(
1630
1684
  `Outbound call ${twilioCallSid} placed. Twilio notifications: https://api.twilio.com${twilioNotificationsPath} (check here if the call drops with no audio).`
@@ -2105,8 +2159,8 @@ var FallbackLLMProvider = class {
2105
2159
  * markers are filtered out so callers can concatenate the yielded strings
2106
2160
  * directly.
2107
2161
  */
2108
- async *completeStream(messages, tools) {
2109
- 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)) {
2110
2164
  if (chunk.type === "text") {
2111
2165
  yield chunk.content ?? "";
2112
2166
  }
@@ -2116,14 +2170,15 @@ var FallbackLLMProvider = class {
2116
2170
  // LLMProvider implementation
2117
2171
  // -----------------------------------------------------------------------
2118
2172
  /** Streaming entry point — yields chunks from the first provider that succeeds. */
2119
- async *stream(messages, tools) {
2173
+ async *stream(messages, tools, opts) {
2120
2174
  const errors = [];
2121
2175
  const result = yield* this.tryProviders(
2122
2176
  messages,
2123
2177
  tools,
2124
2178
  /* availableOnly */
2125
2179
  true,
2126
- errors
2180
+ errors,
2181
+ opts
2127
2182
  );
2128
2183
  if (result === "done") return;
2129
2184
  getLogger().warn(
@@ -2134,7 +2189,8 @@ var FallbackLLMProvider = class {
2134
2189
  tools,
2135
2190
  /* availableOnly */
2136
2191
  false,
2137
- errors
2192
+ errors,
2193
+ opts
2138
2194
  );
2139
2195
  if (retryResult === "done") return;
2140
2196
  throw new AllProvidersFailedError(
@@ -2144,7 +2200,7 @@ var FallbackLLMProvider = class {
2144
2200
  // -----------------------------------------------------------------------
2145
2201
  // Internals
2146
2202
  // -----------------------------------------------------------------------
2147
- async *tryProviders(messages, tools, availableOnly, errors) {
2203
+ async *tryProviders(messages, tools, availableOnly, errors, opts) {
2148
2204
  for (let i = 0; i < this.providers.length; i++) {
2149
2205
  if (availableOnly && !this.availability[i]) continue;
2150
2206
  for (let attempt = 0; attempt < this.maxRetryPerProvider; attempt++) {
@@ -2153,7 +2209,7 @@ var FallbackLLMProvider = class {
2153
2209
  `FallbackLLMProvider: trying provider ${i}${attempt > 0 ? ` (retry ${attempt})` : ""}`
2154
2210
  );
2155
2211
  let yieldedTokens = false;
2156
- const gen = this.providers[i].stream(messages, tools);
2212
+ const gen = this.providers[i].stream(messages, tools, opts);
2157
2213
  while (true) {
2158
2214
  let iterResult;
2159
2215
  try {
@@ -2273,6 +2329,11 @@ var PatterTool = class {
2273
2329
  maxDurationSec;
2274
2330
  recording;
2275
2331
  started = false;
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;
2276
2337
  constructor(opts) {
2277
2338
  if (!opts.phone) {
2278
2339
  throw new Error("PatterTool: `phone` (a Patter instance) is required.");
@@ -2324,8 +2385,21 @@ var PatterTool = class {
2324
2385
  * `serve()` provides here. No `onCallEnd` callback is wired: the SDK's own
2325
2386
  * per-callId completion registry resolves the result, so the user's
2326
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()`.
2327
2391
  */
2328
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() {
2329
2403
  if (this.started) return;
2330
2404
  if (!this.agent) {
2331
2405
  throw new Error(
@@ -2351,6 +2425,7 @@ var PatterTool = class {
2351
2425
  }
2352
2426
  }
2353
2427
  this.started = false;
2428
+ this.startPromise = null;
2354
2429
  }
2355
2430
  // --- Execution ----------------------------------------------------------
2356
2431
  /**
@@ -2715,7 +2790,8 @@ var UltravoxRealtimeAdapter = class {
2715
2790
  "X-API-Key": this.apiKey,
2716
2791
  "Content-Type": "application/json"
2717
2792
  },
2718
- body: JSON.stringify(body)
2793
+ body: JSON.stringify(body),
2794
+ signal: AbortSignal.timeout(15e3)
2719
2795
  });
2720
2796
  if (!resp.ok) {
2721
2797
  const text = await resp.text().catch(() => "");
@@ -2726,12 +2802,36 @@ var UltravoxRealtimeAdapter = class {
2726
2802
  this.ws = new WebSocket(call.joinUrl);
2727
2803
  await new Promise((resolve, reject) => {
2728
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);
2729
2818
  const onOpen = () => {
2819
+ if (settled) return;
2820
+ settled = true;
2821
+ clearTimeout(timer);
2730
2822
  ws.off("error", onError);
2731
2823
  resolve();
2732
2824
  };
2733
2825
  const onError = (err) => {
2826
+ if (settled) return;
2827
+ settled = true;
2828
+ clearTimeout(timer);
2734
2829
  ws.off("open", onOpen);
2830
+ this.ws = null;
2831
+ try {
2832
+ ws.close();
2833
+ } catch {
2834
+ }
2735
2835
  reject(err);
2736
2836
  };
2737
2837
  ws.once("open", onOpen);
@@ -3570,7 +3670,7 @@ var STT = class extends DeepgramSTT {
3570
3670
  {
3571
3671
  endpointingMs: opts.endpointingMs ?? 150,
3572
3672
  utteranceEndMs: opts.utteranceEndMs === null ? null : opts.utteranceEndMs ?? 1e3,
3573
- smartFormat: opts.smartFormat ?? true,
3673
+ smartFormat: opts.smartFormat ?? false,
3574
3674
  interimResults: opts.interimResults ?? true,
3575
3675
  ...opts.vadEvents !== void 0 ? { vadEvents: opts.vadEvents } : {}
3576
3676
  }
@@ -3888,7 +3988,7 @@ var CartesiaSTT = class {
3888
3988
  });
3889
3989
  ws.once("error", (err) => {
3890
3990
  clearTimeout(timer);
3891
- reject(err);
3991
+ reject(new Error(`Cartesia STT park connect failed: ${describeWarmupError(err)}`));
3892
3992
  });
3893
3993
  });
3894
3994
  return ws;
@@ -4243,7 +4343,7 @@ var SonioxSTT = class _SonioxSTT {
4243
4343
  /** Stable pricing/dashboard key — read by stream-handler/metrics. */
4244
4344
  static providerKey = "soniox";
4245
4345
  ws = null;
4246
- callbacks = [];
4346
+ callbacks = /* @__PURE__ */ new Set();
4247
4347
  final = new TokenAccumulator();
4248
4348
  keepaliveTimer = null;
4249
4349
  apiKey;
@@ -4405,16 +4505,13 @@ var SonioxSTT = class _SonioxSTT {
4405
4505
  if (audio.length === 0) return;
4406
4506
  this.ws.send(audio);
4407
4507
  }
4408
- /** Register a transcript listener (max 10 concurrent listeners). */
4508
+ /** Register a transcript listener. */
4409
4509
  onTranscript(callback) {
4410
- if (this.callbacks.length >= 10) {
4411
- getLogger().warn(
4412
- "SonioxSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback."
4413
- );
4414
- this.callbacks[this.callbacks.length - 1] = callback;
4415
- return;
4416
- }
4417
- 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);
4418
4515
  }
4419
4516
  /** Send the empty-frame stream terminator and close the WebSocket. */
4420
4517
  close() {
@@ -4495,12 +4592,6 @@ var VALID_DOMAINS = /* @__PURE__ */ new Set([
4495
4592
  AssemblyAIDomain.GENERAL,
4496
4593
  AssemblyAIDomain.MEDICAL_V1
4497
4594
  ]);
4498
- var AssemblyAISTTNotConnectedError = class extends Error {
4499
- constructor(message = "AssemblyAISTT is not connected") {
4500
- super(message);
4501
- this.name = "AssemblyAISTTNotConnectedError";
4502
- }
4503
- };
4504
4595
  var AssemblyAISTT = class _AssemblyAISTT {
4505
4596
  constructor(apiKey, options = {}) {
4506
4597
  this.apiKey = apiKey;
@@ -4824,9 +4915,10 @@ var AssemblyAISTT = class _AssemblyAISTT {
4824
4915
  */
4825
4916
  updateConfiguration(params) {
4826
4917
  if (!this.ws || this.ws.readyState !== WebSocket4.OPEN) {
4827
- throw new AssemblyAISTTNotConnectedError(
4828
- "AssemblyAISTT.updateConfiguration: WebSocket is not open"
4918
+ getLogger().debug(
4919
+ "AssemblyAISTT.updateConfiguration: WebSocket is not open \u2014 dropping update (call teardown)."
4829
4920
  );
4921
+ return;
4830
4922
  }
4831
4923
  const payload = {
4832
4924
  type: AssemblyAIClientFrame.UPDATE_CONFIGURATION
@@ -4848,9 +4940,10 @@ var AssemblyAISTT = class _AssemblyAISTT {
4848
4940
  /** Force the server to finalize the current turn (for barge-in). */
4849
4941
  forceEndpoint() {
4850
4942
  if (!this.ws || this.ws.readyState !== WebSocket4.OPEN) {
4851
- throw new AssemblyAISTTNotConnectedError(
4852
- "AssemblyAISTT.forceEndpoint: WebSocket is not open"
4943
+ getLogger().debug(
4944
+ "AssemblyAISTT.forceEndpoint: WebSocket is not open \u2014 dropping request (call teardown)."
4853
4945
  );
4946
+ return;
4854
4947
  }
4855
4948
  this.ws.send(JSON.stringify({ type: AssemblyAIClientFrame.FORCE_ENDPOINT }));
4856
4949
  }
@@ -4865,6 +4958,14 @@ var AssemblyAISTT = class _AssemblyAISTT {
4865
4958
  async close() {
4866
4959
  this.closing = true;
4867
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
+ }
4868
4969
  try {
4869
4970
  this.ws.send(JSON.stringify({ type: AssemblyAIClientFrame.TERMINATE }));
4870
4971
  } catch {
@@ -6068,7 +6169,7 @@ var TTS3 = class extends OpenAITTS {
6068
6169
  opts.model ?? "gpt-4o-mini-tts",
6069
6170
  opts.instructions ?? null,
6070
6171
  opts.speed ?? null,
6071
- opts.antiAlias ?? false
6172
+ opts.antiAlias ?? true
6072
6173
  );
6073
6174
  }
6074
6175
  };
@@ -6242,7 +6343,6 @@ init_esm_shims();
6242
6343
  // src/providers/inworld-tts.ts
6243
6344
  init_esm_shims();
6244
6345
  var INWORLD_BASE_URL = "https://api.inworld.ai/tts/v1/voice:stream";
6245
- var INWORLD_VOICES_URL = "https://api.inworld.ai/tts/v1/voices";
6246
6346
  var InworldModel = {
6247
6347
  TTS_2: "inworld-tts-2",
6248
6348
  TTS_1_5_MAX: "inworld-tts-1.5-max",
@@ -6331,7 +6431,8 @@ var InworldTTS = class {
6331
6431
  */
6332
6432
  async warmup() {
6333
6433
  try {
6334
- await fetch(INWORLD_VOICES_URL, {
6434
+ const voicesUrl = new URL(this.baseUrl).origin + "/tts/v1/voices";
6435
+ await fetch(voicesUrl, {
6335
6436
  method: "GET",
6336
6437
  headers: {
6337
6438
  Authorization: `Basic ${this.authToken}`
@@ -6588,58 +6689,87 @@ var AnthropicLLMProvider = class {
6588
6689
  const toolIndexByBlock = /* @__PURE__ */ new Map();
6589
6690
  const toolIdByBlock = /* @__PURE__ */ new Map();
6590
6691
  let nextIndex = 0;
6591
- while (true) {
6592
- const { done, value } = await reader.read();
6593
- if (done) break;
6594
- buffer += decoder.decode(value, { stream: true });
6595
- const lines = buffer.split("\n");
6596
- buffer = lines.pop() || "";
6597
- for (const line of lines) {
6598
- const trimmed = line.trim();
6599
- if (!trimmed.startsWith("data: ")) continue;
6600
- const data = trimmed.slice(6);
6601
- if (!data || data === "[DONE]") continue;
6602
- let event;
6603
- try {
6604
- event = JSON.parse(data);
6605
- } catch {
6606
- continue;
6607
- }
6608
- if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
6609
- const blockIdx = event.index ?? 0;
6610
- const toolId = event.content_block.id ?? "";
6611
- const toolName = event.content_block.name ?? "";
6612
- const patterIndex = nextIndex++;
6613
- toolIndexByBlock.set(blockIdx, patterIndex);
6614
- toolIdByBlock.set(blockIdx, toolId);
6615
- yield {
6616
- type: "tool_call",
6617
- index: patterIndex,
6618
- id: toolId,
6619
- name: toolName,
6620
- arguments: ""
6621
- };
6622
- continue;
6623
- }
6624
- if (event.type === "content_block_delta") {
6625
- if (event.delta?.type === "text_delta" && event.delta.text) {
6626
- 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;
6627
6719
  continue;
6628
6720
  }
6629
- if (event.delta?.type === "input_json_delta" && event.delta.partial_json) {
6721
+ if (event.type === "message_delta" && event.usage?.output_tokens) {
6722
+ outputTokens = event.usage.output_tokens;
6723
+ continue;
6724
+ }
6725
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
6630
6726
  const blockIdx = event.index ?? 0;
6631
- const patterIndex = toolIndexByBlock.get(blockIdx);
6632
- if (patterIndex !== void 0) {
6633
- yield {
6634
- type: "tool_call",
6635
- index: patterIndex,
6636
- id: toolIdByBlock.get(blockIdx),
6637
- arguments: event.delta.partial_json
6638
- };
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
+ }
6639
6757
  }
6640
6758
  }
6641
6759
  }
6642
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
+ };
6643
6773
  }
6644
6774
  yield { type: "done" };
6645
6775
  }
@@ -6699,16 +6829,17 @@ function toAnthropicMessages(messages) {
6699
6829
  }
6700
6830
  if (role === "tool") {
6701
6831
  const contentStr = typeof rawMsg.content === "string" ? rawMsg.content : JSON.stringify(rawMsg.content);
6702
- out.push({
6703
- role: "user",
6704
- content: [
6705
- {
6706
- type: "tool_result",
6707
- tool_use_id: rawMsg.tool_call_id ?? "",
6708
- content: contentStr
6709
- }
6710
- ]
6711
- });
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
+ }
6712
6843
  continue;
6713
6844
  }
6714
6845
  }
@@ -6848,50 +6979,55 @@ async function* parseOpenAISseStream(response) {
6848
6979
  if (!reader) return;
6849
6980
  const decoder = new TextDecoder();
6850
6981
  let buffer = "";
6851
- while (true) {
6852
- const { done, value } = await reader.read();
6853
- if (done) break;
6854
- buffer += decoder.decode(value, { stream: true });
6855
- const lines = buffer.split("\n");
6856
- buffer = lines.pop() || "";
6857
- for (const line of lines) {
6858
- const trimmed = line.trim();
6859
- if (!trimmed || !trimmed.startsWith("data: ")) continue;
6860
- const data = trimmed.slice(6);
6861
- if (data === "[DONE]") continue;
6862
- let chunk;
6863
- try {
6864
- chunk = JSON.parse(data);
6865
- } catch {
6866
- continue;
6867
- }
6868
- const usage = chunk.usage ?? chunk.x_groq?.usage;
6869
- if (usage) {
6870
- const cached = chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0;
6871
- yield {
6872
- type: "usage",
6873
- inputTokens: usage.prompt_tokens,
6874
- outputTokens: usage.completion_tokens,
6875
- cacheReadInputTokens: cached
6876
- };
6877
- }
6878
- const delta = chunk.choices?.[0]?.delta;
6879
- if (!delta) continue;
6880
- if (delta.content) {
6881
- yield { type: "text", content: delta.content };
6882
- }
6883
- if (delta.tool_calls) {
6884
- 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;
6885
7003
  yield {
6886
- type: "tool_call",
6887
- index: tc.index,
6888
- id: tc.id,
6889
- name: tc.function?.name,
6890
- arguments: tc.function?.arguments
7004
+ type: "usage",
7005
+ inputTokens: usage.prompt_tokens,
7006
+ outputTokens: usage.completion_tokens,
7007
+ cacheReadInputTokens: cached
6891
7008
  };
6892
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
+ }
6893
7026
  }
6894
7027
  }
7028
+ } finally {
7029
+ reader.cancel().catch(() => {
7030
+ });
6895
7031
  }
6896
7032
  }
6897
7033
 
@@ -7056,11 +7192,21 @@ var CerebrasLLMProvider = class {
7056
7192
  }
7057
7193
  const advisoryMs = parseRateLimitResetMs(response.headers);
7058
7194
  const exponentialMs = RETRY_BACKOFF_BASE_MS * Math.pow(2, attempt);
7059
- const delayMs = Math.max(advisoryMs, exponentialMs);
7195
+ const delayMs = Math.min(5e3, Math.max(advisoryMs, exponentialMs));
7060
7196
  getLogger().warn(
7061
7197
  `Cerebras API ${response.status} (attempt ${attempt + 1}/${maxAttempts}); retrying after ${delayMs}ms`
7062
7198
  );
7063
- 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
+ });
7064
7210
  }
7065
7211
  throw new PatterError(`Cerebras API error ${lastStatus}: ${lastErrText || "request failed"}`);
7066
7212
  }
@@ -7221,47 +7367,52 @@ var GoogleLLMProvider = class {
7221
7367
  let buffer = "";
7222
7368
  let nextIndex = 0;
7223
7369
  let lastUsage;
7224
- while (true) {
7225
- const { done, value } = await reader.read();
7226
- if (done) break;
7227
- buffer += decoder.decode(value, { stream: true });
7228
- const lines = buffer.split("\n");
7229
- buffer = lines.pop() || "";
7230
- for (const line of lines) {
7231
- const trimmed = line.trim();
7232
- if (!trimmed.startsWith("data: ")) continue;
7233
- const data = trimmed.slice(6);
7234
- if (!data) continue;
7235
- let payload;
7236
- try {
7237
- payload = JSON.parse(data);
7238
- } catch {
7239
- continue;
7240
- }
7241
- if (payload.usageMetadata) {
7242
- lastUsage = payload.usageMetadata;
7243
- }
7244
- const candidate = payload.candidates?.[0];
7245
- const parts = candidate?.content?.parts ?? [];
7246
- for (const part of parts) {
7247
- if (part.functionCall) {
7248
- const args = part.functionCall.args ?? {};
7249
- const callId = part.functionCall.id ?? `gemini_call_${nextIndex}`;
7250
- yield {
7251
- type: "tool_call",
7252
- index: nextIndex,
7253
- id: callId,
7254
- name: part.functionCall.name ?? "",
7255
- arguments: JSON.stringify(args)
7256
- };
7257
- 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 {
7258
7386
  continue;
7259
7387
  }
7260
- if (part.text) {
7261
- 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
+ }
7262
7410
  }
7263
7411
  }
7264
7412
  }
7413
+ } finally {
7414
+ reader.cancel().catch(() => {
7415
+ });
7265
7416
  }
7266
7417
  if (lastUsage) {
7267
7418
  yield {
@@ -7355,7 +7506,17 @@ function toGeminiContents(messages) {
7355
7506
  continue;
7356
7507
  }
7357
7508
  }
7358
- 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 };
7359
7520
  }
7360
7521
 
7361
7522
  // src/llm/google.ts
@@ -7409,6 +7570,57 @@ function float32ToPcm16(samples) {
7409
7570
  }
7410
7571
  return out;
7411
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
+ };
7412
7624
  var DeepFilterNetFilter = class {
7413
7625
  modelPath;
7414
7626
  silenceWarnings;
@@ -7416,8 +7628,9 @@ var DeepFilterNetFilter = class {
7416
7628
  ort = null;
7417
7629
  warned = false;
7418
7630
  closed = false;
7419
- // Fix 5: stateful resamplers for src_sr↔48k conversions so chunk-boundary
7631
+ // Stateful resamplers for src_sr↔48k conversions so chunk-boundary
7420
7632
  // samples are not discarded. Lazy-created and torn down on rate change.
7633
+ // Uses ArbitraryResampler which supports any integer rate pair.
7421
7634
  _resamplerSrcRate = null;
7422
7635
  _upsamplerInst = null;
7423
7636
  _downsamplerInst = null;
@@ -7475,8 +7688,8 @@ var DeepFilterNetFilter = class {
7475
7688
  try {
7476
7689
  if (this._resamplerSrcRate !== sampleRate) {
7477
7690
  this._resamplerSrcRate = sampleRate;
7478
- this._upsamplerInst = new StatefulResampler({ srcRate: sampleRate, dstRate: DEEPFILTERNET_SR });
7479
- 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);
7480
7693
  }
7481
7694
  const samples = pcm16ToFloat32(pcmChunk);
7482
7695
  const pcm16Up = this._upsamplerInst.process(float32ToPcm16(new Float32Array(samples)));
@@ -7636,6 +7849,17 @@ var Tool = class {
7636
7849
  parameters;
7637
7850
  handler;
7638
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;
7639
7863
  constructor(opts) {
7640
7864
  if (!opts.name) {
7641
7865
  throw new Error("Tool requires a non-empty name.");
@@ -7653,6 +7877,9 @@ var Tool = class {
7653
7877
  this.parameters = opts.parameters ?? { type: "object", properties: {} };
7654
7878
  if (hasHandler) this.handler = opts.handler;
7655
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;
7656
7883
  }
7657
7884
  };
7658
7885
  function tool(opts) {
@@ -7811,7 +8038,6 @@ var ChatContext = class _ChatContext {
7811
8038
  // src/services/ivr.ts
7812
8039
  init_esm_shims();
7813
8040
  var DTMF_EVENTS = [
7814
- "0",
7815
8041
  "1",
7816
8042
  "2",
7817
8043
  "3",
@@ -7821,6 +8047,7 @@ var DTMF_EVENTS = [
7821
8047
  "7",
7822
8048
  "8",
7823
8049
  "9",
8050
+ "0",
7824
8051
  "*",
7825
8052
  "#",
7826
8053
  "A",
@@ -8497,18 +8724,24 @@ var TelnyxAdapter = class {
8497
8724
  "/number_orders",
8498
8725
  orderBody
8499
8726
  );
8500
- const orderId = order.data?.id ?? "";
8727
+ const orderId = order.data?.id;
8728
+ if (!orderId) throw new Error("TelnyxAdapter: /number_orders returned no order id");
8501
8729
  return { phoneNumber: chosen, orderId };
8502
8730
  }
8503
8731
  /** Attach a number to a Call Control Application. */
8504
8732
  async configureNumber(phoneNumber, opts) {
8505
8733
  if (!phoneNumber) throw new Error("TelnyxAdapter: phoneNumber is required");
8506
8734
  if (!opts.connectionId) throw new Error("TelnyxAdapter: connectionId is required");
8507
- await this.request(
8508
- "PATCH",
8509
- `/phone_numbers/${encodeURIComponent(phoneNumber)}/voice`,
8510
- { connection_id: opts.connectionId, tech_prefix_enabled: false }
8511
- );
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
+ }
8512
8745
  }
8513
8746
  /**
8514
8747
  * Place an outbound call on the Call Control Application.
@@ -8612,7 +8845,7 @@ var TelnyxSTT = class {
8612
8845
  /** Stable pricing/dashboard key — read by stream-handler/metrics. */
8613
8846
  static providerKey = "telnyx_stt";
8614
8847
  ws = null;
8615
- callbacks = [];
8848
+ callbacks = /* @__PURE__ */ new Set();
8616
8849
  headerSent = false;
8617
8850
  /** Open the streaming WebSocket and arm message handlers. */
8618
8851
  async connect() {
@@ -8668,14 +8901,13 @@ var TelnyxSTT = class {
8668
8901
  }
8669
8902
  this.ws.send(audio);
8670
8903
  }
8671
- /** Register a transcript listener (max 10 concurrent listeners). */
8904
+ /** Register a transcript listener. */
8672
8905
  onTranscript(callback) {
8673
- if (this.callbacks.length >= 10) {
8674
- getLogger().warn("TelnyxSTT: maximum of 10 onTranscript callbacks reached; replacing the last callback.");
8675
- this.callbacks[this.callbacks.length - 1] = callback;
8676
- return;
8677
- }
8678
- 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);
8679
8911
  }
8680
8912
  /** Close the streaming WebSocket. */
8681
8913
  close() {
@@ -8686,6 +8918,7 @@ var TelnyxSTT = class {
8686
8918
  }
8687
8919
  this.ws = null;
8688
8920
  }
8921
+ this.headerSent = false;
8689
8922
  }
8690
8923
  };
8691
8924
 
@@ -8706,6 +8939,7 @@ var TelnyxTTSSampleRate = {
8706
8939
  HZ_24000: 24e3
8707
8940
  };
8708
8941
  var DEFAULT_VOICE = TelnyxTTSVoice.NATURAL_HD_ASTRA;
8942
+ var FRAME_TIMEOUT_MS2 = 3e4;
8709
8943
  var TelnyxTTS = class {
8710
8944
  constructor(apiKey, voice = DEFAULT_VOICE, baseUrl = TELNYX_TTS_WS_URL) {
8711
8945
  this.apiKey = apiKey;
@@ -8733,69 +8967,83 @@ var TelnyxTTS = class {
8733
8967
  */
8734
8968
  async *synthesizeStream(text) {
8735
8969
  const url = `${this.baseUrl}?voice=${encodeURIComponent(this.voice)}`;
8736
- const ws = new WebSocket8(url, {
8737
- headers: { Authorization: `Bearer ${this.apiKey}` }
8738
- });
8739
- await new Promise((resolve, reject) => {
8740
- const timer = setTimeout(() => reject(new Error("Telnyx TTS connect timeout")), 1e4);
8741
- ws.once("open", () => {
8742
- clearTimeout(timer);
8743
- 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}` }
8744
8983
  });
8745
- ws.once("error", (err) => {
8746
- clearTimeout(timer);
8747
- 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
+ });
8748
8994
  });
8749
- });
8750
- const queue = [];
8751
- const waiters = [];
8752
- function push(item) {
8753
- const w = waiters.shift();
8754
- if (w) {
8755
- w(item);
8756
- } else {
8757
- queue.push(item);
8758
- }
8759
- }
8760
- ws.on("message", (raw) => {
8761
- let data;
8762
- try {
8763
- data = JSON.parse(raw.toString());
8764
- } catch {
8765
- getLogger().warn("TelnyxTTS: received invalid JSON");
8766
- return;
8767
- }
8768
- const audioB64 = data.audio;
8769
- if (!audioB64) return;
8770
- try {
8771
- const audioBytes = Buffer.from(audioB64, "base64");
8772
- if (audioBytes.length > 0) {
8773
- 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;
8774
9004
  }
8775
- } catch {
8776
- }
8777
- });
8778
- ws.on("close", () => {
8779
- push(null);
8780
- });
8781
- ws.on("error", (err) => {
8782
- push({ error: err instanceof Error ? err : new Error(String(err)) });
8783
- });
8784
- ws.send(JSON.stringify({ text: " " }));
8785
- ws.send(JSON.stringify({ text }));
8786
- ws.send(JSON.stringify({ text: "" }));
8787
- 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: "" }));
8788
9024
  while (true) {
8789
- 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
+ });
8790
9037
  if (item === null) return;
8791
9038
  if (typeof item === "object" && "error" in item) throw item.error;
8792
9039
  yield item;
8793
9040
  }
8794
9041
  } finally {
8795
9042
  try {
8796
- ws.close();
9043
+ ws?.close();
8797
9044
  } catch {
8798
9045
  }
9046
+ ws?.removeAllListeners();
8799
9047
  }
8800
9048
  }
8801
9049
  };
@@ -8867,6 +9115,7 @@ export {
8867
9115
  PRICING_VERSION,
8868
9116
  PartialStreamError,
8869
9117
  Patter,
9118
+ PatterConfigError,
8870
9119
  PatterConnectionError,
8871
9120
  PatterError,
8872
9121
  PatterTool,
@@ -8954,6 +9203,8 @@ export {
8954
9203
  mulawToPcm16,
8955
9204
  notifyDashboard,
8956
9205
  openaiTts,
9206
+ openclawConsult,
9207
+ openclawPostCallNotifier,
8957
9208
  pcm16ToMulaw,
8958
9209
  resample16kTo8k,
8959
9210
  resample24kTo16k,