@@ -27,6 +27,7 @@ import {
2727 canonicalizeSpeechProviderId ,
2828 getSpeechProvider ,
2929 listSpeechProviders ,
30+ normalizeSpeechProviderId ,
3031 normalizeTtsAutoMode ,
3132 parseTtsDirectives ,
3233 scheduleCleanup ,
@@ -56,6 +57,7 @@ export type ResolvedTtsConfig = {
5657 prefsPath ?: string ;
5758 maxTextLength : number ;
5859 timeoutMs : number ;
60+ rawConfig ?: TtsConfig ;
5961} ;
6062
6163type TtsUserPrefs = {
@@ -117,6 +119,16 @@ function resolveConfiguredTtsAutoMode(raw: TtsConfig): TtsAutoMode {
117119 return normalizeTtsAutoMode ( raw . auto ) ?? ( raw . enabled ? "always" : "off" ) ;
118120}
119121
122+ function normalizeConfiguredSpeechProviderId (
123+ providerId : string | undefined ,
124+ ) : TtsProvider | undefined {
125+ const normalized = normalizeSpeechProviderId ( providerId ) ;
126+ if ( ! normalized ) {
127+ return undefined ;
128+ }
129+ return normalized === "edge" ? "microsoft" : normalized ;
130+ }
131+
120132function resolveTtsPrefsPathValue ( prefsPath : string | undefined ) : string {
121133 if ( prefsPath ?. trim ( ) ) {
122134 return resolveUserPath ( prefsPath . trim ( ) ) ;
@@ -184,26 +196,76 @@ function asProviderConfigMap(value: unknown): Record<string, unknown> {
184196 : { } ;
185197}
186198
187- function resolveSpeechProviderConfigs (
188- raw : TtsConfig ,
189- cfg : OpenClawConfig ,
190- timeoutMs : number ,
191- ) : Record < string , SpeechProviderConfig > {
192- const providerConfigs : Record < string , SpeechProviderConfig > = { } ;
199+ function resolveRawProviderConfig (
200+ raw : TtsConfig | undefined ,
201+ providerId : string ,
202+ ) : SpeechProviderConfig {
203+ if ( ! raw ) {
204+ return { } ;
205+ }
193206 const rawProviders = asProviderConfigMap ( raw . providers ) ;
194- for ( const provider of listSpeechProviders ( cfg ) ) {
195- providerConfigs [ provider . id ] =
196- provider . resolveConfig ?.( {
197- cfg,
198- rawConfig : {
199- ...( raw as Record < string , unknown > ) ,
200- providers : rawProviders ,
201- } ,
202- timeoutMs,
203- } ) ??
204- asProviderConfig ( rawProviders [ provider . id ] ?? ( raw as Record < string , unknown > ) [ provider . id ] ) ;
207+ const direct = rawProviders [ providerId ] ?? ( raw as Record < string , unknown > ) [ providerId ] ;
208+ return asProviderConfig ( direct ) ;
209+ }
210+
211+ function resolveLazyProviderConfig (
212+ config : ResolvedTtsConfig ,
213+ providerId : string ,
214+ cfg ?: OpenClawConfig ,
215+ ) : SpeechProviderConfig {
216+ const canonical =
217+ normalizeConfiguredSpeechProviderId ( providerId ) ?? providerId . trim ( ) . toLowerCase ( ) ;
218+ const existing = config . providerConfigs [ canonical ] ;
219+ if ( existing ) {
220+ return existing ;
221+ }
222+ const rawConfig = resolveRawProviderConfig ( config . rawConfig , canonical ) ;
223+ const resolvedProvider = getSpeechProvider ( canonical , cfg ) ;
224+ const next =
225+ cfg && resolvedProvider ?. resolveConfig
226+ ? resolvedProvider . resolveConfig ( {
227+ cfg,
228+ rawConfig : {
229+ ...( config . rawConfig as Record < string , unknown > | undefined ) ,
230+ providers : asProviderConfigMap ( config . rawConfig ?. providers ) ,
231+ } ,
232+ timeoutMs : config . timeoutMs ,
233+ } )
234+ : rawConfig ;
235+ config . providerConfigs [ canonical ] = next ;
236+ return next ;
237+ }
238+
239+ function collectDirectProviderConfigEntries ( raw : TtsConfig ) : Record < string , SpeechProviderConfig > {
240+ const entries : Record < string , SpeechProviderConfig > = { } ;
241+ const rawProviders = asProviderConfigMap ( raw . providers ) ;
242+ for ( const [ providerId , value ] of Object . entries ( rawProviders ) ) {
243+ const normalized = normalizeConfiguredSpeechProviderId ( providerId ) ?? providerId ;
244+ entries [ normalized ] = asProviderConfig ( value ) ;
245+ }
246+ const reservedKeys = new Set ( [
247+ "auto" ,
248+ "enabled" ,
249+ "maxTextLength" ,
250+ "mode" ,
251+ "modelOverrides" ,
252+ "prefsPath" ,
253+ "provider" ,
254+ "providers" ,
255+ "summaryModel" ,
256+ "timeoutMs" ,
257+ ] ) ;
258+ for ( const [ key , value ] of Object . entries ( raw as Record < string , unknown > ) ) {
259+ if ( reservedKeys . has ( key ) ) {
260+ continue ;
261+ }
262+ if ( typeof value !== "object" || value === null || Array . isArray ( value ) ) {
263+ continue ;
264+ }
265+ const normalized = normalizeConfiguredSpeechProviderId ( key ) ?? key ;
266+ entries [ normalized ] ??= asProviderConfig ( value ) ;
205267 }
206- return providerConfigs ;
268+ return entries ;
207269}
208270
209271export function getResolvedSpeechProviderConfig (
@@ -212,8 +274,10 @@ export function getResolvedSpeechProviderConfig(
212274 cfg ?: OpenClawConfig ,
213275) : SpeechProviderConfig {
214276 const canonical =
215- canonicalizeSpeechProviderId ( providerId , cfg ) ?? providerId . trim ( ) . toLowerCase ( ) ;
216- return config . providerConfigs [ canonical ] ?? { } ;
277+ canonicalizeSpeechProviderId ( providerId , cfg ) ??
278+ normalizeConfiguredSpeechProviderId ( providerId ) ??
279+ providerId . trim ( ) . toLowerCase ( ) ;
280+ return resolveLazyProviderConfig ( config , canonical , cfg ) ;
217281}
218282
219283export function resolveTtsConfig ( cfg : OpenClawConfig ) : ResolvedTtsConfig {
@@ -225,15 +289,16 @@ export function resolveTtsConfig(cfg: OpenClawConfig): ResolvedTtsConfig {
225289 auto,
226290 mode : raw . mode ?? "final" ,
227291 provider :
228- canonicalizeSpeechProviderId ( raw . provider , cfg ) ??
229- resolveRegistryDefaultSpeechProviderId ( cfg ) ,
292+ normalizeConfiguredSpeechProviderId ( raw . provider ) ??
293+ ( providerSource === "config" ? raw . provider ?. trim ( ) . toLowerCase ( ) || "" : "" ) ,
230294 providerSource,
231295 summaryModel : raw . summaryModel ?. trim ( ) || undefined ,
232296 modelOverrides : resolveModelOverridePolicy ( raw . modelOverrides ) ,
233- providerConfigs : resolveSpeechProviderConfigs ( raw , cfg , timeoutMs ) ,
297+ providerConfigs : collectDirectProviderConfigEntries ( raw ) ,
234298 prefsPath : raw . prefsPath ,
235299 maxTextLength : raw . maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH ,
236300 timeoutMs,
301+ rawConfig : raw ,
237302 } ;
238303}
239304
@@ -368,12 +433,14 @@ export function setTtsEnabled(prefsPath: string, enabled: boolean): void {
368433
369434export function getTtsProvider ( config : ResolvedTtsConfig , prefsPath : string ) : TtsProvider {
370435 const prefs = readPrefs ( prefsPath ) ;
371- const prefsProvider = canonicalizeSpeechProviderId ( prefs . tts ?. provider ) ;
436+ const prefsProvider =
437+ canonicalizeSpeechProviderId ( prefs . tts ?. provider ) ??
438+ normalizeConfiguredSpeechProviderId ( prefs . tts ?. provider ) ;
372439 if ( prefsProvider ) {
373440 return prefsProvider ;
374441 }
375442 if ( config . providerSource === "config" ) {
376- return canonicalizeSpeechProviderId ( config . provider ) ?? config . provider ;
443+ return normalizeConfiguredSpeechProviderId ( config . provider ) ?? config . provider ;
377444 }
378445
379446 for ( const provider of sortSpeechProvidersForAutoSelection ( ) ) {
0 commit comments