fix: fail closed talk provider selection

This commit is contained in:
Peter Steinberger
2026-03-08 16:03:16 +00:00
parent ca5e352c53
commit b7ad8fd661
8 changed files with 245 additions and 36 deletions

View File

@@ -99,10 +99,18 @@ class TalkModeManager(
val providerConfig = value.asObjectOrNull() ?: return@mapNotNull null
providerId to providerConfig
}?.toMap().orEmpty()
val providerId =
normalizeTalkProviderId(rawProvider)
?: providers.keys.sorted().firstOrNull()
?: defaultTalkProvider
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 {},

View File

@@ -68,6 +68,50 @@ class TalkModeConfigParsingTest {
assertEquals("voice-normalized", selection?.config?.get("voiceId")?.jsonPrimitive?.content)
}
@Test
fun rejectsNormalizedTalkProviderPayloadWhenProviderMissingFromProviders() {
val talk =
json.parseToJsonElement(
"""
{
"provider": "acme",
"providers": {
"elevenlabs": {
"voiceId": "voice-normalized"
}
}
}
""".trimIndent(),
)
.jsonObject
val selection = TalkModeManager.selectTalkProviderConfig(talk)
assertEquals(null, selection)
}
@Test
fun rejectsNormalizedTalkProviderPayloadWhenProviderIsAmbiguous() {
val talk =
json.parseToJsonElement(
"""
{
"providers": {
"acme": {
"voiceId": "voice-acme"
},
"elevenlabs": {
"voiceId": "voice-normalized"
}
}
}
""".trimIndent(),
)
.jsonObject
val selection = TalkModeManager.selectTalkProviderConfig(talk)
assertEquals(null, selection)
}
@Test
fun fallsBackToLegacyTalkFieldsWhenNormalizedPayloadMissing() {
val legacyApiKey = "legacy-key" // pragma: allowlist secret

View File

@@ -31,10 +31,19 @@ public enum TalkConfigParsing {
let hasNormalizedPayload = rawProvider != nil || rawProviders != nil
if hasNormalizedPayload {
let normalizedProviders = self.normalizedTalkProviders(rawProviders)
let providerID =
self.normalizedTalkProviderID(rawProvider) ??
normalizedProviders.keys.min() ??
defaultProvider
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] ?? [:],

View File

@@ -66,6 +66,36 @@ struct TalkConfigParsingTests {
#expect(selection == nil)
}
@Test func rejectsNormalizedPayloadWhenProviderMissingFromProviders() {
let talk: [String: AnyCodable] = [
"provider": AnyCodable("acme"),
"providers": AnyCodable([
"elevenlabs": [
"voiceId": "voice-normalized",
],
]),
]
let selection = TalkConfigParsing.selectProviderConfig(talk, defaultProvider: "elevenlabs")
#expect(selection == nil)
}
@Test func rejectsNormalizedPayloadWhenMultipleProvidersAndNoProvider() {
let talk: [String: AnyCodable] = [
"providers": AnyCodable([
"acme": [
"voiceId": "voice-acme",
],
"elevenlabs": [
"voiceId": "voice-eleven",
],
]),
]
let selection = TalkConfigParsing.selectProviderConfig(talk, defaultProvider: "elevenlabs")
#expect(selection == nil)
}
@Test func bridgesFoundationDictionary() {
let raw: [String: Any] = [
"provider": "elevenlabs",