mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
refactor: require canonical talk resolved payload
This commit is contained in:
@@ -93,29 +93,7 @@ class TalkModeManager(
|
|||||||
val rawProviders = talk["providers"].asObjectOrNull()
|
val rawProviders = talk["providers"].asObjectOrNull()
|
||||||
val hasNormalizedPayload = rawProvider != null || rawProviders != null
|
val hasNormalizedPayload = rawProvider != null || rawProviders != null
|
||||||
if (hasNormalizedPayload) {
|
if (hasNormalizedPayload) {
|
||||||
val providers =
|
return null
|
||||||
rawProviders?.entries?.mapNotNull { (key, value) ->
|
|
||||||
val providerId = normalizeTalkProviderId(key) ?: return@mapNotNull null
|
|
||||||
val providerConfig = value.asObjectOrNull() ?: return@mapNotNull null
|
|
||||||
providerId to providerConfig
|
|
||||||
}?.toMap().orEmpty()
|
|
||||||
val explicitProviderId = normalizeTalkProviderId(rawProvider)
|
|
||||||
if (explicitProviderId != null) {
|
|
||||||
if (providers.isNotEmpty() && providers[explicitProviderId] == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return TalkProviderConfigSelection(
|
|
||||||
provider = explicitProviderId,
|
|
||||||
config = providers[explicitProviderId] ?: buildJsonObject {},
|
|
||||||
normalizedPayload = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val providerId = providers.keys.singleOrNull() ?: return null
|
|
||||||
return TalkProviderConfigSelection(
|
|
||||||
provider = providerId,
|
|
||||||
config = providers[providerId] ?: buildJsonObject {},
|
|
||||||
normalizedPayload = true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return TalkProviderConfigSelection(
|
return TalkProviderConfigSelection(
|
||||||
provider = defaultTalkProvider,
|
provider = defaultTalkProvider,
|
||||||
@@ -1425,6 +1403,9 @@ class TalkModeManager(
|
|||||||
val config = root?.get("config").asObjectOrNull()
|
val config = root?.get("config").asObjectOrNull()
|
||||||
val talk = config?.get("talk").asObjectOrNull()
|
val talk = config?.get("talk").asObjectOrNull()
|
||||||
val selection = selectTalkProviderConfig(talk)
|
val selection = selectTalkProviderConfig(talk)
|
||||||
|
if (talk != null && selection == null) {
|
||||||
|
Log.w(tag, "talk config ignored: normalized payload missing talk.resolved")
|
||||||
|
}
|
||||||
val activeProvider = selection?.provider ?: defaultTalkProvider
|
val activeProvider = selection?.provider ?: defaultTalkProvider
|
||||||
val activeConfig = selection?.config
|
val activeConfig = selection?.config
|
||||||
val sessionCfg = config?.get("session").asObjectOrNull()
|
val sessionCfg = config?.get("session").asObjectOrNull()
|
||||||
|
|||||||
@@ -62,10 +62,7 @@ class TalkModeConfigParsingTest {
|
|||||||
.jsonObject
|
.jsonObject
|
||||||
|
|
||||||
val selection = TalkModeManager.selectTalkProviderConfig(talk)
|
val selection = TalkModeManager.selectTalkProviderConfig(talk)
|
||||||
assertNotNull(selection)
|
assertEquals(null, selection)
|
||||||
assertEquals("elevenlabs", selection?.provider)
|
|
||||||
assertTrue(selection?.normalizedPayload == true)
|
|
||||||
assertEquals("voice-normalized", selection?.config?.get("voiceId")?.jsonPrimitive?.content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1996,7 +1996,7 @@ extension TalkModeManager {
|
|||||||
let selection = Self.selectTalkProviderConfig(talk)
|
let selection = Self.selectTalkProviderConfig(talk)
|
||||||
if talk != nil, selection == nil {
|
if talk != nil, selection == nil {
|
||||||
GatewayDiagnostics.log(
|
GatewayDiagnostics.log(
|
||||||
"talk config ignored: legacy payload unsupported on iOS beta; expected talk.provider/providers")
|
"talk config ignored: normalized payload missing talk.resolved")
|
||||||
}
|
}
|
||||||
let activeProvider = selection?.provider ?? Self.defaultTalkProvider
|
let activeProvider = selection?.provider ?? Self.defaultTalkProvider
|
||||||
let activeConfig = selection?.config
|
let activeConfig = selection?.config
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Testing
|
|||||||
private let iOSSilenceTimeoutMs = 900
|
private let iOSSilenceTimeoutMs = 900
|
||||||
|
|
||||||
@Suite struct TalkConfigParsingTests {
|
@Suite struct TalkConfigParsingTests {
|
||||||
@Test func prefersNormalizedTalkProviderPayload() {
|
@Test func rejectsNormalizedTalkProviderPayloadWithoutResolved() {
|
||||||
let talk: [String: Any] = [
|
let talk: [String: Any] = [
|
||||||
"provider": "elevenlabs",
|
"provider": "elevenlabs",
|
||||||
"providers": [
|
"providers": [
|
||||||
@@ -20,8 +20,7 @@ private let iOSSilenceTimeoutMs = 900
|
|||||||
TalkConfigParsing.bridgeFoundationDictionary(talk),
|
TalkConfigParsing.bridgeFoundationDictionary(talk),
|
||||||
defaultProvider: "elevenlabs",
|
defaultProvider: "elevenlabs",
|
||||||
allowLegacyFallback: false)
|
allowLegacyFallback: false)
|
||||||
#expect(selection?.provider == "elevenlabs")
|
#expect(selection == nil)
|
||||||
#expect(selection?.config["voiceId"]?.stringValue == "voice-normalized")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func ignoresLegacyTalkFieldsWhenNormalizedPayloadMissing() {
|
@Test func ignoresLegacyTalkFieldsWhenNormalizedPayloadMissing() {
|
||||||
|
|||||||
@@ -831,6 +831,9 @@ extension TalkModeRuntime {
|
|||||||
timeoutMs: 8000)
|
timeoutMs: 8000)
|
||||||
let talk = snap.config?["talk"]?.dictionaryValue
|
let talk = snap.config?["talk"]?.dictionaryValue
|
||||||
let selection = Self.selectTalkProviderConfig(talk)
|
let selection = Self.selectTalkProviderConfig(talk)
|
||||||
|
if talk != nil, selection == nil {
|
||||||
|
self.ttsLogger.info("talk config ignored: normalized payload missing talk.resolved")
|
||||||
|
}
|
||||||
let activeProvider = selection?.provider ?? Self.defaultTalkProvider
|
let activeProvider = selection?.provider ?? Self.defaultTalkProvider
|
||||||
let activeConfig = selection?.config
|
let activeConfig = selection?.config
|
||||||
let silenceTimeoutMs = Self.resolvedSilenceTimeoutMs(talk)
|
let silenceTimeoutMs = Self.resolvedSilenceTimeoutMs(talk)
|
||||||
@@ -870,7 +873,7 @@ extension TalkModeRuntime {
|
|||||||
self.ttsLogger
|
self.ttsLogger
|
||||||
.info("talk provider \(activeProvider, privacy: .public) unsupported; using system voice")
|
.info("talk provider \(activeProvider, privacy: .public) unsupported; using system voice")
|
||||||
} else if selection?.normalizedPayload == true {
|
} else if selection?.normalizedPayload == true {
|
||||||
self.ttsLogger.info("talk config provider elevenlabs")
|
self.ttsLogger.info("talk config provider from talk.resolved")
|
||||||
}
|
}
|
||||||
return TalkRuntimeConfig(
|
return TalkRuntimeConfig(
|
||||||
voiceId: resolvedVoice,
|
voiceId: resolvedVoice,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Testing
|
|||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
struct TalkModeConfigParsingTests {
|
struct TalkModeConfigParsingTests {
|
||||||
@Test func `prefers normalized talk provider payload`() {
|
@Test func `rejects normalized talk provider payload without resolved`() {
|
||||||
let talk: [String: AnyCodable] = [
|
let talk: [String: AnyCodable] = [
|
||||||
"provider": AnyCodable("elevenlabs"),
|
"provider": AnyCodable("elevenlabs"),
|
||||||
"providers": AnyCodable([
|
"providers": AnyCodable([
|
||||||
@@ -15,9 +15,7 @@ struct TalkModeConfigParsingTests {
|
|||||||
]
|
]
|
||||||
|
|
||||||
let selection = TalkModeRuntime.selectTalkProviderConfig(talk)
|
let selection = TalkModeRuntime.selectTalkProviderConfig(talk)
|
||||||
#expect(selection?.provider == "elevenlabs")
|
#expect(selection == nil)
|
||||||
#expect(selection?.normalizedPayload == true)
|
|
||||||
#expect(selection?.config["voiceId"]?.stringValue == "voice-normalized")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func `falls back to legacy talk fields when normalized payload missing`() {
|
@Test func `falls back to legacy talk fields when normalized payload missing`() {
|
||||||
|
|||||||
@@ -26,28 +26,9 @@ public enum TalkConfigParsing {
|
|||||||
if let resolvedSelection = self.resolvedProviderConfig(talk) {
|
if let resolvedSelection = self.resolvedProviderConfig(talk) {
|
||||||
return resolvedSelection
|
return resolvedSelection
|
||||||
}
|
}
|
||||||
let rawProvider = talk["provider"]?.stringValue
|
let hasNormalizedPayload = talk["provider"] != nil || talk["providers"] != nil
|
||||||
let rawProviders = talk["providers"]
|
|
||||||
let hasNormalizedPayload = rawProvider != nil || rawProviders != nil
|
|
||||||
if hasNormalizedPayload {
|
if hasNormalizedPayload {
|
||||||
let normalizedProviders = self.normalizedTalkProviders(rawProviders)
|
return nil
|
||||||
let explicitProviderID = self.normalizedTalkProviderID(rawProvider)
|
|
||||||
if let explicitProviderID {
|
|
||||||
if !normalizedProviders.isEmpty, normalizedProviders[explicitProviderID] == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return TalkProviderConfigSelection(
|
|
||||||
provider: explicitProviderID,
|
|
||||||
config: normalizedProviders[explicitProviderID] ?? [:],
|
|
||||||
normalizedPayload: true)
|
|
||||||
}
|
|
||||||
guard normalizedProviders.count == 1, let providerID = normalizedProviders.keys.first else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return TalkProviderConfigSelection(
|
|
||||||
provider: providerID,
|
|
||||||
config: normalizedProviders[providerID] ?? [:],
|
|
||||||
normalizedPayload: true)
|
|
||||||
}
|
}
|
||||||
guard allowLegacyFallback else { return nil }
|
guard allowLegacyFallback else { return nil }
|
||||||
return TalkProviderConfigSelection(
|
return TalkProviderConfigSelection(
|
||||||
@@ -92,15 +73,4 @@ public enum TalkConfigParsing {
|
|||||||
config: resolved["config"]?.dictionaryValue ?? [:],
|
config: resolved["config"]?.dictionaryValue ?? [:],
|
||||||
normalizedPayload: true)
|
normalizedPayload: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func normalizedTalkProviders(_ raw: AnyCodable?) -> [String: [String: AnyCodable]] {
|
|
||||||
guard let providerMap = raw?.dictionaryValue else { return [:] }
|
|
||||||
return providerMap.reduce(into: [String: [String: AnyCodable]]()) { acc, entry in
|
|
||||||
guard
|
|
||||||
let providerID = self.normalizedTalkProviderID(entry.key),
|
|
||||||
let providerConfig = entry.value.dictionaryValue
|
|
||||||
else { return }
|
|
||||||
acc[providerID] = providerConfig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ struct TalkConfigParsingTests {
|
|||||||
#expect(selection?.config["voiceId"]?.stringValue == "voice-resolved")
|
#expect(selection?.config["voiceId"]?.stringValue == "voice-resolved")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func prefersNormalizedTalkProviderPayload() {
|
@Test func rejectsNormalizedTalkProviderPayloadWithoutResolved() {
|
||||||
let talk: [String: AnyCodable] = [
|
let talk: [String: AnyCodable] = [
|
||||||
"provider": AnyCodable("elevenlabs"),
|
"provider": AnyCodable("elevenlabs"),
|
||||||
"providers": AnyCodable([
|
"providers": AnyCodable([
|
||||||
@@ -36,9 +36,7 @@ struct TalkConfigParsingTests {
|
|||||||
]
|
]
|
||||||
|
|
||||||
let selection = TalkConfigParsing.selectProviderConfig(talk, defaultProvider: "elevenlabs")
|
let selection = TalkConfigParsing.selectProviderConfig(talk, defaultProvider: "elevenlabs")
|
||||||
#expect(selection?.provider == "elevenlabs")
|
#expect(selection == nil)
|
||||||
#expect(selection?.normalizedPayload == true)
|
|
||||||
#expect(selection?.config["voiceId"]?.stringValue == "voice-normalized")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func fallsBackToLegacyTalkFieldsWhenNormalizedPayloadMissing() {
|
@Test func fallsBackToLegacyTalkFieldsWhenNormalizedPayloadMissing() {
|
||||||
|
|||||||
Reference in New Issue
Block a user