From 2325041913f8436dba7c83cb1fd87ebebc341e96 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 22 May 2026 19:29:36 +0000 Subject: [PATCH] fix(gemini realtime): normalize GA-remapped session fields before mapping map_openai_params only recognises the flat OpenAI-beta keys (modalities, input_audio_transcription, turn_detection). For GA clients the upstream shim renames these into the nested GA schema (output_modalities, audio.input.transcription, audio.input.turn_detection), causing them to be silently dropped in _handle_session_update. Add a normalization helper that surfaces the GA-remapped values back at the top level so the existing mapping logic picks them up. Without this, a GA client explicitly requesting modalities=['text'] would still default to audio output. Co-authored-by: Yassin Kortam --- .../llms/gemini/realtime/transformation.py | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/litellm/llms/gemini/realtime/transformation.py b/litellm/llms/gemini/realtime/transformation.py index 689f229e393..bab3431c6a6 100644 --- a/litellm/llms/gemini/realtime/transformation.py +++ b/litellm/llms/gemini/realtime/transformation.py @@ -246,6 +246,45 @@ class GeminiRealtimeConfig(BaseRealtimeConfig): return td return None + @staticmethod + def _normalize_session_payload_for_mapping(session: dict) -> dict: + """Normalize GA-remapped session fields back to their beta keys. + + ``map_openai_params`` only recognises the flat OpenAI-beta key names + (``modalities``, ``input_audio_transcription``, ``turn_detection``). + For GA clients the upstream shim renames these into the nested GA + schema (``output_modalities``, ``audio.input.transcription``, + ``audio.input.turn_detection``), which would otherwise be silently + dropped here. Surface them back at the top level so the existing + mapping logic picks them up without duplicating provider-specific + knowledge of the GA schema in ``map_openai_params``. + """ + if not isinstance(session, dict): + return session + + normalized = dict(session) + + if "modalities" not in normalized and "output_modalities" in normalized: + normalized["modalities"] = normalized["output_modalities"] + + audio = normalized.get("audio") + if isinstance(audio, dict): + input_cfg = audio.get("input") + if isinstance(input_cfg, dict): + if ( + "input_audio_transcription" not in normalized + and "transcription" in input_cfg + ): + normalized["input_audio_transcription"] = input_cfg["transcription"] + + extracted_turn_detection = GeminiRealtimeConfig._extract_turn_detection( + normalized + ) + if extracted_turn_detection is not None and "turn_detection" not in normalized: + normalized["turn_detection"] = extracted_turn_detection + + return normalized + def _handle_session_update( self, json_message: dict, @@ -268,21 +307,16 @@ class GeminiRealtimeConfig(BaseRealtimeConfig): session_payload = json_message.get("session") or {} if session_configuration_request is None: # First session.update - send the setup with all configuration. - # 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_payload) - if ( - extracted_turn_detection is not None - and "turn_detection" not in session_payload - ): - session_payload = { - **session_payload, - "turn_detection": extracted_turn_detection, - } + # Normalize GA-remapped fields (``output_modalities``, + # nested ``audio.input.transcription``, + # ``audio.input.turn_detection``) back to their flat beta keys so + # ``map_openai_params`` picks them up. Without this, GA clients' + # explicit modality / transcription / turn-detection settings + # would be silently dropped because ``map_openai_params`` only + # recognises the flat OpenAI-beta key names. + session_payload = self._normalize_session_payload_for_mapping( + session_payload + ) client_session_configuration_request = self.map_openai_params( optional_params={}, non_default_params=session_payload )