Skip to content

feat: add realtime WebSocket, WebRTC, and client secret handlers#2341

Merged
akshaydeo merged 5 commits intov1.5.0from
feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers
Apr 7, 2026
Merged

feat: add realtime WebSocket, WebRTC, and client secret handlers#2341
akshaydeo merged 5 commits intov1.5.0from
feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers

Conversation

@danpiths
Copy link
Copy Markdown
Collaborator

@danpiths danpiths commented Mar 27, 2026

Summary

Adds the transport-layer implementation for Bifrost's Realtime API: WebSocket
bidirectional relay, WebRTC dual-endpoint relay with provider-owned SDP exchange,
HTTP client secret creation, turn-scoped logging and hook pipeline, route
registration, CORS updates, governance content-type guards, and graceful
shutdown. This is the main handler PR that wires together all infrastructure from
prior PRs.

Changes

  • wsrealtime.go: WebSocket relay with per-request upgrader (provider-driven
    subprotocol), bidirectional message forwarding, turn start/final detection,
    output accumulation, input/tool tracking, heartbeat ping/pong, bounded write
    deadline (30s)
  • webrtc_realtime.go: Major refactor — removed httpClient from handler,
    provider now owns SDP exchange via ExchangeRealtimeWebRTCSDP. Dual-endpoint
    support: GA /v1/realtime/calls routes and legacy /v1/realtime routes
    (OpenAI beta compat via RealtimeLegacyWebRTCProvider). Auto-detects
    multipart vs raw SDP. Extracts model from session JSON or query param.
    Shared establishRelay with closure-based SDP exchange
  • realtime_logging.go: Turn logger with content extraction, usage parsing
    via RealtimeUsageExtractor, cost calculation, metadata propagation, summary
    truncation (1024 chars / 16KB raw), TotalTokens fallback from
    input+output when missing
  • realtime_turn_pipeline.go: Turn context creation, pre/post hook execution,
    request/response building from accumulated session state, plugin state
    propagation between hooks, trace completion via CompleteAndFlushTrace
  • realtime_client_secrets.go: HTTP POST handler for
    /v1/realtime/client_secrets and /v1/realtime/sessions with provider
    resolution and passthrough response
  • logging.go: New /api/logs/sessions/{session_id} and
    /api/logs/sessions/{session_id}/summary endpoints
  • middlewares.go: CORS X-OpenAI-Agents-SDK header, expanded
    isInferenceWSEndpoint for /v1/realtime paths, isJSONContentType helper
  • devpprof.go: Updated goroutine categorizer for renamed
    CompleteAndFlushTrace
  • integrations/openai.go: OpenAIRealtimeClientSecretPaths and
    OpenAIRealtimeWebRTCCallsPaths for route registration
  • governance plugin: Content-type guard to skip non-JSON/non-multipart
    bodies (e.g. application/sdp for WebRTC signaling), exported
    EvaluateGovernanceRequest for use by realtime turn pipeline
  • server.go: Wires all new handlers, adds graceful shutdown hook

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

cd transports
go build ./...
go test ./bifrost-http/handlers/... -run TestRealtime
go test ./bifrost-http/handlers/... -run TestResolveRealtime
go test ./bifrost-http/handlers/... -run TestIsInferenceWSEndpoint
go test ./bifrost-http/handlers/... -run TestIsJSONContentType
go test ./bifrost-http/handlers/... -run TestCreateBifrostContext

cd ../plugins/governance
go build ./...

Screenshots/Recordings

N/A

Breaking changes

  • Yes
  • No

Related issues

N/A

Security considerations

  • WebSocket connections are authenticated via existing middleware before upgrade
  • WebRTC SDP exchange delegates to provider with proper auth — handler no longer
    makes direct HTTP calls to upstream
  • Client secret requests validate Content-Type and provider support before
    forwarding
  • Governance content-type guard ensures non-parseable bodies (SDP) skip
    governance evaluation rather than causing parse errors
  • Relay contexts are isolated from request cancellation to prevent premature
    teardown

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 27, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Realtime support: WebSocket realtime API, WebRTC realtime calls, and realtime client-secrets/session endpoints plus full realtime “turn” pipeline and turn hooks.
  • Improvements

    • Enhanced realtime logging and usage extraction; session logging endpoints with pagination and redacted/enriched metadata.
    • CORS now allows X-OpenAI-Agents-SDK; tracing/observability initialization streamlined; selected realtime routes may bypass standard auth.
  • Bug Fixes

    • Preserve full parsed payloads for realtime.turn log updates.
  • Tests

    • Extensive unit tests for realtime, WebRTC, client-secrets, logging, and middleware.

Walkthrough

Adds end-to-end realtime transports (WebSocket and WebRTC), a realtime client-secrets HTTP endpoint, a realtime turn pipeline and logging, middleware path/auth updates, integration wiring with Close semantics, server shutdown Close calls, Pion WebRTC deps, tracing middleware API change, and governance request evaluation export.

Changes

Cohort / File(s) Summary
Realtime WebSocket
transports/bifrost-http/handlers/wsrealtime.go, transports/bifrost-http/handlers/wsrealtime_test.go
New WSRealtimeHandler: /v1/realtime and OpenAI realtime paths, auth/ephemeral-token extraction from Sec-WebSocket-Protocol, provider/model resolution, session manager, client↔provider relay loops, heartbeat/timeouts, turn lifecycle, error forwarding, and tests for parsing, context copying, KV mapping, and relay shutdown.
Realtime WebRTC
transports/bifrost-http/handlers/webrtc_realtime.go, transports/bifrost-http/handlers/webrtc_realtime_test.go
New WebRTCRealtimeHandler: GA and legacy /v1/realtime/calls endpoints, multipart/raw-SDP parsing, model/provider normalization, key resolution (including ephemeral mapping), SDP exchange and upstream offer/answer, bidirectional RTP + data-channel relay, buffering/limits, lifecycle/cleanup, and unit tests.
Realtime Client Secrets
transports/bifrost-http/handlers/realtime_client_secrets.go, transports/bifrost-http/handlers/realtime_client_secrets_test.go, transports/bifrost-http/integrations/openai.go
New RealtimeClientSecretsHandler: JSON-only /v1/realtime/client_secrets & /v1/realtime/sessions, request normalization (strip provider prefix), provider/model resolution, optional governance evaluation, upstream passthrough, ephemeral token→virtual-key KV caching, path helper utilities, and tests.
Realtime Turn Pipeline & Logging
transports/bifrost-http/handlers/realtime_turn_pipeline.go, transports/bifrost-http/handlers/realtime_logging.go, transports/bifrost-http/handlers/realtime_logging_test.go
New turn pipeline: build EI pre-requests and LM post-responses, stream timing/context propagation, output/usage extraction, error mapping, hook orchestration (start/finalize/error), accumulation of deltas, and comprehensive tests. Exports multiple pipeline helper functions.
Session Logging & WS Broadcasts
transports/bifrost-http/handlers/logging.go, transports/bifrost-http/handlers/websocket.go
Added session log endpoints (GET /api/logs/sessions/{session_id} + summary), parent-request-id fallback parsing and redacted-name enrichment; WebSocket broadcast trimming now preserves full parsed payloads for realtime.turn logs.
WS Responses Context & Close
transports/bifrost-http/handlers/wsresponses.go, transports/bifrost-http/handlers/wsresponses_test.go
Refactored createBifrostContextFromAuth(handlerStore, auth), capture baggage→parent-request-id, strip sk-bf- prefix for virtual keys, select native WS upstream keys with WS-error fallback, add WSResponsesHandler.Close(), and tests for baggage parsing.
Middlewares & Endpoint Matching
transports/bifrost-http/handlers/middlewares.go, transports/bifrost-http/handlers/middlewares_test.go
Added CORS header X-OpenAI-Agents-SDK; expanded inference WS endpoint detection (/responses, /realtime, /openai/*); introduced realtime-transport auth-bypass path set; refactored tracing middleware API (NewTracingMiddleware drops obsPlugins) and pinned per-request tracer usage; updated tests.
Integration Handler & Server Wiring
transports/bifrost-http/handlers/integrations.go, transports/bifrost-http/server/server.go
NewIntegrationHandler signature expanded to accept/store wsRealtime, webrtcRealtime, and realtimeClientSecrets (in addition to wsResponses); RegisterRoutes registers realtime handlers conditionally; added IntegrationHandler.Close() and invoked during shutdown/error paths.
WS Responses Minor
transports/bifrost-http/handlers/wsresponses.go
Added WSResponsesHandler.Close(), extended auth capture to baggage, moved context helper to package-level, changed virtual-key handling for sk-bf- tokens, and native WS key-selection now replies with WS error on selection failure.
Dev/debug tweak
transports/bifrost-http/handlers/devpprof.go
Adjusted goroutine categorization substring from completeAndFlushTrace to CompleteAndFlushTrace.
Module Dependencies
transports/go.mod
Added Pion WebRTC/RTCP (github.com/pion/webrtc/v4, github.com/pion/rtcp) and related github.com/pion/* modules plus github.com/wlynxg/anet and golang.org/x/time.
Governance Plugin
plugins/governance/main.go
Exported EvaluateGovernanceRequest(...) on BaseGovernancePlugin (renamed from private), updated callers, and improved HTTP pre-hook content-type detection to skip parsing for non-JSON/non-multipart bodies.
Tracing API Change
transports/bifrost-http/handlers/middlewares.go
NewTracingMiddleware signature changed to drop observability plugins; observability plugins are set on tracer via tracer.SetObservabilityPlugins(...).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Browser Client
    participant WS as WSRealtimeHandler
    participant Provider as Provider API
    participant Logger as Log Store

    Client->>WS: HTTP Upgrade + query(model/deployment)
    WS->>Provider: Resolve provider & obtain WS endpoint
    WS->>Client: WebSocket established

    rect rgba(100,150,200,0.5)
    Note over Client,Provider: Relay Loop (concurrent)
    loop Client → Provider
        Client->>WS: JSON realtime event
        WS->>WS: Parse, update session summaries, trigger turn-start hooks
        WS->>Provider: Translate & forward event
    end

    loop Provider → Client
        Provider->>WS: JSON realtime event
        WS->>WS: Translate to Bifrost schema, accumulate deltas
        WS->>WS: Finalize turns, run post-hooks
        WS->>Logger: Persist realtime turn
        WS->>Client: Forward translated event
    end
    end

    Client->>WS: Close / EOF
    WS->>Provider: Close upstream
    WS->>Logger: Log relay termination
Loading
sequenceDiagram
    participant Browser as Browser Client
    participant WebRTC as WebRTCRealtimeHandler
    participant Provider as Provider API
    participant Logger as Log Store

    Browser->>WebRTC: POST multipart (sdp offer + session)
    WebRTC->>WebRTC: Validate session, select provider/model & key
    WebRTC->>Provider: POST upstream SDP offer + session
    Provider->>WebRTC: SDP answer
    WebRTC->>Browser: Return browser SDP answer

    rect rgba(150,200,100,0.5)
    Note over Browser,Provider: RTP & DataChannel Relay
    loop RTP Audio Forwarding
        Browser->>WebRTC: RTP audio
        WebRTC->>Provider: Forward RTP
        Provider->>WebRTC: RTP audio
        WebRTC->>Browser: Forward RTP
    end

    loop Data Channel Messages
        Browser->>WebRTC: JSON event (parse, update, hook)
        WebRTC->>Provider: Forward translated event
        Provider->>WebRTC: JSON event (translate, accumulate)
        WebRTC->>Logger: Persist turn
        WebRTC->>Browser: Forward event
    end
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Poem

🐰 I hopped through webs and SDP lanes,
I parsed each turn and balanced the chains,
Minted secrets quick and relayed every stream,
I bound audio, data, and every dream,
Logs and hooks rejoiced—hip hop hooray! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature addition: realtime WebSocket, WebRTC, and client secret handlers.
Description check ✅ Passed The description is comprehensive and well-structured, covering summary, detailed changes, type of change, affected areas, testing instructions, security considerations, and checklist completion.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 27, 2026

Confidence Score: 5/5

Safe to merge; both findings are P2 style/observability issues with no functional impact

No P0/P1 findings. The double-finalization call in the abnormal WS close path is dead code (second call always returns nil since session state was already consumed by the first call). The WebRTC input-recording ordering diverges from the WS relay design intent but does not cause incorrect behavior with the current OpenAI Realtime event protocol where input events and turn-start events are always separate types.

transports/bifrost-http/handlers/wsrealtime.go (dead finalization path) and transports/bifrost-http/handlers/webrtc_realtime.go (input recording before turn-start validation)

Important Files Changed

Filename Overview
transports/bifrost-http/handlers/wsrealtime.go New bidirectional WebSocket relay with heartbeat, turn-scoped hook pipeline, and ephemeral key resolution; one dead-code finalization path in the abnormal upstream-close branch
transports/bifrost-http/handlers/webrtc_realtime.go Major WebRTC relay refactor with dual-endpoint GA/legacy routing, provider-owned SDP exchange, and buffered pending message queues; input recording order differs from the WS relay's deferred-recording design
transports/bifrost-http/handlers/realtime_turn_pipeline.go Realtime turn context creation, pre/post hook execution, and trace completion — well-structured with idempotent consume helpers
transports/bifrost-http/handlers/realtime_logging.go Content extraction, usage parsing, and log message construction for realtime turns; correct TotalTokens fallback and summary truncation
transports/bifrost-http/handlers/realtime_client_secrets.go HTTP handler for minting realtime client secrets with governance evaluation, ephemeral key caching, and model alias resolution
transports/bifrost-http/handlers/middlewares.go CORS X-OpenAI-Agents-SDK header addition, expanded isInferenceWSEndpoint, and new isJSONContentType helper
plugins/governance/main.go Exports EvaluateGovernanceRequest and adds content-type guard to skip non-JSON/non-multipart bodies
transports/bifrost-http/server/server.go Wires new realtime handlers and graceful shutdown hooks
transports/bifrost-http/integrations/openai.go Adds OpenAIRealtimeClientSecretPaths and OpenAIRealtimeWebRTCCallsPaths for route registration

Reviews (15): Last reviewed commit: "feat: add realtime WebSocket, WebRTC, an..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

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

1449-1457: ⚠️ Potential issue | 🟠 Major

Close realtime handlers after the server stops accepting new requests.

IntegrationHandler.Close() runs before s.Server.Shutdown(), so there's a window where the listener can still accept WS/WebRTC upgrades after the realtime handlers have already been torn down. Late arrivals in that window can fail unpredictably or create orphaned sessions during shutdown.

Suggested fix
 	case sig := <-sigChan:
 		logger.Info("received signal %v, initiating graceful shutdown...", sig)
-		if s.IntegrationHandler != nil {
-			logger.Info("closing realtime transport sessions...")
-			s.IntegrationHandler.Close()
-		}
 		// Create shutdown context with timeout
 		shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 		defer cancel()
 		// Perform graceful shutdown
 		if err := s.Server.Shutdown(); err != nil {
 			logger.Error("error during graceful shutdown: %v", err)
 		} else {
 			logger.Info("server gracefully shutdown")
 		}
+		if s.IntegrationHandler != nil {
+			logger.Info("closing realtime transport sessions...")
+			s.IntegrationHandler.Close()
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/server/server.go` around lines 1449 - 1457,
IntegrationHandler.Close() is being called before s.Server.Shutdown(), which
allows the listener to still accept WS/WebRTC upgrades after realtime handlers
are torn down; instead, perform the server shutdown first using the created
shutdown context (call s.Server.Shutdown(shutdownCtx) and wait for its result)
and only after Shutdown returns (and the listener stops accepting new
connections) call s.IntegrationHandler.Close() with the existing nil check and
logging; ensure you use the shutdownCtx variable (context.WithTimeout) for
Shutdown and preserve error logging around both Shutdown and
IntegrationHandler.Close().
🧹 Nitpick comments (2)
transports/bifrost-http/handlers/middlewares_test.go (1)

74-75: Prefer set-style header assertions for the default CORS list.

These exact comma-separated comparisons are brittle whenever the default header list grows or gets reordered. The helpers already in this file (containsHeader / splitHeaders) would keep the test focused on behavior instead of string formatting.

Also applies to: 895-899

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares_test.go` around lines 74 - 75,
Replace the brittle exact-string assertion for the
"Access-Control-Allow-Headers" value with a set-style check using the existing
helpers: call splitHeaders on
ctx.Response.Header.Peek("Access-Control-Allow-Headers") and then use
containsHeader to assert that each expected header (e.g., "Content-Type",
"Authorization", "X-Requested-With", "X-Stainless-Timeout",
"X-OpenAI-Agents-SDK") is present; do the same refactor for the other occurrence
flagged in the comment (also using containsHeader and splitHeaders) so the tests
validate presence regardless of ordering or extra defaults.
transports/bifrost-http/handlers/websocket.go (1)

183-214: Trim a copy, not the shared log pointer.

This now mutates the caller-owned *logstore.Log before broadcasting. If the same pointer is reused by other callbacks or later reads, those consumers will see the trimmed/nil fields instead of the original entry.

♻️ Proposed fix
-	if logEntry.Object != "realtime.turn" {
-		if len(logEntry.InputHistoryParsed) > 1 {
-			logEntry.InputHistoryParsed = logEntry.InputHistoryParsed[len(logEntry.InputHistoryParsed)-1:]
+	payload := *logEntry
+
+	if payload.Object != "realtime.turn" {
+		if len(payload.InputHistoryParsed) > 1 {
+			payload.InputHistoryParsed = payload.InputHistoryParsed[len(payload.InputHistoryParsed)-1:]
 		}
-		if len(logEntry.ResponsesInputHistoryParsed) > 1 {
-			logEntry.ResponsesInputHistoryParsed = logEntry.ResponsesInputHistoryParsed[len(logEntry.ResponsesInputHistoryParsed)-1:]
+		if len(payload.ResponsesInputHistoryParsed) > 1 {
+			payload.ResponsesInputHistoryParsed = payload.ResponsesInputHistoryParsed[len(payload.ResponsesInputHistoryParsed)-1:]
 		}
-		logEntry.OutputMessageParsed = nil
-		logEntry.ResponsesOutputParsed = nil
-		logEntry.EmbeddingOutputParsed = nil
-		logEntry.RerankOutputParsed = nil
-		logEntry.ParamsParsed = nil
-		logEntry.ToolsParsed = nil
-		logEntry.ToolCallsParsed = nil
-		logEntry.SpeechOutputParsed = nil
-		logEntry.TranscriptionOutputParsed = nil
-		logEntry.ImageGenerationOutputParsed = nil
-		logEntry.ListModelsOutputParsed = nil
-		logEntry.CacheDebugParsed = nil
+		payload.OutputMessageParsed = nil
+		payload.ResponsesOutputParsed = nil
+		payload.EmbeddingOutputParsed = nil
+		payload.RerankOutputParsed = nil
+		payload.ParamsParsed = nil
+		payload.ToolsParsed = nil
+		payload.ToolCallsParsed = nil
+		payload.SpeechOutputParsed = nil
+		payload.TranscriptionOutputParsed = nil
+		payload.ImageGenerationOutputParsed = nil
+		payload.ListModelsOutputParsed = nil
+		payload.CacheDebugParsed = nil
 	}
@@
-		Payload:   logEntry,
+		Payload:   &payload,
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/websocket.go` around lines 183 - 214, The
handler currently mutates the caller-owned *logstore.Log (logEntry) before
broadcasting; instead create a broadcast-only copy (e.g., copyVal := *logEntry
or a shallow copy) and perform all trimming/nil assignments on that copy so the
original pointer is untouched; when copying, ensure slice fields
(InputHistoryParsed, ResponsesInputHistoryParsed, etc.) are reduced on the copy
by slicing to their last element and pointer/parsed fields (OutputMessageParsed,
ResponsesOutputParsed, EmbeddingOutputParsed, RerankOutputParsed, ParamsParsed,
ToolsParsed, ToolCallsParsed, SpeechOutputParsed, TranscriptionOutputParsed,
ImageGenerationOutputParsed, ListModelsOutputParsed, CacheDebugParsed) are set
to nil on the copy, then set Payload to the copy when building the message
struct instead of the original logEntry.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/logging.go`:
- Around line 109-134: The handler currently ignores malformed pagination params
and silently falls back to defaults; update the parsing logic around the
limit/offset/order query handling (the blocks using ctx.QueryArgs().Peek,
strconv.Atoi, and setting pagination.Limit/pagination.Offset/pagination.Order)
to reject invalid values: if strconv.Atoi returns an error for limit or offset,
call SendError(ctx, fasthttp.StatusBadRequest, "<param> must be an integer") and
return; for limit enforce >0 and <= sessionLogPageLimit as now, for offset
enforce >=0 as now; for order, if value is non-empty and not "asc" or "desc",
call SendError with a BadRequest explaining allowed values and return. Ensure
all early-exit error paths use SendError so malformed params produce 400 instead
of silently using defaults.
- Around line 97-100: The handler currently type-asserts
ctx.UserValue("session_id").(string) which can panic if the router didn't
populate the param; change this to the safe pattern used in getLogByID by first
doing value, ok := ctx.UserValue("session_id") and checking ok and that value is
a non-empty string before assigning sessionID, returning SendError(ctx,
fasthttp.StatusBadRequest, "session_id is required") if the check fails; ensure
you update the sessionID variable usage accordingly and keep the same error path
and return logic.

In `@transports/bifrost-http/handlers/realtime_client_secrets.go`:
- Around line 232-240: The isJSONContentType function currently matches raw
strings and can misclassify types; change it to parse the content type using
mime.ParseMediaType inside isJSONContentType (referencing the isJSONContentType
function) and then check the parsed mediatype: return true if mediatype ==
"application/json" or strings.HasSuffix(mediatype, "+json"); treat empty input
or parse errors as non-JSON (return false) and ensure you lower-case the parsed
mediatype before checking.

In `@transports/bifrost-http/handlers/realtime_logging.go`:
- Around line 39-49: newRealtimeTurnLogger currently dereferences config when
building the realtimeTurnLogger struct (assigning store: config.LogsStore and
pricingManager: config.ModelCatalog) which will panic if config is nil; change
the construction to first compute enabled as you do, then set local variables
(e.g., store and pricingManager) to nil by default and only assign from
config.LogsStore and config.ModelCatalog when config != nil, and finally return
&realtimeTurnLogger{enabled: enabled, store: store, pricingManager:
pricingManager, notify: notify} so no fields are read from a nil config.
- Around line 72-99: Use the propagated realtime session ID from the Bifrost
context (BifrostContextKeyRealtimeSessionID) as the log grouping key instead of
always using session.ID(); read the value from the request/context (e.g.,
ctx.Value(BifrostContextKeyRealtimeSessionID) or the Bifrost context created by
createBifrostContextFromAuth()/newRealtimeTurnContext()), if present and
non-empty use that ID for entry.ParentRequestID and set
MetadataParsed["realtime_session_id"] to it, otherwise fall back to session.ID()
as currently done so turn logs group with the rest of the session.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 413-445: shouldGracefullyDisconnectRealtime currently only looks
for budget and rate-limit strings, so quota-related terminal errors (e.g.,
insufficient_quota, quota_exceeded, hard-limit) classified by
mapRealtimeWireErrorFields still keep sessions alive; update
shouldGracefullyDisconnectRealtime to also detect quota exhaustion by checking
bifrostErr.Type, bifrostErr.Error.Type, bifrostErr.Error.Code and
bifrostErr.Error.Message for terms like "insufficient_quota", "quota_exceeded",
"quota", "hard_limit" or "hard-limit" (in addition to existing budget checks),
or reuse the same classification logic from mapRealtimeWireErrorFields/bifrost
helper to return true for those cases.

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 454-457: The bind methods (bindUpstreamChannel and
bindDownstreamChannel) and the close() logic currently race on
upstreamChannel/downstreamChannel because close() reads and closes them without
holding channelMu; fix by taking channelMu, copying r.upstreamChannel and
r.downstreamChannel into local variables while holding the lock, then release
the lock and perform Close() on the local copies; apply the same pattern
wherever the code reads/closes these fields (including the other bind methods
and the close()/connection-state callback sites referenced).
- Around line 146-160: The code is reusing the inbound Authorization instead of
resolving a provider key; call SelectKeyForProviderRequestType(...) before
creating relayCtx and pass the returned provider key into
newRealtimeRelayContext(...) and establishRelay(...), and ensure
exchangeUpstreamSDP(...) and any hook invocations receive that resolved key
instead of nil so upstream requests use the routed provider key; update function
signatures/locals (e.g., newRealtimeRelayContext, establishRelay,
exchangeUpstreamSDP and the turn-hook calls) to accept and propagate the
providerKey returned by SelectKeyForProviderRequestType().
- Around line 644-678: The sendUpstream/sendDownstream functions currently
append unbounded queuedDataChannelMessage entries to
r.pendingToUpstream/r.pendingToDownstream; add a hard cap (e.g.,
MaxPendingMessages and/or MaxPendingBytes constants) and check before appending
in sendUpstream/sendDownstream: if adding the message would exceed the cap, do
not append and instead fail the relay (close the connection / call whatever
existing relay shutdown method you have) while holding channelMu to avoid races;
also update flushPending to account for message size accounting and to
clear/reset counters when clearing pending slices. Ensure you reference and
update the fields pendingToUpstream, pendingToDownstream, and any new counters
on the relay struct (e.g., pendingBytesUpstream/pendingBytesDownstream) and keep
using queuedDataChannelMessage and sendDataChannelMessage for sending.

In `@transports/bifrost-http/handlers/wsresponses_test.go`:
- Around line 68-73: The test currently treats empty-string values as equivalent
to absent keys; update the ignored-case assertions in the test that calls
createBifrostContextFromAuth so they assert the context keys are absent instead
of empty strings: check that ctx.Value(schemas.BifrostContextKeyParentRequestID)
and ctx.Value(schemas.BifrostContextKeyRealtimeSessionID) are nil (or not
present) and fail if they are present (non-nil) — reference the keys
schemas.BifrostContextKeyParentRequestID and
schemas.BifrostContextKeyRealtimeSessionID and the helper
createBifrostContextFromAuth to locate where to change the assertions.

---

Outside diff comments:
In `@transports/bifrost-http/server/server.go`:
- Around line 1449-1457: IntegrationHandler.Close() is being called before
s.Server.Shutdown(), which allows the listener to still accept WS/WebRTC
upgrades after realtime handlers are torn down; instead, perform the server
shutdown first using the created shutdown context (call
s.Server.Shutdown(shutdownCtx) and wait for its result) and only after Shutdown
returns (and the listener stops accepting new connections) call
s.IntegrationHandler.Close() with the existing nil check and logging; ensure you
use the shutdownCtx variable (context.WithTimeout) for Shutdown and preserve
error logging around both Shutdown and IntegrationHandler.Close().

---

Nitpick comments:
In `@transports/bifrost-http/handlers/middlewares_test.go`:
- Around line 74-75: Replace the brittle exact-string assertion for the
"Access-Control-Allow-Headers" value with a set-style check using the existing
helpers: call splitHeaders on
ctx.Response.Header.Peek("Access-Control-Allow-Headers") and then use
containsHeader to assert that each expected header (e.g., "Content-Type",
"Authorization", "X-Requested-With", "X-Stainless-Timeout",
"X-OpenAI-Agents-SDK") is present; do the same refactor for the other occurrence
flagged in the comment (also using containsHeader and splitHeaders) so the tests
validate presence regardless of ordering or extra defaults.

In `@transports/bifrost-http/handlers/websocket.go`:
- Around line 183-214: The handler currently mutates the caller-owned
*logstore.Log (logEntry) before broadcasting; instead create a broadcast-only
copy (e.g., copyVal := *logEntry or a shallow copy) and perform all trimming/nil
assignments on that copy so the original pointer is untouched; when copying,
ensure slice fields (InputHistoryParsed, ResponsesInputHistoryParsed, etc.) are
reduced on the copy by slicing to their last element and pointer/parsed fields
(OutputMessageParsed, ResponsesOutputParsed, EmbeddingOutputParsed,
RerankOutputParsed, ParamsParsed, ToolsParsed, ToolCallsParsed,
SpeechOutputParsed, TranscriptionOutputParsed, ImageGenerationOutputParsed,
ListModelsOutputParsed, CacheDebugParsed) are set to nil on the copy, then set
Payload to the copy when building the message struct instead of the original
logEntry.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5aaa8c44-1d0c-4ddc-80ef-0659f1bd6325

📥 Commits

Reviewing files that changed from the base of the PR and between e97b881 and 33a3e91.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod

Comment thread transports/bifrost-http/handlers/logging.go Outdated
Comment thread transports/bifrost-http/handlers/logging.go
Comment thread transports/bifrost-http/handlers/realtime_client_secrets.go Outdated
Comment thread transports/bifrost-http/handlers/realtime_logging.go Outdated
Comment thread transports/bifrost-http/handlers/realtime_logging.go Outdated
Comment thread transports/bifrost-http/handlers/realtime_turn_pipeline.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go Outdated
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/wsresponses_test.go Outdated
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from e97b881 to f8b747f Compare March 30, 2026 10:24
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from 33a3e91 to 2d1c18b Compare March 30, 2026 10:24
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 125-132: The handler currently trims the transport input via
strings.TrimSpace(inputText) and drops whitespace-only turns before running
pre-hooks; change it so you do not alter or drop inputText prior to the hook
pipeline—remove the TrimSpace call and use the original inputText when
constructing the schemas.ResponsesMessage (preserving whitespace and
empty-content turns), while still keeping itemType/role and minimal validation
elsewhere; ensure pre-hooks receive the unmodified inputText so Bifrost acts as
a passthrough gateway.
- Around line 40-54: The code sets schemas.BifrostContextKeyRealtimeSessionID to
resolvedSessionID which may come from baggage (grouping) and incorrectly
overwrites the provider-assigned session id; update the logic so that
ctx.SetValue(schemas.BifrostContextKeyParentRequestID, resolvedSessionID) is
still set from resolvedSessionID but do NOT set
schemas.BifrostContextKeyRealtimeSessionID from resolvedSessionID; instead set
schemas.BifrostContextKeyRealtimeSessionID only when there is an actual provider
session id (providerSessionID trimmed/non-empty) and keep the separate provider
key schemas.BifrostContextKeyRealtimeProviderSessionID populated as before (use
providerSessionID).

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 773-795: ConvertToBifrostContext carries the caller's W3C baggage
`session-id` into schemas.BifrostContextKeyParentRequestID but
newRealtimeRelayContext currently doesn't preserve that key when copying values;
update newRealtimeRelayContext to check
requestCtx.Value(schemas.BifrostContextKeyParentRequestID) and, if non-nil, call
relayCtx.SetValue(schemas.BifrostContextKeyParentRequestID, value) along with
the other keys (ensure you reference requestCtx and relayCtx in the same
loop/logic used for copying the existing context keys).
- Around line 585-609: WebRTC turns never reach the turn logger because we never
snapshot the session's buffered input/tool state or call
r.turnLogger.logTurn(...) before finalizeRealtimeTurnHooks consumes buffers and
we also miss logging on the EI path and provider response.done/error events; fix
by capturing the current realtime state (call
finalizedRealtimeToolOutputSummary(event) and
finalizedRealtimeInputSummary(event) into local vars and preserve r.session's
raw/input/tool values before calling finalizeRealtimeTurnHooks) and invoke
r.turnLogger.logTurn(...) in the EI path where startRealtimeTurnHooks is run
(after snapshotting) and in the provider response.done and response.error
handling code paths so every WebRTC turn records a log entry using the preserved
snapshot and the same identifiers used elsewhere (reference
r.session.AddRealtimeToolOutput,
r.session.SetRealtimeInputText/SetRealtimeInputRaw, startRealtimeTurnHooks,
finalizeRealtimeTurnHooks, and r.turnLogger.logTurn).
- Around line 99-103: Remove the hard-fail check that requires a raw
Authorization header in the WebRTC realtime handler: delete or bypass the block
that reads authorization :=
strings.TrimSpace(string(ctx.Request.Header.Peek("Authorization"))) and returns
a 401 via SendBifrostError/newRealtimeWebRTCError when empty, and instead rely
on the transport auth middleware / lib.ConvertToBifrostContext(...) derived auth
(e.g., use the existing Bifrost context or ctx.UserValue used elsewhere in this
handler) to determine request identity; ensure no other logic in this function
(the WebRTC realtime handler) assumes the presence of the raw header before
using the converted auth.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Around line 239-265: The WS path never calls h.turnLogger.logTurn because
finalizeRealtimeTurnHooks consumes the buffered input/tool state; before calling
finalizeRealtimeTurnHooks (and before any startRealtimeTurnHooks/WriteMessage
that leads to provider response.done or error), snapshot the current buffered
summaries returned by finalizedRealtimeToolOutputSummary(event) and
finalizedRealtimeInputSummary(event) into local vars (capturing values used by
session.AddRealtimeToolOutput,
session.SetRealtimeInputText/SetRealtimeInputRaw), then call
h.turnLogger.logTurn(...) using those captured values on the client EI path and
in provider response.done/error handling branches so WS turns are logged just
like HTTP sessions. Ensure you update the branches around
startRealtimeTurnHooks, provider.ToProviderRealtimeEvent, upstream.WriteMessage
and provider response handlers to use the captured summaries before
finalizeRealtimeTurnHooks mutates session state.

In `@transports/bifrost-http/server/server.go`:
- Around line 1449-1452: Start() currently only closes realtime sessions
(s.IntegrationHandler.Close()) in the signal-handling branch, leaving sessions
open when Serve returns an error via errChan; add a shared cleanup helper (e.g.,
closeRealtimeSessions or s.closeRealtimeSessions) that checks
s.IntegrationHandler != nil and calls Close(), and invoke that helper from both
the errChan branch and the signal branch (after wsPool close if present) so
realtime session managers are always torn down regardless of which exit path
runs; update references to IntegrationHandler.Close() to use the new helper.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa4b0abc-bb11-46fe-bdc6-9ea1959d57e7

📥 Commits

Reviewing files that changed from the base of the PR and between 33a3e91 and 2d1c18b.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (2)
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/middlewares.go

Comment thread transports/bifrost-http/handlers/realtime_turn_pipeline.go
Comment thread transports/bifrost-http/handlers/realtime_turn_pipeline.go Outdated
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go Outdated
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go Outdated
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/wsrealtime.go Outdated
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from 2d1c18b to b91d7ac Compare March 30, 2026 12:49
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from f8b747f to 7e6f0c6 Compare March 30, 2026 12:49
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
transports/bifrost-http/handlers/logging.go (1)

168-177: Consider extracting duplicate toSlice helper.

This helper is duplicated at lines 346-355 (getLogs). Consider extracting to a package-level function to reduce duplication.

♻️ Suggested refactor
+// mapKeysToSlice extracts keys from a string set into a slice.
+func mapKeysToSlice(m map[string]struct{}) []string {
+	if len(m) == 0 {
+		return nil
+	}
+	out := make([]string, 0, len(m))
+	for id := range m {
+		out = append(out, id)
+	}
+	return out
+}

Then replace both inline toSlice closures with calls to mapKeysToSlice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/logging.go` around lines 168 - 177, The
inline toSlice closure is duplicated (once at this block and again in getLogs);
extract it into a package-level helper like mapKeysToSlice that takes
map[string]struct{} and returns []string, then replace both local closures with
calls to mapKeysToSlice; update imports/comments if needed and ensure the new
function name (e.g., mapKeysToSlice) is unexported and placed near other helpers
in transports/bifrost-http/handlers/logging.go so both the current function and
getLogs reference it.
transports/bifrost-http/handlers/wsresponses_test.go (1)

48-68: Cover the new auth-header normalization branches too.

These tests only exercise baggage parsing, but createBifrostContextFromAuth now also changes Authorization: Bearer sk-bf-*, x-api-key, and x-goog-api-key handling plus ShouldAllowDirectKeys() gating. A small table-driven test here would make those precedence and normalization rules much safer to change later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/wsresponses_test.go` around lines 48 - 68,
Add a table-driven test in wsresponses_test.go that exercises
createBifrostContextFromAuth for the new auth-header normalization branches:
include cases for Authorization: Bearer sk-bf-*, x-api-key, and x-goog-api-key
(with and without surrounding whitespace), plus combinations that verify
precedence between baggage and direct keys and when ShouldAllowDirectKeys()
gates direct-key handling; for each case provide an authHeaders input and assert
the resulting ctx.Value(s) (e.g., schemas.BifrostContextKeyParentRequestID and
any other context keys set by createBifrostContextFromAuth) match expected
normalized values or are nil for empty/whitespace, and include a case where
ShouldAllowDirectKeys() is false to ensure direct-key headers are ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/realtime_logging.go`:
- Around line 274-290: The helper extractRealtimeResponseDoneAssistantText
currently returns only the first non-empty content block; change it to
concatenate all content blocks' non-empty fields in order (text, then
transcript, then refusal) for each realtimeResponseDoneOutput of Type "message".
Implement this by iterating outputs and their Content blocks, for each block
select the first non-empty trimmed value among block.Text, block.Transcript,
block.Refusal, append it to a builder (with a single separating space if there
is prior content), and return the combined trimmed string; keep the function
signature and use the types realtimeResponseDoneOutput and the function name
extractRealtimeResponseDoneAssistantText to locate the code.

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 756-788: The relay context created in newRealtimeRelayContext does
not copy over schemas.BifrostContextKeyRequestID, so detached relays lose the
gateway request id; fix by including schemas.BifrostContextKeyRequestID in the
set of keys copied from requestCtx to relayCtx (i.e., ensure
requestCtx.Value(schemas.BifrostContextKeyRequestID) is checked and passed to
relayCtx.SetValue), preserving request correlation for post-hook/logging.
- Around line 581-589: The session's active turn hooks (checked via
r.session.PeekRealtimeTurnHooks()) must be cleared/finalized when the upstream
provider responds with an error/abort event so the dead turn doesn't block
subsequent starts; update the handlers that process upstream events (the branch
handling event.Error != nil and any abort/error events around the provider
response handling, including the block after startRealtimeTurnHooks(...) and the
similar block at 621-630) to invoke the same turn-finalization/cleanup used on
normal completion (i.e., call the session cleanup routine that clears the active
realtime turn hooks — the same logic used at the successful-turn completion
path) and then send/close with the error event (e.g., call r.closeWithErrorEvent
or sendDownstream as appropriate) so the hook state is always cleared on
terminal error/abort.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Around line 243-257: You start the turn via startRealtimeTurnHooks (and mark
it via session.PeekRealtimeTurnHooks()) before calling
provider.ToProviderRealtimeEvent or before handling provider-sent error events,
so if translation fails or the provider sends an error path the turn remains
marked and blocks future turns; fix by either moving
provider.ToProviderRealtimeEvent translation before calling
startRealtimeTurnHooks, or ensure you always unwind the started turn on all
non-final/error paths by invoking the turn-cleanup counterpart (the function
that reverses startRealtimeTurnHooks) or explicitly clearing
session.PeekRealtimeTurnHooks() in the error branches (specifically where
provider.ToProviderRealtimeEvent returns an error and where incoming provider
events contain event.Error != nil) so the session state is not left stuck.

In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 233-235: The call to SelectKeyForProviderRequestType using
schemas.WebSocketResponsesRequest must not be downgraded to an HTTP fallback
when it returns an error; change the handling in the ws responses handler (where
SelectKeyForProviderRequestType is called) so that on error you return/propagate
a terminal error (or a specific authorization/permission error) instead of
returning false to let the HTTP bridge try ResponsesStreamRequest; ensure the
handler enforces the WebSocket-only gate by either aborting the request flow on
SelectKeyForProviderRequestType error or by passing the
WebSocketResponsesRequest restriction into any fallback selection logic so
ResponsesStreamRequest cannot succeed when the WS key is denied.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/logging.go`:
- Around line 168-177: The inline toSlice closure is duplicated (once at this
block and again in getLogs); extract it into a package-level helper like
mapKeysToSlice that takes map[string]struct{} and returns []string, then replace
both local closures with calls to mapKeysToSlice; update imports/comments if
needed and ensure the new function name (e.g., mapKeysToSlice) is unexported and
placed near other helpers in transports/bifrost-http/handlers/logging.go so both
the current function and getLogs reference it.

In `@transports/bifrost-http/handlers/wsresponses_test.go`:
- Around line 48-68: Add a table-driven test in wsresponses_test.go that
exercises createBifrostContextFromAuth for the new auth-header normalization
branches: include cases for Authorization: Bearer sk-bf-*, x-api-key, and
x-goog-api-key (with and without surrounding whitespace), plus combinations that
verify precedence between baggage and direct keys and when
ShouldAllowDirectKeys() gates direct-key handling; for each case provide an
authHeaders input and assert the resulting ctx.Value(s) (e.g.,
schemas.BifrostContextKeyParentRequestID and any other context keys set by
createBifrostContextFromAuth) match expected normalized values or are nil for
empty/whitespace, and include a case where ShouldAllowDirectKeys() is false to
ensure direct-key headers are ignored.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcc8b295-70de-415d-a37a-3c7c0987fc7a

📥 Commits

Reviewing files that changed from the base of the PR and between 2d1c18b and b91d7ac.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (3)
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/go.mod
🚧 Files skipped from review as they are similar to previous changes (3)
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/handlers/integrations.go

Comment thread transports/bifrost-http/handlers/realtime_logging.go Outdated
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go Outdated
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/wsrealtime.go Outdated
Comment thread transports/bifrost-http/handlers/wsresponses.go Outdated
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from b91d7ac to c3a8913 Compare March 30, 2026 14:50
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from 7e6f0c6 to 8f0f53b Compare March 30, 2026 14:50
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 (1)
transports/bifrost-http/handlers/wsresponses.go (1)

515-548: ⚠️ Potential issue | 🟠 Major

Don’t change the virtual-key value shape only on the WS path.

Lines 519-547 strip sk-bf- before storing schemas.BifrostContextKeyVirtualKey, but transports/bifrost-http/lib/ctx.go:255-279 still stores the full token for HTTP requests and transports/bifrost-http/integrations/utils.go:395-403 reads the stored value back verbatim. That makes the same virtual key resolve differently depending on transport and can break WS key lookup.

♻️ Align the WS helper with the existing HTTP path
-				ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(token, "sk-bf-"))
+				ctx.SetValue(schemas.BifrostContextKeyVirtualKey, token)
...
-			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(auth.apiKey, "sk-bf-"))
+			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, auth.apiKey)
...
-			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(auth.googAPIKey, "sk-bf-"))
+			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, auth.googAPIKey)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/wsresponses.go` around lines 515 - 548, The
WS handler is normalizing virtual keys by stripping the "sk-bf-" prefix before
calling ctx.SetValue for schemas.BifrostContextKeyVirtualKey (in the
auth.authorization, auth.apiKey, auth.googAPIKey branches), which diverges from
the HTTP path; change the WS code so it stores the full token string (do not
call strings.TrimPrefix) when setting schemas.BifrostContextKeyVirtualKey via
ctx.SetValue, keeping the same token shape as the HTTP path and ensuring
downstream lookups that expect the full "sk-bf-..." value work correctly (update
the branches that currently call strings.TrimPrefix to pass the original token
variable instead).
🧹 Nitpick comments (2)
transports/bifrost-http/handlers/realtime_logging.go (1)

1-110: LGTM — Well-structured summary extraction logic.

The cascading fallback chain in extractRealtimeTurnSummary and the recursive text collection are clean implementations. A few minor observations:

  1. Lines 91–104: Map iteration order in Go is non-deterministic, so collectRealtimeTextFragments may produce differently-ordered summaries for the same JSON input. For logging/display purposes this is acceptable, but if reproducibility matters (e.g., for cache keys or comparisons), consider sorting map keys before iterating.

  2. Line 151: Uses encoding/json rather than sonic for a simple string unmarshal. Since extractRealtimeExtraParamString is called per-event during turn processing, consider switching to sonic.Unmarshal for consistency with line 80 and potential performance gains in high-throughput scenarios.

Neither is blocking — flagging as optional refinements.

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_logging.go` around lines 1 - 110,
collectRealtimeTextFragments iterates maps in non-deterministic order which can
yield different summary ordering; modify collectRealtimeTextFragments to extract
map keys, sort them (e.g., using sort.Strings), and iterate in that
deterministic order when handling map[string]any so summaries are reproducible;
additionally, locate the function extractRealtimeExtraParamString and replace
its use of encoding/json.Unmarshal with sonic.Unmarshal for consistency and
better performance (ensure error handling remains equivalent and imports are
updated).
transports/bifrost-http/handlers/webrtc_realtime.go (1)

807-815: Consider using sonic for consistency.

realtimeEventTypeFromPayload uses json.Unmarshal at line 811. While this is only used in logging (lines 876-880), switching to sonic.Unmarshal would be consistent with the rest of the codebase. Low priority since it's not on the message forwarding hot path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/webrtc_realtime.go` around lines 807 - 815,
Replace the use of encoding/json in realtimeEventTypeFromPayload by calling
sonic.Unmarshal so behavior stays identical but matches the codebase convention;
update imports to include the sonic package (e.g., github.com/bytedance/sonic)
and change the json.Unmarshal call to sonic.Unmarshal while keeping the same
error handling and return of trimmed envelope.Type to ensure no behavioral
change (this function is used for logging only).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/realtime_client_secrets.go`:
- Around line 65-66: The handler currently strips provider from the route for
providerKey/model selection (resolveRealtimeClientSecretTarget -> providerKey,
model) but then forwards the original body (with provider-prefixed model) to
CreateRealtimeClientSecret and session creation, causing upstream provider
failures; normalize the forwarded payload by replacing the body["model"] (or
equivalent payload field) with the stripped/bare model returned from
resolveRealtimeClientSecretTarget before calling CreateRealtimeClientSecret and
any session-related calls so the upstream sees the bare model (mirror the
rewrite logic used in webrtc_realtime_test.go). Ensure the same normalization is
applied in the code paths around CreateRealtimeClientSecret and the session
creation logic referenced in the same handler.

---

Outside diff comments:
In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 515-548: The WS handler is normalizing virtual keys by stripping
the "sk-bf-" prefix before calling ctx.SetValue for
schemas.BifrostContextKeyVirtualKey (in the auth.authorization, auth.apiKey,
auth.googAPIKey branches), which diverges from the HTTP path; change the WS code
so it stores the full token string (do not call strings.TrimPrefix) when setting
schemas.BifrostContextKeyVirtualKey via ctx.SetValue, keeping the same token
shape as the HTTP path and ensuring downstream lookups that expect the full
"sk-bf-..." value work correctly (update the branches that currently call
strings.TrimPrefix to pass the original token variable instead).

---

Nitpick comments:
In `@transports/bifrost-http/handlers/realtime_logging.go`:
- Around line 1-110: collectRealtimeTextFragments iterates maps in
non-deterministic order which can yield different summary ordering; modify
collectRealtimeTextFragments to extract map keys, sort them (e.g., using
sort.Strings), and iterate in that deterministic order when handling
map[string]any so summaries are reproducible; additionally, locate the function
extractRealtimeExtraParamString and replace its use of encoding/json.Unmarshal
with sonic.Unmarshal for consistency and better performance (ensure error
handling remains equivalent and imports are updated).

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 807-815: Replace the use of encoding/json in
realtimeEventTypeFromPayload by calling sonic.Unmarshal so behavior stays
identical but matches the codebase convention; update imports to include the
sonic package (e.g., github.com/bytedance/sonic) and change the json.Unmarshal
call to sonic.Unmarshal while keeping the same error handling and return of
trimmed envelope.Type to ensure no behavioral change (this function is used for
logging only).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 42bff76b-27db-46ba-963f-491140f7b103

📥 Commits

Reviewing files that changed from the base of the PR and between b91d7ac and c3a8913.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (4)
  • transports/go.mod
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/handlers/websocket.go

Comment thread transports/bifrost-http/handlers/realtime_client_secrets.go Outdated
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from 8f0f53b to 061807b Compare March 30, 2026 17:00
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from c3a8913 to 13ed4ad Compare March 30, 2026 17:00
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

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

516-520: ⚠️ Potential issue | 🟡 Minor

Parse the Bearer scheme case-insensitively.

The new sk-bf- normalization still only runs for an exact Bearer prefix, so valid headers like authorization: bearer sk-bf-... never populate BifrostContextKeyVirtualKey and fall through as unauthenticated.

Suggested fix
-	if strings.HasPrefix(auth.authorization, "Bearer ") {
-		token := strings.TrimPrefix(auth.authorization, "Bearer ")
+	if parts := strings.Fields(auth.authorization); len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
+		token := parts[1]
 		if strings.HasPrefix(token, "sk-bf-") {
 			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(token, "sk-bf-"))
 		} else if handlerStore.ShouldAllowDirectKeys() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/wsresponses.go` around lines 516 - 520, The
code only recognizes an exact "Bearer " prefix so headers like "bearer
sk-bf-..." are ignored; update the check on auth.authorization to be
case-insensitive (e.g. use strings.EqualFold or compare strings.ToLower for the
prefix) while extracting the original token from the original string, then
continue to TrimPrefix/TrimSpace and test token for the "sk-bf-" prefix and call
ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(token,
"sk-bf-")) as before; target the auth.authorization prefix logic (the
strings.HasPrefix call and subsequent token extraction) and make it
case-insensitive without lowercasing the token itself.
🧹 Nitpick comments (2)
transports/bifrost-http/handlers/logging.go (1)

168-177: Consider extracting toSlice to a package-level helper.

This inline toSlice function is duplicated in getLogs (lines 346-355) and getMCPLogs (lines 1288-1297). Extracting it to a package-level helper like mapKeysToSlice would reduce duplication across handlers.

♻️ Example package-level helper
// mapKeysToSlice extracts keys from a map[string]struct{} into a slice.
func mapKeysToSlice(m map[string]struct{}) []string {
	if len(m) == 0 {
		return nil
	}
	out := make([]string, 0, len(m))
	for id := range m {
		out = append(out, id)
	}
	return out
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/logging.go` around lines 168 - 177, The
inline toSlice function is duplicated in handlers getLogs and getMCPLogs;
extract it to a package-level helper named mapKeysToSlice(m map[string]struct{})
[]string, copy the exact behavior (return nil when empty, preallocate with
len(m), iterate keys), place it at the top of the package (unexported), and
replace the inline toSlice closures in getLogs, getMCPLogs and the current
handler with calls to mapKeysToSlice to remove duplication and keep behavior
identical.
transports/bifrost-http/handlers/middlewares_test.go (1)

74-75: Extract the default CORS allow-header list into one test helper.

X-OpenAI-Agents-SDK is now duplicated in multiple exact-string assertions here, and the file still has other CORS tests with separately hardcoded default header sets. Centralizing that list will keep future header additions from drifting across branches.

Also applies to: 895-899

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares_test.go` around lines 74 - 75,
Create a single test helper (e.g., getDefaultCORSHeaders or defaultAllowHeaders)
that returns the canonical "Content-Type, Authorization, X-Requested-With,
X-Stainless-Timeout, X-Api-Key, X-OpenAI-Agents-SDK" string and use it in all
assertions instead of hardcoding the literal; update the assertion shown (the
Access-Control-Allow-Headers check) and the other duplicated checks (e.g., the
group around the other CORS assertions mentioned) to call that helper so future
header additions are changed in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 192-208: The code trims contentOverride and other message text
(via strings.TrimSpace) before assigning into the synthetic
schemas.ResponsesMessage.ContentStr, which causes post-hooks to see a different
value (and drops whitespace-only completions); instead only use TrimSpace for
emptiness checks and preserve the original text for storage. Update the blocks
that set msg.Content (those using contentOverride,
extractRealtimeResponseDoneContentText, and chat message content) to check
emptiness with a trimmed copy but assign the untrimmed original string to
schemas.ResponsesMessageContent.ContentStr (keep existing IDs/Status logic
unchanged).
- Around line 544-548: RunRealtimeTurnPreHooks currently defers hooks.Cleanup()
unconditionally which can panic when hooks or its Cleanup is nil; change this to
match the active-hook path by only deferring Cleanup when hooks != nil and
hooks.Cleanup != nil (i.e., check the returned hooks value and its Cleanup
before calling defer hooks.Cleanup()), so the terminal-event/fallback path is
guarded the same way as the activeHooks branch.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Around line 306-319: The turn lifecycle bookkeeping (calls to
finalizeRealtimeTurnHooks and session.ClearRealtimeTurnHooks) must run
regardless of provider.ShouldForwardRealtimeEvent; move those branches out so
they execute before checking ShouldForwardRealtimeEvent. Specifically, inside
the event handling in wsrealtime.go, first check for event.Type ==
provider.RealtimeTurnFinalEvent() and call session.ConsumeRealtimeOutputText()
and finalizeRealtimeTurnHooks(...) (propagate any bifrostErr to
clientConn.writeRealtimeError and return), and check event.Error and call
session.ClearRealtimeTurnHooks() as needed, then after those bookkeeping steps
run provider.ShouldForwardRealtimeEvent(event) to decide whether to
continue/skip forwarding; keep finalizedRealtimeInputSummary handling unchanged.

In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 233-236: The SelectKeyForProviderRequestType call's error branch
must mirror the other realtime handlers instead of hardcoding
403/permission_denied; replace the current writeWSError(session, 403,
"permission_denied", ...) with the same error propagation used where
SelectKeyForProviderRequestType is called elsewhere (the handlers that use
SelectKeyForProviderRequestType in the realtime paths). Concretely, map the
returned err to the same HTTP status/code/message used by the other handlers (or
pass it through the shared error helper they use) so writeWSError receives the
status and error details derived from err rather than a hard-coded 403; keep
SelectKeyForProviderRequestType and writeWSError as the focal symbols to locate
and change this branch.

---

Outside diff comments:
In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 516-520: The code only recognizes an exact "Bearer " prefix so
headers like "bearer sk-bf-..." are ignored; update the check on
auth.authorization to be case-insensitive (e.g. use strings.EqualFold or compare
strings.ToLower for the prefix) while extracting the original token from the
original string, then continue to TrimPrefix/TrimSpace and test token for the
"sk-bf-" prefix and call ctx.SetValue(schemas.BifrostContextKeyVirtualKey,
strings.TrimPrefix(token, "sk-bf-")) as before; target the auth.authorization
prefix logic (the strings.HasPrefix call and subsequent token extraction) and
make it case-insensitive without lowercasing the token itself.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/logging.go`:
- Around line 168-177: The inline toSlice function is duplicated in handlers
getLogs and getMCPLogs; extract it to a package-level helper named
mapKeysToSlice(m map[string]struct{}) []string, copy the exact behavior (return
nil when empty, preallocate with len(m), iterate keys), place it at the top of
the package (unexported), and replace the inline toSlice closures in getLogs,
getMCPLogs and the current handler with calls to mapKeysToSlice to remove
duplication and keep behavior identical.

In `@transports/bifrost-http/handlers/middlewares_test.go`:
- Around line 74-75: Create a single test helper (e.g., getDefaultCORSHeaders or
defaultAllowHeaders) that returns the canonical "Content-Type, Authorization,
X-Requested-With, X-Stainless-Timeout, X-Api-Key, X-OpenAI-Agents-SDK" string
and use it in all assertions instead of hardcoding the literal; update the
assertion shown (the Access-Control-Allow-Headers check) and the other
duplicated checks (e.g., the group around the other CORS assertions mentioned)
to call that helper so future header additions are changed in one place.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7407ec43-bcd2-4d20-891e-66484c89ce10

📥 Commits

Reviewing files that changed from the base of the PR and between c3a8913 and 13ed4ad.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (4)
  • transports/go.mod
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/handlers/webrtc_realtime.go

Comment thread transports/bifrost-http/handlers/realtime_turn_pipeline.go
Comment thread transports/bifrost-http/handlers/realtime_turn_pipeline.go Outdated
Comment thread transports/bifrost-http/handlers/wsrealtime.go Outdated
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from 13ed4ad to d86f3f5 Compare March 31, 2026 06:16
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from 061807b to bf57993 Compare March 31, 2026 06:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

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

515-548: ⚠️ Potential issue | 🟠 Major

Preserve the full sk-bf-... token in BifrostContextKeyVirtualKey.

x-bf-vk and the non-WS HTTP path both store the raw virtual-key token, but these three branches strip the prefix before setting BifrostContextKeyVirtualKey. That makes WS upgrades behave differently from every other auth path and can break downstream virtual-key lookup/allowlist logic. Because createBifrostContextFromAuth is shared with wsrealtime.go, the regression hits both WS endpoints.

🔧 Suggested fix
-				ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(token, "sk-bf-"))
+				ctx.SetValue(schemas.BifrostContextKeyVirtualKey, token)

-			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(auth.apiKey, "sk-bf-"))
+			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, auth.apiKey)

-			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(auth.googAPIKey, "sk-bf-"))
+			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, auth.googAPIKey)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/wsresponses.go` around lines 515 - 548, The
three branches in createBifrostContextFromAuth (handling auth.authorization,
auth.apiKey, auth.googAPIKey) currently call strings.TrimPrefix(..., "sk-bf-")
before ctx.SetValue(schemas.BifrostContextKeyVirtualKey, ...) which strips the
required "sk-bf-" prefix; change those assignments to store the full token
(including the "sk-bf-" prefix) instead of the trimmed value and remove the
TrimPrefix calls for virtual-key branches so WS code paths (wsrealtime.go) and
non-WS HTTP paths behave consistently when looking up virtual keys.
🧹 Nitpick comments (1)
transports/bifrost-http/handlers/middlewares_test.go (1)

74-75: Deduplicate default CORS allow-headers test literal.

The same long header string is repeated; extracting a shared test constant will reduce drift when defaults change again.

♻️ Proposed refactor
+const expectedDefaultCORSAllowHeaders = "Content-Type, Authorization, X-Requested-With, X-Stainless-Timeout, X-Api-Key, X-OpenAI-Agents-SDK"
...
- if string(ctx.Response.Header.Peek("Access-Control-Allow-Headers")) != "Content-Type, Authorization, X-Requested-With, X-Stainless-Timeout, X-Api-Key, X-OpenAI-Agents-SDK" {
+ if string(ctx.Response.Header.Peek("Access-Control-Allow-Headers")) != expectedDefaultCORSAllowHeaders {
    t.Errorf("Access-Control-Allow-Headers header not set correctly")
  }
...
- expectedHeaders := "Content-Type, Authorization, X-Requested-With, X-Stainless-Timeout, X-Api-Key, X-OpenAI-Agents-SDK"
+ expectedHeaders := expectedDefaultCORSAllowHeaders

Also applies to: 895-899

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares_test.go` around lines 74 - 75,
Extract the repeated CORS header string into a single test constant (e.g., const
expectedDefaultAllowHeaders = "Content-Type, Authorization, X-Requested-With,
X-Stainless-Timeout, X-Api-Key, X-OpenAI-Agents-SDK") at the top of
transports/bifrost-http/handlers/middlewares_test.go and replace the inline
literal used in the assertion in the test that checks
ctx.Response.Header.Peek("Access-Control-Allow-Headers") and the other
occurrence (around lines ~895-899) with that constant; update any t.Errorf
messages if desired to reference the constant for clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 447-449: The OnDataChannel handler currently overwrites
r.downstreamChannel unconditionally via r.bindDownstreamChannel(dc); change it
to allow only a single downstream channel by checking r.downstreamChannel first
and rejecting or closing any additional DataChannel: in the
r.downstreamPC.OnDataChannel callback, if r.downstreamChannel != nil then
log/notify and call dc.Close() (or ignore the channel) instead of binding;
otherwise call r.bindDownstreamChannel(dc). Apply the same duplicate-guard logic
to the other OnDataChannel site that similarly sets r.downstreamChannel.
- Around line 697-721: The close() method on webrtcRealtimeRelay currently tears
down PCs/channels without clearing any in-flight turn plugin state left by
startRealtimeTurnHooks(); update webrtcRealtimeRelay.close() to invoke
finalizeRealtimeTurnHooks(r.session) (or the appropriate per-session finalize)
and then ClearRealtimeTurnHooks(r.session) (or ClearRealtimeTurnHooks() on the
relay/session object) early in the shutdown path—before cancelling context and
closing channels/PCs—to ensure plugin state is cleaned; perform nil checks on
r.session and handle/ignore returned errors so cleanup never blocks teardown.

In `@transports/bifrost-http/handlers/websocket.go`:
- Around line 183-205: The branch currently sends the full logEntry for
"realtime.turn" which can include huge parsed fields; instead create/send a
bounded projection for realtime.turn by replacing or copying logEntry into a
lightweight shape that contains only essential metadata and the combined turn
view (e.g., ID, Object, CreatedAt/Timestamp, the last element of
InputHistoryParsed and ResponsesInputHistoryParsed, and a small
truncated/summarized OutputMessageParsed if needed) and set all heavy parsed
fields (ParamsParsed, ToolCallsParsed, CacheDebugParsed, EmbeddingOutputParsed,
RerankOutputParsed, SpeechOutputParsed, TranscriptionOutputParsed,
ImageGenerationOutputParsed, ListModelsOutputParsed, CacheDebugParsed, etc.) to
nil; update the websocket.go code around logEntry handling so when
logEntry.Object == "realtime.turn" you build and send that bounded projection
instead of the full logEntry (use the existing symbols logEntry,
InputHistoryParsed, ResponsesInputHistoryParsed, OutputMessageParsed,
ParamsParsed, ToolCallsParsed, CacheDebugParsed to locate the change).

In `@transports/bifrost-http/server/server.go`:
- Around line 1441-1444: Reverse the shutdown order so the HTTP/WebSocket/WebRTC
listener is stopped before closing live sessions: call the server shutdown
method (Server.Shutdown() / s.Shutdown()) and wait for it to complete/return
error handling, then call s.IntegrationHandler.Close() to tear down current
realtime sessions; this prevents new realtime sessions from being registered
after you've cleaned up existing ones. Ensure you handle and log any errors from
the shutdown call and only proceed to IntegrationHandler.Close() after shutdown
succeeds or after a controlled timeout.

---

Outside diff comments:
In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 515-548: The three branches in createBifrostContextFromAuth
(handling auth.authorization, auth.apiKey, auth.googAPIKey) currently call
strings.TrimPrefix(..., "sk-bf-") before
ctx.SetValue(schemas.BifrostContextKeyVirtualKey, ...) which strips the required
"sk-bf-" prefix; change those assignments to store the full token (including the
"sk-bf-" prefix) instead of the trimmed value and remove the TrimPrefix calls
for virtual-key branches so WS code paths (wsrealtime.go) and non-WS HTTP paths
behave consistently when looking up virtual keys.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/middlewares_test.go`:
- Around line 74-75: Extract the repeated CORS header string into a single test
constant (e.g., const expectedDefaultAllowHeaders = "Content-Type,
Authorization, X-Requested-With, X-Stainless-Timeout, X-Api-Key,
X-OpenAI-Agents-SDK") at the top of
transports/bifrost-http/handlers/middlewares_test.go and replace the inline
literal used in the assertion in the test that checks
ctx.Response.Header.Peek("Access-Control-Allow-Headers") and the other
occurrence (around lines ~895-899) with that constant; update any t.Errorf
messages if desired to reference the constant for clarity.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6fec4dc-6906-4e25-9860-f9a4071f6da6

📥 Commits

Reviewing files that changed from the base of the PR and between 13ed4ad and d86f3f5.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (3)
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/handlers/wsrealtime.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go

Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/websocket.go
Comment thread transports/bifrost-http/server/server.go
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from bf57993 to 87f5ecf Compare March 31, 2026 08:00
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from d86f3f5 to 7ff4c91 Compare March 31, 2026 08:00
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/integrations.go`:
- Around line 72-80: IntegrationHandler.Close currently tears down wsRealtime
and webrtcRealtime but omits the WS responses transport; add a nil-check and
call the WS responses handler's Close (e.g., if h.wsResponses != nil {
h.wsResponses.Close() }) inside IntegrationHandler.Close, and implement a Close
method on WSResponsesHandler that gracefully shuts down its owned session
manager and the shared upstream pool by invoking the session manager
shutdown/close method (e.g., sessionManager.Close()/Shutdown()) and the upstream
pool's Close/Shutdown method so /v1/responses sockets are included in graceful
shutdown.

In `@transports/bifrost-http/handlers/logging.go`:
- Around line 35-39: parseParentRequestIDFilter currently trims the
session/parent_request_id and returns the trimmed value which mutates
caller-provided exact-match IDs; change it to only use strings.TrimSpace to
check for emptiness but return the original unmodified query value (i.e., store
raw := string(ctx.QueryArgs().Peek("parent_request_id")) and if
strings.TrimSpace(raw) != "" return raw; otherwise get raw session_id and do the
same). Apply the same pattern in getLogSessionByID (and related spots at the
other noted locations): use TrimSpace only as an emptiness guard and forward the
original sessionID to filters.ParentRequestID / PluginLogManager.GetSessionLogs
without altering the ID.

In `@transports/bifrost-http/handlers/realtime_logging.go`:
- Around line 89-109: The collector collectRealtimeTextFragments currently only
recurses into map[string]any and []any, so JSON string literals (e.g. "hello")
parsed by sonic.Unmarshal are ignored; update the type switch in
collectRealtimeTextFragments to handle case string (and/or *string if used
elsewhere) by trimming and appending non-empty text to parts (same logic as
inside the map case) so standalone JSON string values are included in the
summary; keep existing recursion for map[string]any and []any and reuse the same
trimming/append behavior for consistency.

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 239-240: installDataChannelRelay() currently ignores errors from
the upstream CreateDataChannel call, leaving the relay partially initialized;
update installDataChannelRelay() so that when CreateDataChannel (for label from
RealtimeWebRTCDataChannelLabel()) returns an error or nil channel the function
immediately calls relay.close() and returns that error (propagating it back to
establishRelay()), ensuring the SDP handshake cannot succeed with a missing
upstream event path and preventing messages from piling in the pending queue.
- Around line 621-627: The error path currently calls ClearRealtimeTurnHooks()
but leaves realtime accumulators intact, which lets failed-turn input/raw/tool
buffers leak into the next turn; update the error branch (the handler around
event.Error != nil) to drain those buffers by calling
ConsumeRealtimeInputText(), ConsumeRealtimeInputRaw(), and
ConsumeRealtimeToolOutputs() before returning (or alternatively extend
ClearRealtimeTurnHooks() to also reset those buffers), ensuring the same symbols
(ClearRealtimeTurnHooks, ConsumeRealtimeInputText, ConsumeRealtimeInputRaw,
ConsumeRealtimeToolOutputs, finalizeRealtimeTurnHooks) are used so subsequent
turns don't consume stale data.

In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 233-236: The code selects a key using
SelectKeyForProviderRequestType with schemas.WebSocketResponsesRequest in
tryNativeWSUpstream but falls back to executeHTTPBridge/ResponsesStreamRequest
which re-selects a key using schemas.ResponsesStreamRequest, allowing different
authorization; fix by carrying the WS-selected key (or its authorization result)
into the fallback path instead of re-selecting: update tryNativeWSUpstream to
pass the selected key/token into executeHTTPBridge (and through
ResponsesStreamRequest → handleStreamRequest → tryStreamRequest) or
pre-authorize the ResponsesStreamRequest with the already-selected key, ensuring
SelectKeyForProviderRequestType is not called again with a different RequestType
and that AllowedRequests gating uses the original WS key.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8debebbf-f0a8-48e9-a407-526a6b68cda1

📥 Commits

Reviewing files that changed from the base of the PR and between d86f3f5 and 7ff4c91.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (3)
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/handlers/middlewares.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/go.mod
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go

Comment thread transports/bifrost-http/handlers/integrations.go
Comment thread transports/bifrost-http/handlers/logging.go Outdated
Comment thread transports/bifrost-http/handlers/realtime_logging.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go Outdated
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go Outdated
Comment thread transports/bifrost-http/handlers/wsresponses.go
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from dc4441a to 7ed9dd7 Compare April 3, 2026 11:07
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from 5b3bf16 to 0d37096 Compare April 3, 2026 11:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

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

523-554: ⚠️ Potential issue | 🔴 Critical

Preserve the full sk-bf-... token when a header carries a virtual key.

Virtual keys are generated and stored with the sk-bf- prefix. Stripping that prefix here means Authorization, x-api-key, and x-goog-api-key resolve a different value than x-bf-vk, so the same valid VK will be accepted or rejected depending on which header the client used. Please store the full token in BifrostContextKeyVirtualKey on all three paths, and add a focused regression test around createBifrostContextFromAuth for Bearer sk-bf-*.

🔒 Suggested fix
 	if auth.authorization != "" {
 		if strings.HasPrefix(auth.authorization, "Bearer ") {
 			token := strings.TrimPrefix(auth.authorization, "Bearer ")
 			if strings.HasPrefix(token, "sk-bf-") {
-				ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(token, "sk-bf-"))
+				ctx.SetValue(schemas.BifrostContextKeyVirtualKey, token)
 			} else if handlerStore.ShouldAllowDirectKeys() {
 				key := schemas.Key{
@@
 	if auth.apiKey != "" {
 		if strings.HasPrefix(auth.apiKey, "sk-bf-") {
-			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(auth.apiKey, "sk-bf-"))
+			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, auth.apiKey)
 		} else if handlerStore.ShouldAllowDirectKeys() {
 			key := schemas.Key{
@@
 	if auth.googAPIKey != "" {
 		if strings.HasPrefix(auth.googAPIKey, "sk-bf-") {
-			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(auth.googAPIKey, "sk-bf-"))
+			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, auth.googAPIKey)
 		} else if handlerStore.ShouldAllowDirectKeys() {
 			key := schemas.Key{

Based on learnings, virtual keys are always generated as sk-bf- + lowercase UUID and header extraction should not strip that prefix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/wsresponses.go` around lines 523 - 554, The
code is stripping the "sk-bf-" prefix before storing virtual keys; update the
three places that call ctx.SetValue(schemas.BifrostContextKeyVirtualKey,
strings.TrimPrefix(...)) so they store the full token (e.g., use the original
token string, not strings.TrimPrefix) for Authorization, apiKey and googAPIKey
paths; ensure the change is applied in the createBifrostContextFromAuth flow and
add a focused regression test that calls createBifrostContextFromAuth (or the
function that builds the context from auth) with "Bearer sk-bf-..." to assert
the context value for schemas.BifrostContextKeyVirtualKey equals the full
"sk-bf-..." string.
♻️ Duplicate comments (1)
transports/bifrost-http/handlers/realtime_turn_pipeline.go (1)

111-133: ⚠️ Potential issue | 🟡 Minor

Preserve user whitespace in the pre-hook request.

For ChatMessageRoleUser, this helper is still feeding the EI pre-hook pipeline with strings.TrimSpace(turnInput.Summary). That rewrites whitespace-significant prompts and drops whitespace-only turns before governance/logging see them. Normalize tool summaries if you want, but keep the original user text.

♻️ Proposed fix
 for _, turnInput := range turnInputs {
-	summary := strings.TrimSpace(turnInput.Summary)
-	if summary == "" {
-		continue
-	}
 	switch turnInput.Role {
 	case string(schemas.ChatMessageRoleTool):
+		summary := strings.TrimSpace(turnInput.Summary)
+		if summary == "" {
+			continue
+		}
 		itemType := schemas.ResponsesMessageTypeFunctionCallOutput
 		output := &schemas.ResponsesToolMessageOutputStruct{
 			ResponsesToolCallOutputStr: schemas.Ptr(summary),
 		}
 		input = append(input, schemas.ResponsesMessage{
@@
 	case string(schemas.ChatMessageRoleUser):
 		itemType := schemas.ResponsesMessageTypeMessage
 		role := schemas.ResponsesInputMessageRoleUser
 		input = append(input, schemas.ResponsesMessage{
 			Type:    &itemType,
 			Role:    &role,
-			Content: &schemas.ResponsesMessageContent{ContentStr: schemas.Ptr(summary)},
+			Content: &schemas.ResponsesMessageContent{ContentStr: schemas.Ptr(turnInput.Summary)},
 		})
 	}
 }

As per coding guidelines: "In the Bifrost HTTP handlers under transports/bifrost-http/handlers, implement minimal input validation for user-provided fields (e.g., prompts) and avoid trimming whitespace. Treat Bifrost as a passthrough gateway: forward inputs as-is to providers if accepted, and return provider rejections to the caller."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go` around lines 111
- 133, The code is trimming and dropping user messages by using
strings.TrimSpace(summary) and skipping empty summaries; change the logic to
only normalize/skip tool outputs while preserving user input verbatim: compute
trimmedSummary for tools only (use for ChatMessageRoleTool and the
empty-check/continue), but for ChatMessageRoleUser use turnInput.Summary
directly (do not TrimSpace or drop whitespace-only strings) when building the
ResponsesMessage (keep Role/Type as
ResponsesInputMessageRoleUser/ResponsesMessageTypeMessage). Ensure the branch
still normalizes tool summaries into ResponsesToolMessageOutputStruct but treats
user content as passthrough.
🧹 Nitpick comments (2)
transports/bifrost-http/handlers/realtime_logging.go (2)

1-10: Use sonic.Unmarshal for consistency with the rest of the file.

The extractRealtimeExtraParamString function uses encoding/json.Unmarshal at line 205, while the rest of the file consistently uses sonic.Unmarshal (lines 80, 165, 307, 399). Switching to sonic would allow removing the encoding/json import entirely.

♻️ Proposed fix
 import (
-	"encoding/json"
 	"strings"
 
 	"github.com/bytedance/sonic"
 	"github.com/maximhq/bifrost/core/schemas"
 	bfws "github.com/maximhq/bifrost/transports/bifrost-http/websocket"
 )
 func extractRealtimeExtraParamString(event *schemas.BifrostRealtimeEvent, key string) string {
 	if event == nil || event.ExtraParams == nil {
 		return ""
 	}
 	raw, ok := event.ExtraParams[key]
 	if !ok || len(raw) == 0 {
 		return ""
 	}
 	var value string
-	if err := json.Unmarshal(raw, &value); err != nil {
+	if err := sonic.Unmarshal(raw, &value); err != nil {
 		return ""
 	}
 	return strings.TrimSpace(value)
 }

As per coding guidelines: "Use github.com/bytedance/sonic for JSON marshaling in hot paths."

Also applies to: 196-209

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_logging.go` around lines 1 - 10,
The function extractRealtimeExtraParamString currently calls
encoding/json.Unmarshal; replace that with sonic.Unmarshal to match the rest of
the file (see other uses at lines with sonic.Unmarshal) and update the error
handling accordingly, then remove the now-unused "encoding/json" import from the
file; locate extractRealtimeExtraParamString to make this change and ensure
import list only includes "github.com/bytedance/sonic" plus existing imports.

112-138: Redundant nil check.

Line 124 has if event != nil && ... but event is already guaranteed to be non-nil at this point since the function returns early at line 113-115 if event == nil.

♻️ Proposed fix
 	switch event.Type {
 	case schemas.RTEventInputAudioTransCompleted:
 		if transcript := extractRealtimeExtraParamString(event, "transcript"); transcript != "" {
 			return transcript
 		}
 		return realtimeMissingTranscriptText
 	default:
-		if event != nil && event.Type == schemas.RTEventConversationItemDone && schemas.IsRealtimeUserInputEvent(event) {
+		if event.Type == schemas.RTEventConversationItemDone && schemas.IsRealtimeUserInputEvent(event) {
 			if summary := extractRealtimeItemSummary(event.Item); summary != "" {
 				return summary
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_logging.go` around lines 112 - 138,
The nil check on event inside finalizedRealtimeInputSummary is redundant because
the function already returns if event == nil; remove the `event != nil &&`
portion in the conditional that checks `event.Type ==
schemas.RTEventConversationItemDone && schemas.IsRealtimeUserInputEvent(event)`
so the clause simply tests those two conditions; update the conditional to use
`event.Type == schemas.RTEventConversationItemDone &&
schemas.IsRealtimeUserInputEvent(event)` (keep the rest of the logic: call
extractRealtimeItemSummary(event.Item) and
realtimeItemHasMissingAudioTranscript(event.Item) as before) to preserve
behavior and compilation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/governance/main.go`:
- Around line 370-376: The code currently treats only empty or
"application/json" as JSON; update the JSON detection around
lowerCT/isMultipart/isJSON to first strip any parameters (e.g., mediatype :=
strings.SplitN(lowerCT, ";", 2)[0]) and then set isJSON true not only when
mediatype == "" or strings.HasPrefix(mediatype, "application/json") but also
when the mediatype has the +json suffix (e.g., strings.HasPrefix(mediatype,
"application/") && strings.HasSuffix(mediatype, "+json")) so types like
application/vnd.openai+json are considered JSON before the early return that
skips governance.

In `@transports/bifrost-http/handlers/middlewares.go`:
- Around line 845-857: The tracer pointer is being loaded repeatedly which
allows SetTracer()/Store to swap the underlying tracer mid-request; change the
request-handling code (e.g., in methods like CreateTrace, StartSpan and any
request-scoped completion callbacks) to load the tracer once at the start
(tracer := m.tracer.Load()), return early if nil, and close over that local
variable for every subsequent tracer call and for all completion callbacks so
the same tracer instance is used for the whole request lifecycle.

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 139-168: The multipart branch currently treats an empty
sessionField as fatal; instead, change the logic in the multipart handling
(where firstMultipartValue(form.Value, "session") is read and
resolveRealtimeSDPTarget is called) so that if sessionField is empty you do not
return a 400 but allow execution to fall through to the existing ?model= parsing
path below; only call resolveRealtimeSDPTarget when sessionField is non-empty,
and keep the later usage of schemas.ParseModelString and
realtimeDefaultProviderForPath unchanged so multipart requests that provide sdp
and a model query param will be accepted.
- Around line 587-607: The handler currently calls r.close() immediately on
PeerConnectionStateDisconnected; change it to start a short grace timer (e.g.,
5–10s) when state == PeerConnectionStateDisconnected and only call r.close()
when that timer fires, but cancel/stop the timer if the connection later
transitions to PeerConnectionStateConnected or to terminal states
(PeerConnectionStateFailed/PeerConnectionStateClosed). Concretely: inside
handleState (the pc.OnConnectionStateChange callback) create and store a timer
(e.g., r.disconnectTimer *time.Timer) when Disconnected, ensure you stop and nil
that timer on Connected/Failed/Closed before taking other actions, and keep the
existing immediate behavior for Failed/Closed (sending upstreamConnectedOrError
and closing). Ensure timer access is synchronized (use existing mutex or add
one) to avoid races when stopping/clearing the timer.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Around line 240-276: The code records pending realtime tool/input updates into
the session before validating the event and before the duplicate-turn guard,
causing rejected events to remain in session; move the calls to
RecordRealtimeToolOutput and RecordRealtimeInput so they only run after the
event has passed validation and will be forwarded to the provider (i.e., after
ShouldStartRealtimeTurn duplicate-turn checks and after
provider.ToProviderRealtimeEvent returns no error), or only record them
conditionally when startsTurn is false and provider.ToProviderRealtimeEvent
succeeds; update usages of
pendingRealtimeToolOutputUpdate/pendingRealtimeInputUpdate accordingly so
startRealtimeTurnHooks and finalizeRealtimeTurnHooksWithError never see entries
from events that were rejected.
- Around line 360-405: The finalization branches (when event.Type ==
provider.RealtimeTurnFinalEvent() and when event.Error != nil handled by
finalizeRealtimeTurnHooksWithError) never consult
shouldGracefullyDisconnectRealtime, so the relay can keep accepting turns on a
session that should be terminal; after calling finalizeRealtimeTurnHooks or
finalizeRealtimeTurnHooksWithError (and after handling any returned
bifrostErr/finalizeErr), call shouldGracefullyDisconnectRealtime(providerKey,
event.Error) and if it returns true write the terminal realtime error to the
client via clientConn.writeRealtimeError (or the existing bifrostErr/finalizeErr
when present) and return nil to stop the loop instead of continuing to accept
messages; ensure this check happens before continuing to the message
encoding/WriteMessage path.

---

Outside diff comments:
In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 523-554: The code is stripping the "sk-bf-" prefix before storing
virtual keys; update the three places that call
ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(...)) so
they store the full token (e.g., use the original token string, not
strings.TrimPrefix) for Authorization, apiKey and googAPIKey paths; ensure the
change is applied in the createBifrostContextFromAuth flow and add a focused
regression test that calls createBifrostContextFromAuth (or the function that
builds the context from auth) with "Bearer sk-bf-..." to assert the context
value for schemas.BifrostContextKeyVirtualKey equals the full "sk-bf-..."
string.

---

Duplicate comments:
In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 111-133: The code is trimming and dropping user messages by using
strings.TrimSpace(summary) and skipping empty summaries; change the logic to
only normalize/skip tool outputs while preserving user input verbatim: compute
trimmedSummary for tools only (use for ChatMessageRoleTool and the
empty-check/continue), but for ChatMessageRoleUser use turnInput.Summary
directly (do not TrimSpace or drop whitespace-only strings) when building the
ResponsesMessage (keep Role/Type as
ResponsesInputMessageRoleUser/ResponsesMessageTypeMessage). Ensure the branch
still normalizes tool summaries into ResponsesToolMessageOutputStruct but treats
user content as passthrough.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/realtime_logging.go`:
- Around line 1-10: The function extractRealtimeExtraParamString currently calls
encoding/json.Unmarshal; replace that with sonic.Unmarshal to match the rest of
the file (see other uses at lines with sonic.Unmarshal) and update the error
handling accordingly, then remove the now-unused "encoding/json" import from the
file; locate extractRealtimeExtraParamString to make this change and ensure
import list only includes "github.com/bytedance/sonic" plus existing imports.
- Around line 112-138: The nil check on event inside
finalizedRealtimeInputSummary is redundant because the function already returns
if event == nil; remove the `event != nil &&` portion in the conditional that
checks `event.Type == schemas.RTEventConversationItemDone &&
schemas.IsRealtimeUserInputEvent(event)` so the clause simply tests those two
conditions; update the conditional to use `event.Type ==
schemas.RTEventConversationItemDone && schemas.IsRealtimeUserInputEvent(event)`
(keep the rest of the logic: call extractRealtimeItemSummary(event.Item) and
realtimeItemHasMissingAudioTranscript(event.Item) as before) to preserve
behavior and compilation.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20383a42-74be-4ab3-8364-c46257b3ce76

📥 Commits

Reviewing files that changed from the base of the PR and between dc4441a and 7ed9dd7.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (20)
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (2)
  • transports/go.mod
  • transports/bifrost-http/handlers/realtime_logging_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/wsresponses_test.go

Comment thread plugins/governance/main.go
Comment thread transports/bifrost-http/handlers/middlewares.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/wsrealtime.go
Comment thread transports/bifrost-http/handlers/wsrealtime.go
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from 0d37096 to bc98af8 Compare April 6, 2026 06:17
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from 7ed9dd7 to 29fc633 Compare April 6, 2026 06:17
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
plugins/governance/main.go (1)

50-60: ⚠️ Potential issue | 🟠 Major

Keep BaseGovernancePlugin backwards-compatible.

Adding EvaluateGovernanceRequest here makes the exported interface source-incompatible for any out-of-tree governance plugin implementation, while the new realtime client-secret path only needs this single capability. Prefer a separate narrow interface/type assertion in the realtime handler instead of widening the base plugin contract. Please verify whether any downstream/custom governance plugins implement BaseGovernancePlugin before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/governance/main.go` around lines 50 - 60, The BaseGovernancePlugin
interface was widened by adding EvaluateGovernanceRequest which breaks source
compatibility for existing out-of-tree plugins; revert that change and instead
define a new narrow interface (e.g., EvaluateGovernancePlugin) that declares
EvaluateGovernanceRequest, leave BaseGovernancePlugin unchanged, and update the
realtime client-secret path/handler to type-assert the plugin to
EvaluateGovernancePlugin and call EvaluateGovernanceRequest only when the
assertion succeeds; also check for and update any usages that assumed the new
method so downstream plugins remain source-compatible.
♻️ Duplicate comments (1)
transports/bifrost-http/handlers/realtime_turn_pipeline.go (1)

111-133: ⚠️ Potential issue | 🟡 Minor

Preserve buffered input whitespace in pre-hook requests.

strings.TrimSpace(turnInput.Summary) still normalizes the buffered turn before RunRealtimeTurnPreHooks, so whitespace-only user turns disappear. Only skip ""; leave everything else unchanged.

♻️ Proposed fix
 	for _, turnInput := range turnInputs {
-		summary := strings.TrimSpace(turnInput.Summary)
+		summary := turnInput.Summary
 		if summary == "" {
 			continue
 		}

As per coding guidelines: "implement minimal input validation for user-provided fields (e.g., prompts) and avoid trimming whitespace. Treat Bifrost as a passthrough gateway: forward inputs as-is to providers if accepted, and return provider rejections to the caller."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go` around lines 111
- 133, The code currently calls strings.TrimSpace on turnInput.Summary before
RunRealtimeTurnPreHooks, which removes whitespace-only user turns and violates
passthrough behavior; change the logic in the loop over turnInputs (where
turnInput.Summary is used to build schemas.ResponsesMessage /
ResponsesToolMessageOutputStruct) to only treat the exact empty string "" as
skip and otherwise use turnInput.Summary verbatim (no trimming) when
constructing message content or tool output; ensure this applies to both the
user branch (building ResponsesMessage with ContentStr) and the tool branch
(ResponsesToolCallOutputStr) so RunRealtimeTurnPreHooks and downstream providers
receive the original whitespace intact.
🧹 Nitpick comments (1)
transports/bifrost-http/handlers/realtime_logging.go (1)

196-208: Prefer sonic.Unmarshal in this per-event helper.

This helper sits on the realtime logging hot path and is the only spot in the file still using encoding/json; switching it keeps JSON handling consistent and lets you drop the extra import. Please verify there isn’t a deliberate reason this field needs the stdlib decoder.

♻️ Proposed cleanup
-import (
-	"encoding/json"
-	"strings"
+import (
+	"strings"

 	"github.com/bytedance/sonic"
 	"github.com/maximhq/bifrost/core/schemas"
 	bfws "github.com/maximhq/bifrost/transports/bifrost-http/websocket"
 )
@@
 	var value string
-	if err := json.Unmarshal(raw, &value); err != nil {
+	if err := sonic.Unmarshal(raw, &value); err != nil {
 		return ""
 	}
As per coding guidelines, `Use github.com/bytedance/sonic for JSON marshaling in hot paths; use standard encoding/json only when custom marshaling is needed.`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_logging.go` around lines 196 - 208,
In extractRealtimeExtraParamString, replace the use of encoding/json's
json.Unmarshal with github.com/bytedance/sonic.Unmarshal for decoding raw into
value (keep the same variable names: event, ExtraParams, raw, value) and remove
the encoding/json import; ensure the call signature and error handling remain
identical and verify there is no deliberate reason to keep the stdlib decoder
before committing the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/realtime_client_secrets_test.go`:
- Around line 243-249: The test builds an expiry JSON but ignores the computed
past timestamp (expired) and instead uses a hard-coded 0; update the test so the
JSON `expires_at` field uses the `expired` value (converted to string/number as
appropriate) when calling cacheRealtimeEphemeralKeyMapping, and remove the
unused placeholder `_ = expired`; this ensures cacheRealtimeEphemeralKeyMapping
is exercised with a nonzero past expiration.

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 1085-1091: Delete the unused helper function
realtimeSDPMaxMessageSize from
transports/bifrost-http/handlers/webrtc_realtime.go because it is dead code (the
codebase uses parseRealtimeSDPMaxMessageSize and
constrainRealtimeSDPMaxMessageSize instead); remove the entire func
realtimeSDPMaxMessageSize(...) definition and its body, run a build or go vet to
confirm no remaining references, and keep parseRealtimeSDPMaxMessageSize and
constrainRealtimeSDPMaxMessageSize intact.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Line 347: The loop currently short-circuits when event.RawData is empty by
writing the original upstream frame and returning, which bypasses the translated
Bifrost/OpenAI event encoding; instead remove the early return so that after
handling the empty RawData case you still call the translated-event encode/send
path (the code referenced around lines 447-452 that encodes/sends the translated
event) so terminal quota/rate-limit errors are encoded in provider-translated
format; ensure event.RawData branching only chooses which payload to base the
translation on but always proceeds to the translated write/encode routine (use
the same translated encoding function used in the 447-452 block and in the other
similar blocks at 422-443 and 457-473).
- Around line 291-306: The current error branch calls
finalizeRealtimeTurnHooksWithError(...) but ignores its returned error and
continues, which can leave the session in an inconsistent state; change the
handling so that if finalizeRealtimeTurnHooksWithError(...) returns a non-nil
error you propagate/handle it the same way as the normal
finalizeRealtimeTurnHooks failure path (i.e., stop processing the
connection/session—return from the enclosing handler to force a clean session
restart) instead of just continuing after clientConn.writeRealtimeError(...);
target the startsTurn branch where finalizeRealtimeTurnHooksWithError,
clientConn.writeRealtimeError, and the surrounding loop are used.

---

Outside diff comments:
In `@plugins/governance/main.go`:
- Around line 50-60: The BaseGovernancePlugin interface was widened by adding
EvaluateGovernanceRequest which breaks source compatibility for existing
out-of-tree plugins; revert that change and instead define a new narrow
interface (e.g., EvaluateGovernancePlugin) that declares
EvaluateGovernanceRequest, leave BaseGovernancePlugin unchanged, and update the
realtime client-secret path/handler to type-assert the plugin to
EvaluateGovernancePlugin and call EvaluateGovernanceRequest only when the
assertion succeeds; also check for and update any usages that assumed the new
method so downstream plugins remain source-compatible.

---

Duplicate comments:
In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 111-133: The code currently calls strings.TrimSpace on
turnInput.Summary before RunRealtimeTurnPreHooks, which removes whitespace-only
user turns and violates passthrough behavior; change the logic in the loop over
turnInputs (where turnInput.Summary is used to build schemas.ResponsesMessage /
ResponsesToolMessageOutputStruct) to only treat the exact empty string "" as
skip and otherwise use turnInput.Summary verbatim (no trimming) when
constructing message content or tool output; ensure this applies to both the
user branch (building ResponsesMessage with ContentStr) and the tool branch
(ResponsesToolCallOutputStr) so RunRealtimeTurnPreHooks and downstream providers
receive the original whitespace intact.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/realtime_logging.go`:
- Around line 196-208: In extractRealtimeExtraParamString, replace the use of
encoding/json's json.Unmarshal with github.com/bytedance/sonic.Unmarshal for
decoding raw into value (keep the same variable names: event, ExtraParams, raw,
value) and remove the encoding/json import; ensure the call signature and error
handling remain identical and verify there is no deliberate reason to keep the
stdlib decoder before committing the change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ffcbec79-f803-4f09-a46f-c911c34ae96e

📥 Commits

Reviewing files that changed from the base of the PR and between 7ed9dd7 and 29fc633.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (20)
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (4)
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/go.mod
  • transports/bifrost-http/handlers/middlewares.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/server/server.go

Comment thread transports/bifrost-http/handlers/realtime_client_secrets_test.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go Outdated
Comment thread transports/bifrost-http/handlers/wsrealtime.go
Comment thread transports/bifrost-http/handlers/wsrealtime.go
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from 29fc633 to c1558b5 Compare April 7, 2026 07:45
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from bc98af8 to 11cfa9e Compare April 7, 2026 07:45
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
transports/bifrost-http/handlers/middlewares.go (1)

840-863: Retain the active observability plugin set across SetTracer() swaps.

SetObservabilityPlugins() now updates only the current tracer. If SetTracer() installs a fresh tracer later, it starts without those plugins and exports stay disabled until another SetObservabilityPlugins() call. Caching the latest plugin slice on the middleware and replaying it in SetTracer() would make tracer hot-swaps and test replacements safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares.go` around lines 840 - 863, The
TracingMiddleware currently only applies observability plugins via
SetObservabilityPlugins to the tracer present at call time, so a later SetTracer
installs a new tracer without those plugins; modify TracingMiddleware to cache
the latest obsPlugins slice (add a field like cachedObsPlugins
[]schemas.ObservabilityPlugin or atomic.Pointer to the slice), update that cache
in SetObservabilityPlugins and still apply to the current tracer, and in
SetTracer replay/apply the cached plugins to the newly stored tracer (use the
existing m.tracer.Store(tracer) then call tracer.SetObservabilityPlugins(cached)
if cached is non-nil) so plugin state is retained across tracer swaps for
TracingMiddleware, SetObservabilityPlugins, and SetTracer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/governance/main.go`:
- Around line 50-53: The code uses raw string context keys "model" and "modelId"
(e.g., via ctx.Value/SetValue) which must be replaced with a dedicated named
context-key type; create a new typed key (e.g., type governanceCtxKey string)
and exported constants (e.g., governanceCtxKeyModel, governanceCtxKeyModelID)
then update all usages in BaseGovernancePlugin implementations and
applyRoutingRules() to use these typed keys instead of plain strings (search for
"model" and "modelId" usages around the BaseGovernancePlugin methods and the
applyRoutingRules function and replace Value/SetValue calls accordingly). Ensure
the new keys are used everywhere referenced (including the other file locations
noted) to avoid string collisions.

In `@transports/bifrost-http/handlers/realtime_logging.go`:
- Around line 159-177: The helper realtimeItemHasMissingAudioTranscript
currently only treats absent or nil transcript as missing; update it to also
treat empty-string transcripts as missing by asserting part["transcript"] as a
string and checking if strings.TrimSpace(transcript) == "" (or transcript == "")
before returning true, leaving other logic unchanged; then add a regression test
in transports/bifrost-http/handlers/realtime_logging_test.go that constructs a
RealtimeItem with an input_audio part whose "transcript" is "" and asserts the
code path produces the same placeholder/flag as the nil/absent case.

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 102-104: Update the top comment for the handleCallsRequest handler
to reflect the correct contract: state that the multipart branch strictly
requires both "sdp" and "session" parts (no optional session) and that the
"?model=" fallback only applies to the legacy raw-SDP path; reference the
handler name handleCallsRequest and the multipart vs raw-SDP branches so readers
know which code paths the comment is describing.
- Around line 892-896: When ToProviderRealtimeEvent translation fails (the call
r.provider.ToProviderRealtimeEvent(event) currently logs and returns), don't
drop the lifecycle event; instead log the error with contextual details and fall
back to forwarding the original upstream event so the client still receives
terminal/error lifecycle updates. Update the block around
r.provider.ToProviderRealtimeEvent and msg.Data so that on err you call
logger.Warn/Logger.Error with the error, set msg.Data to a safe fallback (e.g.,
the raw event bytes or a marshaled wrapper of event) and continue without
returning, ensuring lifecycle events reach the browser.
- Around line 62-74: The unprefixed legacy WebRTC routes are missing—after
registering the GA endpoint (r.POST("/v1/realtime/calls", handler)) and the
OpenAI-prefixed paths, call integrations.OpenAIRealtimePaths("") and iterate its
returned paths to set h.legacyRoutes[path] = schemas.OpenAI and r.POST(path,
handler) so the base legacy routes (e.g., "/v1/realtime", "/realtime") are
registered; alternatively, explicitly call r.POST for "/v1/realtime" and
"/realtime" and set h.legacyRoutes for those keys to ensure the legacy handler
is reachable.

In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 240-243: The current flow calls
h.client.SelectKeyForProviderRequestType before running RunStreamPreHooks and
returns early on error, which prevents hooks.ShortCircuitResponse from
short-circuiting valid requests; move the call to RunStreamPreHooks (and the
handling of hooks.ShortCircuitResponse) to occur before
h.client.SelectKeyForProviderRequestType in the ws responses native path, or
alternatively remove the terminal return in the SelectKeyForProviderRequestType
error branch so the HTTP bridge path can handle selection failures; update the
logic around h.client.SelectKeyForProviderRequestType, RunStreamPreHooks,
hooks.ShortCircuitResponse, and the writeWSError branch so pre-hooks always run
before key-selection is treated as terminal.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/middlewares.go`:
- Around line 840-863: The TracingMiddleware currently only applies
observability plugins via SetObservabilityPlugins to the tracer present at call
time, so a later SetTracer installs a new tracer without those plugins; modify
TracingMiddleware to cache the latest obsPlugins slice (add a field like
cachedObsPlugins []schemas.ObservabilityPlugin or atomic.Pointer to the slice),
update that cache in SetObservabilityPlugins and still apply to the current
tracer, and in SetTracer replay/apply the cached plugins to the newly stored
tracer (use the existing m.tracer.Store(tracer) then call
tracer.SetObservabilityPlugins(cached) if cached is non-nil) so plugin state is
retained across tracer swaps for TracingMiddleware, SetObservabilityPlugins, and
SetTracer.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a674e7df-f60a-424a-8adb-8e68e940bed6

📥 Commits

Reviewing files that changed from the base of the PR and between 29fc633 and c1558b5.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (20)
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (7)
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/go.mod
  • transports/bifrost-http/handlers/middlewares_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go

Comment thread plugins/governance/main.go
Comment thread transports/bifrost-http/handlers/realtime_logging.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go Outdated
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/wsresponses.go
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from 11cfa9e to 9af380f Compare April 7, 2026 09:09
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from c1558b5 to b25fc77 Compare April 7, 2026 09:09
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
plugins/governance/main.go (1)

1010-1021: ⚠️ Potential issue | 🟠 Major

Required-header enforcement now has a bypass.

The new realtime paths call EvaluateGovernanceRequest directly, but validateRequiredHeaders() still only runs from PreLLMHook and PreMCPHook. That means configured required_headers are skipped on those entry points. Move the header check into this shared evaluator, or expose a shared wrapper and make the realtime handlers call that instead.

🛠️ Minimal fix
 func (p *GovernancePlugin) EvaluateGovernanceRequest(ctx *schemas.BifrostContext, evaluationRequest *EvaluationRequest, requestType schemas.RequestType) (*EvaluationResult, *schemas.BifrostError) {
+	if headerErr := p.validateRequiredHeaders(ctx); headerErr != nil {
+		if ctx != nil {
+			ctx.SetValue(governanceRejectedContextKey, true)
+		}
+		return nil, headerErr
+	}
+
 	// Check if authentication is mandatory (either VK or user auth)

Then the explicit header checks in PreLLMHook and PreMCPHook can be removed to keep the policy in one place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/governance/main.go` around lines 1010 - 1021, Required header
enforcement is bypassed for realtime paths because validateRequiredHeaders() is
only called from PreLLMHook and PreMCPHook while realtime handlers call
EvaluateGovernanceRequest() directly; move the required-header check into the
shared evaluator by invoking validateRequiredHeaders (or inline its logic) from
EvaluateGovernanceRequest so every entry path runs the same check, then remove
the duplicated explicit header checks from PreLLMHook and PreMCPHook; update
EvaluateGovernanceRequest to return the same *schemas.BifrostError when headers
are missing and ensure callers (including realtime handlers) handle that error
consistently.
transports/bifrost-http/handlers/wsresponses.go (1)

521-554: ⚠️ Potential issue | 🟠 Major

Keep BifrostContextKeyVirtualKey in one canonical format.

x-bf-vk is stored verbatim, but Lines 526, 540, and 553 strip sk-bf- when the same credential arrives via Authorization, x-api-key, or x-goog-api-key. Downstream code reads BifrostContextKeyVirtualKey verbatim — for example, transports/bifrost-http/handlers/realtime_client_secrets.go:140-168 forwards it straight into governance evaluation and ephemeral-key mapping — so the value now depends on which header the client used. If the canonical form is supposed to be stripped, x-bf-vk needs the same normalization too; right now this is header-dependent.

💡 One consistent fix is to keep the full token everywhere
 	if auth.authorization != "" {
 		if strings.HasPrefix(auth.authorization, "Bearer ") {
 			token := strings.TrimPrefix(auth.authorization, "Bearer ")
 			if strings.HasPrefix(token, "sk-bf-") {
-				ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(token, "sk-bf-"))
+				ctx.SetValue(schemas.BifrostContextKeyVirtualKey, token)
 			} else if handlerStore.ShouldAllowDirectKeys() {
 				key := schemas.Key{
 					ID:     "header-provided",
 					Value:  *schemas.NewEnvVar(token),
 					Models: schemas.WhiteList{"*"},
@@
 	if auth.apiKey != "" {
 		if strings.HasPrefix(auth.apiKey, "sk-bf-") {
-			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(auth.apiKey, "sk-bf-"))
+			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, auth.apiKey)
 		} else if handlerStore.ShouldAllowDirectKeys() {
 			key := schemas.Key{
 				ID:     "header-provided",
 				Value:  *schemas.NewEnvVar(auth.apiKey),
 				Models: schemas.WhiteList{"*"},
@@
 	if auth.googAPIKey != "" {
 		if strings.HasPrefix(auth.googAPIKey, "sk-bf-") {
-			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, strings.TrimPrefix(auth.googAPIKey, "sk-bf-"))
+			ctx.SetValue(schemas.BifrostContextKeyVirtualKey, auth.googAPIKey)
 		} else if handlerStore.ShouldAllowDirectKeys() {
 			key := schemas.Key{
 				ID:     "header-provided",
 				Value:  *schemas.NewEnvVar(auth.googAPIKey),
 				Models: schemas.WhiteList{"*"},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/wsresponses.go` around lines 521 - 554, The
Bifrost virtual-key value is inconsistently normalized: remove the conditional
TrimPrefix calls and store the full token (including the "sk-bf-" prefix) into
the context everywhere so BifrostContextKeyVirtualKey is canonical regardless of
header. Concretely, in the handlers that inspect auth.authorization, auth.apiKey
and auth.googAPIKey (where you currently call strings.TrimPrefix(..., "sk-bf-")
before ctx.SetValue), change those ctx.SetValue calls to pass the original token
string (not the TrimPrefix result); keep the existing logic that detects the
"sk-bf-" prefix but do not strip it. This ensures
ctx.SetValue(schemas.BifrostContextKeyVirtualKey, <fullToken>) is used
consistently across Authorization, x-api-key and x-goog-api-key paths.
🧹 Nitpick comments (5)
transports/bifrost-http/handlers/middlewares_test.go (1)

441-474: Add the other OpenAI realtime aliases to the auth/path tests.

These tables only cover the /openai/v1/... minting paths. The router also exposes /openai/realtime/... and /openai/openai/realtime/..., so a prefix-matching regression on those aliases would still slip past both the negative helper test and the auth-required test.

🧪 Suggested additions
 	nonTransportPaths := []string{
 		"/v1/realtime/client_secrets",
 		"/v1/realtime/sessions",
 		"/openai/v1/realtime/client_secrets",
 		"/openai/v1/realtime/sessions",
+		"/openai/realtime/client_secrets",
+		"/openai/realtime/sessions",
+		"/openai/openai/realtime/client_secrets",
+		"/openai/openai/realtime/sessions",
 		"/v1/chat/completions",
 	}
@@
 	routes := []string{
 		"/v1/realtime",
 		"/openai/v1/realtime",
+		"/openai/realtime",
+		"/openai/openai/realtime",
 		"/v1/realtime/calls?model=gpt-realtime",
 		"/openai/v1/realtime/calls?model=gpt-realtime",
+		"/openai/realtime/calls?model=gpt-realtime",
+		"/openai/openai/realtime/calls?model=gpt-realtime",
 	}
@@
 	routes := []string{
 		"/v1/realtime/client_secrets",
 		"/v1/realtime/sessions",
 		"/openai/v1/realtime/client_secrets",
 		"/openai/v1/realtime/sessions",
+		"/openai/realtime/client_secrets",
+		"/openai/realtime/sessions",
+		"/openai/openai/realtime/client_secrets",
+		"/openai/openai/realtime/sessions",
 	}

Also applies to: 729-804

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares_test.go` around lines 441 - 474,
The TestIsRealtimeTransportEndpoint tests only include /openai/v1/... aliases;
update TestIsRealtimeTransportEndpoint to also assert positive matches for
/openai/realtime, /openai/openai/realtime and their /calls variants (e.g.,
/openai/realtime, /openai/openai/realtime, /openai/realtime/calls,
/openai/openai/realtime/calls) and add corresponding negative cases for
/client_secrets and /sessions under those aliases (e.g.,
/openai/realtime/client_secrets, /openai/openai/realtime/sessions) so that
isRealtimeTransportEndpoint is exercised for all router aliases; also mirror
these additions in the related auth/path test(s) referenced around lines 729-804
(the auth-required tests) so the same alias coverage is enforced there.
transports/bifrost-http/handlers/realtime_logging.go (1)

200-213: Consider using sonic for consistency.

This function uses encoding/json while the rest of the file uses github.com/bytedance/sonic. While this isn't a hot path, using sonic would maintain consistency.

♻️ Optional: Use sonic for consistency
 func extractRealtimeExtraParamString(event *schemas.BifrostRealtimeEvent, key string) string {
 	if event == nil || event.ExtraParams == nil {
 		return ""
 	}
 	raw, ok := event.ExtraParams[key]
 	if !ok || len(raw) == 0 {
 		return ""
 	}
 	var value string
-	if err := json.Unmarshal(raw, &value); err != nil {
+	if err := sonic.Unmarshal(raw, &value); err != nil {
 		return ""
 	}
 	return strings.TrimSpace(value)
 }

As per coding guidelines: "Use github.com/bytedance/sonic for JSON marshaling in hot paths; use standard encoding/json only when custom marshaling is needed."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_logging.go` around lines 200 - 213,
The function extractRealtimeExtraParamString uses encoding/json; replace the
json.Unmarshal call with github.com/bytedance/sonic's Unmarshal to keep JSON
handling consistent with the rest of the file: update imports to include
"github.com/bytedance/sonic" (and remove "encoding/json" if present), call
sonic.Unmarshal(raw, &value) and keep the same error handling and trimming logic
in the function to preserve behavior.
transports/bifrost-http/handlers/wsrealtime.go (1)

163-170: Consider debug logging when ephemeral key mapping lookup fails.

When isRealtimeEphemeralToken(token) is true but lookupRealtimeEphemeralKeyMapping fails, the code silently continues. While downstream auth will naturally fail, a debug-level log here could aid troubleshooting expired or invalid ephemeral tokens.

🔧 Optional enhancement
 	token := extractRealtimeBearerTokenFromHeader(auth.authorization)
 	if isRealtimeEphemeralToken(token) {
 		mapping, ok := lookupRealtimeEphemeralKeyMapping(h.handlerStore.GetKVStore(), token)
 		if ok {
 			applyRealtimeEphemeralKeyMapping(bifrostCtx, mapping)
+		} else {
+			logger.Debug("ephemeral key mapping not found for realtime token")
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/wsrealtime.go` around lines 163 - 170, When
isRealtimeEphemeralToken(token) returns true but
lookupRealtimeEphemeralKeyMapping(...) returns ok == false, add a debug-level
log entry (including the token or a safe redaction and any relevant context like
auth.authorization, bifrostCtx or the KV store identifier) to make
expired/invalid ephemeral token lookups visible; retain the existing call to
applyRealtimeEphemeralKeyMapping(...) when ok is true. Locate the token
extraction and lookup around
extractRealtimeBearerTokenFromHeader(auth.authorization),
lookupRealtimeEphemeralKeyMapping(h.handlerStore.GetKVStore(), token), and
applyRealtimeEphemeralKeyMapping(bifrostCtx, mapping) and insert the debug
logging in the branch where ok == false.
transports/bifrost-http/handlers/realtime_turn_pipeline.go (2)

474-481: Minor: Error message style inconsistency.

Line 479 uses a capitalized sentence with trailing period ("Conversation already has an active response in progress.") while other error messages use lowercase without punctuation (e.g., line 469: "realtime turn pipeline is unavailable"). Consider aligning to a consistent style.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go` around lines 474
- 481, The error message in the Bifrost error return (constructing
schemas.BifrostError / ErrorField in the realtime_turn_pipeline.go handler) is
inconsistent with other messages — change "Conversation already has an active
response in progress." to match the lowercase, no-punctuation style used
elsewhere (e.g., "realtime turn pipeline is unavailable"); update the
ErrorField.Message string to a lowercase phrase without trailing punctuation to
keep style consistent.

109-145: Pre-request builder correctly maps turn inputs.

The function appropriately handles user messages and tool outputs for the pre-hook request. One optional enhancement: consider including turnInput.ItemID as the CallID for function_call_output messages to improve tool result tracking in hooks.

♻️ Optional enhancement for tool tracking
 case string(schemas.ChatMessageRoleTool):
     itemType := schemas.ResponsesMessageTypeFunctionCallOutput
     output := &schemas.ResponsesToolMessageOutputStruct{
         ResponsesToolCallOutputStr: schemas.Ptr(summary),
     }
-    input = append(input, schemas.ResponsesMessage{
+    msg := schemas.ResponsesMessage{
         Type:                 &itemType,
         ResponsesToolMessage: &schemas.ResponsesToolMessage{Output: output},
-    })
+    }
+    if strings.TrimSpace(turnInput.ItemID) != "" {
+        msg.CallID = schemas.Ptr(strings.TrimSpace(turnInput.ItemID))
+    }
+    input = append(input, msg)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go` around lines 109
- 145, buildRealtimeTurnPreRequest currently maps tool outputs into
ResponsesToolMessageOutputStruct but does not set a call identifier; update the
function so that when handling a tool output (case
string(schemas.ChatMessageRoleTool)) it assigns turnInput.ItemID to the
appropriate CallID field on the tool message structure (attach ItemID to the
ResponsesToolMessage / ResponsesToolMessageOutputStruct used for
ResponsesMessageTypeFunctionCallOutput) so hooks can track tool results; locate
the tool-output branch in buildRealtimeTurnPreRequest and set the CallID from
turnInput.ItemID before appending the ResponsesMessage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/governance/main.go`:
- Around line 50-52: You widened the exported BaseGovernancePlugin by adding
EvaluateGovernanceRequest which is a breaking API change; revert
BaseGovernancePlugin to its original narrow surface (remove
EvaluateGovernanceRequest) and instead introduce a new interface (e.g.,
RealtimeGovernanceEvaluator or GovernanceEvaluator) that declares
EvaluateGovernanceRequest, then change internal consumers that need the
evaluation capability to depend on the new interface (replace usages expecting
EvaluateGovernanceRequest with the new interface) while leaving external-facing
BaseGovernancePlugin unchanged (so existing implementors/tests are not broken).

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 1045-1066: newRealtimeRelayContext currently copies a set of
governance context keys but omits the enterprise business-unit keys, causing
BifrostContextKeyGovernanceBusinessUnitID and
BifrostContextKeyGovernanceBusinessUnitName to be lost when the relay context
detaches; update the key list in newRealtimeRelayContext to include
schemas.BifrostContextKeyGovernanceBusinessUnitID and
schemas.BifrostContextKeyGovernanceBusinessUnitName so those values are
preserved in the detached relay context.
- Around line 152-170: The parseCallsWebRTCRequest currently accepts raw SDP +
?model= on /realtime/calls which re-enables the legacy fallback; update
parseCallsWebRTCRequest to reject non-multipart raw-SDP when the route is the
calls route (i.e., detect the calls path via the existing path parameter or
realtimeDefaultProviderForPath(path) logic) and return a BadRequest
realtimeWebRTCError indicating multipart /calls must include a session (or that
raw-SDP fallback is not allowed) instead of proceeding to parse provider/model;
keep existing behavior for other realtime routes (still parse rawModel ->
providerKey, model using schemas.ParseModelString) but for calls force
multipart-only handling.

---

Outside diff comments:
In `@plugins/governance/main.go`:
- Around line 1010-1021: Required header enforcement is bypassed for realtime
paths because validateRequiredHeaders() is only called from PreLLMHook and
PreMCPHook while realtime handlers call EvaluateGovernanceRequest() directly;
move the required-header check into the shared evaluator by invoking
validateRequiredHeaders (or inline its logic) from EvaluateGovernanceRequest so
every entry path runs the same check, then remove the duplicated explicit header
checks from PreLLMHook and PreMCPHook; update EvaluateGovernanceRequest to
return the same *schemas.BifrostError when headers are missing and ensure
callers (including realtime handlers) handle that error consistently.

In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 521-554: The Bifrost virtual-key value is inconsistently
normalized: remove the conditional TrimPrefix calls and store the full token
(including the "sk-bf-" prefix) into the context everywhere so
BifrostContextKeyVirtualKey is canonical regardless of header. Concretely, in
the handlers that inspect auth.authorization, auth.apiKey and auth.googAPIKey
(where you currently call strings.TrimPrefix(..., "sk-bf-") before
ctx.SetValue), change those ctx.SetValue calls to pass the original token string
(not the TrimPrefix result); keep the existing logic that detects the "sk-bf-"
prefix but do not strip it. This ensures
ctx.SetValue(schemas.BifrostContextKeyVirtualKey, <fullToken>) is used
consistently across Authorization, x-api-key and x-goog-api-key paths.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/middlewares_test.go`:
- Around line 441-474: The TestIsRealtimeTransportEndpoint tests only include
/openai/v1/... aliases; update TestIsRealtimeTransportEndpoint to also assert
positive matches for /openai/realtime, /openai/openai/realtime and their /calls
variants (e.g., /openai/realtime, /openai/openai/realtime,
/openai/realtime/calls, /openai/openai/realtime/calls) and add corresponding
negative cases for /client_secrets and /sessions under those aliases (e.g.,
/openai/realtime/client_secrets, /openai/openai/realtime/sessions) so that
isRealtimeTransportEndpoint is exercised for all router aliases; also mirror
these additions in the related auth/path test(s) referenced around lines 729-804
(the auth-required tests) so the same alias coverage is enforced there.

In `@transports/bifrost-http/handlers/realtime_logging.go`:
- Around line 200-213: The function extractRealtimeExtraParamString uses
encoding/json; replace the json.Unmarshal call with github.com/bytedance/sonic's
Unmarshal to keep JSON handling consistent with the rest of the file: update
imports to include "github.com/bytedance/sonic" (and remove "encoding/json" if
present), call sonic.Unmarshal(raw, &value) and keep the same error handling and
trimming logic in the function to preserve behavior.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 474-481: The error message in the Bifrost error return
(constructing schemas.BifrostError / ErrorField in the realtime_turn_pipeline.go
handler) is inconsistent with other messages — change "Conversation already has
an active response in progress." to match the lowercase, no-punctuation style
used elsewhere (e.g., "realtime turn pipeline is unavailable"); update the
ErrorField.Message string to a lowercase phrase without trailing punctuation to
keep style consistent.
- Around line 109-145: buildRealtimeTurnPreRequest currently maps tool outputs
into ResponsesToolMessageOutputStruct but does not set a call identifier; update
the function so that when handling a tool output (case
string(schemas.ChatMessageRoleTool)) it assigns turnInput.ItemID to the
appropriate CallID field on the tool message structure (attach ItemID to the
ResponsesToolMessage / ResponsesToolMessageOutputStruct used for
ResponsesMessageTypeFunctionCallOutput) so hooks can track tool results; locate
the tool-output branch in buildRealtimeTurnPreRequest and set the CallID from
turnInput.ItemID before appending the ResponsesMessage.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Around line 163-170: When isRealtimeEphemeralToken(token) returns true but
lookupRealtimeEphemeralKeyMapping(...) returns ok == false, add a debug-level
log entry (including the token or a safe redaction and any relevant context like
auth.authorization, bifrostCtx or the KV store identifier) to make
expired/invalid ephemeral token lookups visible; retain the existing call to
applyRealtimeEphemeralKeyMapping(...) when ok is true. Locate the token
extraction and lookup around
extractRealtimeBearerTokenFromHeader(auth.authorization),
lookupRealtimeEphemeralKeyMapping(h.handlerStore.GetKVStore(), token), and
applyRealtimeEphemeralKeyMapping(bifrostCtx, mapping) and insert the debug
logging in the branch where ok == false.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8666a70d-bc05-4f8f-9fe1-2814aa7ea31a

📥 Commits

Reviewing files that changed from the base of the PR and between c1558b5 and b25fc77.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (20)
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (6)
  • transports/go.mod
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go

Comment thread plugins/governance/main.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from b25fc77 to 5b980ac Compare April 7, 2026 10:59
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from 9af380f to 12565cc Compare April 7, 2026 10:59
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
plugins/governance/main.go (1)

1021-1153: ⚠️ Potential issue | 🔴 Critical

Add validateRequiredHeaders check before calling EvaluateGovernanceRequest in the realtime client-secrets handler.

The realtime client-secrets handler calls EvaluateGovernanceRequest without first validating required headers (line 161 in realtime_client_secrets.go). Both PreLLMHook and PreMCPHook enforce validateRequiredHeaders before invoking EvaluateGovernanceRequest; the realtime handler should follow the same pattern to prevent configured requiredHeaders from being bypassed.

Evidence
realtime_client_secrets.go:161
_, bifrostErr := governancePlugin.EvaluateGovernanceRequest(bifrostCtx, ...)
// ^ No validateRequiredHeaders call in evaluateMintingGovernance

PreLLMHook (plugins/governance/main.go:1217)
if headerErr := p.validateRequiredHeaders(ctx); headerErr != nil { ... }
_, bifrostError := p.EvaluateGovernanceRequest(ctx, evaluationRequest, req.RequestType)

PreMCPHook (plugins/governance/main.go:1317)
if headerErr := p.validateRequiredHeaders(ctx); headerErr != nil { ... }
_, bifrostError := p.EvaluateGovernanceRequest(ctx, evaluationRequest, schemas.MCPToolExecutionRequest)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/governance/main.go` around lines 1021 - 1153, The realtime
client-secrets handler (evaluateMintingGovernance) calls
EvaluateGovernanceRequest without checking required headers; add a call to
p.validateRequiredHeaders(ctx) immediately before invoking
EvaluateGovernanceRequest, mirror the behavior in PreLLMHook/PreMCPHook by
returning the header error (bifrost error) if validateRequiredHeaders returns
non-nil, and only proceed to call EvaluateGovernanceRequest when header
validation passes.
♻️ Duplicate comments (1)
transports/bifrost-http/handlers/realtime_turn_pipeline.go (1)

112-133: ⚠️ Potential issue | 🟡 Minor

Preserve realtime turn input for pre-hooks (don’t trim/drop whitespace).

At Line 112, strings.TrimSpace(turnInput.Summary) plus the Line 113 empty-check rewrites input semantics and drops whitespace-only turns before pre-hooks see them.

Suggested fix
-		summary := strings.TrimSpace(turnInput.Summary)
-		if summary == "" {
+		summary := turnInput.Summary
+		if summary == "" {
 			continue
 		}

As per coding guidelines: "In the Bifrost HTTP handlers under transports/bifrost-http/handlers, implement minimal input validation for user-provided fields (e.g., prompts) and avoid trimming whitespace."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go` around lines 112
- 133, The code currently trims and drops whitespace-only turnInput.Summary;
instead preserve the original input for pre-hooks by assigning summary :=
turnInput.Summary (no strings.TrimSpace) and remove the subsequent if summary ==
"" continue check so whitespace-only summaries are not dropped; keep the
existing switch handling that builds schemas.ResponsesMessage (referencing
turnInput.Summary, schemas.ResponsesMessage, schemas.ResponsesMessageContent,
schemas.ResponsesToolMessageOutputStruct, etc.) so pre-hooks receive the exact
raw summary.
🧹 Nitpick comments (1)
transports/bifrost-http/handlers/logging.go (1)

156-196: Extract the redaction hydration into a shared helper.

This is now the same selected-key / virtual-key / routing-rule enrichment block that getLogs already carries. Pulling it into one helper will keep deleted-object fallback and future field additions from drifting between the list and session views.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/logging.go` around lines 156 - 196, Extract
the repeated enrichment logic into a shared helper (e.g.,
hydrateRedactedReferences or EnrichLogsWithRedactedNames) that accepts context
and the slice of logs and performs the three-step process currently in this
block: build selectedKeyIDs/virtualKeyIDs/routingRuleIDs maps, call
h.redactedKeysManager.GetAllRedactedKeys, GetAllRedactedVirtualKeys,
GetAllRedactedRoutingRules, then iterate logs and call findRedactedKey,
findRedactedVirtualKey, findRedactedRoutingRule to set
SelectedKey/VirtualKey/RoutingRule while preserving the existing pointer/nil
checks and deleted-object fallback behavior; replace the inline code in this
handler with a call to that helper and reuse it from getLogs so both places use
the same logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/realtime_client_secrets.go`:
- Line 83: The payload is being forwarded with the pre-key-selection model
string so providers may receive an alias; after resolving the key via
SelectKeyForProviderRequestType (and/or using providerKey.Aliases returned by
resolveRealtimeClientSecretTarget) you must remap the model alias to the
canonical model and re-normalize the body before sending the mint request.
Concretely: in the realtime_client_secrets flow, after you obtain providerKey
(from resolveRealtimeClientSecretTarget/SelectKeyForProviderRequestType and any
key.Aliases), update the model variable to the resolved/canonical model from the
selected key and re-run whatever normalization logic produced normalizedBody so
the outbound payload uses the resolved model string (apply same steps for the
other occurrence range 119-136).

In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 1045-1066: The detached relay context in newRealtimeRelayContext
is copying most governance keys but omits the governance IncludeOnlyKeys and
PluginName fields; update the key propagation loop (the one iterating over
schemas.BifrostContextKeyRequestID, ... ,
schemas.BifrostContextKeyGovernanceUserID) to also include
schemas.BifrostContextKeyGovernanceIncludeOnlyKeys and
schemas.BifrostContextKeyGovernancePluginName so the detached relay retains full
governance scoping for hooks and logging.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Around line 493-496: The branch that handles non-empty deploymentParam calls
schemas.ParseModelString but returns errRealtimeDeploymentFormat only when both
provider or model are blank; update it to mirror the model branch by returning
errRealtimeDeploymentFormat when ParseModelString yields an empty provider or an
empty model (strings.TrimSpace(model) == ""), so the error message correctly
requires provider/model format for invalid deployment values — adjust the checks
around deploymentParam, provider, and model in the wsrealtime handler (where
deploymentParam, schemas.ParseModelString, and errRealtimeDeploymentFormat are
used) and apply the same fix to the other occurrence at the later block (lines
around the second ParseModelString usage).

In `@transports/bifrost-http/server/server.go`:
- Around line 1467-1470: IntegrationHandler.Close() only snapshot-closes
sessions but native relay goroutines may still be running and can call into
s.wsPool after you call s.wsPool.Close(); change the integration shutdown path
to wait for active relays to drain before closing the shared WS pool: add a
synchronous drain/wait step (e.g. expose a Drain/Wait method on the
IntegrationHandler or SessionManager that uses a sync.WaitGroup or channel to
wait for Session.Close() / native relay goroutines to finish) and call that
drain method after IntegrationHandler.Close() and before s.wsPool.Close(); apply
the same pattern to the other integration close sites (the signal-path cleanup
referenced around the other spots) so all code closes IntegrationHandler, waits
for drain, then shuts down s.wsPool/Shutdown().

---

Outside diff comments:
In `@plugins/governance/main.go`:
- Around line 1021-1153: The realtime client-secrets handler
(evaluateMintingGovernance) calls EvaluateGovernanceRequest without checking
required headers; add a call to p.validateRequiredHeaders(ctx) immediately
before invoking EvaluateGovernanceRequest, mirror the behavior in
PreLLMHook/PreMCPHook by returning the header error (bifrost error) if
validateRequiredHeaders returns non-nil, and only proceed to call
EvaluateGovernanceRequest when header validation passes.

---

Duplicate comments:
In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 112-133: The code currently trims and drops whitespace-only
turnInput.Summary; instead preserve the original input for pre-hooks by
assigning summary := turnInput.Summary (no strings.TrimSpace) and remove the
subsequent if summary == "" continue check so whitespace-only summaries are not
dropped; keep the existing switch handling that builds schemas.ResponsesMessage
(referencing turnInput.Summary, schemas.ResponsesMessage,
schemas.ResponsesMessageContent, schemas.ResponsesToolMessageOutputStruct, etc.)
so pre-hooks receive the exact raw summary.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/logging.go`:
- Around line 156-196: Extract the repeated enrichment logic into a shared
helper (e.g., hydrateRedactedReferences or EnrichLogsWithRedactedNames) that
accepts context and the slice of logs and performs the three-step process
currently in this block: build selectedKeyIDs/virtualKeyIDs/routingRuleIDs maps,
call h.redactedKeysManager.GetAllRedactedKeys, GetAllRedactedVirtualKeys,
GetAllRedactedRoutingRules, then iterate logs and call findRedactedKey,
findRedactedVirtualKey, findRedactedRoutingRule to set
SelectedKey/VirtualKey/RoutingRule while preserving the existing pointer/nil
checks and deleted-object fallback behavior; replace the inline code in this
handler with a call to that helper and reuse it from getLogs so both places use
the same logic.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 41785829-c71d-4f6b-9616-768eb663472a

📥 Commits

Reviewing files that changed from the base of the PR and between b25fc77 and 5b980ac.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (20)
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (2)
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • transports/bifrost-http/handlers/devpprof.go
  • transports/go.mod
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go

Comment thread transports/bifrost-http/handlers/realtime_client_secrets.go
Comment thread transports/bifrost-http/handlers/webrtc_realtime.go
Comment thread transports/bifrost-http/handlers/wsrealtime.go
Comment thread transports/bifrost-http/server/server.go
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch from 5b980ac to 5e43323 Compare April 7, 2026 12:33
@danpiths danpiths force-pushed the feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers branch from 12565cc to 73e51ea Compare April 7, 2026 12:33
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
transports/bifrost-http/handlers/webrtc_realtime.go (1)

102-106: ⚠️ Potential issue | 🟡 Minor

Comment contradicts the actual /calls fallback behavior.

These lines say raw-SDP ?model= fallback is legacy-only, but parseCallsWebRTCRequest (Lines 152-170) still supports raw-SDP + ?model= for /v1/realtime/calls. Please align the comment to current behavior to avoid future regressions.

Suggested doc fix
-// Raw SDP bodies (application/sdp) fall back to ?model= for the legacy
-// raw-SDP path only; the multipart contract has no ?model= fallback.
+// Non-multipart raw SDP bodies are also accepted with a `?model=` fallback
+// (Bifrost extension), while multipart `/calls` requires both `sdp` and
+// `session` form fields.

Based on learnings: parseCallsWebRTCRequest intentionally supports both multipart and raw-SDP + ?model= on /v1/realtime/calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/webrtc_realtime.go` around lines 102 - 106,
The top-of-file comment incorrectly states that raw-SDP bodies fall back to
?model= only for a legacy path, but the code in parseCallsWebRTCRequest (used by
/v1/realtime/calls) still supports raw-SDP + ?model=; update the comment in
webrtc_realtime.go to reflect the current behavior: multipart bodies require
both "sdp" and "session" form fields and read model from session.model, while
raw application/sdp requests for /v1/realtime/calls also support the ?model=
fallback (i.e., both multipart and raw-SDP+?model are supported by
parseCallsWebRTCRequest).
🧹 Nitpick comments (3)
transports/bifrost-http/handlers/logging.go (1)

156-195: Extract the log-reference hydration into a shared helper.

This block is now a near-copy of the getLogs enrichment path: collect referenced IDs, fetch redacted objects, then attach SelectedKey, VirtualKey, and RoutingRule. Keeping that in one helper will make the session view and the list view much harder to accidentally drift apart.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/logging.go` around lines 156 - 195, Extract
the repeated "collect IDs, fetch redacted objects, attach
SelectedKey/VirtualKey/RoutingRule" logic into a single helper (e.g.,
hydrateLogReferences or hydrateLogs) and call it from both this handler and the
existing getLogs enrichment path; the helper should accept context and the slice
of logs (same type as result.Logs), build maps for
SelectedKeyID/VirtualKeyID/RoutingRuleID, call
h.redactedKeysManager.GetAllRedactedKeys, GetAllRedactedVirtualKeys,
GetAllRedactedRoutingRules, then loop logs and use findRedactedKey,
findRedactedVirtualKey, and findRedactedRoutingRule to set SelectedKey,
VirtualKey, and RoutingRule, preserving the existing nil/pointer checks and
string-empty guards so behavior remains identical.
transports/bifrost-http/handlers/wsresponses.go (1)

521-562: Extract the virtual/direct-key normalization into one helper.

Authorization, x-api-key, and x-goog-api-key now all encode the same sk-bf- vs direct-key split and the same header-provided key construction. Pulling that into a small helper will make precedence changes and future auth-source additions much less likely to drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/wsresponses.go` around lines 521 - 562,
Refactor the repeated logic that handles auth.authorization, auth.apiKey, and
auth.googAPIKey into a single helper (e.g., normalizeAndSetAuthKey or
setAuthKeyFromToken) that accepts the raw token string, the context (ctx), and
handlerStore; inside the helper check strings.HasPrefix(token,"sk-bf-") to set
schemas.BifrostContextKeyVirtualKey with strings.TrimPrefix(token,"sk-bf-") or,
if handlerStore.ShouldAllowDirectKeys(), construct the schemas.Key (ID:
"header-provided", Value: *schemas.NewEnvVar(token), Models:
schemas.WhiteList{"*"}, Weight: 1.0) and set schemas.BifrostContextKeyDirectKey;
then replace the three duplicated blocks that reference auth.authorization,
auth.apiKey, and auth.googAPIKey with calls to this helper (for Authorization
first pass the trimmed Bearer token) to preserve precedence and centralize
behavior.
transports/bifrost-http/handlers/realtime_turn_pipeline.go (1)

674-675: Minor: Redundant ExtraFields assignment.

Line 675 is redundant since line 674's struct copy already copies the ExtraFields value. This doesn't cause any bug (both lines produce the same result), but could be removed for clarity:

 copied := *bifrostErr
-copied.ExtraFields = bifrostErr.ExtraFields
 if bifrostErr.Error != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go` around lines 674
- 675, The code creates a struct copy with "copied := *bifrostErr" and then
redundantly assigns "copied.ExtraFields = bifrostErr.ExtraFields"; remove the
second assignment to avoid duplication. Locate the assignment involving "copied"
and "ExtraFields" (around the use of "bifrostErr" and "copied" in
realtime_turn_pipeline.go) and delete the redundant "copied.ExtraFields =
bifrostErr.ExtraFields" line so the struct copy alone provides the same data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 712-722: The loop over values in realtime_turn_pipeline.go
currently lets later header values override earlier classifications (using
variables values/value/lower to set statusCode), which diverges from
mapRealtimeWireErrorFields' first-match-wins behavior; update the loop in the
function that inspects realtimeErr.Type/realtimeErr.Message so that once you set
statusCode for a matched case (e.g., invalid_request_error → 400 or rate
limit/billing → 429) you immediately stop further processing (either break the
loop or return the mapped status), ensuring consistency with
mapRealtimeWireErrorFields.

In `@transports/bifrost-http/handlers/wsrealtime.go`:
- Around line 415-456: The code records the upstream-native input frame via
session.RecordRealtimeInput(inputItemID, inputSummary, string(message)) before
message may be rewritten for translated providers by
provider.ToProviderRealtimeEvent, causing hooks (finalizeRealtimeTurnHooks /
realtime logging that uses combineRealtimeInputRaw -> ExtraFields.RawRequest) to
see the wrong payload; fix by moving the RecordRealtimeInput call to after the
event.RawData rewrite (i.e., after the block that calls
provider.ToProviderRealtimeEvent and updates message) or change
RecordRealtimeInput to accept the rewritten bytes so the buffered/raw request
used by finalizeRealtimeTurnHooks and combineRealtimeInputRaw is the
translated/client-facing frame rather than the upstream-native one.

---

Duplicate comments:
In `@transports/bifrost-http/handlers/webrtc_realtime.go`:
- Around line 102-106: The top-of-file comment incorrectly states that raw-SDP
bodies fall back to ?model= only for a legacy path, but the code in
parseCallsWebRTCRequest (used by /v1/realtime/calls) still supports raw-SDP +
?model=; update the comment in webrtc_realtime.go to reflect the current
behavior: multipart bodies require both "sdp" and "session" form fields and read
model from session.model, while raw application/sdp requests for
/v1/realtime/calls also support the ?model= fallback (i.e., both multipart and
raw-SDP+?model are supported by parseCallsWebRTCRequest).

---

Nitpick comments:
In `@transports/bifrost-http/handlers/logging.go`:
- Around line 156-195: Extract the repeated "collect IDs, fetch redacted
objects, attach SelectedKey/VirtualKey/RoutingRule" logic into a single helper
(e.g., hydrateLogReferences or hydrateLogs) and call it from both this handler
and the existing getLogs enrichment path; the helper should accept context and
the slice of logs (same type as result.Logs), build maps for
SelectedKeyID/VirtualKeyID/RoutingRuleID, call
h.redactedKeysManager.GetAllRedactedKeys, GetAllRedactedVirtualKeys,
GetAllRedactedRoutingRules, then loop logs and use findRedactedKey,
findRedactedVirtualKey, and findRedactedRoutingRule to set SelectedKey,
VirtualKey, and RoutingRule, preserving the existing nil/pointer checks and
string-empty guards so behavior remains identical.

In `@transports/bifrost-http/handlers/realtime_turn_pipeline.go`:
- Around line 674-675: The code creates a struct copy with "copied :=
*bifrostErr" and then redundantly assigns "copied.ExtraFields =
bifrostErr.ExtraFields"; remove the second assignment to avoid duplication.
Locate the assignment involving "copied" and "ExtraFields" (around the use of
"bifrostErr" and "copied" in realtime_turn_pipeline.go) and delete the redundant
"copied.ExtraFields = bifrostErr.ExtraFields" line so the struct copy alone
provides the same data.

In `@transports/bifrost-http/handlers/wsresponses.go`:
- Around line 521-562: Refactor the repeated logic that handles
auth.authorization, auth.apiKey, and auth.googAPIKey into a single helper (e.g.,
normalizeAndSetAuthKey or setAuthKeyFromToken) that accepts the raw token
string, the context (ctx), and handlerStore; inside the helper check
strings.HasPrefix(token,"sk-bf-") to set schemas.BifrostContextKeyVirtualKey
with strings.TrimPrefix(token,"sk-bf-") or, if
handlerStore.ShouldAllowDirectKeys(), construct the schemas.Key (ID:
"header-provided", Value: *schemas.NewEnvVar(token), Models:
schemas.WhiteList{"*"}, Weight: 1.0) and set schemas.BifrostContextKeyDirectKey;
then replace the three duplicated blocks that reference auth.authorization,
auth.apiKey, and auth.googAPIKey with calls to this helper (for Authorization
first pass the trimmed Bearer token) to preserve precedence and centralize
behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c26fa0c2-b6fc-4a29-ad49-e0718540629c

📥 Commits

Reviewing files that changed from the base of the PR and between 5b980ac and 5e43323.

⛔ Files ignored due to path filters (1)
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (20)
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go
  • transports/bifrost-http/handlers/realtime_client_secrets_test.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_logging_test.go
  • transports/bifrost-http/handlers/realtime_turn_pipeline.go
  • transports/bifrost-http/handlers/webrtc_realtime.go
  • transports/bifrost-http/handlers/webrtc_realtime_test.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/wsrealtime.go
  • transports/bifrost-http/handlers/wsresponses.go
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
✅ Files skipped from review due to trivial changes (4)
  • transports/bifrost-http/handlers/wsresponses_test.go
  • transports/go.mod
  • transports/bifrost-http/integrations/openai.go
  • transports/bifrost-http/handlers/websocket.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/realtime_logging.go
  • transports/bifrost-http/handlers/realtime_client_secrets.go

Comment thread transports/bifrost-http/handlers/realtime_turn_pipeline.go
Comment thread transports/bifrost-http/handlers/wsrealtime.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 7, 2026
Copy link
Copy Markdown
Contributor

akshaydeo commented Apr 7, 2026

Merge activity

  • Apr 7, 2:56 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Apr 7, 3:01 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo akshaydeo changed the base branch from feature/03-27-feat_add_realtime_session_state_and_transport_context_helpers to graphite-base/2341 April 7, 2026 15:00
@akshaydeo akshaydeo changed the base branch from graphite-base/2341 to v1.5.0 April 7, 2026 15:00
@akshaydeo akshaydeo dismissed coderabbitai[bot]’s stale review April 7, 2026 15:00

The base branch was changed.

@akshaydeo akshaydeo merged commit aa4376b into v1.5.0 Apr 7, 2026
14 of 17 checks passed
@akshaydeo akshaydeo deleted the feature/03-27-feat_add_realtime_websocket_webrtc_and_client_secret_handlers branch April 7, 2026 15:01
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.

3 participants