diff --git a/extensions/voice-call/src/config.test.ts b/extensions/voice-call/src/config.test.ts index fad18e67faa..1b12e9e84c5 100644 --- a/extensions/voice-call/src/config.test.ts +++ b/extensions/voice-call/src/config.test.ts @@ -189,4 +189,30 @@ describe("normalizeVoiceCallConfig", () => { expect(normalized.tunnel.provider).toBe("none"); expect(normalized.webhookSecurity.allowedHosts).toEqual([]); }); + + it("accepts partial nested TTS overrides and preserves nested objects", () => { + const normalized = normalizeVoiceCallConfig({ + tts: { + provider: "elevenlabs", + elevenlabs: { + apiKey: { + source: "env", + provider: "elevenlabs", + id: "ELEVENLABS_API_KEY", + }, + voiceSettings: { + speed: 1.1, + }, + }, + }, + }); + + expect(normalized.tts?.provider).toBe("elevenlabs"); + expect(normalized.tts?.elevenlabs?.apiKey).toEqual({ + source: "env", + provider: "elevenlabs", + id: "ELEVENLABS_API_KEY", + }); + expect(normalized.tts?.elevenlabs?.voiceSettings).toEqual({ speed: 1.1 }); + }); }); diff --git a/extensions/voice-call/src/config.ts b/extensions/voice-call/src/config.ts index 405eda179d9..9721cf380e2 100644 --- a/extensions/voice-call/src/config.ts +++ b/extensions/voice-call/src/config.ts @@ -368,6 +368,55 @@ function cloneDefaultVoiceCallConfig(): VoiceCallConfig { return structuredClone(DEFAULT_VOICE_CALL_CONFIG); } +function normalizeVoiceCallTtsConfig( + defaults: VoiceCallTtsConfig, + overrides: DeepPartial> | undefined, +): VoiceCallTtsConfig { + if (!defaults && !overrides) { + return undefined; + } + + return TtsConfigSchema.parse({ + ...(defaults ?? {}), + ...(overrides ?? {}), + modelOverrides: + defaults?.modelOverrides || overrides?.modelOverrides + ? { + ...(defaults?.modelOverrides ?? {}), + ...(overrides?.modelOverrides ?? {}), + } + : undefined, + elevenlabs: + defaults?.elevenlabs || overrides?.elevenlabs + ? { + ...(defaults?.elevenlabs ?? {}), + ...(overrides?.elevenlabs ?? {}), + voiceSettings: + defaults?.elevenlabs?.voiceSettings || overrides?.elevenlabs?.voiceSettings + ? { + ...(defaults?.elevenlabs?.voiceSettings ?? {}), + ...(overrides?.elevenlabs?.voiceSettings ?? {}), + } + : undefined, + } + : undefined, + openai: + defaults?.openai || overrides?.openai + ? { + ...(defaults?.openai ?? {}), + ...(overrides?.openai ?? {}), + } + : undefined, + edge: + defaults?.edge || overrides?.edge + ? { + ...(defaults?.edge ?? {}), + ...(overrides?.edge ?? {}), + } + : undefined, + }); +} + export function normalizeVoiceCallConfig(config: VoiceCallConfigInput): VoiceCallConfig { const defaults = cloneDefaultVoiceCallConfig(); return { @@ -387,7 +436,7 @@ export function normalizeVoiceCallConfig(config: VoiceCallConfigInput): VoiceCal }, streaming: { ...defaults.streaming, ...config.streaming }, stt: { ...defaults.stt, ...config.stt }, - tts: config.tts ?? defaults.tts, + tts: normalizeVoiceCallTtsConfig(defaults.tts, config.tts), }; } diff --git a/src/daemon/service.ts b/src/daemon/service.ts index b0de0baa49b..9685ed1ece5 100644 --- a/src/daemon/service.ts +++ b/src/daemon/service.ts @@ -64,7 +64,9 @@ export type GatewayService = { readRuntime: (env: GatewayServiceEnv) => Promise; }; -const GATEWAY_SERVICE_REGISTRY = { +type SupportedGatewayServicePlatform = "darwin" | "linux" | "win32"; + +const GATEWAY_SERVICE_REGISTRY: Record = { darwin: { label: "LaunchAgent", loadedText: "loaded", @@ -101,12 +103,17 @@ const GATEWAY_SERVICE_REGISTRY = { readCommand: readScheduledTaskCommand, readRuntime: readScheduledTaskRuntime, }, -} satisfies Partial>; +}; + +function isSupportedGatewayServicePlatform( + platform: NodeJS.Platform, +): platform is SupportedGatewayServicePlatform { + return Object.hasOwn(GATEWAY_SERVICE_REGISTRY, platform); +} export function resolveGatewayService(): GatewayService { - const service = GATEWAY_SERVICE_REGISTRY[process.platform]; - if (service) { - return service; + if (isSupportedGatewayServicePlatform(process.platform)) { + return GATEWAY_SERVICE_REGISTRY[process.platform]; } throw new Error(`Gateway service install not supported on ${process.platform}`); }