Skip to content

feat: add Gemini Live API (BidiGenerateContent) realtime support - #4988

Open
Shaik-Sirajuddin wants to merge 4 commits into
maximhq:devfrom
Shaik-Sirajuddin:feat/gemini-live-api
Open

feat: add Gemini Live API (BidiGenerateContent) realtime support#4988
Shaik-Sirajuddin wants to merge 4 commits into
maximhq:devfrom
Shaik-Sirajuddin:feat/gemini-live-api

Conversation

@Shaik-Sirajuddin

@Shaik-Sirajuddin Shaik-Sirajuddin commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Description

Adds schemas.RealtimeProvider support for Gemini Live (BidiGenerateContent), closing #3736.

Details

  • Native text→audio and audio→audio; text→text and audio→text via Gemini's outputAudioTranscription side-channel, since current Gemini Live models only support AUDIO as a response modality (TEXT is rejected at setup).
  • Fixes a shared-transport gap affecting all realtime providers: session.update omitting model now falls back to the connection's resolved model — needed because Gemini's wire protocol only learns the model from the session payload, unlike OpenAI/Azure which also carry it on the connect URL.
  • WebRTC and ephemeral client-secret minting are explicitly out of scope (no public WebRTC spec / differently-shaped ephemeral-token API for Gemini) — documented as unsupported rather than silently missing.
  • No transport routing changes needed — the existing generic /v1/realtime websocket endpoint already routes to any RealtimeProvider implementation.

Checklist

  • Tests added/updated and passing (go test ./...)
  • Verified against a real endpoint (not just mocked/unit tests)
  • Docs updated

Implements schemas.RealtimeProvider for Gemini, closing maximhq#3736. Current Gemini
Live models are audio-output-only (TEXT responseModalities rejected at setup),
so T->T/S->T are achieved via the outputAudioTranscription side-channel
alongside native T->S/S->S audio support. Also fixes a shared-transport gap
where session.update omitting a model would fail providers whose wire
protocol only learns the model from the session payload.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added Gemini Live (realtime) support including audio streaming, transcripts, turn completion, and tool-call handling.
    • Enabled Gemini realtime scenario coverage with the live-preview model.
  • Bug Fixes
    • Improved realtime relaying to gracefully handle providers that omit/drop turn-starting events.
    • Ensured missing session model values are backfilled during realtime setup.
    • Redacted sensitive websocket URL details from dial error messages.
  • Documentation
    • Updated OpenAPI specs and Gemini provider documentation with Gemini Live support and limitations.
  • Tests
    • Added realtime event translation and URL-redaction regression tests.

Walkthrough

Adds Gemini Live realtime provider support, relay integration, tests, and docs, and updates upstream WebSocket handling to redact dial URLs in error paths.

Changes

Gemini realtime support

Layer / File(s) Summary
Provider protocol
core/providers/gemini/realtime.go
Declares Gemini realtime support, WebSocket endpoint handling, WebRTC rejection, turn rules, and realtime wire structs.
Event translation
core/providers/gemini/realtime.go
Maps Gemini Live frames to canonical realtime events and serializes canonical events back to Gemini messages, including tool, usage, and output extraction.
Relay wiring
transports/bifrost-http/handlers/realtime_turn_pipeline.go, transports/bifrost-http/handlers/webrtc_realtime.go, transports/bifrost-http/handlers/wsrealtime.go, core/internal/llmtests/realtime.go, core/providers/gemini/gemini_test.go, core/providers/gemini/realtime_test.go, docs/openapi/paths/inference/realtime.yaml, docs/providers/supported-providers/gemini.mdx
Backfills missing session model, handles empty provider payloads, and adds Gemini realtime coverage in tests and docs.

WebSocket URL redaction

Layer / File(s) Summary
Dial error redaction
transports/bifrost-http/websocket/connection.go, transports/bifrost-http/websocket/connection_redact_test.go, transports/bifrost-http/websocket/pool.go
Adds URL redaction for upstream WebSocket dial errors and sanitizes pool dialing targets.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant BifrostRelay
  participant GeminiProvider
  participant GeminiLive

  Client->>BifrostRelay: session.update / input_audio_buffer.append
  BifrostRelay->>BifrostRelay: sanitize with connection model
  BifrostRelay->>GeminiProvider: ToProviderRealtimeEvent(event)
  GeminiProvider->>GeminiLive: setup / clientContent / realtimeInput
  GeminiLive-->>GeminiProvider: serverContent / usageMetadata
  GeminiProvider->>BifrostRelay: ToBifrostRealtimeEvent(frame)
  BifrostRelay-->>Client: forward canonical realtime event
Loading

Possibly related PRs

  • maximhq/bifrost#3335: Both PRs change the realtime WebSocket/WebRTC turn relay pipeline, which the Gemini realtime work builds on.

Suggested reviewers: danpiths, Pratham-Mishra04, roroghost17, akshaydeo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the goal and a few details, but it misses most required template sections like Type of change, Affected areas, and Breaking changes. Reformat the PR description to include all template headings, especially Summary, Changes, Type of change, Affected areas, How to test, and Security considerations.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: Gemini Live realtime support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

Safe to merge after fixing the gateway documentation example, which shows Gemini raw wire format rather than Bifrost canonical translated events.

The provider implementation, transport changes, and security mitigations are well-tested and previous review findings were all addressed. One concrete defect is present in the new docs section: the Gateway code example checks msg.setupComplete (Gemini raw wire key) but clients connecting through Bifrost receive canonical { type: 'session.created' } events — users copying this example would get a non-functional integration that silently hangs.

docs/providers/supported-providers/gemini.mdx — the Gateway onmessage example needs the msg.setupComplete check replaced with msg.type === 'session.created'.

Important Files Changed

Filename Overview
core/providers/gemini/realtime.go New Gemini Live (BidiGenerateContent) provider implementation: wire-format structs, ToBifrostRealtimeEvent, ToProviderRealtimeEvent, usage/turn extraction. Correctly handles multi-part audio concatenation, bundled turnComplete+content frames, parallel tool calls, tool response wrapping, and model resource-name prefix.
core/providers/gemini/realtime_test.go Comprehensive unit tests covering audio delta, multi-part concatenation, bundled transcript+audio, input/output transcription, turnComplete+content, tool calls, session update with tool conversion, and nil-return for unmapped events.
transports/bifrost-http/websocket/pool.go Added DialURL to PoolKey for credential-bearing dial URLs, and dial() updated to use it. Return() reconstructs the key from conn fields but omits DialURL, creating a latent bucket mismatch if Gemini connections are ever returned rather than discarded.
transports/bifrost-http/websocket/connection.go Added SanitizeEndpointForPoolKey (strips credential-shaped query params) and redactURLForLog (strips all query params and userinfo before error messages). Both functions are well-tested in connection_redact_test.go.
transports/bifrost-http/handlers/wsrealtime.go Pool endpoint sanitization added (Gemini API key stripped from pool key), model fallback wired to sanitizeRealtimeSessionEventForProvider, and defensive nil-providerEvent guard added with correct turn-hook finalization.
transports/bifrost-http/handlers/realtime_turn_pipeline.go Added connectionModel parameter to sanitizeRealtimeSessionEventForProvider; fills in session.model from the connection URL when the client omits it (needed for Gemini's protocol which only learns the model from the setup payload).
docs/providers/supported-providers/gemini.mdx New Realtime section with modality table, limitations, and wire-translation table. The Gateway code example checks msg.setupComplete (raw Gemini wire key) but Bifrost translates this to a canonical session.created event before forwarding — the example would silently fail when used through the gateway.
core/internal/llmtests/realtime.go Extends RunRealtimeTest to dispatch to runGeminiRealtimeTest. Test harness correctly speaks Gemini's raw wire protocol (bypassing Bifrost's translation layer) and asserts on both audio and turnComplete events.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant C as Client (WS)
    participant B as Bifrost WS Relay
    participant P as Pool
    participant G as Gemini Live

    C->>B: "WS connect ?model=gemini/gemini-3.1-flash-live-preview"
    B->>B: SanitizeEndpointForPoolKey strips ?key from pool key
    B->>P: Get with sanitized Endpoint + DialURL with credential
    P->>G: "Dial using DialURL with ?key=SECRET"
    G-->>P: WS connection established
    P-->>B: UpstreamConn stores sanitized endpoint only
    C->>B: session.update (model omitted)
    B->>B: sanitizeRealtimeSessionEventForProvider fills model from connectionModel
    B->>G: setup with model + AUDIO modality + transcription toggles
    G-->>B: setupComplete
    B->>B: ToBifrostRealtimeEvent maps to session.created
    B-->>C: session.created canonical event
    C->>B: conversation.item.create
    B->>G: "clientContent turns turnComplete=false"
    C->>B: response.create
    B->>B: "ShouldStartRealtimeTurn=true, start turn hooks"
    B->>G: "clientContent turnComplete=true"
    G-->>B: serverContent modelTurn + outputTranscription bundled
    B->>B: ToBifrostRealtimeEvent produces response.audio.delta with transcript
    B-->>C: response.audio.delta
    G-->>B: "serverContent turnComplete=true + usageMetadata"
    B->>B: finalize turn hooks extract usage
    B-->>C: response.done
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant C as Client (WS)
    participant B as Bifrost WS Relay
    participant P as Pool
    participant G as Gemini Live

    C->>B: "WS connect ?model=gemini/gemini-3.1-flash-live-preview"
    B->>B: SanitizeEndpointForPoolKey strips ?key from pool key
    B->>P: Get with sanitized Endpoint + DialURL with credential
    P->>G: "Dial using DialURL with ?key=SECRET"
    G-->>P: WS connection established
    P-->>B: UpstreamConn stores sanitized endpoint only
    C->>B: session.update (model omitted)
    B->>B: sanitizeRealtimeSessionEventForProvider fills model from connectionModel
    B->>G: setup with model + AUDIO modality + transcription toggles
    G-->>B: setupComplete
    B->>B: ToBifrostRealtimeEvent maps to session.created
    B-->>C: session.created canonical event
    C->>B: conversation.item.create
    B->>G: "clientContent turns turnComplete=false"
    C->>B: response.create
    B->>B: "ShouldStartRealtimeTurn=true, start turn hooks"
    B->>G: "clientContent turnComplete=true"
    G-->>B: serverContent modelTurn + outputTranscription bundled
    B->>B: ToBifrostRealtimeEvent produces response.audio.delta with transcript
    B-->>C: response.audio.delta
    G-->>B: "serverContent turnComplete=true + usageMetadata"
    B->>B: finalize turn hooks extract usage
    B-->>C: response.done
Loading

Reviews (4): Last reviewed commit: "fix: keep Gemini's API key out of the re..." | Re-trigger Greptile

Comment on lines +24 to +29
func (provider *GeminiProvider) RealtimeWebSocketURL(key schemas.Key, model string) string {
base := provider.networkConfig.BaseURL
base = strings.Replace(base, "https://", "wss://", 1)
base = strings.Replace(base, "http://", "ws://", 1)
base = strings.TrimSuffix(base, "/v1beta")
return base + "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent?key=" + url.QueryEscape(key.Value.GetValue())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security API key embedded in WebSocket URL stored as pool endpoint key

The Gemini API key flows directly into PoolKey.Endpoint as part of the wss://...?key=<API_KEY> URL. Every other realtime provider (OpenAI, ElevenLabs, Azure) sends credentials via request headers, keeping secrets out of the pool key and connection identifiers. With Gemini's URL-based auth the key lives unmasked as a Go map key inside the pool and will appear in plaintext in access logs at any reverse-proxy or load balancer that logs request URLs including query parameters. The PR acknowledges this is required by Gemini's protocol, but no mitigations are applied — e.g. the pool key could use key.ID + a constant Gemini sentinel as the Endpoint value (stripping the query param), while keeping the full URL only in the actual dial call.

Comment thread core/providers/gemini/realtime.go
Comment thread transports/bifrost-http/handlers/wsrealtime.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
transports/bifrost-http/handlers/webrtc_realtime.go (1)

846-868: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract the duplicated finalize-turn-and-close sequence.

The new len(providerEvent) == 0 guard (872-896) duplicates the exact finalizeRealtimeTurnHooksOnTransportErrorcloseWithErrorEvent sequence already present in the err != nil branch above (846-868), differing only in the error message string. Extracting a small helper avoids having two places to keep in sync if the finalize/close logic changes.

♻️ Proposed refactor
+func (r *webrtcRealtimeRelay) finalizeTurnAndClose(status int, code, msg string) {
+	if finalizeErr := finalizeRealtimeTurnHooksOnTransportError(
+		r.client,
+		r.bifrostCtx,
+		r.session,
+		r.providerKey,
+		r.model,
+		r.key,
+		status,
+		code,
+		msg,
+	); finalizeErr != nil {
+		r.closeWithErrorEvent(newRealtimeTurnErrorEventPayload(finalizeErr))
+		return
+	}
+	r.closeWithErrorEvent(newRealtimeTurnErrorEventPayload(newRealtimeWireBifrostError(status, code, msg)))
+}
+
 func (r *webrtcRealtimeRelay) handleDownstreamMessage(msg webrtc.DataChannelMessage) {
   ...
   if err != nil {
     if startsTurn {
-      if finalizeErr := finalizeRealtimeTurnHooksOnTransportError(
-        r.client, r.bifrostCtx, r.session, r.providerKey, r.model, r.key,
-        400, "invalid_request_error", err.Error(),
-      ); finalizeErr != nil {
-        r.closeWithErrorEvent(newRealtimeTurnErrorEventPayload(finalizeErr))
-        return
-      }
-      r.closeWithErrorEvent(newRealtimeTurnErrorEventPayload(newRealtimeWireBifrostError(400, "invalid_request_error", err.Error())))
-      return
+      r.finalizeTurnAndClose(400, "invalid_request_error", err.Error())
+      return
     }
     ...
   }
   ...
   if len(providerEvent) == 0 {
     if startsTurn {
-      if finalizeErr := finalizeRealtimeTurnHooksOnTransportError(
-        r.client, r.bifrostCtx, r.session, r.providerKey, r.model, r.key,
-        400, "invalid_request_error", "provider dropped a turn-starting event",
-      ); finalizeErr != nil {
-        r.closeWithErrorEvent(newRealtimeTurnErrorEventPayload(finalizeErr))
-        return
-      }
-      r.closeWithErrorEvent(newRealtimeTurnErrorEventPayload(newRealtimeWireBifrostError(400, "invalid_request_error", "provider dropped a turn-starting event")))
+      r.finalizeTurnAndClose(400, "invalid_request_error", "provider dropped a turn-starting event")
     }
     return
   }

Also applies to: 872-896

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@transports/bifrost-http/handlers/webrtc_realtime.go` around lines 846 - 868,
The error-handling path in the WebRTC realtime handler duplicates the same
finalize-and-close flow in both the existing err != nil branch and the new
len(providerEvent) == 0 guard. Extract that repeated
finalizeRealtimeTurnHooksOnTransportError → closeWithErrorEvent sequence into a
small helper in webrtc_realtime.go, and have both branches call it with the
appropriate error message/context so the logic stays in sync.
transports/bifrost-http/handlers/wsrealtime.go (1)

379-398: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gemini realtime needs a setup-first relay
This path only translates an existing session.update into Gemini setup; it never injects one. If the client starts with conversation.item.create or input_audio_buffer.append, the first upstream message won’t be setup, and Gemini can reject the connection. Add an initial Gemini session.update/setup before forwarding any other event, or fail fast when the client doesn’t send one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@transports/bifrost-http/handlers/wsrealtime.go` around lines 379 - 398, The
realtime relay in wsrealtime.go currently only maps an incoming session.update
to Gemini setup via provider.ToProviderRealtimeEvent and does not ensure a setup
message is sent first. Update the realtime startup flow around
startsTurn/startRealtimeTurnHooks so Gemini receives an initial
session.update/setup before any conversation.item.create or
input_audio_buffer.append events are forwarded, or otherwise reject the
connection early if no setup has been provided. Use the existing session,
provider, and provider.ToProviderRealtimeEvent path to locate where to inject or
validate the first upstream event.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/providers/gemini/realtime.go`:
- Around line 357-369: The RTEventSessionUpdate handling in gemini realtime
forwarding is passing Session.Tools directly into geminiSetup, but Gemini
expects its own tool format. Update the session update path in the realtime
event switch to translate bifrostEvent.Session.Tools through the Gemini tool
mapper before assigning setup.Tools, or clearly change the RealtimeSession.Tools
contract to be Gemini-native; use the geminiSetup and toGeminiModelResourceName
area as the anchor for the fix.

---

Outside diff comments:
In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 846-868: The error-handling path in the WebRTC realtime handler
duplicates the same finalize-and-close flow in both the existing err != nil
branch and the new len(providerEvent) == 0 guard. Extract that repeated
finalizeRealtimeTurnHooksOnTransportError → closeWithErrorEvent sequence into a
small helper in webrtc_realtime.go, and have both branches call it with the
appropriate error message/context so the logic stays in sync.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Around line 379-398: The realtime relay in wsrealtime.go currently only maps
an incoming session.update to Gemini setup via provider.ToProviderRealtimeEvent
and does not ensure a setup message is sent first. Update the realtime startup
flow around startsTurn/startRealtimeTurnHooks so Gemini receives an initial
session.update/setup before any conversation.item.create or
input_audio_buffer.append events are forwarded, or otherwise reject the
connection early if no setup has been provided. Use the existing session,
provider, and provider.ToProviderRealtimeEvent path to locate where to inject or
validate the first upstream event.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 83573b1a-b72b-49b5-8759-0f0a8ddb7a78

📥 Commits

Reviewing files that changed from the base of the PR and between d9577ba and fc86e6e.

📒 Files selected for processing (9)
  • core/internal/llmtests/realtime.go
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/realtime.go
  • core/providers/gemini/realtime_test.go
  • docs/openapi/paths/inference/realtime.yaml
  • docs/providers/supported-providers/gemini.mdx
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/wsrealtime.go

Comment thread core/providers/gemini/realtime.go
- Redact query-string secrets (Gemini's API key) from dial-error messages in
  the websocket pool/connection layer, since Gemini authenticates via a URL
  query param instead of headers like every other realtime provider.
- Concatenate multiple audio inlineData parts in a single serverContent
  message instead of keeping only the first.
- Document the intentional WS-vs-WebRTC error-handling asymmetry for dropped
  turn-starting events (pre-existing convention, not something to unify here).
@coderabbitai
coderabbitai Bot requested a review from danpiths July 7, 2026 09:38
- Convert session.update tools through the existing Gemini tool mapper
  (convertBifrostToolsToGemini) instead of forwarding the canonical
  OpenAI-shaped tool array verbatim, which Gemini's wire protocol rejects.
- Extract the duplicated finalize-turn-and-close sequence in
  webrtc_realtime.go into a shared helper.
- Document the setup-must-be-first-frame limitation as a deliberately
  deferred gap (would require a RealtimeProvider interface change shared by
  all providers) rather than leaving it unexplained.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 7, 2026
@Shaik-Sirajuddin

Copy link
Copy Markdown
Contributor Author

Gemini Studio api keys were used to conduct end to end tests , for additive changes

Follow-up to the Greptile security finding — the earlier fix only redacted
dial-error messages, but the raw key still lived in PoolKey.Endpoint itself:
the Go map key held in memory for the pool's lifetime and stored on
UpstreamConn for diagnostics.

Adds PoolKey.DialURL: an optional field carrying the real (possibly
credential-bearing) dial target, decoupled from Endpoint, which now stays a
sanitized identity value via the new SanitizeEndpointForPoolKey helper. Only
strips credential-shaped query param names (key/token/secret/auth), so
OpenAI's ?model= and Azure's ?deployment= params — needed for correct
per-model pool bucketing — are left untouched. Confirmed via the realtime
path's existing Discard-only usage (never Return) that this can't cause a
pool-key mismatch for reuse, and all existing pool/websocket/handler tests
pass unmodified.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
transports/bifrost-http/websocket/connection_redact_test.go (1)

57-105: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Missing malformed-URL test case for SanitizeEndpointForPoolKey.

TestRedactURLForLog covers the invalid-URL fallback, but TestSanitizeEndpointForPoolKey's table has no equivalent case for a malformed rawURL. Given the fail-open behavior flagged in connection.go (returns raw input unchanged on parse error), a test case here would have caught that gap and should be added once/if the fail-open path is fixed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@transports/bifrost-http/websocket/connection_redact_test.go` around lines 57
- 105, Add a malformed-URL table case to TestSanitizeEndpointForPoolKey so the
fallback behavior is covered alongside the existing secret-preservation cases.
Use SanitizeEndpointForPoolKey with an invalid rawURL input and assert the
current fail-open behavior (or the intended fixed behavior if connection.go
changes) is explicitly tested, matching the pattern used by TestRedactURLForLog.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@transports/bifrost-http/websocket/connection_redact_test.go`:
- Around line 57-105: Add a malformed-URL table case to
TestSanitizeEndpointForPoolKey so the fallback behavior is covered alongside the
existing secret-preservation cases. Use SanitizeEndpointForPoolKey with an
invalid rawURL input and assert the current fail-open behavior (or the intended
fixed behavior if connection.go changes) is explicitly tested, matching the
pattern used by TestRedactURLForLog.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f2e12ec-aeca-4b62-94ad-ce75b8be6074

📥 Commits

Reviewing files that changed from the base of the PR and between c26d0b9 and 24d00fb.

📒 Files selected for processing (4)
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/websocket/connection.go
  • transports/bifrost-http/websocket/connection_redact_test.go
  • transports/bifrost-http/websocket/pool.go

@akshaydeo
akshaydeo force-pushed the dev branch 2 times, most recently from bb4c251 to 5a52603 Compare July 14, 2026 17:22
@Shaik-Sirajuddin

Copy link
Copy Markdown
Contributor Author

End-to-end verification (official openai Python SDK, live Gemini API)

Drove Bifrost's /v1/realtime?model=gemini/<model> through the real openai pip package's
client.realtime.connect(...) (pointed at a local Bifrost instance via base_url/websocket_base_url, not a
hand-rolled script) — one turn: session.updateconversation.item.create (text) → response.create.

Before (e0c541da8, parent of this feature):

recv {"type": "error", "error": {"message": "provider does not support realtime: gemini", "type": "invalid_request_error"}}

WS upgrade succeeds (HTTP 101) but every client send then fails locally with ConnectionClosedError — server pushes
one error frame and drops the socket.

After (this branch):

recv setupComplete: {}
recv serverContent.modelTurn: inlineData audio/pcm;rate=24000 (+ outputTranscription: "Hello there, friend.")
recv serverContent.generationComplete: true
recv serverContent.turnComplete: true, usageMetadata: {promptTokenCount: 147, responseTokenCount: 36, totalTokenCount: 183}
recv sessionResumptionUpdate: {resumable: true}

Full turn completes with audio deltas, transcript, and usage — Gemini's native wire JSON passed through verbatim
(RawData), which the generic OpenAI-SDK event loop still surfaces correctly.

Raw JSONL (both runs)

before-fix-oai-sdk-raw.jsonl

{"dir": "meta", "raw": {"event": "connected"}}
{"dir": "send_error", "raw": {"label": "session.update", "exception": "ConnectionClosedError(None, None, None)"}}
{"dir": "recv", "raw": {"error": {"message": "provider does not support realtime: gemini", "type": "invalid_request_error"}, "type": "error"}}
{"dir": "meta", "raw": {"event": "loop_exited", "got_terminal": true}}

after-fix-oai-sdk-raw.jsonl

{"dir": "recv", "raw": {"setupComplete": {}}}
{"dir": "recv", "raw": {"serverContent": {"modelTurn": {"parts": [{"inlineData": {"mimeType": "audio/pcm;rate=24000", "data": "..."}}]}}}}
{"dir": "recv", "raw": {"serverContent": {"modelTurn": {"parts": [{"inlineData": {"mimeType": "audio/pcm;rate=24000", "data": "..."}}]}}, "outputTranscription": {"text": "Hello there, friend."}}}
{"dir": "recv", "raw": {"serverContent": {"generationComplete": true}}}
{"dir": "recv", "raw": {"serverContent": {"turnComplete": true}, "usageMetadata": {"promptTokenCount": 147, "responseTokenCount": 36, "totalTokenCount": 183}}}
{"dir": "recv", "raw": {"sessionResumptionUpdate": {"newHandle": "...", "resumable": true}}}

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.

1 participant