feat: Resolve voice call configuration by merging environment variables into settings.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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)",
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user