
    ,j                       d Z 	 ddlZn# e$ r Y nw xY wddlZddlZddlZddlZddlZddlZ ej	        e
          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mZmZmZ ddlmZ ddlmZ ddlmZ ddlmZ ded	ee         fd
Z ddl!m"Z"m#Z#m$Z$ ddl%m&Z& ddl'm(Z( ddl)m*Z*m+Z+  e            Z, ee-          j.        dz  Z/ e(e,e/          Z0e0re0D ]Z1e2                    de1           ne2                    d           ddl3m4Z4m5Z5m6Z6m7Z7 ddl8m9Z9 ddl:m;Z< ddl=m>Z> ddl?m@Z@ ddlAmBZB ddlCmDZD ddlEmFZFmGZG ddlHmIZI ddlJmKZK ddlLmMZM ddlNmOZOmPZPmQZQmRZRmSZSmTZT ddl!mUZU dd lVmWZWmXZXmYZYmZZZm[Z[m\Z\m]Z]m^Z^m_Z_m`Z`maZa dd!lbmcZdmeZfmgZhmiZi dd"ljmkZkmlZlmmZm dd#lnmoZpmqZq dd$lrmsZsmtZu dd%lvmwZwmxZxmyZymzZzm{Z{m|Z|m}Z}m~Z~mZmZ dd&lmZmZmZmZmZ d'Z ej                    Zd(Zd	efd)Zddd*d+edz  d,edz  d	efd-Zd	efd.Z G d/ d0e          Z G d1 d2          Z	 	 	 	 	 	 	 	 	 	 	 	 dDd7ed8ed9ed,ed:ed;ed<ed=ed>ed?ed@edAefdBZe
dCk    rddlZ ej        e           dS dS )Eao  
AI Agent Runner with Tool Calling

This module provides a clean, standalone agent that can execute AI models
with tool calling capabilities. It handles the conversation loop, tool execution,
and response management.

Features:
- Automatic tool calling loop until completion
- Configurable model parameters
- Error handling and recovery
- Message history management
- Support for multiple model providers

Usage:
    from run_agent import AIAgent
    
    agent = AIAgent(base_url="http://localhost:30000/v1", model="claude-opus-4-20250514")
    response = agent.run_conversation("Tell me about the latest Python updates")
    N)ListDictAnyOptional)datetime)Path)SimpleNamespace)get_hermes_homesourcereturnc                     | dk    rdS t           j                            d          pd                                                                }|r|dk    rdS 	 t          j                    S # t          $ r Y dS w xY w)a*  Working directory to stamp on a new session row, or None.

    Only local CLI sessions get a recorded cwd: the directory the process was
    launched from is meaningful for ``hermes -c`` / ``--resume`` (relaunch
    where you left off). Gateway/cron/remote-backend sessions have no stable
    host cwd to restore, so they record nothing.

    ``TERMINAL_ENV`` is set by the CLI's config bridge (``load_cli_config``);
    a non-"local" backend (docker/ssh/modal/...) means the host cwd is
    irrelevant to the agent's tools, so we skip it there too.
    cliNTERMINAL_ENVlocal)osenvirongetstriplowergetcwdOSError)r   backends     ./home/ubuntu/.hermes/hermes-agent/run_agent.py_launch_cwd_for_sessionr   D   s     tz~~n--8??AAGGIIG 7g%%ty{{   tts   A, ,
A:9A:)OpenAI_SafeWriter_get_proxy_for_base_url)IterationBudget)load_hermes_dotenv)get_provider_request_timeoutget_provider_stale_timeoutz.env)hermes_homeproject_envz$Loaded environment variables from %sz7No .env file found. Using system environment variables.)get_tool_definitionsget_toolset_for_toolhandle_function_callcheck_toolset_requirements)
cleanup_vm)set_interrupt)cleanup_browser)sanitize_context)FailoverReason)redact_sensitive_text)estimate_request_tokens_roughis_local_endpoint)normalize_usage)ContextCompressor)jittered_backoff)DEFAULT_AGENT_IDENTITYbuild_skills_system_promptbuild_context_files_promptbuild_environment_hintsbuild_nous_subscription_promptload_soul_md)_get_proxy_from_env)_SURROGATE_RE_sanitize_surrogates_sanitize_structure_surrogates_sanitize_messages_surrogates%_escape_invalid_chars_in_json_strings_repair_tool_call_arguments_strip_non_ascii_sanitize_messages_non_ascii_sanitize_tools_non_ascii_strip_images_from_messages_sanitize_structure_non_ascii)"_derive_responses_function_call_id_deterministic_call_id_split_responses_tool_id_summarize_user_message_for_log)ToolGuardrailDecisionappend_toolguard_guidancetoolguard_synthetic_result)FILE_MUTATING_TOOL_NAMESfile_mutation_result_landed)convert_scratchpad_to_thinksave_trajectory)
_should_parallelize_tool_batch_is_destructive_command_extract_parallel_scope_path_paths_overlap_is_multimodal_tool_result_multimodal_text_summary!_append_subdir_hint_to_multimodal_extract_file_mutation_targets_extract_error_preview_trajectory_normalize_msg)atomic_json_writebase_url_host_matchesbase_url_hostnameis_truthy_value"model_forces_max_completion_tokens   z0.14.1c                      ddl m}  dd|  iS )zGReturn the User-Agent RouterMint needs to avoid Cloudflare 1010 blocks.r   )__version__
User-AgentzHermesAgent/)
hermes_clira   )_HERMES_VERSIONs    r   _routermint_headersre      s.    999999 	6_66     )providerbase_urlrg   rh   c                    | dS |                                  sdS |dk    s$t          |pd                              d          rdS t          |                                           dk    S )u?  Decide whether to wait for credential-pool rotation instead of falling back.

    The existing pool-rotation path requires the pool to (1) exist and (2) have
    at least one entry not currently in exhaustion cooldown.  But rotation is
    only meaningful when the pool has more than one entry.

    With a single-credential pool (common for Gemini OAuth, Vertex service
    accounts, and any "one personal key" configuration), the primary entry
    just 429'd and there is nothing to rotate to.  Waiting for the pool
    cooldown to expire means retrying against the same exhausted quota — the
    daily-quota 429 will recur immediately, and the retry budget is burned.

    Additionally, Google CloudCode / Gemini CLI rate limits are ACCOUNT-level
    throttles — even a multi-entry pool shares the same quota window, so
    rotation won't recover.  Skip straight to the fallback for those (#13636).

    In those cases we must fall back to the configured ``fallback_model``
    instead.  Returns True only when rotation has somewhere to go.

    See issues #11314 and #13636.
    NFgoogle-gemini-cli cloudcode-pa://   )has_availablestr
startswithlenentries)poolrg   rh   s      r   !_pool_may_recover_from_rate_limitrt      su    0 |u u &&&#hn"*=*=*H*HIZ*[*[&ut||~~""rf   c                      ddl } dt           d|                                                                  d|                                  d}|d|dd	S )
z8Return default HTTP headers required by Qwen Portal API.r   Nz	QwenCode/z (z; )enablez
qwen-oauth)rb   zX-DashScope-CacheControlzX-DashScope-UserAgentzX-DashScope-AuthType)platform_QWEN_CODE_VERSIONsystemr   machine)_plat_uas     r   _qwen_portal_headersr~     sf    
X(
X
XELLNN,@,@,B,B
X
Xemmoo
X
X
XC$,!$ ,	  rf   c                   f     e Zd ZdZdddddedee         dee         dee         ddf
 fd	Z xZS )
_StreamErrorEventa  Synthesized provider error surfaced from a Responses ``error`` SSE frame.

    Some Codex-style Responses backends (xAI for subscription/quota
    failures, custom relays under malformed-tool-call conditions) emit a
    standalone ``type=error`` frame instead of routing the failure
    through ``response.failed`` or returning an HTTP 4xx.  The fallback
    streaming path raises this exception so ``_summarize_api_error`` and
    ``_extract_api_error_context`` see a familiar ``.body`` /
    ``.status_code`` shape and the entitlement detector can match the
    underlying provider message ("do not have an active Grok
    subscription", etc.).
    N)codeparamstatus_codemessager   r   r   r   c                    t                                          |           || _        || _        || _        || _        d|||ddi| _        d S )Nerror)r   r   r   type)super__init__r   r   r   r   body)selfr   r   r   r   	__class__s        r   r   z_StreamErrorEvent.__init__'  sb     	!!!	
& "	 %
			rf   )	__name__
__module____qualname____doc__ro   r   intr   __classcell__)r   s   @r   r   r     s         " ##%)
 
 

 sm	

 }
 c]
 

 
 
 
 
 
 
 
 
 
rf   r   c                      e Zd ZdZdZedefd            Zej        deddfd            Z	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddededededede	e         dz  dede	e         dz  dede
dedee         dee         deded ed!ed"ed#e
d$ed%ee         d&ee         d'ee         d(ed)ed*ed+ee         d,ed-ed.ed/ed0ed1ed2ed3ed4ed5ed6ed7ed8ed9ed:ed;e
d<eeef         d=ed>eeef         d?eeeef                  d@edAedBedCedDedEedFedGedHedIedJedKedLedMdNdOeeef         dPedQe
dRe
dSe
dTefdUZdV ZddWZdddddXdYdZee         d[ee         d\ee	         d]ed^eddfd_Z	 	 	 dd\ee	         dZee         d]efd`Zddaee
         ddfdbZddcZdd ZddedfefdgZdefdhZdefdiZdjeddfdkZdjeddfdlZddmZdneddfdoZ djeddfdpZ!djeddfdqZ"ddrZ#ddsZ$	 ddteeeeef                           deee
f         fduZ%dvdwl&m'Z( e)deeef         fdx            Z*dyeeef         dzeddfd{Z+e)d|e,defd}            Z-d|e,defd~Z.ddded|e,de
de
dedyeeeef                  ddfdZ/ddd|e,de
de
dedyeeeef                  ddfdZ0dede,ddfdZ1deeef         fdZ2ddZ3ddZ4ddedefdZ5ddedefdZ6ddedefdZ7defdZ8de9eef         fdZ:dedefdZ;ddee         dee         fdZ<defdZ=ddddddee         dee         dee         dee         de9eef         f
dZ>e)dedefd            Z?e)dddedee         defd            Z@de
deAfdZBe)dedee
         fd            ZCdedefdZDdedefdZEe)dedefd            ZFdefdZG	 ddedtee	         defdZHdededteeeef                  defdZIdee         fdZJdeddfdZKdvdlLmMZMmNZNmOZO e)dee         dee         dee         fd            ZP	 	 ddee         dededdfdZQddddddee         dee         dee         dee         deeef         f
dZRdtee         ddfdZSddtee         dee         fdZTdtee         ddfdZUdtee         de
fdZVddtee         dee         fdZWdtee         dee         fdZXdefdZYdteeeef                  dededeeeef                  fdZZdteeeef                  dedefdZ[e)deeeef                  dee
         defdń            Z\e)dedefdǄ            Z]e)d|e^defdȄ            Z_dnedee         fdɄZ`dedefd˄Zae)d|e^deeef         fd̄            Zbdedeeeef                  fd΄Zce)de
fdτ            Zde)dnedefdЄ            ZeefdvddddԜdede
de
de
de
defdل            Zgefdedefdڄ            Zhdeeeef                  deeef         fdۄZidededee         deeef         fd݄Zjddddddޜdededede
dedeeeef                  dededee
         dee
         dee
         dee         dee         ddfdZkdddeeef         ded|ee^         deel         fdZme)dedefd            Zne)d             Zoddteeeef                  fdZpddjeddfdZqddZrdedefdZsdee         fdZtdedeeef         dededdf
dZudefdZv ewjx        d          Zyefdedefd            Zzefdeeeeef         f         defd            Z{defdZ|e)dedefd            Z}dte	d e
ddfdZ~deddfdZdzeddfdZd ZdzeddfdZddZdefdZd	 Zd
 ZdzeddfdZdeAfdZddte	ddfdZddte	ddfdZdddedededte	dz  ddf
dZddZddZdeeeef                  ddfdZedefd            Zddedeeef         fdZddedefdZe)defd            Ze)defd            Z eh d          Ze)dteeeef                  deeeef                  fd            Ze)d eeef         defd!            Ze)dteeeef                  deeeef                  fd"            Ze)d#e	de	fd$            Ze)d#e	de	fd%            Zdededz  fd&Zd' Ze)dd(ed)ed*e
defd+            Ze)d,ede9ee         ee         f         fd-            Z	 dd.ed/ee         defd0Zdefd1Zdefd2Zdej        fd3Ze)d4edefd5            Ze)ddedefd6            Zd7eAded8edefd9Ze)d4ede
fd:            Zd4eded8eddfd;Zdedefd<Zdedefd=Zdefd>Ze)deAdefd?            Zd@edeAfdAZddBdedeeA         defdCZd4ededdfdDZd4ededdfdEZddeAd4edFefdGZddeAd4efdHZdXdedfedefdIZdXdedfedefdJZdefdKZdefdLZdeddfdMZddNZddOZdddPdee
         dQedRee         deeeef                  de9eef         f
dSZdefdTZdeAfdUZddVZdeAfdWZddXZdeddfdYZe)dedefdZ            Zdedefd[ZĐd\eeef         ddfd]Zdeddfd^Zdeddfd_Zdeddfd`ZdefdaZddbdeAdFefdcZʐdddddefdeZdefdfZdefdgZ͐dhe^de
de
defdiZe)dedefdj            Ze)dkede9eeel         f         fdl            ZАdkedmedefdnZdefdoZdefdpZdedmedefdqZԐddefdrZՐdse	de	fdtZ֐dse	de	fduZdededefdvZؐdse	defdwZِdse	defdxZdefdyZdefdzZܐdse	de	fd{Zdte	ddfd|Zސdse	deAfd}Zdefd~Zde	e         fdZdee         fdZdeAdz  fdZdedeAfdZdefdZdefdZdefdZdefdZdeAdeAddfdZdse	de
fdZe)ddeAdddeAfd            Ze)ddddte	d,ede
fd            ZdefdZddddddte	dede
dededfede9fdZdeddfdZdedefdZdedeAdededef
dZdedefdZddte	dede
ddfdZdeAdefdZ	 	 	 	 ddedeAdedee         dte	dededee	eAeef                           defdZe)ddedededefd            Zddte	dede
ddfdZddte	dede
ddfdZdte	de
defdZ	 	 	 	 	 ddededeeeef                  dedee         dee         deeef         fdZddjedee         defdZdddededteeeef                  dededeeef         fdZdS (  AIAgentz
    AI Agent with tool calling capabilities.

    This class manages the conversation flow, tool execution, and response handling
    for AI models that support function calling.
    z[hermes-agent: tool call arguments were corrupted in this session and have been dropped to keep the conversation alive. See issue #15236.]r   c                     | j         S N)	_base_urlr   s    r   rh   zAIAgent.base_urlM  s
    ~rf   valueNc                 v    || _         |r|                                nd| _        t          |          | _        d S Nrk   )r   r   _base_url_lowerr\   _base_url_hostnamer   r   s     r   rh   zAIAgent.base_urlQ  s8    05=u{{}}}2"3E":":rf   rk   Z         ?Falld        
   rh   api_keyrg   api_modeacp_commandacp_argscommandargsmodelmax_iterations
tool_delayenabled_toolsetsdisabled_toolsetssave_trajectoriesverbose_logging
quiet_modetool_progress_modeephemeral_system_promptlog_prefix_chars
log_prefixproviders_allowedproviders_ignoredproviders_orderprovider_sortprovider_require_parametersprovider_data_collectionopenrouter_min_coding_score
session_idtool_progress_callbacktool_start_callbacktool_complete_callbackthinking_callbackreasoning_callbackclarify_callbackread_terminal_callbackstep_callbackstream_delta_callbackinterim_assistant_callbacktool_gen_callbackstatus_callbacknotice_callbacknotice_clear_callback
max_tokensreasoning_configservice_tierrequest_overridesprefill_messagesrx   user_iduser_id_alt	user_namechat_id	chat_name	chat_type	thread_idgateway_session_keyskip_context_filesload_soul_identityskip_memoryparent_session_iditeration_budgetr   fallback_modelcheckpoints_enabledcheckpoint_max_snapshotscheckpoint_max_total_size_mbcheckpoint_max_file_size_mbpass_session_idcF                    ddl m}F  |F| fi d|d|d|d|d|d|d	|d
|d|	d|
d|d|d|d|d|d|d|d|d|d|d|d|d|d|d|d|d|d|d|d |d!|d"| d#|!d$|"d%|#d&|$d'|%d(|&d)|'d*|(d+|)d,|*d-|+d.|,d/|-d0|.d1|/d2|0d3|1d4|2d5|3d6|4d7|5d8|6d9|7d:|8d;|9d<|:d=|;d>|<d?|=d@|>dA|?dB|@dC|AdD|BdE|CdF|DdG|E dHS )Iu2   Forwarder — see ``agent.agent_init.init_agent``.r   )
init_agentrh   r   rg   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   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rx   r   r   r   r   r   r   r   r   r   r   r   
session_dbr   r   r   credential_poolr   r   r   r   r   N)agent.agent_initr   )Gr   rh   r   rg   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   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rx   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   sG                                                                          r   r   zAIAgent.__init__W  s7   R 	0/////
G	
 G	
 G	
XG	
 GG	
 X	G	

 XG	
 $G	
 XG	
 GG	
 G	
 %G	
 *>G	
 "zG	
 .-G	
 0/G	
 0/G	
  ,O!G	
" "z#G	
$  21%G	
& %<$;'G	
( .-)G	
* "z+G	
, 0/-G	
. 0//G	
0 ,O1G	
2 (-3G	
4 )D(C5G	
6 &>%=7G	
8 )D(C9G	
: "z;G	
< $:#9=G	
> !4 3?G	
@ $:#9AG	
B 0/CG	
D  21EG	
F .-GG	
H $:#9IG	
J (-KG	
L #8"7MG	
N (B'AOG	
P 0/QG	
R ,OSG	
T ,OUG	
V #8"7WG	
X "zYG	
Z .-[G	
\ &]G	
^ 0/_G	
` .-aG	
b XcG	
d GeG	
f $gG	
h  iiG	
j GkG	
l  imG	
n  ioG	
p  iqG	
r !4 3sG	
t  21uG	
v  21wG	
x $yG	
z "z{G	
| 0/}G	
~ .-G	
@ *>AG	
B ,OCG	
D !4 3EG	
F &>%=GG	
H *F)EIG	
J )D(CKG	
L ,OMG	
 G	
 G	
 G	
rf   c                     | j         | j         S 	 ddlm}  |            | _         | j         S # t          $ r'}t                              dd           Y d}~dS d}~ww xY w)ai  Return a SessionDB for recall, lazily creating it if an entrypoint forgot.

        Most frontends pass ``session_db`` into ``AIAgent`` explicitly, but recall
        is important enough that a missing constructor argument should degrade by
        opening the default state DB instead of making the advertised
        ``session_search`` tool unusable.
        Nr   )	SessionDBz SessionDB unavailable for recallTexc_info)_session_dbhermes_stater   	Exceptionloggerdebug)r   r   excs      r   _get_session_db_for_recallz"AIAgent._get_session_db_for_recall  s     '##	......(y{{D## 	 	 	LL;dLKKK44444	s   , 
AAAc                 z   | j         s| j        sdS | j        pt          j                            dd          }	 | j                            | j        || j        | j	        | j
        d| j        t          |                     d| _         dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)zDCreate session DB row on first use. Disables _session_db on failure.NHERMES_SESSION_SOURCEr   )r   r   r   model_configsystem_promptr   r   cwdTz5Session DB creation failed (will retry next turn): %s)_session_db_createdr   rx   r   r   r   create_sessionr   r   _session_init_model_config_cached_system_prompt_parent_session_idr   r   r   warning)r   r   es      r   _ensure_db_sessionzAIAgent._ensure_db_session  s    # 	4+; 	FP"*..1H%"P"P	++?j!<"8"&"9+F33 , 	 	 	 (,D$$$ 	 	 	 NNG        	s   AB
 

B:B55B:Told_session_idnew_session_idprevious_messagescarry_over_contextreset_enginer  r  r  r  r  c          
         t          | dd          }|sdS |r\|Zt          |d          rJ	 |                    ||           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY w|rXt          |d          rH	 |                                 n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wt          |p|dup|p|          }	|pt          | dd          pd}
|	r|
rt          |d	          r||t          | d
d          pt          j	        
                    dd          t          | dd          t          |dd          t          | dd          d}|                    |           d |                                D             }	  |j        |
fi | n2# t          $ r%}t                              d|           Y d}~nd}~ww xY w|r`|r`|
r`t          |d          rR	 |                    ||
           dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY wdS dS dS dS )a  Notify the active context engine about a host session transition.

        Generic host-side lifecycle helper. The built-in compressor keeps its
        existing reset behavior; plugin engines that implement richer hooks
        (``on_session_end``, ``on_session_reset``, ``on_session_start``,
        ``carry_over_new_session_context``) can flush old-session state,
        reset runtime counters, bind to the new session, and optionally
        carry retained context forward.
        context_compressorNon_session_endz3context engine on_session_end during transition: %son_session_resetz5context engine on_session_reset during transition: %sr   rk   on_session_startrx   r   r   r   context_length_gateway_session_key)r  r  rx   r   r  conversation_idc                 "    i | ]\  }}|d v	||S )r    .0kvs      r   
<dictcomp>z>AIAgent._transition_context_engine_session.<locals>.<dictcomp>I  s)    [[[daqPZGZGZQGZGZGZrf   z5context engine on_session_start during transition: %scarry_over_new_session_contextzCcontext engine carry_over_new_session_context during transition: %s)getattrhasattrr  r   r   r   r  boolr   r   r   updateitemsr  r  )r   r  r  r  r  r  extra_contextenginer   should_starttarget_session_idstart_contexts               r   "_transition_context_engine_sessionz*AIAgent._transition_context_engine_session  s~   & 3T:: 	F 	Y/;P`@a@a;Y%%n6GHHHH Y Y YRTWXXXXXXXXY  	[GF,>?? 	[['')))) [ [ [TVYZZZZZZZZ[   ,! 	
 
 +SgdL".M.MSQS 	[- 	['&BT2U2U 	["0&8#D*d;;mrz~~Negl?m?m w33")&2BD"I"I#*41G#N#N M   ///[[m.A.A.C.C[[[M[''(9KK]KKKK [ [ [TVYZZZZZZZZ[ 		i		i "		i  @AA			ii55nFWXXXXX i i ibdghhhhhhhhhi		i 		i 		i 		i 		i 		i 		i 		is_   A 
A1A,,A1B 
C&CC'F6 6
G% G  G%?H 
I!IIc                    d| _         d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _	        d| _
        d| _        d| _        |                     |t          | dd          ||d           dS )	ax  Reset all session-scoped token counters to 0 for a fresh session.
        
        This method encapsulates the reset logic for all session-level metrics
        including:
        - Token usage counters (input, output, total, prompt, completion)
        - Cache read/write tokens
        - API call count
        - Reasoning tokens
        - Estimated cost tracking
        - Context compressor internal counters
        
        The method safely handles optional attributes (e.g., context compressor)
        using ``hasattr`` checks.

        When ``previous_messages`` / ``old_session_id`` / ``carry_over_context``
        are provided, the active context engine is notified through the
        full transition lifecycle (``_transition_context_engine_session``)
        instead of a bare reset. Default callers pass nothing and keep the
        existing reset-only behavior.
        r   g        unknownnoner   NTr  )session_total_tokenssession_input_tokenssession_output_tokenssession_prompt_tokenssession_completion_tokenssession_cache_read_tokenssession_cache_write_tokenssession_reasoning_tokenssession_api_callssession_estimated_cost_usdsession_cost_statussession_cost_source_user_turn_countr#  r  )r   r  r  r  s       r   reset_session_statezAIAgent.reset_session_stateZ  s    6 %&!$%!%&"%&")*&)*&*+'()%!"*-'#, #)  !" 	//)"4t<</1 	0 	
 	
 	
 	
 	
rf   config_context_lengthc           
      2   | j         pd                                                                dk    rdS 	 ddlm} ddlm} |t          | dd          }t          |pd|          } || j	        | j
        t          | dd          |          }|rSt          | d	d          }|B|                    | j	        || j
        t          | dd          | j         | j        
           dS dS dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)zT
        Preload the LM Studio model with at least Hermes' minimum context.
        rk   lmstudioNr   )MINIMUM_CONTEXT_LENGTH)ensure_lmstudio_model_loaded_config_context_lengthr   r
  )r   r  rh   r   rg   r   zLM Studio preload skipped: %s)rg   r   r   agent.model_metadatar8  hermes_cli.modelsr9  r  maxr   rh   update_modelr   r   r   r   )r   r5  r8  r9  
target_ctx
loaded_ctxccerrs           r   _ensure_lmstudio_runtime_loadedz'AIAgent._ensure_lmstudio_runtime_loaded  s~    MR&&((..00J>>F	?CCCCCCFFFFFF$,(/6NPT(U(U%27a9OPPJ55
DM74B+G+G J  
 T#7>>>OO"j'1!% 'i < <!%!% $       ">  	? 	? 	?LL8#>>>>>>>>>	?s   B+C& &
D0DDc                 .    ddl m}  || |||||          S )u?   Forwarder — see ``agent.agent_runtime_helpers.switch_model``.r   )switch_model)agent.agent_runtime_helpersrE  )r   	new_modelnew_providerr   rh   r   rE  s          r   rE  zAIAgent.switch_model  s.    <<<<<<|D)\7HhWWWrf   c                 d    	 | j         pt          } ||i | dS # t          t          f$ r Y dS w xY w)a3  Print that silently handles broken pipes / closed stdout.

        In headless environments (systemd, Docker, nohup) stdout may become
        unavailable mid-session.  A raw ``print()`` raises ``OSError`` which
        can crash cron jobs and lose completed work.

        Internally routes through ``self._print_fn`` (default: builtin
        ``print``) so callers such as the CLI can inject a renderer that
        handles ANSI escape sequences properly (e.g. prompt_toolkit's
        ``print_formatted_text(ANSI(...))``) without touching this method.
        N)	_print_fnprintr   
ValueError)r   r   kwargsfns       r   _safe_printzAIAgent._safe_print  sU    	(5BB$ 	 	 	DD	s    //forcerQ  c                    t          | dd          rdS |st          | dd          rdS |s|                                 r	| j        sdS  | j        |i | dS )ue  Verbose print — suppressed when actively streaming tokens.

        Pass ``force=True`` for error/warning messages that should always be
        shown even during streaming playback (TTS or display).

        During tool execution (``_executing_tools`` is True), printing is
        allowed even with stream consumers registered because no tokens
        are being streamed at that point.

        After the main response has been delivered and the remaining tool
        calls are post-response housekeeping (``_mute_post_response``),
        all non-forced output is suppressed.

        ``suppress_status_output`` is a stricter CLI automation mode used by
        parseable single-query flows such as ``hermes chat -q``. In that mode,
        all status/diagnostic prints routed through ``_vprint`` are suppressed
        so stdout stays machine-readable.
        suppress_status_outputFN_mute_post_response)r  _has_stream_consumers_executing_toolsrO  )r   rQ  r   rM  s       r   _vprintzAIAgent._vprint  s    & 41599 	F 	'<eDD 	F 	3355 	d>S 	F$)&)))))rf   c                     | j         dS t          t          dd          }|dS 	 t          |                                          S # t
          t          t          f$ r Y dS w xY w)a  Return True when quiet-mode spinner output has a safe sink.

        In headless/stdio-protocol environments, a raw spinner with no custom
        ``_print_fn`` falls back to ``sys.stdout`` and can corrupt protocol
        streams such as ACP JSON-RPC. Allow quiet spinners only when either:
        - output is explicitly rerouted via ``_print_fn``; or
        - stdout is a real TTY.
        NTstdoutF)rJ  r  sysr  isattyAttributeErrorrL  r   )r   streams     r   _should_start_quiet_spinnerz#AIAgent._should_start_quiet_spinner  sp     >%4h-->5	(((
G4 	 	 	55	s    A A! A!c                 J    | j         o| j         ot          | dd          dk    S )aZ  Return True when quiet-mode tool summaries should print directly.

        Quiet mode is used by both the interactive CLI and embedded/library
        callers. The CLI may still want compact progress hints when no callback
        owns rendering. Embedded/library callers, on the other hand, expect
        quiet mode to be truly silent.
        rx   rk   r   )r   r   r  r   s    r    _should_emit_quiet_tool_messagesz(AIAgent._should_emit_quiet_tool_messages  s5     O 7//7j"--6	
rf   r   c                    	 |                      | j         | d           n# t          $ r Y nw xY w| j        rF	 |                     d|           dS # t          $ r  t                              dd           Y dS w xY wdS )u  Emit a lifecycle status message to both CLI and gateway channels.

        CLI users see the message via ``_vprint(force=True)`` so it is always
        visible regardless of verbose/quiet mode.  Gateway consumers receive
        it through ``status_callback("lifecycle", ...)``.

        This helper never raises — exceptions are swallowed so it cannot
        interrupt the retry/fallback logic.
        TrP  	lifecyclez%status_callback error in _emit_statusr   NrW  r   r   r   r   r   r   r   s     r   _emit_statuszAIAgent._emit_status  s    	LLDO6W66dLCCCC 	 	 	D	 	UU$$['::::: U U UDtTTTTTTU	U 	U    # 
00A &A=<A=c                    	 |                      | j         | d           n# t          $ r Y nw xY w| j        rF	 |                     d|           dS # t          $ r  t                              dd           Y dS w xY wdS )a+  Emit a user-visible warning through the same status plumbing.

        Unlike debug logs, these warnings are meant for degraded side paths
        such as auxiliary compression or memory flushes where the main turn can
        continue but the user needs to know something important failed.
        TrP  warnz&status_callback error in _emit_warningr   Nrc  rd  s     r   _emit_warningzAIAgent._emit_warning  s    	LLDO6W66dLCCCC 	 	 	D	 	VV$$VW55555 V V VEPTUUUUUUV	V 	Vrf  c                     | j         rE	 |                      |           dS # t          $ r  t                              dd           Y dS w xY wdS )u@  Fire a structured ``AgentNotice`` to the active driver (TUI / CLI).

        Driver-agnostic: the bound ``notice_callback`` renders it however that
        driver does (TUI status-bar override, CLI console line). Swallows all
        callback errors — a notice must NEVER break the agent loop (D-D fail-open).
        z%notice_callback error in _emit_noticeTr   N)r   r   r   r   )r   notices     r   _emit_noticezAIAgent._emit_notice'  s{      	UU$$V,,,,, U U UDtTTTTTTU	U 	U     &A
	A
keyc                     | j         rE	 |                      |           dS # t          $ r  t                              dd           Y dS w xY wdS )zEClear a previously-fired sticky notice by ``key`` (e.g. on recovery).z1notice_clear_callback error in _emit_notice_clearTr   N)r   r   r   r   r   rn  s     r   _emit_notice_clearzAIAgent._emit_notice_clear4  sz    % 	aa**3///// a a aP[_``````a	a 	arm  c                     	 t          | dd          }|	g }|| _        |                    d|f           dS # t          $ r Y dS w xY w)a  Buffer a retry/fallback status message.

        Stored as a (kind, text) tuple where ``kind`` is one of:
        - ``"status"``  -> replays via ``_emit_status``
        - ``"vprint"``  -> replays via ``_vprint(force=True)``
        - ``"warn"``    -> replays via ``_emit_warning``
        Used to defer noisy retry chatter until we know whether the
        turn ultimately recovered or failed.
        _retry_status_bufferNstatusr  rs  appendr   r   r   bufs      r   _buffer_statuszAIAgent._buffer_statusG  sj    	$ 6==C{,/)JJ'*+++++ 	 	 	DD	   37 
AAc                     	 t          | dd          }|	g }|| _        |                    d|f           dS # t          $ r Y dS w xY w)z0Buffer a vprint(force=True) retry/fallback line.rs  Nvprintru  rw  s      r   _buffer_vprintzAIAgent._buffer_vprint[  sj    	$ 6==C{,/)JJ'*+++++ 	 	 	DD	rz  c                 |    	 t          | dd          }|r|                                 dS dS # t          $ r Y dS w xY w)u=   Drop buffered retry messages — call on successful recovery.rs  N)r  clearr   )r   rx  s     r   _clear_status_bufferzAIAgent._clear_status_bufferf  s^    	$ 6==C 		  	 	 	DD	s   '- 
;;c                 |   	 t          | dd          }|sdS t          |          }|                                 |D ]n\  }}	 |dk    r|                     |           n<|dk    r|                     |           n |                     | j         | d           _# t          $ r Y kw xY wdS # t          $ r Y dS w xY w)u   Emit buffered retry messages — call on terminal failure.

        Surfaces the full retry/fallback trace so the user can see what
        was tried before the turn gave up.
        rs  Nrt  rh  TrP  )r  listr  re  ri  rW  r   r   )r   rx  messageskindmsgs        r   _flush_status_bufferzAIAgent._flush_status_buffero  s
   	$ 6==C CyyHIIKKK% 	 		cx''))#....**3////%>%>%>dKKK    D	 	  	 	 	DD	s;   B- *B- ABB- 
B(%B- 'B((B- -
B;:B;r  c                 D   d}d}t          |t                    r|ng }|D ]u}t          |t                    r|                    d          dk    r1|                    dd          }t          |t                    r|r|dz  }|t          |          z  }vd| _        ||dS )	u&  Disable Responses encrypted reasoning replay and strip cached state.

        Called from the conversation_loop retry path when the provider
        rejects a replayed ``codex_reasoning_items`` blob with HTTP 400
        ``invalid_encrypted_content``.  Sets ``self._codex_reasoning_replay_enabled``
        to ``False`` (consumed by ``codex_responses_adapter._chat_messages_to_responses_input``
        and ``transports/codex.py`` to drop ``reasoning.encrypted_content``
        from subsequent requests) and pops ``codex_reasoning_items`` from
        every assistant message in ``messages`` so they cannot be replayed
        again later in the session.

        Returns a small stats dict ``{"messages": int, "items": int}``
        counting what was stripped — purely for diagnostic logging.
        r   role	assistantcodex_reasoning_itemsNrm   F)r  r  )
isinstancer  dictr   poprq   _codex_reasoning_replay_enabled)r   r  stripped_messagesstripped_itemstarget_messagesr  r  s          r   _disable_codex_reasoning_replayz'AIAgent._disable_codex_reasoning_replay  s    $ &04&@&@H((b" 	- 	-Cc4(( CGGFOO{,J,JGG3T::E%&& -5 -!Q&!#e**,/4,-GGGrf   r   )STREAM_DIAG_HEADERSc                  "    ddl m}   |             S )u9   Forwarder — see ``agent.stream_diag.stream_diag_init``.r   stream_diag_init)agent.stream_diagr  r  s    r   _stream_diag_initzAIAgent._stream_diag_init  s%     	766666!!!rf   diaghttp_responsec                 ,    ddl m}  || ||           dS )uE   Forwarder — see ``agent.stream_diag.stream_diag_capture_response``.r   )stream_diag_capture_responseN)r  r  )r   r  r  r  s       r   _stream_diag_capture_responsez%AIAgent._stream_diag_capture_response  s2     	CBBBBB$$T4?????rf   r   c                 $    ddl m}  ||           S )u@   Forwarder — see ``agent.stream_diag.flatten_exception_chain``.r   )flatten_exception_chain)r  r  )r   r  s     r   _flatten_exception_chainz AIAgent._flatten_exception_chain  s'     	>=====&&u---rf   c                    t          | dd          dk    rdS t          |t                    sdS t          |t          t          j        f          rdS t          |                                                                          }d|v S )a  Return True for malformed provider streaming data from SDK parsers.

        Some Anthropic-compatible streaming providers can send a malformed
        event-stream frame.  The Anthropic SDK surfaces that as a plain
        ``ValueError`` such as ``expected ident at line 1 column 149``.  That
        is provider wire-format trouble, not local request validation, so it
        should follow the same retry path as a truncated JSON body.
        r   Nanthropic_messagesFexpected ident at line)	r  r  rL  UnicodeEncodeErrorjsonJSONDecodeErrorro   r   r   )r   r   r   s      r   _is_provider_stream_parse_errorz'AIAgent._is_provider_stream_parse_error  s     4T**.BBB5%,, 	5e0$2FGHH 	5e**""$$**,,'722rf   )r  r  attemptmax_attemptsmid_tool_callc          	      6    ddl m}  || ||||||           dS )u9   Forwarder — see ``agent.stream_diag.log_stream_retry``.r   )log_stream_retry)r  r   r  r  r  r  N)r  r  )r   r  r   r  r  r  r  r  s           r   _log_stream_retryzAIAgent._log_stream_retry  sJ     	766666t5'%]	
 	
 	
 	
 	
 	
rf   c                4    ddl m}  || |||||           dS )u9   Forwarder — see ``agent.stream_diag.emit_stream_drop``.r   )emit_stream_drop)r   r  r  r  r  N)r  r  )r   r   r  r  r  r  r  s          r   _emit_stream_dropzAIAgent._emit_stream_drop  sH     	766666w\'d	
 	
 	
 	
 	
 	
rf   taskr   c                 L   	 |                      |          }n# t          $ r t          |          }Y nw xY w|p|j        j                                        }t          |          dk    r|dd                                         dz   }|                     d| d|            dS )z4Surface a compact warning for failed auxiliary work.   N   ...u   ⚠ Auxiliary z	 failed: )	_summarize_api_errorr   ro   r   r   r   rq   rstripri  )r   r  r   details       r   _emit_auxiliary_failurezAIAgent._emit_auxiliary_failure  s    	..s33FF 	 	 	XXFFF	2CM299;;v;;DSD\((**U2FCDCC6CCDDDDDs    44c           	          t          | dd          pdt          | dd          pdt          | dd          pdt          | dd          pdt          | dd          pddS )zBReturn the live main runtime for session-scoped auxiliary routing.r   rk   rg   rh   r   r   )r   rg   rh   r   r   )r  r   s    r   _current_main_runtimezAIAgent._current_main_runtime  sw     T7B//52j"55;j"55;tY339rj"55;
 
 	
rf   c                 (    ddl m}  ||            dS )uY   Forwarder — see ``agent.conversation_compression.check_compression_model_feasibility``.r   )#check_compression_model_feasibilityN)agent.conversation_compressionr  )r   r  s     r   $_check_compression_model_feasibilityz,AIAgent._check_compression_model_feasibility	  s+    VVVVVV++D11111rf   c                 (    ddl m}  ||            dS )uP   Forwarder — see ``agent.conversation_compression.replay_compression_warning``.r   )replay_compression_warningN)r  r  )r   r  s     r   _replay_compression_warningz#AIAgent._replay_compression_warning  s+    MMMMMM""4(((((rf   c                     |t          |          }n/t          | dd          pt          t          | dd                    }|dk    S )z8Return True when a base URL targets OpenAI's native API.Nr   rk   r   zapi.openai.comr\   r  r   rh   hostnames      r   _is_direct_openai_urlzAIAgent._is_direct_openai_url  s[    (22HHt%92>> BS/44C CH +++rf   c                 x    |"t          |                                          }nt          | dd          pd}d|v S )u  Return True when a base URL targets Azure OpenAI.

        Azure OpenAI exposes an OpenAI-compatible endpoint at
        ``{resource}.openai.azure.com/openai/v1`` that accepts the
        standard ``openai`` Python client.  Unlike api.openai.com it
        does NOT support the Responses API — gpt-5.x models are served
        on the regular ``/chat/completions`` path — so routing decisions
        must treat Azure separately from direct OpenAI.
        Nr   rk   zopenai.azure.com)ro   r   r  )r   rh   urls      r   _is_azure_openai_urlzAIAgent._is_azure_openai_url  sE     h--%%''CC$ 1266<"C!S((rf   c                     |t          |          }n/t          | dd          pt          t          | dd                    }|dk    S )zKReturn True when a base URL targets GitHub Copilot's OpenAI-compatible API.Nr   rk   r   api.githubcopilot.comr  r  s      r   _is_github_copilot_urlzAIAgent._is_github_copilot_url-  s[    (22HHt%92>> BS/44C CH 222rf   c                     t          | j        | j                  }||S t          t	          j        dd                    S )a  Resolve the effective per-call request timeout in seconds.

        Priority:
          1. ``providers.<id>.models.<model>.timeout_seconds`` (per-model override)
          2. ``providers.<id>.request_timeout_seconds`` (provider-wide)
          3. ``HERMES_API_TIMEOUT`` env var (legacy escape hatch)
          4. 1800.0s default

        Used by OpenAI-wire chat completions (streaming and non-streaming) so
        the per-provider config knob wins over the 1800s default.  Without this
        helper, the hardcoded ``HERMES_API_TIMEOUT`` fallback would always be
        passed as a per-call ``timeout=`` kwarg, overriding the client-level
        timeout the AIAgent.__init__ path configured.
        NHERMES_API_TIMEOUTg      @)r    rg   r   floatr   getenv)r   cfgs     r   _resolved_api_call_timeoutz"AIAgent._resolved_api_call_timeout7  s<     +4=$*EE?JRY3V<<===rf   c                     t          | j        | j                  }||dfS t          j        d          }|t          |          dfS dS )au  Resolve the base non-stream stale timeout and whether it is implicit.

        Priority:
          1. ``providers.<id>.models.<model>.stale_timeout_seconds``
          2. ``providers.<id>.stale_timeout_seconds``
          3. ``HERMES_API_CALL_STALE_TIMEOUT`` env var
          4. 90.0s default (time-to-first-byte for non-streaming / Codex
             internal-streaming requests; lowered from 300s in May 2026 so
             fallback providers kick in faster when upstream providers
             stall).  The detector still scales up for large contexts in
             ``_compute_non_stream_stale_timeout``.

        Returns ``(timeout_seconds, uses_implicit_default)`` so the caller can
        preserve legacy behaviors that only apply when the user has *not*
        explicitly configured a stale timeout, such as auto-disabling the
        detector for local endpoints.
        NFHERMES_API_CALL_STALE_TIMEOUT)g     V@T)r!   rg   r   r   r  r  )r   r  env_timeouts      r   %_resolved_api_call_stale_timeout_basez-AIAgent._resolved_api_call_stale_timeout_baseK  sT    $ )
CC?:i ?@@"%%u,,zrf   api_payloadc                 &   |                                  \  }}t          | dd          p| j        pd}|r |rt          |          rt	          d          S ddlm}  ||          }|dk    rt          |d          S |d	k    rt          |d
          S |S )a^  Compute the effective non-stream stale timeout for this request.

        Accepts either the full ``api_kwargs`` dict (Chat Completions or
        Responses API) or a legacy ``messages`` list.  Context-size scaling
        applies the same way to both shapes via
        :func:`agent.chat_completion_helpers.estimate_request_context_tokens`.
        r   Nrk   infr   )estimate_request_context_tokensi g      n@P  g     b@)r  r  rh   r/   r  agent.chat_completion_helpersr  r=  )r   r  
stale_baseuses_implicit_defaultrh   r  
est_tokenss          r   !_compute_non_stream_stale_timeoutz)AIAgent._compute_non_stream_stale_timeoutg  s     -1,V,V,X,X)
)4d33Jt}J  	 X 	 2CH2M2M 	 <<QQQQQQ44[AA
z5)))z5)))rf   c                 
   | j         dk    rdS | j        dk    p)t          | dd          dk    odt          | dd          pdv }|sdS ||n| j        pd}|                                }t          j        d	|          sdS d
|dS )uz  Return an actionable hint when this request matches a known
        Codex silent-reject configuration, else ``None``.

        The ChatGPT Codex backend (``chatgpt.com/backend-api/codex``) has
        historically silently dropped certain model requests: the connection
        is accepted but no stream events are emitted and no error is raised.
        The stale-call detector ends the hang, but a generic "timed out"
        message gives the user no path forward.

        This helper substitutes an actionable hint into the stale-timeout
        warning when the request matches a known silent-reject pattern.
        Currently flagged: ``gpt-5.5`` family on the Codex backend.  See
        hermes-agent #21444 for the symptom history.  The upstream backend
        behavior has historically come and gone with ChatGPT entitlement
        changes — the heuristic stays in place as future-proofing even when
        the symptom is dormant.

        Does NOT fix the backend issue.  Only converts an opaque stale-timeout
        into actionable text so users learn the workaround in seconds rather
        than digging through logs.
        codex_responsesNopenai-codexr   rk   chatgpt.comz/backend-api/codexr   z(?:^|[/\-_])gpt-5\.5(?:$|[\-_])z/Codex backend appears to be silently rejecting a   on chatgpt.com/backend-api/codex (no stream events, no error). This is a known backend-side pattern that has affected ChatGPT Plus accounts intermittently. Workaround: try `gpt-5.4` on the same OAuth profile, or `gpt-5.3-codex`, or switch to a different model/provider in your fallback chain. Some ChatGPT Codex accounts do not support `gpt-5.4-codex`. See hermes-agent#21444 for symptom history.)r   rg   r  r   r   research)r   r   is_codex_backend	eff_modelmodel_lowers        r   _codex_silent_hang_hintzAIAgent._codex_silent_hang_hint|  s    , =---4M^+ 2B77=H Y(WT;Lb-Q-Q-WUWX	 	   	4#/UUTZFB	oo''
 y;[II 	4:i : : :		
rf   c                 ,    t          | j        d          S )z1Return True when the base URL targets OpenRouter.openrouter.air[   r   r   s    r   _is_openrouter_urlzAIAgent._is_openrouter_url  s    $T%9?KKKrf   rg   rh   r   r   c                .    ddl m}  || ||||          S )uP   Forwarder — see ``agent.agent_runtime_helpers.anthropic_prompt_cache_policy``.r   )anthropic_prompt_cache_policyr  )rF  r  )r   rg   rh   r   r   r  s         r   _anthropic_prompt_cache_policyz&AIAgent._anthropic_prompt_cache_policy  s5     	NMMMMM,,THxbjrwxxxxrf   c                     |                                  }d|v r|                    dd          d         }|                    d          S )a>  Return True for models that require the Responses API path.

        GPT-5.x models are rejected on /v1/chat/completions by both
        OpenAI and OpenRouter (error: ``unsupported_api_for_model``).
        Detect these so the correct api_mode is set regardless of
        which provider is serving the model.
        /rm   zgpt-5)r   rsplitrp   )r   ms     r   _model_requires_responses_apiz%AIAgent._model_requires_responses_api  sD     KKMM!88a  $A||G$$$rf   rg   c                    |pd                                                                 }|dk    rdS |dk    r"	 ddlm}  ||           S # t          $ r Y nw xY wt
                              |           S )zCReturn True when this provider/model pair should use Responses API.rk   nousFcopilotr   )!_should_use_copilot_responses_api)r   r   r<  r  r   r   r  )r   rg   normalized_providerr  s       r   &_provider_model_requires_responses_apiz.AIAgent._provider_model_requires_responses_api  s      (~24466<<>> &((5)++OOOOOO88???     44U;;;s   A	 	
AAc                     |                                  s<|                                 s(|                                 st          | j                  rd|iS d|iS )u  Return the correct max tokens kwarg for the current provider.

        OpenAI's newer models (gpt-4o, gpt-4.1, gpt-5+, o-series) require
        'max_completion_tokens'. Azure OpenAI and GitHub Copilot also require
        'max_completion_tokens' for those families served via their
        OpenAI-compatible endpoints. OpenRouter, local models, and older
        OpenAI models use 'max_tokens'.

        The check is URL-first (api.openai.com / Azure / Copilot all use the
        new kwarg), then falls back to a model-name check so third-party
        OpenAI-compatible endpoints fronting those models are recognised —
        URL-only detection misses that case and silently sends the wrong
        kwarg, which the upstream model rejects with a 400.
        max_completion_tokensr   )r  r  r  r^   r   r   s     r   _max_tokens_paramzAIAgent._max_tokens_param  sn      &&((	4((**	4 **,,	4 2$*==		4 ,U33e$$rf   
api_kwargsc                     t          | t                    sdS dD ]I}|                     |          }	 t          |          }n# t          t
          f$ r Y ;w xY w|dk    r|c S JdS )z@Extract the outgoing response token cap from a prepared request.N)max_output_tokensr  r   r   )r  r  r   r   	TypeErrorrL  )r  rn  rawr   s       r   %_requested_output_cap_from_api_kwargsz-AIAgent._requested_output_cap_from_api_kwargs  s     *d++ 	4O 	 	C..%%CCz*   qyy ts   AAAcontentc                 v    |sdS |                      |          }t          |                                          S )a  
        Check if content has actual text after any reasoning/thinking blocks.

        This detects cases where the model only outputs reasoning but no actual
        response, which indicates an incomplete generation that should be retried.
        Must stay in sync with _strip_think_blocks() tag variants.

        Args:
            content: The assistant message content to check

        Returns:
            True if there's meaningful content after think blocks, False otherwise
        F)_strip_think_blocksr  r   )r   r
  cleaneds      r   _has_content_after_think_blockz&AIAgent._has_content_after_think_block  s>      	5 **733 GMMOO$$$rf   c                 &    ddl m}  || |          S )uE   Forwarder — see ``agent.agent_runtime_helpers.strip_think_blocks``.r   )strip_think_blocks)rF  r  )r   r
  r  s      r   r  zAIAgent._strip_think_blocks#  s'    BBBBBB!!$000rf   c                     | sdS |                                  }|sdS |                    d          rdS |                    d          rdS |d         }|dv rdS t          |          dk    rdS dS )zCHeuristic: does visible assistant text look intentionally finished?Fz```T^r  u%   .!?:)"']}。！？：）】」』》^i  )r  endswithord)r
  strippedlasts      r   _has_natural_response_endingz$AIAgent._has_natural_response_ending(  s      	5>>## 	5U## 	4S!! 	4|;;;4t994urf   c                     | j         pd                                }| j        pd                                }d|vr|dk    rdS d| j        v s	d| j        v rdS t	          | j        ot          | j                            S )zHDetect the narrow backend family affected by Ollama/GLM stop misreports.rk   glmzaiFollamaz:11434T)r   r   rg   r   r  rh   r/   )r   r  provider_lowers      r   _is_ollama_glm_backendzAIAgent._is_ollama_glm_backend<  s    z'R..00--24466##%(?(?5t+++x4;O/O/O4DMF&7&F&FGGGrf   finish_reasonc                    |dk    s| j         dk    rdS |                                 sdS t          d |pg D                       sdS |t          |dd          rdS t          |dd          }t	          |t
                    sdS |                     |                                          }|sdS t          |          dk     st          j
        d	|          sdS |                     |           S )
zIDetect conservative stop->length misreports for Ollama-hosted GLM models.stopchat_completionsFc              3   r   K   | ]2}t          |t                    o|                    d           dk    V  3dS )r  toolN)r  r  r   )r  r  s     r   	<genexpr>z:AIAgent._should_treat_stop_as_truncated.<locals>.<genexpr>Q  sT       
 
 sD!!?cggfoo&?
 
 
 
 
 
rf   N
tool_callsr
  r   z\s)r   r  anyr  r  ro   r  r   rq   r  r  r  )r   r  assistant_messager  r
  visible_texts         r   _should_treat_stop_as_truncatedz'AIAgent._should_treat_stop_as_truncatedF  s&    F""dm7I&I&I5**,, 	5 
 
 B
 
 
 
 
 	 5$0A<QU(V(V$5+Y=='3'' 	5//88>>@@ 	5|r!!5,)G)G!544\BBBBrf   user_messageassistant_contentc                 *    ddl m}  || |||          S )uT   Forwarder — see ``agent.agent_runtime_helpers.looks_like_codex_intermediate_ack``.r   )!looks_like_codex_intermediate_ack)rF  r-  )r   r*  r+  r  r-  s        r   "_looks_like_codex_intermediate_ackz*AIAgent._looks_like_codex_intermediate_acke  s0     	RQQQQQ00|EVX`aaarf   c                 &    ddl m}  || |          S )uD   Forwarder — see ``agent.agent_runtime_helpers.extract_reasoning``.r   )extract_reasoning)rF  r0  )r   r'  r0  s      r   _extract_reasoningzAIAgent._extract_reasoningo  s(    AAAAAA  '8999rf   task_idc                 &    ddl m}  || |          S )uK   Forwarder — see ``agent.chat_completion_helpers.cleanup_task_resources``.r   )cleanup_task_resources)r  r4  )r   r2  r4  s      r   _cleanup_task_resourceszAIAgent._cleanup_task_resourcest  s'    HHHHHH%%dG444rf   )_MEMORY_REVIEW_PROMPT_SKILL_REVIEW_PROMPT_COMBINED_REVIEW_PROMPTreview_messagesprior_snapshotc                 &    ddl m}  || |          S )uR   Forwarder — see ``agent.background_review.summarize_background_review_actions``.r   )#summarize_background_review_actions)agent.background_reviewr<  )r9  r:  r<  s      r   $_summarize_background_review_actionsz,AIAgent._summarize_background_review_actions  s*     	POOOOO22?NSSSrf   messages_snapshotreview_memoryreview_skillsc                     ddl m}  || |||          \  }}t          j        |dd          }|                                 dS )u^  Spawn the background memory/skill review thread.

        Thin wrapper — the heavy lifting lives in
        ``agent.background_review.spawn_background_review_thread`` which
        returns the thread target.  ``threading.Thread`` is constructed
        here so existing tests that patch ``run_agent.threading.Thread``
        keep working.
        r   )spawn_background_review_thread)r@  rA  Tz	bg-review)targetdaemonnameN)r=  rC  	threadingThreadstart)r   r?  r@  rA  rC  rD  _promptts           r   _spawn_background_reviewz AIAgent._spawn_background_review  sh     	KJJJJJ88''	
 
 
 F4kJJJ						rf   write_originexecution_contextr2  tool_call_idrN  rO  rP  c                .    ddl m}  || ||||          S )uJ   Forwarder — see ``agent.background_review.build_memory_write_metadata``.r   )build_memory_write_metadatarM  )r=  rR  )r   rN  rO  r2  rP  rR  s         r   _build_memory_write_metadataz$AIAgent._build_memory_write_metadata  s@     	HGGGGG**%/%
 
 
 	
rf   c                    t          | dd          }t          | dd          }||dS d|cxk    rt          |          k     rAn dS ||         }t          |t                    r"|                    d          dk    r||d<   dS dS dS dS )a  Rewrite the current-turn user message before persistence/return.

        Some call paths need an API-only user-message variant without letting
        that synthetic text leak into persisted transcripts or resumed session
        history. When an override is configured for the active turn, mutate the
        in-memory messages list in place so both persistence and returned
        history stay clean.
        _persist_user_message_idxN_persist_user_message_overrider   r  userr
  )r  rq   r  r  r   )r   r  idxoverrider  s        r   $_apply_persist_user_message_overridez,AIAgent._apply_persist_user_message_override  s     d7>>4!A4HHs{F####c(mm######3-C#t$$ *F)B)B!)I $#* *)B)Brf   conversation_historyc                     |                      |           |                     |           || _        |                     |           |                     ||           dS )zSave session state to both JSON log and SQLite on any exit path.

        Ensures conversations are never lost, even on errors or early returns.
        N))_drop_trailing_empty_response_scaffoldingrZ  _session_messages_save_session_log_flush_messages_to_session_db)r   r  r[  s      r   _persist_sessionzAIAgent._persist_session  se    
 	66x@@@11(;;;!)x(((**85IJJJJJrf   c                    d}|rt          |d         t                    r|d                             d          s|d                             d          ri|                                 d}|rQt          |d         t                    r6|d                             d          N|d                             d          i|sdS |rt          |d         t                    ro|d                             d          dk    rP|                                 |r:t          |d         t                    r|d                             d          dk    P|rkt          |d         t                    rR|d                             d          d	k    r5|d                             d
          r|                                 dS dS dS dS dS )u  Remove private empty-response retry/failure scaffolding from transcript tails.

        Also rewinds past any trailing tool-result / assistant(tool_calls) pair
        that the failed iteration left hanging. Without this, the tail ends at
        a raw ``tool`` message and the next user turn lands as
        ``...tool, user, user`` — a protocol-invalid sequence that most
        providers silently reject (returns empty content), causing the
        empty-retry loop to fire forever. See #<TBD>.
        Fr  _empty_recovery_synthetic_empty_terminal_sentinelTNr  r#  r  r%  )r  r  r   r  )r   r  dropped_scaffoldings      r   r]  z1AIAgent._drop_trailing_empty_response_scaffolding  s    $		'8B<..		'   !<==			'
 B<##$>??		' LLNNN"& 		'8B<..		'   !<==			'
 B<##$>??		'" # 	F 	8B<..	   ((F22LLNNN	 	8B<..	   ((F22 	8B<..	   ((K77  .. 8 LLNNNNN	 	 	 	 8777rf   c                 &    ddl m}  || |          S )uJ   Forwarder — see ``agent.agent_runtime_helpers.repair_message_sequence``.r   )repair_message_sequence)rF  rg  )r   r  rg  s      r   _repair_message_sequencez AIAgent._repair_message_sequence  s'    GGGGGG&&tX666rf   c                    | j         sdS |                     |           	 | j        s|                                  |rt	          |          nd}t          || j                  }||d         D ]}|                    dd          }|                    d          }t          |          rt          |          }nt          |t                    rg }|D ]}	t          |	t                    rP|	                    d          dk    r7|                    t          |	                    dd                               gt          |	t                    r,|	                    d          d	v r|                    d
           |rd                    |          nd}d}
t!          |d          r3t          |j        t                    r|j        rd |j        D             }
n0t          |                    d          t                    r|d         }
| j                             | j        |||                    d          |
|                    d          |                    d          |dk    r|                    d          nd|dk    r|                    d          nd|dk    r|                    d          nd|dk    r|                    d          nd|dk    r|                    d          nd           t	          |          | _        dS # t(          $ r&}t*                              d|           Y d}~dS d}~ww xY w)u%  Persist any un-flushed messages to the SQLite session store.

        Uses _last_flushed_db_idx to track which messages have already been
        written, so repeated calls (from multiple exit paths) only write
        truly new messages — preventing the duplicate-write bug (#860).
        Nr   r  r%  r
  r   textrk   >   image	image_urlinput_imagez[screenshot]
r%  c                 @    g | ]}|j         j        |j         j        d S ))rF  	arguments)functionrF  rp  r  tcs     r   
<listcomp>z9AIAgent._flush_messages_to_session_db.<locals>.<listcomp>/  s:     ' ' ' "$!1@UVV' ' 'rf   	tool_namerP  r  r  	reasoningreasoning_contentreasoning_detailsr  codex_message_items)r   r  r
  ru  r%  rP  r  rv  rw  rx  r  ry  z$Session DB append_message failed: %s)r   rZ  r   r  rq   r=  _last_flushed_db_idxr   rT   rU   r  r  r  rv  ro   joinr  r%  append_messager   r   r   r   )r   r  r[  	start_idx
flush_fromr  r  r
  _txtptool_calls_datar  s               r   r`  z%AIAgent._flush_messages_to_session_db  s~     	F11(;;;/	F+ *'')))5IP0111qIY(ABBJ
, & &wwvy11''),, .g66 
@6w??GG.. @D$ 8 8%a.. 8155==F3J3J KKAEE&",=,=(>(>????'400 8QUU6]]Fk5k5k KK77715?diiooo4G"&3-- 8*S^T2R2R 8WZWe 8' '"%.' ' 'OO   5 5t<< 8&),&7O //##!ggk22.!$!8!8"%''/":":6:k6I6Icggk222tFJkFYFYcgg.A&B&B&B_cFJkFYFYcgg.A&B&B&B_cNRVaNaNa#''2I*J*J*JgkJNR]J]J]0E(F(F(Fcg 0     ),HD%%% 	F 	F 	FNNA1EEEEEEEEE	Fs   K+L 
L=L88L=c                     |sg S d}t          t          |          dz
  dd          D ]%}||                             d          dk    r|} n&||                                S |d|         S )a  
        Get messages up to (but not including) the last assistant turn.
        
        This is used when we need to "roll back" to the last successful point
        in the conversation, typically when the final assistant message is
        incomplete or malformed.
        
        Args:
            messages: Full message list
            
        Returns:
            Messages up to the last complete assistant turn (ending with user/tool message)
        Nrm   r  r  r  )rangerq   r   copy)r   r  last_assistant_idxis       r   "_get_messages_up_to_last_assistantz*AIAgent._get_messages_up_to_last_assistantG  s      	I "s8}}q("b11 	 	A{v&&+55%&" 6 %==??" +++,,rf   c                 $    ddl m}  ||           S )uJ   Forwarder — see ``agent.system_prompt.format_tools_for_system_message``.r   )format_tools_for_system_message)agent.system_promptr  )r   r  s     r    _format_tools_for_system_messagez(AIAgent._format_tools_for_system_messagef  s%    GGGGGG..t444rf   
user_query	completedc                 *    ddl m}  || |||          S )uO   Forwarder — see ``agent.agent_runtime_helpers.convert_to_trajectory_format``.r   )convert_to_trajectory_format)rF  r  )r   r  r  r  r  s        r   _convert_to_trajectory_formatz%AIAgent._convert_to_trajectory_formatk  s+    LLLLLL++D(J	RRRrf   c                 r    | j         sdS |                     |||          }t          || j        |           dS )a  
        Save conversation trajectory to JSONL file.
        
        Args:
            messages (List[Dict]): Complete message history
            user_query (str): Original user query
            completed (bool): Whether the conversation completed successfully
        N)r   r  _save_trajectory_to_filer   )r   r  r  r  
trajectorys        r   _save_trajectoryzAIAgent._save_trajectoryp  sE     % 	F77*iXX
 TZCCCCCrf   error_contextr   c                    |dvrdS t          | t                    sdS t          |                     d          pd                                          }t          |                     d          pd                                          }t          |                     d          pd                                          }t          |                     d          pd                                          }| d| d| d| }|                                sdS d	|v rdS d
|v rdS d|v rdS d|v rd|v rdS d|v rd|v rdS dS )uM  Detect subscription/entitlement 403s that masquerade as auth failures.

        Returned True only when the body text matches a known entitlement
        shape AND the status is 401/403.  Refreshing an OAuth token cannot
        fix an unsubscribed account, so callers should surface the error
        instead of looping the credential pool.

        Current matches:
          * xAI OAuth: "do not have an active Grok subscription" /
            "out of available resources" / "does not have permission" + "grok"

        Disambiguator for xAI (#29344): the same ``code`` text ("The caller
        does not have permission to execute the specified operation") is
        returned for BOTH an unsubscribed account AND a stale OAuth access
        token.  xAI ships an explicit signal in the ``error`` field that
        tells the two apart: a ``[WKE=unauthenticated:...]`` suffix (and/or
        the ``OAuth2 access token could not be validated`` phrasing) means
        the credentials failed validation — that's recoverable by refreshing
        the token, NOT by surfacing an entitlement message.  When either
        signal is present we return False eagerly so the credential-pool
        refresh path runs, letting long-running TUI sessions recover from
        stale tokens without an exit/reopen cycle.

        Extend here for new providers as we discover them (Anthropic's
        Claude Max OAuth entitlement errors look distinct enough today that
        the existing 1M-context-beta branch handles them; revisit if other
        subscription tiers start producing the same loop signature).
        >   N    Fr   rk   reasonr   r    z[wke=unauthenticated:z*oauth2 access token could not be validated'do not have an active grok subscriptionTout of available resourcesgrokdoes not have permission)r  r  ro   r   r   r   )r  r   r   r  r   rB  haystacks          r   _is_entitlement_failurezAIAgent._is_entitlement_failure  s   B ...5-.. 	5 m''	228b99??AA]&&x006B77==??=$$V,,23399;;-##G,,23399;;55555555~~ 	5 #h..578CC54@@4'833(8J8J4%11f6H6H4urf   r  c                 ~    | s| S |                                  }d|v pd|v od|v pd|v od|v }|s| S d}d| v r| S |  | S )u  Append a neutral hint when xAI's OAuth surface returns the
        permission-denied 403.

        xAI's ``/v1/responses`` endpoint replies to several distinct failure
        modes with the SAME body::

            {"code": "The caller does not have permission to execute the
             specified operation", "error": "You have either run out of
             available resources or do not have an active Grok subscription.
             Manage subscriptions at https://grok.com/?_s=usage or subscribe
             at https://grok.com/supergrok"}

        That body covers several real causes we cannot distinguish without
        more info from xAI.  The most common (and least obvious) one is
        that **X Premium+ does NOT include API access** — only standalone
        SuperGrok subscribers can use Hermes against xai-oauth.  Lots of
        users see Grok in their X app, assume it works here too, and hit
        this 403 with no idea why.  Lead the hint with that.

        Other possible causes:
          * No Grok subscription at all
          * SuperGrok tier doesn't include the requested model (e.g.
            grok-4.3 may need a higher tier)
          * Monthly quota exhausted (the ``?_s=usage`` URL hints at this)

        Surface the raw xAI text verbatim and point at
        https://grok.com/?_s=usage where the user can see WHICH applies.

        Matched once per detail string — won't double-decorate if the
        upstream already concatenated the same text.
        r  r  r  r  uZ   — xAI rejected this OAuth account. NOTE: X Premium+ does NOT include xAI API access — only standalone SuperGrok subscribers can use this provider. Other possible causes: no Grok subscription, your tier doesn't include this model, or your quota is exhausted. Check https://grok.com/?_s=usage to see which, or run `/model` to switch providers.zX Premium+ does NOT include)r   )r  r   is_entitlementhints       r   _decorate_xai_entitlement_errorz'AIAgent._decorate_xai_entitlement_error  s    B  	M5> I,5I&E/I*e3G% 	
  	M: 	 )F22M $   rf   c                    t          |           }t          | t                    r#d|                                v rd|dd          S d|v sd|v rt	          j        d|t          j                  }|r'|                    d                                          nd	}t	          j        d
|          }|r'|                    d                                          nd}t          | dd          }g }|r|
                    d|            |
                    |           |r|
                    d|            d                    |          S t          | dd          }t          |t                    rt          |                    d          t                    r)|                    di                               d          n|                    d          }	|	rAt          | dd          }|rd| dnd}
t                              |
 |	dd                    S t          | dd          }|rd| dnd}
t                              |
 |dd                    S )zExtract a human-readable one-liner from an API error.

        Handles Cloudflare HTML error pages (502, 503, etc.) by pulling the
        <title> tag instead of dumping raw HTML.  Falls back to a truncated
        str(error) for everything else.
        r  z'Malformed provider streaming response: Ni,  z	<!DOCTYPE<htmlz<title[^>]*>([^<]+)</title>rm   z!HTML error page (title not found)z2Cloudflare Ray ID:\s*<strong[^>]*>([^<]+)</strong>r   zHTTP zRay u    — r   r   r   : rk   r   )ro   r  rL  r   r  r  
IGNORECASEgroupr   r  rv  r{  r  r   r   r  )r   r  r  titlerayray_idr   partsr   r  prefixs              r   r  zAIAgent._summarize_api_error  s|    %jj uj))	I(CIIKK77HS#YHHH #C	8#r}MMA*+TAGGAJJ$$&&&1TE)QSVWWC-0:SYYq\\'')))dF!%==KE 42[22333LL ._F__---<<&&& ufd++dD!! 	W:DTXXgEVEVX\:]:]v$((7B''++I666cgckcklucvcvC W%e]DAA4?G00000R>>&?U#dsd)?U?UVVV e]D99,7?(((((R66&7M#dsd)7M7MNNNrf   c                     t          |          rt          |t                    sdS |sd S t          |          dk    rdS |d d          d|dd           S )Nz<entra-id-bearer>   z***r_   r  )callabler  ro   rq   rp  s     r   _mask_api_key_for_logszAIAgent._mask_api_key_for_logs*  sn     C== 	'C!5!5 	'&& 	4s88r>>5bqb'((c"##h(((rf   	error_msgc                     |sdS |                                                     d          sd|v rdS d                    |                                          }t	          |          dk    r|dd         dz   }|S )	a  
        Clean up error messages for user display, removing HTML content and truncating.
        
        Args:
            error_msg: Raw error message from API or exception
            
        Returns:
            Clean, user-friendly error message
        zUnknown errorz<!DOCTYPE htmlr  z:Service temporarily unavailable (HTML error page returned)r     Nr  )r   rp   r{  splitrq   )r   r  r  s      r   _clean_error_messagezAIAgent._clean_error_message5  s      	#"? ??''(899 	PW	=Q=QOO ((9??,,-- w<<#dsdme+Grf   c                 $    ddl m}  ||           S )uL   Forwarder — see ``agent.agent_runtime_helpers.extract_api_error_context``.r   )extract_api_error_context)rF  r  )r   r  s     r   _extract_api_error_contextz"AIAgent._extract_api_error_contextO  s(     	JIIIII((///rf   responsec                     |dS t          |dd          }|sdS ddlm} t          || j        | j                  } ||          }|                    dd           |j        |d<   |j        |d<   |S )	zLToken buckets for ``post_api_request`` plugins (no raw ``response`` object).Nusager   )asdict)rg   r   	raw_usageprompt_tokenstotal_tokens)	r  dataclassesr  r0   rg   r   r  r  r  )r   r  r  r  cusummarys         r   #_usage_summary_for_api_request_hookz+AIAgent._usage_summary_for_api_request_hookU  s    4Hgt44	 	4&&&&&&YWWW&**K&&&#%#3 "$/rf   c                      t          j        dd          } 	 t          dt          |                     S # t          t
          f$ r Y dS w xY w)NHERMES_PLUGIN_PAYLOAD_MAX_CHARS50000  r  )r   r  r=  r   r  rL  )r  s    r   _hook_payload_max_charszAIAgent._hook_payload_max_charse  sV    i97CC	tSXX&&&:& 	 	 	55	s   4 A	A	c                     t          | t                    sdS |                                                     dd          }h d}||v p|                    d          S )NF-_>   cookier   
set_cookieauthorizationproxy_authorization_api_key)r  ro   r   replacer  )rn  loweredexacts      r   _is_sensitive_hook_keyzAIAgent._is_sensitive_hook_keym  sg    #s## 	5))++%%c3//
 
 
 %?7#3#3J#?#??rf   r_   i@     depth	max_depth
max_stringmax_sequencer  r  r  r  c                    k    rdt          |          j         dS |"t          |t          t          t
          f          r|S t          |t                    r6t          |          k    r!|d          dt          |          z
   dz   S |S t          |t          t          f          rdt          |           dS t          |t                    ri }t          |                                          D ]o\  }\  }}	|k    rt          |          z
  |d<    nKt          |          }
                     |
          rd||
<   O                     |	dz   	          ||
<   p|S t          |t          t           t"          f          ret          |          } fd
|d          D             }t          |          k    r'|                    dt          |          z
  i           |S 	 t'          |d          rY	 |                    d          }n$# t*          $ r |                                }Y nw xY w                     |dz   	          S n# t,          $ r Y nw xY w	 ddlm}m}  ||          r&                      ||          dz   	          S n# t,          $ r Y nw xY wt          |t4                    r*                     t7          |          dz   	          S t'          |d          rY	 d t7          |                                          D             }                     |dz   	          S # t,          $ r Y nw xY wt          |          d          S )N<z depth limit>z...[truncated z chars]z bytes>_truncated_itemsz
<redacted>rm   r  c           	      J    g | ]}                     |d z              S )rm   r  )_hook_jsonable)r  itemclsr  r  r  r  s     r   rt  z*AIAgent._hook_jsonable.<locals>.<listcomp>  sS     	 	 	  ""!)')!- #  	 	 	rf   
model_dumpr  )moder   )r  is_dataclass__dict__c                 ^    i | ]*\  }}t          |                              d           '||+S )r  ro   rp   r  s      r   r  z*AIAgent._hook_jsonable.<locals>.<dictcomp>  sH          1q66,,S11 q     rf   )r   r   r  r  r   r  ro   rq   bytes	bytearrayr  	enumerater  r  r  r  tuplesetrv  r  r  r  r   r  r  r  r	   vars)r  r   r  r  r  r  outrX  rn  r  str_keyseqdumpedr  r  public_attrss   ` ````          r   r  zAIAgent._hook_jsonable{  s    9:tE{{+::::=JutS%.@AA=LeS!! 	5zzJ&&[j[),]SZZ*=T,],],]]]LeeY/00 	+*s5zz****eT"" 	"$C$-ekkmm$<$<   [c4,&&.1%jj<.GC*+Ec((--g66 	#/CLL#&#5#5#ai"+#-%1 $6 $ $CLL JedE3/00 	u++C	 	 	 	 	 	 	 	  .	 	 	C 3xx,&&

.C<0GHIIIJ	ul++ 0"--6-::FF  0 0 0"--//FFF0))!)')!- *     	 	 	D		88888888|E"" ))F5MM!)')!- *     	 	 	D	e_-- 	%%Uai#%) &    5*%% 	    $U 1 1 3 3     
 )) !)')!- *       5zz+:+&&s[   6I  H I  H?<I  >H??I   
I-,I-18J+ +
J87J8AM 
M M c                 ,   |                      |          }|                                 }	 t          j        |dt                    }n'# t
          $ r t	          |          d |         cY S w xY wt          |          |k    r|S |                      |dd          }	 t          j        |dt                    }n'# t
          $ r t	          |          d |         cY S w xY wt          |          |k    r|S dt          |          j        |d |         dS )NF)ensure_asciidefaultr  2   )r  r  T)
_truncatedoriginal_typepreview)	r  r  r  dumpsro   r   rq   r   r   )r  r   payloadlimitencodeds        r   _sanitize_hook_payloadzAIAgent._sanitize_hook_payload  s;   $$U++++--	(jucJJJGG 	( 	( 	(w<<''''	(w<<5  N$$Ut"$MM	(jucJJJGG 	( 	( 	(w<<''''	(w<<5  N!%[[1vv
 
 	
s#   A !A,+A,B: :!CCc                 r    d |pi                                  D             }|                     d|d          S )Nc                 "    i | ]\  }}|d v	||S )>   timeouthttp_clientr  )r  rn  r   s      r   r  z9AIAgent._api_request_payload_for_hook.<locals>.<dictcomp>  s4     
 
 
U444 444rf   POST)methodr   )r  r  )r   r  r   s      r   _api_request_payload_for_hookz%AIAgent._api_request_payload_for_hook  s[    
 
)/R6688
 
 

 **  
 
 	
rf   r'  c          
          t          |dd           pg }|                     t          |dd           |t          |dd          t          |dd           |d|                     |          d          S )Nr%  r   r  r  r
  )r  r
  r%  )r   r  r'  r  )r  r  r  )r   r  r'  r  r%  s        r   _api_response_payload_for_hookz&AIAgent._api_response_payload_for_hook	  s     .dCCIr
** 7D99!.#$5v{KK&'8)TJJ",& &
 AA(KK	 	
 
 	
rf   )r   retry_countmax_retries	retryabler  turn_idapi_request_idapi_call_countapi_start_time
error_typeerror_messager  r  r  r  c                T   	 ddl m} |                    d          sd S t          j                    }|                    d|||| j        pd| j        pd| j        | j        | j	        | j
        |||z
  |||	|
|||||d|                     |                     d S # t          $ r Y d S w xY w)Nr   )pluginsapi_request_errorrk   )r   r   )r2  r  r  r   rx   r   rg   rh   r   r  api_duration
started_atended_atr   r  r  r  r  r   request)rc   r  has_hooktimeinvoke_hookr   rx   r   rg   rh   r   r  r   )r   r2  r  r  r  r  r  r  r  r   r  r  r  r  _pluginsr  s                   r   _invoke_api_request_error_hookz&AIAgent._invoke_api_request_error_hook%  s   *!	666666$$%899 y{{H  #-?0b,"j-%6)!'''#&,  :::FF1 !     4  	 	 	DD	s   B A8B 
B'&B')r   c                ,    ddl m}  || |||          S )uI   Forwarder — see ``agent.agent_runtime_helpers.dump_api_request_debug``.r   )dump_api_request_debug)r  r   )rF  r!  )r   r  r  r   r!  s        r   _dump_api_request_debugzAIAgent._dump_api_request_debug]  s1     	GFFFFF%%dJvUSSSSrf   c                     | s| S t          |           } t          j        dd|           } t          j        dd|           } |                                 S )zCConvert REASONING_SCRATCHPAD to think tags and clean up whitespace.z\n+(<think>)z\n\1z(</think>)\n+z\1\n)rN   r  subr   )r
  s    r   _clean_session_contentzAIAgent._clean_session_contenth  sS      	N-g66&'7;;&)7G<<}}rf   c                    | | S t          | t                    rt          |           S t          | t                    rg }| D ]}t          |t                    rt	          |          }t          |                    d          t                    rt          |d                   |d<   t          |                    d          t                    rt          |d                   |d<   |                    |           |S | S )uD  Apply secret redaction to message content (str or list-of-parts).

        Handles both plain-string content and the OpenAI/Anthropic multimodal
        shape where ``content`` is a list of ``{"type": "text", "text": ...}``
        / ``{"type": "image_url", ...}`` / ``{"type": "input_text", "content": ...}``
        parts. Image / binary parts are left untouched; only text fields are
        passed through ``redact_sensitive_text``.

        Respects ``HERMES_REDACT_SECRETS`` via ``redact_sensitive_text`` —
        when disabled the helper is effectively a no-op.
        Nrj  r
  )r  ro   r-   r  r  r   rv  )r
  redactedparts      r   _redact_message_contentzAIAgent._redact_message_contentr  s     ?Ngs## 	2(111gt$$ 
	H & &dD)) Q::D!$((6"2"2C88 K'<T&\'J'JV!$((9"5"5s;; Q*?Y*P*PY%%%%Orf   c                 &   t          | dd          sdS |p| j        }|sdS 	 | j        d| j         dz  }n# t          $ r Y dS w xY w	 g }|D ]}|                    d          dk    rB|                    d          r-t          |          }|                     |d                   |d<   d|v r:t          |          }|                     |                    d                    |d<   |	                    |           |
                                r	 t          j        |                    d	
                    }|                    dt          |                    dg                               }|t          |          k    r%t          j        d|t          |                     dS n# t          $ r Y nw xY w| j        | j        | j        | j        | j                                        t-          j                                                    t1          | j        pd          | j        pg t          |          |d
}t7          ||dt8                     dS # t          $ r/}| j        rt          j        d|            Y d}~dS Y d}~dS d}~ww xY w)a  Optional per-session JSON snapshot writer.

        Gated by ``sessions.write_json_snapshots`` (default False).  state.db
        is the canonical message store; this writer exists only for users
        whose external tooling consumes ``~/.hermes/sessions/session_{sid}.json``
        directly.  When the flag is off this is a fast no-op.

        When enabled, rewrites the snapshot after every persistence point with
        the full message list (assistant content normalized via
        ``_clean_session_content`` to convert REASONING_SCRATCHPAD to think
        tags).  The truncation guard ("don't overwrite a larger log with
        fewer messages") is preserved so resume + branch don't clobber a
        fuller existing snapshot.
        _session_json_enabledFNsession_.jsonr  r  r
  utf-8encodingmessage_countr  zHSkipping session log overwrite: existing has %d messages, current has %drk   )
r   r   rh   rx   session_startlast_updatedr   toolsr1  r     )indentr  zFailed to save session log: )r  r^  logs_dirr   r   r   r  r%  r)  rv  existsr  loads	read_textrq   loggingr   r   rh   rx   r2  	isoformatr   nowr-   r   r4  rZ   ro   r   r   )	r   r  log_filer  r  existingexisting_countentryr  s	            r   r_  zAIAgent._save_session_log  s    t4e<< 	F5t5 	F
	}'H$/'H'H'HHHH 	 	 	FF	6	DG $ $776??k11cggi6H6H1s))C%)%@%@Y%P%PC	N ##s))C%)%A%A#'')BTBT%U%UC	Ns####
    
#z(*<*<g*<*N*NOOH%-\\/3x||T^`bGcGcCdCd%e%eN%G44f*CLL    5 !   D #o M M!%!3!=!=!?!? ( 8 8 : :!6t7Q7WUW!X!X)r!$W# E 	       	D 	D 	D# D Bq B BCCCCCCCCCD D D D D D	DsN   6 
AAC	I BF* )I *
F74I 6F77BI 
J!JJc                    d| _         || _        | j        t          d| j                   d| _        nd| _        t          | dd          }t          | dd          }|Q|O|5  t          |          }ddd           n# 1 swxY w Y   |D ]#}	 t          d|           # t          $ r Y  w xY w| j        5  t          | j	                  }ddd           n# 1 swxY w Y   |D ]J}	 |
                    |           # t          $ r%}t                              d|           Y d}~Cd}~ww xY w| j        s?t          d|r!t          |          dk    rd	|dd          d
n	|rd	| dndz              dS dS )a  
        Request the agent to interrupt its current tool-calling loop.
        
        Call this from another thread (e.g., input handler, message receiver)
        to gracefully stop the agent and process a new message.
        
        Also signals long-running tool executions (e.g. terminal commands)
        to terminate early, so the agent can respond immediately.
        
        Args:
            message: Optional new message that triggered the interrupt.
                     If provided, the agent will include this in its response context.
        
        Example (CLI):
            # In a separate input thread:
            if user_typed_something:
                agent.interrupt(user_input)
        
        Example (Messaging):
            # When new message arrives for active session:
            if session_has_running_agent:
                running_agent.interrupt(new_message.text)
        TNF_tool_worker_threads_tool_worker_threads_lockz0Failed to propagate interrupt to child agent: %su   
⚡ Interrupt requested(   z: 'z...''rk   )_interrupt_requested_interrupt_message_execution_thread_id_set_interrupt _interrupt_thread_signal_pendingr  r  r   _active_children_lock_active_children	interruptr   r   r   rK  rq   )	r   r   _tracker_tracker_lock_worker_tids_wtidchildren_copychildr  s	            r   rN  zAIAgent.interrupt  s   0 %)!") $04!:;;;49D11 59D1 4!7>>&A4HHM$= . .#H~~. . . . . . . . . . . . . . .%  "4////    D ' 	8 	8 !677M	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8" 	T 	TET(((( T T TOQRSSSSSSSST 	V-W  2TY\]dYeYehjYjYj1Iwss|1I1I1I1I  EL  qT  qAv}  qA  qA  qA  qA  RT  U  V  V  V  V  V	V 	VsN   "A>>BBB
B,+B,6CCC'C==
D,D''D,c                    d| _         d| _        d| _        | j        t	          d| j                   t          | dd          }t          | dd          }|Q|O|5  t          |          }ddd           n# 1 swxY w Y   |D ]#}	 t	          d|           # t          $ r Y  w xY wt          | dd          }|$|5  d| _        ddd           dS # 1 swxY w Y   dS dS )zMClear any pending interrupt request and the per-thread tool interrupt signal.FNrC  rD  _pending_steer_lock)	rG  rH  rK  rI  rJ  r  r  r   _pending_steer)r   rO  rP  rQ  rR  _steer_locks         r   clear_interruptzAIAgent.clear_interrupt*	  s   $)!"&05-$05$";<<< 4!7>>&A4HHM$= . .#H~~. . . . . . . . . . . . . . .%  "5%0000    D d$94@@" + +&*#+ + + + + + + + + + + + + + + + + + #"s6   A66A:=A:B
B$#B$<CCCrj  c                 B   |r|                                 sdS |                                 }t          | dd          }|$t          | dd          }|r|dz   |z   n|| _        dS |5  | j        r| j        dz   |z   | _        n|| _        ddd           n# 1 swxY w Y   dS )a  
        Inject a user message into the next tool result without interrupting.

        Unlike interrupt(), this does NOT stop the current tool call. The
        text is stashed and the agent loop appends it to the LAST tool
        result's content once the current tool batch finishes. The model
        sees the steer as part of the tool output on its next iteration.

        Thread-safe: callable from gateway/CLI/TUI threads. Multiple calls
        before the drain point concatenate with newlines.

        Args:
            text: The user text to inject. Empty strings are ignored.

        Returns:
            True if the steer was accepted, False if the text was empty.
        FrV  NrW  rn  T)r   r  rW  )r   rj  r  _lockr?  s        r   steerzAIAgent.steerK	  s   $  	4::<< 	5**,,3T::= t%5t<<HAI"V8d?W#<#<wD4 	. 	." .&*&9D&@7&J##&-#		. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	.
 ts   &"BBBc                     t          | dd          }|t          | dd          }d| _        |S |5  | j        }d| _        ddd           n# 1 swxY w Y   |S )zReturn the pending steer text (if any) and clear the slot.

        Safe to call from the agent execution thread after appending tool
        results. Returns None when no steer is pending.
        rV  NrW  )r  rW  )r   r[  rj  s      r   _drain_pending_steerzAIAgent._drain_pending_steero	  s     3T::=4!1488D"&DK 	' 	'&D"&D	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' s   AAAru  resultis_errorc                    |t           vrdS t          | dd          }|dS t          ||          }|sdS t          ||          }|r$|s"t	          |          }|D ]}	|	|vr||d||	<   dS |D ]}	|                    |	d           dS )a  Record a ``write_file`` / ``patch`` outcome for the turn-end verifier.

        On failure, store ``{path: {error_preview, tool}}`` entries.  On
        success, remove any prior failure entries for the same paths (the
        model recovered within the turn).  Silently no-ops if the per-turn
        state dict hasn't been initialised yet (e.g. a tool dispatched
        outside ``run_conversation``).
        N_turn_failed_file_mutations)r#  error_preview)_FILE_MUTATING_TOOLSr  rW   rM   rX   r  )
r   ru  r   r_  r`  statetargetslandedr  paths
             r   _record_file_mutation_resultz$AIAgent._record_file_mutation_result	  s     000F;TBB=F0DAA 	F,Y?? 	&F 	&,V44G   u$$ ))0# #E$K    & &		$%%%%& &rf   c                    	 ddl }|j                            d          }|(|                                                                dvS 	 ddlm}  |            pi }n# t          $ r i }Y nw xY wt          |t                    r|                    d          nd}t          |t                    r&d|v r"t          |                    d                    S n# t          $ r Y nw xY wdS )	ao  Check whether the per-turn file-mutation verifier footer is on.

        Config path: ``display.file_mutation_verifier`` (bool, default True).
        ``HERMES_FILE_MUTATION_VERIFIER`` env var overrides config.  Exposed
        as a method so tests can patch a single seam without reaching into
        the private ``_turn_failed_file_mutations`` state dict.
        r   NHERMES_FILE_MUTATION_VERIFIER>   0noofffalseload_configdisplayfile_mutation_verifierTr   r   r   r   r   hermes_cli.configrq  r   r  r  r  r   _osenv_load_config_cfg_displays         r   _file_mutation_verifier_enabledz'AIAgent._file_mutation_verifier_enabled	  s   	+//"ABBCyy{{((**2MMMIIIIII#|~~+   .8t.D.DNtxx	***$H(D)) D.F(.R.RHLL)ABBCCC 	 	 	D	t7   AC A C A-*C ,A--A)C 
C%$C%zE(?<![/:\w.`])(?:~/|/|[A-Za-z]:[/\\])(?:[\w.\-]+[/\\])*[\w.\-]+\.[\w]+c                 B    |s|S | j                             d |          S )u  Wrap bare file paths in backticks so they aren't auto-delivered.

        The gateway's ``extract_local_files`` scans response text for bare
        absolute/home paths ending in a deliverable extension and uploads
        any that exist on disk as native attachments — but it explicitly
        skips paths inside inline-code (`` `...` ``) spans.  Backticking
        every path the footer renders defeats that auto-detection while
        keeping the path fully human-readable.  Paths already wrapped in a
        backtick (the negative lookbehind excludes a preceding `` ` ``) are
        left untouched so we never double-wrap.
        c                 4    d|                      d           dS )N`r   )r  )r  s    r   <lambda>z2AIAgent._neutralize_footer_paths.<locals>.<lambda>	  s    1BQWWQZZ1B1B1B rf   )_FOOTER_PATH_REr$  )r  rj  s     r   _neutralize_footer_pathsz AIAgent._neutralize_footer_paths	  s.      	K"&&'B'BDIIIrf   failedc           	      0   |sdS dt          |           dg}d}|                                D ]\  }}|dk    r n|                    d          pd                                }|                    d          pd}|r|                    d	| d
| d|            n|                    d	| d
| d           |dz  }t          |          |z
  }|dk    r|                    d| d           |                     d                    |                    S )u  Render the per-turn failed-mutation dict as a user-facing footer.

        Displays up to 10 paths with their first error preview, then a
        count of any additional failures.  Returns an empty string when
        the dict is empty so callers can concatenate unconditionally.

        Every file path that reaches the user-facing text — both the bullet
        path and any path echoed inside the tool's error preview — is
        backtick-wrapped via ``_neutralize_footer_paths`` so the gateway's
        bare-path media extractor can never auto-attach a protected file
        (e.g. ``~/.hermes/config.yaml``) to a messaging channel (#35584).
        rk   u   ⚠️ File-mutation verifier: z file(s) were NOT modified this turn despite any wording above that may suggest otherwise. Run `git status` or `read_file` to confirm.r   r   rc  r#  patchu     • `u   ` — [z] z] failedrm   u     • … and z morern  )rq   r  r   r   rv  r  r{  )	r  r  linesshownrh  infor  r#  	remainings	            r   $_format_file_mutation_failure_footerz,AIAgent._format_file_mutation_failure_footer	  s^     	2&6{{& & &
  ,,.. 		 		JD${{xx006B==??G88F##.wD DEtEEDEEGEEFFFFBtBBDBBBCCCQJEEKK%'	q==LL:):::;;; ++DIIe,<,<===rf   c                    	 ddl }|j                            d          }|(|                                                                dvS 	 ddlm}  |            pi }n# t          $ r i }Y nw xY wt          |t                    r|                    d          nd}t          |t                    r&d|v r"t          |                    d                    S n# t          $ r Y nw xY wdS )	aY  Check whether the end-of-turn completion explainer footer is on.

        Config path: ``display.turn_completion_explainer`` (bool, default
        True).  ``HERMES_TURN_COMPLETION_EXPLAINER`` env var overrides
        config.  Exposed as a method so tests can patch a single seam,
        mirroring ``_file_mutation_verifier_enabled``.
        r   N HERMES_TURN_COMPLETION_EXPLAINER>   rl  rm  rn  ro  rp  rr  turn_completion_explainerTrt  rv  s         r   "_turn_completion_explainer_enabledz*AIAgent._turn_completion_explainer_enabled
  s   	+//"DEECyy{{((**2MMMIIIIII#|~~+   .8t.D.DNtxx	***$H(D)) G.IX.U.UHLL)DEEFFF 	 	 	D	tr}  turn_exit_reasonc                 v   | sdS t          |           }|                    d          rdS d}|dk    r|dz   S |dk    r|dz   S |dk    r|d	z   S |d
k    r|dz   S |dk    r|dz   S |dk    r|dz   S |dk    r|dz   S |                    d          r|dz   S |                    d          r|dz   S |dk    r|dz   S dS )u  Render a user-facing explanation for an abnormal turn ending.

        Maps the internal ``turn_exit_reason`` to a short, actionable
        message so a turn that produced no usable assistant reply (empty
        content after retries, a partial/truncated stream, a still-pending
        tool result, or an iteration/budget limit) is never silent from
        the UI's perspective — the symptom users report in #34452.

        Returns an empty string for reasons that are NOT abnormal (e.g.
        a normal ``text_response(...)`` exit), so callers can concatenate
        or substitute unconditionally without warning on healthy turns
        like a terse ``Done.``.
        rk   text_responseu   ⚠️ No reply: empty_response_exhaustedzthe model returned empty content after retries and any fallback providers. Try `continue`, switch model/provider, or inspect the tool output above.!all_retries_exhausted_no_responsezall API retries were exhausted before a response was produced (provider errors / rate limits). Try `continue` or switch provider.partial_stream_recoveryzsstreaming stopped early and only a partial response was recovered. Send `continue` to resume from where it stopped.fallback_prior_turn_contentzano new content was produced this turn; showing recovered prior context. Send `continue` to retry.interrupted_during_api_callz[the request was interrupted mid-call before a reply was received. Send `continue` to retry.budget_exhaustedzfthe per-turn iteration/cost budget was exhausted before a final answer. Send `continue` to keep going. ollama_runtime_context_too_smallzjthe local model's context window was too small to finish. Increase the context size or use a larger model.max_iterations_reachedz}the maximum tool-iteration limit was reached before a final answer. Send `continue` to keep going, or raise `max_iterations`.error_near_max_iterationsztan error occurred near the iteration limit before a final answer. Check the tool output above, then send `continue`.pending_tool_resultzthe turn stopped while a tool result was still pending and the model produced no follow-up text. Send `continue` to let it summarize.r  )r  r  r  s      r   #_format_turn_completion_explanationz+AIAgent._format_turn_completion_explanation"
  s      	2%&& _-- 	2$///44 888&& ...NN
 222;;
 22266
 '''??
 777CC
 566 	$$ 899 	MM
 ***$$ rrf   num_tool_msgsc                 (    ddl m}  || ||          S )uV   Forwarder — see ``agent.agent_runtime_helpers.apply_pending_steer_to_tool_results``.r   )#apply_pending_steer_to_tool_results)rF  r  )r   r  r  r  s       r   $_apply_pending_steer_to_tool_resultsz,AIAgent._apply_pending_steer_to_tool_results
  s)    SSSSSS224=QQQrf   descc                     t          j                     | _        || _        t          j                            d          r$	 ddlm}  |             dS # t          $ r Y dS w xY wdS )u  Update the last-activity timestamp and description (thread-safe).

        Also bridges to the kanban board's heartbeat fields when this
        process is a dispatcher-spawned worker (HERMES_KANBAN_TASK set),
        so the dispatcher watchdog doesn't reclaim an actively-running
        worker as stale (#31752). Bridge is rate-limited (60s) and
        best-effort — it never raises into the agent loop.
        HERMES_KANBAN_TASKr   )!heartbeat_current_worker_from_envN)	r  _last_activity_ts_last_activity_descr   r   r   tools.kanban_toolsr  r   )r   r  r  s      r   _touch_activityzAIAgent._touch_activity
  s     "&#' :>>.// 		PPPPPP1133333   
 			 		s    A 
A A c                     |dS t          |dd          }|sdS 	 ddlm}  ||| j                  }|	|| _        dS dS # t
          $ r Y dS w xY w)zParse x-ratelimit-* headers from an HTTP response and cache the state.

        Called after each streaming API call.  The httpx Response object is
        available on the OpenAI SDK Stream via ``stream.response``.
        Nheadersr   )parse_rate_limit_headersr  )r  agent.rate_limit_trackerr  rg   _rate_limit_stater   )r   r  r  r  re  s        r   _capture_rate_limitszAIAgent._capture_rate_limits
  s      F-D99 	F	IIIIII,,Wt}MMME ).&&& !  	 	 	DD	s   !A   
AAc                     | j         S )z1Return the last captured RateLimitState, or None.)r  r   s    r   get_rate_limit_statezAIAgent.get_rate_limit_state
  s    %%rf   c                 H   	 ddl m}  |            }n# t          $ r d}Y nw xY w||| _        | j        |j        | _        t          | dd          }t          |t                    rd|d<   |j	        }t                              d|j        |j        pd|j        |j        |d	|d
z  z  nd           |                                  dS |dS t          |dd          }|sdS t!          t"          j                            d                    }	 ddl m}  ||| j                  }	n# t          $ r Y dS w xY w|	|rt                              d           dS |	| _        | j        |	j        | _        |r|                                 }
|	j	        }t                              d|	j        |	j        pd|	j        |	j        |d	|d
z  z  nd|
d|
dz  z  nd|	j        t1          d          k    r
d|	j        z  nd|	j        r
d|	j        z  nd	  	         |                                  dS )u  Parse x-nous-credits-* headers, cache CreditsState, fire threshold notices.

        Fail-open throughout — header issues never break the agent loop. The PARSE is
        swallowed (any error → treated as a miss → keep last-known). The notice
        EVALUATION/EMIT is a SEPARATE block that WARNS on failure (R1-M2): a bug in the
        depletion-notice path must not vanish silently under the parse swallow.
        r   )dev_fixture_credits_stateN_credits_latchTseen_below_90u   credits ▸ [FIXTURE] remaining=%d (%s) · paid=%s · denom=%s · used=%s (real headers bypassed — `echo clear` / unset HERMES_DEV_CREDITS_FIXTURE to restore)?z%.0f%%r   zn/ar  HERMES_DEV_CREDITS)parse_credits_headersr  uo   credits ▸ response had no valid x-nous-credits-* headers (miss — producer off / non-Nous path / >TTL stale)uY   credits ▸ remaining=%d (%s) · paid=%s · denom=%s · used=%s · Δspent=%s · age=%s%su   %.1f¢i'  r  z%.0fsu    · disabled=%srk   )agent.credits_trackerr  r   _credits_state_credits_session_start_microsremaining_microsr  r  r  used_fractionr   r  remaining_usdpaid_accessdenominator_kind_emit_credits_noticesr]   r   r   r   r  rg   get_credits_spent_microsage_secondsr  disabled_reason)r   r  r  _fixture_latch_usedr  _devr  re  spentuseds               r   _capture_creditszAIAgent._capture_credits
  s   	GGGGGG0022HH 	 	 	HHH	"*D195=5N2T#3T::F&$'' /*.'*EKKi)&-#$).3.?US[))U   &&(((F F-D99 	Frz~~.BCCDD	CCCCCC))'DMJJJEE 	 	 	FF	= K   F $-5161GD. 	 1133E&DKK,&#*s!&-1-=TCZ((5050AUU]++u161BeEll1R1R5,,,X]?D?T\"U%:::Z\   	""$$$$$s    ""D' '
D54D5c                 N   t          | dd          t          | dd          dS |                                 sdS t          | dd          }|dS 	 ddlm}m} t          | dd          }|t                      ddd	x}| _         |t          | d
d          pdt          | dd          pd          } ||||          \  }}|D ]}|                     |           |D ]}	|                     |	           dS # t          $ r  t                              dd           Y dS w xY w)uP  Run the threshold policy on the current credits state and emit notices.

        Shared by the warm path (_capture_credits) and the L3 cold-start seed, so a
        session that opens already depleted warns immediately — not only after the first
        inference header. Runs only when a notice consumer is bound (messaging binds none
        → state still cached for /usage, no policy). WARNS on failure rather than
        swallowing (R1-M2): a depletion-path bug must not vanish silently. Emits clears
        FIRST, then shows (so depleted lands last in a latest-wins slot).
        r   Nr   r  r   )evaluate_credits_noticesis_free_tier_modelr  F)activer  
usage_bandr   rk   rh   )model_is_freez%credits notice evaluation/emit failedTr   )r  _credits_notices_enabledr  r  r  r  r  rq  rl  r   r   r   )
r   re  r  r  latchr  to_showto_clearrn  rk  s
             r   r  zAIAgent._emit_credits_notices  s    4*D119gdLcei>j>j>rF,,.. 	F.55=F	SZZZZZZZZD"2D99E}9<QVfj.k.kk+ /.gr**0bj"--3 M !9 8Ub c c cGX - -'',,,,! * *!!&))))* * 	S 	S 	SNNBTNRRRRRR	Ss   B'C: :&D$#D$c                 \   t          | dd          }||S d}	 ddlm}  |            pi }t          |t                    r|                    d          nd}t          |t                    r&d|v r"t          |                    d                    }n# t          $ r d}Y nw xY w|| _        |S )uh  Whether credits notices are enabled (config display.credits_notices).

        Read once per agent and cached — the policy runs after every API
        response, and the setting governs UI noise, not correctness, so a
        config flip applying on the next session is fine.  Fail-open True
        (preserve current behaviour) on any config error.
        _credits_notices_enabled_cacheNTr   rp  rr  credits_notices)	r  ru  rq  r  r  r   r  r   r  )r   cachedenabledry  rz  r{  s         r   r  z AIAgent._credits_notices_enabled'  s     ?FFM	EEEEEE<>>'RD.8t.D.DNtxx	***$H(D)) @.?8.K.Kx||,=>>?? 	 	 	GGG	.5+s   A9B B"!B"c                     | j         S )z/Return the last captured CreditsState, or None.)r  r   s    r   get_credits_statezAIAgent.get_credits_state>  s    ""rf   c                 J    | j         | j        dS | j         | j        j        z
  S )z\Session-cumulative micros spent = first_seen_remaining - current_remaining. None if no data.N)r  r  r  r   s    r   r  z AIAgent.get_credits_spent_microsB  s,    -59L9T41D4G4XXXrf   c                 |   |dS t          |dd          }|sdS 	 |                    d          }|sdS |                                dk    r2| xj        dz  c_        t                              d| j                   dS t                              d|                                           dS # t          $ r Y dS w xY w)zRead X-OpenRouter-Cache-Status from response headers and log it.

        Increments ``_or_cache_hits`` on HIT so callers can report savings.
        Nr  zx-openrouter-cache-statusHITrm   z)OpenRouter response cache HIT (total: %d)zOpenRouter response cache %s)r  r   upper_or_cache_hitsr   r  r   r   )r   r  r  rt  s       r   _check_openrouter_cache_statusz&AIAgent._check_openrouter_cache_statusH  s    
  F-D99 	F
	[[!<==F ||~~&&##q(##GI\]]]]];V\\^^LLLLL 	 	 	DD	s   B- AB- >-B- -
B;:B;c           	          t          j                     | j        z
  }| j        | j        t          |d          | j        | j        | j        | j        j        | j        j	        dS )zReturn a snapshot of the agent's current activity for diagnostics.

        Called by the gateway timeout handler to report what the agent was doing
        when it was killed, and by the periodic "still working" notifications.
        rm   )last_activity_tslast_activity_descseconds_since_activitycurrent_toolr  r   budget_used
budget_max)
r  r  r  round_current_tool_api_call_countr   r   r  	max_total)r   elapseds     r   get_activity_summaryzAIAgent.get_activity_summary^  sc     )++ 66 $ 6"&":&+GQ&7&7 ."2"105/9	
 	
 		
rf   c                 h   | j         rY	 | j                             |pg            n# t          $ r Y nw xY w	 | j                                          n# t          $ r Y nw xY wt	          | d          r?| j        r:	 | j                            | j        pd|pg            dS # t          $ r Y dS w xY wdS dS )uC  Shut down the memory provider and context engine — call at actual session boundaries.

        This calls on_session_end() then shutdown_all() on the memory
        manager, and on_session_end() on the context engine.
        NOT called per-turn — only at CLI exit, /reset, gateway
        session expiry, etc.
        r
  rk   N)_memory_managerr  r   shutdown_allr  r
  r   r   r  s     r   shutdown_memory_providerz AIAgent.shutdown_memory_providerp  s!     	$33HNCCCC   $113333    4-.. 	43J 	'66O)rN        	 	 	 	s/   & 
33A 
AA9$B 
B-,B-c                    | j         r.	 | j                             |pg            n# t          $ r Y nw xY wt          | d          r?| j        r:	 | j                            | j        pd|pg            dS # t          $ r Y dS w xY wdS dS )u  Trigger end-of-session extraction without tearing providers down.
        Called when session_id rotates (e.g. /new, context compression);
        providers keep their state and continue running under the old
        session_id — they just flush pending extraction now.r
  rk   N)r  r  r   r  r
  r   r  s     r   commit_memory_sessionzAIAgent.commit_memory_session  s    
  	$33HNCCCC    4-.. 	43J 	'66O)rN        	 	 	 	s   & 
33$A4 4
BB)r  original_user_messagefinal_responseinterruptedc                .   |rdS | j         r|r|sdS t          |d          }t          |d          }|r|sdS 	 d| j        pdi}|||d<    | j         j        ||fi | | j                             || j        pd           dS # t
          $ r Y dS w xY w)us  Mirror a completed turn into external memory providers.

        Called at the end of ``run_conversation`` with the cleaned user
        message (``original_user_message``) and the finalised assistant
        response.  The external memory backend gets both ``sync_all`` (to
        persist the exchange) and ``queue_prefetch_all`` (to start
        warming context for the next turn) in one shot.

        Uses ``original_user_message`` rather than ``user_message``
        because the latter may carry injected skill content that bloats
        or breaks provider queries.

        Interrupted turns are skipped entirely (#15218).  A partial
        assistant output, an aborted tool chain, or a mid-stream reset
        is not durable conversational truth — mirroring it into an
        external memory backend pollutes future recall with state the
        user never saw completed.  The prefetch is gated on the same
        flag: the user's next message is almost certainly a retry of
        the same intent, and a prefetch keyed on the interrupted turn
        would fire against stale context.

        Normal completed turns still sync as before.  The whole body is
        wrapped in ``try/except Exception`` because external memory
        providers are strictly best-effort — a misconfigured or offline
        backend must not block the user from seeing their response.
        Nrn  )sepr   rk   r  )r   )r  rH   r   sync_allqueue_prefetch_allr   )r   r  r  r  r  	user_textresponse_textsync_kwargss           r   _sync_external_memory_for_turnz&AIAgent._sync_external_memory_for_turn  s   D  	F$ 	 	<Q 	F 44ItTTT	7DQQQ 	m 	F	')>B?K#*2J')D )    
  33?0b 4       	 	 	DD	s   A	B 
BBc                    	 | j         5  t          | j                  }| j                                         ddd           n# 1 swxY w Y   |D ]M}	 |                                 # t
          $ r) 	 |                                 n# t
          $ r Y nw xY wY Jw xY wn# t
          $ r Y nw xY w	 t          | dd          }|!|                     |dd           d| _	        dS dS # t
          $ r Y dS w xY w)u  Release LLM client resources WITHOUT tearing down session tool state.

        Used by the gateway when evicting this agent from _agent_cache for
        memory-management reasons (LRU cap or idle TTL) — the session may
        resume at any time with a freshly-built AIAgent that reuses the
        same task_id / session_id, so we must NOT kill:
          - process_registry entries for task_id (user's bg shells)
          - terminal sandbox for task_id (cwd, env, shell state)
          - browser daemon for task_id (open tabs, cookies)
          - memory provider (has its own lifecycle; keeps running)

        We DO close:
          - OpenAI/httpx client pool (big chunk of held memory + sockets;
            the rebuilt agent gets a fresh client anyway)
          - Active child subagents (per-turn artefacts; safe to drop)

        Safe to call multiple times.  Distinct from close() — which is the
        hard teardown for actual session boundaries (/new, /reset, session
        expiry).
        Nclientcache_evictTr  shared)
rL  r  rM  r  release_clientsr   closer  _close_openai_clientr  )r   childrenrT  r  s       r   r  zAIAgent.release_clients  s   ,	+ . . 566%++---. . . . . . . . . . . . . . . "  ))++++    $   	  	 	 	D		T8T22F!))&t)TTT" "!  	 	 	DD	s   B .AB AB 
AB A('B (
B3BB
BBBBB BB 
B,+B,02C& &
C43C4c                    t          | dd          pd}	 ddlm} |                    |           n# t          $ r Y nw xY w	 t          |           n# t          $ r Y nw xY w	 t          |           n# t          $ r Y nw xY w	 | j        5  t          | j	                  }| j	        
                                 ddd           n# 1 swxY w Y   |D ]'}	 |                                 # t          $ r Y $w xY wn# t          $ r Y nw xY w	 t          | dd          }||                     |dd	
           d| _        n# t          $ r Y nw xY w	 g | _        dS # t          $ r Y dS w xY w)a   Release all resources held by this agent instance.

        Cleans up subprocess resources that would otherwise become orphans:
        - Background processes tracked in ProcessRegistry
        - Terminal sandbox environments
        - Browser daemon sessions
        - Active child agents (subagent delegation)
        - OpenAI/httpx client connections

        Safe to call multiple times (idempotent).  Each cleanup step is
        independently guarded so a failure in one does not prevent the rest.
        r   Nrk   r   )process_registry)r2  r  agent_closeTr  )r  tools.process_registryr  kill_allr   r(   r*   rL  r  rM  r  r  r	  r  r^  )r   r2  r  r
  rT  r  s         r   r  zAIAgent.close  s_    $d339r	??????%%g%6666 	 	 	D		w 	 	 	D		G$$$$ 	 	 	D	
	+ . . 566%++---. . . . . . . . . . . . . . . "  KKMMMM    D
  	 	 	D		T8T22F!))&t)TTT" 	 	 	D		%'D""" 	 	 	DD	s   2 
??A 
A A $A4 4
B BC< .C:C< C

C< C
C< C+*C< +
C85C< 7C88C< <
D	D	2E   
EEE 
E('E(historyc                    d}t          |          D ]}|                    d          dk    r|                    dd          }d|vr7	 t          j        |          }d|v r%t	          |d         t
                    r
|d         } nv# t          j        t          f$ r Y w xY w|rP| j        	                    |d	           | j
        s-|                     | j         d
t          |           d           t          d           dS )a  
        Recover todo state from conversation history.
        
        The gateway creates a fresh AIAgent per message, so the in-memory
        TodoStore is empty. We scan the history for the most recent todo
        tool response and replay it to reconstruct the state.
        Nr  r#  r
  rk   z"todos"todosF)mergeu   📋 Restored z todo item(s) from history)reversedr   r  r9  r  r  r  r  _todo_storewriter   rW  r   rq   rJ  )r   r  last_todo_responser  r
  datas         r   _hydrate_todo_storezAIAgent._hydrate_todo_storeS  s6    "G$$ 	 	Cwwv&((ggi,,G''z'**d??z$w-'F'F?)-g&E()4     	t""#5U"CCC? trrsCU?V?Vrrrsssus   
;BB! B!c                     | j         S )z)Check if an interrupt has been requested.)rG  r   s    r   is_interruptedzAIAgent.is_interrupteds  s     ((rf   system_messagec                 (    ddl m}  || |          S )uD   Forwarder — see ``agent.system_prompt.build_system_prompt_parts``.r   )build_system_prompt_partsr  )r  r  )r   r  r  s      r   _build_system_prompt_partsz"AIAgent._build_system_prompt_parts  s*    AAAAAA((nMMMMrf   c                 (    ddl m}  || |          S )u>   Forwarder — see ``agent.system_prompt.build_system_prompt``.r   )build_system_promptr  )r  r"  )r   r  r"  s      r   _build_system_promptzAIAgent._build_system_prompt  s*    ;;;;;;""4GGGGrf   c                     t          | t                    r.|                     dd          p|                     dd          pdS t          | dd          pt          | dd          pdS )z8Extract call ID from a tool_call entry (dict or object).call_idrk   idr  r  r   r  )rs  s    r   _get_tool_call_id_staticz AIAgent._get_tool_call_id_static  sk     b$ 	C66)R((BBFF4,<,<BBr9b))HWRr-B-BHbHrf   c                     t          | t                    rD|                     d          }t          |t                    r|                    dd          pdS dS t          | dd          }t          |dd          pdS )ab  Extract function name from a tool_call entry (dict or object).

        Gemini's OpenAI-compatibility endpoint requires every `role: tool`
        message to carry the matching function name. OpenAI/Anthropic/ollama
        tolerate its absence, so the field is best-effort: callers fall back
        to "" and the message still works elsewhere.
        rq  rF  rk   Nr'  )rs  rN  s     r   _get_tool_call_name_staticz"AIAgent._get_tool_call_name_static  s     b$ 	
##B"d## 0vvfb))/R/2RT**r62&&,",rf   >   r#  rW  rz   rq  r  	developerc                 $    ddl m}  ||           S )uH   Forwarder — see ``agent.agent_runtime_helpers.sanitize_api_messages``.r   )sanitize_api_messages)rF  r-  )r  r-  s     r   _sanitize_api_messageszAIAgent._sanitize_api_messages  s(     	FEEEEE$$X...rf   r  c                 f   t          | t                    r|                     d          dk    rdS |                     d          rdS |                     d          }t          |t                    r|                                rdS nt          |t
                    r|D ]}t          |t                    s|r dS |                    d          }|dv r7|dk    rC|                    dd	          }t          |t                    r|                                r dS  dS n
||d	k    rdS |                     d          p|                     d          }t          |t                    r|                                rdS |                     d          }t          |t
                    r|rdS dS )um  Return True if ``msg`` is an assistant turn whose only payload is reasoning.

        "Thinking-only" means the model emitted reasoning (``reasoning`` or
        ``reasoning_content``) but no visible text and no tool_calls. When sent
        back to providers that convert reasoning into thinking blocks (native
        Anthropic, OpenRouter Anthropic, third-party Anthropic-compatible
        gateways), the resulting message has only thinking blocks — which
        Anthropic rejects with HTTP 400 "The final block in an assistant
        message cannot be `thinking`."

        Symmetric with Claude Code's ``filterOrphanedThinkingOnlyMessages``
        (src/utils/messages.ts). We drop the whole turn from the API copy
        rather than fabricating stub text — the message log (UI transcript)
        keeps the reasoning block; only the wire copy is cleaned.
        r  r  Fr%  r
  r   >   thinkingredacted_thinkingrj  rk   Nrw  rv  Trx  )r  r  r   ro   r   r  )r  r
  blockbtyperj  rv  rds          r   _is_thinking_only_assistantz#AIAgent._is_thinking_only_assistant  s   " #t$$ 	;(F(F577<   	5'')$$gs## 	}} u&& 	   !%..  %$uu		&))===F?? 99VR00D!$,, % %$uuuu  W]]5GG/00HCGGK4H4H	i%% 	)//*;*; 	4WW())b$ 	B 	4urf   c                 $    ddl m}  ||           S )uU   Forwarder — see ``agent.agent_runtime_helpers.drop_thinking_only_and_merge_users``.r   )"drop_thinking_only_and_merge_users)rF  r7  )r  r7  s     r   #_drop_thinking_only_and_merge_usersz+AIAgent._drop_thinking_only_and_merge_users  s(    
 	SRRRRR11(;;;rf   r%  c                 D   ddl m}  |            }t          d | D                       }||k    r| S d}g }| D ]H}|j        j        dk    r!||k     r|                    |           |dz  }3|                    |           It                              d||z
  |           |S )aj  Truncate excess delegate_task calls to max_concurrent_children.

        The delegate_tool caps the task list inside a single call, but the
        model can emit multiple separate delegate_task tool_calls in one
        turn.  This truncates the excess, preserving all non-delegate calls.

        Returns the original list if no truncation was needed.
        r   )_get_max_concurrent_childrenc              3   :   K   | ]}|j         j        d k    dV  dS )delegate_taskrm   N)rq  rF  rr  s     r   r$  z3AIAgent._cap_delegate_task_calls.<locals>.<genexpr>  s1      [[2r{7G?7Z7ZQ7Z7Z7Z7Z[[rf   r<  rm   zUTruncated %d excess delegate_task call(s) to enforce max_concurrent_children=%d limit)tools.delegate_toolr:  sumrq  rF  rv  r   r   )r%  r:  max_childrendelegate_countkept_delegates	truncatedrs  s          r   _cap_delegate_task_callsz AIAgent._cap_delegate_task_calls  s     	EDDDDD3355[[[[[[[\))	 	% 	%B{?22!L00$$R((("a'N  $$$$/\)<	
 	
 	

 rf   c                 L   t                      }g }| D ]n}|j        j        |j        j        f}||vr+|                    |           |                    |           It                              d|j        j                   ot          |          t          |           k     r|n| S )zRemove duplicate (tool_name, arguments) pairs within a single turn.

        Only the first occurrence of each unique pair is kept.
        Returns the original list if no duplicates were found.
        zRemoved duplicate tool call: %s)	r  rq  rF  rp  addrv  r   r   rq   )r%  seenuniquers  rn  s        r   _deduplicate_tool_callszAIAgent._deduplicate_tool_calls  s     EE 	T 	TB;#R[%:;C$b!!!!@"+BRSSSSVs:66vvJFrf   c                 &    ddl m}  || |          S )uC   Forwarder — see ``agent.agent_runtime_helpers.repair_tool_call``.r   )repair_tool_call)rF  rJ  )r   ru  rJ  s      r   _repair_tool_callzAIAgent._repair_tool_call  s'    @@@@@@i000rf   c                 (    ddl m}  ||            dS )uC   Forwarder — see ``agent.system_prompt.invalidate_system_prompt``.r   )invalidate_system_promptN)r  rM  )r   rM  s     r   _invalidate_system_promptz!AIAgent._invalidate_system_prompt  s+    @@@@@@  &&&&&rf   fn_namerp  indexc                 $    t          | ||          S )u  Generate a deterministic call_id from tool call content.

        Used as a fallback when the API doesn't provide a call_id.
        Deterministic IDs prevent cache invalidation — random UUIDs would
        make every API call's prefix unique, breaking OpenAI's prompt cache.
        )_codex_deterministic_call_id)rO  rp  rP  s      r   rF   zAIAgent._deterministic_call_id$  s     ,GYFFFrf   raw_idc                      t          |           S )z8Split a stored tool id into (call_id, response_item_id).)_codex_split_responses_tool_id)rS  s    r   rG   z AIAgent._split_responses_tool_id.  s     .f555rf   r%  response_item_idc                 "    t          ||          S )zCBuild a valid Responses `function_call.id` (must start with `fc_`).)(_codex_derive_responses_function_call_id)r   r%  rV  s      r   rE   z*AIAgent._derive_responses_function_call_id3  s     8AQRRRrf   c                 J    t          j                    }|j         d|j         S )N:)rG  current_threadrF  ident)r   threads     r   _thread_identityzAIAgent._thread_identity;  s(    )+++.....rf   c                     t          | dd          }t          | dd          }t          | dd          }d|                                  d| d| d| S )	Nrg   r%  rh   r   zthread=z
 provider=z
 base_url=z model=)r  r^  )r   rg   rh   r   s       r   _client_log_contextzAIAgent._client_log_context?  s}    4Y774Y77gy111d++-- 1 1 1 1 1 1).1 1	
rf   c                 `    t          | dd           }|t          j                    }|| _        |S )N_client_lock)r  rG  RLockrb  )r   locks     r   _openai_client_lockzAIAgent._openai_client_lockH  s2    t^T22<?$$D $Drf   r  c                    ddl m} t          | |          rdS t          | dd          }|-t	          |          r |            rdS nt          |          rdS t          | dd          }|t          t          |dd                    S dS )a  Check if an OpenAI client is closed.

        Handles both property and method forms of is_closed:
        - httpx.Client.is_closed is a bool property
        - openai.OpenAI.is_closed is a method returning bool

        Prior bug: getattr(client, "is_closed", False) returned the bound method,
        which is always truthy, causing unnecessary client recreation on every call.
        r   MockF	is_closedNT_client)unittest.mockrh  r  r  r  r  )r  rh  is_closed_attrr  s       r   _is_openai_client_closedz AIAgent._is_openai_client_closedO  s     	'&&&&&fd## 	5 d;;%'' !>##  4 n%% tfi66"[%@@AAAurf   c                    	 dd l }dd l}|j        |j        dfg}t	          |d          rg|                    |j        |j        df           |                    |j        |j        df           |                    |j        |j	        df           n2t	          |d          r"|                    |j        |j
        df           t          |           }|                    |                    |          |	          S # t          $ r Y d S w xY w)
Nr   rm   TCP_KEEPIDLE   r      TCP_KEEPALIVE)socket_options)	transportproxy)httpxsocket
SOL_SOCKETSO_KEEPALIVEr  rv  IPPROTO_TCPro  TCP_KEEPINTVLTCP_KEEPCNTrr  r   ClientHTTPTransportr   )rh   _httpx_socket
_sock_opts_proxys        r   _build_keepalive_http_clientz$AIAgent._build_keepalive_http_clientm  sF   	""""$$$$"-w/CQGHJw// T!!7#68Lb"QRRR!!7#68Mr"RSSS!!7#68KQ"OPPPP/22 T!!7#68Mr"RSSS
 -X66F== ..j.II !     	 	 	44	s   C:C= =
D
Dclient_kwargsr  c                ,    ddl m}  || |||          S )uG   Forwarder — see ``agent.agent_runtime_helpers.create_openai_client``.r   )create_openai_clientr  )rF  r  )r   r  r  r  r  s        r   _create_openai_clientzAIAgent._create_openai_client  s.    DDDDDD##D-vVVVVrf   c                 $    ddl m}  ||           S )uJ   Forwarder — see ``agent.agent_runtime_helpers.force_close_tcp_sockets``.r   )force_close_tcp_sockets)rF  r  )r  r  s     r   _force_close_tcp_socketsz AIAgent._force_close_tcp_sockets  s(     	HGGGGG&&v...rf   c                R   |d S |                      |          }	 |                                 t                              d||||                                            d S # t
          $ r;}t                              d|||                                 |           Y d }~d S d }~ww xY w)Nz<OpenAI client closed (%s, shared=%s, tcp_force_closed=%d) %sz6OpenAI client close failed (%s, shared=%s) %s error=%s)r  r  r   r  r`  r   r   )r   r  r  r  force_closedr   s         r   r	  zAIAgent._close_openai_client  s    >F 44V<<	LLNNNKKN((**      	 	 	LLH((**        	s   AA! !
B&+0B!!B&c                   |                                  5  t          | dd           }	 |                     | j        |d          }nR# t          $ rE}t
                              d||                                 |           Y d }~d d d            dS d }~ww xY w|| _        d d d            n# 1 swxY w Y   | 	                    |d| d           dS )Nr  Tr  z7Failed to rebuild shared OpenAI client (%s) %s error=%sFzreplace:)
re  r  r  _client_kwargsr   r   r   r`  r  r	  )r   r  
old_client
new_clientr   s        r   _replace_primary_openai_clientz&AIAgent._replace_primary_openai_client  sU   %%'' 	% 	% x66J	!778KTZcg7hh

   M,,..	   uuu	% 	% 	% 	% 	% 	% 	% 	% %DK	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	!!*5H5H5HQU!VVVts:   B+AB+
B/B?B+B
B++B/2B/c                   |                                  5  t          | dd           }|#|                     |          s|cd d d            S d d d            n# 1 swxY w Y   t                              d||                                            |                     d|           st          d          |                                  5  | j        cd d d            S # 1 swxY w Y   d S )Nr  zCDetected closed shared OpenAI client; recreating before use (%s) %szrecreate_closed:r  z'Failed to recreate closed OpenAI client)	re  r  rm  r   r   r`  r  RuntimeErrorr  )r   r  r  s      r   _ensure_primary_openai_clientz%AIAgent._ensure_primary_openai_client  s   %%'' 	 	T8T22F!$*G*G*O*O!	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
 	Q$$&&	
 	
 	

 22:UV:U:U2VV 	JHIII%%'' 	 	;	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s#   *AAAC!!C%(C%c                 $    ddl m}  ||           S )uK   Forwarder — see ``agent.agent_runtime_helpers.cleanup_dead_connections``.r   )cleanup_dead_connections)rF  r  )r   r  s     r   _cleanup_dead_connectionsz!AIAgent._cleanup_dead_connections  s%    HHHHHH''---rf   c                    t          | t                    sdS g }|                     d          }t          |t                    r|                    |           |                     d          }t          |t                    r|                    |           dt
          dt          ffdt          fd|D                       S )zHReturn True when the outbound request still contains native image parts.Fr  inputr   r   c                 "   t          | t                    rH|                     d          }|dv rdS t          fd|                                 D                       S t          | t
                    rt          fd| D                       S dS )Nr   >   rl  rm  Tc              3   .   K   | ]} |          V  d S r   r  r  r  _contains_images     r   r$  zPAIAgent._api_kwargs_have_image_parts.<locals>._contains_image.<locals>.<genexpr>  s-      FF!??1--FFFFFFrf   c              3   .   K   | ]} |          V  d S r   r  r  s     r   r$  zPAIAgent._api_kwargs_have_image_parts.<locals>._contains_image.<locals>.<genexpr>  s-      ==!??1--======rf   F)r  r  r   r&  valuesr  )r   ptyper  s     r   r  z=AIAgent._api_kwargs_have_image_parts.<locals>._contains_image  s    %&& G		&))8884FFFFu||~~FFFFFF%&& >====u======5rf   c              3   .   K   | ]} |          V  d S r   r  )r  r  r  s     r   r$  z7AIAgent._api_kwargs_have_image_parts.<locals>.<genexpr>  s-      @@T??4((@@@@@@rf   )r  r  r   r  extendr   r  r&  )r  
candidatesr  response_inputr  s       @r   _api_kwargs_have_image_partsz$AIAgent._api_kwargs_have_image_parts  s     *d++ 	5
>>*--h%% 	(h''' $00nd++ 	.n---	3 	4 	 	 	 	 	 	 @@@@Z@@@@@@rf   	is_visionc                (    ddl m}  |d|          S )Nr   )copilot_request_headersT)is_agent_turnr  )hermes_cli.copilot_authr  )r   r  r  s      r   _copilot_headers_for_requestz$AIAgent._copilot_headers_for_request  s*    CCCCCC&&TYOOOOrf   )r  c                   ddl m} |                     |          }t          ||          r|S |                                 5  t          | j                  }d d d            n# 1 swxY w Y   d|d<   t          t          |	                    dd                    d          r0| 
                    |pi           r|                     d	          |d
<   |                     ||d          S )Nr   rg  r  r  rh   rk   r  T)r  default_headersFr  )rk  rh  r  r  re  r  r  r[   ro   r   r  r  r  )r   r  r  rh  primary_clientrequest_kwargss         r   _create_request_openai_clientz%AIAgent._create_request_openai_client  sH   &&&&&&;;6;JJnd++ 	"!!%%'' 	7 	7!$"566N	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 )*}%!#n&8&8R&H&H"I"IKbcc	b11*2BCC	b 150Q0Q\`0Q0a0aN,-)).PU)VVVs   A$$A(+A(c                6    |                      ||d           d S )NFr  )r	  )r   r  r  s      r   _close_request_openai_clientz$AIAgent._close_request_openai_client	  s#    !!&!FFFFFrf   c                &   |dS 	 |                      |          }t                              d|||                                            dS # t          $ r:}t                              d||                                 |           Y d}~dS d}~ww xY w)u  Cross-thread abort: shut sockets down without releasing FDs.

        Companion to :meth:`_close_request_openai_client` for stranger-thread
        callers (interrupt-check loop, stale-call detector). Calling
        ``client.close()`` from a thread that does not own the active httpx
        connection raced the still-live SSL BIO and corrupted unrelated file
        descriptors when the kernel recycled the just-freed TCP FD (#29507).

        Here we only ``shutdown(SHUT_RDWR)`` the pool's sockets. That unblocks
        the owning worker thread's pending ``recv``/``send`` with an EOF or
        ``EPIPE`` so it can unwind and close ``client`` from its own context
        — which is where the FD release belongs.
        Nz`OpenAI client aborted (%s, shared=False, tcp_force_closed=%d, deferred_close=stranger_thread) %sz9OpenAI client abort failed (%s, shared=False) %s error=%s)r  r   r  r`  r   r   )r   r  r  shutdown_countr   s        r   _abort_request_openai_clientz$AIAgent._abort_request_openai_client  s     >F	!::6BBNKK5((**      	 	 	LLK((**	        	s   AA 
B/BBon_first_deltac                 *    ddl m}  || |||          S )u;   Forwarder — see ``agent.codex_runtime.run_codex_stream``.r   )run_codex_stream)agent.codex_runtimer  )r   r  r  r  r  s        r   _run_codex_streamzAIAgent._run_codex_stream-  s+    888888j&.IIIrf   c                 (    ddl m}  || ||          S )uK   Forwarder — see ``agent.codex_runtime.run_codex_create_stream_fallback``.r   ) run_codex_create_stream_fallback)r  r  )r   r  r  r  s       r   !_run_codex_create_stream_fallbackz)AIAgent._run_codex_create_stream_fallback2  s)    HHHHHH//j&IIIrf   c                   | j         dk    s	| j        dvrdS 	 | j        dk    rddlm}  |d          }nddlm}  |d          }n9# t
          $ r,}t                              d	| j        |           Y d }~dS d }~ww xY wt          |	                    d
          pd          
                                }t          | j        pd          
                                }|r*|r(||k    r"t                              d| j                   dS 	 | j        dk    rddlm}  ||          }nddlm}  ||          }n9# t
          $ r,}t                              d| j        |           Y d }~dS d }~ww xY w|	                    d
          }	|	                    d          }
t          |	t                    r|	
                                sdS t          |
t                    r|

                                sdS |	
                                | _        |

                                                    d          | _        | j        | j        d
<   | j        | j        d<   |                     | j         d          sdS dS )Nr  >   	xai-oauthr  Fr  r   )!resolve_codex_runtime_credentials)refresh_if_expiring)%resolve_xai_oauth_runtime_credentialsz%s singleton read failed: %sr   rk   z%s singleton tokens differ from the active api_key; skipping singleton force-refresh to avoid silent account swap. Reactive credential rotation should go through the pool.)force_refreshz %s credential refresh failed: %srh   r  _credential_refreshr  T)r   rg   hermes_cli.authr  r  r   r   r   ro   r   r   r   r  r  rh   r  r  )r   rQ  r  singleton_nowr  r   singleton_key
active_keycredsr   rh   s              r   %_try_refresh_codex_client_credentialsz-AIAgent._try_refresh_codex_client_credentials7  s   =---Fc1c1c5	}..MMMMMM A A(-! ! ! RQQQQQ E E(-! ! !  	 	 	LL7LLL55555	 M--i88>B??EEGG+,,2244
 	Z 	MZ,G,GLLK 	   5	}..MMMMMM99NNNQQQQQQ==ERRR 	 	 	LL;T]CPPP55555	 ))I&&99Z(('3'' 	w}} 	5(C(( 	0@0@ 	5}} ((//44)-I&*.-J'22T]:_:_:_2`` 	5ts/   0A	 	
A?!A::A?0D> >
E4!E//E4c                2   | j         dk    s| j        dk    rdS 	 ddlm}  |t	          t          j        dd                    |          }n3# t          $ r&}t          	                    d	|           Y d }~dS d }~ww xY w|
                    d
          }|
                    d          }t          |t                    r|                                sdS t          |t                    r|                                sdS |                                | _        |                                                    d          | _        | j        | j        d
<   | j        | j        d<   | j                            dd            |                     d          sdS dS )Nr!  r  Fr   ) resolve_nous_runtime_credentialsHERMES_NOUS_TIMEOUT_SECONDS15)timeout_secondsr  z"Nous credential refresh failed: %sr   rh   r  r  nous_credential_refreshr  T)r   rg   r  r  r  r   r  r   r   r   r   r  ro   r   r   r  rh   r  r  r  )r   rQ  r  r  r   r   rh   s          r   $_try_refresh_nous_client_credentialsz,AIAgent._try_refresh_nous_client_credentials  s   
 =...$-62I2I5		HHHHHH44 %bi0Mt&T&T U U#  EE  	 	 	LL=sCCC55555	 ))I&&99Z(('3'' 	w}} 	5(C(( 	0@0@ 	5}} ((//44)-I&*.-J' 1488822:S2TT 	5ts   3A 
A>A99A>c                 0   | j         dk    rdS 	 ddlm}  |            \  }}n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY wt          |t                    r|                                sdS |                                }|| _	        | j	        | j
        d<   | j        | j
        d<   |                     t          | j        pd	                     |                     d
          sdS t                              d|           dS )a]  Refresh Copilot credentials and rebuild the shared OpenAI client.

        Copilot tokens may remain the same string across refreshes (`gh auth token`
        returns a stable OAuth token in many setups). We still rebuild the client
        on 401 so retries recover from stale auth/client state without requiring
        a session restart.
        r  Fr   )resolve_copilot_tokenz%Copilot credential refresh failed: %sNr   rh   rk   copilot_credential_refreshr  z%Copilot credentials refreshed from %sT)rg   r  r  r   r   r   r  ro   r   r   r  rh   "_apply_client_headers_for_base_urlr  r  )r   r  	new_tokentoken_sourcer   s        r   '_try_refresh_copilot_client_credentialsz/AIAgent._try_refresh_copilot_client_credentials  s=    =I%%5	EEEEEE&;&;&=&=#I|| 	 	 	LL@#FFF55555	 )S)) 	1B1B 	5OO%%	 )-I&*.-J'//DM4GR0H0HIII22:V2WW 	5;\JJJts   # 
AAAc                 :   | j         dk    st          | d          sdS | j        dk    rdS t          | dd          pd}d|v rdS 	 dd	lm}m}  |            }n3# t          $ r&}t          	                    d
|           Y d }~dS d }~ww xY wt          |t                    r|                                sdS |                                }|| j        k    rdS 	 | j                                         n# t          $ r Y nw xY w	  ||t          | dd           t!          | j        | j                            | _        n3# t          $ r&}t                              d|           Y d }~dS d }~ww xY w|| _        ddlm} | j        dk    r ||          nd| _        dS )Nr  _anthropic_api_keyF	anthropic_anthropic_base_urlrk   z	azure.comr   )resolve_anthropic_tokenbuild_anthropic_clientz'Anthropic credential refresh failed: %sr  z?Failed to rebuild Anthropic client after credential refresh: %s)_is_oauth_tokenT)r   r  rg   r  agent.anthropic_adapterr  r  r   r   r   r  ro   r   r  _anthropic_clientr  r    r   r   r  _is_anthropic_oauth)r   _baser  r  r  r   r  s          r   )_try_refresh_anthropic_client_credentialsz1AIAgent._try_refresh_anthropic_client_credentials  s+   =000FZ8[8[05 =K''5 3R88>B%5	________//11II 	 	 	LLBCHHH55555	 )S)) 	1B1B 	5OO%%	///5	"((**** 	 	 	D		%;%;3T::4T]DJOO& & &D""
  	 	 	NN\^abbb55555	 #,
 	<;;;;;AER]A]A]??9#=#=#=ch tsB   A 
B"BBC2 2
C?>C?:D> >
E.E))E.c                 ^   ddl m}m} t          |d          r |            | j        d<   nlt          |d          r ||          | j        d<   nGt          |d          rt                      | j        d<   nt          |d          rddlm}  |            | j        d<   nt          |d	          rd
di| j        d<   nt          |d          rt                      | j        d<   nt          |d          r3ddl m	}  || j        
                    dd                    | j        d<   nod }	 ddlm}  || j                  }|r|j        rt          |j                  }n# t           $ r Y nw xY w|r|| j        d<   n| j                            dd            |                                  d S )Nr   )build_nvidia_nim_headersbuild_or_headersr  r  zintegrate.api.nvidia.comzapi.routermint.comr  )copilot_default_headersapi.kimi.comrb   zclaude-code/0.1.0portal.qwen.air  )_codex_cloudflare_headersr   rk   get_provider_profile)agent.auxiliary_clientr  r  r[   r  re   r<  r  r~   r  r   	providersr  rg   r  r  r   r  _apply_user_default_headers)	r   rh   r  r  r  r  _ph_headers_gpf2_ph2s	            r   r  z*AIAgent._apply_client_headers_for_base_url  sb   	
 	
 	
 	
 	
 	
 	
 	

 !?;;  	A5E5E5G5GD 122"8-GHH 	A5M5Mh5W5WD 122"8-ABB 	A5H5J5JD 122"8-DEE 	AAAAAAA5L5L5N5ND 122"8^<< 	A6BDW5XD 122"8-=>> 	A5I5K5KD 122"8];; 	AHHHHHH5N5N#''	2666 6D 122
 KCCCCCCuT]++ =D0 ="&t';"<"<K    A9D#$566#''(94@@@
 	((*****s   -3E! !
E.-E.c                     | j         dv rdS ddlm}  || j                            d                    }|r|| j        d<   dS dS )a#  Merge user-configured request headers onto the OpenAI client.

        Reads ``model.default_headers`` from config.yaml and merges it onto
        ``self._client_kwargs["default_headers"]``, with user values taking
        precedence over provider- and SDK-supplied defaults.

        This exists for ``custom`` OpenAI-compatible endpoints sitting behind
        a gateway/WAF that rejects the OpenAI Python SDK's identifying headers
        (``User-Agent: OpenAI/Python ...``, ``X-Stainless-*``). Setting e.g.
        ``model.default_headers: {User-Agent: curl/8.7.1}`` lets the request
        reach such an upstream instead of failing with an opaque 4xx/502 even
        though the same body works under ``curl``. (#40033)

        Delegates the config read + merge to
        ``agent.auxiliary_client._apply_user_default_headers`` so the main and
        auxiliary clients can never drift on precedence or value handling.

        No-op for Anthropic/Bedrock modes, which don't use the OpenAI client,
        and when no overrides are configured.
        )r  bedrock_converseNr   )r  r  )r   r  r  r  r   )r   _merge_user_headersmergeds      r   r  z#AIAgent._apply_user_default_headers*  s    * =FFFF	
 	
 	
 	
 	
 	
 %$T%8%<%<=N%O%OPP 	<5;D 1222	< 	<rf   c                    t          |dd           pt          |dd          }t          |dd           pt          |dd           p| j        }| j        dk    rddlm}m} 	 | j                                         n# t          $ r Y nw xY w|| _	        || _
         |||t          | j        | j                  	          | _        | j        d
k    r ||          nd| _        || _        || _        d S || _        t!          |t"                    r|                    d          n|| _        | j        | j        d<   | j        | j        d<   |                     | j                   |                     d           d S )Nruntime_api_keyaccess_tokenrk   runtime_base_urlrh   r  r   )r  r  r  r  Fr  r   credential_rotationr  )r  rh   r   r  r  r  r  r  r   r  r  r    rg   r   r  r   r  ro   r  r  r  r  )r   rA  runtime_keyruntime_baser  r  s         r   _swap_credentialzAIAgent._swap_credentialH  s   e%6==cP^`bAcAcu&8$??t75R\^bCcCctgkgt=000WWWWWWWW&,,....    '2D#'3D$%;%;\4T]DJOO& & &D" HL}XcGcGc{'C'C'CinD$&DL(DMF"4>|S4Q4Qc++C000Wc)-I&*.-J'//>>>++3H+IIIIIs    A: :
BB)classified_reasonr  has_retried_429r  c                .    ddl m}  || ||||          S )uO   Forwarder — see ``agent.agent_runtime_helpers.recover_with_credential_pool``.r   )recover_with_credential_pool)r   r  r  r  )rF  r  )r   r   r  r  r  r  s         r   _recover_with_credential_poolz%AIAgent._recover_with_credential_poolf  sJ     	MLLLLL++Dk[j  P  `m  n  n  n  	nrf   c                     | j         }|dS | j        dk    s1t          t          | dd                                        d          rdS |                                S )zEWhether a rate-limit retry should wait for same-provider credentials.NFrj   rh   rk   rl   )_credential_poolrg   ro   r  rp   rn   )r   rs   s     r   '_credential_pool_may_recover_rate_limitz/AIAgent._credential_pool_may_recover_rate_limitr  sf    $<5M00074R0011<<=NOO 1 5!!###rf   c                     | j         dk    r|                                  ddlm}  ||t	          | dd                      | j        j        j        di |S )Nr  r   )sanitize_anthropic_kwargsr   rk   )r   r  )r   r  r  r  r  r  r  create)r   r  r  s      r   _anthropic_messages_createz"AIAgent._anthropic_messages_create  s    =000::<<< 	FEEEEE!!74r#B#B	
 	
 	
 	
 6t%.5CC
CCCrf   c                 N   t          t          | dd                    }t          | dd          dk    r+ddlm} t          | dd	          pd	} ||          | _        dS dd
lm}  || j        t          | dd          t          | j        | j	                  |          | _        dS )u)  Rebuild the Anthropic client after an interrupt or stale call.

        Handles both direct Anthropic and Bedrock-hosted Anthropic models
        correctly — rebuilding with the Bedrock SDK when provider is bedrock,
        rather than always falling back to build_anthropic_client() which
        requires a direct Anthropic API key.

        Honors ``self._oauth_1m_beta_disabled`` (set by the reactive recovery
        path when an OAuth subscription rejects the 1M-context beta) so the
        rebuilt client carries the reduced beta set.
        _oauth_1m_beta_disabledFrg   Nbedrockr   )build_anthropic_bedrock_client_bedrock_regionz	us-east-1)r  r  )r  drop_context_1m_beta)
r  r  r  r  r  r  r  r    rg   r   )r   _drop_1mr  regionr  s        r   _rebuild_anthropic_clientz!AIAgent._rebuild_anthropic_client  s     &?GGHH4T**i77NNNNNNT#4kBBQkF%C%CF%K%KD"""FFFFFF%;%;'3T::4T]DJOO%-	& & &D"""rf   c                 &    ddl m}  || |          S )uK   Forwarder — see ``agent.chat_completion_helpers.interruptible_api_call``.r   )interruptible_api_call)r  r  )r   r  r  s      r   _interruptible_api_callzAIAgent._interruptible_api_call  s'    HHHHHH%%dJ777rf   c                 D   t          | dd          }||                                }|rxt          | dd          }||                    |          }|rNd | j        | j        fD             }|D ]}	  ||           # t
          $ r Y w xY w|                     |           t          | dd          }|d|                                }|rNd | j        | j        fD             }|D ]}	  ||           # t
          $ r Y w xY w|                     |           d| _        dS )zDReset tracking for text delivered during the current model response._stream_think_scrubberN_stream_context_scrubberc                     g | ]}||S r   r  r  cbs     r   rt  z;AIAgent._reset_stream_delivery_tracking.<locals>.<listcomp>  s     t t tegesesesesrf   c                     g | ]}||S r   r  r  s     r   rt  z;AIAgent._reset_stream_delivery_tracking.<locals>.<listcomp>  s    pppBacaoRaoaoaorf   rk   )r  flushfeedr   _stream_callbackr   _record_streamed_assistant_text _current_streamed_assistant_text)r   think_scrubber
think_tailctx_scrubber	callbacksr  scrubbertails           r   _reset_stream_delivery_trackingz'AIAgent._reset_stream_delivery_tracking  s    !'?FF%'--//J E  't-GNN+!-!2!2:!>!>J E t tt/I4K`.a t t tI' ! !!BzNNNN( ! ! ! D!88DDD 4!;TBB>>##D ;pp4+EtG\*]ppp	#  B4$   44T:::02---s$   1A==
B
	B
(C44
D Dc                 n    t          |t                    r|rt          | dd          |z   | _        dS dS dS )zCAccumulate visible assistant text emitted through stream callbacks.r%  rk   N)r  ro   r  r%  )r   rj  s     r   r$  z'AIAgent._record_streamed_assistant_text  sR    dC   	T 	@"EEL 111	 	 	 	rf   c                     t          | t                    sdS t          j        dd|                                           S )Nrk   z\s+r  )r  ro   r  r$  r   )rj  s    r   _normalize_interim_visible_textz'AIAgent._normalize_interim_visible_text  s9    $$$ 	2vfc4((..000rf   c           	          |                      |                     |pd                    }|sdS |                      |                     t          | dd          pd                    }t          |          o||k    S )Nrk   Fr%  )r/  r  r  r  )r   r
  visible_contentstreameds       r   _interim_content_was_streamedz%AIAgent._interim_content_was_streamed  s    >>$$W]33
 
  	577$$WT3UWY%Z%Z%`^`aa
 
 H~~=(o"==rf   assistant_msgc                    t          | dd          }|t          |t                    sdS |                    d          }|                     |pd                                          }|r|dk    rdS |                     |          }	  |||           dS # t          $ r  t          	                    dd	           Y dS w xY w)
zESurface a real mid-turn assistant commentary message to the UI layer.r   Nr
  rk   z(empty))already_streamedz interim_assistant_callback errorTr   )
r  r  r  r   r  r   r3  r   r   r   )r   r4  r  r
  visibler6  s         r   _emit_interim_assistant_messagez'AIAgent._emit_interim_assistant_message  s    T7>>:Zt<<:F##I..**7=b99??AA 	'Y..F==gFF	LBw)9:::::: 	L 	L 	LLL;dLKKKKKK	Ls   	B &CCc                    t          | dd          r%|r#|                                rd| _        d|z   }d}nd}t          |t                    rt          | dd          }||                    |pd          }n|                     |pd          }t          | dd          }||                    |          }nt          |          }|s&t          | d	d          s|                    d
          }|sdS d | j	        | j
        fD             }d}|D ] }	  ||           d}# t          $ r Y w xY w|r|                     |           dS dS )z;Fire all registered stream delta callbacks (display + TTS)._stream_needs_breakF

Tr  Nrk   r  r%  rn  c                     g | ]}||S r   r  r  s     r   rt  z.AIAgent._fire_stream_delta.<locals>.<listcomp>(  s    hhhBY[YgRYgYgYgrf   )r  r   r:  r  ro   r"  r  r+   lstripr   r#  r   r$  )r   rj  prepended_breakr&  r*  r)  	deliveredr  s           r   _fire_stream_deltazAIAgent._fire_stream_delta  s    4.66 	$4 	$DJJLL 	$',D$D=D"OO#OdC   	) %T+CTJJN)%**4:266 //
;; t%?FFH#}}T** (--" )78", , ) {{4(( 	Fhh4#=t?T"Uhhh		 	 	B4 		    	70066666	7 	7s   D!!
D.-D.c                 V    | j         }|	  ||           dS # t          $ r Y dS w xY wdS )z&Fire reasoning callback if registered.N)r   r   )r   rj  r  s      r   _fire_reasoning_deltazAIAgent._fire_reasoning_delta3  sN    $>4    >    
&&c                 V    | j         }|	  ||           dS # t          $ r Y dS w xY wdS )a  Notify display layer that the model is generating tool call arguments.

        Fires once per tool name when the streaming response begins producing
        tool_call / tool_use tokens.  Gives the TUI a chance to show a spinner
        or status line so the user isn't staring at a frozen screen while a
        large tool payload (e.g. a 45 KB write_file) is being generated.
        N)r   r   )r   ru  r  s      r   _fire_tool_gen_startedzAIAgent._fire_tool_gen_started<  sP     #>9    >rC  c                 :    | j         dupt          | dd          duS )z4Return True if any streaming consumer is registered.Nr#  )r   r  r   s    r   rU  zAIAgent._has_stream_consumersK  s/     &d2 Ct/66dB	
rf   r  c                *    ddl m}  || ||          S )uU   Forwarder — see ``agent.chat_completion_helpers.interruptible_streaming_api_call``.r   ) interruptible_streaming_api_callrG  )r  rI  )r   r  r  rI  s       r   !_interruptible_streaming_api_callz)AIAgent._interruptible_streaming_api_callR  s0     	SRRRRR//jQ_````rf   zFailoverReason | Nonec                 &    ddl m}  || |          S )uJ   Forwarder — see ``agent.chat_completion_helpers.try_activate_fallback``.r   )try_activate_fallback)r  rL  )r   r  rL  s      r   _try_activate_fallbackzAIAgent._try_activate_fallbackY  s'    GGGGGG$$T6222rf   c                 p    t          | dd          pg }t          | dd          }|t          |          k     S )aW  Whether a fallback provider is actually available to switch to.

        Used to gate user-facing "trying fallback..." status so we don't
        announce a fallback that will never be attempted (the user has no
        fallback chain configured).  Mirrors the early-return guard in
        ``try_activate_fallback`` (#35314, #17446).
        _fallback_chainN_fallback_indexr   )r  rq   )r   chainrP  s      r   _has_pending_fallbackzAIAgent._has_pending_fallback^  s>     /66<"/33s5zz!!rf   c                 $    ddl m}  ||           S )uJ   Forwarder — see ``agent.agent_runtime_helpers.restore_primary_runtime``.r   )restore_primary_runtime)rF  rT  )r   rT  s     r   _restore_primary_runtimez AIAgent._restore_primary_runtimel  s%    GGGGGG&&t,,,rf   	api_errorc                ,    ddl m}  || |||          S )uP   Forwarder — see ``agent.agent_runtime_helpers.try_recover_primary_transport``.r   )try_recover_primary_transport)r  r  )rF  rX  )r   rV  r  r  rX  s        r   _try_recover_primary_transportz&AIAgent._try_recover_primary_transportq  s2     	NMMMMM,,T9+cnoooorf   c                     t          | t                    sdS | D ]1}t          |t                    r|                    d          dv r dS 2dS )NFr   >   rl  rm  T)r  r  r  r   )r
  r(  s     r   _content_has_image_partsz AIAgent._content_has_image_partsx  s^    '4(( 	5 	 	D$%% $((6*:*:>Z*Z*Ztturf   rl  c                    t          | pd                              d          \  }}}d}|                    d          rZ|t          d          d                              dd          d                                         }|                    d          r|}d	d
dddd                    |d          }t          j        d|d          }	 |5  |	                    t          j        |                     d d d            n# 1 swxY w Y   n:# t          $ r- 	 t          j        |j                   n# t           $ r Y nw xY w w xY wt#          |j                  }t          |          |fS )Nrk   ,
image/jpegdata:;rm   r   zimage/z.pngz.gifz.webpz.jpg)z	image/pngz	image/gifz
image/webpr^  z	image/jpganthropic_image_F)r  suffixdelete)ro   	partitionrp   rq   r  r   r   tempfileNamedTemporaryFiler  base64	b64decoder   r   unlinkrF  r   r   )	rl  headerr  r  mime	mime_partrb  tmprh  s	            r    _materialize_data_url_for_visionz(AIAgent._materialize_data_url_for_vision  s   io2..88==4W%% 	!s7||}}-33C;;A>DDFFI##H-- ! ! 
 
 #dF

 	 )1CF[`aaa
	 2 2		&*4001112 2 2 2 2 2 2 2 2 2 2 2 2 2 2 	 	 		#(####   	 CH~~4yy$sT   D (D8D DD DD 
ED54E5
E?EEEr  c                 D   t          j        t          |pd                              d                                                    }| j                            |          }|r|S ddd                    |d          }d}t          |pd          }d }|                    d          r|                     |          \  }}d}		 d	d
l	m
}
 t          j         |
||                    }t          |t                    rt          j        |          ni }|                    d          pd                                }	n# t"          $ r}d| }	Y d }~nd }~ww xY w|r:|                                r&	 |                                 nS# t(          $ r Y nGw xY wnB# |r:|                                r'	 |                                 w # t(          $ r Y w w xY ww w xY w|	sd}	d| d|	 d}|r-t          |pd                              d          s	|d| dz  }|| j        |<   |S )Nrk   r.  r  ztool result)r  r#  rW  zDescribe everything visible in this image in thorough detail. Include any text, code, UI, data, objects, people, layout, colors, and any other notable visual information.r_  r   )vision_analyze_tool)rl  user_promptanalysiszImage analysis failed: zImage analysis failed.z[The z- attached an image. Here's what it contains:
]z@
[If you need a closer look, use vision_analyze with image_url: )hashlibsha256ro   encode	hexdigest_anthropic_image_fallback_cacher   rp   rn  tools.vision_toolsrp  asynciorunr  r  r9  r   r   r8  ri  r   )r   rl  r  	cache_keyr  
role_labelanalysis_promptvision_sourcecleanup_pathdescriptionrp  result_jsonr_  r  notes                  r   &_describe_image_for_anthropic_fallbackz.AIAgent._describe_image_for_anthropic_fallback  s   N3yB#7#7#>#>w#G#GHHRRTT	599)DD 	M %!
 
 #dF

 	
8 	 IO,,'+##G,, 	_*.*O*OP]*^*^'M<	>>>>>>!+##mYYY K 1;;0L0LTTZ,,,RTF!::j117R>>@@KK 	8 	8 	87A77KKKKKK	8   3 3 5 5  ''))))   D   3 3 5 5  ''))))   D   	32K_z__Q\___ 	Y_"!5!5!@!@!I!I 	dTadddD ;?,Y7sg   A9D? >F ?
E	EF EF 2F 
FFG0GG
GGGGc                    	 ddl m} ddlm}  |            }t	          | dd          pd                                }t	          | dd          pd                                } ||||          du S # t          $ r Y dS w xY w)	a  Return True if the active provider+model reports native vision.

        Used to decide whether to strip image content parts from API-bound
        messages (for non-vision models) or let the provider adapter handle
        them natively (for vision-capable models).

        Resolution order (see ``agent.image_routing._supports_vision_override``):
          1. ``model.supports_vision`` (top-level, single-model shortcut)
          2. ``providers.<provider>.models.<model>.supports_vision``
          3. models.dev capability lookup
        Custom/local models absent from models.dev would otherwise be
        misclassified as non-vision and have their images stripped.
        r   rp  )_lookup_supports_visionrg   rk   r   TF)ru  rq  agent.image_routingr  r  r   r   )r   rq  r  r  rg   r   s         r   _model_supports_visionzAIAgent._model_supports_vision  s    	555555CCCCCC+--Cj"55;BBDDHT7B//52<<>>E**8UC@@DHH 	 	 	55	s   A.A1 1
A?>A?c                     	 ddl m} t          | dd          pd                                } ||          }|t          |dd          S n# t          $ r Y nw xY wdS )a/  Return True if the active provider accepts list-type tool content.

        Some providers (e.g. Xiaomi MiMo) support multimodal user messages
        but reject list-type tool message content with 400 errors.  This
        checks the provider profile's ``supports_vision_tool_messages`` field.
        r   r  rg   rk   Nsupports_vision_tool_messagesT)r  r  r  r   r   )r   r  rg   profiles       r   '_provider_supports_vision_tool_messagesz/AIAgent._provider_supports_vision_tool_messages  s    	666666j"55;BBDDH**844G"w(GNNN # 	 	 	D	ts   AA 
AAc                    |                      |          s|S g }g }|D ]}t          |t                    r<|                                r'|                    |                                           Tt          |t
                    sj|                    d          }|dv rOt          |                    dd          pd                                          }|r|                    |           |dv r|                    di           }t          |t
                    r|                    dd          nt          |pd          }	|	r*|                    |                     |	|                     n|                    d           kt          |                    dd          pd                                          }|r|                    |           d	                    d
 |D                                                       }
d                    d |D                                                       }|
r	|r|
 d	| S |
r|
S |r|S dS )Nr   >   rj  
input_textrj  rk   >   rl  rm  rl  r  z:[An image was attached but no image source was available.]r;  c              3      K   | ]}||V  	d S r   r  )r  r  s     r   r$  z8AIAgent._preprocess_anthropic_content.<locals>.<genexpr>  s'      BBdTBTBBBBBBrf   rn  c              3      K   | ]}||V  	d S r   r  )r  rj  s     r   r$  z8AIAgent._preprocess_anthropic_content.<locals>.<genexpr>  s'      ??D$?4??????rf   zI[A multimodal message was converted to text for Anthropic compatibility.])	r[  r  ro   r   rv  r  r   r  r{  )r   r
  r  
text_partsimage_notesr(  r  rj  
image_datarl  r  rb  s               r   _preprocess_anthropic_contentz%AIAgent._preprocess_anthropic_content  sy   ,,W55 	N "
!# 	( 	(D$$$ ::<< 4%%djjll333dD)) HHV$$E...488FB//5266<<>> ,%%d+++444!XXk266
9CJPT9U9UpJNN5"555[^_i_omo[p[p	 e&&t'R'RS\^b'c'cdddd&&'cdddtxx++1r2288::D (!!$'''BBkBBBBBHHJJ??J?????EEGG 	+f 	+**&*** 	M 	MZZrf   c                     |p| j         }t          | dd          }|	i }|| _        |                    |          }|ddlm}  ||          }|||<   |S )zReturn the cached transport for the given (or current) api_mode.

        Lazy-initializes on first call per api_mode. Returns None if no
        transport is registered for the mode.
        _transport_cacheNr   )get_transport)r   r  r  r   agent.transportsr  )r   r   r  cacherK  r  s         r   _get_transportzAIAgent._get_transport'  sw     (4=0$77=E$)D!IIdOO9666666d##AE$Krf   api_messagesc           
      j    t           fd|D                       s|S                                  r|S t          j        |          }|D ]g}t	          |t
                    s                     |                    d          t          |                    dd          pd                    |d<   h|S )Nc              3      K   | ]A}t          |t                    o'                    |                    d                     V  BdS r
  Nr  r  r[  r   r  r  r   s     r   r$  z>AIAgent._prepare_anthropic_messages_for_api.<locals>.<genexpr>;  `       
 
 sD!!Wd&C&CCGGIDVDV&W&W
 
 
 
 
 
rf   r
  r  rW  	r&  r  r  deepcopyr  r  r  r   ro   r   r  transformedr  s   `   r   #_prepare_anthropic_messages_for_apiz+AIAgent._prepare_anthropic_messages_for_api9  s     
 
 
 
#
 
 
 
 
 	    &&(( 	  mL11 	 	Cc4(( !??	""CGGFF++5v66 C	NN rf   c           
      j    t           fd|D                       s|S                                  r|S t          j        |          }|D ]g}t	          |t
                    s                     |                    d          t          |                    dd          pd                    |d<   h|S )a  Strip native image parts when the active model lacks vision.

        Runs on the chat.completions / codex_responses paths. Vision-capable
        models pass through unchanged (provider and any downstream translator
        handle the image parts natively). Non-vision models get each image
        replaced by a cached vision_analyze text description so the turn
        doesn't fail with "model does not support image input".
        c              3      K   | ]A}t          |t                    o'                    |                    d                     V  BdS r  r  r  s     r   r$  zAAIAgent._prepare_messages_for_non_vision_model.<locals>.<genexpr>^  r  rf   r
  r  rW  r  r  s   `   r   &_prepare_messages_for_non_vision_modelz.AIAgent._prepare_messages_for_non_vision_modelU  s      
 
 
 
#
 
 
 
 
 	   &&(( 	 mL11 
	 
	Cc4(( 
 "??	""CGGFF++5v66 C	NN rf   c           	      H   t          |          s|S |                    d          pg }|                     |          s|S |                                 r|                                 s:t
                              d|t          | dd                     t          |          S t          | dd          pd	                                
                                t          | dd          pd	                                f}t          | dd          }|r<||v r8t
                              d||d	         |d
                    t          |          S |S t          |          }|dk    rt          j        d|d          S t
                              d|| j        | j                   |S )a  Return the tool message content that is safe for the active model.

        Multimodal tool results normally unwrap to OpenAI-style content parts so
        vision-capable models can inspect screenshots.  Text-only providers must
        not receive those image parts, because a rejected tool result becomes
        part of the canonical history and can make the next user turn fail before
        the agent has a chance to recover.
        r
  uT   Tool %s: provider %s does not accept list-type tool content — sending text summaryrg   rk   r   _no_list_tool_content_modelsNua   Tool %s: model %s/%s known to reject list-type tool content this session — sending text summaryr   rm   computer_usezcomputer_use returned screenshot/image content, but the active model/provider does not support image input. Switch to a vision-capable model for desktop computer use, or use browser tools for browser tasks.)r   text_summaryzWTool %s returned image content for non-vision model %s/%s; falling back to text summary)rT   r   r[  r  r  r   r   r  rU   r   r   r  r  r   rg   r   )r   ru  r_  r
  rn  no_listr  s          r   %_tool_result_content_for_active_modelz-AIAgent._tool_result_content_for_active_modelu  s    *&11 	M**Y''-2,,W55 	N&&(( 	
 ??AA 87wtZ<<  
 0777z2..4";;==CCEEw++1r88::C d$BDIIG 83'>>Ds1vs1v  
 0777N*622&&:/
 !(     	+MJ	
 	
 	
 rf   c                 $    ddl m}  ||          S )uX   Forwarder — see ``agent.conversation_compression.try_shrink_image_parts_in_messages``.r   )"try_shrink_image_parts_in_messages)r  r  )r   r  r  s      r   #_try_shrink_image_parts_in_messagesz+AIAgent._try_shrink_image_parts_in_messages  s%    UUUUUU11,???rf   c                 P   t          |t                    sdS t          | dd          pd                                                                t          | dd          pd                                f}t          | d          st                      | _        |d         r| j                            |           d}|D ]g}t          |t                    r|
                    d          dk    r2|
                    d	          }t          |t                    s]g }d}|D ]}t          |t                    sQt          |t                    r;|                                r'|                    |                                           h|
                    d
          }	|	dk    s|	dk    rd}|	dv rMt          |
                    d          pd                                          }
|
r|                    |
           |sE|rd                    |          |d	<   nd|d	<   d}i|S )u  Downgrade list-type tool messages to text summaries in-place.

        Recovery path for providers that reject list-type tool message content
        (e.g. Xiaomi MiMo's 400 "text is not set"; see issue #27344).  Walks
        ``api_messages`` for any ``role: "tool"`` message whose ``content`` is
        a list containing image parts, replaces the content with the existing
        text part(s) (or a minimal placeholder if none survive), and records
        the active (provider, model) in ``self._no_list_tool_content_models``
        so subsequent ``_tool_result_content_for_active_model`` calls in this
        session preemptively downgrade screenshots without a round-trip.

        Returns True when at least one tool message was downgraded — the
        caller (the 400 recovery branch in ``agent.conversation_loop``) uses
        this to decide whether to retry the API call with the modified
        history or surface the original error.
        Frg   rk   r   r  rm   r  r#  r
  r   rl  rm  T>   rj  r  rj  r;  uS   [image content removed — provider does not accept list-type tool message content])r  r  r  r   r   r  r  r  rE  r  r   ro   rv  r{  )r   r  rn  changedr  r
  r  	had_imager(  r  rj  s              r   )_try_strip_image_parts_from_tool_messagesz1AIAgent._try_strip_image_parts_from_tool_messages  sM   " ,-- 	5 T:r**0b7799??AAT7B''-24466
 t;<< 	603D-q6 	7-11#666 '	 '	Cc4(( CGGFOOv,E,Eggi((Ggt,,  %'JI 0 0!$-- !$,, 8 8"))$**,,777((K''5M+A+A $I222txx//5266<<>>D 0"))$///   !'Z!8!8I6 I GGrf   c                     t          | dd          pd                                dv rdS t          | dd          pd                                }d|v pd|v pd|v pd	|v pd
|v pd|v pd|v S )a  True when using an anthropic-compatible endpoint that preserves dots in model names.
        Alibaba/DashScope keeps dots (e.g. qwen3.5-plus).
        MiniMax keeps dots (e.g. MiniMax-M2.7).
        Xiaomi MiMo keeps dots (e.g. mimo-v2.5, mimo-v2.5-pro).
        OpenCode Go/Zen keeps dots for non-Claude models (e.g. minimax-m2.5-free).
        ZAI/Zhipu keeps dots (e.g. glm-4.7, glm-5.1).
        AWS Bedrock uses dotted inference-profile IDs
        (e.g. ``global.anthropic.claude-opus-4-7``,
        ``us.anthropic.claude-sonnet-4-5-20250929-v1:0``) and rejects
        the hyphenated form with
        ``HTTP 400 The provided model identifier is invalid``.
        Regression for #11976; mirrors the opencode-go fix for #5211
        (commit f77be22c), which extended this same allowlist.rg   rk   >   
minimax-cnopencode-goopencode-zenr  xiaomialibabar  minimaxTrh   	dashscopealiyuncsr  zopencode.ai/zen/zbigmodel.cnxiaomimimo.comzbedrock-runtime.)r  r   )r   bases     r   _anthropic_preserve_dotsz AIAgent._anthropic_preserve_dots  s     D*b))/R6688 =
 
 
 4j"--3::<<4 *T!*D * "T)* $	*
  4'* "T)
	
rf   c                 ,    t          | j        d          S )z2Return True when the base URL targets Qwen Portal.r  r  r   s    r   _is_qwen_portalzAIAgent._is_qwen_portal%  s    $T%9;KLLLrf   c                    t          j        |          }|s|S |D ]}t          |t                    s|                    d          }t          |t
                    r
d|dg|d<   Lt          |t                    rfg }|D ]Z}t          |t
                    r|                    d|d           0t          |t                    r|                    |           [|r||d<   |D ]}t          |t                    ro|                    d          dk    rV|                    d          }t          |t                    r*|r(t          |d         t                    rddi|d         d	<    n|S )
Nr
  rj  r   rj  r  rz   r  r   	ephemeralcache_control)r  r  r  r  r   ro   r  rv  )r   r  preparedr  r
  normalized_partsr(  s          r   _qwen_prepare_chat_messagesz#AIAgent._qwen_prepare_chat_messages)  s   =.. 	O 	6 	6Cc4(( ggi((G'3'' 6+17"C"C!DIGT** 
6 $& # 6 6D!$,, 6(//0N0NOOOO#D$// 6(//555# 6%5C	N  	 	C#t$$ H)D)D''),,gt,, I IZPRUY=Z=Z I4:K3HGBK0rf   c                    |sdS |D ]}t          |t                    s|                    d          }t          |t                    r
d|dg|d<   Lt          |t                    rfg }|D ]Z}t          |t                    r|                    d|d           0t          |t                    r|                    |           [|r||d<   |D ]}t          |t                    rp|                    d          dk    rW|                    d          }t          |t                    r*|r(t          |d         t                    rdd	i|d         d
<    dS dS )u<   In-place variant — mutates an already-copied message list.Nr
  rj  r  r  rz   r  r   r  r  )r  r  r   ro   r  rv  )r   r  r  r
  r  r(  s         r   #_qwen_prepare_chat_messages_inplacez+AIAgent._qwen_prepare_chat_messages_inplaceJ  s    	F 	6 	6Cc4(( ggi((G'3'' 
6+17"C"C!DIGT** 6#% # 6 6D!$,, 6(//0N0NOOOO#D$// 6(//555# 6%5C	N 	 	C#t$$ H)D)D''),,gt,, I IZPRUY=Z=Z I4:K3HGBK0	 	rf   c                 &    ddl m}  || |          S )uE   Forwarder — see ``agent.chat_completion_helpers.build_api_kwargs``.r   )build_api_kwargs)r  r  )r   r  r  s      r   _build_api_kwargszAIAgent._build_api_kwargsf  s'    BBBBBBl333rf   c                 H   t          | j        d          rdS t          | j        d          st          | j        d          r5	 ddlm} t	           || j                            S # t          $ r Y dS w xY w| j        pd                                	                                d	k    r-| 
                                }t          d
 |D                       S d| j        vrdS d| j        v rdS | j        pd	                                d}t          fd|D                       S )a3  Return True when reasoning extra_body is safe to send for this route/model.

        OpenRouter forwards unknown extra_body fields to upstream providers.
        Some providers/routes reject `reasoning` with 400s, so gate it to
        known reasoning-capable model families and direct Nous Portal.
        znousresearch.comTzmodels.github.air  r   github_model_reasoning_effortsFrk   r7  c              3   &   K   | ]}|o|d k    V  dS )rn  Nr  )r  opts     r   r$  z9AIAgent._supports_reasoning_extra_body.<locals>.<genexpr>  s+      <<s+se|<<<<<<rf   
openrouterzapi.mistral.ai)	z	deepseek/z
anthropic/zopenai/zx-ai/zgoogle/gemini-2zgoogle/gemma-4z
qwen/qwen3ztencent/hy3-previewzxiaomi/c              3   B   K   | ]}                     |          V  d S r   )rp   )r  r  r   s     r   r$  z9AIAgent._supports_reasoning_extra_body.<locals>.<genexpr>  s1      SS5##F++SSSSSSrf   )r[   r   r<  r  r  r   r   rg   r   r   "_lmstudio_reasoning_options_cachedr&  )r   r  optsreasoning_model_prefixesr   s       @r   _supports_reasoning_extra_bodyz&AIAgent._supports_reasoning_extra_bodyk  se    !!57IJJ 	4!$"68JKK		$T%9;RSS		LLLLLL::4:FFGGG   uuMR&&((..00J>>::<<D<<t<<<<<<t3335t3335!r((**
$
  SSSS:RSSSSSSs   "A' '
A54A5c           	         ddl }t          | dd          }|	i x}| _        | j        | j        f}|                    |          }|$|\  }}|s|                                |z
  dk     r|S 	 ddlm}  || j        | j        t          | dd                    }n# t          $ r g }Y nw xY w||                                f||<   |S )a  Probe LM Studio's published reasoning ``allowed_options`` once per
        (model, base_url). The list (e.g. ``["off","on"]`` or
        ``["off","minimal","low"]``) is needed both for the supports-reasoning
        gate and for clamping the emitted ``reasoning_effort`` so toggle-style
        models don't 400 on ``high``. Cache is keyed on (model, base_url) so
        ``/model`` swaps and base-URL changes don't reuse a stale list.
        Non-empty results are cached permanently (model capabilities don't
        change). Empty results (transient probe failure OR genuinely
        non-reasoning model) are cached with a 60-second TTL to avoid an
        HTTP round-trip on every turn while still retrying reasonably soon.
        r   N_lm_reasoning_opts_cache<   ) lmstudio_model_reasoning_optionsr   rk   )
r  r  r  r   rh   r   	monotonicr<  r  r   )r   _timer  rn  r  r  tsr  s           r   r  z*AIAgent._lmstudio_reasoning_options_cached  s    	8$??=466ED1z4=)3HD" ))B."44	JJJJJJ33
DM74B+G+G DD  	 	 	DDD	EOO--.c
s   +,B B'&B'c                 T    ddl m}  || j        |                                           S )a  Resolve a safe top-level ``reasoning_effort`` for LM Studio.

        The iteration-limit summary path calls ``chat.completions.create()``
        directly, bypassing the transport. Share the helper so the two paths
        can't drift on effort resolution and clamping.
        r   )resolve_lmstudio_effort)agent.lmstudio_reasoningr  r   r  )r   r  s     r   *_resolve_lmstudio_summary_reasoning_effortz2AIAgent._resolve_lmstudio_summary_reasoning_effort  sA     	EDDDDD&&!3355
 
 	
rf   c                    	 ddl m} n# t          $ r Y dS w xY w || j                  }|sdS | j        rt          | j        t                    rk| j                            d          du rdS t          | j                            dd                    	                                
                                }nd}|dk    rd	|v rd	}n ||vr|d
k    rd|v rd}nd|v rd}n|d         }d|iS )zDFormat reasoning payload for GitHub Models/OpenAI-compatible routes.r   r  Nr  Feffortmediumxhighhighminimallow)r<  r  r   r   r   r  r  r   ro   r   r   )r   r  supported_effortsrequested_efforts       r   #_github_models_reasoning_extra_bodyz+AIAgent._github_models_reasoning_extra_body  sZ   	HHHHHHH 	 	 	44	 ;:4:FF  	4  	(Z0Et%L%L 	($((33u<<t"%))(H==   eggeegg   (w&&65F+F+F%%6669,,:K1K1K#(  ...#+  #4Q#7 *++s   	 
c                 (    ddl m}  || ||          S )uL   Forwarder — see ``agent.chat_completion_helpers.build_assistant_message``.r   )build_assistant_message)r  r  )r   r'  r  r  s       r   _build_assistant_messagez AIAgent._build_assistant_message  s*    IIIIII&&t->NNNrf   c                 $   | j         | j        t          | d| j                  f}t          | dd          }||d         |k    r|d         S |                                 p'|                                 p|                                 }||f| _        |S )u  Return True when the active provider enforces reasoning_content echo-back.

        DeepSeek v4 thinking and Kimi / Moonshot thinking both reject replays
        of assistant tool-call messages that omit ``reasoning_content`` (refs
        #15250, #17400). Xiaomi MiMo thinking mode has the same requirement.

        Result cached on the AIAgent instance keyed by (provider, model,
        base_url); invalidated whenever ``switch_model()`` /
        ``_try_activate_fallback()`` mutate any of those. This is hot — the
        agent loop hits ~16 invocations per turn, each of which would
        otherwise re-run ~5 ``base_url_host_matches`` (and therefore
        ``urlparse``) calls under it. Caching drops the per-turn cost from
        ~5us × 16 = ~80us to <1us.
        r   _thinking_pad_cacheNr   rm   )rg   r   r  rh   _needs_deepseek_tool_reasoning_needs_kimi_tool_reasoning_needs_mimo_tool_reasoningr  )r   rn  r  r_  s       r   _needs_thinking_reasoning_padz%AIAgent._needs_thinking_reasoning_pad  s     }dj'$8I4=*Y*YZ4d;;&)s"2"2!9//11 1..001..00 	
 %(= rf   c                     | j         dv p>t          | j        d          p)t          | j        d          pt          | j        d          S )a  Return True when the current provider is Kimi / Moonshot thinking mode.

        Kimi ``/coding`` and Moonshot thinking mode both require
        ``reasoning_content`` on every assistant tool-call message; omitting
        it causes the next replay to fail with HTTP 400.

        Detection is host-driven, not model-name-driven: aggregators like
        OpenRouter that re-export Kimi/Moonshot models speak their own
        protocol and reject ``reasoning_content`` echoes. We only enable the
        kimi-reasoning replay when the request actually targets a
        kimi/moonshot endpoint or the dedicated kimi-coding provider.
        >   kimi-codingkimi-coding-cnr  zmoonshot.aizmoonshot.cn)rg   r[   rh   r   s    r   r  z"AIAgent._needs_kimi_tool_reasoning  sU     M>> C$T]NCCC$T]MBBC %T]MBB		
rf   c                     | j         pd                                }| j        pd                                }|dk    pd|v pt          | j        d          S )a  Return True when the current provider is DeepSeek thinking mode.

        DeepSeek V4 thinking mode requires ``reasoning_content`` on every
        assistant tool-call turn; omitting it causes HTTP 400 when the
        message is replayed in a subsequent API request (#15250).
        rk   deepseekzapi.deepseek.comrg   r   r   r[   rh   r   rg   r   s      r   r  z&AIAgent._needs_deepseek_tool_reasoning  se     M'R..00!r((**
" HU"H$T]4FGG	
rf   c                     | j         pd                                }| j        pd                                }|dk    p-d|v p)t          | j        d          pt          | j        d          S )aM  Return True when the current provider is Xiaomi MiMo thinking mode.

        MiMo thinking mode requires ``reasoning_content`` on every assistant
        tool-call message when replaying history; omitting it causes HTTP 400.
        Refs: https://platform.xiaomimimo.com/docs/zh-CN/usage-guide/passing-back-reasoning_content
        rk   r  mimozapi.xiaomimimo.comr  r  r   s      r   r  z"AIAgent._needs_mimo_tool_reasoning'  s|     M'R..00!r((**  FF$T]4HIIF %T]4DEE		
rf   
source_msgapi_msgc                 (    ddl m}  || ||          S )uQ   Forwarder — see ``agent.agent_runtime_helpers.copy_reasoning_content_for_api``.r   )copy_reasoning_content_for_api)rF  r  )r   r  r  r  s       r   _copy_reasoning_content_for_apiz'AIAgent._copy_reasoning_content_for_api7  s)    NNNNNN--dJHHHrf   c                 &    ddl m}  || |          S )uV   Forwarder — see ``agent.agent_runtime_helpers.reapply_reasoning_echo_for_provider``.r   )#reapply_reasoning_echo_for_provider)rF  r	  )r   r  r	  s      r   $_reapply_reasoning_echo_for_providerz,AIAgent._reapply_reasoning_echo_for_provider<  s'    SSSSSS224FFFrf   z
str | Nonec                     |                      d          }t          |t                    s| S ddlm} ddh ||          sdhz  fd|D             | d<   | S )uY  Strip Codex Responses API fields from tool_calls for strict providers.

        Providers like Mistral, Fireworks, and other strict OpenAI-compatible APIs
        validate the Chat Completions schema and reject unknown fields (call_id,
        response_item_id) with 400 or 422 errors. These fields are preserved in
        the internal message history — this method only modifies the outgoing
        API copy.

        ``extra_content`` (Gemini thought_signature) is also stripped — strict
        providers reject it with "Extra inputs are not permitted" — UNLESS the
        outgoing ``model`` is itself Gemini-family, in which case it must be
        replayed (Gemini 3 thinking models 400 without it). Defaults to
        stripping when no model is supplied.

        Creates new tool_call dicts rather than mutating in-place, so the
        original messages list retains call_id/response_item_id for Codex
        Responses API compatibility (e.g. if the session falls back to a
        Codex provider later).

        Fields stripped: call_id, response_item_id, extra_content (model-gated)
        r%  r   )!_model_consumes_thought_signaturer%  rV  extra_contentc                 ~    g | ]9}t          |t                    r fd |                                D             n|:S )c                 $    i | ]\  }}|v	||S r  r  )r  r  r  _STRIP_KEYSs      r   r  zJAIAgent._sanitize_tool_calls_for_strict_api.<locals>.<listcomp>.<dictcomp>`  s)    AAAdaA[,@,@Q,@,@,@rf   )r  r  r  )r  rs  r  s     r   rt  z?AIAgent._sanitize_tool_calls_for_strict_api.<locals>.<listcomp>_  s_     !
 !
 !
  "d##,AAAAbhhjjAAAA)+!
 !
 !
rf   )r   r  r  !agent.transports.chat_completionsr  )r  r   r%  r  r  s       @r   #_sanitize_tool_calls_for_strict_apiz+AIAgent._sanitize_tool_calls_for_strict_apiA  s    . [[..
*d++ 	NWWWWWW "450077 	:%(99K!
 !
 !
 !
 !!
 !
 !

 rf   r   r   c                *    ddl m}  || ||          S )uO   Forwarder — see ``agent.agent_runtime_helpers.sanitize_tool_call_arguments``.r   )sanitize_tool_call_argumentsr  )rF  r  )r  r   r   r  s       r   _sanitize_tool_call_argumentsz%AIAgent._sanitize_tool_call_argumentsf  s0     	MLLLLL++HVPZ[[[[rf   c                     | j         dk    S )a  Determine if tool_calls need sanitization for strict APIs.

        Codex Responses API uses fields like call_id and response_item_id
        that are not part of the standard Chat Completions schema. These
        fields must be stripped when calling any other API to avoid
        validation errors (400 Bad Request).

        Returns:
            bool: True if sanitization is needed (non-Codex API), False otherwise.
        r  )r   r   s    r   _should_sanitize_tool_callsz#AIAgent._should_sanitize_tool_callsq  s     } 111rf   r  approx_tokensr2  focus_topicrQ  r  r  c          	      2    ddl m}  || ||||||          S )u:  Forwarder — see ``agent.conversation_compression.compress_context``.

        ``force=True`` is passed by the manual ``/compress`` slash command
        so users can bypass the summary-failure cooldown after an
        auto-compress abort.  Auto-compress callers use the default
        ``force=False``.
        r   )compress_contextr  )r  r  )r   r  r  r  r2  r  rQ  r  s           r   _compress_contextzAIAgent._compress_context~  sB     	DCCCCC(N'k
 
 
 	
rf   decisionc                 8    |j         r| j        || _        dS dS dS )z?Record the first guardrail decision that should stop this turn.N)should_halt_tool_guardrail_halt_decisionr   r  s     r   _set_tool_guardrail_haltz AIAgent._set_tool_guardrail_halt  s3     	:D$F$N19D...	: 	:$N$Nrf   c                 @    |j         pd}d| d|j         d|j         dS )Nza toolzI stopped retrying z) because it hit the tool-call guardrail (z) after z repeated non-progressing attempts. The last tool result explains the blocker; the next step is to change strategy instead of repeating the same call.)ru  r   count)r   r  r#  s      r   #_toolguard_controlled_halt_responsez+AIAgent._toolguard_controlled_halt_response  sP    !-XE$ E EE E'/~E E E	
rf   function_argsfunction_resultc                    | j                             ||||          }|j        dv rt          ||          }|j        r|                     |           |S )N)r  >   haltrh  )_tool_guardrails
after_callactionrJ   r!  r$  )r   ru  r(  r)  r  r  s         r   _append_guardrail_observationz%AIAgent._append_guardrail_observation  sq     (33	 4 
 
 ?...7RRO 	4))(333rf   c                 J    |                      |           t          |          S r   )r$  rK   r#  s     r   _guardrail_block_resultzAIAgent._guardrail_block_result  s$    %%h///)(333rf   effective_task_idc                     |j         }d| _        	 t          |          s|                     ||||          d| _        S |                     ||||          d| _        S # d| _        w xY w)a=  Execute tool calls from the assistant message and append results to messages.

        Dispatches to concurrent execution only for batches that look
        independent: read-only tools may always share the parallel path, while
        file reads/writes may do so only when their target paths do not overlap.
        TF)r%  rV  rP   _execute_tool_calls_sequential_execute_tool_calls_concurrent)r   r'  r  r2  r  r%  s         r   _execute_tool_callszAIAgent._execute_tool_calls  s     '1
 !%
	*1*== ::%x1BN  %*D!!	 66!8->  %*D!!ED!))))s   &A A 	A&c                 f   ddl m}  ||                    d          |                    d          |                    d          |                    d          |                    d          |                    d          |                    d	          |                    d
          | 	  	        S )zSingle call site for delegate_task dispatch.

        New DELEGATE_TASK_SCHEMA fields only need to be added here to reach all
        invocation paths (concurrent, sequential, inline).
        r   )r<  goalcontexttoolsetstasksr   r   r   r  )	r8  r9  r:  r;  r   r   r   r  parent_agent)r=  r<  r   )r   r(  _delegate_tasks      r   _dispatch_delegate_taskzAIAgent._dispatch_delegate_task  s     	HGGGGG~""6**!%%i00"&&z22##G,,(,,-=>>%))-88"&&z22""6**

 

 

 
	
rf   function_namepre_tool_block_checkedskip_tool_request_middlewaretool_request_middleware_tracec	                 4    ddl m}	  |	| ||||||||	  	        S )u>   Forwarder — see ``agent.agent_runtime_helpers.invoke_tool``.r   )invoke_tool)rF  rD  )
r   r?  r(  r2  rP  r  r@  rA  rB  rD  s
             r   _invoke_toolzAIAgent._invoke_tool  sE     	<;;;;;{"()

 

 
	
rf        labelr6  c                    ddl }ddl}|                    d          j        }t	          d|t          |          z
            }g }|                    d          D ]\}t          |          |k    r|                    |           +|                    ||dd          }	|	                    |	p|g           ]d|z   
                    |          }
| |  |
 S )	aF  Word-wrap verbose tool output to fit the terminal width.

        Splits *text* on existing newlines and wraps each line individually,
        preserving intentional line breaks (e.g. pretty-printed JSON).
        Returns a ready-to-print string with *label* on the first line and
        continuation lines indented.
        r   N)x      rE  rn  TF)widthbreak_long_wordsbreak_on_hyphens)shutiltextwrapget_terminal_sizecolumnsr=  rq   r  rv  wrapr  r{  )rG  rj  r6  _shutil_twcols
wrap_width	out_linesraw_linewrappedr   s              r   _wrap_verbosezAIAgent._wrap_verbose  s    	!   ((33;TCKK/00
!	

4(( 	8 	8H8}}
**  ****((8:4849 # ; ;   !6XJ7777v##I..'%''''rf   c                 ,    ddl m}  || ||||          S )uH   Forwarder — see ``agent.tool_executor.execute_tool_calls_concurrent``.r   )execute_tool_calls_concurrent)agent.tool_executorr\  )r   r'  r  r2  r  r\  s         r   r5  z&AIAgent._execute_tool_calls_concurrent	  0    EEEEEE,,T3DhPacqrrrrf   c                 ,    ddl m}  || ||||          S )uH   Forwarder — see ``agent.tool_executor.execute_tool_calls_sequential``.r   )execute_tool_calls_sequential)r]  r`  )r   r'  r  r2  r  r`  s         r   r4  z&AIAgent._execute_tool_calls_sequential  r^  rf   c                 (    ddl m}  || ||          S )uJ   Forwarder — see ``agent.chat_completion_helpers.handle_max_iterations``.r   )handle_max_iterations)r  rb  )r   r  r  rb  s       r   _handle_max_iterationszAIAgent._handle_max_iterations  s)    GGGGGG$$T8^DDDrf   stream_callbackpersist_user_messagec           	      0    ddl m}  || ||||||          S )u?   Forwarder — see ``agent.conversation_loop.run_conversation``.r   run_conversation)agent.conversation_looprh  )r   r*  r  r[  r2  rd  re  rh  s           r   rh  zAIAgent.run_conversation  sD     	=<<<<<lNDXZacr  uI  J  J  	Jrf   c                 @    |                      ||          }|d         S )a  
        Simple chat interface that returns just the final response.

        Args:
            message (str): User message
            stream_callback: Optional callback invoked with each text delta during streaming.

        Returns:
            str: Final assistant response
        )rd  r  rg  )r   r   rd  r_  s       r   chatzAIAgent.chat%  s(     &&w&PP&''rf   )should_review_memoryrl  c                0    ddl m}  || |||||          S )uD   Forwarder — see ``agent.codex_runtime.run_codex_app_server_turn``.r   )run_codex_app_server_turn)r*  r  r  r2  rl  )r  rn  )r   r*  r  r  r2  rl  rn  s          r   _run_codex_app_server_turnz"AIAgent._run_codex_app_server_turn3  sQ     	BAAAAA((L`u  AI  ]n  EY  Z  Z  Z  	Zrf   )ENNNNNNNNrk   r   r   NNFFFr   Nr   rk   NNNNFNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNFFFNNNNNFr   r   r   F)r   N)NNFr   )rk   rk   rk   )FF)r   )rk   )NN)NNFFN)rF  )NNNNN)r   r   r   r   &_TOOL_CALL_ARGUMENTS_CORRUPTION_MARKERpropertyro   rh   setterr  r   r  r   r  r   r  r   r   r   r   r  r#  r4  rC  rE  rO  rW  r^  r`  re  ri  rl  rq  ry  r}  r  r  r  r  r  _STREAM_DIAG_HEADERSstaticmethodr  r  BaseExceptionr  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  r  r)  r.  r1  r5  r=  r6  r7  r8  r>  rL  rS  rZ  ra  r]  rh  r`  r  r  r  r  r  r  r   r  r  r  r  r  r  r  classmethodr  r  r  r
  r  r   r"  r%  r)  r_  rN  rY  r\  r^  ri  r|  r  compiler  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(  r*  	frozenset_VALID_API_ROLESr.  r5  r8  rC  rH  rK  rN  rF   rG   rE   r^  r`  rG  rc  re  rm  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  r  r  r,  r$  r/  r3  r8  r@  rB  rE  rU  rJ  rM  rR  rU  rY  r[  rn  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  r
  r  r  r  r  rI   r$  r'  r/  r1  r6  r>  rE  rZ  r5  r4  rc  rh  rk  ro  r  rf   r   r   r   @  s,        	O +
 #    X _;c ;d ; ; ; _; %)!% &*'+"' % "''+ #'+'+%)!,1(,7;+/(,+/&*'+%)+/"&*./3&*$($(*.+/ ,015#'#(#(!!%.2)-$)(*,/+- %MQ
 Q
Q
 Q
 	Q

 Q
 Q
 s)d"Q
 Q
 3i$Q
 Q
 Q
 Q
 s)Q
  9Q
  Q
  !Q
" #Q
$  %Q
& "%'Q
( )Q
* +Q
,  9-Q
.  9/Q
0 c1Q
2 3Q
4 &*5Q
6 #&7Q
8 &.e_9Q
: ;Q
< !)=Q
> &?Q
@ !)AQ
B $CQ
D %EQ
F #GQ
H !)IQ
J  KQ
L  (MQ
N %-OQ
P $QQ
R "SQ
T "UQ
V  (WQ
X YQ
Z sCx.[Q
\ ]Q
^  S>_Q
` tCH~.aQ
b cQ
d eQ
f gQ
h iQ
j kQ
l mQ
n oQ
p qQ
r !sQ
t !uQ
v !wQ
x yQ
| }Q
~ ,Q
@ S#XAQ
D "EQ
F #&GQ
H '*IQ
J &)KQ
L MQ
 Q
 Q
 Q
f  &   6 )-(,,0#(!Ci Ci Ci !Ci !	Ci
 $D>Ci !Ci Ci 
Ci Ci Ci CiN -1(,#(	2
 2
#D>2
 !2
 !	2
 2
 2
 2
h? ?Xc] ?^b ? ? ? ?BX X X X
  $ ,1 * * *D * * * *6T    &
$ 
 
 
 
UC UD U U U U(VS VT V V V V"U U U Uac ad a a a a&c d    (	c 	d 	 	 	 	      8 48H H4S#X/0H 
c3hH H H HF NMMMMM"tCH~ " " " \"
@cN@36@	@ @ @ @ . .# . . . \.
3] 3t 3 3 3 34 *.
 
 
 
 	

 
 
 
 tCH~&
 

 
 
 
0 *.
 
 
 
 	

 
 
 tCH~&
 

 
 
 
 	EC 	Em 	E 	E 	E 	E 	E
tCH~ 
 
 
 
2 2 2 2
) ) ) )
, ,c ,T , , , ,) )S )D ) ) ) ) 3 3s 3d 3 3 3 3>E > > > >(uUD[7I    8S U    *2
 2
Xc] 2
hsm 2
 2
 2
 2
hLD L L L L #'"&"&#
y 
y 
y 3-
y 3-	
y
 3-
y }
y 
tTz	
y 
y 
y 
y %S %T % % % \%  #'< < << 3-< 
	< < < \<*%s %t % % % %0 # (3-    \%c %d % % % %.13 13 1 1 1 1
 c d    \&H H H H H $(	C CC 4.	C
 
C C C C>bb b tCH~&	b
 
b b b b:x} : : : :
5s 5t 5 5 5 5          TdTT
T 
cT T T \T $#	 :  	
 
   6 '++/!%&*
 
 
 sm
 $C=	

 #
 sm
 
c3h
 
 
 
$*T$Z *D * * * *$	K 	Kd 	K4PT: 	K 	K 	K 	K3$t* 3QU 3 3 3 3j7d 7 7 7 7 7
9F 9Fd4j 9FX\]aXb 9F 9F 9F 9Fv-4: -$t* - - - ->5# 5 5 5 5
Sd4S>6J SX[ Shl Squvz{~  AD  |D  wE  rF S S S S
Dd38n)= D3 D[_ D D D D AS#X/Ac]A 
A A A \AF 6! 6! 6! 6! 6! \6!p +OI +O# +O +O +O \+OZ	)# 	)(3- 	) 	) 	) 	)c c    4 0) 0S#X 0 0 0 \0
C HTRUWZRZ^D\      S    \ @C @D @ @ @ \@ 
 g' g' g'g' 	g'
 g' g' g' 
g' g' g' [g'R 
3 
3 
 
 
 [
,
c3h8P 
UYZ]_bZbUc 
 
 
 


 

  }
 
c3h
 
 
 
N &*%)%)$( $6 6 6 6 	6
 6 6 6 T#s(^,6 6 6 c]6 c]6 c]6 D>6 6  
!6 6 6 6z &*	T 	T 	TcN	T 		T
 	"	T 
$	T 	T 	T 	T      \   \:SD SD$tCH~*> SD SD SD SDlBV BV BV BV BV BV BVH+ + + +B"# "$ " " " "Hhsm     %&%& 38n%& 	%&
 %& 
%& %& %& %&N    F !bjP O JC JC J J J [J  &>$sDcN?R:S &>X[ &> &> &> [&>PD    6 Zc Zc Z Z Z \ZxRT RRU RZ^ R R R R
C D    ,# $    && & &O%c O%d O% O% O% O%b#S #S #S #SJ$    .# # #Y Y YC D    ,
d 
 
 
 
$      6 d d    > !%; ; ;  #; 	;
 ; +; 
; ; ; ;z- - - -^A A A AF4S#X+? D    @ ) ) ) ) X)N N NSRUX N N N N
H H3 H# H H H H
 I I I I \I -# - - - \-  !y!a!a!abb/d38n)= /$tCQTH~BV / / / \/
 3c3h 3D 3 3 3 \3j <tCH~&<	d38n	< < < \< T d    \< GD GT G G G \G"13 13: 1 1 1 1
' ' '
 G G G GC GPS G G G \G 6 6x}hsm7S1T 6 6 6 \6 +/S SS #3-S 
	S S S S/# / / / /
S 
 
 
 
Y_          \:  s C    \0W4 WC WQU WZ] W W W W
 / / / / / \/
3 3  QU    0     "s s     .4 . . . .
 A A$ A A A \A4P P$ P P P P
 Z^ W W Ws WQU Wbe W W W W4G3 G3 G4 G G G G3 3 4    BJ JD J# JV^ J J J J
J JD J# J J J J
 FJ H H Hd Hd H H H HZ $ $ $ $ 
	$ $ $ $L! ! ! ! !F14 1 1 1 1f++3 ++4 ++ ++ ++ ++Z< < < <<J J J JF 7;26
n 
n 
n c]
n 	
n
 $N3
n  S#X/
n 
tTz	
n 
n 
n 
n$ $ $ $ $
DT 
D 
D 
D 
D   48$ 8 8 8 8'3 '3 '3 '3RC D     1c 1c 1 1 1 \1
	>S 	>T 	> 	> 	> 	>LT#s(^ LPT L L L L37s 37t 37 37 37 37j# $         
t 
 
 
 
 ?Ca a aa3;a a a a3 3-D 3PT 3 3 3 3

"t 
" 
" 
" 
"-$ - - - -
p"p47pFIp	p p p p # $    \ C E#xPT~BU<V    \:1 13 1SV 1 1 1 1f    0    "*[S *[ *[ *[ *[ *[ *[X s    $     84 D    @=s =C =TW = = = =~@ @ @ @ @ @
Hd Ht H H H HT 
$  
  
  
  
DM M M M M     BD T    84d 4t 4 4 4 4
(T (T (T (T (TT DI        D
HSM 
 
 
 
,TD[ , , , ,@O OQU O O O O
t    6
D 
 
 
 
(
 
 
 
 

D 
 
 
 
 I$ I IRV I I I I
G G# G G G G
 " "T ", "Z^ " " " \"H  	\ \ \\ 	\
 
\ \ \ \\2T 2 2 2 2 ^br{  QU  ej 
 
 
$ 
 
WZ 
lo 
  KN 
  ^b 
  ot 
 
 
 
:1F :4 : : : :

<Q 
VY 
 
 
 
  	  
   (40E 4# 4 4 4 4* *t *X[ *mp *y} * * * *.
T 
c 
 
 
 
( KO49:?UY	
 
# 
d 
WZ 
#+C=
CG
-1
 48
 5=T$sCx.=Q4R	
 _b	
 
 
 
& ( (S ( (S (s ( ( ( \(0s s$ scf sx{ s  EI s s s s
s s$ scf sx{ s  EI s s s s
Et ES ES E E E E #59.2.2J JJ J #4S>2	J
 J "(+J 'smJ 
c3hJ J J J( (C ((82D (PS ( ( ( (* &+Z Z Z Z  #	Z
 tCH~&Z Z #Z 
c3hZ Z Z Z Z Zrf   r   rk   r   Fr   queryr   r   	max_turnsr   r   
list_toolsr   save_sampleverboser   c                    t          d           t          d           |rddlm}m} ddlm}m} t          d           t          d           t          d           t          d	            |            }g }g }g }|                                D ]_\  }} ||          }|rM||f}|d
v r|                    |           0|dv r|                    |           J|                    |           `t          d           |D ]X\  }}|d         rd	                    |d                   nd}t          d|dd|d                     t          d|            Yt          d           |D ]p\  }}|d         rd	                    |d                   nd}t          d|dd|d                     t          d|            t          d|d                     qt          d           |D ]9\  }}t          d|dd|d                     t          d|d                     :t          d            |            }|                                D ]b\  }}|d         rdnd}t          d | d!| d"|d                     |d         s+t          d#d	                    |d$                               c |            }t          d%t          |           d&           t          |          D ]'}t          |          }t          d'| d(| d)           (t          d*           t          d+           t          d,           t          d-           t          d.           t          d            t          d/           t          d0           t          d            t          d1           t          d2           t          d            t          d3           t          d4           d5S d5}d5}|r1d6 |                    d7          D             }t          d8|            |r1d9 |                    d7          D             }t          d:|            |r-t          d;           t          d<           t          d=           	 t          ||||||||
|>	  	        } n*# t          $ r}!t          d?|!            Y d5}!~!d5S d5}!~!ww xY w| d@}"n| }"t          dA|"            t          dB           |                     |"          }#t          dB           t          dC           t          d           t          dD|#dE                     t          dF|#dG                     t          dHt          |#dI                               |#dJ         r3t          dK           t          dL           t          |#dJ                    |	rt#          t%          j                              d5dM         }$dN|$ dO}%|                     |#dI         |"|#dE                   }&|&t+          j                                                    ||#dE         |"dP}	 t1          |%dQdRS          5 }'|'                    t5          j        |dTdUV                     d5d5d5           n# 1 swxY w Y   t          dW|%            n)# t8          $ r}!t          dX|!            Y d5}!~!nd5}!~!ww xY wt          dY           d5S )Za  
    Main function for running the agent directly.

    Args:
        query (str): Natural language query for the agent. Defaults to Python 3.13 example.
        model (str): Model name to use (OpenRouter format: provider/model). Defaults to anthropic/claude-sonnet-4.6.
        api_key (str): API key for authentication. Uses OPENROUTER_API_KEY env var if not provided.
        base_url (str): Base URL for the model API. Defaults to https://openrouter.ai/api/v1
        max_turns (int): Maximum number of API call iterations. Defaults to 10.
        enabled_toolsets (str): Comma-separated list of toolsets to enable. Supports predefined
                              toolsets (e.g., "research", "development", "safe").
                              Multiple toolsets can be combined: "web,vision"
        disabled_toolsets (str): Comma-separated list of toolsets to disable (e.g., "terminal")
        list_tools (bool): Just list available tools and exit
        save_trajectories (bool): Save conversation trajectories to JSONL files (appends to trajectory_samples.jsonl). Defaults to False.
        save_sample (bool): Save a single trajectory sample to a UUID-named JSONL file for inspection. Defaults to False.
        verbose (bool): Enable verbose logging for debugging. Defaults to False.
        log_prefix_chars (int): Number of characters to show in log previews for tool calls/responses. Defaults to 20.

    Toolset Examples:
        - "research": Web search, extract, crawl + vision tools
    u   🤖 AI Agent with Tool Callingz2==================================================r   )get_all_tool_namesget_available_toolsets)get_all_toolsetsget_toolset_infou    📋 Available Tools & Toolsets:z2--------------------------------------------------u'   
🎯 Predefined Toolsets (New System):z(---------------------------------------->   webvisioncreativeterminalrv  >   rr  research
full_stackdevelopmentcontent_creationu   
📌 Basic Toolsets:resolved_toolsz, r&  u     • r  z - r  z    Tools: u5   
📂 Composite Toolsets (built from other toolsets):includesz    Includes: z    Total tools: 
tool_countu!   
🎭 Scenario-Specific Toolsets:20u3   
📦 Legacy Toolsets (for backward compatibility):	availableu   ✅u   ❌z  r  r  z    Requirements: requirementsu   
🔧 Individual Tools (z available):u     📌 z (from rv   u   
💡 Usage Examples:z  # Use predefined toolsetszR  python run_agent.py --enabled_toolsets=research --query='search for Python news'zN  python run_agent.py --enabled_toolsets=development --query='debug this code'zP  python run_agent.py --enabled_toolsets=safe --query='analyze without terminal'z  # Combine multiple toolsetszM  python run_agent.py --enabled_toolsets=web,vision --query='analyze website'z  # Disable toolsetszQ  python run_agent.py --disabled_toolsets=terminal --query='no command execution'z&  # Run with trajectory saving enabledzF  python run_agent.py --save_trajectories --query='your question here'Nc                 6    g | ]}|                                 S r  r   r  rK  s     r   rt  zmain.<locals>.<listcomp>  s      P P Pq P P Prf   r]  u   🎯 Enabled toolsets: c                 6    g | ]}|                                 S r  r  r  s     r   rt  zmain.<locals>.<listcomp>  s     !R!R!R!''))!R!R!Rrf   u   🚫 Disabled toolsets: u   💾 Trajectory saving: ENABLEDu:      - Successful conversations → trajectory_samples.jsonlu7      - Failed conversations → failed_trajectories.jsonl)	rh   r   r   r   r   r   r   r   r   u    ❌ Failed to initialize agent: zTell me about the latest developments in Python 3.13 and what new features developers should know about. Please search for current information and try it out.u   
📝 User Query: z3
==================================================u   📋 CONVERSATION SUMMARYu   ✅ Completed: r  u   📞 API Calls: 	api_callsu   💬 Messages: r  r  u   
🎯 FINAL RESPONSE:z------------------------------r_   sample_r-  )conversations	timestampr   r  rz  wr.  r/  Fr5  )r  r6  u"   
💾 Sample trajectory saved to: u   
⚠️ Failed to save sample: u    
👋 Agent execution completed!)rK  model_toolsr  r  r:  r  r  r  rv  r{  rq   sortedr%   r  r   r  rh  ro   uuiduuid4r  r   r=  r<  openr  r  r  r   )(rz  r   r   rh   r{  r   r   r|  r   r}  r~  r   r  r  r  r  all_toolsetsbasic_toolsetscomposite_toolsetsscenario_toolsetsrF  toolsetr  rA  	tools_strincludes_strlegacy_toolsetsrt  	all_toolsru  enabled_toolsets_listdisabled_toolsets_listagentr  r  r_  	sample_idsample_filenamer  fs(                                           r   mainr  @  s   H 

+,,,	(OOO  PJJJJJJJJ????????0111h 	8999h'')) )//11 		4 		4MD'##D))D 4tQQQ"))%0000fff&--e4444%,,U333 	&'''( 	- 	-JD$=ABR=S_		$'7"8999Y_I<4<<<tM':<<===+	++,,,, 	FGGG, 	< 	<JD$:>z:JV499T*%5666PVL<4<<<tM':<<===1<11222:d<&8::;;;; 	2333+ 	< 	<JD$<4<<<tM':<<===:d<&8::;;;; 	DEEE0022)//11 	N 	NJD$";/:UUUF=v====](;==>>>$ NL499T.5I+J+JLLMMM '&((	F#i..FFFGGG	** 	: 	:I*955G8I88g8889999&'''+,,,bccc^___`aaad-...]^^^d$%%%abbbd6777VWWW !! A P P4D4J4J34O4O P P P?(=??@@@ C!R!R5F5L5LS5Q5Q!R!R!RA)?AABBB I/000JKKKGHHH$24/#-

 

 

    444555
 }b 	

 
	
,

,
,---	/ ##J//F	/	
%&&&	(OOO	
1F;/
1
1222	
2VK0
2
2333	
5Cz 233
5
5666 (&'''hf%&'''  :
%%bqb)	4I444 88:;
 

 (!1133,
 
	:osW=== I
5uQGGGHHHI I I I I I I I I I I I I I I IIIJJJJ 	: 	: 	:8Q8899999999	: 

-.....sT   R! !
S+SSZ2 +ZZ2 ZZ2 ZZ2 2
[<[[__main__)Nrk   Nrk   r   NNFFFFr   )r   hermes_bootstrapModuleNotFoundErrorrz  rg  r  rt  r  r;  	getLoggerr   r   r   r  rZ  re  r  rG  r  typingr   r   r   r   r   pathlibr   typesr	   hermes_constantsr
   ro   r   agent.process_bootstrapr   r   r   agent.iteration_budgetr   hermes_cli.env_loaderr   hermes_cli.timeoutsr    r!   _hermes_home__file__parent_project_env_loaded_env_paths	_env_pathr  r  r$   r%   r&   r'   tools.terminal_toolr(   tools.interruptr)   rJ  tools.browser_toolr*   agent.memory_managerr+   agent.error_classifierr,   agent.redactr-   r;  r.   r/   agent.usage_pricingr0   agent.context_compressorr1   agent.retry_utilsr2   agent.prompt_builderr3   r4   r5   r6   r7   r8   r9   agent.message_sanitizationr:   r;   r<   r=   r>   r?   r@   rA   rB   rC   rD   agent.codex_responses_adapterrE   rX  rF   rR  rG   rU  rH   agent.tool_guardrailsrI   rJ   rK    agent.tool_result_classificationrL   rd  rM   agent.trajectoryrN   rO   r  agent.tool_dispatch_helpersrP   rQ   rR   rS   rT   rU   rV   rW   rX   rY   utilsrZ   r[   r\   r]   r^   _MAX_TOOL_WORKERSEvent_openrouter_prewarm_donery   r  re   r  rt   r~   r   r   r   r   r  fireFirer  rf   r   <module>r     s   .	 	 	 	
 	D	      		8	$	$ 				 				 



        , , , , , , , , , , , ,             ! ! ! ! ! ! , , , , , ,C HSM    <         
 3 2 2 2 2 2 5 4 4 4 4 4       
   tH~~$v-&&<\ZZZ  K& G G	:IFFFFG KKIJJJ            + * * * * * ; ; ; ; ; ; . . . . . . 2 1 1 1 1 1 1 1 1 1 1 1 . . . . . .        0 / / / / / 6 6 6 6 6 6 . . . . . .                8 7 7 7 7 7                                             
                                      C  C  C  C  C  C  C  C  C  C  C  C  C  C   +9?,,   T     %) #  #  #Tz #47$J #	 #  #  #  #F
d 
 
 
 
$
 $
 $
 $
 $
	 $
 $
 $
N~KZ ~KZ ~KZ ~KZ ~KZ ~KZ ~KZ ~KZBX  !#S/ S/S/S/ S/ 	S/
 S/ S/ S/ S/ S/ S/ S/ S/ S/ S/ S/l zKKKDIdOOOOO s   	 