From 7ad2ebb515cede2f6d75700f0735a91c83c95e05 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 30 May 2026 08:33:06 -0400 Subject: [PATCH] fix(google): guard realtime browser session expiries --- extensions/google/realtime-voice-provider.test.ts | 15 +++++++++++++++ extensions/google/realtime-voice-provider.ts | 10 ++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/extensions/google/realtime-voice-provider.test.ts b/extensions/google/realtime-voice-provider.test.ts index 6e41721b73f..1406f6831ac 100644 --- a/extensions/google/realtime-voice-provider.test.ts +++ b/extensions/google/realtime-voice-provider.test.ts @@ -100,6 +100,7 @@ describe("buildGoogleRealtimeVoiceProvider", () => { afterEach(() => { vi.useRealTimers(); + vi.restoreAllMocks(); for (const key of ENV_KEYS) { const value = envSnapshot[key]; if (value === undefined) { @@ -452,6 +453,20 @@ describe("buildGoogleRealtimeVoiceProvider", () => { ]); }); + it("rejects browser session expiry outside Date range", async () => { + vi.spyOn(Date, "now").mockReturnValue(8_640_000_000_000_001); + const provider = buildGoogleRealtimeVoiceProvider(); + + await expect( + provider.createBrowserSession?.({ + providerConfig: { + apiKey: "gemini-key", + }, + }), + ).rejects.toThrow("Google realtime browser session expiry is outside the supported Date range"); + expect(createTokenMock).not.toHaveBeenCalled(); + }); + it("can opt out of Google Live session resumption and context compression", async () => { const provider = buildGoogleRealtimeVoiceProvider(); const bridge = provider.createBridge({ diff --git a/extensions/google/realtime-voice-provider.ts b/extensions/google/realtime-voice-provider.ts index 87ba2bdc8e0..bfa847ee8c2 100644 --- a/extensions/google/realtime-voice-provider.ts +++ b/extensions/google/realtime-voice-provider.ts @@ -16,6 +16,7 @@ import type { ThinkingConfig, TurnCoverage, } from "@google/genai"; +import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-onboard"; import type { RealtimeVoiceAudioFormat, @@ -857,6 +858,11 @@ async function createGoogleRealtimeBrowserSession( const voice = req.voice ?? config.voice ?? GOOGLE_REALTIME_DEFAULT_VOICE; const expiresAtMs = Date.now() + GOOGLE_REALTIME_BROWSER_SESSION_TTL_MS; const newSessionExpiresAtMs = Date.now() + GOOGLE_REALTIME_BROWSER_NEW_SESSION_TTL_MS; + const expireTime = timestampMsToIsoString(expiresAtMs); + const newSessionExpireTime = timestampMsToIsoString(newSessionExpiresAtMs); + if (!expireTime || !newSessionExpireTime) { + throw new Error("Google realtime browser session expiry is outside the supported Date range"); + } const ai = createGoogleGenAI({ apiKey, httpOptions: { @@ -866,8 +872,8 @@ async function createGoogleRealtimeBrowserSession( const token = await ai.authTokens.create({ config: { uses: 1, - expireTime: new Date(expiresAtMs).toISOString(), - newSessionExpireTime: new Date(newSessionExpiresAtMs).toISOString(), + expireTime, + newSessionExpireTime, liveConnectConstraints: { model, config: buildGoogleLiveConnectConfig({