UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

621 lines (620 loc) 26.4 kB
import { c as isRecord } from "./utils-CCC-BEJH.js"; import { s as hasConfiguredSecretInput, u as normalizeResolvedSecretInputString } from "./types.secrets-_0JOMGE5.js"; import { At as boolean, Et as array, Nn as record, Rn as string, Tn as object, Zn as unknown, dn as literal, wn as number, yt as _enum } from "./schemas-6cH6bZ7o.js"; import { M as TtsConfigSchema } from "./zod-schema.core-1wQTyhOa.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import { r as buildSecretInputSchema } from "./secret-input-DVCzFGxN.js"; import { E as REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES, T as REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME } from "./session-log-runtime-GkK49CJb.js"; import "./realtime-voice-BRCFRGg7.js"; import "./api-CS4KHy5x.js"; //#region extensions/voice-call/src/deep-merge.ts const BLOCKED_MERGE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]); /** Deep-merge plain objects, keeping base values when overrides are undefined. */ function deepMergeDefined(base, override) { if (!isRecord(base) || !isRecord(override)) return override === void 0 ? base : override; const result = { ...base }; for (const [key, value] of Object.entries(override)) { if (BLOCKED_MERGE_KEYS.has(key) || value === void 0) continue; const existing = result[key]; result[key] = key in result ? deepMergeDefined(existing, value) : value; } return result; } //#endregion //#region extensions/voice-call/src/realtime-defaults.ts /** Baseline instructions that keep realtime calls brief and route deep work to agent consult. */ 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.`; //#endregion //#region extensions/voice-call/src/config.ts /** * E.164 phone number format: +[country code][number] * Examples use 555 prefix (reserved for fictional numbers) */ const E164Schema = string().regex(/^\+[1-9]\d{1,14}$/, "Expected E.164 format, e.g. +15550001234"); /** * Controls how inbound calls are handled: * - "disabled": Block all inbound calls (outbound only) * - "allowlist": Only accept calls from numbers in allowFrom * - "pairing": Unknown callers can request pairing (future) * - "open": Accept all inbound calls (dangerous!) */ const InboundPolicySchema = _enum([ "disabled", "allowlist", "pairing", "open" ]); const SecretInputSchema = buildSecretInputSchema(); const TelnyxConfigSchema = object({ /** Telnyx API v2 key */ apiKey: string().min(1).optional(), /** Telnyx connection ID (from Call Control app) */ connectionId: string().min(1).optional(), /** Public key for webhook signature verification */ publicKey: string().min(1).optional() }).strict(); const TwilioConfigSchema = object({ /** Twilio Account SID */ accountSid: string().min(1).optional(), /** Twilio Auth Token */ authToken: SecretInputSchema.optional() }).strict(); const PlivoConfigSchema = object({ /** Plivo Auth ID (starts with MA/SA) */ authId: string().min(1).optional(), /** Plivo Auth Token */ authToken: string().min(1).optional() }).strict(); const VoiceCallNumberRouteConfigSchema = object({ /** Greeting message for inbound calls to this number. */ inboundGreeting: string().optional(), /** TTS override for inbound calls to this number. Deep-merges with global voice-call TTS. */ tts: TtsConfigSchema, /** Agent ID to use for voice response generation for this number. */ agentId: string().min(1).optional(), /** Optional model override for voice responses for this number. */ responseModel: string().optional(), /** System prompt for voice responses for this number. */ responseSystemPrompt: string().optional(), /** Timeout for response generation in ms for this number. */ responseTimeoutMs: number().int().positive().optional() }).strict(); const VoiceCallServeConfigSchema = object({ /** Port to listen on */ port: number().int().positive().default(3334), /** Bind address */ bind: string().default("127.0.0.1"), /** Webhook path */ path: string().min(1).default("/voice/webhook") }).strict().default({ port: 3334, bind: "127.0.0.1", path: "/voice/webhook" }); const VoiceCallTailscaleConfigSchema = object({ /** * Tailscale exposure mode: * - "off": No Tailscale exposure * - "serve": Tailscale serve (private to tailnet) * - "funnel": Tailscale funnel (public HTTPS) */ mode: _enum([ "off", "serve", "funnel" ]).default("off"), /** Path for Tailscale serve/funnel (should usually match serve.path) */ path: string().min(1).default("/voice/webhook") }).strict().default({ mode: "off", path: "/voice/webhook" }); const VoiceCallTunnelConfigSchema = object({ /** * Tunnel provider: * - "none": No tunnel (use publicUrl if set, or manual setup) * - "ngrok": Use ngrok for public HTTPS tunnel * - "tailscale-serve": Tailscale serve (private to tailnet) * - "tailscale-funnel": Tailscale funnel (public HTTPS) */ provider: _enum([ "none", "ngrok", "tailscale-serve", "tailscale-funnel" ]).default("none"), /** ngrok auth token (optional, enables longer sessions and more features) */ ngrokAuthToken: string().min(1).optional(), /** ngrok custom domain (paid feature, e.g., "myapp.ngrok.io") */ ngrokDomain: string().min(1).optional(), /** * Allow ngrok free tier compatibility mode. * When true, forwarded headers may be trusted for loopback requests * to reconstruct the public ngrok URL used for signing. * * IMPORTANT: This does NOT bypass signature verification. */ allowNgrokFreeTierLoopbackBypass: boolean().default(false) }).strict().default({ provider: "none", allowNgrokFreeTierLoopbackBypass: false }); const VoiceCallWebhookSecurityConfigSchema = object({ /** * Allowed hostnames for webhook URL reconstruction. * Only these hosts are accepted from forwarding headers. */ allowedHosts: array(string().min(1)).default([]), /** * Trust X-Forwarded-* headers without a hostname allowlist. * WARNING: Only enable if you trust your proxy configuration. */ trustForwardingHeaders: boolean().default(false), /** * Trusted proxy IP addresses. Forwarded headers are only trusted when * the remote IP matches one of these addresses. */ trustedProxyIPs: array(string().min(1)).default([]) }).strict().default({ allowedHosts: [], trustForwardingHeaders: false, trustedProxyIPs: [] }); /** * Call mode determines how outbound calls behave: * - "notify": Deliver message and auto-hangup after delay (one-way notification) * - "conversation": Stay open for back-and-forth until explicit end or timeout */ const CallModeSchema = _enum(["notify", "conversation"]); const VoiceCallSessionScopeSchema = _enum(["per-phone", "per-call"]); const OutboundConfigSchema = object({ /** Default call mode for outbound calls */ defaultMode: CallModeSchema.default("notify"), /** Seconds to wait after TTS before auto-hangup in notify mode */ notifyHangupDelaySec: number().int().nonnegative().default(3) }).strict().default({ defaultMode: "notify", notifyHangupDelaySec: 3 }); const RealtimeToolSchema = object({ type: literal("function"), name: string().min(1), description: string(), parameters: object({ type: literal("object"), properties: record(string(), unknown()), required: array(string()).optional() }) }).strict(); const VoiceCallRealtimeProvidersConfigSchema = record(string(), record(string(), unknown())).default({}); const VoiceCallRealtimeToolPolicySchema = _enum(REALTIME_VOICE_AGENT_CONSULT_TOOL_POLICIES); const VoiceCallRealtimeConsultPolicySchema = _enum([ "auto", "substantive", "always" ]); const VoiceCallRealtimeFastContextSourceSchema = _enum(["memory", "sessions"]); const VoiceCallRealtimeFastContextConfigSchema = object({ /** Enable bounded memory/session lookup before the full consult agent. */ enabled: boolean().default(false), /** Hard deadline for the fast context lookup. */ timeoutMs: number().int().positive().default(800), /** Maximum memory/session hits to inject into the realtime tool result. */ maxResults: number().int().positive().default(3), /** Indexed sources used by the fast context lookup. */ sources: array(VoiceCallRealtimeFastContextSourceSchema).min(1).default(["memory", "sessions"]), /** Fall back to the full agent consult when fast context has no answer. */ fallbackToConsult: boolean().default(false) }).strict().default({ enabled: false, timeoutMs: 800, maxResults: 3, sources: ["memory", "sessions"], fallbackToConsult: false }); const VoiceCallRealtimeAgentContextConfigSchema = object({ /** Inject a compact agent persona/context capsule into realtime voice instructions. */ enabled: boolean().default(false), /** Maximum number of characters from the generated capsule to append. */ maxChars: number().int().positive().default(6e3), /** Include configured agent identity fields. */ includeIdentity: boolean().default(true), /** Include selected workspace files such as SOUL.md and IDENTITY.md. */ includeWorkspaceFiles: boolean().default(true), /** Workspace-relative files to include, bounded by maxChars. */ files: array(string().min(1)).default([ "SOUL.md", "IDENTITY.md", "USER.md" ]) }).strict().default({ enabled: false, maxChars: 6e3, includeIdentity: true, includeWorkspaceFiles: true, files: [ "SOUL.md", "IDENTITY.md", "USER.md" ] }); const VoiceCallRealtimeConsultThinkingLevelSchema = _enum([ "off", "minimal", "low", "medium", "high", "xhigh", "adaptive", "max" ]); const VoiceCallStreamingProvidersConfigSchema = record(string(), record(string(), unknown())).default({}); const VoiceCallRealtimeConfigSchema = object({ /** Enable realtime voice-to-voice mode. */ enabled: boolean().default(false), /** Provider id from registered realtime voice providers. */ provider: string().min(1).optional(), /** Optional override for the local WebSocket route path. */ streamPath: string().min(1).optional(), /** System instructions passed to the realtime provider. */ instructions: string().default(DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS), /** Tool policy for the shared OpenClaw agent consult tool. */ toolPolicy: VoiceCallRealtimeToolPolicySchema.default("safe-read-only"), /** Guidance for when the realtime model should call the OpenClaw agent consult tool. */ consultPolicy: VoiceCallRealtimeConsultPolicySchema.default("auto"), /** Optional thinking level override for the regular agent behind realtime consults. */ consultThinkingLevel: VoiceCallRealtimeConsultThinkingLevelSchema.optional(), /** Optional fast mode override for the regular agent behind realtime consults. */ consultFastMode: boolean().optional(), /** Tool definitions exposed to the realtime provider. */ tools: array(RealtimeToolSchema).default([]), /** Low-latency memory/session context for the consult tool. */ fastContext: VoiceCallRealtimeFastContextConfigSchema, /** Bounded agent persona/context injection for the fast realtime voice path. */ agentContext: VoiceCallRealtimeAgentContextConfigSchema, /** Provider-owned raw config blobs keyed by provider id. */ providers: VoiceCallRealtimeProvidersConfigSchema }).strict().default({ enabled: false, instructions: DEFAULT_VOICE_CALL_REALTIME_INSTRUCTIONS, toolPolicy: "safe-read-only", consultPolicy: "auto", tools: [], fastContext: { enabled: false, timeoutMs: 800, maxResults: 3, sources: ["memory", "sessions"], fallbackToConsult: false }, agentContext: { enabled: false, maxChars: 6e3, includeIdentity: true, includeWorkspaceFiles: true, files: [ "SOUL.md", "IDENTITY.md", "USER.md" ] }, providers: {} }); const VoiceCallStreamingConfigSchema = object({ /** Enable real-time audio streaming (requires WebSocket support) */ enabled: boolean().default(false), /** Provider id from registered realtime transcription providers. */ provider: string().min(1).optional(), /** WebSocket path for media stream connections */ streamPath: string().min(1).default("/voice/stream"), /** Provider-owned raw config blobs keyed by provider id. */ providers: VoiceCallStreamingProvidersConfigSchema, /** * Close unauthenticated media stream sockets if no valid `start` frame arrives in time. * Protects against pre-auth idle connection hold attacks. */ preStartTimeoutMs: number().int().positive().default(5e3), /** Maximum number of concurrently pending (pre-start) media stream sockets. */ maxPendingConnections: number().int().positive().default(32), /** Maximum pending media stream sockets per source IP. */ maxPendingConnectionsPerIp: number().int().positive().default(4), /** Hard cap for all open media stream sockets (pending + active). */ maxConnections: number().int().positive().default(128) }).strict().default({ enabled: false, streamPath: "/voice/stream", providers: {}, preStartTimeoutMs: 5e3, maxPendingConnections: 32, maxPendingConnectionsPerIp: 4, maxConnections: 128 }); const VoiceCallConfigSchema = object({ /** Enable voice call functionality */ enabled: boolean().default(false), /** Active provider (telnyx, twilio, plivo, or mock) */ provider: _enum([ "telnyx", "twilio", "plivo", "mock" ]).optional(), /** Telnyx-specific configuration */ telnyx: TelnyxConfigSchema.optional(), /** Twilio-specific configuration */ twilio: TwilioConfigSchema.optional(), /** Plivo-specific configuration */ plivo: PlivoConfigSchema.optional(), /** Phone number to call from (E.164) */ fromNumber: E164Schema.optional(), /** Default phone number to call (E.164) */ toNumber: E164Schema.optional(), /** Inbound call policy */ inboundPolicy: InboundPolicySchema.default("disabled"), /** Allowlist of phone numbers for inbound calls (E.164) */ allowFrom: array(E164Schema).default([]), /** Greeting message for inbound calls */ inboundGreeting: string().optional(), /** Per-dialed-number overrides for inbound calls. Keys are E.164 numbers. */ numbers: record(E164Schema, VoiceCallNumberRouteConfigSchema).default({}), /** Outbound call configuration */ outbound: OutboundConfigSchema, /** Maximum call duration in seconds */ maxDurationSeconds: number().int().positive().default(300), /** * Maximum age of a call in seconds before it is automatically reaped. * Catches calls stuck before answer (for example, local mock calls that * never receive provider webhooks). Set to 0 to disable. */ staleCallReaperSeconds: number().int().nonnegative().default(120), /** Silence timeout for end-of-speech detection (ms) */ silenceTimeoutMs: number().int().positive().default(800), /** Timeout for user transcript (ms) */ transcriptTimeoutMs: number().int().positive().default(18e4), /** Ring timeout for outbound calls (ms) */ ringTimeoutMs: number().int().positive().default(3e4), /** Maximum concurrent calls */ maxConcurrentCalls: number().int().positive().default(1), /** Webhook server configuration */ serve: VoiceCallServeConfigSchema, /** @deprecated Prefer tunnel config. */ tailscale: VoiceCallTailscaleConfigSchema, /** Tunnel configuration (unified ngrok/tailscale) */ tunnel: VoiceCallTunnelConfigSchema, /** Webhook signature reconstruction and proxy trust configuration */ webhookSecurity: VoiceCallWebhookSecurityConfigSchema, /** Real-time audio streaming configuration */ streaming: VoiceCallStreamingConfigSchema, /** Realtime voice-to-voice configuration */ realtime: VoiceCallRealtimeConfigSchema, /** Session memory scope for voice conversations. */ sessionScope: VoiceCallSessionScopeSchema.default("per-phone"), /** Public webhook URL override (if set, bypasses tunnel auto-detection) */ publicUrl: string().url().optional(), /** Skip webhook signature verification (development only, NOT for production) */ skipSignatureVerification: boolean().default(false), /** TTS override (deep-merges with core messages.tts) */ tts: TtsConfigSchema, /** Store path for call logs */ store: string().optional(), /** Agent ID to use for voice response generation. Defaults to "main". */ agentId: string().min(1).optional(), /** Optional model override for generating voice responses. */ responseModel: string().optional(), /** System prompt for voice responses */ responseSystemPrompt: string().optional(), /** Timeout for response generation in ms (default 30s) */ responseTimeoutMs: number().int().positive().default(3e4) }).strict(); const TWILIO_AUTH_TOKEN_PATH = "plugins.entries.voice-call.config.twilio.authToken"; const DEFAULT_VOICE_CALL_CONFIG = VoiceCallConfigSchema.parse({}); function cloneDefaultVoiceCallConfig() { return structuredClone(DEFAULT_VOICE_CALL_CONFIG); } function normalizeWebhookLikePath(pathname) { const trimmed = pathname.trim(); if (!trimmed) return "/"; const prefixed = trimmed.startsWith("/") ? trimmed : `/${trimmed}`; if (prefixed === "/") return prefixed; return prefixed.endsWith("/") ? prefixed.slice(0, -1) : prefixed; } function defaultRealtimeStreamPathForServePath(servePath) { const normalized = normalizeWebhookLikePath(servePath); if (normalized.endsWith("/webhook")) return `${normalized.slice(0, -8)}/stream/realtime`; if (normalized === "/") return "/voice/stream/realtime"; return `${normalized}/stream/realtime`; } function normalizeVoiceCallTtsConfig(defaults, overrides) { if (!defaults && !overrides) return; return TtsConfigSchema.parse(deepMergeDefined(defaults ?? {}, overrides ?? {})); } function normalizePhoneRouteKey(phone) { return phone?.replace(/\D/g, "") ?? ""; } function resolveVoiceCallNumberRouteKey(config, phone) { const routes = config.numbers; if (!routes) return; if (phone && Object.hasOwn(routes, phone)) return phone; const normalizedPhone = normalizePhoneRouteKey(phone); if (!normalizedPhone) return; return Object.keys(routes).find((routeKey) => normalizePhoneRouteKey(routeKey) === normalizedPhone); } function resolveVoiceCallEffectiveConfig(config, phoneOrRouteKey) { const numberRouteKey = resolveVoiceCallNumberRouteKey(config, phoneOrRouteKey); if (!numberRouteKey) return { config }; const route = config.numbers[numberRouteKey]; if (!route) return { config }; return { numberRouteKey, config: { ...config, ...route, tts: normalizeVoiceCallTtsConfig(config.tts, route.tts), numbers: config.numbers } }; } function sanitizeVoiceCallProviderConfigs(value) { if (!value) return {}; return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0)); } function sanitizeVoiceCallNumberRoutes(value) { if (!value) return {}; return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0).map(([key, route]) => [key, VoiceCallNumberRouteConfigSchema.parse(route)])); } function resolveTwilioAuthToken(config) { return normalizeResolvedSecretInputString({ value: config.twilio?.authToken, path: TWILIO_AUTH_TOKEN_PATH }); } function normalizeVoiceCallConfig(config) { const defaults = cloneDefaultVoiceCallConfig(); const serve = { ...defaults.serve, ...config.serve }; const streamingProvider = config.streaming?.provider; const streamingProviders = sanitizeVoiceCallProviderConfigs(config.streaming?.providers ?? defaults.streaming.providers); const realtimeProvider = config.realtime?.provider ?? defaults.realtime.provider; const realtimeProviders = sanitizeVoiceCallProviderConfigs(config.realtime?.providers ?? defaults.realtime.providers); const realtimeFastContext = { ...defaults.realtime.fastContext, ...config.realtime?.fastContext, sources: config.realtime?.fastContext?.sources ?? defaults.realtime.fastContext.sources }; const realtimeAgentContext = { ...defaults.realtime.agentContext, ...config.realtime?.agentContext, files: config.realtime?.agentContext?.files ?? defaults.realtime.agentContext.files }; return { ...defaults, ...config, allowFrom: config.allowFrom ?? defaults.allowFrom, numbers: sanitizeVoiceCallNumberRoutes(config.numbers ?? defaults.numbers), outbound: { ...defaults.outbound, ...config.outbound }, serve, tailscale: { ...defaults.tailscale, ...config.tailscale }, tunnel: { ...defaults.tunnel, ...config.tunnel }, webhookSecurity: { ...defaults.webhookSecurity, ...config.webhookSecurity, allowedHosts: config.webhookSecurity?.allowedHosts ?? defaults.webhookSecurity.allowedHosts, trustedProxyIPs: config.webhookSecurity?.trustedProxyIPs ?? defaults.webhookSecurity.trustedProxyIPs }, streaming: { ...defaults.streaming, ...config.streaming, provider: streamingProvider, providers: streamingProviders }, realtime: { ...defaults.realtime, ...config.realtime, provider: realtimeProvider, streamPath: config.realtime?.streamPath ?? defaultRealtimeStreamPathForServePath(serve.path ?? defaults.serve.path), tools: config.realtime?.tools ?? defaults.realtime.tools, consultThinkingLevel: VoiceCallRealtimeConsultThinkingLevelSchema.optional().parse(config.realtime?.consultThinkingLevel ?? defaults.realtime.consultThinkingLevel), consultFastMode: config.realtime?.consultFastMode ?? defaults.realtime.consultFastMode, fastContext: realtimeFastContext, agentContext: realtimeAgentContext, providers: realtimeProviders }, tts: normalizeVoiceCallTtsConfig(defaults.tts, config.tts) }; } function resolveVoiceCallSessionKey(params) { const explicit = params.explicitSessionKey?.trim(); if (explicit) return explicit; if (params.config.sessionScope === "per-call") return `voice:call:${params.callId}`; const normalizedPhone = params.phone?.replace(/\D/g, ""); return normalizedPhone ? `voice:${normalizedPhone}` : `voice:${params.callId}`; } /** * Resolves the configuration by merging environment variables into missing fields. * Returns a new configuration object with environment variables applied. */ function resolveVoiceCallConfig(config) { const resolved = normalizeVoiceCallConfig(config); if (resolved.provider === "telnyx") { resolved.telnyx = resolved.telnyx ?? {}; resolved.telnyx.apiKey = resolved.telnyx.apiKey ?? process.env.TELNYX_API_KEY; resolved.telnyx.connectionId = resolved.telnyx.connectionId ?? process.env.TELNYX_CONNECTION_ID; resolved.telnyx.publicKey = resolved.telnyx.publicKey ?? process.env.TELNYX_PUBLIC_KEY; } if (resolved.provider === "twilio") { resolved.fromNumber = resolved.fromNumber ?? process.env.TWILIO_FROM_NUMBER; resolved.twilio = resolved.twilio ?? {}; resolved.twilio.accountSid = resolved.twilio.accountSid ?? process.env.TWILIO_ACCOUNT_SID; resolved.twilio.authToken = resolved.twilio.authToken ?? process.env.TWILIO_AUTH_TOKEN; } if (resolved.provider === "plivo") { resolved.plivo = resolved.plivo ?? {}; resolved.plivo.authId = resolved.plivo.authId ?? process.env.PLIVO_AUTH_ID; resolved.plivo.authToken = resolved.plivo.authToken ?? process.env.PLIVO_AUTH_TOKEN; } resolved.tunnel = resolved.tunnel ?? { provider: "none", allowNgrokFreeTierLoopbackBypass: false }; resolved.tunnel.allowNgrokFreeTierLoopbackBypass = resolved.tunnel.allowNgrokFreeTierLoopbackBypass ?? false; resolved.tunnel.ngrokAuthToken = resolved.tunnel.ngrokAuthToken ?? process.env.NGROK_AUTHTOKEN; resolved.tunnel.ngrokDomain = resolved.tunnel.ngrokDomain ?? process.env.NGROK_DOMAIN; resolved.webhookSecurity = resolved.webhookSecurity ?? { allowedHosts: [], trustForwardingHeaders: false, trustedProxyIPs: [] }; resolved.webhookSecurity.allowedHosts = resolved.webhookSecurity.allowedHosts ?? []; resolved.webhookSecurity.trustForwardingHeaders = resolved.webhookSecurity.trustForwardingHeaders ?? false; resolved.webhookSecurity.trustedProxyIPs = resolved.webhookSecurity.trustedProxyIPs ?? []; return normalizeVoiceCallConfig(resolved); } /** * Validate that the configuration has all required fields for the selected provider. */ function validateProviderConfig(config) { const errors = []; if (!config.enabled) return { valid: true, errors: [] }; if (!config.provider) errors.push("plugins.entries.voice-call.config.provider is required"); 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"); if (config.provider === "telnyx") { if (!config.telnyx?.apiKey) errors.push("plugins.entries.voice-call.config.telnyx.apiKey is required (or set TELNYX_API_KEY env)"); if (!config.telnyx?.connectionId) errors.push("plugins.entries.voice-call.config.telnyx.connectionId is required (or set TELNYX_CONNECTION_ID env)"); if (!config.skipSignatureVerification && !config.telnyx?.publicKey) errors.push("plugins.entries.voice-call.config.telnyx.publicKey is required (or set TELNYX_PUBLIC_KEY env)"); } if (config.provider === "twilio") { if (!config.twilio?.accountSid) errors.push("plugins.entries.voice-call.config.twilio.accountSid is required (or set TWILIO_ACCOUNT_SID env)"); if (!hasConfiguredSecretInput(config.twilio?.authToken)) errors.push("plugins.entries.voice-call.config.twilio.authToken is required (or set TWILIO_AUTH_TOKEN env)"); } if (config.provider === "plivo") { if (!config.plivo?.authId) errors.push("plugins.entries.voice-call.config.plivo.authId is required (or set PLIVO_AUTH_ID env)"); if (!config.plivo?.authToken) errors.push("plugins.entries.voice-call.config.plivo.authToken is required (or set PLIVO_AUTH_TOKEN env)"); } if (config.realtime.enabled && config.inboundPolicy === "disabled") errors.push("plugins.entries.voice-call.config.inboundPolicy must not be \"disabled\" when realtime.enabled is true"); 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"); if (config.realtime.enabled && config.provider && config.provider !== "twilio" && config.provider !== "telnyx") errors.push("plugins.entries.voice-call.config.provider must be \"twilio\" or \"telnyx\" when realtime.enabled is true"); return { valid: errors.length === 0, errors }; } //#endregion export { resolveVoiceCallEffectiveConfig as a, deepMergeDefined as c, resolveVoiceCallConfig as i, normalizeVoiceCallConfig as n, resolveVoiceCallSessionKey as o, resolveTwilioAuthToken as r, validateProviderConfig as s, VoiceCallConfigSchema as t };