mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 06:27:27 +00:00
refactor: share Apple talk config parsing
This commit is contained in:
@@ -1970,57 +1970,15 @@ extension TalkModeManager {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
struct TalkProviderConfigSelection {
|
||||
let provider: String
|
||||
let config: [String: Any]
|
||||
static func selectTalkProviderConfig(_ talk: [String: AnyCodable]?) -> TalkProviderConfigSelection? {
|
||||
TalkConfigParsing.selectProviderConfig(
|
||||
talk,
|
||||
defaultProvider: Self.defaultTalkProvider,
|
||||
allowLegacyFallback: false)
|
||||
}
|
||||
|
||||
private static func normalizedTalkProviderID(_ raw: String?) -> String? {
|
||||
let trimmed = (raw ?? "").trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
|
||||
static func selectTalkProviderConfig(_ talk: [String: Any]?) -> TalkProviderConfigSelection? {
|
||||
guard let talk else { return nil }
|
||||
let rawProvider = talk["provider"] as? String
|
||||
let rawProviders = talk["providers"] as? [String: Any]
|
||||
guard rawProvider != nil || rawProviders != nil else { return nil }
|
||||
let providers = rawProviders ?? [:]
|
||||
let normalizedProviders = providers.reduce(into: [String: [String: Any]]()) { acc, entry in
|
||||
guard
|
||||
let providerID = Self.normalizedTalkProviderID(entry.key),
|
||||
let config = entry.value as? [String: Any]
|
||||
else { return }
|
||||
acc[providerID] = config
|
||||
}
|
||||
let providerID =
|
||||
Self.normalizedTalkProviderID(rawProvider) ??
|
||||
normalizedProviders.keys.min() ??
|
||||
Self.defaultTalkProvider
|
||||
return TalkProviderConfigSelection(
|
||||
provider: providerID,
|
||||
config: normalizedProviders[providerID] ?? [:])
|
||||
}
|
||||
|
||||
static func resolvedSilenceTimeoutMs(_ talk: [String: Any]?) -> Int {
|
||||
switch talk?["silenceTimeoutMs"] {
|
||||
case let timeout as Int where timeout > 0:
|
||||
return timeout
|
||||
case let timeout as Double
|
||||
where timeout > 0 && timeout.rounded(.towardZero) == timeout && timeout <= Double(Int.max):
|
||||
return Int(timeout)
|
||||
case let timeout as NSNumber:
|
||||
if CFGetTypeID(timeout) == CFBooleanGetTypeID() {
|
||||
return Self.defaultSilenceTimeoutMs
|
||||
}
|
||||
let value = timeout.doubleValue
|
||||
if value > 0 && value.rounded(.towardZero) == value && value <= Double(Int.max) {
|
||||
return Int(value)
|
||||
}
|
||||
return Self.defaultSilenceTimeoutMs
|
||||
default:
|
||||
return Self.defaultSilenceTimeoutMs
|
||||
}
|
||||
static func resolvedSilenceTimeoutMs(_ talk: [String: AnyCodable]?) -> Int {
|
||||
TalkConfigParsing.resolvedSilenceTimeoutMs(talk, fallback: Self.defaultSilenceTimeoutMs)
|
||||
}
|
||||
|
||||
func reloadConfig() async {
|
||||
@@ -2034,7 +1992,7 @@ extension TalkModeManager {
|
||||
)
|
||||
guard let json = try JSONSerialization.jsonObject(with: res) as? [String: Any] else { return }
|
||||
guard let config = json["config"] as? [String: Any] else { return }
|
||||
let talk = config["talk"] as? [String: Any]
|
||||
let talk = TalkConfigParsing.bridgeFoundationDictionary(config["talk"] as? [String: Any])
|
||||
let selection = Self.selectTalkProviderConfig(talk)
|
||||
if talk != nil, selection == nil {
|
||||
GatewayDiagnostics.log(
|
||||
@@ -2043,12 +2001,12 @@ extension TalkModeManager {
|
||||
let activeProvider = selection?.provider ?? Self.defaultTalkProvider
|
||||
let activeConfig = selection?.config
|
||||
let silenceTimeoutMs = Self.resolvedSilenceTimeoutMs(talk)
|
||||
self.defaultVoiceId = (activeConfig?["voiceId"] as? String)?
|
||||
self.defaultVoiceId = activeConfig?["voiceId"]?.stringValue?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let aliases = activeConfig?["voiceAliases"] as? [String: Any] {
|
||||
if let aliases = activeConfig?["voiceAliases"]?.dictionaryValue {
|
||||
var resolved: [String: String] = [:]
|
||||
for (key, value) in aliases {
|
||||
guard let id = value as? String else { continue }
|
||||
guard let id = value.stringValue else { continue }
|
||||
let normalizedKey = key.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
let trimmedId = id.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !normalizedKey.isEmpty, !trimmedId.isEmpty else { continue }
|
||||
@@ -2061,14 +2019,14 @@ extension TalkModeManager {
|
||||
if !self.voiceOverrideActive {
|
||||
self.currentVoiceId = self.defaultVoiceId
|
||||
}
|
||||
let model = (activeConfig?["modelId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let model = activeConfig?["modelId"]?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
self.defaultModelId = (model?.isEmpty == false) ? model : Self.defaultModelIdFallback
|
||||
if !self.modelOverrideActive {
|
||||
self.currentModelId = self.defaultModelId
|
||||
}
|
||||
self.defaultOutputFormat = (activeConfig?["outputFormat"] as? String)?
|
||||
self.defaultOutputFormat = activeConfig?["outputFormat"]?.stringValue?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let rawConfigApiKey = (activeConfig?["apiKey"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let rawConfigApiKey = activeConfig?["apiKey"]?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let configApiKey = Self.normalizedTalkApiKey(rawConfigApiKey)
|
||||
let localApiKey = Self.normalizedTalkApiKey(
|
||||
GatewaySettingsStore.loadTalkProviderApiKey(provider: activeProvider))
|
||||
@@ -2087,7 +2045,7 @@ extension TalkModeManager {
|
||||
self.gatewayTalkDefaultModelId = self.defaultModelId
|
||||
self.gatewayTalkApiKeyConfigured = (self.apiKey?.isEmpty == false)
|
||||
self.gatewayTalkConfigLoaded = true
|
||||
if let interrupt = talk?["interruptOnSpeech"] as? Bool {
|
||||
if let interrupt = talk?["interruptOnSpeech"]?.boolValue {
|
||||
self.interruptOnSpeech = interrupt
|
||||
}
|
||||
self.silenceWindow = TimeInterval(silenceTimeoutMs) / 1000
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import OpenClawKit
|
||||
import Testing
|
||||
@testable import OpenClaw
|
||||
|
||||
@@ -15,9 +16,10 @@ import Testing
|
||||
"voiceId": "voice-legacy",
|
||||
]
|
||||
|
||||
let selection = TalkModeManager.selectTalkProviderConfig(talk)
|
||||
let selection = TalkModeManager.selectTalkProviderConfig(
|
||||
TalkConfigParsing.bridgeFoundationDictionary(talk))
|
||||
#expect(selection?.provider == "elevenlabs")
|
||||
#expect(selection?.config["voiceId"] as? String == "voice-normalized")
|
||||
#expect(selection?.config["voiceId"]?.stringValue == "voice-normalized")
|
||||
}
|
||||
|
||||
@Test func ignoresLegacyTalkFieldsWhenNormalizedPayloadMissing() {
|
||||
@@ -26,7 +28,8 @@ import Testing
|
||||
"apiKey": "legacy-key", // pragma: allowlist secret
|
||||
]
|
||||
|
||||
let selection = TalkModeManager.selectTalkProviderConfig(talk)
|
||||
let selection = TalkModeManager.selectTalkProviderConfig(
|
||||
TalkConfigParsing.bridgeFoundationDictionary(talk))
|
||||
#expect(selection == nil)
|
||||
}
|
||||
|
||||
@@ -53,7 +56,7 @@ import Testing
|
||||
"silenceTimeoutMs": 1500,
|
||||
]
|
||||
|
||||
#expect(TalkModeManager.resolvedSilenceTimeoutMs(talk) == 1500)
|
||||
#expect(TalkModeManager.resolvedSilenceTimeoutMs(TalkConfigParsing.bridgeFoundationDictionary(talk)) == 1500)
|
||||
}
|
||||
|
||||
@Test func defaultsSilenceTimeoutMsWhenMissing() {
|
||||
@@ -65,7 +68,7 @@ import Testing
|
||||
"silenceTimeoutMs": 0,
|
||||
]
|
||||
|
||||
#expect(TalkModeManager.resolvedSilenceTimeoutMs(talk) == 900)
|
||||
#expect(TalkModeManager.resolvedSilenceTimeoutMs(TalkConfigParsing.bridgeFoundationDictionary(talk)) == 900)
|
||||
}
|
||||
|
||||
@Test func defaultsSilenceTimeoutMsWhenBool() {
|
||||
@@ -73,6 +76,6 @@ import Testing
|
||||
"silenceTimeoutMs": true,
|
||||
]
|
||||
|
||||
#expect(TalkModeManager.resolvedSilenceTimeoutMs(talk) == 900)
|
||||
#expect(TalkModeManager.resolvedSilenceTimeoutMs(TalkConfigParsing.bridgeFoundationDictionary(talk)) == 900)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user