feat: Resolve voice call configuration by merging environment variables into settings.

This commit is contained in:
Shakker Nerd
2026-01-26 14:01:08 +00:00
parent 1da6c05e62
commit d37df28319
3 changed files with 74 additions and 27 deletions

View File

@@ -1,8 +1,8 @@
import { Type } from "@sinclair/typebox";
import type { CoreConfig } from "./src/core-bridge.js";
import {
VoiceCallConfigSchema,
resolveVoiceCallConfig,
validateProviderConfig,
type VoiceCallConfig,
} from "./src/config.js";
@@ -145,8 +145,10 @@ const voiceCallPlugin = {
description: "Voice-call plugin with Telnyx/Twilio/Plivo providers",
configSchema: voiceCallConfigSchema,
register(api) {
const cfg = voiceCallConfigSchema.parse(api.pluginConfig);
const validation = validateProviderConfig(cfg);
const config = resolveVoiceCallConfig(
voiceCallConfigSchema.parse(api.pluginConfig),
);
const validation = validateProviderConfig(config);
if (api.pluginConfig && typeof api.pluginConfig === "object") {
const raw = api.pluginConfig as Record<string, unknown>;
@@ -167,7 +169,7 @@ const voiceCallPlugin = {
let runtime: VoiceCallRuntime | null = null;
const ensureRuntime = async () => {
if (!cfg.enabled) {
if (!config.enabled) {
throw new Error("Voice call disabled in plugin config");
}
if (!validation.valid) {
@@ -176,7 +178,7 @@ const voiceCallPlugin = {
if (runtime) return runtime;
if (!runtimePromise) {
runtimePromise = createVoiceCallRuntime({
config: cfg,
config,
coreConfig: api.config as CoreConfig,
ttsRuntime: api.runtime.tts,
logger: api.logger,
@@ -457,7 +459,7 @@ const voiceCallPlugin = {
({ program }) =>
registerVoiceCallCli({
program,
config: cfg,
config,
ensureRuntime,
logger: api.logger,
}),
@@ -467,7 +469,7 @@ const voiceCallPlugin = {
api.registerService({
id: "voicecall",
start: async () => {
if (!cfg.enabled) return;
if (!config.enabled) return;
try {
await ensureRuntime();
} catch (err) {

View File

@@ -381,6 +381,52 @@ export type VoiceCallConfig = z.infer<typeof VoiceCallConfigSchema>;
// Configuration Helpers
// -----------------------------------------------------------------------------
/**
* Resolves the configuration by merging environment variables into missing fields.
* Returns a new configuration object with environment variables applied.
*/
export function resolveVoiceCallConfig(config: VoiceCallConfig): VoiceCallConfig {
const resolved = JSON.parse(JSON.stringify(config)) as VoiceCallConfig;
// Telnyx
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;
}
// Twilio
if (resolved.provider === "twilio") {
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;
}
// Plivo
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;
}
// Tunnel Config
resolved.tunnel = resolved.tunnel ?? { provider: "none", allowNgrokFreeTier: true };
resolved.tunnel.ngrokAuthToken =
resolved.tunnel.ngrokAuthToken ?? process.env.NGROK_AUTHTOKEN;
resolved.tunnel.ngrokDomain =
resolved.tunnel.ngrokDomain ?? process.env.NGROK_DOMAIN;
return resolved;
}
/**
* Validate that the configuration has all required fields for the selected provider.
*/
@@ -403,12 +449,12 @@ export function validateProviderConfig(config: VoiceCallConfig): {
}
if (config.provider === "telnyx") {
if (!config.telnyx?.apiKey && !process.env.TELNYX_API_KEY) {
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 && !process.env.TELNYX_CONNECTION_ID) {
if (!config.telnyx?.connectionId) {
errors.push(
"plugins.entries.voice-call.config.telnyx.connectionId is required (or set TELNYX_CONNECTION_ID env)",
);
@@ -416,12 +462,12 @@ export function validateProviderConfig(config: VoiceCallConfig): {
}
if (config.provider === "twilio") {
if (!config.twilio?.accountSid && !process.env.TWILIO_ACCOUNT_SID) {
if (!config.twilio?.accountSid) {
errors.push(
"plugins.entries.voice-call.config.twilio.accountSid is required (or set TWILIO_ACCOUNT_SID env)",
);
}
if (!config.twilio?.authToken && !process.env.TWILIO_AUTH_TOKEN) {
if (!config.twilio?.authToken) {
errors.push(
"plugins.entries.voice-call.config.twilio.authToken is required (or set TWILIO_AUTH_TOKEN env)",
);
@@ -429,12 +475,12 @@ export function validateProviderConfig(config: VoiceCallConfig): {
}
if (config.provider === "plivo") {
if (!config.plivo?.authId && !process.env.PLIVO_AUTH_ID) {
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 && !process.env.PLIVO_AUTH_TOKEN) {
if (!config.plivo?.authToken) {
errors.push(
"plugins.entries.voice-call.config.plivo.authToken is required (or set PLIVO_AUTH_TOKEN env)",
);

View File

@@ -1,6 +1,6 @@
import type { CoreConfig } from "./core-bridge.js";
import type { VoiceCallConfig } from "./config.js";
import { validateProviderConfig } from "./config.js";
import { resolveVoiceCallConfig, validateProviderConfig } from "./config.js";
import { CallManager } from "./manager.js";
import type { VoiceCallProvider } from "./providers/base.js";
import { MockProvider } from "./providers/mock.js";
@@ -37,17 +37,15 @@ function resolveProvider(config: VoiceCallConfig): VoiceCallProvider {
switch (config.provider) {
case "telnyx":
return new TelnyxProvider({
apiKey: config.telnyx?.apiKey ?? process.env.TELNYX_API_KEY,
connectionId:
config.telnyx?.connectionId ?? process.env.TELNYX_CONNECTION_ID,
publicKey: config.telnyx?.publicKey ?? process.env.TELNYX_PUBLIC_KEY,
apiKey: config.telnyx?.apiKey,
connectionId: config.telnyx?.connectionId,
publicKey: config.telnyx?.publicKey,
});
case "twilio":
return new TwilioProvider(
{
accountSid:
config.twilio?.accountSid ?? process.env.TWILIO_ACCOUNT_SID,
authToken: config.twilio?.authToken ?? process.env.TWILIO_AUTH_TOKEN,
accountSid: config.twilio?.accountSid,
authToken: config.twilio?.authToken,
},
{
allowNgrokFreeTier: config.tunnel?.allowNgrokFreeTier ?? true,
@@ -61,8 +59,8 @@ function resolveProvider(config: VoiceCallConfig): VoiceCallProvider {
case "plivo":
return new PlivoProvider(
{
authId: config.plivo?.authId ?? process.env.PLIVO_AUTH_ID,
authToken: config.plivo?.authToken ?? process.env.PLIVO_AUTH_TOKEN,
authId: config.plivo?.authId,
authToken: config.plivo?.authToken,
},
{
publicUrl: config.publicUrl,
@@ -85,7 +83,7 @@ export async function createVoiceCallRuntime(params: {
ttsRuntime?: TelephonyTtsRuntime;
logger?: Logger;
}): Promise<VoiceCallRuntime> {
const { config, coreConfig, ttsRuntime, logger } = params;
const { config: rawConfig, coreConfig, ttsRuntime, logger } = params;
const log = logger ?? {
info: console.log,
warn: console.warn,
@@ -93,6 +91,8 @@ export async function createVoiceCallRuntime(params: {
debug: console.debug,
};
const config = resolveVoiceCallConfig(rawConfig);
if (!config.enabled) {
throw new Error(
"Voice call disabled. Enable the plugin entry in config.",
@@ -125,9 +125,8 @@ export async function createVoiceCallRuntime(params: {
provider: config.tunnel.provider,
port: config.serve.port,
path: config.serve.path,
ngrokAuthToken:
config.tunnel.ngrokAuthToken ?? process.env.NGROK_AUTHTOKEN,
ngrokDomain: config.tunnel.ngrokDomain ?? process.env.NGROK_DOMAIN,
ngrokAuthToken: config.tunnel.ngrokAuthToken,
ngrokDomain: config.tunnel.ngrokDomain,
});
publicUrl = tunnelResult?.publicUrl ?? null;
} catch (err) {