
    iFY                        U d 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	m
Z
 ddlmZmZ ddlmZmZ  ej        e          Zda ej                    Z ej                    Zd Zd Zd Z e             	 dd	lmZ  e             n(# e$ r Ze                     d
e           Y dZ[ndZ[ww xY w	 ddl!m"Z"  e"             n(# e$ r Ze                     de           Y dZ[ndZ[ww xY w ej#                    Z$ee%e%f         e&d<    ej'                    Z(ee%e)f         e&d<   g a*ee%         e&d<   ddgdgdgdgdgg dg ddgg dg ddgdZ+	 	 	 d=dee%         dee%         d e,d!eee%ef                  fd"Z-h d#Z.d$d%hZ/d&e%d'ee%ef         d!ee%ef         fd(Z0d)e%fd*Z1d>d)e%d+e,fd,Z2d)e%fd-Z3	 	 	 	 	 	 d?d.e%d/ee%ef         d0e	e%         d1e	e%         d2e	e%         d3e	e%         d4e	ee%                  d5e,d!e%fd6Z4d!ee%         fd7Z5d&e%d!e	e%         fd8Z6d!ee%e)f         fd9Z7d!ee%e,f         fd:Z8d>d;e,d!e
ee%         ee)         f         fd<Z9dS )@a  
Model Tools Module

Thin orchestration layer over the tool registry. Each tool file in tools/
self-registers its schema, handler, and metadata via tools.registry.register().
This module triggers discovery (by importing all tool modules), then provides
the public API that run_agent.py, cli.py, batch_runner.py, and the RL
environments consume.

Public API (signatures preserved from the original 2,400-line version):
    get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode) -> list
    handle_function_call(function_name, function_args, task_id, user_task) -> str
    TOOL_TO_TOOLSET_MAP: dict          (for batch_runner.py)
    TOOLSET_REQUIREMENTS: dict         (for cli.py, doctor.py)
    get_all_tool_names() -> list
    get_toolset_for_tool(name) -> str
    get_available_toolsets() -> dict
    check_toolset_requirements() -> dict
    check_tool_availability(quiet) -> tuple
    N)DictAnyListOptionalTuple)discover_builtin_toolsregistry)resolve_toolsetvalidate_toolsetc                      t           5  t          t                                          rt          j                    at          cddd           S # 1 swxY w Y   dS )a^  Return a long-lived event loop for running async tool handlers.

    Using a persistent loop (instead of asyncio.run() which creates and
    *closes* a fresh loop every time) prevents "Event loop is closed"
    errors that occur when cached httpx/AsyncOpenAI clients attempt to
    close their transport on a dead loop during garbage collection.
    N)_tool_loop_lock
_tool_loop	is_closedasyncionew_event_loop     3/home/agentuser/.hermes/hermes-agent/model_tools.py_get_tool_loopr   ,   s     
  !5!5!7!7 /11J                 s   :AAAc                      t          t          dd          } | |                                 r3t          j                    } t          j        |            | t          _        | S )u  Return a persistent event loop for the current worker thread.

    Each worker thread (e.g., delegate_task's ThreadPoolExecutor threads)
    gets its own long-lived loop stored in thread-local storage.  This
    prevents the "Event loop is closed" errors that occurred when
    asyncio.run() was used per-call: asyncio.run() creates a loop, runs
    the coroutine, then *closes* the loop — but cached httpx/AsyncOpenAI
    clients remain bound to that now-dead loop and raise RuntimeError
    during garbage collection or subsequent use.

    By keeping the loop alive for the thread's lifetime, cached clients
    stay valid and their cleanup runs on a live loop.
    loopN)getattr_worker_thread_localr   r   r   set_event_loopr   )r   s    r   _get_worker_loopr   ;   sV     '66D|t~~''|%''t$$$$(!Kr   c                 .   	 t          j                    }n# t          $ r d}Y nw xY w|r|                                rmddl}|j                            d          5 }|                    t           j        |           }|	                    d          cddd           S # 1 swxY w Y   t          j                    t          j                    ur#t                      }|                    |           S t                      }|                    |           S )a  Run an async coroutine from a sync context.

    If the current thread already has a running event loop (e.g., inside
    the gateway's async stack or Atropos's event loop), we spin up a
    disposable thread so asyncio.run() can create its own loop without
    conflicting.

    For the common CLI path (no running loop), we use a persistent event
    loop so that cached async clients (httpx / AsyncOpenAI) remain bound
    to a live loop and don't trigger "Event loop is closed" on GC.

    When called from a worker thread (parallel tool execution), we use a
    per-thread persistent loop to avoid both contention with the main
    thread's shared loop AND the "Event loop is closed" errors caused by
    asyncio.run()'s create-and-destroy lifecycle.

    This is the single source of truth for sync->async bridging in tool
    handlers. The RL paths (agent_loop.py, tool_context.py) also provide
    outer thread-pool wrapping as defense-in-depth, but each handler is
    self-protecting via this function.
    Nr      )max_workersi,  )timeout)r   get_running_loopRuntimeError
is_runningconcurrent.futuresfuturesThreadPoolExecutorsubmitrunresult	threadingcurrent_threadmain_threadr   run_until_completer   )coror   
concurrentpoolfutureworker_loop	tool_loops          r   
_run_asyncr3   Q   sV   ,'))     .!! .!!!!22q2AA 	.T[[d33F===--	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. !!)>)@)@@@&((--d333  I''---s    %%6B  B$'B$)discover_mcp_toolszMCP tool discovery failed: %s)discover_pluginszPlugin discovery failed: %sTOOL_TO_TOOLSET_MAPTOOLSET_REQUIREMENTS_last_resolved_tool_names
web_searchweb_extractterminalvision_analyzemixture_of_agentsimage_generate)skills_list
skill_viewskill_manage)
browser_navigatebrowser_snapshotbrowser_clickbrowser_typebrowser_scrollbrowser_backbrowser_pressbrowser_get_imagesbrowser_visionbrowser_consolecronjob)
rl_list_environmentsrl_select_environmentrl_get_current_configrl_edit_configrl_start_trainingrl_check_statusrl_stop_trainingrl_get_resultsrl_list_runsrl_test_inference)	read_file
write_filepatchsearch_filestext_to_speech)	web_toolsterminal_toolsvision_tools	moa_toolsimage_toolsskills_toolsbrowser_toolscronjob_toolsrl_tools
file_tools	tts_toolsFenabled_toolsetsdisabled_toolsets
quiet_modereturnc           	      N   t                      }| | D ]}t          |          rSt          |          }|                    |           |s,t	          d| d|rd                    |          nd            d|t          v rMt          |         }|                    |           |s(t	          d| dd                    |                      |st	          d|            ϐn?|rdd	lm}  |            D ]$}|                    t          |                     %|D ]}t          |          rSt          |          }|	                    |           |s,t	          d
| d|rd                    |          nd            d|t          v rMt          |         }|	                    |           |s(t	          d| dd                    |                      |st	          d|            n5dd	lm}  |            D ]$}|                    t          |                     %t          j        ||          }	d |	D             }
d|
v rdddlm}m} ||
z  } ||          }t          |	          D ]<\  }}|                    di                               d          dk    r
d|d|	|<    n=d|
v rddh|
z  }|st          |	          D ]z\  }}|                    di                               d          dk    rH|d                             dd          }|                    dd          }di |d         d|id|	|<    n{|sS|	rBd |	D             }t	          dt%          |	           dd                    |                      nt	          d           d |	D             a|	S )a  
    Get tool definitions for model API calls with toolset-based filtering.

    All tools must be part of a toolset to be accessible.

    Args:
        enabled_toolsets: Only include tools from these toolsets.
        disabled_toolsets: Exclude tools from these toolsets (if enabled_toolsets is None).
        quiet_mode: Suppress status prints.

    Returns:
        Filtered list of OpenAI-format tool definitions.
    Nu   ✅ Enabled toolset 'z': z, zno toolsu   ✅ Enabled legacy toolset 'u   ⚠️  Unknown toolset: r   )get_all_toolsetsu   🚫 Disabled toolset 'u   🚫 Disabled legacy toolset 'quietc                 *    h | ]}|d          d         S functionnamer   .0ts     r   	<setcomp>z'get_tool_definitions.<locals>.<setcomp>  s!    JJJaAjM&1JJJr   execute_code)SANDBOX_ALLOWED_TOOLSbuild_execute_code_schemarq   rr   )typerq   rB   r9   r:   description zV For simple information retrieval, prefer web_search or web_extract (faster, cheaper).c                 *    g | ]}|d          d         S rp   r   rs   s     r   
<listcomp>z(get_tool_definitions.<locals>.<listcomp>3  s!    HHHA!J-/HHHr   u   🛠️  Final tool selection (z	 tools): u<   🛠️  No tools selected (all filtered out or unavailable)c                 *    g | ]}|d          d         S rp   r   rs   s     r   r~   z(get_tool_definitions.<locals>.<listcomp>9  s!     O O O1:v!6 O O Or   )setr   r
   updateprintjoin_LEGACY_TOOLSET_MAPtoolsetsrl   difference_updater	   get_definitionstools.code_execution_toolrx   ry   	enumerategetreplacelenr8   )rg   rh   ri   tools_to_includetoolset_nameresolvedlegacy_toolsrl   ts_namefiltered_toolsavailable_tool_namesrx   ry   sandbox_enableddynamic_schemaitdweb_tools_availabledesc
tool_namess                       r   get_tool_definitionsr      s   &  EE#, 	F 	FL-- F*<88 ''111! vt,ttZbCr499XCVCVCVhrttuuu!4442<@ ''555! eccc$))T`JaJaccddd! FDlDDEEE	F 
 >------'')) 	> 	>G##OG$<$<====- 	F 	FL-- F*<88 228<<<! xvLvv\dEtTYYxEXEXEXjtvvwww!4442<@ 22<@@@! ge<eeDIIVbLcLceefff! FDlDDEEE	F 	.-----'')) 	> 	>G##OG$<$<==== -.>jQQQN KJ>JJJ ---^^^^^^^^/2FF22?CC~.. 	 	EArvvj"%%))&11^CC-7^$T$Tq! D 111+];>RR" 	">22  266*b))--f559KKKj>--mR@@D<<p D
 !+$Kr*~$K}d$K$K) )N1% E L  R 	RHHHHHJiC4G4GiiRVR[R[\fRgRgiijjjjPQQQ !P O O O Or   >   todomemorydelegate_tasksession_searchrW   rZ   	tool_nameargsc                    |rt          |t                    s|S t          j        |           }|s|S |                    d          pi                     d          }|s|S |                                D ]d\  }}t          |t                    s|                    |          }|s3|                    d          }|sKt          ||          }||ur|||<   e|S )aA  Coerce tool call arguments to match their JSON Schema types.

    LLMs frequently return numbers as strings (``"42"`` instead of ``42``)
    and booleans as strings (``"true"`` instead of ``true``).  This compares
    each argument value against the tool's registered JSON Schema and attempts
    safe coercion when the value is a string but the schema expects a different
    type.  Original values are preserved when coercion fails.

    Handles ``"type": "integer"``, ``"type": "number"``, ``"type": "boolean"``,
    and union types (``"type": ["integer", "string"]``).
    
parameters
propertiesrz   )
isinstancedictr	   
get_schemar   itemsstr_coerce_value)	r   r   schemar   keyvalueprop_schemaexpectedcoerceds	            r   coerce_tool_argsr   N  s     z$--  ++F **\**0b55lCCJ jjll    
U%%% 	 nnS)) 	??6** 	x00%DIKr   r   c                     t          |t                    r|D ]}t          | |          }|| ur|c S | S |dv rt          | |dk              S |dk    rt	          |           S | S )zAttempt to coerce a string *value* to *expected_type*.

    Returns the original string when coercion is not applicable or fails.
    )integernumberr   )integer_onlyboolean)r   listr   _coerce_number_coerce_boolean)r   expected_typeru   r(   s       r   r   r   u  s    
 -&&  	 	A"5!,,FU"" #---e=I3MOOOO	!!u%%%Lr   r   c                    	 t          |           }n# t          t          f$ r | cY S w xY w||k    s&|t          d          k    s|t          d          k    r|S |t          |          k    rt          |          S |r| S |S )zFTry to parse *value* as a number.  Returns original string on failure.infz-inf)float
ValueErrorOverflowErrorint)r   r   fs      r   r   r     s    %LL&    	AvveEll""a5==&8&8CFF{{1vv Hs    ((c                 r    |                                                                  }|dk    rdS |dk    rdS | S )zGTry to parse *value* as a boolean.  Returns original string on failure.trueTfalseF)striplower)r   lows     r   r   r     s<    
++--



C
f}}t
g~~uLr   function_namefunction_argstask_idtool_call_id
session_id	user_taskenabled_toolsskip_pre_tool_call_hookc           	      `   t          | |          }	 | t          v rt          j        d|  di          S |sKd}	 ddlm}	  |	| ||pd|pd|pd          }n# t          $ r Y nw xY w|t          j        d|id	          S n/	 dd
lm}
  |
d| ||pd|pd|pd           n# t          $ r Y nw xY w| t          vr%	 ddl	m
}  ||pd           n# t          $ r Y nw xY w| dk    r$||nt          }t          j        | |||          }nt          j        | |||          }	 dd
lm}
  |
d| |||pd|pd|pd           n# t          $ r Y nw xY w|S # t          $ rQ}d|  dt          |           }t                              |           t          j        d|id	          cY d}~S d}~ww xY w)a  
    Main function call dispatcher that routes calls to the tool registry.

    Args:
        function_name: Name of the function to call.
        function_args: Arguments for the function.
        task_id: Unique identifier for terminal/browser session isolation.
        user_task: The user's original task (for browser_snapshot context).
        enabled_tools: Tool names enabled for this session.  When provided,
                       execute_code uses this list to determine which sandbox
                       tools to generate.  Falls back to the process-global
                       ``_last_resolved_tool_names`` for backward compat.

    Returns:
        Function result as a JSON string.
    errorz" must be handled by the agent loopNr   )get_pre_tool_call_block_messager|   )r   r   r   F)ensure_ascii)invoke_hookpre_tool_call)r   r   r   r   r   )notify_other_tool_calldefaultrw   )r   r   )r   r   post_tool_call)r   r   r(   r   r   r   zError executing z: )r   _AGENT_LOOP_TOOLSjsondumpshermes_cli.pluginsr   	Exceptionr   _READ_SEARCH_TOOLStools.file_toolsr   r8   r	   dispatchr   loggerr   )r   r   r   r   r   r   r   r   block_messager   r   r   r   r(   e	error_msgs                   r   handle_function_callr     s   6 %]MBBMSD---:w=(\(\(\]^^^
 ' 	+/M
NNNNNN ? ?!!#Mr)/R!-!3! ! !     (z7M":OOOO )
::::::#+&#Mr)/R!-!3        
  222CCCCCC&&w';)<<<<    N** 0=/HmmNgO&}-  FF &}#  F	666666K '"2%+)/R      	 	 	D	  D D D@}@@A@@	Yz7I.UCCCCCCCCCDs   !E E A E 
A# E "A##E  E B  E  
B-*E ,B--E :C E 
CE CAE !E  ?E  
E
E EE 
F-AF("F-(F-c                  (    t          j                    S )z!Return all registered tool names.)r	   get_all_tool_namesr   r   r   r   r     s    &(((r   c                 *    t          j        |           S )z%Return the toolset a tool belongs to.)r	   get_toolset_for_tool)r   s    r   r   r   !  s    (333r   c                  (    t          j                    S )z0Return toolset availability info for UI display.)r	   get_available_toolsetsr   r   r   r   r   &  s    *,,,r   c                  (    t          j                    S )z>Return {toolset: available_bool} for every registered toolset.)r	   check_toolset_requirementsr   r   r   r   r   +  s    .000r   rn   c                 ,    t          j        |           S )z.Return (available_toolsets, unavailable_info).rm   )r	   check_tool_availabilityrm   s    r   r   r   0  s    +%8888r   )NNF)F)NNNNNF):__doc__r   r   loggingr)   typingr   r   r   r   r   tools.registryr   r	   r   r
   r   	getLogger__name__r   r   Lockr   localr   r   r   r3   tools.mcp_toolr4   r   r   debugr   r5   get_tool_to_toolset_mapr6   r   __annotations__get_toolset_requirementsr7   r   r8   r   boolr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>r      s]    *        3 3 3 3 3 3 3 3 3 3 3 3 3 3 ; ; ; ; ; ; ; ; 6 6 6 6 6 6 6 6		8	$	$ 
 ).""&y((     ,,. ,. ,.f     5111111 5 5 5
LL0!4444444453333333 3 3 3
LL.222222223 'Gh&F&H&H T#s(^ H H H(I(I(K(K d39o K K K (* 49 ) ) ) .!l%&%&$%AAA    [   GFF"#-  > #'#'w w3iwCyw w 
$sCx.	w w w wD JII !>2 $ $4S> $d38n $ $ $ $N    ( # T    $3     ""& $#)-$)pD pDpDS>pD c]pD 3-	pD
 pD }pD DI&pD "pD 	pD pD pD pDn)DI ) ) ) )
4C 4HSM 4 4 4 4
-S$Y - - - -
1DdO 1 1 1 1
9 94 9E$s)T$Z:O4P 9 9 9 9 9 9s0   6B B,B''B,0C C&C!!C&