From cb61d5c271b5462e832167c36e1de8e8554e97e4 Mon Sep 17 00:00:00 2001 From: VinayGuthal Date: Wed, 24 Sep 2025 16:51:51 -0400 Subject: [PATCH 1/2] add close reason --- firebase-ai/CHANGELOG.md | 1 + .../google/firebase/ai/LiveGenerativeModel.kt | 33 +++++++++++-------- .../firebase/ai/common/APIController.kt | 4 +-- .../google/firebase/ai/type/LiveSession.kt | 4 +-- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md index 0d4a2333f7d..4432555a470 100644 --- a/firebase-ai/CHANGELOG.md +++ b/firebase-ai/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - [changed] **Breaking Change**: Removed the `candidateCount` option from `LiveGenerationConfig` +- [changed] Added better error messages to `ServiceConnectionHandshakeFailedException` # 17.3.0 diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt index a696ddd5f73..edcb7c2e036 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt @@ -116,22 +116,29 @@ internal constructor( val data: String = Json.encodeToString(clientMessage) try { val webSession = controller.getWebSocketSession(location) - webSession.send(Frame.Text(data)) - val receivedJsonStr = webSession.incoming.receive().readBytes().toString(Charsets.UTF_8) - val receivedJson = JSON.parseToJsonElement(receivedJsonStr) + try { + webSession.send(Frame.Text(data)) + val receivedJsonStr = webSession.incoming.receive().readBytes().toString(Charsets.UTF_8) + val receivedJson = JSON.parseToJsonElement(receivedJsonStr) - return if (receivedJson is JsonObject && "setupComplete" in receivedJson) { - LiveSession( - session = webSession, - blockingDispatcher = blockingDispatcher, - firebaseApp = firebaseApp - ) - } else { - webSession.close() - throw ServiceConnectionHandshakeFailedException("Unable to connect to the server") + return if (receivedJson is JsonObject && "setupComplete" in receivedJson) { + LiveSession( + session = webSession, + blockingDispatcher = blockingDispatcher, + firebaseApp = firebaseApp + ) + } else { + webSession.close() + throw ServiceConnectionHandshakeFailedException("Unable to connect to the server") + } + } catch (e: ClosedReceiveChannelException) { + val reason = webSession.closeReason.await() + val message = + "Channel was closed by the server.${if(reason!=null) " Details: ${reason.message}" else "" }" + throw ServiceConnectionHandshakeFailedException(message, e) } } catch (e: ClosedReceiveChannelException) { - throw ServiceConnectionHandshakeFailedException("Channel was closed by the server", e) + throw ServiceConnectionHandshakeFailedException("Channel was closed by the server.", e) } } diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/APIController.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/APIController.kt index 720b2c50a63..b199698aa7b 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/APIController.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/APIController.kt @@ -38,7 +38,7 @@ import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.plugins.websocket.ClientWebSocketSession +import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession import io.ktor.client.plugins.websocket.WebSockets import io.ktor.client.plugins.websocket.webSocketSession import io.ktor.client.request.HttpRequestBuilder @@ -174,7 +174,7 @@ internal constructor( "wss://firebasevertexai.googleapis.com/ws/google.firebase.vertexai.v1beta.GenerativeService/BidiGenerateContent?key=$key" } - suspend fun getWebSocketSession(location: String): ClientWebSocketSession = + suspend fun getWebSocketSession(location: String): DefaultClientWebSocketSession = client.webSocketSession(getBidiEndpoint(location)) { applyCommonHeaders() } fun generateContentStream( diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt index a91d7e4aedf..ccdc3e7fe95 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt @@ -29,7 +29,7 @@ import com.google.firebase.ai.common.util.CancelledCoroutineScope import com.google.firebase.ai.common.util.accumulateUntil import com.google.firebase.ai.common.util.childJob import com.google.firebase.annotations.concurrent.Blocking -import io.ktor.client.plugins.websocket.ClientWebSocketSession +import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession import io.ktor.websocket.Frame import io.ktor.websocket.close import io.ktor.websocket.readBytes @@ -59,7 +59,7 @@ import kotlinx.serialization.json.Json @OptIn(ExperimentalSerializationApi::class) public class LiveSession internal constructor( - private val session: ClientWebSocketSession, + private val session: DefaultClientWebSocketSession, @Blocking private val blockingDispatcher: CoroutineContext, private var audioHelper: AudioHelper? = null, private val firebaseApp: FirebaseApp, From 12259fd05268443e9127d4ba7970dbf0b93b9ed7 Mon Sep 17 00:00:00 2001 From: VinayGuthal Date: Thu, 25 Sep 2025 13:27:38 -0400 Subject: [PATCH 2/2] update live generative model --- .../google/firebase/ai/LiveGenerativeModel.kt | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt index edcb7c2e036..d5afca6b960 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt @@ -32,6 +32,7 @@ import com.google.firebase.ai.type.Tool import com.google.firebase.annotations.concurrent.Blocking import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider import com.google.firebase.auth.internal.InternalAuthProvider +import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession import io.ktor.websocket.Frame import io.ktor.websocket.close import io.ktor.websocket.readBytes @@ -114,31 +115,28 @@ internal constructor( ) .toInternal() val data: String = Json.encodeToString(clientMessage) + var webSession: DefaultClientWebSocketSession? = null try { - val webSession = controller.getWebSocketSession(location) - try { - webSession.send(Frame.Text(data)) - val receivedJsonStr = webSession.incoming.receive().readBytes().toString(Charsets.UTF_8) - val receivedJson = JSON.parseToJsonElement(receivedJsonStr) + webSession = controller.getWebSocketSession(location) + webSession.send(Frame.Text(data)) + val receivedJsonStr = webSession.incoming.receive().readBytes().toString(Charsets.UTF_8) + val receivedJson = JSON.parseToJsonElement(receivedJsonStr) - return if (receivedJson is JsonObject && "setupComplete" in receivedJson) { - LiveSession( - session = webSession, - blockingDispatcher = blockingDispatcher, - firebaseApp = firebaseApp - ) - } else { - webSession.close() - throw ServiceConnectionHandshakeFailedException("Unable to connect to the server") - } - } catch (e: ClosedReceiveChannelException) { - val reason = webSession.closeReason.await() - val message = - "Channel was closed by the server.${if(reason!=null) " Details: ${reason.message}" else "" }" - throw ServiceConnectionHandshakeFailedException(message, e) + return if (receivedJson is JsonObject && "setupComplete" in receivedJson) { + LiveSession( + session = webSession, + blockingDispatcher = blockingDispatcher, + firebaseApp = firebaseApp + ) + } else { + webSession.close() + throw ServiceConnectionHandshakeFailedException("Unable to connect to the server") } } catch (e: ClosedReceiveChannelException) { - throw ServiceConnectionHandshakeFailedException("Channel was closed by the server.", e) + val reason = webSession?.closeReason?.await() + val message = + "Channel was closed by the server.${if(reason!=null) " Details: ${reason.message}" else "" }" + throw ServiceConnectionHandshakeFailedException(message, e) } }