+
    iHàj#%  ã                  ó  a € 0 t $ R t^ RIHt ^ RIt^ RIHt . ROtRtR]	R&   ]
;QJ d    . R	 ] 4       F  NK  	  5M! R	 ] 4       4      tR]	R&   ]
;QJ d    . R
 ] 4       F  NK  	  5M! R
 ] 4       4      tR]	R&    ! R R4      tR# )a�  Stateful scrubber for reasoning/thinking blocks in streamed assistant text.

The regex ``_strip_think_blocks`` is correct for a complete string but, run per-delta, erases an
opening ``<think>`` that arrives alone, so downstream state machines leak reasoning. This class
holds partial tags at delta boundaries until resolved; ``flush()`` releases held-back prose that
was not a tag; ``reset()`` at the top of each turn. An open tag only starts a block at a block
boundary (stream start / after a newline / whitespace-only line so far), so prose that *mentions*
``<think>`` is not suppressed; closed pairs are always suppressed (intentional).
)ÚannotationsN)ÚTupleÚTHINK_CLOSE_TAGSÚTHINK_OPEN_TAGSÚTHINK_TAG_NAMESÚStreamingThinkScrubberútuple[str, ...]c              #  óJ   "  € T F  pR VP                  4        R2x € K  	  R# 5i)Ú<Ú>N©Úlower©Ú.0Únames   & Ú9/home/ubuntu/.hermes/hermes-agent/agent/think_scrubber.pyÚ	<genexpr>r      s   é € Ð(YÉÀ¨1¨T¯Z©Z«\¨N¸!Õ)<Ëùó   ‚!#c              #  óJ   "  € T F  pR VP                  4        R2x € K  	  R# 5i)ú</r   Nr   r   s   & r   r   r      s   é € Ð)[É?À4¨B¨t¯z©z«|¨n¸AÕ*>Ë?ùr   c                  ó¬  € ] tR t^t$ Rt]tR]R&   ]t	R]R&   ]]	,           t
R]R&   ]! R ]
 4       4      tR]R&   ]P                  ! R	R
P                  R ]	 4       4      ,           R,           ]P                   4      tR R ltR R ltR R ltR R ltR R ltR R lt]R R l4       tR R ltR R ltR R  lt]R! R" l4       t]R# R$ l4       tR%t R&# )'r   uY  Stateful scrubber for streaming reasoning/thinking blocks.

State: ``_in_block`` (inside an open block; text discarded), ``_buf`` (held-back partial-tag
tail), ``_last_emitted_ended_newline`` (True iff the last emission ended with ``\n`` or nothing
was emitted yet â€” decides whether an open tag at buffer position 0 sits at a block boundary).
r   Ú
_OPEN_TAGSÚ_CLOSE_TAGSÚ	_ALL_TAGSc              #  	ó8   "  € T F  p\        V4      x € K  	  R # 5i©N)Úlen)r   Útags   & r   r   Ú StreamingThinkScrubber.<genexpr>+   s   é € Ð:±	¨œC ŸH˜H³	ùs   ‚ÚintÚ_MAX_TAG_LENz(?:Ú|c              #  	óN   "  € T F  p\         P                  ! V4      x € K  	  R # 5ir   )ÚreÚescape)r   Úts   & r   r   r   .   s   é € Ð;©{¨!œŸš 1Ÿ˜«{ùs   ‚#%z)[ \t\n\r]*c               ó   € V ^8„  d   QhRR/# ©é   ÚreturnÚNone© )Úformats   "r   Ú__annotate__Ú#StreamingThinkScrubber.__annotate__1   s   € ÷ ñ ˜$ñ ó    c                	ó&   € V P                  4        R # r   )Úreset©Úselfs   &r   Ú__init__ÚStreamingThinkScrubber.__init__1   s   € Ø�
‰
Žr/   c               ó   € V ^8„  d   QhRR/# r'   r+   )r,   s   "r   r-   r.   4   s   € ÷ #ñ #�tñ #r/   c                ó>   € RV n         RV n        RV n        RV n        R# )z4Reset all state.  Call at the top of every new turn.FÚ TN)Ú	_in_blockÚ_bufÚ_last_emitted_ended_newlineÚlast_hiddenr2   s   &r   r1   ÚStreamingThinkScrubber.reset4   s    € à$ˆŒØˆŒ	Ø15ˆÔ(à "ˆÖr/   c               ó$   € V ^8„  d   QhRRRRRR/# )r(   Úoutú	list[str]ÚtextÚstrr)   r*   r+   )r,   s   "r   r-   r.   <   s&   € ÷ Cñ C˜ð C¨#ð C°$ñ Cr/   c                óŠ   € V P                  V4      pV'       d*   VP                  V4       VP                  R4      V n        R# R# )zVAppend visible prose to *out* (orphan close tags stripped) and track the newline flag.Ú
N)Ú_strip_orphan_close_tagsÚappendÚendswithr;   )r3   r?   rA   s   &&&r   Ú_emitÚStreamingThinkScrubber._emit<   s7   € à×,Ñ,¨TÓ2ˆßØ�J‰J�tÔØ/3¯}©}¸TÓ/BˆDÖ,ñ r/   c               ó    € V ^8„  d   QhRRRR/# ©r(   rA   rB   r)   r+   )r,   s   "r   r-   r.   C   s   € ÷ -ñ -˜ð - ñ -r/   c           
     óö  € RV n         V'       g   R# V P                  V,           pRV n        . p. pV'       Edœ   V P                  '       dy   V P                  W P                  4      w  rVVR8X  d-   VP                  V P                  W P                  4      4       EM9VP                  VRV 4       W%V,           R pRV n        K“  V P                  V4      pV P                  W#4      w  r‰Ve�   VR8X  g   V^ ,          V8:  dz   V P                  W2RV^ ,           4       VP                  W"P                  RV^ ,          4      ^,           VP                  RV^ ,          V^,          4       4       W'^,          R pEKI  VR8w  d*   V P                  W2RV 4       RV n        W(V	,           R pEKy  V P                  W0P                  W P                  4      4        RP                  V4      V n         RP                  V4      # )z_Feed one delta; return the scrubbed visible portion ("" when it is all reasoning or held back).r8   NFr   r
   Téÿÿÿÿ)r<   r:   r9   Ú_find_first_tagr   rF   Ú_hold_partialÚ_find_earliest_closed_pairÚ_find_open_at_boundaryrH   ÚindexÚrindexr   Újoin)
r3   rA   Úbufr?   ÚhiddenÚ	close_idxÚ	close_lenÚpairÚopen_idxÚopen_lens
   &&        r   ÚfeedÚStreamingThinkScrubber.feedC   sº  € àˆÔßÙØ�i‰i˜$ÕˆØˆŒ	ØˆØˆçˆcØ�~�~ˆ~Ø'+×';Ñ';¸C×AQÑAQÓ'RÑ$�	Ø ”?à—M‘M $×"4Ñ"4°S×:JÑ:JÓ"KÔLÙØ—‘˜c * 9˜oÔ.Ø iÕ/Ð0Ð1�Ø!&�”Ùð
 ×2Ñ2°3Ó7ˆDØ!%×!<Ñ!<¸SÓ!FÑˆHØÒ X°¤^°t¸AµwÀ(Ô7JØ—
‘
˜3 H T¨!¥W Ô.à—‘˜c§)¡)¨C°°aµÓ"9¸AÕ"=¸c¿j¹jÈÈdÐSTÍgÐW[Ð\]ÕW^Ó>_Ð`ÔaØ˜q�'˜(�m�ÚØ˜2Œ~Ø—
‘
˜3 I X Ô/Ø!%�”Ø XÕ-Ð.Ð/�Úð �J‰J�s×.Ñ.¨s·N±NÓCÔDØàŸ7™7 6›?ˆÔØ�w‰w�s‹|Ðr/   c               ó$   € V ^8„  d   QhRRRRRR/# )r(   rU   rB   Útagsr   r)   r+   )r,   s   "r   r-   r.   r   s!   € ÷ ,ñ , ð ,¨Oð ,Àñ ,r/   c                ól   € V P                  W4      pV'       d   W) R MRV n        V'       d   VRV)  # T# )zPMove a trailing partial-tag prefix of *buf* into ``_buf``; return the remainder.Nr8   )Ú_max_partial_suffixr:   )r3   rU   r_   Úhelds   &&& r   rO   Ú$StreamingThinkScrubber._hold_partialr   s8   € à×'Ñ'¨Ó2ˆß#'�C˜˜‘K¨RˆŒ	ß"ˆs�6�T�Eˆ{Ð+¨Ð+r/   c               ó   € V ^8„  d   QhRR/# )r(   r)   rB   r+   )r,   s   "r   r-   r.   x   s   € ÷ 	Cñ 	C�sñ 	Cr/   c                ó¢   € V P                   '       d   RMV P                  pRV n        RV n         RV n        V'       d   V P                  V4      # R# )un  End-of-stream flush: inside an unterminated block the held-back content is discarded (leaking
partial reasoning is worse than a truncated answer), otherwise the tail is emitted verbatim.
Always resets the boundary flag â€” intra-turn retries flush then stream again without ``reset()``,
and a stale False flag made the new stream's opening ``<think>`` look mid-line.r8   FT)r9   r:   r;   rE   )r3   Útails   & r   ÚflushÚStreamingThinkScrubber.flushx   sE   € ð
 —^—^�^‰r¨¯©ˆØˆŒ	ØˆŒØ+/ˆÔ(ß6:ˆt×,Ñ,¨TÓ2ÐBÀÐBr/   c               ó$   € V ^8„  d   QhRRRRRR/# )r(   rU   rB   r_   r   r)   útuple[int, int]r+   )r,   s   "r   r-   r.   †   s!   € ÷ .ñ .˜Sð .¨ð .¸Oñ .r/   c                óÀ   € V P                  4       pV Uu. uF)  q2P                  V4      ;pR8w  g   K  V\        V4      3NK+  	  ppV'       d   \        V4      # R# u upi )zOReturn (earliest_index, tag_length) over *tags* (case-insensitive), or (-1, 0).rM   ©rM   é    )r   Úfindr   Úmin)rU   r_   Ú	buf_lowerr   ÚidxÚhitss   &&    r   rN   Ú&StreamingThinkScrubber._find_first_tag…   sX   € ð —I‘I“Kˆ	Ù+/ÓV©4 C¿>¹>È#Ó;NÐ4N°CÐSUÑ3U”�”c˜#“h“©4ˆÐVß Œs�4‹yÐ- gÐ-ùò Ws
   •A³Ac               ó   € V ^8„  d   QhRR/# )r(   rU   rB   r+   )r,   s   "r   r-   r.   Œ   s   € ÷ 	-ñ 	-¨cñ 	-r/   c                ón  € VP                  4       p. p\        V P                  V P                  4       Fk  w  rEVP	                  V4      pVR8w  d"   VP	                  WV\        V4      ,           4      MRpVR8w  g   KI  VP                  Wg\        V4      ,           34       Km  	  V'       d   \        V4      # R# )zg(start_idx, end_idx) of the earliest ``<tag>...</tag>`` pair (non-greedy, case-insensitive), else None.NrM   )r   Úzipr   r   rn   r   rF   ro   )r3   rU   rp   ÚpairsÚopen_tagÚ	close_tagrZ   rW   s   &&      r   rP   Ú1StreamingThinkScrubber._find_earliest_closed_pairŒ   s’   € à—I‘I“Kˆ	ØˆÜ#& t§¡¸×8HÑ8HÖ#IÑˆHØ —~‘~ hÓ/ˆHØOWÐ[]Ì~˜	Ÿ™ y¼SÀ»]Õ2JÔKÐceˆIØ˜BŽØ—‘˜h´C¸	³NÕ(BÐCÖDñ	 $J÷
 #Œs�5‹zÐ,¨Ð,r/   c               ó$   € V ^8„  d   QhRRRRRR/# )r(   rU   rB   Úalready_emittedr@   r)   rj   r+   )r,   s   "r   r-   r.   —   s!   € ÷ 
.ñ 
.¨#ð 
.À	ð 
.Èoñ 
.r/   c                óT  € VP                  4       p. pV P                   Fr  pVP                  V4      pVR8w  d3   V P                  WV4      '       g   VP                  WV^,           4      pK9  VR8w  g   KV  VP	                  V\        V4      34       Kt  	  V'       d   \        V4      # R# )zCReturn the earliest block-boundary open-tag (idx, len), or (-1, 0).rM   rl   )r   r   rn   Ú_is_block_boundaryrF   r   ro   )r3   rU   r|   rp   rr   r   rq   s   &&&    r   rQ   Ú-StreamingThinkScrubber._find_open_at_boundary—   s‡   € à—I‘I“Kˆ	ØˆØ—?”?ˆCØ—.‘. Ó%ˆCØ˜”) D×$;Ñ$;¸CÀo×$VÒ$VØ—n‘n S°­'Ó2’Ø�bŽyØ—‘˜S¤# c£(˜OÖ,ñ #÷ !Œs�4‹yÐ- gÐ-r/   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r(   rU   rB   rq   r   r|   r@   r)   Úboolr+   )r,   s   "r   r-   r.   £   s/   € ÷ 	dñ 	d cð 	d°ð 	dÀið 	dÐTXñ 	dr/   c                óö   € V'       d   VR,          P                  R4      MV P                  pV^ 8X  d   V# VRV pVP                  R4      pVR8X  d   TMR;'       d    WV^,           R P                  4       R8H  # )a  True iff *idx* is a block boundary: position 0 after a newline-terminated (or no) prior emission,
or any position whose preceding text on the current line is whitespace-only (when no newline
precedes it in *buf*, the prior emission must also have ended with a newline).rD   NTr8   rM   )rG   r;   ÚrfindÚstrip)r3   rU   rq   r|   Úprior_newlineÚ	precedingÚlast_nls   &&&&   r   r~   Ú)StreamingThinkScrubber._is_block_boundary£   sz   € ÷ ?N˜¨Õ+×4Ñ4°TÔ:ÐSW×SsÑSsˆØ�!Œ8Ø Ð Ø˜˜�Iˆ	Ø—/‘/ $Ó'ˆØ!(¨B¤‘°D×cÐc¸iÐRSÍÈÐ>U×>[Ñ>[Ó>]ÐacÑ>cÐcr/   c               ó$   € V ^8„  d   QhRRRRRR/# )r(   rU   rB   r_   r   r)   r   r+   )r,   s   "r   r-   r.   ¯   s!   € ÷ ñ  cð °ð ÀSñ r/   c                ó2  aa€ VP                  4       p\        \        \        V4      V P                  ^,
          4      ^ R4       FO  oVS) R o\
        ;QJ d     VV3R lV 4       F  '       g   K   RM	  RM! VV3R lV 4       4      '       g   KM  Su # 	  ^ # )zfLongest buf-suffix that is a strict prefix of any tag (full matches are real tags, handled elsewhere).Nc              3  ór   <"  € T F,  p\        V4      S8„  ;'       d    VP                  S4      x € K.  	  R # 5ir   )r   Ú
startswith)r   r   ÚiÚsuffixs   & €€r   r   Ú=StreamingThinkScrubber._max_partial_suffix.<locals>.<genexpr>´   s-   øé € ÐKÁd¸s”3�s“8˜a‘<×:Ð: C§N¡N°6Ó$:Ô:Ãdùs   ƒ7�7TFrM   )r   Úrangero   r   r    Úany)ÚclsrU   r_   rp   r�   rŽ   s   &&& @@r   ra   Ú*StreamingThinkScrubber._max_partial_suffix®   sr   ù€ ð —I‘I“Kˆ	Ü”sœ3˜y›>¨3×+;Ñ+;¸aÕ+?Ó@À!ÀRÖHˆAØ ˜r˜s�^ˆFß‹sÕKÁdÓK�s�sŠsÕKÁdÓK×KÔKØ’ñ Iñ r/   c               ó    € V ^8„  d   QhRRRR/# rK   r+   )r,   s   "r   r-   r.   ¹   s   € ÷ Lñ L¨Cð L°Cñ Lr/   c                óL   € RV9   d   V P                   P                  RV4      # T# )zPRemove close tags with no matching open (always noise) plus trailing whitespace.r   r8   )Ú_ORPHAN_CLOSE_REÚsub)r’   rA   s   &&r   rE   Ú/StreamingThinkScrubber._strip_orphan_close_tags¸   s(   € ð 6:¸T´\ˆs×#Ñ#×'Ñ'¨¨DÓ1ÐKÀtÐKr/   )r:   r9   r;   r<   N)!Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r   r   Ú__annotations__r   r   r   Úmaxr    r#   ÚcompilerT   Ú
IGNORECASEr–   r4   r1   rH   r\   rO   rg   ÚstaticmethodrN   rP   rQ   r~   Úclassmethodra   rE   Ú__static_attributes__r+   r/   r   r   r      sá   ‡ ñð #2€J�Ó1Ø#3€K�Ó3Ø!+¨kÕ!9€IˆÓ9ÙÑ:±	Ó:Ó:€L�#Ó:à—z’zØ�—‘Ñ;©{Ó;Ó;Õ;¸nÕLÈbÏmÉmóÐõõ#õCõ-õ^,õ	Cð ô.ó ð.õ	-õ
.õ	dð ôó ðð ôLó ôLr/   )r   r   r   r   )	ÚthinkÚthinkingÚ	reasoningÚthoughtÚREASONING_SCRATCHPADu   æ€�è€ƒu   å��æ€�u   æŽ¨ç�†u   æŽ¨æ•²)Ú__conditional_annotations__r�   Ú
__future__r   r#   Útypingr   Ú__all__r   rž   Útupler   r   r   )rª   s   @r   Ú<module>r¯      s€   øðôõ #ã 	Ý â
^€ð$€�ó ÷ $) 5Ñ(YÉÓ(Y§5¡5Ñ(YÉÓ(YÓ#Y€�Ó Yß$) EÑ)[É?Ó)[§E¡EÑ)[É?Ó)[Ó$[Ð �/Ó [÷\Ló \Lr/   