§
    øžyjpH  ã                  ó–  — U d Z ddlmZ ddlZddlZddlZddlmZmZ ddl	m
Z
mZmZmZmZ ddlmZ ddlmZmZmZ  ej        e¦  «        ZdZd=d„ZdZdZdaded<   d>d„Zd?d„Z ej         d¦  «        Z! ej         dej"        ¬¦  «        Z# G d„ d¦  «        Z$ G d„ de¦  «        Z%i Z&ded<   d@d!„Z'dAd%„Z(g d&¢Z)d'ed(<   	 dBdCd+„Z* e'd,¦  «         G d-„ d.e%¦  «        ¦   «         Z+dDd/„Z, e'd0¦  «         G d1„ d2e%¦  «        ¦   «         Z-dEd6„Z. e'd7¦  «         G d8„ d9e%¦  «        ¦   «         Z/ e'd:¦  «         G d;„ d<e%¦  «        ¦   «         Z0dS )Fu  Provider-agnostic streaming TTS: sentence text â†’ int16 PCM chunk iterator.

The keystone of Hermes' conversational voice UX. `stream_tts_to_speaker`
(``tools.tts_tool``) owns the sentence buffer, sounddevice output, and
stop/queue protocol; this module owns the *provider* half â€” turning one
sentence into audio the moment it's ready, so playback starts on sentence one
instead of after the whole reply.

Two provider shapes, one contract (int16 mono PCM at ``sample_rate``):

* **True streamers** (`StreamingTTSProvider.stream`) â€” chunked APIs
  (ElevenLabs pcm_24000, OpenAI pcm, â€¦) that yield audio as it synthesizes.
  Lowest time-to-first-audio.
* **Everyone else** â€” providers with no chunked API still get per-*sentence*
  playback via the proven sync `text_to_speech_tool` path (handled by the
  dispatcher, not here), so edge (the default) is conversational too.

Adding a streamer is `@register("name")` on a `StreamingTTSProvider` subclass;
the dispatcher, config gate (`tts.<name>.streaming`), and resolver come free.
é    )ÚannotationsN)ÚABCÚabstractmethod)ÚCallableÚDictÚIteratorÚListÚOptional)Úresolve_openai_audio_api_key)Ú_get_providerÚ_load_tts_configÚget_env_valuei   Úenv_varÚstrÚprovider_idÚreturnc                ón   — 	 ddl m}  || |¦  «        pdS # t          $ r t          | ¦  «        pdcY S w xY w)u  Provider secret lookup: config > env/.env > credential pool.

    Thin, monkeypatchable seam over ``tools.tts_tool._resolve_provider_key``
    (which delegates to ``resolve_provider_secret``). ALL streaming-provider
    key lookups go through here â€” never bare ``get_env_value``.
    r   )Ú_resolve_provider_keyÚ )Útools.tts_toolr   Ú	Exceptionr   )r   r   r   s      ú9/home/ragecks/.hermes/hermes-agent/tools/tts_streaming.pyÚ_resolve_keyr   *   se   € ð,Ø8Ð8Ð8Ð8Ð8Ð8à$Ð$ W¨kÑ:Ô:Ð@¸bÐ@øÝð ,ð ,ð ,Ý˜WÑ%Ô%Ð+¨Ð+Ð+Ð+ð,øøøs   ‚ –4³4zK[Note: the user interrupted your previous spoken reply before it finished.]g      ^@zOptional[float]Ú_interrupted_atÚNonec                 ó,   — t          j        ¦   «         ad S ©N)ÚtimeÚ	monotonicr   © ó    r   Úmark_speech_interruptedr"   I   s   € å”nÑ&Ô&€O€O€Or!   Úboolc                 ó\   — t           dc} a | duot          j        ¦   «         | z
  t          k     S )z9Pop the latch; True when a barge happened within the TTL.N)r   r   r   Ú_INTERRUPT_TTL_S)Úats    r   Útake_speech_interruptedr'   N   s2   € õ *¨4Ð€BˆØ�Tˆ>ÐF�dœnÑ.Ô.°Ñ3Õ6FÒFÐFr!   z(?<=[.!?])(?:\s|\n)|(?:\n\n)z<think[\s>].*?</think>)Úflagsc                  ó,   — e Zd ZdZddd„Zdd
„Zdd„ZdS )ÚSentenceChunkeraŸ  Incremental sentence cutter for LLM token deltas.

    Shared by the speaker pipeline (`stream_tts_to_speaker`) and the
    speak-stream WebSocket so every surface cuts speech identically. Strips
    ``<think>`` blocks (even split across deltas) and merges fragments shorter
    than *min_len* into the following sentence, so "Ha!" rides along with the
    sentence after it instead of stalling as a tiny clip.
    é   Úmin_lenÚintc                ó"   — || _         d| _        d S )Nr   )r,   Úbuf)Úselfr,   s     r   Ú__init__zSentenceChunker.__init__c   s   € ØˆŒØˆŒˆˆr!   Údeltar   r   ú	List[str]c                óH  — t                                d| j        |z   ¦  «        | _        d| j        v rd| j        vrg S g }d}t                               | j        |¦  «        x}r¿| j        d|                     ¦   «         …         }t          |                     ¦   «         ¦  «        | j        k     r|                     ¦   «         }Œ‚| 	                    |¦  «         | j        |                     ¦   «         d…         | _        d}t                               | j        |¦  «        x}°¿|S )zBAbsorb *delta*; return every complete sentence now ready to speak.r   z<thinkz</think>r   N)
Ú_THINK_BLOCK_REÚsubr/   ÚSENTENCE_BOUNDARY_REÚsearchÚendÚlenÚstripr,   Úappend)r0   r2   ÚoutÚstartÚmÚheads         r   ÚfeedzSentenceChunker.feedg   s  € å"×&Ò& r¨4¬8°eÑ+;Ñ<Ô<ˆŒØ�t”xÐÐ J°d´hÐ$>Ð$>ØˆIØˆØˆÝ'×.Ò.¨t¬x¸Ñ?Ô?Ð?ˆað 	Ø”8˜I˜aŸeše™gœg˜IÔ&ˆDÝ�4—:’:‘<”<Ñ Ô  4¤<Ò/Ð/ØŸš™œ�ØØ�JŠJ�tÑÔÐØ”x §¢¡¤  Ô)ˆDŒHØˆEõ (×.Ò.¨t¬x¸Ñ?Ô?Ð?ˆað 	ð ˆ
r!   c                ó‚   — t                                d| j        ¦  «                             ¦   «         }d| _        |r|gng S )z0Drain the tail (end-of-text or long-idle flush).r   )r5   r6   r/   r;   )r0   Útails     r   ÚflushzSentenceChunker.flushx   s=   € å×"Ò" 2 t¤xÑ0Ô0×6Ò6Ñ8Ô8ˆØˆŒØÐ%�ˆvˆv 2Ð%r!   N)r+   )r,   r-   )r2   r   r   r3   )r   r3   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r1   rA   rD   r    r!   r   r*   r*   Y   s_   € € € € € ðð ðð ð ð ð ðð ð ð ð"&ð &ð &ð &ð &ð &r!   r*   c                  ó†   — e Zd ZU dZdZded<   dZded<   dZded<   dd„Ze	e
dd„¦   «         ¦   «         Ze
dd„¦   «         ZdS )ÚStreamingTTSProviderzDYields raw int16, little-endian, mono PCM chunks at ``sample_rate``.éÀ]  r-   Úsample_rateé   Úchannelsé   Úsample_widthÚ
tts_configr   Úsectionc                ó"   — || _         || _        d S r   )rQ   rR   )r0   rQ   rR   s      r   r1   zStreamingTTSProvider.__init__Š   s   € Ø$ˆŒØˆŒˆˆr!   r   r#   c                 ó   — dS )z?True when this provider's credentials/SDK are usable right now.Nr    r    r!   r   Ú	availablezStreamingTTSProvider.availableŽ   ó   € € € r!   Útextr   úIterator[bytes]c                ó   — dS )z>Yield PCM chunks for ``text``. Raise on failure (caller logs).Nr    ©r0   rW   s     r   ÚstreamzStreamingTTSProvider.stream“   rV   r!   N)rQ   r   rR   r   ©r   r#   ©rW   r   r   rX   )rE   rF   rG   rH   rL   Ú__annotations__rN   rP   r1   Ústaticmethodr   rU   r[   r    r!   r   rJ   rJ   ƒ   s·   € € € € € € ØNÐNà€KÐÐÐÑØ€HÐÐÐÑØ€LÐÐÐÑðð ð ð ð ØðNð Nð Nñ „^ñ „\ðNð ðMð Mð Mñ „^ðMð Mð Mr!   rJ   z%Dict[str, type[StreamingTTSProvider]]Ú	_REGISTRYÚnameúBCallable[[type[StreamingTTSProvider]], type[StreamingTTSProvider]]c                ó   ‡ — dˆ fd„}|S )NÚclsútype[StreamingTTSProvider]r   c                ó   •— | t           ‰<   | S r   )r`   )rd   ra   s    €r   Ú_wrapzregister.<locals>._wrapœ   s   ø€ Ø�	�$‰Øˆ
r!   )rd   re   r   re   r    )ra   rg   s   ` r   Úregisterrh   ›   s(   ø€ ðð ð ð ð ð ð €Lr!   rQ   r   úOptional[StreamingTTSProvider]c                ó  — t                                | ¦  «        }|�|                     ¦   «         sdS 	  |||                     | ¦  «        pi ¦  «        S # t          $ r'}t                               d| |¦  «         Y d}~dS d}~ww xY w)zCConstruct the registered streamer *name* if it's usable, else None.Nz%streaming provider %s init failed: %s)r`   ÚgetrU   r   ÚloggerÚdebug)ra   rQ   rd   Úexcs       r   Ú_try_instantiatero   £   s”   € å
�-Š-˜Ñ
Ô
€CØ
€{˜#Ÿ-š-™/œ/€{ØˆtðØˆs�:˜zŸ~š~¨dÑ3Ô3Ð9°rÑ:Ô:Ð:øÝð ð ð Ý�ŠÐ<¸dÀCÑHÔHÐHØˆtˆtˆtˆtˆtøøøøðøøøs   ´ A Á
BÁBÂB)Ú
elevenlabsÚgeminiÚopenaiÚxair3   Ú_PROVIDER_PRIORITYÚ	preferredúOptional[str]c                ó¾  — |                       d¦  «        pi }t          |                      d¦  «        pd¦  «                             ¦   «                              ¦   «         }|dk    r"t          D ]}t          || ¦  «        }|�|c S ŒdS |rt          || ¦  «        S |pt          | ¦  «                             ¦   «                              ¦   «         }t          || ¦  «        S )u  Return a ready streamer for the *configured* provider, else ``None``.

    Resolution order:

    1. ``tts.streaming.provider`` (config knob) when set:
       * a provider name pins that exact streamer (or ``None`` if unusable);
       * ``auto`` walks the priority list (``elevenlabs â†’ gemini â†’ openai
         â†’ xai``) and returns the first usable streamer â€” an explicit
         opt-in to "give me the best chunked voice available".
    2. Otherwise the *configured* TTS provider (or ``preferred`` override).
       ``None`` means "no chunked API for this provider" â€” the dispatcher
       then speaks per-sentence via the sync path, preserving the user's
       chosen voice. We never silently swap to a different provider just
       to get streaming.
    Ú	streamingÚproviderr   ÚautoN)rk   r   Úlowerr;   rt   ro   r   )rQ   ru   Ústreaming_cfgÚpinnedra   Úinsts         r   Úresolve_streaming_providerr   ¶   sæ   € ð& —N’N ;Ñ/Ô/Ð5°2€MÝ�×"Ò" :Ñ.Ô.Ð4°"Ñ5Ô5×;Ò;Ñ=Ô=×CÒCÑEÔE€FØ�ÒÐÝ&ð 	ð 	ˆDÝ# D¨*Ñ5Ô5ˆDØÐØ���ð  àˆtØð 4Ý ¨
Ñ3Ô3Ð3àÐ2� zÑ2Ô2×9Ò9Ñ;Ô;×AÒAÑCÔC€DÝ˜D *Ñ-Ô-Ð-r!   rp   c                  ó6   — e Zd ZdZdZedd„¦   «         Zdd	„Zd
S )ÚElevenLabsStreameruD   ElevenLabs chunked HTTP â†’ pcm_24000 (the original reference path).rK   r   r#   c                 ó<   — t          t          dd¦  «        ¦  «        S )NÚELEVENLABS_API_KEYrp   ©r#   r   r    r!   r   rU   zElevenLabsStreamer.availableâ   s   € å•LÐ!5°|ÑDÔDÑEÔEÐEr!   rW   r   rX   c              #  ód  K  — ddl m}m}m}m}   |¦   «         ddt          dd¦  «        i || j        ¦  «        ¤Ž}| j                             d|¦  «        }| j                             d| j                             d|¦  «        ¦  «        }|j         	                    |||d	¬
¦  «        E d {V —† d S )Nr   )Ú%DEFAULT_ELEVENLABS_STREAMING_MODEL_IDÚDEFAULT_ELEVENLABS_VOICE_IDÚ_elevenlabs_environment_kwargsÚ_import_elevenlabsÚapi_keyrƒ   rp   Úvoice_idÚstreaming_model_idÚmodel_idÚ	pcm_24000)rW   r‹   r�   Úoutput_formatr    )
r   r†   r‡   rˆ   r‰   r   rR   rk   Útext_to_speechÚconvert)	r0   rW   r†   r‡   rˆ   r‰   Úclientr‹   r�   s	            r   r[   zElevenLabsStreamer.streamæ   s,  è è € ð	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð &Ð#Ð#Ñ%Ô%ð 
ð 
Ý Ð!5°|ÑDÔDð
à,Ð,¨T¬\Ñ:Ô:ð
ð 
ˆð ”<×#Ò# JÐ0KÑLÔLˆØ”<×#Ò#Ø ØŒL×Ò˜ZÐ)NÑOÔOñ
ô 
ˆð Ô(×0Ò0ØØØØ%ð	 1ñ 
ô 
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
r!   Nr\   r]   ©rE   rF   rG   rH   rL   r_   rU   r[   r    r!   r   r�   r�   Ü   sU   € € € € € àNÐNà€KàðFð Fð Fñ „\ðFð
ð 
ð 
ð 
ð 
ð 
r!   r�   c                 óœ   — 	 t          ¦   «                              d¦  «        pi } n# t          $ r Y dS w xY w|                      d¦  «        pdS )z@Return ``tts.openai.api_key`` from config.yaml, or empty string.rr   r   rŠ   )r   rk   r   )Ú
openai_cfgs    r   Ú_openai_config_api_keyr–   ÿ   s`   € ðÝ&Ñ(Ô(×,Ò,¨XÑ6Ô6Ð<¸"ˆ
ˆ
øÝð ð ð Øˆrˆrðøøøà�>Š>˜)Ñ$Ô$Ð*¨Ð*s   ‚#& ¦
4³4rr   c                  ó6   — e Zd ZdZdZedd„¦   «         Zdd	„Zd
S )ÚOpenAIStreamerz?OpenAI speech with ``response_format=pcm`` (24 kHz mono int16).rK   r   r#   c                 óT   — t          t          ¦   «         pt          ¦   «         ¦  «        S r   )r#   r–   r   r    r!   r   rU   zOpenAIStreamer.available  s#   € åÕ*Ñ,Ô,ÐNÕ0LÑ0NÔ0NÑOÔOÐOr!   rW   r   rX   c              #  ó  K  — ddl m}  || j                             d¦  «        pt	          ¦   «         | j                             d¦  «        pt          d¦  «        pd ¬¦  «        }| j                             dd¦  «        }| j                             d	d
¦  «        }|j        j        j         	                    |||d¬¦  «        5 }t          |                     ¦   «         d¦  «        E d {V —† d d d ¦  «         d S # 1 swxY w Y   d S )Nr   )ÚOpenAIrŠ   Úbase_urlÚOPENAI_BASE_URL)rŠ   rœ   Úmodelzgpt-4o-mini-ttsÚvoiceÚalloyÚpcm)rž   rŸ   ÚinputÚresponse_formatzOpenAI streaming TTS)rr   r›   rR   rk   r   r   ÚaudioÚspeechÚwith_streaming_responseÚcreateÚ_cappedÚ
iter_bytes)r0   rW   r›   r’   rž   rŸ   Úresponses          r   r[   zOpenAIStreamer.stream  s€  è è € Ø!Ð!Ð!Ð!Ð!Ð!à�Ø”\×%Ò% iÑ0Ô0ÐRÕ4PÑ4RÔ4Rà”× Ò  Ñ,Ô,ð Ý Ð!2Ñ3Ô3ðàð
ñ 
ô 
ˆð ”× Ò  Ð*;Ñ<Ô<ˆØ”× Ò  ¨'Ñ2Ô2ˆØŒ\Ô Ô8×?Ò?ØØØØ!ð	 @ñ 
ô 
ð 	Nð
 Ý˜x×2Ò2Ñ4Ô4Ð6LÑMÔMÐMÐMÐMÐMÐMÐMÐMð	Nð 	Nð 	Nñ 	Nô 	Nð 	Nð 	Nð 	Nð 	Nð 	Nð 	Nð 	Nøøøð 	Nð 	Nð 	Nð 	Nð 	Nð 	Ns   Ã)C9Ã9C=Ä C=Nr\   r]   r“   r    r!   r   r˜   r˜     s[   € € € € € àIÐIà€KàðPð Pð Pñ „\ðPðNð Nð Nð Nð Nð Nr!   r˜   ÚchunksrX   Úlabelc              #  ó¢   K  — d}| D ]G}|t          |¦  «        z  }|t          k    r$t                               d|t          ¦  «          dS |V — ŒHdS )u  Pass chunks through, aborting past the 16 MiB per-sentence cap.

    The streaming mirror of ``_read_tts_response_bytes``'s bounded-body
    invariant: one sentence of PCM should never approach the cap, so
    exceeding it means a runaway/hostile upstream â€” stop pulling.
    r   z1%s exceeded %d bytes for one sentence; truncatingN)r:   Ú_STREAM_SENTENCE_BYTE_CAPrl   Úwarning)r«   r¬   ÚtotalÚchunks       r   r¨   r¨   (  ss   è è € ð €EØð ð ˆØ•�U‘”ÑˆØÕ,Ò,Ð,Ý�NŠNÐNØ Õ";ñ=ô =ð =àˆFˆFØˆˆˆˆðð r!   rq   c                  ó6   — e Zd ZdZdZedd„¦   «         Zdd	„Zd
S )ÚGeminiStreameru(  Gemini ``streamGenerateContent?alt=sse`` â†’ base64 PCM chunks (24 kHz).

    Salvaged from PR #47588 (@Cdddo) and rebased onto the post-campaign
    infrastructure: credentials via the provider-secret resolver, requests
    (not httpx) with a bounded streamed body, and main's provider ABC.
    rK   r   r#   c                 ó\   — t          t          dd¦  «        pt          dd¦  «        ¦  «        S )NÚGEMINI_API_KEYrq   ÚGOOGLE_API_KEYr„   r    r!   r   rU   zGeminiStreamer.availableD  s5   € åÝÐ)¨8Ñ4Ô4ð 8ÝÐ,¨hÑ7Ô7ñ
ô 
ð 	
r!   rW   r   rX   c              #  ó¸  ‡	‡
‡‡‡‡K  — dd l Šdd lŠ	dd lŠddlm}m}m} t          dd¦  «        pt          dd¦  «        Š
t          | j	         
                    d|¦  «        ¦  «                             ¦   «         p|}t          | j	         
                    d|¦  «        ¦  «                             ¦   «         p|}t          | j	         
                    d¦  «        pt          d	¦  «        p|¦  «                             ¦   «                              d
¦  «        }dd|igigdgddd|iiidœdœŠ|› d|› d�Šdˆ	ˆ
ˆˆˆˆfd„}t           |¦   «         d¦  «        E d {V —† d S )Nr   )ÚDEFAULT_GEMINI_TTS_BASE_URLÚDEFAULT_GEMINI_TTS_MODELÚDEFAULT_GEMINI_TTS_VOICErµ   rq   r¶   rž   rŸ   rœ   ÚGEMINI_BASE_URLú/ÚpartsrW   ÚAUDIOÚvoiceConfigÚprebuiltVoiceConfigÚ	voiceName)ÚresponseModalitiesÚspeechConfig)ÚcontentsÚgenerationConfigz/models/z:streamGenerateContentr   rX   c               3  ó  •K  — ‰                      ‰d‰	dœ‰dd¬¦  «        5 } |                      ¦   «          |                      d¬¦  «        D �]}|r|                     d¦  «        sŒ	 ‰                     |t          d¦  «        d …         ¦  «        }|d         d	         d
         d         }n## t          t          t          t          f$ r Y Œ€w xY w|D ]˜}| 
                    d¦  «        p| 
                    d¦  «        pi }| 
                    dd¦  «        }|sŒG	 ‰
                     |¦  «        V — Œ`# t          t          f$ r%}t                               d|¦  «         Y d }~Œ‘d }~ww xY w�Œ!	 d d d ¦  «         d S # 1 swxY w Y   d S )NÚsse)ÚaltÚkeyé<   T)ÚparamsÚjsonÚtimeoutr[   )Údecode_unicodezdata: Ú
candidatesr   Úcontentr½   Ú
inlineDataÚinline_dataÚdatar   z Gemini SSE: bad base64 audio: %s)ÚpostÚraise_for_statusÚ
iter_linesÚ
startswithÚloadsr:   Ú
ValueErrorÚKeyErrorÚ
IndexErrorÚ	TypeErrorrk   Ú	b64decoderl   r¯   )rª   ÚlineÚeventr½   ÚpartÚinlineÚb64rn   Ú_jsonrŠ   Úbase64ÚpayloadÚrequestsÚurls           €€€€€€r   Ú_sse_chunksz*GeminiStreamer.stream.<locals>._sse_chunksr  s;  øè è € Ø—’ØØ$¨WÐ5Ð5ØØØð ñ ô ð Tð Ø×)Ò)Ñ+Ô+Ð+Ø$×/Ò/¸tÐ/ÑDÔDð Tñ T�DØð ! t§¢°xÑ'@Ô'@ð !Ø ð!Ø %§¢¨Dµ°X±´°°Ô,@Ñ AÔ A˜Ø % lÔ 3°AÔ 6°yÔ AÀ'Ô J˜˜øÝ&­µ*½iÐHð !ð !ð !Ø ˜ð!øøøà %ð Tð T˜Ø!%§¢¨,Ñ!7Ô!7Ð!X¸4¿8º8ÀMÑ;RÔ;RÐ!XÐVX˜Ø$Ÿjšj¨°Ñ4Ô4˜Ø"ð %Ø$ðTØ"(×"2Ò"2°3Ñ"7Ô"7Ð7Ð7Ð7Ð7øÝ *­IÐ6ð Tð Tð TÝ"ŸNšNÐ+MÈsÑSÔSÐSÐSÐSÐSÐSÐSøøøøðTøøøñTðTðTð Tð Tñ Tô Tð Tð Tð Tð Tð Tð Tð Tøøøð Tð Tð Tð Tð Tð Tsb   ¡AE;Á(AB-Â,E;Â-CÃ
E;ÃCÃAE;ÄD2Ä1E;Ä2E(	ÅE#	ÅE;Å#E(	Å(E;Å;E?ÆE?zGemini streaming TTS)r   rX   )rä   rÌ   ræ   r   r¸   r¹   rº   r   r   rR   rk   r;   r   Úrstripr¨   )r0   rW   r¸   r¹   rº   rž   rŸ   rœ   rè   rã   rŠ   rä   rå   ræ   rç   s            @@@@@@r   r[   zGeminiStreamer.streamK  s  øøøøøøè è € ØˆˆˆØÐÐÐàˆˆˆð	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
õ Ð)¨8Ñ4Ô4ð 8ÝÐ,¨hÑ7Ô7ð 	õ �D”L×$Ò$ WÐ.FÑGÔGÑHÔH×NÒNÑPÔPÐlÐTlˆÝ�D”L×$Ò$ WÐ.FÑGÔGÑHÔH×NÒNÑPÔPÐlÐTlˆÝØŒL×Ò˜ZÑ(Ô(ð +ÝÐ.Ñ/Ô/ð+à*ñ
ô 
÷ Š%‰'Œ'—&’&˜‘+”+ð	 	ð " V¨T NÐ#3Ð4Ð5à'. ià!Ø-°¸UÐ/Cð$ð!ð!ð !ð

ð 

ˆð Ð@Ð@ 5Ð@Ð@Ð@ˆð	Tð 	Tð 	Tð 	Tð 	Tð 	Tð 	Tð 	Tð 	Tð 	Tð 	Tõ6 ˜;˜;™=œ=Ð*@ÑAÔAÐAÐAÐAÐAÐAÐAÐAÐAÐAr!   Nr\   r]   r“   r    r!   r   r³   r³   9  sc   € € € € € ðð ð €Kàð
ð 
ð 
ñ „\ð
ðBBð BBð BBð BBð BBð BBr!   r³   rs   c                  óN   — e Zd ZdZdZedd„¦   «         Zdd	„Zdd„Zdd„Z	dd„Z
dS )ÚXAIStreameru¸  xAI WebSocket TTS â†’ binary PCM frames (24 kHz mono int16).

    Salvaged from PR #47588 (@Cdddo): xAI's chunked TTS API is
    WebSocket-only (``wss://api.x.ai/v1/tts``). Credentials route through
    ``resolve_xai_http_credentials`` (OAuth or XAI_API_KEY), same as the
    sync ``_generate_xai_tts`` path. The async WS loop is bridged to the
    sync iterator contract via ``_collect_async`` â€” the seam unit tests
    monkeypatch.
    rK   r   r#   c                 óÌ   — 	 ddl m}   | ¦   «         }t          t          |                     d¦  «        pd¦  «                             ¦   «         ¦  «        S # t          $ r Y dS w xY w)Nr   ©Úresolve_xai_http_credentialsrŠ   r   F)Útools.xai_httprî   r#   r   rk   r;   r   )rî   Úcredss     r   rU   zXAIStreamer.availablež  s|   € ð	ØCÐCÐCÐCÐCÐCà0Ð0Ñ2Ô2ˆEÝ�˜EŸIšI iÑ0Ô0Ð6°BÑ7Ô7×=Ò=Ñ?Ô?Ñ@Ô@Ð@øÝð 	ð 	ð 	Ø�5�5ð	øøøs   ‚AA Á
A#Á"A#rW   r   rX   c              #  óv   K  — t          t          |                      |¦  «        ¦  «        d¦  «        E d {V —† d S )NzxAI streaming TTS)r¨   ÚiterÚ_collect_asyncrZ   s     r   r[   zXAIStreamer.stream¨  sE   è è € Ý�4 × 3Ò 3°DÑ 9Ô 9Ñ:Ô:Ð<OÑPÔPÐPÐPÐPÐPÐPÐPÐPÐPÐPr!   úList[bytes]c                óZ   — dd l }|                     |                      |¦  «        ¦  «        S )Nr   )ÚasyncioÚrunÚ_drain_async)r0   rW   rö   s      r   ró   zXAIStreamer._collect_async­  s+   € Øˆˆˆà�{Š{˜4×,Ò,¨TÑ2Ô2Ñ3Ô3Ð3r!   c              ƒ  ót   K  — g }|                       |¦  «        2 3 d {V —†}|                     |¦  «         Œ6 |S r   )Ú_async_framesr<   )r0   rW   ÚframesÚframes       r   rø   zXAIStreamer._drain_async²  s`   è è € Ø ˆØ×-Ò-¨dÑ3Ô3ð 	!ð 	!ð 	!ð 	!ð 	!ð 	!ð 	!�%Ø�MŠM˜%Ñ Ô Ð Ð ð 4àˆs   š7c           	    ó¸  K  — dd l }dd l}ddlm} ddlm}  |¦   «         }t          |                     d¦  «        pd¦  «                             ¦   «         }|st          d¦  «        ‚t          | j
                             d|¦  «        ¦  «                             ¦   «         p|}t          | j
                             d¦  «        pd	¦  «                             ¦   «         }	|                     |	d
d|› �i¬¦  «        4 ƒd {V —†	 }
|
                     |                     ||ddœ¦  «        ¦  «        ƒ d {V —† 	 	 |
                     ¦   «         ƒ d {V —†}t          |t           t"          t$          f¦  «        rt!          |¦  «        W V — ŒP	 |                     |¦  «        }n1# t(          t*          f$ r |dk    rY d d d ¦  «        ƒd {V —† d S Y Œ”w xY w|                     d¦  «        }|dk    r	 d d d ¦  «        ƒd {V —† d S |dk    rYt,                               d|                     d¦  «        p|                     d¦  «        p|¦  «         	 d d d ¦  «        ƒd {V —† d S �Œ'# t0          $ r^}|j        j        dk    rY d }~d d d ¦  «        ƒd {V —† d S t,                               d|¦  «         Y d }~d d d ¦  «        ƒd {V —† d S d }~ww xY w# 1 ƒd {V —†swxY w Y   d S )Nr   )ÚDEFAULT_XAI_VOICE_IDrí   rŠ   r   z$No xAI credentials for streaming TTSr‹   Ústreaming_urlzwss://api.x.ai/v1/ttsÚAuthorizationzBearer )Úextra_headersr¡   )rW   r‹   r£   TÚdoneÚtypeÚerrorzxAI WS error envelope: %sÚmessageÚConnectionClosedzxAI WS receive failed: %s)rÌ   Ú
websocketsr   rþ   rï   rî   r   rk   r;   ÚRuntimeErrorrR   ÚconnectÚsendÚdumpsÚrecvÚ
isinstanceÚbytesÚ	bytearrayÚ
memoryviewrØ   rÙ   rÜ   rl   r¯   r   Ú	__class__rE   )r0   rW   rã   r  rþ   rî   rð   rŠ   rŸ   Úws_urlÚwsr  ÚenvelopeÚetypern   s                  r   rú   zXAIStreamer._async_frames¸  sÊ  è è € ØÐÐÐàÐÐÐà7Ð7Ð7Ð7Ð7Ð7Ø?Ð?Ð?Ð?Ð?Ð?à,Ð,Ñ.Ô.ˆÝ�e—i’i 	Ñ*Ô*Ð0¨bÑ1Ô1×7Ò7Ñ9Ô9ˆØð 	GÝÐEÑFÔFÐFÝ�D”L×$Ò$ ZÐ1EÑFÔFÑGÔG×MÒMÑOÔOÐgÐSgˆÝØŒL×Ò˜_Ñ-Ô-ÐHÐ1Hñ
ô 
ç
Š%‰'Œ'ð 	ð ×%Ò%Ø ?Ð4G¸gÐ4GÐ4GÐ"Hð &ñ 
ô 
ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	àØ—'’'˜%Ÿ+š+ØØ!Ø#(ð'ð 'ñ ô ñ ô ð ð ð ð ð ð ð ð
ðØ$&§G¢G¡I¤I˜o˜o˜o˜o˜o˜o�GÝ! '­Eµ9½jÐ+IÑJÔJð !Ý# G™nœnÐ,Ð,Ð,Ð,Ø ð!Ø#(§;¢;¨wÑ#7Ô#7˜˜øÝ&­	Ð2ð !ð !ð !Ø" fÒ,Ð,Ø"ð%	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð& !˜ð!øøøð %ŸLšL¨Ñ0Ô0�EØ ’�Øð-	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð.  Ò'Ð'ÝŸšÐ'BØ'/§|¢|°GÑ'<Ô'<Ð'cÀÇÂÈYÑ@WÔ@WÐ'cÐ[cñeô eð eàð5	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ñøõ$ ð ð ð Ø”=Ô)Ð-?Ò?Ð?Ø�F�F�Fð;	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	õ< —’Ð:¸CÑ@Ô@Ð@Ø���ð?	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	øøøøð6øøøð7	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð 	ð 	sz   Ä3K	Ä6AIÆFÆIÆGÆ4IÇIÇ
GÇIÇ=AIÉIÉ
KÉ(KÉ8K	ÊKÊ*K	ËKËK	Ë	
KËKNr\   r]   )rW   r   r   rô   )rW   r   )rE   rF   rG   rH   rL   r_   rU   r[   ró   rø   rú   r    r!   r   rë   rë   �  s—   € € € € € ðð ð €Kàðð ð ñ „\ððQð Qð Qð Qð
4ð 4ð 4ð 4ð
ð ð ð ð0ð 0ð 0ð 0ð 0ð 0r!   rë   )r   r   r   r   r   r   )r   r   r\   )ra   r   r   rb   )ra   r   rQ   r   r   ri   r   )rQ   r   ru   rv   r   ri   )r   r   )r«   rX   r¬   r   r   rX   )1rH   Ú
__future__r   ÚloggingÚrer   Úabcr   r   Útypingr   r   r   r	   r
   Útools.tool_backend_helpersr   r   r   r   r   Ú	getLoggerrE   rl   r®   r   ÚSPEECH_INTERRUPTED_NOTEr%   r   r^   r"   r'   Úcompiler7   ÚDOTALLr5   r*   rJ   r`   rh   ro   rt   r   r�   r–   r˜   r¨   r³   rë   r    r!   r   ú<module>r      sŒ  ððð ð ð* #Ð "Ð "Ð "Ð "Ð "à €€€Ø 	€	€	€	Ø €€€Ø #Ð #Ð #Ð #Ð #Ð #Ð #Ð #Ø ;Ð ;Ð ;Ð ;Ð ;Ð ;Ð ;Ð ;Ð ;Ð ;Ð ;Ð ;Ð ;Ð ;à CÐ CÐ CÐ CÐ CÐ CØ IÐ IÐ IÐ IÐ IÐ IÐ IÐ IÐ IÐ Ià	ˆÔ	˜8Ñ	$Ô	$€ð -Ð ð,ð ,ð ,ð ,ð2 Rð ð Ð Ø#'€Ð 'Ð 'Ð 'Ñ 'ð'ð 'ð 'ð 'ð
Gð Gð Gð Gð "�r”zÐ"AÑBÔBÐ Ø�"”*Ð6¸b¼iÐHÑHÔH€ð#&ð #&ð #&ð #&ð #&ñ #&ô #&ð #&ðTMð Mð Mð Mð M˜3ñ Mô Mð Mð* 46€	Ð 5Ð 5Ð 5Ñ 5ðð ð ð ð	ð 	ð 	ð 	ð  !JÐ IÐ IÐ Ð IÐ IÐ IÑ Ið
  $ð.ð .ð .ð .ð .ðL 
€ˆ,ÑÔð
ð 
ð 
ð 
ð 
Ð-ñ 
ô 
ñ Ôð
ðD+ð +ð +ð +ð 
€ˆ(ÑÔðNð Nð Nð Nð NÐ)ñ Nô Nñ ÔðNð>ð ð ð ð" 
€ˆ(ÑÔðSBð SBð SBð SBð SBÐ)ñ SBô SBñ ÔðSBðl 
€ˆ%�„ðWð Wð Wð Wð WÐ&ñ Wô Wñ „ðWð Wð Wr!   