refactor(voice-call): share tts deep merge

This commit is contained in:
Peter Steinberger
2026-03-08 03:22:47 +00:00
parent 5659d7f985
commit ed437434af
3 changed files with 27 additions and 65 deletions

View File

@@ -5,6 +5,7 @@ import {
TtsProviderSchema,
} from "openclaw/plugin-sdk/voice-call";
import { z } from "zod";
import { deepMergeDefined } from "./deep-merge.js";
// -----------------------------------------------------------------------------
// Phone Number Validation
@@ -376,45 +377,7 @@ function normalizeVoiceCallTtsConfig(
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,
});
return TtsConfigSchema.parse(deepMergeDefined(defaults ?? {}, overrides ?? {}));
}
export function normalizeVoiceCallConfig(config: VoiceCallConfigInput): VoiceCallConfig {

View File

@@ -0,0 +1,23 @@
const BLOCKED_MERGE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
export function deepMergeDefined(base: unknown, override: unknown): unknown {
if (!isPlainObject(base) || !isPlainObject(override)) {
return override === undefined ? base : override;
}
const result: Record<string, unknown> = { ...base };
for (const [key, value] of Object.entries(override)) {
if (BLOCKED_MERGE_KEYS.has(key) || value === undefined) {
continue;
}
const existing = result[key];
result[key] = key in result ? deepMergeDefined(existing, value) : value;
}
return result;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

View File

@@ -1,5 +1,6 @@
import type { VoiceCallTtsConfig } from "./config.js";
import type { CoreConfig } from "./core-bridge.js";
import { deepMergeDefined } from "./deep-merge.js";
import { convertPcmToMulaw8k } from "./telephony-audio.js";
export type TelephonyTtsRuntime = {
@@ -20,8 +21,6 @@ export type TelephonyTtsProvider = {
synthesizeForTelephony: (text: string) => Promise<Buffer>;
};
const BLOCKED_MERGE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
export function createTelephonyTtsProvider(params: {
coreConfig: CoreConfig;
ttsOverride?: VoiceCallTtsConfig;
@@ -79,28 +78,5 @@ function mergeTtsConfig(
if (!base) {
return override;
}
return deepMerge(base, override);
}
function deepMerge<T>(base: T, override: T): T {
if (!isPlainObject(base) || !isPlainObject(override)) {
return override;
}
const result: Record<string, unknown> = { ...base };
for (const [key, value] of Object.entries(override)) {
if (BLOCKED_MERGE_KEYS.has(key) || value === undefined) {
continue;
}
const existing = (base as Record<string, unknown>)[key];
if (isPlainObject(existing) && isPlainObject(value)) {
result[key] = deepMerge(existing, value);
} else {
result[key] = value;
}
}
return result as T;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
return deepMergeDefined(base, override) as VoiceCallTtsConfig;
}