Skip to content

feat: surface Live API session close and error events to receive() callers - #1154

Open
subhashpolisetti wants to merge 1 commit into
googleapis:mainfrom
subhashpolisetti:gh-live-lifecycle-callbacks
Open

subhashpolisetti wants to merge 1 commit into
googleapis:mainfrom
subhashpolisetti:gh-live-lifecycle-callbacks

Conversation

@subhashpolisetti

Copy link
Copy Markdown

What

After a Live session is established, GenAiWebSocketClient only logs onClosed/onFailure — they never reach the receive() consumer or any future, so a caller can't tell when the server closes the session or the connection drops.

This adds a backward-compatible overload that surfaces those events:

public CompletableFuture<Void> receive(
    Consumer<LiveServerMessage> onMessage, Consumer<Throwable> onError, Runnable onClose)

onError/onClose are invoked from onFailure/onClosed after setup (terminal, mutually exclusive, nullable). The existing receive(Consumer) delegates to the overload, so its behavior is unchanged.

Why

Found building the Gemini Live API integration for langchain4j — the wrapper had no way to notify the app when a session ends. See #1153.

Tests

AsyncLiveTest covers the close and error paths.

Fixes #1153

@google-cla

google-cla Bot commented Aug 3, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

…llers

After setup, GenAiWebSocketClient only logs onClosed/onFailure — they never
reach the receive() message consumer or any future, so a caller cannot react
when the server closes a live session or the WebSocket drops. receive() itself
returns an already-completed future (registration only), so it can't carry this
either.

Add an overload receive(onMessage, onError, onClose) that also surfaces error
and close. The existing receive(Consumer) delegates to it, so its behavior is
unchanged. onError/onClose are terminal and mutually exclusive (failure ->
onError, normal close -> onClose) and may be null.

Fixes googleapis#1153
@subhashpolisetti
subhashpolisetti force-pushed the gh-live-lifecycle-callbacks branch from 97a355a to 3967e46 Compare August 3, 2026 07:58
@subhashpolisetti

Copy link
Copy Markdown
Author

@googlebot I signed it!

@hemasekhar-p hemasekhar-p self-assigned this Aug 4, 2026
@davetraction

Copy link
Copy Markdown

Thanks for this — we hit the same gap in production and would very much like it merged. One suggestion so it also covers server-initiated closes on the OkHttp client:

On OkHttp, onClosed only fires after the client completes the close handshake, and GenAiWebSocketClient.onClosing currently just logs and never calls webSocket.close(code, reason). So when the server closes the session, the socket sits half-closed: onClosing is logged, onClosed does not fire until the application itself calls session.close(), and every send* in between is silently dropped (the boolean from webSocket.send is discarded).

Concrete case we observed with 1.73.0 on Vertex: a FunctionResponse with scheduling set on gemini-3.8-live-extended-thinking-preview makes the server close with 1007 — FunctionResponse.scheduling is not supported for this model; the field must be left unset. The SDK logged Live session closing with code: 1,007 … immediately, but Live session closed … only appeared ~7 s later when our code tore the session down — the application believed it was connected the whole time.

With this PR as written, onClose would fire at that same late moment. Two small changes would make it fire when the server actually closes:

  1. In onClosing, either invoke the close callback there, or complete the handshake (webSocket.close(code, reason)) so onClosed follows right away.
  2. Pass code and reason to the callback (e.g. BiConsumer<Integer, String> instead of Runnable), so callers can tell a 1007 rejected argument from a normal 1000 — that is the difference between "retry with a fixed request" and "the session simply ended".

Until this lands we're working around it with a java.util.logging handler on the AsyncLive logger that parses the close record parameters, which is obviously fragile — hence the interest in this PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live API: no way to observe session close or connection errors after setup

3 participants