Files
openclaw/apps/macos/Sources/OpenClaw/TalkModeGatewayConfig.swift
2026-03-08 16:22:48 +00:00

105 lines
4.4 KiB
Swift

import Foundation
import OpenClawKit
struct TalkModeGatewayConfigState {
let activeProvider: String
let normalizedPayload: Bool
let missingResolvedPayload: Bool
let voiceId: String?
let voiceAliases: [String: String]
let modelId: String?
let outputFormat: String?
let interruptOnSpeech: Bool
let silenceTimeoutMs: Int
let apiKey: String?
let seamColorHex: String?
}
enum TalkModeGatewayConfigParser {
static func parse(
snapshot: ConfigSnapshot,
defaultProvider: String,
defaultModelIdFallback: String,
defaultSilenceTimeoutMs: Int,
envVoice: String?,
sagVoice: String?,
envApiKey: String?
) -> TalkModeGatewayConfigState {
let talk = snapshot.config?["talk"]?.dictionaryValue
let selection = TalkConfigParsing.selectProviderConfig(talk, defaultProvider: defaultProvider)
let activeProvider = selection?.provider ?? defaultProvider
let activeConfig = selection?.config
let silenceTimeoutMs = TalkConfigParsing.resolvedSilenceTimeoutMs(
talk,
fallback: defaultSilenceTimeoutMs)
let ui = snapshot.config?["ui"]?.dictionaryValue
let rawSeam = ui?["seamColor"]?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let voice = activeConfig?["voiceId"]?.stringValue
let rawAliases = activeConfig?["voiceAliases"]?.dictionaryValue
let resolvedAliases: [String: String] =
rawAliases?.reduce(into: [:]) { acc, entry in
let key = entry.key.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
let value = entry.value.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !key.isEmpty, !value.isEmpty else { return }
acc[key] = value
} ?? [:]
let model = activeConfig?["modelId"]?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines)
let resolvedModel = (model?.isEmpty == false) ? model! : defaultModelIdFallback
let outputFormat = activeConfig?["outputFormat"]?.stringValue
let interrupt = talk?["interruptOnSpeech"]?.boolValue
let apiKey = activeConfig?["apiKey"]?.stringValue
let resolvedVoice: String? = if activeProvider == defaultProvider {
(voice?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? voice : nil) ??
(envVoice?.isEmpty == false ? envVoice : nil) ??
(sagVoice?.isEmpty == false ? sagVoice : nil)
} else {
(voice?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? voice : nil)
}
let resolvedApiKey: String? = if activeProvider == defaultProvider {
(envApiKey?.isEmpty == false ? envApiKey : nil) ??
(apiKey?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? apiKey : nil)
} else {
nil
}
return TalkModeGatewayConfigState(
activeProvider: activeProvider,
normalizedPayload: selection?.normalizedPayload == true,
missingResolvedPayload: talk != nil && selection == nil,
voiceId: resolvedVoice,
voiceAliases: resolvedAliases,
modelId: resolvedModel,
outputFormat: outputFormat,
interruptOnSpeech: interrupt ?? true,
silenceTimeoutMs: silenceTimeoutMs,
apiKey: resolvedApiKey,
seamColorHex: rawSeam.isEmpty ? nil : rawSeam)
}
static func fallback(
defaultModelIdFallback: String,
defaultSilenceTimeoutMs: Int,
envVoice: String?,
sagVoice: String?,
envApiKey: String?
) -> TalkModeGatewayConfigState {
let resolvedVoice =
(envVoice?.isEmpty == false ? envVoice : nil) ??
(sagVoice?.isEmpty == false ? sagVoice : nil)
let resolvedApiKey = envApiKey?.isEmpty == false ? envApiKey : nil
return TalkModeGatewayConfigState(
activeProvider: "elevenlabs",
normalizedPayload: false,
missingResolvedPayload: false,
voiceId: resolvedVoice,
voiceAliases: [:],
modelId: defaultModelIdFallback,
outputFormat: nil,
interruptOnSpeech: true,
silenceTimeoutMs: defaultSilenceTimeoutMs,
apiKey: resolvedApiKey,
seamColorHex: nil)
}
}