+
    iY                     $  a  R8 t70 t R t^ RIt^ RIt^ RIt^ RIt^ RIHtHtH	t	H
t
Ht ^ RIHt ^ RIHtHt ]P"                  ! ]4      tRs]P*                  ! 4       t]P.                  ! 4       tR tR tR tR t]! 4         ^ R	IHt ]! 4         ^ RI"H#t# ]#! 4        ]PH                  ! 4       t%] ^ k ]PL                  ! 4       t'] ^k . s(] ^k RRR.RR.RR.RR.RR.R. R9OR. R:ORR.R. R;OR. R<OR R!./t)R=R" R# llt*0 R>mt+RR0t,R$ R% lt-R& R' lt.R?R( R) llt/R* R+ lt0R@R, R- llt1R. R/ lt2R0 R1 lt3R2 R3 lt4R4 R5 lt5R?R6 R7 llt6R#   ] d   t ]PC                  R
] 4        Rt A LRt A ii ; i  ] d   t ]PC                  R] 4        Rt A LRt A ii ; i)Aa  
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)registry)resolve_toolsetvalidate_toolsetc                     \         ;_uu_ 4        \        e   \        P                  4       '       d   \        P                  ! 4       s\        uuRRR4       #   + '       g   i     R# ; i)aJ  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     (/home/ubuntu/hermes-agent/model_tools.py_get_tool_loopr   ,   s:     
!5!5!7!7 //1J 
s   "AAA(	c                     \        \        RR4      p V e   V P                  4       '       d7   \        P                  ! 4       p \        P
                  ! V 4       V \        n        V # )uw  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   ;   sN     '6D|t~~''%%'t$$(!Kr   c                D    \         P                  ! 4       pV'       d|   VP                  4       '       df   ^ RIpVP
                  P                  ^R7      ;_uu_ 4       pVP                  \         P                  V 4      pVP                  RR7      uuRRR4       # \        P                  ! 4       \        P                  ! 4       Jd   \        4       pVP                  V 4      # \        4       pVP                  V 4      #   \         d    Rp Li ; i  + '       g   i     L; i)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.
N)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_asyncr0   Q   s    ,'') !!!22q2AAT[[d3F===- BA !)>)>)@@&(--d33 I''--)   BAs   C< 2D<DDD	c                     . ROp ^ RI pV  F  p VP                  V4       K  	  R#   \         d"   p\        P	                  RY#4        Rp?KB  Rp?ii ; i)zImport all tool modules to trigger their registry.register() calls.

Wrapped in a function so import errors in optional tools (e.g., fal_client
not installed) don't prevent the rest from loading.
Nz#Could not import tool module %s: %s)ztools.web_toolsztools.terminal_toolztools.file_toolsztools.vision_toolsztools.mixture_of_agents_toolztools.image_generation_toolztools.skills_toolztools.skill_manager_toolztools.browser_toolztools.cronjob_toolsztools.rl_training_toolztools.tts_toolztools.todo_toolztools.memory_toolztools.session_search_toolztools.clarify_toolztools.code_execution_toolztools.delegate_toolztools.process_registryztools.send_message_toolztools.homeassistant_tool)	importlibimport_module	Exceptionloggerwarning)_modulesr2   mod_namees       r   _discover_toolsr:      sR    H0 	O##H-   	ONN@(NN	Os   &AAA)discover_mcp_toolszMCP tool discovery failed: %s)discover_pluginszPlugin discovery failed: %s	web_tools
web_searchweb_extractterminal_toolsterminalvision_toolsvision_analyze	moa_toolsmixture_of_agentsimage_toolsimage_generateskills_toolsbrowser_toolscronjob_toolscronjobrl_tools
file_tools	read_filesearch_files	tts_toolstext_to_speechc                    V ^8  d   QhR\         \        ,          R\         \        ,          R\        R\         \        \        \        3,          ,          /# )   enabled_toolsetsdisabled_toolsets
quiet_modereturn)r   strboolr   r   )formats   "r   __annotate__r[      sK     w w3iwCyw w 
$sCx.	wr   c           	        \        4       pV e   V  F  p\        V4      '       dS   \        V4      pVP                  V4       V'       g-   \	        RT RV'       d   RP                  V4      MR 24       Kd  Kf  V\        9   dK   \        V,          pVP                  V4       V'       g#   \	        RV RRP                  V4       24       K  K  V'       d   K  \	        RV 24       K  	  EM?V'       Ed	   ^ RIHp V! 4        F  pVP                  \        V4      4       K  	  V F  p\        V4      '       dS   \        V4      pVP                  V4       V'       g-   \	        RT RV'       d   RP                  V4      MR 24       Kd  Kf  V\        9   dK   \        V,          pVP                  V4       V'       g#   \	        R	V RRP                  V4       24       K  K  V'       d   K  \	        RV 24       K  	  M.^ RIHp V! 4        F  pVP                  \        V4      4       K  	  \        P                  ! W2R
7      p	V	 U
u0 uF  qR,          R,          kK  	  pp
RV9   d_   ^ RIHpHp W,          pV! V4      p\        V	4       F7  w  ppVP!                  R/ 4      P!                  R4      R8X  g   K.  RRRV/V	V&    M	  RV9   d   RR0V,          pV'       g   \        V	4       Fo  w  ppVP!                  R/ 4      P!                  R4      R8X  g   K.  VR,          P!                  RR4      pVP#                  RR4      pRRR/ VR,          CRV/C/V	V&    M	  V'       g\   V	'       dI   V	 U
u. uF  qR,          R,          NK  	  pp
\	        R\%        V	4       RRP                  V4       24       M\	        R4       V	 U
u. uF  qR,          R,          NK  	  up
sV	# u up
i u up
i u up
i )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.
u   ✅ Enabled toolset 'z': z, zno toolsu   ✅ Enabled legacy toolset 'u   ⚠️  Unknown toolset: )get_all_toolsetsu   🚫 Disabled toolset 'u   🚫 Disabled legacy toolset 'quietfunctionnameexecute_code)SANDBOX_ALLOWED_TOOLSbuild_execute_code_schematypebrowser_navigater>   r?   description zV For simple information retrieval, prefer web_search or web_extract (faster, cheaper).u   🛠️  Final tool selection (z	 tools): u<   🛠️  No tools selected (all filtered out or unavailable))setr	   r   updateprintjoin_LEGACY_TOOLSET_MAPtoolsetsr]   difference_updater   get_definitionstools.code_execution_toolrc   rd   	enumerategetreplacelen_last_resolved_tool_names)rT   rU   rV   tools_to_includetoolset_nameresolvedlegacy_toolsr]   ts_namefiltered_toolstavailable_tool_namesrc   rd   sandbox_enableddynamic_schemaitdweb_tools_availabledesc
tool_namess   &&&                  r   get_tool_definitionsr      s   &  E#,L--*<8 ''1!1,sZb499XCVhrBstu "!442<@ ''5!8c$))T`JaIbcd " "z5l^DE - 
	-')G##OG$<= * .L--*<8 228<!3L>\dTYYxEXjtDuvw "!442<@ 22<@!:<.DIIVbLcKdef " "z5l^DE . 	.')G##OG$<= * --.>QN <JJ>ajM&11>J --^/F2?C~.EArvvj"%))&1^C%+Z^$Tq! / 11+];>RR"">2266*b)--f59KKj>--mR@D<<pD
 
"$Kr*~$K}d$K)N1%  3 9GHAJ-//JH3C4G3H	RVR[R[\fRgQhijPQ AO O1:v!6!6 O[ KJ I !Ps   O/O$O)c                    V ^8  d   QhR\         R\        \         \        3,          R\        \         \        3,          /# )rS   	tool_nameargsrW   )rX   r   r   )rZ   s   "r   r[   r[   t  s1     $ $ $4S> $d38n $r   c                   V'       d   \        V\        4      '       g   V# \        P                  ! V 4      pV'       g   V# VP	                  R4      ;'       g    / P	                  R4      pV'       g   V# VP                  4        Fi  w  rE\        V\        4      '       g   K  VP	                  V4      pV'       g   K8  VP	                  R4      pV'       g   KS  \        WW4      pWJg   Ke  WV&   Kk  	  V# )a!  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
propertiesre   )
isinstancedictr   
get_schemars   itemsrX   _coerce_value)	r   r   schemar   keyvalueprop_schemaexpectedcoerceds	   &&       r   coerce_tool_argsr   t  s     z$--  +F**\*00b55lCJjjl
%%% nnS)??6*0I # Kr   c                $    V ^8  d   QhR\         /# rS   r   rX   )rZ   s   "r   r[   r[     s       r   c                    \        V\        4      '       d    V F  p\        W4      pW0Jg   K  Vu # 	  V # VR9   d   \        WR8H  R7      # VR8X  d   \	        V 4      # V # )z~Attempt to coerce a string *value* to *expected_type*.

Returns the original string when coercion is not applicable or fails.
integer)integer_onlyboolean)r   number)r   listr   _coerce_number_coerce_boolean)r   expected_typer}   r%   s   &&  r   r   r     sh    
 -&&A"5,F"  --eI3MOO	!u%%Lr   c                0    V ^8  d   QhR\         R\        /# )rS   r   r   )rX   rY   )rZ   s   "r   r[   r[     s      # T r   c                     \        V 4      pY"8w  g!   T\        R4      8X  g   T\        R4      8X  d   T# T\        T4      8X  d   \        T4      # T'       d   T # T#   \        \        3 d    T u # i ; i)zFTry to parse *value* as a number.  Returns original string on failure.infz-inf)float
ValueErrorOverflowErrorint)r   r   fs   && r   r   r     sn    %L 	veEl"a5=&8CF{1vH & s   A A21A2c                $    V ^8  d   QhR\         /# r   r   )rZ   s   "r   r[   r[     s      3 r   c                f    V P                  4       P                  4       pVR8X  d   R# VR8X  d   R# V # )zGTry to parse *value* as a boolean.  Returns original string on failure.trueTfalseF)striplower)r   lows   & r   r   r     s/    
++-


C
f}
g~Lr   c                   V ^8  d   QhR\         R\        \         \        3,          R\        \         ,          R\        \         ,          R\        \         ,          R\        \         ,          R\        \        \         ,          ,          R\         /# )	rS   function_namefunction_argstask_idtool_call_id
session_id	user_taskenabled_toolsrW   )rX   r   r   r   r   )rZ   s   "r   r[   r[     s     YD YDYDS>YD c]YD 3-	YD
 YD }YD DI&YD 	YDr   c           
     $   \        W4      pV \        9  d    ^ RIHp T! T;'       g    R4        V \
        9   d   \        P                  ! RV  R2/4      #  ^ RIH	p T! RT TT;'       g    RT;'       g    RT;'       g    RR7       V R	8X  d'   Ve   TM\        p	\        P                  ! WVV	R7      p
M\        P                  ! WVVR7      p
 ^ RIH	p T! RT TT
T;'       g    RT;'       g    RT;'       g    RR7       V
#   \         d     Li ; i  \         d     Li ; i  \         d     T
# i ; i  \         dK   pRT  R\        T4       2p\        P                  T4       \        P                  ! RT/RR7      u R
p?# R
p?ii ; i)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.
)notify_other_tool_calldefaulterrorz" must be handled by the agent loop)invoke_hookpre_tool_callrh   )r   r   r   r   r   rb   N)r   r   )r   r   post_tool_call)r   r   r%   r   r   r   zError executing z: F)ensure_ascii)r   _READ_SEARCH_TOOLStools.file_toolsr   r4   _AGENT_LOOP_TOOLSjsondumpshermes_cli.pluginsr   rv   r   dispatchrX   r5   r   )r   r   r   r   r   r   r   r   r   r   r%   r9   	error_msgs   &&&&&&&      r   handle_function_callr     s   4 %]BM ..	?"7#7#7i84D--::w=/9[(\]^^	6'"2%++)//R N* 0=/HmNgO&&-F &&#F	6 '"2%++)//R e  		"  		<  		
  D&}oRAx@	Yzz7I.UCCDs   D D %D: D ,
D 7
D D 
AD: D( &
D( 1
D( <D( D: DDD%"D: $D%%D: (D73D: 6D77D: :F?F
F
Fc                :    V ^8  d   QhR\         \        ,          /# rS   rW   )r   rX   )rZ   s   "r   r[   r[   +  s     ) )DI )r   c                 ,    \         P                  ! 4       # )z!Return all registered tool names.)r   get_all_tool_namesr   r   r   r   r   +  s    &&((r   c                F    V ^8  d   QhR\         R\        \         ,          /# )rS   r   rW   )rX   r   )rZ   s   "r   r[   r[   0  s     4 4C 4HSM 4r   c                .    \         P                  ! V 4      # )z%Return the toolset a tool belongs to.)r   get_toolset_for_tool)r   s   &r   r   r   0  s    ((33r   c                F    V ^8  d   QhR\         \        \        3,          /# r   )r   rX   r   )rZ   s   "r   r[   r[   5  s     - -S$Y -r   c                 ,    \         P                  ! 4       # )z0Return toolset availability info for UI display.)r   get_available_toolsetsr   r   r   r   r   5  s    **,,r   c                F    V ^8  d   QhR\         \        \        3,          /# r   )r   rX   rY   )rZ   s   "r   r[   r[   :  s     1 1DdO 1r   c                 ,    \         P                  ! 4       # )z>Return {toolset: available_bool} for every registered toolset.)r   check_toolset_requirementsr   r   r   r   r   :  s    ..00r   c                ~    V ^8  d   QhR\         R\        \        \        ,          \        \        ,          3,          /# )rS   r_   rW   )rY   r   r   rX   r   )rZ   s   "r   r[   r[   ?  s,     9 94 9E$s)T$Z:O4P 9r   c                0    \         P                  ! V R7      # )z.Return (available_toolsets, unavailable_info).r^   )r   check_tool_availabilityr^   s   &r   r   r   ?  s    ++%88r   c                    V ^8  d   Qh/ ^ \         9   d   \        \        \        3,          ;R&   ^\         9   d   \        \        \        3,          ;R&   ^\         9   d   \        \        ,          ;R&   # )rS   TOOL_TO_TOOLSET_MAPTOOLSET_REQUIREMENTSrv   )__conditional_annotations__r   rX   r   r   )rZ   s   "r   r[   r[      s^      z I HT#s(^ H{~ L Kd39o KF * )49 )Gr   )skills_list
skill_viewskill_manage)rf   browser_snapshotbrowser_clickbrowser_typebrowser_scrollbrowser_backbrowser_pressbrowser_closebrowser_get_imagesbrowser_visionbrowser_console)
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)rN   
write_filepatchrO   )NNF>   todomemorydelegate_tasksession_search)F)NNNNN)8r   __doc__r   r   loggingr&   typingr   r   r   r   r   tools.registryr   rn   r   r	   	getLogger__name__r5   r   Lockr   localr   r   r   r0   r:   tools.mcp_toolr;   r4   r9   debugr   r<   get_tool_to_toolset_mapr   get_toolset_requirementsr   rv   rm   r   r   r   r   r   r   r   r   r   r   r   r   r   r[   )r   s   @r   <module>r
     s  *     3 3 # 6			8	$ 
.." ( ,,.f#OL  51
33 '/&F&F&H  H(0(I(I(K  K (*  ) ,.zl%&%&$%A  i[  F"#- <wD J !>2 $N($YD@)
4
-
1
9 9_  5
LL0!445  3
LL.223s0   ?E E, E)E$$E),F3F

F