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 } from "@sinclair/typebox";
|
||||||
|
|
||||||
import type { CoreConfig } from "./src/core-bridge.js";
|
import type { CoreConfig } from "./src/core-bridge.js";
|
||||||
import {
|
import {
|
||||||
VoiceCallConfigSchema,
|
VoiceCallConfigSchema,
|
||||||
|
resolveVoiceCallConfig,
|
||||||
validateProviderConfig,
|
validateProviderConfig,
|
||||||
type VoiceCallConfig,
|
type VoiceCallConfig,
|
||||||
} from "./src/config.js";
|
} from "./src/config.js";
|
||||||
@@ -145,8 +145,10 @@ const voiceCallPlugin = {
|
|||||||
description: "Voice-call plugin with Telnyx/Twilio/Plivo providers",
|
description: "Voice-call plugin with Telnyx/Twilio/Plivo providers",
|
||||||
configSchema: voiceCallConfigSchema,
|
configSchema: voiceCallConfigSchema,
|
||||||
register(api) {
|
register(api) {
|
||||||
const cfg = voiceCallConfigSchema.parse(api.pluginConfig);
|
const config = resolveVoiceCallConfig(
|
||||||
const validation = validateProviderConfig(cfg);
|
voiceCallConfigSchema.parse(api.pluginConfig),
|
||||||
|
);
|
||||||
|
const validation = validateProviderConfig(config);
|
||||||
|
|
||||||
if (api.pluginConfig && typeof api.pluginConfig === "object") {
|
if (api.pluginConfig && typeof api.pluginConfig === "object") {
|
||||||
const raw = api.pluginConfig as Record<string, unknown>;
|
const raw = api.pluginConfig as Record<string, unknown>;
|
||||||
@@ -167,7 +169,7 @@ const voiceCallPlugin = {
|
|||||||
let runtime: VoiceCallRuntime | null = null;
|
let runtime: VoiceCallRuntime | null = null;
|
||||||
|
|
||||||
const ensureRuntime = async () => {
|
const ensureRuntime = async () => {
|
||||||
if (!cfg.enabled) {
|
if (!config.enabled) {
|
||||||
throw new Error("Voice call disabled in plugin config");
|
throw new Error("Voice call disabled in plugin config");
|
||||||
}
|
}
|
||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
@@ -176,7 +178,7 @@ const voiceCallPlugin = {
|
|||||||
if (runtime) return runtime;
|
if (runtime) return runtime;
|
||||||
if (!runtimePromise) {
|
if (!runtimePromise) {
|
||||||
runtimePromise = createVoiceCallRuntime({
|
runtimePromise = createVoiceCallRuntime({
|
||||||
config: cfg,
|
config,
|
||||||
coreConfig: api.config as CoreConfig,
|
coreConfig: api.config as CoreConfig,
|
||||||
ttsRuntime: api.runtime.tts,
|
ttsRuntime: api.runtime.tts,
|
||||||
logger: api.logger,
|
logger: api.logger,
|
||||||
@@ -457,7 +459,7 @@ const voiceCallPlugin = {
|
|||||||
({ program }) =>
|
({ program }) =>
|
||||||
registerVoiceCallCli({
|
registerVoiceCallCli({
|
||||||
program,
|
program,
|
||||||
config: cfg,
|
config,
|
||||||
ensureRuntime,
|
ensureRuntime,
|
||||||
logger: api.logger,
|
logger: api.logger,
|
||||||
}),
|
}),
|
||||||
@@ -467,7 +469,7 @@ const voiceCallPlugin = {
|
|||||||
api.registerService({
|
api.registerService({
|
||||||
id: "voicecall",
|
id: "voicecall",
|
||||||
start: async () => {
|
start: async () => {
|
||||||
if (!cfg.enabled) return;
|
if (!config.enabled) return;
|
||||||
try {
|
try {
|
||||||
await ensureRuntime();
|
await ensureRuntime();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -381,6 +381,52 @@ export type VoiceCallConfig = z.infer<typeof VoiceCallConfigSchema>;
|
|||||||
// Configuration Helpers
|
// 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.
|
* 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.provider === "telnyx") {
|
||||||
if (!config.telnyx?.apiKey && !process.env.TELNYX_API_KEY) {
|
if (!config.telnyx?.apiKey) {
|
||||||
errors.push(
|
errors.push(
|
||||||
"plugins.entries.voice-call.config.telnyx.apiKey is required (or set TELNYX_API_KEY env)",
|
"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(
|
errors.push(
|
||||||
"plugins.entries.voice-call.config.telnyx.connectionId is required (or set TELNYX_CONNECTION_ID env)",
|
"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.provider === "twilio") {
|
||||||
if (!config.twilio?.accountSid && !process.env.TWILIO_ACCOUNT_SID) {
|
if (!config.twilio?.accountSid) {
|
||||||
errors.push(
|
errors.push(
|
||||||
"plugins.entries.voice-call.config.twilio.accountSid is required (or set TWILIO_ACCOUNT_SID env)",
|
"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(
|
errors.push(
|
||||||
"plugins.entries.voice-call.config.twilio.authToken is required (or set TWILIO_AUTH_TOKEN env)",
|
"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.provider === "plivo") {
|
||||||
if (!config.plivo?.authId && !process.env.PLIVO_AUTH_ID) {
|
if (!config.plivo?.authId) {
|
||||||
errors.push(
|
errors.push(
|
||||||
"plugins.entries.voice-call.config.plivo.authId is required (or set PLIVO_AUTH_ID env)",
|
"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(
|
errors.push(
|
||||||
"plugins.entries.voice-call.config.plivo.authToken is required (or set PLIVO_AUTH_TOKEN env)",
|
"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 { CoreConfig } from "./core-bridge.js";
|
||||||
import type { VoiceCallConfig } from "./config.js";
|
import type { VoiceCallConfig } from "./config.js";
|
||||||
import { validateProviderConfig } from "./config.js";
|
import { resolveVoiceCallConfig, validateProviderConfig } from "./config.js";
|
||||||
import { CallManager } from "./manager.js";
|
import { CallManager } from "./manager.js";
|
||||||
import type { VoiceCallProvider } from "./providers/base.js";
|
import type { VoiceCallProvider } from "./providers/base.js";
|
||||||
import { MockProvider } from "./providers/mock.js";
|
import { MockProvider } from "./providers/mock.js";
|
||||||
@@ -37,17 +37,15 @@ function resolveProvider(config: VoiceCallConfig): VoiceCallProvider {
|
|||||||
switch (config.provider) {
|
switch (config.provider) {
|
||||||
case "telnyx":
|
case "telnyx":
|
||||||
return new TelnyxProvider({
|
return new TelnyxProvider({
|
||||||
apiKey: config.telnyx?.apiKey ?? process.env.TELNYX_API_KEY,
|
apiKey: config.telnyx?.apiKey,
|
||||||
connectionId:
|
connectionId: config.telnyx?.connectionId,
|
||||||
config.telnyx?.connectionId ?? process.env.TELNYX_CONNECTION_ID,
|
publicKey: config.telnyx?.publicKey,
|
||||||
publicKey: config.telnyx?.publicKey ?? process.env.TELNYX_PUBLIC_KEY,
|
|
||||||
});
|
});
|
||||||
case "twilio":
|
case "twilio":
|
||||||
return new TwilioProvider(
|
return new TwilioProvider(
|
||||||
{
|
{
|
||||||
accountSid:
|
accountSid: config.twilio?.accountSid,
|
||||||
config.twilio?.accountSid ?? process.env.TWILIO_ACCOUNT_SID,
|
authToken: config.twilio?.authToken,
|
||||||
authToken: config.twilio?.authToken ?? process.env.TWILIO_AUTH_TOKEN,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
allowNgrokFreeTier: config.tunnel?.allowNgrokFreeTier ?? true,
|
allowNgrokFreeTier: config.tunnel?.allowNgrokFreeTier ?? true,
|
||||||
@@ -61,8 +59,8 @@ function resolveProvider(config: VoiceCallConfig): VoiceCallProvider {
|
|||||||
case "plivo":
|
case "plivo":
|
||||||
return new PlivoProvider(
|
return new PlivoProvider(
|
||||||
{
|
{
|
||||||
authId: config.plivo?.authId ?? process.env.PLIVO_AUTH_ID,
|
authId: config.plivo?.authId,
|
||||||
authToken: config.plivo?.authToken ?? process.env.PLIVO_AUTH_TOKEN,
|
authToken: config.plivo?.authToken,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
publicUrl: config.publicUrl,
|
publicUrl: config.publicUrl,
|
||||||
@@ -85,7 +83,7 @@ export async function createVoiceCallRuntime(params: {
|
|||||||
ttsRuntime?: TelephonyTtsRuntime;
|
ttsRuntime?: TelephonyTtsRuntime;
|
||||||
logger?: Logger;
|
logger?: Logger;
|
||||||
}): Promise<VoiceCallRuntime> {
|
}): Promise<VoiceCallRuntime> {
|
||||||
const { config, coreConfig, ttsRuntime, logger } = params;
|
const { config: rawConfig, coreConfig, ttsRuntime, logger } = params;
|
||||||
const log = logger ?? {
|
const log = logger ?? {
|
||||||
info: console.log,
|
info: console.log,
|
||||||
warn: console.warn,
|
warn: console.warn,
|
||||||
@@ -93,6 +91,8 @@ export async function createVoiceCallRuntime(params: {
|
|||||||
debug: console.debug,
|
debug: console.debug,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const config = resolveVoiceCallConfig(rawConfig);
|
||||||
|
|
||||||
if (!config.enabled) {
|
if (!config.enabled) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Voice call disabled. Enable the plugin entry in config.",
|
"Voice call disabled. Enable the plugin entry in config.",
|
||||||
@@ -125,9 +125,8 @@ export async function createVoiceCallRuntime(params: {
|
|||||||
provider: config.tunnel.provider,
|
provider: config.tunnel.provider,
|
||||||
port: config.serve.port,
|
port: config.serve.port,
|
||||||
path: config.serve.path,
|
path: config.serve.path,
|
||||||
ngrokAuthToken:
|
ngrokAuthToken: config.tunnel.ngrokAuthToken,
|
||||||
config.tunnel.ngrokAuthToken ?? process.env.NGROK_AUTHTOKEN,
|
ngrokDomain: config.tunnel.ngrokDomain,
|
||||||
ngrokDomain: config.tunnel.ngrokDomain ?? process.env.NGROK_DOMAIN,
|
|
||||||
});
|
});
|
||||||
publicUrl = tunnelResult?.publicUrl ?? null;
|
publicUrl = tunnelResult?.publicUrl ?? null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user