Ë
    áß²j×�  ã                  ó  — U d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
mZ ddlmZ  ej                  e«      Z ej"                  d«      Z eh d£«      ZdHd	„Z ej"                  d
«      ZdIdJd„ZdHd„ZdKd„ZdLd„ZdMd„Z eee¬«      Z eeed¬«      Z eee¬«      Z eeed¬«      ZeZ dNd„Z!dOd„Z"dZ#dPd„Z$dddœZ%dQd„Z&dRdSd„Z'dTdUd„Z(ddd d!œZ)dVd"„Z*dWd#„Z+h d$£Z,dXd%„Z-d&Z.d'Z/dYd(„Z0dZd)„Z1dZd*„Z2g d+¢Z3d[d,„Z4d\d-„Z5d]d^d.„Z6d_d/„Z7d`d0„Z8dad1„Z9dbd2„Z:dcd3„Z;d4 ed5d6h«       e«       d7d8fd9 e«        ed9h«      d:d;fd< e«        ed=h«      d>d?ffZ<d@e=dA<   e<D � ci c]  } | d   | “Œ
 c} Z>dddB„Z?dedC„Z@dfdD„ZAdgdE„ZBdhdF„ZCdidG„ZDyc c} w )ja+  Message and tool-payload sanitization helpers (pure; documented in-place mutation).

Walk OpenAI-format message lists and structured payloads, repairing or stripping
characters that would crash ``json.dumps`` in the OpenAI SDK or be rejected upstream.
``run_agent`` re-exports them for old imports.
é    )ÚannotationsN)Úpartial)ÚAnyÚCallable)Ú_provider_model_keyz[\ud800-\udfff]>   ÚnameÚroleÚcontentÚ
tool_callsc                ó.   — t         j                  d| «      S )zHReplace lone surrogate code points with U+FFFD; no-op when none present.u   ï¿½)Ú_SURROGATE_REÚsub©Útexts    ú?/home/ubuntu/.hermes/hermes-agent/agent/message_sanitization.pyÚ_sanitize_surrogatesr      s   € ä×Ñ˜X tÓ,Ð,ó    z[A-Za-z0-9_-]{1,64}c           
     óø   — t        | t        «      s|S t        j                  | «      r| S t	        j
                  ddt	        j
                  dd| j                  «       «      «      j                  d«      }|dd xs |S )u�  Coerce a *replayed* tool/function name to ``^[A-Za-z0-9_-]{1,64}$``. Valid names are returned
    as-is (identity â€” prompt-cache safe); invalid runs collapse to ``_`` and the result is cut at 64;
    empty/all-invalid â†’ ``fallback``. Deterministic, so the same stored name always renders the same
    bytes. Never apply to live tool definitions (schema names must match the dispatch registry).z_+Ú_z[^A-Za-z0-9_-]Né@   )Ú
isinstanceÚstrÚ_VALID_TOOL_NAME_REÚ	fullmatchÚrer   Ústrip)r   ÚfallbackÚcoerceds      r   Úcoerce_tool_namer   (   si   € ô
 �dœCÔ ØˆÜ×$Ñ$ TÔ*ØˆÜ�f‰f�U˜C¤§¡Ð(9¸3ÀÇ
Á
ÃÓ!MÓN×TÑTÐUXÓY€GØ�3�Bˆ<Ò#˜8Ð#r   c                óF   — | j                  dd¬«      j                  d«      S )uS   Drop non-ASCII characters â€” last resort for ASCII-only system encodings (LANG=C).ÚasciiÚignore©Úerrors)ÚencodeÚdecoder   s    r   Ú_strip_non_asciir'   5   s    € à�;‰;�w xˆ;Ó0×7Ñ7¸Ó@Ð@r   c                óš   — t        | t        «      r| j                  |«      n| |   }t        |t        «      r ||«      n|}||k(  ry|| |<   y)zGApply ``fix`` to ``container[key]`` if it is a str; True if it changed.FT)r   ÚdictÚgetr   )Ú	containerÚkeyÚfixÚvalueÚfixeds        r   Ú_fix_str_fieldr0   :   sI   € ä",¨Y¼Ô"=ˆI�M‰M˜#ÔÀ9ÈSÁ>€EÜ$ U¬CÔ0‰C�ŒJ°e€EØ�‚~ØØ€Iˆc�NØr   c                ól  — d}| g}|r¬|j                  «       }t        |t        «      r|j                  «       nt        |t        «      rt        |«      nd}t	        |«      D ]N  \  }}t        |t        «      r|t        |||«      z  }Œ't        |t        t        f«      sŒ>|j                  |«       ŒP |rŒ¬|S )zHApply ``fix`` to every str inside nested dict/list ``payload`` in-place.F© )	Úpopr   r)   ÚitemsÚlistÚ	enumerater   r0   Úappend)Úpayloadr-   ÚfoundÚstackÚnoder4   r,   r.   s           r   Ú_sanitize_structurer<   D   sž   € à€EØˆI€EÙ
Ø�y‰y‹{ˆÜ *¨4´Ô 6�—
‘
”ÌzÐZ^Ô`dÔOe¼IÀd¼OÐkmˆÜ˜u›+ò 	$‰JˆC�Ü˜%¤Ô%Øœ¨¨c°3Ó7Ñ7‘Ü˜E¤D¬$ <Õ0Ø—‘˜UÕ#ð		$ò ð €Lr   c               óœ  — ddl m} d}| D �]³  }t        |t        «      sŒd}|j	                  d«      }t        |t
        «      r#|D �cg c]  }t        |t        «      sŒ|df‘Œ c}nd}	|	�|	n|dfg}
|
j                  |df«       |j	                  d«      }t        |t
        «      r|nd	D ]e  }t        |t        «      r|j	                  d
«      nd}|
|rt        |t        «      r|dfgng z  }
|
t        |t        «      r|r|dfgng |dfgz   ng z  }
Œg |
D ]  \  }}|t        |||«      z  }Œ |j                  «       D �cg c]  }|d   t        vsŒ|‘Œ c}D ]O  \  }}t        |t        «      r|t        |||«      z  }Œ'|sŒ*t        |t        t
        f«      sŒA|t        ||«      z  }ŒQ |s�Œ |j                  |d«       d}�Œ¶ |S c c}w c c}w )a  Apply ``fix`` to the string fields of every message dict in-place (content / part text,
    name, tool_call arguments, non-core top-level str fields). ``deep=True`` adds tool_call ids,
    function names, and NESTED non-core fields (``reasoning_details`` from byte-level models).r   ©Ú_DB_PERSISTED_MARKERFr
   r   Nr   r   r2   ÚfunctionÚidÚ	argumentsT)Úagent.context_compressorr?   r   r)   r*   r5   r7   r0   r4   Ú_MESSAGE_CORE_KEYSr   r<   r3   )Úmessagesr-   Údeepr?   r9   ÚmsgÚ	msg_foundr
   ÚpÚpartsÚfieldsr   ÚtcÚfnr+   r,   Úkvr.   s                     r   Ú_sanitize_messagesrO   S   sß  € õ >à€EØó ˆÜ˜#œtÔ$ØØˆ	Ø—'‘'˜)Ó$ˆÜISÐT[Ô]aÔIb gÖE ´¸A¼tÕ1D�!�V’ÓEÐhlˆØÐ+‘°3¸	Ð2BÐ1CˆØ�‰�s˜F�mÔ$Ø—W‘W˜\Ó*ˆ
Ü *¨:´tÔ <‘*À"ò 	mˆBÜ'1°"´dÔ';�—‘˜
Ô#ÀˆBØ¡d¬z¸"¼dÔ/C˜˜D�z‘lÈÑKˆFØÔPZÐ[]Ô_cÔPd©˜˜V˜‘~°2¸2¸{Ð:KÐ9LÒLÐjlÑl‰Fð	mð %ò 	=‰NˆI�sØœ¨	°3¸Ó<Ñ<‰Ið	=à(+¯	©	«ÖW "°r¸!±uÔDVÒ7Vš2ÒWò 	=‰JˆC�Ü˜%¤Ô%Øœ^¨C°°cÓ:Ñ:‘	Úœ* U¬T´4¨LÕ9ØÔ0°¸Ó<Ñ<‘	ð		=ó
 ð �G‰GÐ(¨$Ô/ØŠEð1ð2 €Lùò) Fùò Xs   ÁGÁGÄ=G	ÅG	)r-   T)r-   rF   Fc                ó¸   — t        |«       | j                  rC|j                  d«      �&|d   t        | dd«      u rddlm}  ||d   «      |d<   t        |«       yy)a‰  Outbound-request chokepoint for every built kwargs dict (main loop and iteration summary).

    Tool descriptions, extra_body and kwargs strings can carry invalid code points that
    providers reject with a non-retryable 400 (#50959); one in-place walk makes the whole
    payload json.dumps()-safe. The ASCII strip is opt-in via the recovery flag set after an
    ASCII-codec rejection.
    ÚtoolsNr   )Ú_clone_message_for_send)Ú_sanitize_structure_surrogatesÚ_force_ascii_payloadr*   ÚgetattrÚagent.conversation_looprR   Ú_sanitize_structure_non_ascii)ÚagentÚ
api_kwargsrR   s      r   Úsanitize_outbound_kwargsrZ      sb   € ô # :Ô.Ø×!Ò!ð
 �>‰>˜'Ó"Ð.°:¸gÑ3FÌ'ÐRWÐY`ÐbfÓJgÑ3gåGá"9¸*ÀWÑ:MÓ"NˆJ�wÑÜ% jÕ1ð "r   c                óX  — g }d}d}|t        | «      k  r†| |   }|r3|dk(  r.|dz   t        | «      k  r|j                  | ||dz    «       |dz  }ŒH|dk(  r| }|j                  |rt        |«      dk  rdt        |«      d	›�n|«       |dz  }|t        | «      k  rŒ†d
j                  |«      S )z Escape literal control chars (0x00-0x1F) inside JSON string values as ``\uXXXX``
    (for llama.cpp-style output mixing control chars with other malformations).Fr   ú\é   é   ú"é    z\uÚ04xÚ )Úlenr7   ÚordÚjoin)ÚrawÚoutÚ	in_stringÚiÚchs        r   Ú%_escape_invalid_chars_in_json_stringsrk   •   s¼   € ð €CØ€IØ	€AØ
Œc�#‹hŠ,Ø�‰VˆÙ˜˜tš¨¨A©´°C³Ò(8Ø�J‰J�s˜1˜Q ™U�|Ô$Ø�‰FˆAØØ�Š9Ø%˜ˆIØ�
‰
©)¼¸B»À$º�Sœ˜R› ˜Ñ&ÈBÔOØ	ˆQ‰ˆð Œc�#‹h‹,ð �7‰7�3‹<Ðr   i † c                ób   — 	 t        j                  | «       y# t         j                  $ r Y yw xY w)NTF)ÚjsonÚloadsÚJSONDecodeErrorr   s    r   Ú	_loads_okrp   ­   s.   € ðÜ�
‰
�4ÔØøÜ×Ñò Ùðús   ‚ ˜.­.ú}ú])ú{ú[c                óD  — g }g }d}dt        | «      }}||k  rÓ| |   }|r*|dk(  r|j                  | ||dz    «       |dz  }Œ.|dk(  r‰d}n†|dk(  rd}n~|t        v r|j                  |«       nd|dv r`|d„ |D «       v rUt        |d	      |k7  r6|j                  t        |j                  «          «       t        |d	      |k7  rŒ6|j                  «        |j                  |«       |d
z  }||k  rŒÓ|rydj	                  |«      dj	                  d„ t        |«      D «       «      z   S )uP  Close a JSON prefix's open braces/brackets in stack order, ignoring delimiters
    inside string values (``{"code": "}"}`` keeps one open brace, not a balanced
    document). A closer that does not match the stack top but does match a deeper opener
    gets the missing inner closers inserted BEFORE it: ``{"a": [{"b": 1}}`` â†’ the model
    dropped the ``]`` and let the neighbouring ``}`` close in its place, so the counts
    balance and nothing can be appended. ``None`` when the text ends inside an
    unterminated string â€” that content is unrecoverable and must not be guessed.
    Fr   r\   r^   r_   Tz}]c              3  ó.   K  — | ]  }t         |   –— Œ y ­w©N©Ú_JSON_CLOSERS)Ú.0Úos     r   ú	<genexpr>z*_rebalance_json_closers.<locals>.<genexpr>Ò   s   è ø€ Ò"C¸¤=°Õ#3Ñ"Cùó   ‚éÿÿÿÿr]   Nrb   c              3  ó.   K  — | ]  }t         |   –— Œ y ­wrw   rx   )rz   rj   s     r   r|   z*_rebalance_json_closers.<locals>.<genexpr>Ú   s   è ø€ Ò!N¸¤-°Õ"3Ñ!Nùr}   )rc   r7   ry   r3   re   Úreversed)rf   rg   r:   rh   ri   Únrj   s          r   Ú_rebalance_json_closersr‚   ¸   s+  € ð €CØ€EØ€IØŒc�#‹h€q€AØ
ˆaŠ%Ø�‰VˆÙØ�TŠzØ—
‘
˜3˜q  Q¡˜<Ô(Ø�Q‘�ØØ�SŠyØ!‘	Ø�3ŠYØ‰IØ”=Ñ Ø�L‰L˜ÕØ�4‰Z˜BÑ"C¸UÔ"CÑCÜ  b¡	Ñ*¨bÒ0Ø—
‘
œ=¨¯©«Ñ5Ô6ô    b¡	Ñ*¨bÓ0à�I‰IŒKØ�
‰
�2ŒØ	ˆQ‰ˆð% ˆa‹%ñ& ØØ�7‰7�3‹<˜"Ÿ'™'Ñ!N¼hÀu»oÔ!NÓNÑNÐNr   c                óö  — t        | t        «      r| j                  «       nd}|st        j	                  d|«       y|dk(  rt        j	                  d|«       y	 t        j                  t        j                  |d¬«      d¬	«      }||k7  rt        j	                  d
|«       |S # t
        j                  t        t        f$ r Y nw xY wt        j                  dd|«      }t        |«      xs |}t        d«      D ]|  }t        |«      sh|j!                  d«      r#|j#                  d«      |j#                  d«      kD  s6|j!                  d«      r#|j#                  d«      |j#                  d«      kD  s n|dd }Œ~ t        |«      r t        j	                  d||dd |dd «       |S t%        |«      }||k7  r+t        |«      r t        j	                  d||dd |dd «       |S t        j	                  d||dt&         «       y)zµRepair malformed tool_call argument JSON (truncation, trailing commas, Python ``None``,
    control chars); ``"{}"`` if unrepairable so the request succeeds. Repairs log at WARNING.rb   z*Sanitized empty tool_call arguments for %sz{}ÚNonez0Sanitized Python-None tool_call arguments for %sF)Ústrict©ú,ú:)Ú
separatorsz>Repaired unescaped control chars in tool_call arguments for %sz,\s*([}\]])z\1é2   rq   rs   rr   rt   Nr~   u8   Repaired malformed tool_call arguments for %s: %s â†’ %séP   uA   Repaired control-char-laced tool_call arguments for %s: %s â†’ %suP   Unrepairable tool_call arguments for %s â€” replaced with empty object (was: %s))r   r   r   ÚloggerÚwarningrm   Údumpsrn   ro   Ú	TypeErrorÚ
ValueErrorr   r   r‚   Úrangerp   ÚendswithÚcountrk   Ú_FULL_ARGS_LOG_BOUND)Úraw_argsÚ	tool_nameÚraw_strippedÚreserialisedr/   r   Úescapeds          r   Ú_repair_tool_call_argumentsrš   Ý   sã  € ô (2°(¼CÔ'@�8—>‘>Ô#Àb€LáÜ�‰ÐCÀYÔOØà�vÒÜ�‰ÐIÈ9ÔUØðÜ—z‘z¤$§*¡*¨\À%Ô"HÐU_Ô`ˆØ˜<Ò'Ü�N‰NÐ[Ð]fÔgØÐøÜ× Ñ ¤)¬ZÐ8ò Ùðúô �F‰F�> 5¨,Ó7€EÜ# EÓ*Ò3¨e€EÜ�2‹Yò ˆÜ�UÔØ�^‰^˜CÔ  U§[¡[°Ó%5¸¿¹ÀCÓ8HÒ%HØ—‘˜sÔ#¨¯©°CÓ(8¸5¿;¹;ÀsÓ;KÒ(KáØ�c�r�
‰ðô �ÔÜ�‰ÐQÐS\Ð^jÐknÐlnÐ^oÐqvÐwzÐxzÐq{Ô|Øˆô 4°EÓ:€GØ�%ÒœI gÔ.Ü�‰ØOÐQZÐ\hÐilÐjlÐ\mÐovÐwzÐxzÐo{ô	
ð ˆä
‡N�NØZØ�<Ð 5Ô!5Ð6ôð s   ÁAB" Â"CÃCc                óÐ   — | r| d   nd}t        |t        «      r|j                  d«      dk7  ryt        |t        «      r|nd}ddlm}  || d	|j                  «       xs d
dœ«       y)uW  Append a synthetic assistant turn when an interrupted tail is a tool result: a transcript
    ending on a raw ``tool`` message makes the next user message land as ``tool â†’ user``, an
    alternation violation strict providers (Gemini, Claude) answer by hallucinating a
    continuation. Mutates in place; True if a closing turn was appended.r~   Nr	   ÚtoolFrb   r   )Úappend_messageÚ	assistantzOperation interrupted.)r	   r
   T)r   r)   r*   r   Úagent.message_metadatar�   r   )rE   Úfinal_responseÚlastr   r�   s        r   Úclose_interrupted_tool_sequencer¢     s_   € ñ
 $ˆ8�BŠ<¨€DÜ�dœDÔ! T§X¡X¨fÓ%5¸Ò%?ØÜ'¨¼Ô<‰>À"€DÝ5á�8 k¸d¿j¹j»lÒ>fÐNfÑgÔhØr   ÚlengthÚstopr   )Ú
max_tokensÚendÚfunction_callc                óv   — t        | t        «      r| s| S | j                  «       }t        j	                  ||«      S )a  Fold a wire ``finish_reason`` to the lowercase OpenAI contract value.

    Non-string and empty values pass through unchanged (callers keep their
    ``or "stop"`` defaults and the Poolside int-reason path); contract values
    are returned byte-identical.
    )r   r   ÚlowerÚ_FINISH_REASON_ALIASESr*   )rf   Úlowereds     r   Únormalize_finish_reasonr¬   3  s3   € ô �cœ3Ô¡sØˆ
Ø�i‰i‹k€GÜ!×%Ñ% g¨wÓ7Ð7r   c                óâ   — t        | t        «      r| sy	 t        t        j                  | ddt
        ¬«      j                  d«      «      S # t        t        f$ r t        d„ | D «       «      cY S w xY w)zêExact serialized byte size of ``messages`` (HTTP 413 is a BYTE-size error the token
    estimator, pricing images flat, cannot score). Non-serializable values fall back to
    ``str()`` so a malformed message can never crash recovery.r   Fr†   )Úensure_asciir‰   Údefaultúutf-8c              3  óD   K  — | ]  }t        t        |«      «      –— Œ y ­wrw   )rc   r   )rz   Úms     r   r|   z,serialized_messages_bytes.<locals>.<genexpr>I  s   è ø€ Ò1 1”3”s˜1“v—;Ñ1ùs   ‚ )
r   r5   rc   rm   rŽ   r   r%   r�   r�   Úsum)rE   s    r   Úserialized_messages_bytesr´   @  sd   € ô �h¤Ô%©XØð2Ü”4—:‘:˜h°UÀzÔ[^Ô_×fÑfÐgnÓoÓpÐpøÜ”zÐ"ò 2ÜÑ1¨Ô1Ó1Ò1ð2ús   •4A
 Á
!A.Á-A.>   ÚimageÚ	image_urlÚinput_imagec                óf  — ddl m} ddlm} d}g }t	        | «      D ]ú  \  }}t        |t        «      r|j                  d«      nd}t        |t        «      sŒ:|D �cg c],  }t        |t        «      r|j                  d«      t        v rŒ+|‘Œ. }	}t        |	«      t        |«      k  sŒ‰d}|	r|	|d<   |j                  |d«       nN|j                  d	«      d
k(  s|j                  d«      rd|d<   |j                  |d«       n|j                  |«        ||«       Œü t        |«      D ]  }| |= Œ |S c c}w )u  Remove image content parts from all messages in-place (server rejected images).

    ``tool`` / ``tool_calls`` messages left empty get a placeholder, NOT deleted (deleting
    orphans the paired ``tool_call_id`` â†’ HTTP 400); other now-empty messages are dropped.
    Rewritten messages lose their ``api_content`` sidecar (it carries the removed images):
    a caller rewriting a persisted row must not leave bytes that replay them next turn. The
    current callers pass per-call clones, where this is a no-op.
    r   r>   )Údrop_stale_api_contentFr
   NÚtypeTr	   rœ   r   u:   [image content removed â€” server does not support images])rC   r?   Úagent.turn_contextr¹   r6   r   r)   r*   r5   Ú_IMAGE_PART_TYPESrc   r3   r7   r€   )
rE   r?   r¹   r9   Ú	to_deleteri   rG   r
   rI   Ú	new_partss
             r   Ú_strip_images_from_messagesr¿   O  s  € õ >Ý9à€EØ€IÜ˜HÓ%ò (‰ˆˆ3Ü(2°3¼Ô(=�#—'‘'˜)Ô$À4ˆÜ˜'¤4Ô(ØØ 'Öl˜1´
¸1¼dÔ0CÈÏÉÈfËÔYjÒHj’QÐlˆ	ÐlÜˆy‹>œC ›LÓ(ØˆEÙØ!*��I‘à—‘Ð,¨dÕ3Ø—‘˜“ FÒ*¨c¯g©g°lÔ.CØ!]��I‘Ø—‘Ð,¨dÕ3à× Ñ  Ô#Ù" 3Õ'ð!(ô" �iÓ ò ˆØ�Q‰Kðà€Lùò ms   Á,D.ÂD.)z%only 'text' content type is supportedz#only text content type is supportedzimage_url is not supportedzimage content is not supportedzmultimodal is not supportedz#multimodal content is not supportedz!multimodal input is not supportedzvision is not supportedzvision input is not supportedzdoes not support imageszdoes not support image inputzdoes not support multimodalzdoes not support visionzmodel does not support imagezunexpected item type in contentzimage_url'. expectedz,unknown variant `image_url`, expected `text`z(unknown variant image_url, expected textz+no endpoints found that support image input)z8image data you provided does not represent a valid imagezfailed to decode imagec                ól   — t        | «      | j                  vryt        |t        «      xr t	        |«      S )aö  Send-path image strip for a model that rejected image content (see turn_recovery).

    Runs on the per-call ``api_messages`` copy in Hermes's own message format, BEFORE the
    provider-specific conversion: the part types this stripper knows are that format's, and a
    converted payload (Bedrock Converse ``{"image": ...}`` blocks carry no ``type``) would slip
    past it. History is never touched. Keyed on each rejecting (provider, model), so a model
    that accepts images gets them again.
    F)r   Ú_image_rejecting_modelsr   r5   r¿   )rX   Úapi_messagess     r   Ú strip_images_for_rejecting_modelrÃ   ž  s2   € ô ˜5Ó!¨×)FÑ)FÑFØÜ�l¤DÓ)ÒWÔ.IÈ,Ó.WÐWr   c                ón   ‡— t        | xs d«      j                  «       Št        ˆfd„t        D «       «      S )zMReturn True when a provider error says image/multimodal input is unsupported.rb   c              3  ó&   •K  — | ]  }|‰v –— Œ
 y ­wrw   r2   ©rz   ÚphraseÚbodys     €r   r|   z6_looks_like_image_content_rejection.<locals>.<genexpr>¯  s   øè ø€ ÒE &ˆv˜Œ~ÑEùó   ƒ)r   r©   ÚanyÚ_IMAGE_REJECTION_PHRASES©Ú
error_bodyrÈ   s    @r   Ú#_looks_like_image_content_rejectionrÎ   ¬  s-   ø€ äˆzÒ˜RÓ ×&Ñ&Ó(€DÜÓEÔ,DÔEÓEÐEr   c                ón   ‡— t        | xs d«      j                  «       Št        ˆfd„t        D «       «      S )zXReturn True when the rejection is about a bad image payload, not the model's capability.rb   c              3  ó&   •K  — | ]  }|‰v –— Œ
 y ­wrw   r2   rÆ   s     €r   r|   z6_looks_like_corrupt_image_rejection.<locals>.<genexpr>µ  s   øè ø€ ÒC &ˆv˜Œ~ÑCùrÉ   )r   r©   rÊ   Ú_IMAGE_CORRUPT_PHRASESrÌ   s    @r   Ú#_looks_like_corrupt_image_rejectionrÒ   ²  s-   ø€ äˆzÒ˜RÓ ×&Ñ&Ó(€DÜÓCÔ,BÔCÓCÐCr   )r   r¢   r   rS   Ú_sanitize_messages_surrogatesr   rk   rš   r'   Ú_sanitize_messages_non_asciiÚ_sanitize_tools_non_asciir¿   rW   rZ   rÃ   Údeterministic_call_idÚcoalesce_tool_call_idÚtool_call_id_variantsÚtool_result_id_variantsÚuniquify_tool_call_idsÚreasoning_echo_familyÚmatches_reasoning_echo_familyÚneeds_reasoning_echoÚstale_thinking_reaches_wireÚapply_reasoning_content_policyÚreapply_reasoning_echoc                ó^   — t        | t        «      r| j                  |«      S t        | |d«      S )zHRead ``key`` from a tool-call entry that may be a dict or an SDK object.N)r   r)   r*   rU   )rL   r,   s     r   Ú	_tc_fieldrâ   Ð  s'   € ä$ R¬Ô.ˆ2�6‰6�#‹;ÐJ´G¸BÀÀTÓ4JÐJr   c                ód   — t        | t        «      r| j                  ||«       y t        | ||«       y rw   )r   r)   Ú__setitem__Úsetattr)rL   r,   r.   s      r   Ú_tc_setræ   Õ  s$   € Ü",¨R´Ô"6€B‡N�N�3˜Õ¼GÀBÈÈUÕ<Sr   c                óŠ   — | › d|› d|› �}dt        j                  |j                  dd¬«      «      j                  «       dd › �S )zWDeterministic call_id fallback when the API omits one (random ids would break caching).rˆ   Úcall_r°   Úreplacer#   Né   )ÚhashlibÚsha256r%   Ú	hexdigest)Úfn_namerB   ÚindexÚseeds       r   rÖ   rÖ   ä  sN   € àˆY�a˜	�{ ! E 7Ð+€DØ”7—>‘> $§+¡+¨g¸i +Ó"HÓI×SÑSÓUÐVYÐWYÐZÐ[Ð\Ð\r   c                ó  — t        «       }| D ]f  }t        |t        «      r|j                  «       nd}|sŒ(|j	                  |«       |j                  d„ d„ |j                  d«      D «       D «       «       Œh t        |«      S )z¸Every wire spelling of one tool-call identifier: Responses bridges may expose the pairing
    id and response-item id separately or as ``call_id|response_item_id``; all alias ONE call.rb   c              3  ó&   K  — | ]	  }|sŒ|–— Œ y ­wrw   r2   )rz   rI   s     r   r|   z+_expand_tool_id_variants.<locals>.<genexpr>ò  s   è ø€ ÒX !ÒVWœAÑXùs   ‚Šc              3  ó<   K  — | ]  }|j                  «       –— Œ y ­wrw   )r   )rz   Úparts     r   r|   z+_expand_tool_id_variants.<locals>.<genexpr>ò  s   è ø€ Ò'R¸¨¯
©
¯Ñ'Rùs   ‚Ú|)Úsetr   r   r   ÚaddÚupdateÚsplitÚ	frozenset)ÚvaluesÚvariantsrf   r.   s       r   Ú_expand_tool_id_variantsrý   ê  so   € ô ›€HØò YˆÜ)¨#¬sÔ3�—	‘	”¸ˆÚØ�L‰L˜ÔØ�O‰OÑXÑ'RÀÇÁÈSÓAQÔ'RÔXÕXð	Yô
 �XÓÐr   c                ó>   ‡ — t        t        ˆ fd„dD «       «      «      S )z<Return all pairing-id variants carried by a tool-call entry.c              3  ó6   •K  — | ]  }t        ‰|«      –— Œ y ­wrw   )râ   )rz   ÚkrL   s     €r   r|   z(tool_call_id_variants.<locals>.<genexpr>ø  s   øè ø€ Ò)j¸q¬)°B¸×*:Ñ)jùs   ƒ)Úcall_idrA   Úresponse_item_id)rý   Útuple)rL   s   `r   rØ   rØ   ö  s   ø€ ä#¤EÓ)jÐDiÔ)jÓ$jÓkÐkr   c                ó   — t        | f«      S )z>Return all matching variants for a role=tool ``tool_call_id``.)rý   )Útool_call_ids    r   rÙ   rÙ   û  s   € ä# \ OÓ4Ð4r   c                óØ   — t        | d«      t        | d«      fD ]O  }t        |t        «      r|j                  «       nd}|sŒ(|j	                  dd«      d   j                  «       xs |c S  y)zãEffective call id of a tool_call entry (dict or object); ``""`` when none. Codex Responses
    carry ``call_id`` (authoritative pairing key), Chat Completions ``id`` only, and bridge ids
    may be ``call_id|response_item_id``.r  rA   rb   rõ   r]   r   )râ   r   r   r   rù   )rL   rf   r.   s      r   r×   r×      sh   € ô ˜"˜iÓ(¬)°B¸Ó*=Ð>ò ;ˆÜ)¨#¬sÔ3�—	‘	”¸ˆÚØ—;‘;˜s AÓ& qÑ)×/Ñ/Ó1Ò:°UÒ:ð;ð r   c                óþ  ‡‡— t        «       Š| xs g D �]@  }t        |d«      xs t        |d«      xs d}t        |t        «      r|j	                  «       nd}|j                  dd«      d   Š‰sŒ\‰‰vr‰j                  ‰«       Œrt        ˆˆfd„t        dt        ‰«      d	z   «      D «       «      }‰j                  |«       	 t        |d«      }t        |dt        |t        «      rd|v r|› d|j                  dd«      d   › �n|«       t        |d«      rt        |d|«       t        t        |d«      d«      xs d}t        j                  d‰||«       �ŒC | S # t        $ r t        j                  d
‰«       Y �Œhw xY w)uì  Ensure every tool call in one assistant turn has a distinct id.

    Some providers reuse one id across a batch; the pre-API sanitizer then keeps only the
    first call/result pair per id and strict providers reject duplicates. Later collisions
    get a deterministic ``<id>_d<n>`` suffix (never uuid4 â€” cache-prefix stability). Mutates
    entries (SDK models / SimpleNamespace / dicts) in place. Blank ids are left for the
    deterministic fallback in ``build_assistant_message``.
    r  rA   rb   rõ   r]   r   c              3  ó@   •K  — | ]  }‰› d |› �‰vsŒ‰› d |› �–— Œ y­w)Ú_dNr2   )rz   r�   ÚcidÚseens     €€r   r|   z)uniquify_tool_call_ids.<locals>.<genexpr>!  s/   øè ø€ Òb¨È3È%ÈrÐRSÐQTÈÐ]aÒHa˜˜˜R ˜s”mÑbùs   ƒ’r^   é   z,Could not uniquify duplicate tool call id %sr@   r   ú?zyModel reused tool call id %s within one turn; renamed the duplicate to %s (tool=%s) to keep call/result pairing lossless.)rö   râ   r   r   r   rù   r÷   Únextr‘   rc   ræ   Ú	ExceptionrŒ   r�   )r   rL   rf   Únew_idÚoldÚ_fn_namer
  r  s         @@r   rÚ   rÚ     sp  ù€ ô “€DØÒ˜Bó 
ˆä˜˜IÓ&ÒC¬)°B¸Ó*=ÒCÀˆÜ'¨¬SÔ1ˆc�i‰iŒk°rˆà�i‰i˜˜QÓ Ñ"ˆÙØØ�d‰?Ø�H‰H�SŒMØäÔb¬U°1´c¸$³iÀ!±mÓ-DÔbÓbˆØ�‰�Ôð	ä˜B Ó%ˆCÜ�B˜ÄJÈsÔTWÔDXÐ]`ÐdgÑ]g  ¨¨#¯)©)°C¸Ó*;¸AÑ*>Ð)?Ñ@ÐmsÔtÜ˜˜YÔ'Ü˜˜I vÔ.ô œY r¨:Ó6¸Ó?ÒFÀ3ˆÜ�‰ð,Ø-0°&¸(ö	
ð3
ð: Ðøô ò 	Ü�N‰NÐIÈ3ÔOÚð	ús   ÃA EÅE<Å;E<Úkimizkimi-codingzkimi-coding-cnr2   )zapi.kimi.comzmoonshot.aizmoonshot.cnÚdeepseek)r  )zapi.deepseek.comÚmimoÚxiaomi)r  )zapi.xiaomimimo.comzxiaomimimo.comr  Ú_REASONING_ECHO_RULESc                óì   ‡‡	‡
— ddl mŠ	 t        |    \  }}}}}|xs dj                  «       Š
||v xsC |xs dj                  «       |v xs+ t	        ˆ
fd„|D «       «      xs t	        ˆˆ	fd„|D «       «      S )z©True when (provider, model, base_url) matches one echo-back family (families can overlap;
    membership is tested independently). Raises KeyError for an unknown family.r   )Úbase_url_host_matchesrb   c              3  ó&   •K  — | ]  }|‰v –— Œ
 y ­wrw   r2   )rz   r   Úmodel_lowers     €r   r|   z0matches_reasoning_echo_family.<locals>.<genexpr>Z  s   øè ø€ Ò8 cˆs�kÔ!Ñ8ùrÉ   c              3  ó0   •K  — | ]  } ‰‰|«      –— Œ y ­wrw   r2   )rz   ÚhostÚbase_urlr  s     €€r   r|   z0matches_reasoning_echo_family.<locals>.<genexpr>Z  s   øè ø€ Ò?xÐjnÑ@UÐV^Ð`d×@eÑ?xùs   ƒ)Úutilsr  Ú_REASONING_ECHO_RULE_BY_FAMILYr©   rÊ   )ÚfamilyÚproviderÚmodelr  r   Úraw_providersÚlowered_providersÚ
model_subsÚhostsr  r  s      `     @@r   rÜ   rÜ   Q  s‚   ú€ õ ,ä=[Ð\bÑ=cÑ:€A€}Ð'¨°UØ’;˜B×%Ñ%Ó'€Kà�MÐ!ò 	y h¢n°"×%;Ñ%;Ó%=ÐARÐ%Rò 	yÜÓ8¨ZÔ8Ó8ò	yÜ<?Ô?xÐrwÔ?xÓ<xðr   c                óP   ‡ ‡‡— d„ t         D «       }t        ˆˆˆ fd„|D «       d«      S )z�``"kimi"`` / ``"deepseek"`` / ``"mimo"`` (first match in table order) when the
    endpoint enforces reasoning_content echo-back, else ``None`` (strip side).c              3  ó&   K  — | ]	  }|d    –— Œ y­w)r   Nr2   )rz   Úrules     r   r|   z(reasoning_echo_family.<locals>.<genexpr>a  s   è ø€ Ò:˜D��Q•Ñ:ùs   ‚c              3  ó@   •K  — | ]  }t        |‰‰‰«      sŒ|–— Œ y ­wrw   )rÜ   )rz   Úfr  r#  r"  s     €€€r   r|   z(reasoning_echo_family.<locals>.<genexpr>b  s!   øè ø€ Òd�qÔ(EÀaÈÐSXÐZbÕ(c”Ñdùs   ƒ—N)r  r  )r"  r#  r  Úfamiliess   ``` r   rÛ   rÛ   ^  s$   ú€ ñ ;Ô$9Ô:€HÜÕd˜HÔdÐfjÓkÐkr   c                ó    — t        | ||«      duS )z<True when the endpoint requires reasoning_content echo-back.N)rÛ   )r"  r#  r  s      r   rÝ   rÝ   e  s   € ä  ¨5°(Ó;À4ÐGÐGr   c                ó2   — | xs ddk7  xr t        |||«      S )uÄ  True when stale assistant reasoning text is actually replayed on the wire for the route.

    The single wire-truth predicate the compaction TRIGGER estimator and the tail-budget
    walks must share: if they disagree, a reasoning-heavy session can look over-threshold
    to preflight yet fully tail-protected to the walk â€” an infinite compaction loop.
    ``codex_responses`` never reads the text keys (continuity rides the encrypted sidecar).
    rb   Úcodex_responses)rÝ   )Úapi_moder"  r#  r  s       r   rÞ   rÞ   j  s%   € ð ŠN˜Ð0Ñ0ÒdÔ5IÈ(ÐTYÐ[cÓ5dÐdr   c                ó,  — | j                  d«      dk7  ry|s|j                  dd«       y| j                  d«      | j                  d«      }}t        |t        «      r
|xs d|d<   yt        |t        «      r|r| j                  d«      s||d<   yd|d<   y)z§Copy provider-facing reasoning fields onto an API replay message (mutates ``api_msg``).
    ``needs_thinking_pad`` is the require-side flag (``needs_reasoning_echo``).r	   rž   NÚreasoning_contentÚ	reasoningÚ r   )r*   r3   r   r   )Ú
source_msgÚapi_msgÚneeds_thinking_padÚexistingr4  s        r   rß   rß   u  s•   € ð ‡~�~�fÓ Ò,ØÙð 	�‰Ð'¨Ô.ØØ$Ÿ.™.Ð)<Ó=¸z¿~¹~ÈkÓ?Zˆi€Hô �(œCÔ à'/¢°3ˆÐ#Ò$Ü	�IœsÔ	#©	¸*¿.¹.ÈÔ:Và'0ˆÐ#Ò$ð (+ˆÐ#Ò$r   c                óî   — d}| D ]m  }|j                  d«      dk7  rŒ|r8|j                  d«      rŒ,t        |||«       ||j                  d«      rdndz  }ŒRd|v sŒW|j                  dd«       |dz  }Œo |S )a’  Re-pad (or strip) assistant turns' reasoning_content for the ACTIVE provider.

    ``api_messages`` is built once under the primary provider; a mid-conversation fallback
    can switch providers, so baked-in fields must be reconciled: TO a require-side provider
    re-applies the pad (else 400), TO a strict one strips it (else 422). Idempotent.
    Returns the number of assistant turns changed.
    r   r	   rž   r3  r]   N)r*   rß   r3   )rÂ   r8  Úchangedr7  s       r   rà   rà   ˜  sŠ   € ð €GØò ˆØ�;‰;�vÓ +Ò-Øñ Ø—;‘;Ð2Õ3Ü.¨w¸ÐASÔTØ §¡Ð,?Ô @™1ÀaÑG‘Ø  GÒ+Ø�K‰KÐ+¨TÔ2Ø�q‰L‰Gð'ð( €Nr   )r   r   Úreturnr   )Úinvalid_tool_call)r   r   r   r   r<  r   )r+   r   r,   r   r-   úCallable[[str], str]r<  Úbool)r8   r   r-   r>  r<  r?  )rE   r5   r-   r>  rF   r?  r<  r?  )rX   r   rY   r)   r<  r„   )rf   r   r<  r   )r   r   r<  r?  )rf   r   r<  z
str | None)r  )r•   r   r–   r   r<  r   rw   )rE   r5   r    r   r<  r?  )rf   r   r<  r   )rE   r5   r<  Úint)rE   r5   r<  r?  )rX   r   rÂ   r   r<  r?  )rÍ   r   r<  r?  )rL   r   r,   r   r<  r   )rL   r   r,   r   r.   r   r<  r„   )r   )rî   r   rB   r   rï   r@  r<  r   )rû   ztuple[Any, ...]r<  úfrozenset[str])rL   r   r<  rA  )r  r   r<  rA  )rL   r   r<  r   )r   r5   r<  r5   )
r!  r   r"  r   r#  r   r  r   r<  r?  )r"  r   r#  r   r  r   r<  z'str | None')r"  r   r#  r   r  r   r<  r?  )
r1  r   r"  r   r#  r   r  r   r<  r?  )r6  r)   r7  r)   r8  r?  r<  r„   )rÂ   r5   r8  r?  r<  r@  )EÚ__doc__Ú
__future__r   rë   rm   Úloggingr   Ú	functoolsr   Útypingr   r   Úagent.vision_message_prepr   Ú	getLoggerÚ__name__rŒ   Úcompiler   rú   rD   r   r   r   r'   r0   r<   rO   rS   rÓ   rW   rÔ   rÕ   rZ   rk   r”   rp   ry   r‚   rš   r¢   rª   r¬   r´   r¼   r¿   rË   rÑ   rÃ   rÎ   rÒ   Ú__all__râ   ræ   rÖ   rý   rØ   rÙ   r×   rÚ   r  Ú__annotations__r   rÜ   rÛ   rÝ   rÞ   rß   rà   )r*  s   0r   ú<module>rM     s-  ðòõ #ã Û Û Û 	Ý ß  å 9à	ˆ×	Ñ	˜8Ó	$€ð �—
‘
Ð-Ó.€ñ ÒHÓIÐ ó-ð !�b—j‘jÐ!7Ó8Ð ô
$óAó
óó ñJ ")Ð)<ÐBVÔ!WÐ Ù 'Ð(:Ð@TÐ[_Ô `Ð Ù 'Ð(;ÐAQÔ RÐ Ù&Ð'9Ð?OÐV[Ô\Ð Ø9Ð ó2ó,ð* Ð óð  Ñ$€ó"OôJ7ôtð, ØØ!ñÐ ó
8ó	2ò :Ð ó!ðNÐ ð6Ð óXóFóDò€ó0Kó
Tô]ó	óló
5ó
ó'ð~ ‰Y˜Ð'7Ð8Ó9¹9»;ÈÐLzÐ{Ø‘“™i¨¨Ó5°}ÐF[Ð\Ø‰Y‹[™) X JÓ/°Ð<dÐeð	 Ð �uó ð =RÖ!R°D $ q¡'¨4¡-Ò!RÐ ó
ólóHó
eó +ôFùòU "Ss   Å!F
