fix(realtime): normalize Vertex AI nested turn_detection and unify session.created guardrail ordering

- Vertex AI _build_vertex_ai_setup_config now lifts nested
  audio.input.turn_detection to the top level before calling
  map_openai_params, mirroring the parent GeminiRealtimeConfig
  behavior. Without this, guardrail-injected create_response: False
  was silently dropped for GA-protocol Vertex AI clients.
- realtime_streaming session.created handling now sends the
  (possibly re-typed) event first and then triggers the guardrail
  turn-detection update for both first and duplicate cases, removing
  the inconsistent guardrail-then-event ordering for duplicates.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-22 17:16:34 +00:00
parent 8e6fe6c8ad
commit 76225f35b1
No known key found for this signature in database
2 changed files with 24 additions and 8 deletions

View file

@ -525,7 +525,10 @@ class RealTimeStreaming:
else [transformed_response]
)
for event in events:
if isinstance(event, dict) and event.get("type") == "session.created":
is_session_created_event = (
isinstance(event, dict) and event.get("type") == "session.created"
)
if is_session_created_event:
if self._session_created_sent_to_client:
# A synthetic session.created (with placeholder defaults) was
# already forwarded to the client when we connected. The
@ -535,16 +538,14 @@ class RealTimeStreaming:
# `session.updated` so the client learns the corrected
# configuration without seeing two `session.created` events.
event = {**event, "type": "session.updated"}
await self._maybe_send_guardrail_turn_detection_update()
else:
self._session_created_sent_to_client = True
event_str = json.dumps(event)
## For audio/VAD guardrail path: forward session.created first, then inject.
if (
isinstance(event, dict)
and event.get("type") == "session.created"
and self._has_audio_transcription_guardrails()
):
## For audio/VAD guardrail path: forward the (possibly re-typed)
## session.created first, then inject the guardrail turn-detection
## update. Handling first and duplicate session.created via the same
## path keeps the event-then-guardrail ordering consistent.
if is_session_created_event and self._has_audio_transcription_guardrails():
self.store_message(event_str)
await self.websocket.send_text(event_str)
await self._maybe_send_guardrail_turn_detection_update()

View file

@ -155,6 +155,21 @@ class VertexAIRealtimeConfig(GeminiRealtimeConfig):
def _build_vertex_ai_setup_config(self, model: str, session_params: dict) -> dict:
"""Build Vertex AI setup configuration with proper model path and defaults."""
# Normalize ``turn_detection`` to the top level so map_openai_params
# picks it up whether the client used the flat beta shape or the
# GA nested shape (session.audio.input.turn_detection). Without
# this, guardrail-injected ``create_response: False`` would be
# silently dropped for GA clients because map_openai_params only
# looks at top-level keys.
extracted_turn_detection = self._extract_turn_detection(session_params)
if (
extracted_turn_detection is not None
and "turn_detection" not in session_params
):
session_params = {
**session_params,
"turn_detection": extracted_turn_detection,
}
setup_config = self.map_openai_params(
optional_params={}, non_default_params=session_params
)