diff --git a/ai-logic/firebase-ai/CHANGELOG.md b/ai-logic/firebase-ai/CHANGELOG.md index 45dd2d53c1a..54f72b37109 100644 --- a/ai-logic/firebase-ai/CHANGELOG.md +++ b/ai-logic/firebase-ai/CHANGELOG.md @@ -3,6 +3,8 @@ - [feature] Added support for [Maps Grounding](https://ai.google.dev/gemini-api/docs/maps-grounding) (#7950) - [fixed] Fixed an issue causing network timeouts to throw the incorrect exception type, instead of `RequestTimeoutException` (#7966) +- [fixed] Fixed an issue causing the SDK to throw an exception if an unknown message was received + from the LiveAPI model, instead of ignoring it (#7975) # 17.10.1 diff --git a/ai-logic/firebase-ai/api.txt b/ai-logic/firebase-ai/api.txt index 84b4d766ec5..861f8d97567 100644 --- a/ai-logic/firebase-ai/api.txt +++ b/ai-logic/firebase-ai/api.txt @@ -1301,6 +1301,9 @@ package com.google.firebase.ai.type { property public final java.util.List functionIds; } + @com.google.firebase.ai.type.PublicPreviewAPI public final class LiveServerUnknownMessage implements com.google.firebase.ai.type.LiveServerMessage { + } + @com.google.firebase.ai.type.PublicPreviewAPI public final class LiveSession { method public suspend Object? close(kotlin.coroutines.Continuation); method public boolean isAudioConversationActive(); diff --git a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveServerMessage.kt b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveServerMessage.kt index 1efc06f9a52..2a97bac05c1 100644 --- a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveServerMessage.kt +++ b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveServerMessage.kt @@ -16,6 +16,7 @@ package com.google.firebase.ai.type +import android.util.Log import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlinx.serialization.DeserializationStrategy @@ -136,6 +137,15 @@ public class LiveServerSetupComplete : LiveServerMessage { } } +@PublicPreviewAPI +public class LiveServerUnknownMessage private constructor() : LiveServerMessage { + @Serializable + internal data class InternalWrapper(@Transient val unused: Unit? = null) : + InternalLiveServerMessage { + override fun toPublic() = LiveServerUnknownMessage() + } +} + /** * Request for the client to execute the provided [functionCalls]. * @@ -233,10 +243,13 @@ internal object LiveServerMessageSerializer : "toolCallCancellation" in jsonObject -> LiveServerToolCallCancellation.InternalWrapper.serializer() "goAway" in jsonObject -> LiveServerGoAway.InternalWrapper.serializer() - else -> - throw SerializationException( - "Unknown LiveServerMessage response type. Keys found: ${jsonObject.keys}" + else -> { + Log.w( + "LiveServerMsgSerializer", + "Ignoring unknown LiveServerMessage response type. Keys found: ${jsonObject.keys}" ) + LiveServerUnknownMessage.InternalWrapper.serializer() + } } } } diff --git a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt index 579f046d42d..1c106aa8028 100644 --- a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt +++ b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt @@ -578,6 +578,7 @@ internal constructor( // Notify the application goAwayHandler?.invoke(it) } + is LiveServerUnknownMessage -> {} // Ignore. Logging happens at de-serialization time } } .launchIn(networkScope) diff --git a/ai-logic/firebase-ai/src/test/java/com/google/firebase/ai/type/LiveServerMessageTests.kt b/ai-logic/firebase-ai/src/test/java/com/google/firebase/ai/type/LiveServerMessageTests.kt index f6921046f94..c6ca0b6f813 100644 --- a/ai-logic/firebase-ai/src/test/java/com/google/firebase/ai/type/LiveServerMessageTests.kt +++ b/ai-logic/firebase-ai/src/test/java/com/google/firebase/ai/type/LiveServerMessageTests.kt @@ -17,7 +17,6 @@ package com.google.firebase.ai.type import com.google.firebase.ai.common.JSON -import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -109,10 +108,11 @@ internal class LiveServerMessageTests { } @Test - fun `LiveServerMessageSerializer throws on unknown message type`() { + fun `LiveServerMessageSerializer returns LiveServerUnknownMessage for unrecognized message`() { val json = """{"unknownType": {"data": "value"}}""" - shouldThrow { JSON.decodeFromString(json) } + val message = JSON.decodeFromString(json) + message.toPublic().shouldBeInstanceOf() } @Test