import Foundation import OpenClawKit struct TalkModeGatewayConfigState { let activeProvider: String let normalizedPayload: Bool let missingResolvedPayload: Bool let defaultVoiceId: String? let voiceAliases: [String: String] let defaultModelId: String let defaultOutputFormat: String? let rawConfigApiKey: String? let interruptOnSpeech: Bool? let silenceTimeoutMs: Int } enum TalkModeGatewayConfigParser { static func parse( config: [String: Any], defaultProvider: String, defaultModelIdFallback: String, defaultSilenceTimeoutMs: Int ) -> TalkModeGatewayConfigState { let talk = TalkConfigParsing.bridgeFoundationDictionary(config["talk"] as? [String: Any]) let selection = TalkConfigParsing.selectProviderConfig( talk, defaultProvider: defaultProvider, allowLegacyFallback: false) let activeProvider = selection?.provider ?? defaultProvider let activeConfig = selection?.config let defaultVoiceId = activeConfig?["voiceId"]?.stringValue? .trimmingCharacters(in: .whitespacesAndNewlines) let voiceAliases: [String: String] if let aliases = activeConfig?["voiceAliases"]?.dictionaryValue { var resolved: [String: String] = [:] for (key, value) in aliases { 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 } resolved[normalizedKey] = trimmedId } voiceAliases = resolved } else { voiceAliases = [:] } let model = activeConfig?["modelId"]?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) let defaultModelId = (model?.isEmpty == false) ? model! : defaultModelIdFallback let defaultOutputFormat = activeConfig?["outputFormat"]?.stringValue? .trimmingCharacters(in: .whitespacesAndNewlines) let rawConfigApiKey = activeConfig?["apiKey"]?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) let interruptOnSpeech = talk?["interruptOnSpeech"]?.boolValue let silenceTimeoutMs = TalkConfigParsing.resolvedSilenceTimeoutMs( talk, fallback: defaultSilenceTimeoutMs) return TalkModeGatewayConfigState( activeProvider: activeProvider, normalizedPayload: selection?.normalizedPayload == true, missingResolvedPayload: talk != nil && selection == nil, defaultVoiceId: defaultVoiceId, voiceAliases: voiceAliases, defaultModelId: defaultModelId, defaultOutputFormat: defaultOutputFormat, rawConfigApiKey: rawConfigApiKey, interruptOnSpeech: interruptOnSpeech, silenceTimeoutMs: silenceTimeoutMs) } }