
    PL
j^                      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ZddlZddl	Z	ddl
Z
ddlmZ ddl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 dd	lmZ dd
lmZ ddlmZmZ ddl m!Z!m"Z"m#Z#m$Z$  ej%        e&          Z'd Z(dadZ)ddddbdZ*dcdZ+dddZ,dddded(Z-dfd-Z.dgd.Z/dhd/Z0 e1h d0          Z2did2Z3dd3djd:Z4ddddd;dkd@Z5dldEZ6dmdGZ7	 	 dndodNZ8dpdQZ9dgdRZ:dqdUZ;drdYZ<dhdZZ=dsd[Z>dtd]Z?dud_Z@g d`ZAdS )vu  Assorted AIAgent runtime helpers — moved out of run_agent.py for clarity.

Each function takes the parent ``AIAgent`` as its first argument
(``agent``) except for the static helpers (``sanitize_tool_call_arguments``,
``drop_thinking_only_and_merge_users``) which are stateless.  AIAgent
keeps thin forwarders for backward compatibility.

Methods covered:
* ``convert_to_trajectory_format`` — internal -> trajectory-file format
* ``sanitize_tool_call_arguments`` — repair corrupted JSON in tool_calls
* ``repair_message_sequence`` — enforce alternation invariants
* ``strip_think_blocks`` — remove inline reasoning from stored content
* ``recover_with_credential_pool`` — rotate pool entries on 429
* ``try_recover_primary_transport`` — re-create OpenAI client after rate-limit
* ``drop_thinking_only_and_merge_users`` — Anthropic-style cleanup
* ``restore_primary_runtime`` — un-do fallback activation
* ``extract_reasoning`` — pull reasoning fields out of API responses
* ``dump_api_request_debug`` — write request body for post-mortem
* ``anthropic_prompt_cache_policy`` — compute cache_control breakpoints
* ``create_openai_client`` — build the per-agent OpenAI SDK client
    )annotationsN)datetime)Path)AnyDictListOptionalTuple)get_provider_request_timeout)_repair_tool_call_arguments_sanitize_surrogates_trajectory_normalize_msg)convert_scratchpad_to_think)classify_api_errorFailoverReason)base_url_host_matchesbase_url_hostnameenv_var_enabledatomic_json_writec                     ddl } | S )z4Lazy ``run_agent`` reference for test-patch routing.r   N	run_agentr   s    ?/home/kuhnn/.hermes/hermes-agent/agent/agent_runtime_helpers.py_rar   2   s        messagesList[Dict[str, Any]]
user_querystr	completedboolreturnc                	   d |D             }g }d|                                   d}|                    d|d           |                    d|d           d}|t          |          k     r||         }|d         d	k    rd
|v r-|d
         r$d}|                    d          r&|d                                         rd|d          d}|                    d          r5|d                                         r|t          |d                   dz   z  }|d
         D ]}	|	rt          |	t                    s	 t          |	d         d         t                    r t          j
        |	d         d                   n|	d         d         }
nB# t          j        $ r0 t          j        d|	d         d         dd                     i }
Y nw xY w|	d         d         |
d}|dt          j        |d           dz  }d|vrd|z   }|                    d|                                d           g }|dz   }|t          |          k     r8||         d         dk    r%||         }d }|d         }	 |                                                    d!          rt          j
        |          }n# t          j        t"          f$ r Y nw xY wt          |          }|t          |d
                   k     r|d
         |         d         d         nd"}|t          j        |                    d#d          ||d$d          z  }|d%z  }|                    |           |dz  }|t          |          k     r||         d         dk    %|r0|                    dd                    |          d           |dz
  }nd}|                    d          r&|d                                         rd|d          d}|d         pd}|t          |          z  }d|vrd|z   }|                    d|                                d           n*|d         d&k    r|                    d|d         d           |dz  }|t          |          k     |S )'aQ  
    Convert internal message format to trajectory format for saving.
    
    Args:
        messages (List[Dict]): Internal message history
        user_query (str): Original user query
        completed (bool): Whether the conversation completed successfully
        
    Returns:
        List[Dict]: Messages in trajectory format
    c                ,    g | ]}t          |          S  r   .0ms     r   
<listcomp>z0convert_to_trajectory_format.<locals>.<listcomp>H   s!    ???)!,,???r   a  You are a function calling AI model. You are provided with function signatures within <tools> </tools> XML tags. You may call one or more functions to assist with the user query. If available tools are not relevant in assisting with user query, just respond in natural conversational language. Don't make assumptions about what values to plug into functions. After calling & executing the functions, you will be provided with function results within <tool_response> </tool_response> XML tags. Here are the available tools:
<tools>
a  
</tools>
For each function call return a JSON object, with the following pydantic model json schema for each:
{'title': 'FunctionCall', 'type': 'object', 'properties': {'name': {'title': 'Name', 'type': 'string'}, 'arguments': {'title': 'Arguments', 'type': 'object'}}, 'required': ['name', 'arguments']}
Each function call should be enclosed within <tool_call> </tool_call> XML tags.
Example:
<tool_call>
{'name': <function-name>,'arguments': <args-dict>}
</tool_call>system)fromvaluehuman   role	assistant
tool_calls 	reasoningz<think>
z

</think>
content
function	argumentsz2Unexpected invalid JSON in trajectory conversion: Nd   name)r:   r8   z<tool_call>
Fensure_asciiz
</tool_call>
z<think>z<think>
</think>
gpttoolz<tool_response>
){[unknowntool_call_id)rB   r:   r5   z
</tool_response>user) _format_tools_for_system_messageappendlengetstripr   
isinstancedictr    jsonloadsJSONDecodeErrorloggingwarningdumpsrstrip
startswithAttributeErrorjoin)agentr   r   r!   
trajectory
system_msgimsgr5   	tool_callr8   tool_call_jsontool_responsesjtool_msgtool_responsetool_content
tool_index	tool_nameraw_contents                       r   convert_to_trajectory_formatrd   9   s,    @?h???HJ
	b
 ::<<
	b 
	b 
	b            	
A
c(mm

qkv;+%%s""s<'8"  77;'' IC,<,B,B,D,D IH#k*:HHHG779%% R#i.*>*>*@*@ R :3y>JJTQQG "%\!2 p pI$QJy$,G,GQ'V`ajkuav  xC  bD  FI  WJ  WJ  %rDJy/D[/Q$R$R$R  PY  Zd  Pe  fq  Pr		/ ' ' '    )H]fgq]rs~]  AE  BE  AE  ^F  )H  )H  I  I  I$&				' !** 5f =%.& &N otz.W\/]/]/]ooooGG G++3g=G!!!$^^--# #    "$E#h--''HQK,?6,I,I'{H$7M $,I#6L'--//:::FF D+/:l+C+CL 0.A    "%^!4!4J &C,=(>(>>> L)*5jA&II& 
 "TZ(0^R(H(H )#/1 1 %*	&+ &+ &+ +M
 "%99M"))-888FA5 #h--''HQK,?6,I,I: " %% &!%>!:!:' '    AA
  77;'' IC,<,B,B,D,D IH#k*:HHHG ")n26{CCC G++3g=G!!!$]]__# #    
 [F""Y    
 	
Qi c(mm

l s%   :AF

<G	G	/;J+ +KK)logger
session_idlistrf   intc          
        |pt          j        t                    }t          | t                    sdS d}t                      j        j        dfd}d}|t          |           k     r| |         }t          |t                    r|
                    d          dk    r|d	z  }P|
                    d
          }t          |t                    r|s|d	z  }|d	z   }	|D ]}
t          |
t                    s|

                    d          }t          |t                    sD|
                    d          }||dk    rd|d<   gt          |t                    r|                                sd|d<   t          |t                    s	 t          j        |           # t          j        $ r |

                    d          }|
                    dd          }|dd         }|                    d|pd||pd||           d|d<   d}|d	z   }|t          |           k     rk| |         }t          |t                    r|
                    d          dk    rn4|
                    d          |k    r|}n|d	z  }|t          |           k     k|)|                     |	d|dk    r|nd|d           |	d	z  }	n ||           |d	z  }Y w xY w|d	z  }|t          |           k     |S )z<Repair corrupted assistant tool-call argument JSON in-place.r   r^   rJ   r#   Nonec                >   |                      d          }t          |t                    r)|s| d<   n|                              s
 d| | d<   d S || d<   d S 	 t	          j        |          }n# t          $ r t          |          }Y nw xY w d| | d<   d S )Nr5   r6   )rG   rI   r    rR   rK   rP   	TypeError)r^   existingexisting_textmarkers      r   _prepend_markerz5sanitize_tool_call_arguments.<locals>._prepend_marker   s    <<	**h$$ 	 >&,##((00 >)/&=&=8&=&=#F"(HYF	* Jx00MM 	* 	* 	*MMMMM	*!'::=::s   A4 4BBr0   r1   r/   r2   r7   r8   Nr3   z{}idr:   ?P   z~Corrupted tool_call arguments repaired before request (session=%s, message_index=%s, tool_call_id=%s, function=%s, preview=%r)-r>   rB   )r0   r:   rB   r5   )r^   rJ   r#   rj   )rN   	getLogger__name__rI   rg   r   AIAgent&_TOOL_CALL_ARGUMENTS_CORRUPTION_MARKERrF   rJ   rG   r    rH   rK   rL   rM   rO   insert)r   re   rf   logrepairedrp   message_indexrY   r2   	insert_atrZ   r7   r8   rB   function_namepreviewexisting_tool_msg
scan_index	candidatero   s                      @r   sanitize_tool_call_argumentsr      s    
/G%h//Ch%% qHUU]AF; ; ; ; ; ;" M
#h--
'
'}%#t$$ 	;(F(FQMWW\**
*d++ 	: 	QM!A%	# ;	 ;	Ii..  }}Z00Hh--  [11I IOO(,%)S)) )//2C2C (,%i-- *
9%%%%' ( ( ((}}T22 (VS 9 9#CRC._%#! 'C!   )-%$(!*Q.
 3x==00 ( 4I%i66 )--:O:OSY:Y:Y }}^44DD,5)!OJ !3x==00 %,OO!$*5Bc5I5IMMr,8'-	    NII#O$5666AQ(T 	S #h--
'
'V Os   F!!D(KK
List[Dict]c                   |sdS d}t                      }g }|D ]2}t          |t                    s|                    |           .|                    d          }|dk    rt                      }|                    d          pg D ]E}t          |t                    r|                    d          nd}|r|                    |           F|                    |           |dk    r9|                    d          }|r||v r|                    |           |d	z  }	|d
k    rt                      }|                    |           4g }	|D ]}|	rt          |t                    r|                    d          d
k    rt          |	d         t                    r|	d                             d          d
k    rw|	d         }
|
                    dd          }|                    dd          }t          |t                    r.t          |t                    r|r
|r|dz   |z   n|p||
d<   |d	z  }|	                    |           |dk    r|	|dd<   |S )u  Collapse malformed role-alternation left in the live history.

    Providers (OpenAI, OpenRouter, Anthropic) expect strict alternation:
    after the system message, user/tool alternates with assistant, with
    no two consecutive user messages and no tool-result that doesn't
    follow an assistant-with-tool_calls. Violations cause silent empty
    responses on most providers, which triggers the empty-retry loop.

    This runs right before the API call as a defensive belt — by the
    time it fires, the scaffolding strip should already have prevented
    most shapes, but external callers (gateway multi-queue replay,
    session resume, cron, explicit conversation_history passed in by
    host code) can feed in already-broken histories.

    Repairs applied:
      1. Stray ``tool`` messages whose ``tool_call_id`` doesn't match
         any preceding assistant tool_call — dropped.
      2. Consecutive ``user`` messages — merged with newline separator
         so no user input is lost.

    Deliberately does NOT rewind orphan ``assistant(tool_calls)+tool``
    pairs that precede a user message — that pattern IS valid when the
    previous turn completed normally and the user jumped in to redirect
    before the model got a continuation turn (the ongoing dialog
    pattern). The empty-response scaffolding stripper handles the
    genuinely-broken variant via its flag-gated rewind.

    Returns the number of repairs made (for logging/telemetry).
    r   r0   r1   r2   rq   Nr>   rB   r/   rC   r5   r3   

)setrI   rJ   rE   rG   addr    )rU   r   repairsknown_tool_idsfilteredrY   r0   tctc_idmergedprevprev_contentnew_contents                r   repair_message_sequencer   S  s   <  qG
 %%NH ! !#t$$ 	OOC   wwv; UUNww|,,2 . .(22t(<(<Ft$ ."&&u---OOC    V^^GGN++E .00$$$$1v~~ "%OOC     F  	3%%	 6))6":t,, *r
v&&&00":D88Ir22L'')R00K ,,, K1M1M  $7(37\F*[88&5+ Y
 1c{{ Nr   r5   c                   |sdS t          j        dd|t           j        t           j        z            }t          j        dd|t           j        t           j        z            }t          j        dd|t           j        t           j        z            }t          j        dd|t           j        t           j        z            }t          j        dd|t           j        t           j        z            }dD ]8}t          j        d	| d
| dd|t           j        t           j        z            }9t          j        dd|t           j        t           j        z            }t          j        dd|t           j        t           j        z            }t          j        dd|t           j                  }t          j        dd|t           j                  }|S )uW  Remove reasoning/thinking blocks from content, returning only visible text.

    Handles four cases:
      1. Closed tag pairs (``<think>…</think>``) — the common path when
         the provider emits complete reasoning blocks.
      2. Unterminated open tag at a block boundary (start of text or
         after a newline) — e.g. MiniMax M2.7 / NIM endpoints where the
         closing tag is dropped.  Everything from the open tag to end
         of string is stripped.  The block-boundary check mirrors
         ``gateway/stream_consumer.py``'s filter so models that mention
         ``<think>`` in prose aren't over-stripped.
      3. Stray orphan open/close tags that slip through.
      4. Tag variants: ``<think>``, ``<thinking>``, ``<reasoning>``,
         ``<REASONING_SCRATCHPAD>``, ``<thought>`` (Gemma 4), all
         case-insensitive.

    Additionally strips standalone tool-call XML blocks that some open
    models (notably Gemma variants on OpenRouter) emit inside assistant
    content instead of via the structured ``tool_calls`` field:
      * ``<tool_call>…</tool_call>``
      * ``<tool_calls>…</tool_calls>``
      * ``<tool_result>…</tool_result>``
      * ``<function_call>…</function_call>``
      * ``<function_calls>…</function_calls>``
      * ``<function name="…">…</function>`` (Gemma style)
    Ported from openclaw/openclaw#67318. The ``<function>`` variant is
    boundary-gated (only strips when the tag sits at start-of-line or
    after punctuation and carries a ``name="..."`` attribute) so prose
    mentions like "Use <function> in JavaScript" are preserved.
    r3   z<think>.*?</think>flagsz<thinking>.*?</thinking>z<reasoning>.*?</reasoning>z0<REASONING_SCRATCHPAD>.*?</REASONING_SCRATCHPAD>z<thought>.*?</thought>)rZ   r2   tool_resultfunction_callfunction_calls<z\b[^>]*>.*?</>zd(?:(?<=^)|(?<=[\n\r.!?:]))[ \t]*<function\b[^>]*\bname\s*=[^>]*>(?:(?:(?!</function>).)*)</function>zS(?:^|\n)[ \t]*<(?:think|thinking|reasoning|thought|REASONING_SCRATCHPAD)\b[^>]*>.*$z@</?(?:think|thinking|reasoning|thought|REASONING_SCRATCHPAD)>\s*zP</(?:tool_call|tool_calls|tool_result|function_call|function_calls|function)>\s*)resubDOTALL
IGNORECASE)rU   r5   _tc_names      r   strip_think_blocksr     s   >  r f*Bry2=?XYYYGf0"gRYQSQ^E^___Gf2BrySUS`G`aaaGfH"g]_]fikiv]vwwwGf.G29r}C\]]]G9 
 
&333333)bm+	
 
 
 f	0 	i"-'  G f^
i"-'	  G fK
m	  G f[
m	  G Nr   )classified_reasonerror_contextstatus_codeOptional[int]has_retried_429r   Optional[FailoverReason]r   Optional[Dict[str, Any]]tuple[bool, bool]c          	        | j         }|d|fS |}|6|dk    rt          j        }n#|dk    rt          j        }n|dv rt          j        }|t          j        k    rq||nd}|                    ||          }|Nt                      j                            d|t          |dd	                     | 
                    |           d
S d|fS |t          j        k    rd}	|rtt          |                    d          pd                                          }
t          |                    d          pd                                          }d|
v pd|v }	|s|	sdS ||nd}|                    ||          }|Nt                      j                            d|t          |dd	                     | 
                    |           d
S dS |t          j        k    r!|                     ||          r7t                      j                            d||nd| j        pd           d|fS |                                }|Qt                      j                            dt          |dd	                      | 
                    |           d|fS ||nd}|                    ||          }|Nt                      j                            d|t          |dd	                     | 
                    |           d
S d|fS )a  Attempt credential recovery via pool rotation.

    Returns (recovered, has_retried_429).
    On rate limits: first occurrence retries same credential (sets flag True).
                    second consecutive failure rotates to next credential.
    On billing exhaustion: immediately rotates.
    On auth failures: attempts token refresh before rotating.

    `classified_reason` lets the recovery path honor the structured error
    classifier instead of relying only on raw HTTP codes. This matters for
    providers that surface billing/rate-limit/auth conditions under a
    different status code, such as Anthropic returning HTTP 400 for
    "out of extra usage".
    NFi  i  >       )r   r   u4   Credential %s (billing) — rotated to pool entry %srq   rr   TFreasonr3   messageusage_limit_reachedzusage limit has been reached)FTu7   Credential %s (rate limit) — rotated to pool entry %su   Credential %s — entitlement-shaped 403 from %s; skipping pool refresh (account lacks subscription, not a transient auth failure).authprovideru1   Credential auth failure — refreshed pool entry Tr   u@   Credential %s (auth refresh failed) — rotated to pool entry %s)_credential_poolr   billing
rate_limitr   mark_exhausted_and_rotater   re   infogetattr_swap_credentialr    rG   lower_is_entitlement_failurer   try_refresh_current)rU   r   r   r   r   pooleffective_reasonrotate_status
next_entryr   context_reasoncontext_message	refresheds                r   recover_with_credential_poolr     s   , !D|o%%(#-5C-8J&&-2>111'2'>C33]j3kk
!EELF
D#..  
 "":...;o%%>444# 	 !2!28!<!<!BCCIIKKN!-"3"3I">">"D"EEKKMMO%7 E1_D    	': 	;'2'>C33]j3kk
!EELI
D#..  
 "":...;{>...((DD 	*EEL1  +6F,*   /)),,..	 EELqRYZceiknRoRoqqrrr""9---(( (3'>C33]j3kk
!EELR
D#..  
 "":...;/!!r   	api_error	Exceptionretry_countmax_retriesc          
        | j         rdS t          |          j        }|t          vrdS |                                 rdS | j        pd                                                                }|dv rdS 	 t          | dd          /	 | 	                    | j
        dd           n# t          $ r Y nw xY w| j        }t          |d	                   | _        |d
         | _        |d         | _        |d         | _        |d         | _        t%          | d          r| j                                         |d         | _        | j        dk    rlddlm} |d         | _        |d         | _         ||d         |d         t5          | j        | j                            | _        |d         | _        d| _
        n0|                     t          |d	                   dd          | _
        t=          d|z   d          }|                     | j          d| d| j         d| dd           tC          j"        |           dS # t          $ r }	tG          j$        d|	           Y d}	~	dS d}	~	ww xY w)uj  Attempt one extra primary-provider recovery cycle for transient transport failures.

    After ``max_retries`` exhaust, rebuild the primary client (clearing
    stale connection pools) and give it one more attempt before falling
    back.  This is most useful for direct endpoints (custom, Z.AI,
    Anthropic, OpenAI, local models) where a TCP-level hiccup does not
    mean the provider is down.

    Skipped for proxy/aggregator providers (OpenRouter, Nous) which
    already manage connection pools and retries server-side — if our
    retries through them are exhausted, one more rebuilt client won't help.
    Fr3   >   nous-researchnousclientNprimary_recoveryTr   sharedclient_kwargsmodelr   base_urlapi_mode_transport_cacheapi_keyanthropic_messagesr   build_anthropic_clientanthropic_api_keyanthropic_base_urltimeoutis_anthropic_oauth      u   🔁 Transient z on u    — rebuilt client, waiting z"s before one last primary attempt.)forcez%Primary transport recovery failed: %s)%_fallback_activatedtyperv   _TRANSIENT_TRANSPORT_ERRORS_is_openrouter_urlr   rH   r   r   _close_openai_clientr   r   _primary_runtimerJ   _client_kwargsr   r   r   hasattrr   clearr   agent.anthropic_adapterr   _anthropic_api_key_anthropic_base_urlr   _anthropic_client_is_anthropic_oauth_create_openai_clientmin_vprint
log_prefixtimesleeprN   rO   )
rU   r   r   r   
error_typeprovider_lowerrtr   	wait_timees
             r   try_recover_primary_transportr     s      u i)J444u !! un*113399;;N222u05(D))5**L);D +         ##B$788kJJJ5,-- 	+"((***9>111FFFFFF')*='>E$(*+?(@E%&<&<&',@)A4U^U[QQ' ' 'E# )++?(@E%ELL 66R())) 7  EL K++	 U U
 U U U U'0U U U 	 	
 	
 	

 	
9t   ?CCCuuuuus=   3H; B# "H; #
B0-H; /B00F	H; ;
I%I  I%c                   | s| S d | D             }t          |           t          |          z
  }|dk    r| S g }d}|D ]}|r|d         nd}||                    d          dk    r|                    d          dk    r|                    dd          }|                    dd          }t          |          }	t          |t                    r*t          |t                    r|r|rd	nd}
||
z   |z   |	d<   nt          |t
                    r8t          |t
                    r#t          |          t          |          z   |	d<   nt          |t
                    rDt          |t                    r/|rt          |          d
|dgz   |	d<   nt          |          |	d<   nxt          |t                    rLt          |t
                    r7g }|r|                    d
|d           |                    |           ||	d<   n|                    |           |	|d<   |dz  }|                    |           t                      j	        
                    d||           |S )ah  Drop thinking-only assistant turns; merge any adjacent user messages left behind.

    Runs on the per-call ``api_messages`` copy only. The stored
    conversation history (``agent.messages``) is never mutated, so the
    user still sees the thinking block in the CLI/gateway transcript and
    session persistence keeps the full trace. Only the wire copy sent to
    the provider is cleaned.

    Why drop-and-merge rather than inject stub text:
    - Fabricating ``"."`` / ``"(continued)"`` text lies in the history
      and makes future turns see model output the model didn't emit.
    - Dropping the turn preserves honesty; merging adjacent user messages
      preserves the provider's role-alternation invariant.
    - This is the pattern used by Claude Code's ``normalizeMessagesForAPI``
      (filterOrphanedThinkingOnlyMessages + mergeAdjacentUserMessages).
    c                ^    g | ]*}t                      j                            |          (|+S r&   )r   rw   _is_thinking_only_assistantr'   s     r   r*   z6drop_thinking_only_and_merge_users.<locals>.<listcomp>  s2    TTT!suu}'P'PQR'S'STATTTr   r   r   Nr0   rC   r5   r3   r   textr   r   r/   zbPre-call sanitizer: dropped %d thinking-only assistant turn(s), merged %d adjacent user message(s))rF   rG   rJ   rI   r    rg   rE   extendr   re   debug)r   keptdroppedr   mergesr)   r   r   cur_content	prev_copysep
new_blockss               r   "drop_thinking_only_and_merge_usersr	    s   &   UTxTTTD(mmc$ii'G!|| $&FF + +#-vbzz  F**f''88Ir22L%%	2..K
 T

I ,,, K1M1M  ,DDff"'3c'9K'G	)$$L$// J{D4Q4Q '+L'9'9D<M<M'M	)$$L$// J{C4P4P  >+/+=+=!'==A ,Ii(( ,0+=+=Ii((L#.. 
:k43P3P 
35
 N%%v|&L&LMMM!!+...'1	)$$ a   "F2JaKFFMM!EEL	-	   Mr   c                   | j         s	d| _        dS t          | dd          t          j                    k    rdS | j        }	 |d         | _        |d         | _        |d         | _        |d         | _	        t          | d          r| j                                         |d	         | _        t          |d
                   | _        |d         | _        |                    d| j	        dk    o
| j        dk              | _        | j	        dk    rlddlm} |d         | _        |d         | _         ||d         |d         t/          | j        | j                            | _        |d         | _        d| _        n0|                     t          |d
                   dd          | _        | j        }|                    |d         |d         |d         |d         |d                    d| _         d| _        t=          j        d| j        | j                   dS # t@          $ r }t=          j!        d|           Y d}~dS d}~ww xY w) a  Restore the primary runtime at the start of a new turn.

    In long-lived CLI sessions a single AIAgent instance spans multiple
    turns.  Without restoration, one transient failure pins the session
    to the fallback provider for every subsequent turn.  Calling this at
    the top of ``run_conversation()`` makes fallback turn-scoped.

    The gateway caches agents across messages (``_agent_cache`` in
    ``gateway/run.py``), so this restoration IS needed there too.
    r   F_rate_limited_untilr   r   r   r   r   r   r   use_prompt_cachinguse_native_cache_layoutr   	anthropicr   r   r   r   r   Nrestore_primaryTr   compressor_modelcompressor_context_lengthcompressor_base_urlcompressor_api_keycompressor_provider)r   context_lengthr   r   r   z.Primary runtime restored for new turn: %s (%s)z%Failed to restore primary runtime: %s)"r   _fallback_indexr   r   	monotonicr   r   r   r   r   r   r   r   r   rJ   r   _use_prompt_cachingrG   _use_native_cache_layoutr   r   r   r   r   r   r   r   r   context_compressorupdate_modelrN   r   r   rO   )rU   r   r   ccr   s        r   restore_primary_runtimer  *  s    $ 	 !"uu+Q//$.2B2BBBu		B9kJJJ5,-- 	+"((***9#B$788$&';$<! *,%N22Tu~7T*
 *
& >111FFFFFF')*='>E$(*+?(@E%&<&<&',@)A4U^U[QQ' ' 'E# )++?(@E%ELL 66R())( 7  EL %
'(9:-.+,-. 	 	
 	
 	
 %*! !<K	
 	
 	
 t   ?CCCuuuuus   GH 
I!H<<I>   PoolTimeoutReadTimeoutConnectErrorConnectTimeoutAPITimeoutErrorAPIConnectionErrorRemoteProtocolErrorOptional[str]c                   g }t          |d          r!|j        r|                    |j                   t          |d          r*|j        r#|j        |vr|                    |j                   t          |d          r|j        r|j        D ]}t          |t                    ro|                    d          p>|                    d          p)|                    d          p|                    d          }|r||vr|                    |           t          |dd          }|st          |t                    r|D ]}t          |t                    rt|                    d	          dk    r[|                    d          p|                    d          pd
}|
                                }|r||vr|                    |           |st          |t                    rk|rid}|D ]d}	t          j        t          j        z  }
t          j        |	||
          D ]1}|
                                }|r||vr|                    |           2e|rd                    |          S dS )aE  
    Extract reasoning/thinking content from an assistant message.
    
    OpenRouter and various providers can return reasoning in multiple formats:
    1. message.reasoning - Direct reasoning field (DeepSeek, Qwen, etc.)
    2. message.reasoning_content - Alternative field (Moonshot AI, Novita, etc.)
    3. message.reasoning_details - Array of {type, summary, ...} objects (OpenRouter unified)
    
    Args:
        assistant_message: The assistant message object from the API response
        
    Returns:
        Combined reasoning text, or None if no reasoning found
    r4   reasoning_contentreasoning_detailssummarythinkingr5   r   Nr   r3   )z<think>(.*?)</think>z<thinking>(.*?)</thinking>z<thought>(.*?)</thought>z<reasoning>(.*?)</reasoning>z2<REASONING_SCRATCHPAD>(.*?)</REASONING_SCRATCHPAD>r   r   )r   r4   rE   r'  r(  rI   rJ   rG   r   rg   rH   r    r   r   r   findallrT   )rU   assistant_messagereasoning_partsdetailr)  r5   blockthinking_textinline_patternspatternr   cleaneds               r   extract_reasoningr4    s    O  +.. <3D3N <0:;;;  "566 H;L;^ H.oEE""#4#FGGG  "566 4;L;^ 4'9 
	4 
	4F&$'' 	4 JJy)) *zz*--*zz),,* zz&))	   4wo==#**7333
 'D99G :z'488 :  	: 	:E%&& :599V+<+<
+J+J %		* 5 5 P69J9J Pb - 3 3 5 5  :]/%I%I#**=999 4z'377 4G 4
 ' 	4 	4GI-EGWEBBB 4 4++-- 4wo==#**73334  ,{{?+++4r   )error
api_kwargsDict[str, Any]r   r5  Optional[Exception]Optional[Path]c          	        	 t          j        |          }|                    dd           d |                                D             }d}	 t	          | j        dd          }n># t          $ r1}t                      j        	                    d|           Y d}~nd}~ww xY wt          j                                                    | j        |d| j                            d           | j        dk    rd	nd
 d|                     |           dd|dd}|t%          |          j        t)          |          d}dD ]}	t	          ||	d          }
|
|
||	<   t	          |dd          }|||d<   t	          |dd          }|^	 t	          |dd          |d<   |j        |d<   n># t          $ r1}t                      j        	                    d|           Y d}~nd}~ww xY w||d<   t          j                                        d          }| j        d| j         d| dz  }|                    t3          j        |ddt(                    d !           |                     | j         d"|            t;          d#          r*t=          t3          j        |ddt(                               |S # t          $ r)}| j        rtA          j!        d$|            Y d}~dS d}~ww xY w)%a  
    Dump a debug-friendly HTTP request record for the active inference API.

    Captures the request body from api_kwargs (excluding transport-only keys
    like timeout). Intended for debugging provider-side 4xx failures where
    retries are not useful.
    r   Nc                    i | ]
\  }}|||S Nr&   r(   kvs      r   
<dictcomp>z*dump_api_request_debug.<locals>.<dictcomp>  s    ???A1r   r   z,Could not extract API key for debug dump: %sPOST/codex_responsesz
/responsesz/chat/completionszBearer zapplication/json)AuthorizationzContent-Type)methodurlheadersbody)	timestamprf   r   request)r   r   )r   
request_idcodeparamr   rH  responser   response_statusresponse_textz,Could not extract error response details: %sr5  z%Y%m%d_%H%M%S_%frequest_dump__z.jsonF   )r<   indentdefaultzutf-8)encodingu$   🧾 Request debug dump written to: HERMES_DUMP_REQUEST_STDOUTz*Failed to dump API request debug payload: )"copydeepcopypopitemsr   r   r   r   re   r  r   now	isoformatrf   r   rQ   r   _mask_api_key_for_logsr   rv   r    r   strftimelogs_dir
write_textrK   rP   r   r   r   printverbose_loggingrN   rO   )rU   r6  r   r5  rH  r   r   dump_payload
error_info	attr_name
attr_value	body_attrresponse_objrI  	dump_file
dump_errors                   r   dump_api_request_debugrl    s   B}Z((D!!!?????	RelIt<<GG 	R 	R 	REELMqQQQQQQQQ	R "1133* .//44  EenXiFiFill  pC  E  E%Vu/K/KG/T/T%V%V$6   	(
 (
 U,u::* *J T 7 7	$UIt<<
),6Jy)vt44I$%.
6""5*d;;L'Z4;L-Y]4^4^J012>2CJ//  Z Z ZEEL&&'UWXYYYYYYYYZ %/L!LNN++,>??	N%XU5E%X%X	%X%X%XX	J|%3OOO 	 	
 	
 	

 	)ZZyZZ[[[788 	W$*\aQTUUUVVV     	WOUUUVVVtttttsn   A
J1 A$ #J1 $
B.'BJ1 BC'J1 F& %J1 &
G!0'GJ1 G!!CJ1 1
K$;KK$r   r   r   r   r   r   r   r   c                  ||n| j         pd}||n| j        pd}||n| j        pd}||n| j        pd}|                                }	|                                }
d|	v }t          |d          }d|                                v }|dk    }|o|dk    pt          |          dk    }|rd	S |s|r|rd
S |rd|	v rd
S |r|rd	S |r*|
dv }t          |d          pt          |d          }|s|rd	S d|	v }|
dv }|r|rd
S dS )uR  Decide whether to apply Anthropic prompt caching and which layout to use.

    Returns ``(should_cache, use_native_layout)``:
      * ``should_cache`` — inject ``cache_control`` breakpoints for this
        request (applies to OpenRouter Claude, native Anthropic, and
        third-party gateways that speak the native Anthropic protocol).
      * ``use_native_layout`` — place markers on the *inner* content
        blocks (native Anthropic accepts and requires this layout);
        when False markers go on the message envelope (OpenRouter and
        OpenAI-wire proxies expect the looser layout).

    Third-party providers using the native Anthropic transport
    (``api_mode == 'anthropic_messages'`` + Claude-named model) get
    caching with the native layout so they benefit from the same
    cost reduction as direct Anthropic callers, provided their
    gateway implements the Anthropic cache_control contract
    (MiniMax, Zhipu GLM, LiteLLM's Anthropic proxy mode all do).

    Qwen / Alibaba-family models on OpenCode, OpenCode Go, and direct
    Alibaba (DashScope) also honour Anthropic-style ``cache_control``
    markers on OpenAI-wire chat completions. Upstream pi-mono #3392 /
    pi #3393 documented this for opencode-go Qwen. Without markers
    these providers serve zero cache hits, re-billing the full prompt
    on every turn.
    Nr3   claudezopenrouter.ainousresearchr   r  zapi.anthropic.com)TTr   qwen>   
minimax-cnminimaxzapi.minimax.iozapi.minimaxi.com>   alibabaopencodeopencode-goopencode-zen)FF)r   r   r   r   r   r   r   )rU   r   r   r   r   eff_providereff_base_urleff_api_mode	eff_modelmodel_lowerr   	is_claudeis_openrouteris_nous_portalis_anthropic_wireis_native_anthropicis_minimax_provideris_minimax_hostmodel_is_qwenprovider_is_alibaba_familys                       r   anthropic_prompt_cache_policyr  /  s   B !) 4HH%.ORL'388%.:NBL'388%.:NBL+CI//##K!''))NK'I),HHM $|'9'9';';;N$(<< 	d[(b,=l,K,KOb,b 
  z  Y {  &K//{ Y z  ,0II!,0@AA G$\3EFF 	  	/ 	: k)M!/ 4 " " m  {<r   r   rJ   r   r   c                  ddl m}m} t          |          } |              ||                    d                     | j        dk    s6t          |                    dd                                        d          rKddlm	}  |di |}t                      j                            d|||                                            |S | j        d	k    s6t          |                    dd                                        d
          riddlm} d |                                D             }	 |di |	}t                      j                            d|||                                            |S | j        dk    rddlm}
m} t          |                    dd          pd          } ||          rd |                                D             }	d|	vr|                     |          }|||	d<    |
di |	}t                      j                            d|||                                            |S d|vr0|                     |                    dd                    }|||d<    t                      j        di |}t                      j                            d|||                                            |S )Nr   )_validate_base_url_validate_proxy_env_urlsr   zcopilot-acpr3   zacp://copilot)CopilotACPClientz-Copilot ACP client created (%s, shared=%s) %szgoogle-gemini-clizcloudcode-pa://)GeminiCloudCodeClientc                "    i | ]\  }}|d v 	||S )>   r   r   r   
project_iddefault_headersr&   r=  s      r   r@  z(create_openai_client.<locals>.<dictcomp>  s4     
 
 
QWWW qWWWr   z:Gemini Cloud Code Assist client created (%s, shared=%s) %sgemini)GeminiNativeClientis_native_gemini_base_urlc                "    i | ]\  }}|d v 	||S )>   r   r   r   http_clientr  r&   r=  s      r   r@  z(create_openai_client.<locals>.<dictcomp>  s4       A\\\ 1\\\r   r  z/Gemini native client created (%s, shared=%s) %sz(OpenAI client created (%s, shared=%s) %sr&   )agent.auxiliary_clientr  r  rJ   rG   r   r    rR   agent.copilot_acp_clientr  r   re   r   _client_log_contextagent.gemini_cloudcode_adapterr  r[  agent.gemini_native_adapterr  r  _build_keepalive_http_clientOpenAI)rU   r   r   r   r  r  r  r   r  safe_kwargsr  r  r   keepalive_https                 r   create_openai_clientr    sc   SSSSSSSS ''M}((44555~&&#m.?.?
B.O.O*P*P*[*[\k*l*l&======!!22M22;%%''		
 	
 	
 ~,,,M4E4EjRT4U4U0V0V0a0abs0t0t,HHHHHH
 
*0022
 
 
 '&5555H%%''		
 	
 	
 ~!!]]]]]]]]}((R88>B??$$X.. 	 !.!4!4!6!6  K K//!&!C!CH!M!M!-1?K.''66+66FEELA))++	   M$ M));;M<M<MjZ\<]<]^^%+9M-( SUU\**M**FEEL2!!##	   Mr   r3   c                	   ddl m} |s |||          }|dk    r1|dv r-t          |t                    r|rt	          j        dd|          }| j        }| j        }d| _        || _        || _        |r|| _	        || _
        t          | d          r| j                                         |r|| _        |dk    rdd	lm}	m}
m} |d
k    }|r|p| j        p |
            pdn
|p| j        pd}|| _        || _        |pt)          | dd          | _         |	|| j        t-          | j        | j                            | _        |r ||          nd| _        d| _        i | _        nq|p| j        }|p| j	        }||d| _        t-          | j        | j                  }|
|| j        d<   |                     t9          | j                  dd          | _        |                     || j	        ||          \  | _        | _        |                                   t          | d          r| j!        rddl"m#} d}	 ddl$m%}m&}  |            } ||          }n# tN          $ r d}Y nw xY w || j        | j	        | j        | j        t)          | dd          |          }| j!        (                    | j        || j	        t)          | dd          | j        | j
                   d| _)        t          | d          r| j!        r| j!        nd}| j        | j        | j	        | j
        t)          | dd          t9          | j                  | j        | j        |rt)          |d| j                  n| j        |rt)          |d| j	                  n| j	        |rt)          |dd          nd|rt)          |d| j                  n| j        |r|j*        nd|r|j+        ndd| _,        |dk    r-| j,        -                    | j        | j        | j        d           d| _.        d| _/        |pd0                                1                                |pd0                                1                                te          t)          | d g           pg           }rrk    rfd!|D             }|| _3        |r|d         nd| _4        tk          j6        d"||||           dS )#a5  Switch the model/provider in-place for a live agent.

    Called by the /model command handlers (CLI and gateway) after
    ``model_switch.switch_model()`` has resolved credentials and
    validated the model.  This method performs the actual runtime
    swap: rebuilding clients, updating caching flags, and refreshing
    the context compressor.

    The implementation mirrors ``_try_activate_fallback()`` for the
    client-swap logic but also updates ``_primary_runtime`` so the
    change persists across turns (unlike fallback which is
    turn-scoped).
    r   )determine_api_moder   >   rv  rw  z/v1/?$r3   Nr   )r   resolve_anthropic_token_is_oauth_tokenr  r   r   F)r   r   r   switch_modelTr   rm  r  )get_model_context_length)load_configget_compatible_custom_providers_config_context_length)r   r   r   config_context_lengthcustom_providersr   )r   r  r   r   r   r   r   r   r   )r   r   r   r   r   r   r  r  r  r  r  r  r  compressor_threshold_tokens)r   r   r   _fallback_chainc                    g | ]C}|                     d           pd                                                                hvA|DS )r   r3   )rG   rH   r   )r(   entrynew_normold_norms     r   r*   z switch_model.<locals>.<listcomp>  s\     
 
 
		*%%+2244::<<XxDXXX XXXr   z+Model switched in-place: %s (%s) -> %s (%s))7hermes_cli.providersr  rI   r    r   r   r   r   r  r   r   r   r   r   r   r   r   r  r  r   r   r   r   r   r   r   r   r   rJ   _anthropic_prompt_cache_policyr  r  _ensure_lmstudio_runtime_loadedr  agent.model_metadatar  hermes_cli.configr  r  r   r  _cached_system_promptr  threshold_tokensr   updater   r  rH   r   rg   r  _fallback_modelrN   r   )rU   	new_modelnew_providerr   r   r   r  	old_modelold_providerr   r  r  _is_native_anthropiceffective_keyeffective_base_sm_timeoutr  _sm_custom_providersr  r  _sm_cfgnew_context_length_ccfallback_chainr  r  s                           @@r   r  r    ss    877777  >%%lH== 	(((;;;x%% < < 6)R22I>L
 $(E  EK!EN  "!ENu()) '$$&&&   '''	
 	
 	
 	
 	
 	
 	
 	
 	
 	
  ,{:Ym  TTEMT5L5L5N5NTRTt{  uS  @E  @M  uS  QS%#0 $,$[?TVZ0[0[!"8"8540MM#
 #
 #
 G[$eOOM$B$B$B`e!!05=!3U^$& 
  
 35>5;OO".9E +22%&&! 3 
 
 	,,!^	 	- 	
 	
 >Eu= 
))+++ u*++ 
0H 
AAAAAA  $	(VVVVVVVV!kmmG#B#B7#K#K   	( 	( 	(#'   	(55K^M^")%1I4"P"P1
 
 
 	 --+-^E9b11^^ 	. 	
 	
 	
 #'E '.e5I&J&J
quOg
q%
"
"mqCNNN5)R00e233#7#(#ABEVGC%+>>>5;KNbwsJGGGTYTb=@Hgc9b999bKNbwsJGGGTYTb;>%ES%7%7A?B'Is';'; E  '''%%!&!9"'";"'";'
 '
 	 	 	 !&EE "))++1133H"))++1133H'%):B??E2FFN 
H 
X!5!5
 
 
 
 
-
 
 
 +E1?IN1--TEL5<L    s   4I I! I!Fr~   function_argseffective_task_idrB   pre_tool_block_checkedc           
        d}|s(	 ddl m}  ||||pd          }n# t          $ r Y nw xY w|t          j        d|id          S |d	k    r@dd
lm}	  |	|                    d          |                    dd          | j                  S |dk    r| 	                                }
|
s%ddl
m} t          j        d |            d          S ddlm}  ||                    dd          |                    d          |                    dd          |
| j                  S |dk    r|                    dd          }ddlm}  ||                    d          ||                    d          |                    d          | j                  }| j        r|                    d          dv rm	 | j                            |                    dd          ||                    dd          |                     ||                      n# t          $ r Y nw xY w|S | j        r5| j                            |          r| j                            ||          S |d!k    r@dd"lm}  ||                    d#d          |                    d$          | j        %          S |d&k    r|                     |          S t7                                          ||||| j        pd| j        rt=          | j                  ndd'(          S ))a0  Invoke a single tool and return the result string. No display logic.

    Handles both agent-level tools (todo, memory, etc.) and registry-dispatched
    tools. Used by the concurrent execution path; the sequential path retains
    its own inline invocation for backward-compatible display handling.
    Nr   )get_pre_tool_call_block_messager3   )task_idr5  Fr;   todo)	todo_tooltodosmerge)r  r  storesession_search)format_session_db_unavailable)successr5  )r  queryrole_filterlimitr   )r  r  r  dbcurrent_session_idmemorytarget)memory_toolactionr5   old_text)r  r  r5   r  r  >   r   replace)r  rB   )metadataclarify)clarify_toolquestionchoices)r  r  callbackdelegate_taskT)rB   rf   enabled_toolsskip_pre_tool_call_hook)hermes_cli.pluginsr  r   rK   rP   tools.todo_toolr  rG   _todo_store_get_session_db_for_recallhermes_stater  tools.session_search_toolr  rf   tools.memory_toolr  _memory_store_memory_manageron_memory_write_build_memory_write_metadatahas_toolhandle_tool_calltools.clarify_toolr  clarify_callback_dispatch_delegate_taskr   handle_function_callvalid_tool_namesrg   )rU   r~   r  r  rB   r   r  block_messager  
_todo_tool
session_dbr  _session_searchr  _memory_toolresult_clarify_tools                    r   invoke_toolr     s    $(M! 	JJJJJJ;;}6G6M2  MM  	 	 	D	 z7M2GGGG;;;;;;z##G,,##GU33#
 
 
 	

 
*	*	*5577
 	\BBBBBB:%:W:W:Y:YZZ[[[OOOOOO##GR00%))-88##GQ//$/
 
 
 	
 
(	"	"""8X66AAAAAA $$X..!%%i00"&&z22%
 
 
   	]%6%6x%@%@DV%V%V%55!%%h33!%%i44"?? 1%1 @  	 6        		 
5#8#A#A-#P#P 
$55m]SSS	)	#	#DDDDDD}"&&z266!%%i00+
 
 
 	

 
/	)	),,];;;uu))=*;%'-2:?:PZ$u5666VZ$( * 
 
 	
s    
**3AH 
HHrb   
str | Nonec                <   ddl ddlm} |sdS dd}dfd}dd
}|                                }|| j        v r|S  ||          }|| j        v r|S ||| ||          h}t          d          D ]x}	t                      }
|D ]`} ||          }|rQ|
                    |           |
                     ||                     |
                     ||                     a||
z  }y|D ]}|r|| j        v r|c S  ||| j        dd          }|r|d         S dS )at  Attempt to repair a mismatched tool name before aborting.

    Models sometimes emit variants of a tool name that differ only
    in casing, separators, or class-like suffixes. Normalize
    aggressively before falling back to fuzzy match:

    1. Lowercase direct match.
    2. Lowercase + hyphens/spaces -> underscores.
    3. CamelCase -> snake_case (TodoTool -> todo_tool).
    4. Strip trailing ``_tool`` / ``-tool`` / ``tool`` suffix that
       Claude-style models sometimes tack on (TodoTool_tool ->
       TodoTool -> Todo -> todo). Applied twice so double-tacked
       suffixes like ``TodoTool_tool`` reduce all the way.
    5. Fuzzy match (difflib, cutoff=0.7).

    See #14784 for the original reports (TodoTool_tool, Patch_tool,
    BrowserClick_tool were all returning "Unknown tool" before).

    Returns the repaired name if found in valid_tool_names, else None.
    r   N)get_close_matchessr    r#   c                z    |                                                      dd                              dd          S )Nrt   rR   )r   r  )r  s    r   _normzrepair_tool_call.<locals>._norm/  s0    wwyy  c**223<<<r   c                L     j         dd|                                           S )Nz(?<!^)(?=[A-Z])rR  )r   r   )r  r   s    r   _camel_snakez&repair_tool_call.<locals>._camel_snake2  s&    rv(#q1177999r   r  c                    |                                  }dD ]D}|                    |          r-| d t          |                                        d          c S Ed S )N)_toolz-toolr>   z_-)r   endswithrF   rQ   )r  lcsuffixs      r   _strip_tool_suffixz,repair_tool_call.<locals>._strip_tool_suffix5  sg    WWYY0 	6 	6F{{6"" 6CKK<(//555556tr   rS  r/   gffffff?)ncutoff)r  r    r#   r    )r  r    r#   r  )r   difflibr  r   r  ranger   r   )rU   rb   r  r  r	  r  lowered
normalizedcandsrR  extracstrippedmatchesr   s                 @r   repair_tool_callr    s   * III)))))) t= = = =: : : : : :    ooG%(((y!!JU+++ !':||I7N7NOE1XX  %% 	2 	2A))!,,H 2		(###		%%//***		,,x00111   	e,,,HHH  )?1SQQQG qz4r   c           	     "  
 g }| D ]n}|                     d          }|t                      j        j        vr(t                      j                            d|           Y|                    |           o|} t                      }| D ]r}|                     d          dk    rW|                     d          pg D ]?}t                      j                            |          }|r|	                    |           @st                      }| D ]G}|                     d          dk    r,|                     d          }|r|	                    |           H||z
  

rB
fd| D             } t                      j                            dt          
                     ||z
  }|rg }	| D ]}|	                    |           |                     d          dk    r|                     d          pg D ]j}t                      j                            |          }||v r>|	                    dt                      j                            |          d	|d
           k|	} t                      j                            dt          |                     | S )u   Fix orphaned tool_call / tool_result pairs before every LLM call.

    Runs unconditionally — not gated on whether the context compressor
    is present — so orphans from session loading or manual message
    manipulation are always caught.
    r0   z9Pre-call sanitizer: dropping message with invalid role %rr1   r2   r>   rB   c                t    g | ]4}|                     d           dk    r|                     d          v 2|5S )r0   r>   rB   )rG   )r(   r)   orphaned_resultss     r   r*   z)sanitize_api_messages.<locals>.<listcomp>  sO     
 
 
EE&MMV++n0E0EIY0Y0Y 0Y0Y0Yr   z6Pre-call sanitizer: removed %d orphaned tool result(s)u2   [Result unavailable — see context summary above])r0   r:   r5   rB   z0Pre-call sanitizer: added %d stub tool result(s))rG   r   rw   _VALID_API_ROLESre   r  rE   r   _get_tool_call_id_staticr   rF   _get_tool_call_name_static)r   r   rY   r0   surviving_call_idsr   cidresult_call_idsmissing_resultspatchedr  s             @r   sanitize_api_messagesr'  ^  s    H  wwvsuu}555EELK   H!ee 0 0776??k))ggl++1r 0 0eem<<R@@ 0&**3///55O ) )776??f$$''.))C )##C((( ');; 

 
 
 

 
 
 	D !!	
 	
 	
 )?:O 
(* 	 	CNN3wwv+--'',//52  B%%-@@DDCo--$*$'EEM$L$LR$P$P'[,/	( (    >  	
 	
 	
 Or   user_messageassistant_contentc                F  
 t          d |D                       rdS |                     |pd                                                                          

sdS t	          
          dk    rdS t          t          j        d
                    }|sdS d}d}|pd                                                                t          fd|D                       pd	v pd
v }t          
fd|D                       }t          
fd|D                       }	|s|	o|S )zNDetect a planning/ack message that should continue instead of ending the turn.c              3  r   K   | ]2}t          |t                    o|                    d           dk    V  3dS )r0   r>   N)rI   rJ   rG   )r(   rY   s     r   	<genexpr>z4looks_like_codex_intermediate_ack.<locals>.<genexpr>  s@      
S
S3:c4  >SWWV__%>
S
S
S
S
S
Sr   Fr3   i  u@   \b(i['’]ll|i will|let me|i can do that|i can help with that)\b)z	look intozlook atinspectscancheckanalyzreviewexplorereadopenruntestfixr  searchfindwalkthroughzreport back	summarize)	directoryzcurrent directoryzcurrent dircwdrepo
repositorycodebaseprojectfolder
filesystemz	file treefilespathc              3      K   | ]}|v V  	d S r<  r&   )r(   ro   	user_texts     r   r,  z4looks_like_codex_intermediate_ack.<locals>.<genexpr>  s(      @@FFi@@@@@@r   z~/rB  c              3      K   | ]}|v V  	d S r<  r&   r(   ro   assistant_texts     r   r,  z4looks_like_codex_intermediate_ack.<locals>.<genexpr>  s(      #Z#ZFn$<#Z#Z#Z#Z#Z#Zr   c              3      K   | ]}|v V  	d S r<  r&   rI  s     r   r,  z4looks_like_codex_intermediate_ack.<locals>.<genexpr>  s9       & &%+. & & & & & &r   )any_strip_think_blocksrH   r   rF   r"   r   r8  )rU   r(  r)  r   has_future_ackaction_markersworkspace_markersuser_targets_workspaceassistant_mentions_actionassistant_targets_workspacerJ  rG  s             @@r   !looks_like_codex_intermediate_ackrT    s    
S
S(
S
S
SSS u../@/FBGGMMOOUUWWN u
>T!!u
	UWeff N  uN*  #**,,2244I@@@@.?@@@@@ 	9	) 
 !$#Z#Z#Z#Z>#Z#Z#Z Z Z"% & & & &/@& & & # # #A&A`G``r   
source_msgapi_msgrj   c                   |                     d          dk    rdS |                     d          }t          |t                    r'|dk    r|                                 rd|d<   n||d<   dS |                                 }|                     d          }|r3|                     d          rt          |t                    r	|rd|d<   dS t          |t                    r	|r||d<   dS |rd|d<   dS |                    dd           dS )	zACopy provider-facing reasoning fields onto an API replay message.r0   r1   Nr'  r3   r  r4   r2   )rG   rI   r    _needs_thinking_reasoning_padrZ  )rU   rU  rV  rm   needs_thinking_padnormalized_reasonings         r   copy_reasoning_content_for_apir[    sR   ~~f,, ~~122H(C   r>>eAACC>+.G'((+3G'(<<>> &>>+66NN<(( +S11 !	 (+#$ &,, 1E ';#$  '*#$ KK#T*****r   c                \   t          | dd          }|dS 	 t          |dd          }|dS t          |dd          }|dS t          |dd          }|dS t          |dd          pt          |dd          pg }d}t          |          D ]}t          |d	d          pt          |d
d          }|(t          |dd          }	|	$t          |dd          }	|	t          |	dd          }	|	bddl}
	 |	                    d           |	                    d|
j        |
j        z            }|dk    r|dz  }n # t          $ r Y nt          $ r |dz  }Y nw xY w	 |	                    d           # t          $ r Y w xY w# 	 |	                    d           w # t          $ r Y w w xY wxY w|dk    r?t                      j
                            d|           |                     d           dS n># t          $ r1}t                      j
                            d|           Y d}~nd}~ww xY wdS )aE  Detect and clean up dead TCP connections on the primary client.

    Inspects the httpx connection pool for sockets in unhealthy states
    (CLOSE-WAIT, errors).  If any are found, force-closes all sockets
    and rebuilds the primary client from scratch.

    Returns True if dead connections were found and cleaned up.
    r   NF_client
_transport_pool_connectionsr   _network_stream_stream_sockstreamr/   r   Tu@   Found %d dead connection(s) in client pool — rebuilding clientdead_connection_cleanup)r   zDead connection check error: %s)r   rg   socketsetblockingrecvMSG_PEEKMSG_DONTWAITBlockingIOErrorOSErrorr   re   rO   _replace_primary_openai_clientr   r  )rU   r   r  	transportr   connections
dead_countconnrd  sock_socketdataexcs                r   cleanup_dead_connectionsrv  8  s    UHd++F~u7Cfi665Kt<<	5y'400<5D.$// tWd++ 	
 
%% 	 	D /66 24D11  ~67D11D|vx66#"4$77D|$$$$  '''yyG$4w7K$KLL3;;!OJ"         a


 $$T****   D$$T****   D>>EEL  R   008Q0RRR4   C C C<cBBBBBBBBC5s   G. G. G. BG. 2AD65E=6
E E=EE=EE=E-,G. -
E:7G. 9E::G. =F%?FF%
F"	F%!F"	"F%%AG. .
H)8'H$$H)c                   i }t          | dd          }d}t          |t                    r?t          |                    d          t                    r|                    d          n|}t          |t                    rj|                    d          p)|                    d          p|                    d          }t          |t                    r+|                                r|                                |d<   |                    d          p|                    d          }t          |t                    r+|                                r|                                |d<   d	D ]"}|                    |          }|d
vr||d<    n#|                    d          }|d
vrCd|vr?	 t          j                    t          |          z   |d<   n# t          t          f$ r Y nw xY wt          | dd          }	t          |	dd          }
|
r|
                    d          p|
                    d          }|rCd|vr?	 t          j                    t          |          z   |d<   n# t          t          f$ r Y nw xY w|
                    d          }|r	d|vr||d<   d|vr0t	          |                                           }|r|dd         |d<   d|vr|                    d          pd}t          |t                    rt          j        d|t          j                  }|rnt          |                    d                    }|                    d                                          dk    r|dz  n|}t          j                    |z   |d<   n[t          j        d|t          j                  }|r9t          j                    t          |                    d                    z   |d<   |S )z;Extract structured rate-limit details from provider errors.rH  Nr5  rL  r   r   r   error_description)	resets_atreset_at>   Nr3   rz  retry_afterrN  rG  zretry-afterzRetry-Afterzx-ratelimit-reseti  r3   z/quotaResetDelay[:\s\"]+(\\d+(?:\\.\\d+)?)(ms|s)r/   rS  msg     @@z?retry\s+(?:after\s+)?(\d+(?:\.\d+)?)\s*(?:sec|secs|seconds|s\b))r   rI   rJ   rG   r    rH   r   floatrl   
ValueErrorr   r8  r   groupr   )r5  contextrH  payloadr   r   keyr-   r{  rN  rG  ratelimit_resetraw_messagedelay_matchseconds	sec_matchs                   r   extract_api_error_contextr    s    G5&$''DG$ U'1$((72C2CT'J'JT$((7###PT'4   V$$SF(;(;Sw{{7?S?Sfc"" 	/v||~~ 	/ &GH++i((LGKK8K,L,Lgs## 	1 	1!(GI, 	 	CKK$$EJ&&&+
# ' kk-00j((Zw-F-F&*ikkE+4F4F&F
##z*    uj$//Hh	400G 	2kk-00NGKK4N4N 	:W44&*ikkE+4F4F&F
##z*   !++&9:: 	2z88"1GJ%jj&&(( 	3!,TcT!2GI  ++i((.Bgs## 	R)$VX_acanooK Rk//2233,7,=,=a,@,@,F,F,H,HD,P,P%&..V[&*ikkG&;
##IVM 	
  R*.)++iooa>P>P8Q8Q*QGJ'Ns$   ,&G G'&G'?&I& &I:9I:num_tool_msgsc                :   |dk    s|sdS |                                  }|sdS d}t          t          |          dz
  t          t          |          |z
  dz
  d          d          D ]<}||         }t	          |t
                    r|                    d          dk    r|} n=|st          | dd          }|<|5  | j        r| j        dz   |z   | _        n|| _        ddd           n# 1 swxY w Y   n"t          | d	d          }|r|dz   |z   n|| _        dS d
| }	||                             dd          }
t	          |
t                    si	 |
rt          |
          ng }|                    d|	                                d           |||         d<   n-# t          $ r |
 |	 ||         d<   Y nw xY w|
|	z   ||         d<   t                      j                            dt          |          |dd         t          |          dk    rdndz              dS )uQ  Append any pending /steer text to the last tool result in this turn.

    Called at the end of a tool-call batch, before the next API call.
    The steer is appended to the last ``role:"tool"`` message's content
    with a clear marker so the model understands it came from the user
    and NOT from the tool itself. Role alternation is preserved —
    nothing new is inserted, we only modify existing content.

    Args:
        messages: The running messages list.
        num_tool_msgs: Number of tool results appended in this batch;
            used to locate the tail slice safely.
    r   Nr/   r   r0   r>   _pending_steer_lockr6   _pending_steerz

User guidance: r5   r3   r   r   z9Delivered /steer to agent after tool batch (%d chars): %sx   z...)_drain_pending_steerr  rF   maxrI   rJ   rG   r   r  r    rg   rE   lstripr   r   re   r   )rU   r   r  
steer_text
target_idxr]   rY   _lockrm   ro   existing_contentblockss               r   #apply_pending_steer_to_tool_resultsr    s    ++--J  J3x==1$c#h---*G!*KR&P&PRTUU  qkc4   	SWWV__%>%>JE 4d;; 6 6' 6+0+?$+F+SE((+5E(	6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 u&6==HEM#]HtOj$@$@S]E /://F
+//	2>>&,, D	L/?GT*+++RFMM66==??CCDDD.4HZ ++ 	L 	L 	L1A.K6.K.KHZ +++	L +;V*CY'EELCJ4C4S__s%:%:EEC    s%   ;"C))C-0C-AF F54F5r   c                   ddl }d}	 t          | dd          }|dS t          |dd          }|dS t          |dd          }|dS t          |dd          pt          |dd          pg }t          |          D ]}t          |dd          pt          |dd          }|'t          |d	d          }	|	$t          |d
d          }	|	t          |	d	d          }	|	a	 |	                    |j                   n# t
          $ r Y nw xY w	 |	                                 n# t
          $ r Y nw xY w|dz  }n># t          $ r1}
t                      j	        
                    d|
           Y d}
~
nd}
~
ww xY w|S )a  Force-close underlying TCP sockets to prevent CLOSE-WAIT accumulation.

    When a provider drops a connection mid-stream, httpx's ``client.close()``
    performs a graceful shutdown which leaves sockets in CLOSE-WAIT until the
    OS times them out (often minutes).  This method walks the httpx transport
    pool and issues ``socket.shutdown(SHUT_RDWR)`` + ``socket.close()`` to
    force an immediate TCP RST, freeing the file descriptors.

    Returns the number of sockets force-closed.
    r   Nr]  r^  r_  r`  ra  rb  rc  rd  r/   z'Force-close TCP sockets sweep error: %s)rf  r   rg   shutdown	SHUT_RDWRrl  closer   r   re   r  )r   rs  closedr  rn  r   ro  rq  rd  rr  ru  s              r   force_close_tcp_socketsr    s	    F)Kfi661Kt<<	1y'400<1 D.$// tWd++ 	
 %% 	 	D/66 24D11  ~67D11D|vx66#"4$77D|g/0000   

   aKFF-	.  K K KDcJJJJJJJJKMsp   D4 D4 D4 BD4 C76D4 7
DD4 DD4 DD4 
D*'D4 )D**	D4 4
E/>'E**E/)rd   r   r   r   r   r   r	  r  r4  rl  r  r  r  r   r  r'  rT  r[  rv  r  r  r  )r   r   r   r    r!   r"   r#   r   )r   rg   rf   r    r#   rh   )r   r   r#   rh   )r5   r    r#   r    )
r   r   r   r"   r   r   r   r   r#   r   )r   r   r   rh   r   rh   r#   r"   )r   r   r#   r   )r#   r"   )r#   r%  )r6  r7  r   r    r5  r8  r#   r9  )
r   r%  r   r%  r   r%  r   r%  r#   r   )r   rJ   r   r    r   r"   r#   r   )r3   r3   r3   )NNF)r~   r    r  rJ   r  r    rB   r%  r   rg   r  r"   r#   r    )rb   r    r#   r  )r(  r    r)  r    r   r   r#   r"   )rU  rJ   rV  rJ   r#   rj   )r5  r   r#   r7  )r   rg   r  rh   r#   rj   )r   r   r#   rh   )B__doc__
__future__r   rX  rK   rN   osr   	threadingr   uuidr   pathlibr   typingr   r   r   r	   r
   hermes_cli.timeoutsr   agent.message_sanitizationr   r   agent.tool_dispatch_helpersr   agent.trajectoryr   agent.error_classifierr   r   utilsr   r   r   r   ru   rv   re   r   rd   r   r   r   r   r   r	  r  	frozensetr   r4  rl  r  r  r  r   r  r'  rT  r[  rv  r  r  r  __all__r&   r   r   <module>r     s3   , # " " " " "    				 				                   3 3 3 3 3 3 3 3 3 3 3 3 3 3 < < < < < <        B A A A A A 8 8 8 8 8 8 E E E E E E E E ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^		8	$	$  g g g g\ 	k k k k k k^b b b bL\ \ \ \J 37.2c" c" c" c" c" c"NN N N NhR R R RlS S S Sn (i ) ) )   N N N Nn "&P P P P P Pn #""f f f f f fT[ [ [ [|@ @ @ @J GK05U
 U
 U
 U
 U
rF F F FTD D D DPEa Ea Ea EaTD+ D+ D+ D+PD D D DP> > > >D= = = =B8 8 8 8x  r   