@openclaw/voice-call 2026.8.2 → 2026.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,17 @@
1
- import { TtsConfigSchema, isBlockedHostnameOrIp, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "./runtime-api.js";
1
+ import { isBlockedHostnameOrIp, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, sendHttpRequestRejection } from "./runtime-api.js";
2
2
  import "./api.js";
3
- import { t as TWILIO_REGIONS } from "./twilio-region-4fkgz3UG.js";
3
+ import { a as resolveVoiceCallEffectiveConfig, c as resolveVoiceCallSessionKey, f as resolveVoiceCallRealtimeTools, i as resolveVoiceCallConfig, l as resolveVoiceCallStreamExposurePaths, n as normalizeVoiceCallConfig, o as resolveVoiceCallNumberRouteKeyForCall, r as resolveTwilioAuthToken, u as validateProviderConfig } from "./config-DQwvsxeV.js";
4
4
  import { _ as releaseRejectedProviderCall, b as setVoiceCallStateRuntime, d as loadActiveCallsFromStore, g as appendCallReplayKey, l as findCallInStore, p as persistCallRecord, t as resolveDefaultVoiceCallStoreDir, u as getCallHistoryFromStore, v as rememberManagerReplayKey, x as TerminalStates, y as reserveRejectedProviderCall } from "./store-path-C_z9zKnA.js";
5
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
6
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
7
- import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
7
+ import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
8
8
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
9
9
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
10
10
  import { listAgentIds, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-scope-runtime";
11
11
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
12
- import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES, assertRealtimeVoiceAgentConsultModelSelectionUnlocked, buildRealtimeVoiceAgentConsultPolicyInstructions, consultRealtimeVoiceAgent, convertPcmToMulaw8k, createTalkSessionController, recordTalkObservabilityEvent, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow, resolveRealtimeVoiceFastContextConsult } from "openclaw/plugin-sdk/realtime-voice";
13
- import { mergeDeep } from "openclaw/plugin-sdk/plugin-config-runtime";
14
- import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
15
- import { canonicalizeMainSessionAlias } from "openclaw/plugin-sdk/session-store-runtime";
16
- import { resolveSpeechProviderApiKey } from "openclaw/plugin-sdk/speech-core";
12
+ import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, assertRealtimeVoiceAgentConsultModelSelectionUnlocked, buildRealtimeVoiceAgentConsultPolicyInstructions, consultRealtimeVoiceAgent, convertPcmToMulaw8k, createTalkSessionController, recordTalkObservabilityEvent, resolveRealtimeVoiceAgentConsultToolsAllow, resolveRealtimeVoiceFastContextConsult } from "openclaw/plugin-sdk/realtime-voice";
17
13
  import { WEBHOOK_BODY_READ_DEFAULTS, createWebhookInFlightLimiter, normalizeWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";
18
- import { z } from "zod";
19
- import { MAX_TCP_PORT, MAX_TIMER_TIMEOUT_MS, asDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
14
+ import { MAX_TIMER_TIMEOUT_MS, asDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
20
15
  import fs from "node:fs";
21
16
  import crypto from "node:crypto";
22
17
  import { redactIdentifier } from "openclaw/plugin-sdk/logging-core";
@@ -31,690 +26,6 @@ import { URL as URL$1 } from "node:url";
31
26
  import { resolveConfiguredCapabilityProvider } from "openclaw/plugin-sdk/provider-selection-runtime";
32
27
  import { WebSocket, WebSocketServer } from "ws";
33
28
  import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
34
- //#region extensions/voice-call/src/realtime-call-control.ts
35
- /** Stable provider-facing tool name for ending the current phone call. */
36
- const REALTIME_VOICE_END_CALL_TOOL_NAME = "openclaw_end_call";
37
- /** Closure-bound end-call control exposed on every realtime phone call. */
38
- const REALTIME_VOICE_END_CALL_TOOL = {
39
- type: "function",
40
- name: REALTIME_VOICE_END_CALL_TOOL_NAME,
41
- description: "End the current phone call immediately. Speak any final words to the caller before invoking this tool because the call ends as soon as it is invoked and no further reply will be spoken.",
42
- parameters: {
43
- type: "object",
44
- properties: {}
45
- }
46
- };
47
- /** Merge built-in call controls with consult and configured realtime tools. */
48
- function resolveVoiceCallRealtimeTools(policy, customTools = []) {
49
- const tools = resolveRealtimeVoiceAgentConsultTools(policy, customTools).filter((tool) => tool.name !== REALTIME_VOICE_END_CALL_TOOL_NAME);
50
- return [REALTIME_VOICE_END_CALL_TOOL, ...tools];
51
- }
52
- //#endregion
53
- //#region extensions/voice-call/src/realtime-defaults.ts
54
- /** Baseline instructions that keep realtime calls brief and route deep work to agent consult. */
55
- const DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS = `You are OpenClaw's phone-call realtime voice interface. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call ${REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME} before answering. When the caller asks to end the call, speak any final words first, then call ${REALTIME_VOICE_END_CALL_TOOL_NAME}; it ends the call immediately and nothing after it will be spoken.`;
56
- //#endregion
57
- //#region extensions/voice-call/src/tailscale-config.ts
58
- const VoiceCallTailscaleConfigSchema = z.object({
59
- mode: z.enum([
60
- "off",
61
- "serve",
62
- "funnel"
63
- ]).default("off"),
64
- port: z.number().int().positive().max(MAX_TCP_PORT).default(443),
65
- path: z.string().min(1).default("/voice/webhook")
66
- }).strict().default({
67
- mode: "off",
68
- port: 443,
69
- path: "/voice/webhook"
70
- });
71
- const TAILSCALE_FUNNEL_HTTPS_PORTS = /* @__PURE__ */ new Set([
72
- 443,
73
- 8443,
74
- 1e4
75
- ]);
76
- function isTailscalePortAllowed(config) {
77
- return !(config.tailscale.mode === "funnel" || config.tunnel.provider === "tailscale-funnel") || TAILSCALE_FUNNEL_HTTPS_PORTS.has(config.tailscale.port);
78
- }
79
- //#endregion
80
- //#region extensions/voice-call/src/config.ts
81
- /**
82
- * E.164 phone number format: +[country code][number]
83
- * Examples use 555 prefix (reserved for fictional numbers)
84
- */
85
- const E164Schema = z.string().regex(/^\+[1-9]\d{1,14}$/, "Expected E.164 format, e.g. +15550001234");
86
- /**
87
- * Controls how inbound calls are handled:
88
- * - "disabled": Block all inbound calls (outbound only)
89
- * - "allowlist": Only accept calls from numbers in allowFrom
90
- * - "pairing": Unknown callers can request pairing (future)
91
- * - "open": Accept all inbound calls (dangerous!)
92
- */
93
- const InboundPolicySchema = z.enum([
94
- "disabled",
95
- "allowlist",
96
- "pairing",
97
- "open"
98
- ]);
99
- const SecretInputSchema = buildSecretInputSchema();
100
- const TelnyxConfigSchema = z.object({
101
- /** Telnyx API v2 key */
102
- apiKey: z.string().min(1).optional(),
103
- /** Telnyx connection ID (from Call Control app) */
104
- connectionId: z.string().min(1).optional(),
105
- /** Public key for webhook signature verification */
106
- publicKey: z.string().min(1).optional()
107
- }).strict();
108
- const TwilioConfigSchema = z.object({
109
- /** Twilio Account SID */
110
- accountSid: z.string().min(1).optional(),
111
- /** Twilio Auth Token */
112
- authToken: SecretInputSchema.optional(),
113
- /** Twilio processing Region (for example, ie1) */
114
- region: z.enum(TWILIO_REGIONS).optional()
115
- }).strict();
116
- const PlivoConfigSchema = z.object({
117
- /** Plivo Auth ID (starts with MA/SA) */
118
- authId: z.string().min(1).optional(),
119
- /** Plivo Auth Token */
120
- authToken: z.string().min(1).optional()
121
- }).strict();
122
- const VoiceCallNumberRouteConfigSchema = z.object({
123
- /** Greeting message for inbound calls to this number. */
124
- inboundGreeting: z.string().optional(),
125
- /** TTS override for inbound calls to this number. Deep-merges with global voice-call TTS. */
126
- tts: TtsConfigSchema,
127
- /** Agent ID to use for voice response generation for this number. */
128
- agentId: z.string().min(1).optional(),
129
- /** Optional model override for voice responses for this number. */
130
- responseModel: z.string().optional(),
131
- /** System prompt for voice responses for this number. */
132
- responseSystemPrompt: z.string().optional(),
133
- /** Timeout for response generation in ms for this number. */
134
- responseTimeoutMs: z.number().int().positive().optional()
135
- }).strict();
136
- const VoiceCallServeConfigSchema = z.object({
137
- /** Port to listen on */
138
- port: z.number().int().positive().default(3334),
139
- /** Bind address */
140
- bind: z.string().default("127.0.0.1"),
141
- /** Webhook path */
142
- path: z.string().min(1).default("/voice/webhook")
143
- }).strict().default({
144
- port: 3334,
145
- bind: "127.0.0.1",
146
- path: "/voice/webhook"
147
- });
148
- const VoiceCallTunnelConfigSchema = z.object({
149
- /**
150
- * Tunnel provider:
151
- * - "none": No tunnel (use publicUrl if set, or manual setup)
152
- * - "ngrok": Use ngrok for public HTTPS tunnel
153
- * - "tailscale-serve": Tailscale serve (private to tailnet)
154
- * - "tailscale-funnel": Tailscale funnel (public HTTPS)
155
- */
156
- provider: z.enum([
157
- "none",
158
- "ngrok",
159
- "tailscale-serve",
160
- "tailscale-funnel"
161
- ]).default("none"),
162
- /** ngrok auth token (optional, enables longer sessions and more features) */
163
- ngrokAuthToken: z.string().min(1).optional(),
164
- /** ngrok custom domain (paid feature, e.g., "myapp.ngrok.io") */
165
- ngrokDomain: z.string().min(1).optional(),
166
- /**
167
- * Allow ngrok free tier compatibility mode.
168
- * When true, forwarded headers may be trusted for loopback requests
169
- * to reconstruct the public ngrok URL used for signing.
170
- *
171
- * IMPORTANT: This does NOT bypass signature verification.
172
- */
173
- allowNgrokFreeTierLoopbackBypass: z.boolean().default(false)
174
- }).strict().default({
175
- provider: "none",
176
- allowNgrokFreeTierLoopbackBypass: false
177
- });
178
- const VoiceCallWebhookSecurityConfigSchema = z.object({
179
- /**
180
- * Allowed hostnames for webhook URL reconstruction.
181
- * Only these hosts are accepted from forwarding headers.
182
- */
183
- allowedHosts: z.array(z.string().min(1)).default([]),
184
- /**
185
- * Trust X-Forwarded-* headers without a hostname allowlist.
186
- * WARNING: Only enable if you trust your proxy configuration.
187
- */
188
- trustForwardingHeaders: z.boolean().default(false),
189
- /**
190
- * Trusted proxy IP addresses. Forwarded headers are only trusted when
191
- * the remote IP matches one of these addresses.
192
- */
193
- trustedProxyIPs: z.array(z.string().min(1)).default([])
194
- }).strict().default({
195
- allowedHosts: [],
196
- trustForwardingHeaders: false,
197
- trustedProxyIPs: []
198
- });
199
- /**
200
- * Call mode determines how outbound calls behave:
201
- * - "notify": Deliver message and auto-hangup after delay (one-way notification)
202
- * - "conversation": Stay open for back-and-forth until explicit end or timeout
203
- */
204
- const CallModeSchema = z.enum(["notify", "conversation"]);
205
- const VoiceCallSessionScopeSchema = z.enum([
206
- "per-phone",
207
- "per-call",
208
- "main"
209
- ]);
210
- const OutboundConfigSchema = z.object({
211
- /** Default call mode for outbound calls */
212
- defaultMode: CallModeSchema.default("notify"),
213
- /** Seconds to wait after TTS before auto-hangup in notify mode */
214
- notifyHangupDelaySec: z.number().int().nonnegative().default(3)
215
- }).strict().default({
216
- defaultMode: "notify",
217
- notifyHangupDelaySec: 3
218
- });
219
- const RealtimeToolSchema = z.object({
220
- type: z.literal("function"),
221
- name: z.string().min(1),
222
- description: z.string(),
223
- parameters: z.object({
224
- type: z.literal("object"),
225
- properties: z.record(z.string(), z.unknown()),
226
- required: z.array(z.string()).optional()
227
- })
228
- }).strict();
229
- const VoiceCallRealtimeProvidersConfigSchema = z.record(z.string(), z.record(z.string(), z.unknown())).default({});
230
- const VoiceCallRealtimeToolPolicySchema = z.enum(REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES);
231
- const VoiceCallRealtimeConsultPolicySchema = z.enum([
232
- "auto",
233
- "substantive",
234
- "always"
235
- ]);
236
- const VoiceCallRealtimeFastContextSourceSchema = z.enum(["memory", "sessions"]);
237
- const VoiceCallRealtimeFastContextConfigSchema = z.object({
238
- /** Enable bounded memory/session lookup before the full consult agent. */
239
- enabled: z.boolean().default(false),
240
- /** Hard deadline for the fast context lookup. */
241
- timeoutMs: z.number().int().positive().default(800),
242
- /** Maximum memory/session hits to inject into the realtime tool result. */
243
- maxResults: z.number().int().positive().default(3),
244
- /** Indexed sources used by the fast context lookup. */
245
- sources: z.array(VoiceCallRealtimeFastContextSourceSchema).min(1).default(["memory", "sessions"]),
246
- /** Fall back to the full agent consult when fast context has no answer. */
247
- fallbackToConsult: z.boolean().default(false)
248
- }).strict().default({
249
- enabled: false,
250
- timeoutMs: 800,
251
- maxResults: 3,
252
- sources: ["memory", "sessions"],
253
- fallbackToConsult: false
254
- });
255
- const VoiceCallRealtimeAgentContextConfigSchema = z.object({
256
- /** Inject a compact agent persona/context capsule into realtime voice instructions. */
257
- enabled: z.boolean().default(false),
258
- /** Maximum number of characters from the generated capsule to append. */
259
- maxChars: z.number().int().positive().default(6e3),
260
- /** Include configured agent identity fields. */
261
- includeIdentity: z.boolean().default(true),
262
- /** Include selected workspace files such as SOUL.md and IDENTITY.md. */
263
- includeWorkspaceFiles: z.boolean().default(true),
264
- /** Workspace-relative files to include, bounded by maxChars. */
265
- files: z.array(z.string().min(1)).default([
266
- "SOUL.md",
267
- "IDENTITY.md",
268
- "USER.md"
269
- ])
270
- }).strict().default({
271
- enabled: false,
272
- maxChars: 6e3,
273
- includeIdentity: true,
274
- includeWorkspaceFiles: true,
275
- files: [
276
- "SOUL.md",
277
- "IDENTITY.md",
278
- "USER.md"
279
- ]
280
- });
281
- const VoiceCallRealtimeConsultThinkingLevelSchema = z.enum([
282
- "off",
283
- "minimal",
284
- "low",
285
- "medium",
286
- "high",
287
- "xhigh",
288
- "adaptive",
289
- "max",
290
- "ultra"
291
- ]);
292
- const VoiceCallStreamingProvidersConfigSchema = z.record(z.string(), z.record(z.string(), z.unknown())).default({});
293
- const VoiceCallRealtimeConfigSchema = z.object({
294
- /** Enable realtime voice-to-voice mode. */
295
- enabled: z.boolean().default(false),
296
- /** Provider id from registered realtime voice providers. */
297
- provider: z.string().min(1).optional(),
298
- /** Optional override for the local WebSocket route path. */
299
- streamPath: z.string().min(1).optional(),
300
- /** System instructions passed to the realtime provider. */
301
- instructions: z.string().default(DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS),
302
- /** Tool policy for the shared OpenClaw agent consult tool. */
303
- toolPolicy: VoiceCallRealtimeToolPolicySchema.default("safe-read-only"),
304
- /** Guidance for when the realtime model should call the OpenClaw agent consult tool. */
305
- consultPolicy: VoiceCallRealtimeConsultPolicySchema.default("auto"),
306
- /** Optional thinking level override for the regular agent behind realtime consults. */
307
- consultThinkingLevel: VoiceCallRealtimeConsultThinkingLevelSchema.optional(),
308
- /** Optional fast mode override for the regular agent behind realtime consults. */
309
- consultFastMode: z.boolean().optional(),
310
- /** Tool definitions exposed to the realtime provider. */
311
- tools: z.array(RealtimeToolSchema).default([]),
312
- /** Low-latency memory/session context for the consult tool. */
313
- fastContext: VoiceCallRealtimeFastContextConfigSchema,
314
- /** Bounded agent persona/context injection for the fast realtime voice path. */
315
- agentContext: VoiceCallRealtimeAgentContextConfigSchema,
316
- /** Provider-owned raw config blobs keyed by provider id. */
317
- providers: VoiceCallRealtimeProvidersConfigSchema
318
- }).strict().default({
319
- enabled: false,
320
- instructions: DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS,
321
- toolPolicy: "safe-read-only",
322
- consultPolicy: "auto",
323
- tools: [],
324
- fastContext: {
325
- enabled: false,
326
- timeoutMs: 800,
327
- maxResults: 3,
328
- sources: ["memory", "sessions"],
329
- fallbackToConsult: false
330
- },
331
- agentContext: {
332
- enabled: false,
333
- maxChars: 6e3,
334
- includeIdentity: true,
335
- includeWorkspaceFiles: true,
336
- files: [
337
- "SOUL.md",
338
- "IDENTITY.md",
339
- "USER.md"
340
- ]
341
- },
342
- providers: {}
343
- });
344
- const VoiceCallStreamingConfigSchema = z.object({
345
- /** Enable Twilio Media Streams for real-time transcription. */
346
- enabled: z.boolean().default(false),
347
- /** Provider id from registered realtime transcription providers. */
348
- provider: z.string().min(1).optional(),
349
- /** WebSocket path for media stream connections */
350
- streamPath: z.string().min(1).default("/voice/stream"),
351
- /** Provider-owned raw config blobs keyed by provider id. */
352
- providers: VoiceCallStreamingProvidersConfigSchema,
353
- /**
354
- * Close unauthenticated media stream sockets if no valid `start` frame arrives in time.
355
- * Protects against pre-auth idle connection hold attacks.
356
- */
357
- preStartTimeoutMs: z.number().int().positive().default(5e3),
358
- /** Maximum number of concurrently pending (pre-start) media stream sockets. */
359
- maxPendingConnections: z.number().int().positive().default(32),
360
- /** Maximum pending media stream sockets per source IP. */
361
- maxPendingConnectionsPerIp: z.number().int().positive().default(4),
362
- /** Hard cap for all open media stream sockets (pending + active). */
363
- maxConnections: z.number().int().positive().default(128)
364
- }).strict().default({
365
- enabled: false,
366
- streamPath: "/voice/stream",
367
- providers: {},
368
- preStartTimeoutMs: 5e3,
369
- maxPendingConnections: 32,
370
- maxPendingConnectionsPerIp: 4,
371
- maxConnections: 128
372
- });
373
- const VoiceCallConfigSchema = z.object({
374
- /** Enable voice call functionality */
375
- enabled: z.boolean().default(false),
376
- /** Active provider (telnyx, twilio, plivo, or mock) */
377
- provider: z.enum([
378
- "telnyx",
379
- "twilio",
380
- "plivo",
381
- "mock"
382
- ]).optional(),
383
- /** Telnyx-specific configuration */
384
- telnyx: TelnyxConfigSchema.optional(),
385
- /** Twilio-specific configuration */
386
- twilio: TwilioConfigSchema.optional(),
387
- /** Plivo-specific configuration */
388
- plivo: PlivoConfigSchema.optional(),
389
- /** Phone number to call from (E.164) */
390
- fromNumber: E164Schema.optional(),
391
- /** Default phone number to call (E.164) */
392
- toNumber: E164Schema.optional(),
393
- /** Inbound call policy */
394
- inboundPolicy: InboundPolicySchema.default("disabled"),
395
- /** Allowlist of phone numbers for inbound calls (E.164) */
396
- allowFrom: z.array(E164Schema).default([]),
397
- /** Greeting message for inbound calls */
398
- inboundGreeting: z.string().optional(),
399
- /** Per-dialed-number overrides for inbound calls. Keys are E.164 numbers. */
400
- numbers: z.record(E164Schema, VoiceCallNumberRouteConfigSchema).default({}),
401
- /** Outbound call configuration */
402
- outbound: OutboundConfigSchema,
403
- /** Maximum call duration in seconds */
404
- maxDurationSeconds: z.number().int().positive().default(300),
405
- /**
406
- * Maximum age of a call in seconds before it is automatically reaped.
407
- * Catches calls stuck before answer (for example, local mock calls that
408
- * never receive provider webhooks). Set to 0 to disable.
409
- */
410
- staleCallReaperSeconds: z.number().int().nonnegative().default(120),
411
- /** Silence timeout for end-of-speech detection (ms) */
412
- silenceTimeoutMs: z.number().int().positive().default(800),
413
- /** Timeout for user transcript (ms) */
414
- transcriptTimeoutMs: z.number().int().positive().default(18e4),
415
- /** Ring timeout for outbound calls (ms) */
416
- ringTimeoutMs: z.number().int().positive().default(3e4),
417
- /** Maximum concurrent calls */
418
- maxConcurrentCalls: z.number().int().positive().default(1),
419
- /** Webhook server configuration */
420
- serve: VoiceCallServeConfigSchema,
421
- /** @deprecated Prefer tunnel config. */
422
- tailscale: VoiceCallTailscaleConfigSchema,
423
- /** Tunnel configuration (unified ngrok/tailscale) */
424
- tunnel: VoiceCallTunnelConfigSchema,
425
- /** Webhook signature reconstruction and proxy trust configuration */
426
- webhookSecurity: VoiceCallWebhookSecurityConfigSchema,
427
- /** Real-time audio streaming configuration */
428
- streaming: VoiceCallStreamingConfigSchema,
429
- /** Realtime voice-to-voice configuration */
430
- realtime: VoiceCallRealtimeConfigSchema,
431
- /** Session memory scope for voice conversations. */
432
- sessionScope: VoiceCallSessionScopeSchema.default("per-phone"),
433
- /** Public webhook URL override (if set, bypasses tunnel auto-detection) */
434
- publicUrl: z.string().url().optional(),
435
- /** Skip webhook signature verification (development only, NOT for production) */
436
- skipSignatureVerification: z.boolean().default(false),
437
- /** TTS override (deep-merges with core tts) */
438
- tts: TtsConfigSchema,
439
- /** Store path for call logs */
440
- store: z.string().optional(),
441
- /** Agent ID to use for voice response generation. Defaults to "main". */
442
- agentId: z.string().min(1).optional(),
443
- /** Optional model override for generating voice responses. */
444
- responseModel: z.string().optional(),
445
- /** System prompt for voice responses */
446
- responseSystemPrompt: z.string().optional(),
447
- /** Timeout for response generation in ms (default 30s) */
448
- responseTimeoutMs: z.number().int().positive().default(3e4)
449
- }).strict().refine(isTailscalePortAllowed, {
450
- path: ["tailscale", "port"],
451
- message: "Tailscale Funnel HTTPS port must be one of 443, 8443, 10000"
452
- });
453
- const TWILIO_AUTH_TOKEN_PATH = "plugins.entries.voice-call.config.twilio.authToken";
454
- const DEFAULT_VOICE_CALL_CONFIG = VoiceCallConfigSchema.parse({});
455
- function cloneDefaultVoiceCallConfig() {
456
- return structuredClone(DEFAULT_VOICE_CALL_CONFIG);
457
- }
458
- function defaultRealtimeStreamPathForServePath(servePath) {
459
- const normalized = normalizeWebhookPath(servePath);
460
- if (normalized.endsWith("/webhook")) return `${normalized.slice(0, -8)}/stream/realtime`;
461
- if (normalized === "/") return "/voice/stream/realtime";
462
- return `${normalized}/stream/realtime`;
463
- }
464
- function resolveVoiceCallPublicPathPrefix(publicWebhookPath, localWebhookPath) {
465
- const publicPath = normalizeWebhookPath(publicWebhookPath);
466
- const localPathIndex = publicPath.indexOf(normalizeWebhookPath(localWebhookPath));
467
- return localPathIndex > 0 ? publicPath.slice(0, localPathIndex) : "";
468
- }
469
- function resolveVoiceCallStreamExposurePaths(config, webhookPaths = {}) {
470
- const exposurePaths = [];
471
- const localWebhookPath = webhookPaths.localWebhookPath ?? config.serve.path;
472
- const publicPathPrefix = resolveVoiceCallPublicPathPrefix(webhookPaths.publicWebhookPath ?? config.tailscale.path, localWebhookPath);
473
- if (config.realtime.enabled) {
474
- const localPath = normalizeWebhookPath(config.realtime.streamPath ?? defaultRealtimeStreamPathForServePath(config.serve.path));
475
- exposurePaths.push({
476
- localPath,
477
- publicPath: `${publicPathPrefix}${localPath}`
478
- });
479
- }
480
- if (config.streaming.enabled) {
481
- const localPath = normalizeWebhookPath(config.streaming.streamPath);
482
- if (!exposurePaths.some((path) => path.localPath === localPath && path.publicPath === localPath)) exposurePaths.push({
483
- localPath,
484
- publicPath: localPath
485
- });
486
- }
487
- return exposurePaths;
488
- }
489
- function normalizeVoiceCallTtsConfig(defaults, overrides) {
490
- if (!defaults && !overrides) return;
491
- return TtsConfigSchema.parse(mergeDeep(defaults ?? {}, overrides ?? {}));
492
- }
493
- function normalizePhoneRouteKey(phone) {
494
- return phone?.replace(/\D/g, "") ?? "";
495
- }
496
- function resolveVoiceCallNumberRouteKey(config, phone) {
497
- const routes = config.numbers;
498
- if (!routes) return;
499
- if (phone && Object.hasOwn(routes, phone)) return phone;
500
- const normalizedPhone = normalizePhoneRouteKey(phone);
501
- if (!normalizedPhone) return;
502
- return Object.keys(routes).find((routeKey) => normalizePhoneRouteKey(routeKey) === normalizedPhone);
503
- }
504
- /** Resolve inbound-only number routing from a persisted call record. */
505
- function resolveVoiceCallNumberRouteKeyForCall(call) {
506
- if (call.direction !== "inbound") return;
507
- const storedRouteKey = call.metadata?.numberRouteKey;
508
- if (typeof storedRouteKey === "string") return storedRouteKey;
509
- return call.to;
510
- }
511
- function resolveVoiceCallEffectiveConfig(config, phoneOrRouteKey) {
512
- const numberRouteKey = resolveVoiceCallNumberRouteKey(config, phoneOrRouteKey);
513
- if (!numberRouteKey) return { config };
514
- const route = config.numbers[numberRouteKey];
515
- if (!route) return { config };
516
- return {
517
- numberRouteKey,
518
- config: {
519
- ...config,
520
- ...route,
521
- tts: normalizeVoiceCallTtsConfig(config.tts, route.tts),
522
- numbers: config.numbers
523
- }
524
- };
525
- }
526
- function sanitizeVoiceCallProviderConfigs(value) {
527
- if (!value) return {};
528
- return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
529
- }
530
- function sanitizeVoiceCallNumberRoutes(value) {
531
- if (!value) return {};
532
- return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0).map(([key, route]) => [key, VoiceCallNumberRouteConfigSchema.parse(route)]));
533
- }
534
- function resolveTwilioAuthToken(config) {
535
- return normalizeResolvedSecretInputString({
536
- value: config.twilio?.authToken,
537
- path: TWILIO_AUTH_TOKEN_PATH
538
- });
539
- }
540
- function normalizeVoiceCallConfig(config) {
541
- const defaults = cloneDefaultVoiceCallConfig();
542
- const serve = {
543
- ...defaults.serve,
544
- ...config.serve
545
- };
546
- const streamingProvider = config.streaming?.provider;
547
- const streamingProviders = sanitizeVoiceCallProviderConfigs(config.streaming?.providers ?? defaults.streaming.providers);
548
- const realtimeProvider = config.realtime?.provider ?? defaults.realtime.provider;
549
- const realtimeProviders = sanitizeVoiceCallProviderConfigs(config.realtime?.providers ?? defaults.realtime.providers);
550
- const realtimeFastContext = {
551
- ...defaults.realtime.fastContext,
552
- ...config.realtime?.fastContext,
553
- sources: config.realtime?.fastContext?.sources ?? defaults.realtime.fastContext.sources
554
- };
555
- const realtimeAgentContext = {
556
- ...defaults.realtime.agentContext,
557
- ...config.realtime?.agentContext,
558
- files: config.realtime?.agentContext?.files ?? defaults.realtime.agentContext.files
559
- };
560
- return {
561
- ...defaults,
562
- ...config,
563
- allowFrom: config.allowFrom ?? defaults.allowFrom,
564
- numbers: sanitizeVoiceCallNumberRoutes(config.numbers ?? defaults.numbers),
565
- outbound: {
566
- ...defaults.outbound,
567
- ...config.outbound
568
- },
569
- serve,
570
- tailscale: {
571
- ...defaults.tailscale,
572
- ...config.tailscale
573
- },
574
- tunnel: {
575
- ...defaults.tunnel,
576
- ...config.tunnel
577
- },
578
- webhookSecurity: {
579
- ...defaults.webhookSecurity,
580
- ...config.webhookSecurity,
581
- allowedHosts: config.webhookSecurity?.allowedHosts ?? defaults.webhookSecurity.allowedHosts,
582
- trustedProxyIPs: config.webhookSecurity?.trustedProxyIPs ?? defaults.webhookSecurity.trustedProxyIPs
583
- },
584
- streaming: {
585
- ...defaults.streaming,
586
- ...config.streaming,
587
- provider: streamingProvider,
588
- providers: streamingProviders
589
- },
590
- realtime: {
591
- ...defaults.realtime,
592
- ...config.realtime,
593
- provider: realtimeProvider,
594
- streamPath: config.realtime?.streamPath ?? defaultRealtimeStreamPathForServePath(serve.path ?? defaults.serve.path),
595
- tools: config.realtime?.tools ?? defaults.realtime.tools,
596
- consultThinkingLevel: VoiceCallRealtimeConsultThinkingLevelSchema.optional().parse(config.realtime?.consultThinkingLevel ?? defaults.realtime.consultThinkingLevel),
597
- consultFastMode: config.realtime?.consultFastMode ?? defaults.realtime.consultFastMode,
598
- fastContext: realtimeFastContext,
599
- agentContext: realtimeAgentContext,
600
- providers: realtimeProviders
601
- },
602
- tts: normalizeVoiceCallTtsConfig(defaults.tts, config.tts)
603
- };
604
- }
605
- function resolveVoiceCallSessionKey(params) {
606
- const explicit = params.explicitSessionKey?.trim();
607
- if (explicit) return resolveVoiceCallAgentSessionKey({
608
- config: params.config,
609
- sessionKey: explicit,
610
- coreSession: params.coreSession
611
- });
612
- if (params.config.sessionScope === "main") return resolveVoiceCallAgentSessionKey({
613
- config: params.config,
614
- sessionKey: "main",
615
- coreSession: params.coreSession
616
- });
617
- const prefix = `agent:${normalizeAgentId(params.config.agentId)}:voice`;
618
- if (params.config.sessionScope === "per-call") return `${prefix}:call:${params.callId}`.toLowerCase();
619
- const normalizedPhone = params.phone?.replace(/\D/g, "");
620
- return (normalizedPhone ? `${prefix}:${normalizedPhone}` : `${prefix}:${params.callId}`).toLowerCase();
621
- }
622
- /** Resolve persisted or integration-provided keys into the configured agent namespace. */
623
- function resolveVoiceCallAgentSessionKey(params) {
624
- const sessionKey = params.sessionKey.trim();
625
- if (!sessionKey) throw new Error("Voice Call session key cannot be empty");
626
- const lower = sessionKey.toLowerCase();
627
- const agentId = normalizeAgentId(params.config.agentId);
628
- if (lower === "global" || lower === "unknown") return lower;
629
- const parsedInput = parseAgentSessionKey(sessionKey);
630
- let normalizedScopedKey;
631
- if (parsedInput && normalizeAgentId(parsedInput.agentId) === parsedInput.agentId && parsedInput.agentId === agentId) normalizedScopedKey = `agent:${parsedInput.agentId}:${parsedInput.rest}`;
632
- else {
633
- const wrappedInput = parseAgentSessionKey(`agent:${agentId}:${sessionKey}`);
634
- if (!wrappedInput) throw new Error("Voice Call session key could not be normalized");
635
- normalizedScopedKey = `agent:${agentId}:${wrappedInput.rest}`;
636
- }
637
- const canonicalMain = canonicalizeMainSessionAlias({
638
- cfg: { session: params.coreSession },
639
- agentId,
640
- sessionKey: normalizedScopedKey
641
- });
642
- return canonicalMain === normalizedScopedKey ? normalizedScopedKey : canonicalMain;
643
- }
644
- /**
645
- * Resolves the configuration by merging environment variables into missing fields.
646
- * Returns a new configuration object with environment variables applied.
647
- */
648
- function resolveVoiceCallConfig(config) {
649
- const resolved = normalizeVoiceCallConfig(config);
650
- if (resolved.provider === "telnyx") {
651
- resolved.telnyx = resolved.telnyx ?? {};
652
- resolved.telnyx.apiKey = resolved.telnyx.apiKey ?? resolveSpeechProviderApiKey(process.env.TELNYX_API_KEY);
653
- resolved.telnyx.connectionId = resolved.telnyx.connectionId ?? resolveSpeechProviderApiKey(process.env.TELNYX_CONNECTION_ID);
654
- resolved.telnyx.publicKey = resolved.telnyx.publicKey ?? resolveSpeechProviderApiKey(process.env.TELNYX_PUBLIC_KEY);
655
- }
656
- if (resolved.provider === "twilio") {
657
- resolved.fromNumber = resolved.fromNumber ?? resolveSpeechProviderApiKey(process.env.TWILIO_FROM_NUMBER);
658
- resolved.twilio = resolved.twilio ?? {};
659
- resolved.twilio.accountSid = resolved.twilio.accountSid ?? resolveSpeechProviderApiKey(process.env.TWILIO_ACCOUNT_SID);
660
- resolved.twilio.authToken = resolved.twilio.authToken ?? resolveSpeechProviderApiKey(process.env.TWILIO_AUTH_TOKEN);
661
- }
662
- if (resolved.provider === "plivo") {
663
- resolved.plivo = resolved.plivo ?? {};
664
- resolved.plivo.authId = resolved.plivo.authId ?? resolveSpeechProviderApiKey(process.env.PLIVO_AUTH_ID);
665
- resolved.plivo.authToken = resolved.plivo.authToken ?? resolveSpeechProviderApiKey(process.env.PLIVO_AUTH_TOKEN);
666
- }
667
- resolved.tunnel = resolved.tunnel ?? {
668
- provider: "none",
669
- allowNgrokFreeTierLoopbackBypass: false
670
- };
671
- resolved.tunnel.allowNgrokFreeTierLoopbackBypass = resolved.tunnel.allowNgrokFreeTierLoopbackBypass ?? false;
672
- resolved.tunnel.ngrokAuthToken = resolved.tunnel.ngrokAuthToken ?? resolveSpeechProviderApiKey(process.env.NGROK_AUTHTOKEN);
673
- resolved.tunnel.ngrokDomain = resolved.tunnel.ngrokDomain ?? resolveSpeechProviderApiKey(process.env.NGROK_DOMAIN);
674
- resolved.webhookSecurity = resolved.webhookSecurity ?? {
675
- allowedHosts: [],
676
- trustForwardingHeaders: false,
677
- trustedProxyIPs: []
678
- };
679
- resolved.webhookSecurity.allowedHosts = resolved.webhookSecurity.allowedHosts ?? [];
680
- resolved.webhookSecurity.trustForwardingHeaders = resolved.webhookSecurity.trustForwardingHeaders ?? false;
681
- resolved.webhookSecurity.trustedProxyIPs = resolved.webhookSecurity.trustedProxyIPs ?? [];
682
- return normalizeVoiceCallConfig(resolved);
683
- }
684
- /**
685
- * Validate that the configuration has all required fields for the selected provider.
686
- */
687
- function validateProviderConfig(config) {
688
- const errors = [];
689
- if (!config.enabled) return {
690
- valid: true,
691
- errors: []
692
- };
693
- if (!config.provider) errors.push("plugins.entries.voice-call.config.provider is required");
694
- if (!config.fromNumber && config.provider !== "mock") errors.push(config.provider === "twilio" ? "plugins.entries.voice-call.config.fromNumber is required (or set TWILIO_FROM_NUMBER env)" : "plugins.entries.voice-call.config.fromNumber is required");
695
- if (config.provider === "telnyx") {
696
- if (!config.telnyx?.apiKey) errors.push("plugins.entries.voice-call.config.telnyx.apiKey is required (or set TELNYX_API_KEY env)");
697
- if (!config.telnyx?.connectionId) errors.push("plugins.entries.voice-call.config.telnyx.connectionId is required (or set TELNYX_CONNECTION_ID env)");
698
- if (!config.skipSignatureVerification && !config.telnyx?.publicKey) errors.push("plugins.entries.voice-call.config.telnyx.publicKey is required (or set TELNYX_PUBLIC_KEY env)");
699
- }
700
- if (config.provider === "twilio") {
701
- if (!config.twilio?.accountSid) errors.push("plugins.entries.voice-call.config.twilio.accountSid is required (or set TWILIO_ACCOUNT_SID env)");
702
- if (!hasConfiguredSecretInput(config.twilio?.authToken)) errors.push("plugins.entries.voice-call.config.twilio.authToken is required (or set TWILIO_AUTH_TOKEN env)");
703
- }
704
- if (config.provider === "plivo") {
705
- if (!config.plivo?.authId) errors.push("plugins.entries.voice-call.config.plivo.authId is required (or set PLIVO_AUTH_ID env)");
706
- if (!config.plivo?.authToken) errors.push("plugins.entries.voice-call.config.plivo.authToken is required (or set PLIVO_AUTH_TOKEN env)");
707
- }
708
- if (config.realtime.enabled && config.inboundPolicy === "disabled") errors.push("plugins.entries.voice-call.config.inboundPolicy must not be \"disabled\" when realtime.enabled is true");
709
- if (config.realtime.enabled && config.streaming.enabled) errors.push("plugins.entries.voice-call.config.realtime.enabled and plugins.entries.voice-call.config.streaming.enabled cannot both be true");
710
- if (config.streaming.enabled && config.provider && config.provider !== "twilio") errors.push("plugins.entries.voice-call.config.provider must be \"twilio\" when streaming.enabled is true");
711
- if (config.realtime.enabled && config.provider && config.provider !== "twilio" && config.provider !== "telnyx" && config.provider !== "mock") errors.push("plugins.entries.voice-call.config.provider must be \"twilio\", \"telnyx\", or \"mock\" when realtime.enabled is true");
712
- return {
713
- valid: errors.length === 0,
714
- errors
715
- };
716
- }
717
- //#endregion
718
29
  //#region extensions/voice-call/src/allowlist.ts
719
30
  /** Normalize a phone number to digits only. */
720
31
  function normalizePhoneNumber(input) {
@@ -2028,6 +1339,13 @@ async function resolveRealtimeFastContextConsult(params) {
2028
1339
  }
2029
1340
  //#endregion
2030
1341
  //#region extensions/voice-call/src/resolve-call-agent-id.ts
1342
+ /** Setup and startup must resolve the same owner before provisioning telephony. */
1343
+ function resolveVoiceCallAgentId(config, coreConfig) {
1344
+ return config.agentId ? normalizeAgentId(config.agentId) : resolveDefaultAgentId(coreConfig, {
1345
+ surface: "Voice Call",
1346
+ hint: "Set plugins.entries.voice-call.config.agentId to a configured agent ID."
1347
+ });
1348
+ }
2031
1349
  /** Keep one agent owner for the full call, including legacy stored records. */
2032
1350
  function resolveCallAgentId(call, config) {
2033
1351
  return normalizeAgentId(call.agentId ?? config.agentId);
@@ -3369,7 +2687,7 @@ const MAX_WEBHOOK_BODY_BYTES = WEBHOOK_BODY_READ_DEFAULTS.preAuth.maxBytes;
3369
2687
  const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
3370
2688
  const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__";
3371
2689
  const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule(() => import("./realtime-transcription.runtime-CbJAs5t_.js"));
3372
- const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-BPKgHnuI.js"));
2690
+ const loadResponseGeneratorModule = createLazyRuntimeModule(() => import("./response-generator-C0577tL-.js"));
3373
2691
  function appendRecentTalkEventMetadata(call, event) {
3374
2692
  const metadata = call.metadata ?? {};
3375
2693
  const recent = Array.isArray(metadata.recentTalkEvents) ? metadata.recentTalkEvents.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
@@ -3770,10 +3088,10 @@ var VoiceCallWebhookServer = class {
3770
3088
  * Handle incoming HTTP request.
3771
3089
  */
3772
3090
  async handleRequest(req, res, webhookPath) {
3773
- const payload = await this.runWebhookPipeline(req, webhookPath);
3774
- this.writeWebhookResponse(res, payload);
3091
+ const payload = await this.runWebhookPipeline(req, webhookPath, res);
3092
+ if (payload) this.writeWebhookResponse(res, payload);
3775
3093
  }
3776
- async runWebhookPipeline(req, webhookPath) {
3094
+ async runWebhookPipeline(req, webhookPath, res) {
3777
3095
  const url = buildRequestUrl(req.url);
3778
3096
  if (url.pathname === "/voice/hold-music") return {
3779
3097
  statusCode: 200,
@@ -3815,14 +3133,14 @@ var VoiceCallWebhookServer = class {
3815
3133
  try {
3816
3134
  body = await this.readBody(req, MAX_WEBHOOK_BODY_BYTES, WEBHOOK_BODY_TIMEOUT_MS);
3817
3135
  } catch (err) {
3818
- if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) return {
3819
- statusCode: 413,
3820
- body: "Payload Too Large"
3821
- };
3822
- if (isRequestBodyLimitError(err, "REQUEST_BODY_TIMEOUT")) return {
3823
- statusCode: 408,
3824
- body: requestBodyErrorToText("REQUEST_BODY_TIMEOUT")
3825
- };
3136
+ if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
3137
+ await sendHttpRequestRejection(req, res, 413, "Payload Too Large");
3138
+ return null;
3139
+ }
3140
+ if (isRequestBodyLimitError(err, "REQUEST_BODY_TIMEOUT")) {
3141
+ await sendHttpRequestRejection(req, res, 408, requestBodyErrorToText("REQUEST_BODY_TIMEOUT"));
3142
+ return null;
3143
+ }
3826
3144
  throw err;
3827
3145
  }
3828
3146
  const ctx = {
@@ -4020,10 +3338,11 @@ var VoiceCallWebhookServer = class {
4020
3338
  /**
4021
3339
  * Read request body as string with timeout protection.
4022
3340
  */
4023
- readBody(req, maxBytes, timeoutMs = WEBHOOK_BODY_TIMEOUT_MS) {
3341
+ readBody(req, maxBytes, timeoutMs) {
4024
3342
  return readRequestBodyWithLimit(req, {
4025
3343
  maxBytes,
4026
- timeoutMs
3344
+ timeoutMs,
3345
+ destroyOnLimit: false
4027
3346
  });
4028
3347
  }
4029
3348
  /**
@@ -4094,12 +3413,12 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [
4094
3413
  "Do not print secret values or dump environment variables; only check whether required configuration is present.",
4095
3414
  "Be accurate, brief, and speakable."
4096
3415
  ].join(" ");
4097
- const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-BWmhDBtd.js"));
4098
- const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-78mzH8Lk.js"));
4099
- const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-CgYWuWVr.js"));
3416
+ const loadTelnyxProvider = createLazyRuntimeModule(() => import("./telnyx-rcjslHOP.js"));
3417
+ const loadTwilioProvider = createLazyRuntimeModule(() => import("./twilio-B100ALoj.js"));
3418
+ const loadPlivoProvider = createLazyRuntimeModule(() => import("./plivo-BfKoYCMW.js"));
4100
3419
  const loadMockProvider = createLazyRuntimeModule(() => import("./mock-C57qj18W.js"));
4101
3420
  const loadRealtimeVoiceRuntime = createLazyRuntimeModule(() => import("./realtime-voice.runtime-vtdCOWg-.js"));
4102
- const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-oehCkuE2.js"));
3421
+ const loadRealtimeHandler = createLazyRuntimeModule(() => import("./realtime-handler-BYWqpZ6N.js"));
4103
3422
  function resolveVoiceCallConsultSessionKey(call) {
4104
3423
  return resolveVoiceCallSessionKey({
4105
3424
  config: call.config,
@@ -4250,10 +3569,9 @@ async function createVoiceCallRuntime(params) {
4250
3569
  };
4251
3570
  const cfg = fullConfig ?? coreConfig;
4252
3571
  const unresolvedConfig = resolveVoiceCallConfig(rawConfig);
4253
- const configuredAgentId = unresolvedConfig.agentId ? normalizeAgentId(unresolvedConfig.agentId) : resolveDefaultAgentId(cfg);
4254
3572
  const config = {
4255
3573
  ...unresolvedConfig,
4256
- agentId: configuredAgentId
3574
+ agentId: resolveVoiceCallAgentId(unresolvedConfig, cfg)
4257
3575
  };
4258
3576
  if (!config.enabled) throw new Error("Voice call disabled. Enable the plugin entry in config.");
4259
3577
  if (config.skipSignatureVerification) log.warn("[voice-call] SECURITY WARNING: skipSignatureVerification=true disables webhook signature verification (development only). Do not use in production.");
@@ -4442,4 +3760,4 @@ async function createVoiceCallRuntime(params) {
4442
3760
  }
4443
3761
  }
4444
3762
  //#endregion
4445
- export { resolveVoiceCallStreamExposurePaths as C, resolveVoiceCallSessionKey as S, REALTIME_VOICE_END_CALL_TOOL_NAME as T, escapeXml as _, normalizeProviderStatus as a, resolveVoiceCallConfig as b, resolveWebhookExposureStatus as c, setupTailscaleExposureRoutes as d, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS as f, resolveUserPath as g, resolveCallAgentId as h, mapProviderStatusToEndReason as i, cleanupTailscaleExposureRoute as l, resolveVoiceResponseModel as m, normalizeProxyIp as n, canonicalizeVoiceCallMediaBase64 as o, chunkAudio as p, isProviderStatusTerminal as r, getHeader as s, createVoiceCallRuntime as t, getTailscaleSelfInfo as u, mapVoiceToPolly as v, validateProviderConfig as w, resolveVoiceCallPublicPathPrefix as x, VoiceCallConfigSchema as y };
3763
+ export { resolveUserPath as _, normalizeProviderStatus as a, resolveWebhookExposureStatus as c, setupTailscaleExposureRoutes as d, TELEPHONY_DEFAULT_TTS_TIMEOUT_MS as f, resolveVoiceCallAgentId as g, resolveCallAgentId as h, mapProviderStatusToEndReason as i, cleanupTailscaleExposureRoute as l, resolveVoiceResponseModel as m, normalizeProxyIp as n, canonicalizeVoiceCallMediaBase64 as o, chunkAudio as p, isProviderStatusTerminal as r, getHeader as s, createVoiceCallRuntime as t, getTailscaleSelfInfo as u, escapeXml as v, mapVoiceToPolly as y };