Ë
    uPêjÆ/  ã                   ó,   — d Z ddlmZmZ  G d„ d«      Zy)a  
xAI Grok Voice realtime event normalizer.

xAI's Grok Voice realtime API is structurally OpenAI-compatible but ships
several wire-format quirks that cause strict GA clients (e.g. pipecat's
``OpenAIRealtimeLLMService``) to crash before they can process tool calls:

  - ``ping`` keepalive events (unknown to GA clients)
  - ``usage: {}`` on ``response.created`` / ``response.done``
  - ``role: "tool"`` on ``conversation.item.added`` function_call items
  - Missing ``output_index`` / ``content_index`` on streaming response events
  - Missing ``part`` on ``response.content_part.done``

``XAIRealtimeNormalizer`` is plugged into ``RealTimeStreaming`` at handler
construction time (see ``handler.py``) so all normalization is isolated here
and ``RealTimeStreaming`` stays provider-agnostic.
é    )ÚAnyÚFinalc            
       ó¨  — e Zd ZdZ eg d¢«      Z eg d¢«      Zdd„Zdede	fd„Z
dedefd	„Zd
edefd„Zed
eddfd„«       Zededdfd„«       Zededefd„«       Zdeddfd„Zdededededdf
d„Zdedeeef   fd„Zdedefd„Zedededefd„«       Zdededefd„Zedeeef   fd„«       Zedede	deeef   dz  fd„«       Zdededefd„Zy) ÚXAIRealtimeNormalizerzDPer-session normalizer that fixes xAI Grok Voice wire-format quirks.)zresponse.output_item.addedzresponse.output_item.doneúresponse.content_part.addedúresponse.content_part.doneúresponse.output_text.deltaúresponse.output_text.doneú&response.output_audio_transcript.deltaú%response.output_audio_transcript.doneúresponse.output_audio.deltaúresponse.output_audio.donez&response.function_call_arguments.deltaz%response.function_call_arguments.done)r   r   r	   r
   r   r   r   r   ÚreturnNc                 ó   — i | _         y )N)Ú_content_part_by_key)Úselfs    úm/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/llms/xai/realtime/transformation.pyÚ__init__zXAIRealtimeNormalizer.__init__9   s   € ð BDˆÕ!ó    Úeventc                 óN   — t        |t        «      xr |j                  d«      dk(  S )zCReturn True for provider-specific keepalives unknown to GA clients.ÚtypeÚping)Ú
isinstanceÚdictÚget)r   r   s     r   Úshould_dropz!XAIRealtimeNormalizer.should_dropC   s"   € ä˜%¤Ó&ÒF¨5¯9©9°VÓ+<ÀÑ+FÐFr   c                 ó¾   — | j                  |«      }|j                  d«      xs d}| j                  ||«      }| j                  ||«      }| j	                  ||«      }|S )z,Apply all xAI normalization passes in order.r   Ú )Ú_normalize_content_part_eventsr   Ú"_normalize_conversation_item_addedÚ_inject_missing_indicesÚ_normalize_response_usage_event©r   r   Ú
event_types      r   Ú	normalizezXAIRealtimeNormalizer.normalizeG   s`   € à×3Ñ3°EÓ:ˆØ!ŸI™I fÓ-Ò3°ˆ
Ø×7Ñ7¸¸zÓJˆØ×,Ñ,¨U°JÓ?ˆØ×4Ñ4°U¸JÓGˆØˆr   Úsessionc                 ó>   — t        |«      }| j                  |«       |S )a‰  Patch a client ``session.update`` payload before forwarding to xAI.

        Unlike OpenAI, xAI does not default ``turn_detection.create_response``
        to ``True`` for ``server_vad``. Clients such as Pipecat omit the field,
        which leaves VAD detecting speech but never auto-creating a response.
        Only fill the default when the client did not set ``create_response``.
        )r   Ú#_default_server_vad_create_response)r   r'   s     r   Úpatch_outgoing_sessionz,XAIRealtimeNormalizer.patch_outgoing_sessionP   s    € ô �w“-ˆØ×0Ñ0°Ô9Øˆr   c                 óf  — | j                  d«      }t        |t        «      rt        j	                  |«       | j                  d«      }t        |t        «      rZ|j                  d«      }t        |t        «      r8|j                  d«      }t        |t        «      rt        j	                  |«       y y y y )NÚturn_detectionÚaudioÚinput)r   r   r   r   Ú"_ensure_server_vad_create_response)r'   r,   r-   Úaudio_inputÚ	nested_tds        r   r)   z9XAIRealtimeNormalizer._default_server_vad_create_response\   s�   € à '§¡Ð,<Ó =ˆÜ�n¤dÔ+Ü!×DÑDÀ^ÔTà—{‘{ 7Ó+ˆÜ�eœTÔ"Ø!&§¡¨7Ó!3ˆKÜ˜+¤tÔ,Ø#.§?¡?Ð3CÓ#D�	Ü˜i¬Ô.Ü)×LÑLÈYÕWð /ð -ð #r   r,   c                 óB   — | j                  d«      dk(  rd| vrd| d<   y y y )Nr   Ú
server_vadÚcreate_responseT©r   )r,   s    r   r/   z8XAIRealtimeNormalizer._ensure_server_vad_create_responsej   s2   € à×Ñ˜fÓ%¨Ò5Ð:KÐSaÑ:aØ04ˆNÐ,Ò-ð ;bÐ5r   c                 óh   — | j                  d«      | j                  d«      | j                  dd«      fS )NÚresponse_idÚitem_idÚcontent_indexr   r5   )r   s    r   Ú_content_part_keyz'XAIRealtimeNormalizer._content_part_keys   s4   € ð �I‰I�mÓ$Ø�I‰I�iÓ Ø�I‰I�o qÓ)ð
ð 	
r   c                 ó„   — |j                  d«      }t        |t        «      r|| j                  | j	                  |«      <   y y )NÚpart©r   r   r   r   r:   )r   r   r<   s      r   Ú_remember_content_partz,XAIRealtimeNormalizer._remember_content_part{   s:   € Ø—i‘i Ó'ˆÜ�dœDÔ!ØGKˆD×%Ñ% d×&<Ñ&<¸UÓ&CÒDð "r   Ú	part_typeÚfieldÚvaluec                óâ   — |€y | j                  |«      }| j                  j                  |«      }t        |t        «      sd|||i}ni |¥d|j                  d|«      ||i¥}|| j                  |<   y )Nr   )r:   r   r   r   r   )r   r   r?   r@   rA   ÚkeyÚexistingÚupdateds           r   Ú_update_content_part_fieldz0XAIRealtimeNormalizer._update_content_part_field€   sƒ   € Øˆ=ØØ×+Ñ+¨EÓ2ˆØ×3Ñ3×7Ñ7¸Ó<ˆÜ˜(¤DÔ)Ø˜y¨%°Ð7‰GðØðà˜Ÿ™ V¨YÓ7Ø�uñˆGð
 *1ˆ×!Ñ! #Ò&r   c                 óÊ   — |j                  d«      }t        |t        «      r|S | j                  j                  | j	                  |«      «      }t        |t        «      r|S dddœS )Nr<   r-   r   )r   Ú
transcriptr=   )r   r   r<   Úcacheds       r   Ú_resolve_content_partz+XAIRealtimeNormalizer._resolve_content_part�   sZ   € Ø—i‘i Ó'ˆÜ�dœDÔ!ØˆKØ×1Ñ1×5Ñ5°d×6LÑ6LÈUÓ6SÓTˆÜ�fœdÔ#ØˆMØ¨rÑ2Ð2r   c                 ó  — |j                  d«      }|dk(  rI| j                  |«       t        |j                  d«      t        «      si |¥d| j	                  |«      i¥S |S |dk(  r&| j                  |dd|j                  d«      ¬«       |S |dk(  r&| j                  |dd	|j                  d	«      ¬«       |S |d
k(  rI| j                  |«       t        |j                  d«      t        «      si |¥d| j	                  |«      i¥S |S |S )Nr   r   r<   r
   Útext)r?   r@   rA   r   r-   rH   r   )r   r>   r   r   rJ   rF   r$   s      r   r    z4XAIRealtimeNormalizer._normalize_content_part_events˜   s  € Ø!ŸI™I fÓ-ˆ
àÐ6Ò6Ø×'Ñ'¨Ô.Ü˜eŸi™i¨Ó/´Ô6ØK˜%ÐK ¨×)CÑ)CÀEÓ)JÑKÐKØˆLàÐ4Ò4Ø×+Ñ+¨E¸VÈ6ÐY^×YbÑYbÐciÓYjÐ+ÔkØˆLàÐ@Ò@Ø×+Ñ+ØØ!Ø"Ø—i‘i Ó-ð	 ,ô ð ˆLàÐ5Ò5Ø×'Ñ'¨Ô.Ü˜eŸi™i¨Ó/´Ô6ØK˜%ÐK ¨×)CÑ)CÀEÓ)JÑKÐKØˆLàˆr   r%   c                 óž   — |dk7  r| S | j                  d«      }t        |t        «      s| S |j                  d«      dk(  ri | ¥di |¥ddi¥i¥S | S )uÀ   Map ``role: "tool"`` â†’ ``role: "assistant"`` on function_call items.

        xAI uses ``role: "tool"`` which is not in the GA-allowed set
        ("user" | "assistant" | "system").
        zconversation.item.addedÚitemÚroleÚtoolÚ	assistant)r   r   r   )r   r%   rN   s      r   r!   z8XAIRealtimeNormalizer._normalize_conversation_item_addedº   sd   € ð Ð2Ò2ØˆLØ—i‘i Ó'ˆÜ˜$¤Ô%ØˆLØ�8‰8�FÓ˜vÒ%ØC�eÐC˜VÐ%B¨Ð%B¨f°kÑ%BÑCÐCØˆr   c                 óŠ   — || j                   v }|| j                  v }|s|s|S i }|r	d|vrd|d<   |r	d|vrd|d<   |s|S i |¥|¥S )a:  Inject ``output_index`` / ``content_index`` defaults when absent.

        xAI omits both fields on every streaming response event; pydantic GA
        clients require them as non-optional ints.  Defaulting to 0 is correct
        for single-turn single-item responses and harmless for well-formed events.
        Úoutput_indexr   r9   )Ú_EVENTS_NEEDING_OUTPUT_INDEXÚ_EVENTS_NEEDING_CONTENT_INDEX)r   r   r%   Úneeds_outputÚneeds_contentÚpatchs         r   r"   z-XAIRealtimeNormalizer._inject_missing_indicesÎ   ss   € ð )¨D×,MÑ,MÐMˆØ)¨T×-OÑ-OÐOˆÙ¡MØˆLØ')ˆÙ˜N°%Ñ7Ø$%ˆE�.Ñ!Ù˜_°EÑ9Ø%&ˆE�/Ñ"ÙØˆLØ!�%Ð!˜5Ð!Ð!r   c                  óV   — ddddœ} ddd| j                  «       | j                  «       dœS )Nr   ©Úcached_tokensÚtext_tokensÚaudio_tokens©Útotal_tokensÚinput_tokensÚoutput_tokensÚinput_token_detailsÚoutput_token_details)Úcopy)Údefault_detailss    r   Ú_default_ga_usagez'XAIRealtimeNormalizer._default_ga_usageæ   sA   € ð ØØñ2
ˆð ØØØ#2×#7Ñ#7Ó#9Ø$3×$8Ñ$8Ó$:ñ
ð 	
r   ÚusageÚempty_as_nullc                ót  — t        | t        «      sy| s|rdS t        j                  «       S ddddœ}| j	                  dd«      | j	                  dd«      | j	                  dd«      |j                  «       |j                  «       dœ}dD ]-  }| j	                  |«      }t        |t        «      sŒ%i |¥|¥||<   Œ/ |S )	zÛCoerce a usage object into the full OpenAI GA shape.

        ``empty_as_null=True`` for ``response.created`` (usage optional).
        ``empty_as_null=False`` for ``response.done`` (e2e tests assert non-null).
        Nr   rZ   r_   r`   ra   r^   )rb   rc   )r   r   r   rf   r   rd   )rg   rh   re   Ú
normalizedrC   Údetailss         r   Ú_normalize_usagez&XAIRealtimeNormalizer._normalize_usageõ   sÌ   € ô ˜%¤Ô&ØÙÙ(�4ÐWÔ.C×.UÑ.UÓ.WÐWàØØñ2
ˆð "ŸI™I n°aÓ8Ø!ŸI™I n°aÓ8Ø"ŸY™Y ¸Ó:Ø#2×#7Ñ#7Ó#9Ø$3×$8Ñ$8Ó$:ñ-
ˆ
ð Cò 	AˆCØ—i‘i “nˆGÜ˜'¤4Õ(Ø"@ _Ð"@¸Ð"@�
˜3’ð	Að Ðr   c                 óì   — |dvr|S |j                  d«      }t        |t        «      rd|vr|S | j                  |j                  d«      |dk(  ¬«      }||j                  d«      u r|S i |¥di |¥d|i¥i¥S )N)úresponse.createdzresponse.doneÚresponserg   rn   )rh   )r   r   r   rl   )r   r   r%   ro   Únormalized_usages        r   r#   z5XAIRealtimeNormalizer._normalize_response_usage_event  s—   € ØÐBÑBØˆLØŸ)™) JÓ/ˆÜ˜(¤DÔ)¨W¸HÑ-DØˆLØ"&×"7Ñ"7Ø�L‰L˜Ó!Ø$Ð(:Ñ:ð #8ó #
Ðð ˜xŸ|™|¨GÓ4Ñ4ØˆLØM�%ÐM˜Ð%L¨Ð%L°'Ð;KÑ%LÑMÐMr   )r   N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú	frozensetrT   rU   r   ÚobjectÚboolr   r   r&   r*   Ústaticmethodr)   r/   Útupler:   r>   ÚstrrF   r   rJ   r    r!   r"   rf   rl   r#   © r   r   r   r      s   „ ÙNñ
 $-ò	
ó$Ð ñ  %.ò		
ó%Ð!óDðG ð G¨Dó Gð˜tð ¨ó ð
¨dð 
°tó 
ð ðX°Tð X¸dò Xó ðXð ð5¸4ð 5ÀDò 5ó ð5ð ð
 ð 
¨%ò 
ó ð
ðL¨Dð L°Tó Lð
1°ð 1ÀCð 1ÐPSð 1Ð\bð 1Ðgkó 1ð3¨4ð 3°D¸¸c¸±Nó 3ð°Dð ¸Tó ðD ð°$ð ÀCð ÈDò ó ðð&"¨Tð "¸sð "Àtó "ð0 ð
˜t C¨ H™~ò 
ó ð
ð ð ð ¸$ð À4ÈÈSÈÁ>ÐTXÑCXò ó ðð8N°Tð NÀsð NÈtô Nr   r   N)rt   Útypingr   r   r   r{   r   r   ú<module>r}      s   ðñ÷$ ÷HNò HNr   