惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks
Martin Fowler
Martin Fowler
宝玉的分享
宝玉的分享
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
M
MIT News - Artificial intelligence
U
Unit 42
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
I
InfoQ
WordPress大学
WordPress大学
H
Help Net Security
D
Docker
B
Blog
腾讯CDC
A
About on SuperTechFans
Recent Announcements
Recent Announcements
雷峰网
雷峰网
有赞技术团队
有赞技术团队
C
Check Point Blog
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(plugins): start configured speech providers (#76540) ...
amknight · 2026-05-03 · via Recent Commits to openclaw:main

@@ -0,0 +1,189 @@

1+

import type { OpenClawConfig } from "../config/types.openclaw.js";

2+

import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";

3+4+

const TTS_PROVIDER_CONFIG_RESERVED_KEYS = new Set([

5+

"auto",

6+

"enabled",

7+

"maxTextLength",

8+

"mode",

9+

"modelOverrides",

10+

"persona",

11+

"personas",

12+

"prefsPath",

13+

"provider",

14+

"providers",

15+

"summaryModel",

16+

"timeoutMs",

17+

]);

18+19+

function isRecord(value: unknown): value is Record<string, unknown> {

20+

return Boolean(value && typeof value === "object" && !Array.isArray(value));

21+

}

22+23+

function isConfigActivationValueEnabled(value: unknown): boolean {

24+

if (value === false) {

25+

return false;

26+

}

27+

if (isRecord(value) && value.enabled === false) {

28+

return false;

29+

}

30+

return true;

31+

}

32+33+

export function normalizeConfiguredSpeechProviderIdForStartup(value: unknown): string | undefined {

34+

if (typeof value !== "string") {

35+

return undefined;

36+

}

37+

const normalized = normalizeOptionalLowercaseString(value);

38+

if (!normalized) {

39+

return undefined;

40+

}

41+

return normalized === "edge" ? "microsoft" : normalized;

42+

}

43+44+

function resolveProviderConfigActivation(

45+

ttsConfig: Record<string, unknown>,

46+

providerId: string,

47+

): boolean | undefined {

48+

let fromProviders: boolean | undefined;

49+

if (isRecord(ttsConfig.providers)) {

50+

for (const [key, providerConfig] of Object.entries(ttsConfig.providers)) {

51+

if (normalizeConfiguredSpeechProviderIdForStartup(key) === providerId) {

52+

fromProviders = isConfigActivationValueEnabled(providerConfig);

53+

}

54+

}

55+

}

56+

if (fromProviders !== undefined) {

57+

return fromProviders;

58+

}

59+60+

for (const [key, providerConfig] of Object.entries(ttsConfig)) {

61+

if (TTS_PROVIDER_CONFIG_RESERVED_KEYS.has(key) || !isRecord(providerConfig)) {

62+

continue;

63+

}

64+

if (normalizeConfiguredSpeechProviderIdForStartup(key) === providerId) {

65+

return isConfigActivationValueEnabled(providerConfig);

66+

}

67+

}

68+

return undefined;

69+

}

70+71+

function addProviderIfEnabled(

72+

target: Set<string>,

73+

ttsConfig: Record<string, unknown>,

74+

providerId: unknown,

75+

): void {

76+

const normalized = normalizeConfiguredSpeechProviderIdForStartup(providerId);

77+

if (!normalized) {

78+

return;

79+

}

80+

if (resolveProviderConfigActivation(ttsConfig, normalized) !== false) {

81+

target.add(normalized);

82+

}

83+

}

84+85+

function findActivePersona(

86+

ttsConfig: Record<string, unknown>,

87+

): Record<string, unknown> | undefined {

88+

const personaId = normalizeOptionalLowercaseString(

89+

typeof ttsConfig.persona === "string" ? ttsConfig.persona : undefined,

90+

);

91+

if (!personaId || !isRecord(ttsConfig.personas)) {

92+

return undefined;

93+

}

94+

for (const [id, persona] of Object.entries(ttsConfig.personas)) {

95+

if (normalizeOptionalLowercaseString(id) === personaId && isRecord(persona)) {

96+

return persona;

97+

}

98+

}

99+

return undefined;

100+

}

101+102+

function addActivePersonaProvider(target: Set<string>, ttsConfig: Record<string, unknown>): void {

103+

const persona = findActivePersona(ttsConfig);

104+

if (!persona) {

105+

return;

106+

}

107+

const provider = normalizeConfiguredSpeechProviderIdForStartup(persona.provider);

108+

if (!provider) {

109+

return;

110+

}

111+

const rootActivation = resolveProviderConfigActivation(ttsConfig, provider);

112+

const personaActivation = resolveProviderConfigActivation(persona, provider);

113+

if ((personaActivation ?? rootActivation) !== false) {

114+

target.add(provider);

115+

}

116+

}

117+118+

function addConfiguredTtsProviderIds(target: Set<string>, value: unknown): void {

119+

if (!isRecord(value)) {

120+

return;

121+

}

122+

addProviderIfEnabled(target, value, value.provider);

123+

addActivePersonaProvider(target, value);

124+125+

if (isRecord(value.providers)) {

126+

for (const [providerId, providerConfig] of Object.entries(value.providers)) {

127+

if (isConfigActivationValueEnabled(providerConfig)) {

128+

addProviderIfEnabled(target, value, providerId);

129+

}

130+

}

131+

}

132+

for (const [key, providerConfig] of Object.entries(value)) {

133+

if (TTS_PROVIDER_CONFIG_RESERVED_KEYS.has(key) || !isRecord(providerConfig)) {

134+

continue;

135+

}

136+

if (isConfigActivationValueEnabled(providerConfig)) {

137+

addProviderIfEnabled(target, value, key);

138+

}

139+

}

140+

}

141+142+

export function collectConfiguredSpeechProviderIds(config: OpenClawConfig): ReadonlySet<string> {

143+

const configured = new Set<string>();

144+

addConfiguredTtsProviderIds(configured, config.messages?.tts);

145+146+

const agents = config.agents;

147+

if (isRecord(agents) && Array.isArray(agents.list)) {

148+

for (const agent of agents.list) {

149+

if (isRecord(agent)) {

150+

addConfiguredTtsProviderIds(configured, agent.tts);

151+

}

152+

}

153+

}

154+155+

const channels = config.channels;

156+

if (isRecord(channels)) {

157+

for (const channelConfig of Object.values(channels)) {

158+

if (!isRecord(channelConfig)) {

159+

continue;

160+

}

161+

addConfiguredTtsProviderIds(configured, channelConfig.tts);

162+

if (isRecord(channelConfig.voice)) {

163+

addConfiguredTtsProviderIds(configured, channelConfig.voice.tts);

164+

}

165+

if (isRecord(channelConfig.accounts)) {

166+

for (const accountConfig of Object.values(channelConfig.accounts)) {

167+

if (!isRecord(accountConfig)) {

168+

continue;

169+

}

170+

addConfiguredTtsProviderIds(configured, accountConfig.tts);

171+

if (isRecord(accountConfig.voice)) {

172+

addConfiguredTtsProviderIds(configured, accountConfig.voice.tts);

173+

}

174+

}

175+

}

176+

}

177+

}

178+179+

const pluginEntries = config.plugins?.entries;

180+

if (isRecord(pluginEntries)) {

181+

for (const entry of Object.values(pluginEntries)) {

182+

if (isRecord(entry) && isRecord(entry.config)) {

183+

addConfiguredTtsProviderIds(configured, entry.config.tts);

184+

}

185+

}

186+

}

187+188+

return configured;

189+

}