
    KiK                        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 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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+	 	 	 d4dee%         d ee%         d!e,d"eee%ef                  fd#Z-h d$Z.d%d&hZ/	 	 	 d5d'e%d(ee%ef         d)e	e%         d*e	e%         d+e	ee%                  d"e%fd,Z0d"ee%         fd-Z1d.e%d"e	e%         fd/Z2d"ee%e)f         fd0Z3d"ee%e,f         fd1Z4d6d2e,d"e
ee%         ee)         f         fd3Z5dS )7a  
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                      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     (/home/ubuntu/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_asyncr2   Q   sV   ,'))     .!! .!!!!22q2AA 	.T[[d33F===--	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. 	. !!)>)@)@@@&((--d333  I''---s    %%6B  B$'B$c                      g d} ddl }| D ]K}	 |                    |           # t          $ r&}t                              d||           Y d}~Dd}~ww xY wdS )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.
    )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_toolr   Nz#Could not import tool module %s: %s)	importlibimport_module	Exceptionloggerwarning)_modulesr4   mod_namees       r   _discover_toolsr<      s      H0  O O	O##H---- 	O 	O 	ONN@(ANNNNNNNN	OO Os   $
AAA)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_close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>4  s!    JJJaAjM&1JJJr   execute_code)SANDBOX_ALLOWED_TOOLSbuild_execute_code_schemar{   r|   )typer{   rK   rB   rC   description zV For simple information retrieval, prefer web_search or web_extract (faster, cheaper).c                 *    g | ]}|d          d         S rz   r   r}   s     r   
<listcomp>z(get_tool_definitions.<locals>.<listcomp>Y  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 rz   r   r}   s     r   r   z(get_tool_definitions.<locals>.<listcomp>_  s!     O O O1:v!6 O O Or   )setr
   r	   updateprintjoin_LEGACY_TOOLSET_MAPtoolsetsrv   difference_updater   get_definitionstools.code_execution_toolr   r   	enumerategetreplacelenrA   )rq   rr   rs   tools_to_includetoolset_nameresolvedlegacy_toolsrv   ts_namefiltered_toolsavailable_tool_namesr   r   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_searchra   rd   function_namefunction_argstask_id	user_taskenabled_toolsc                    | t           vr%	 ddlm}  ||pd           n# t          $ r Y nw xY w	 | t          v rt          j        d|  di          S 	 ddlm}  |d| ||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           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.
    r   )notify_other_tool_calldefaulterrorz" must be handled by the agent loop)invoke_hookpre_tool_callr   )	tool_nameargsr   r   N)r   r   )r   r   post_tool_call)r   r   r'   r   zError executing z: F)ensure_ascii)_READ_SEARCH_TOOLStools.file_toolsr   r6   _AGENT_LOOP_TOOLSjsondumpshermes_cli.pluginsr   rA   r   dispatchstrr7   r   )r   r   r   r   r   r   r   r   r'   r;   	error_msgs              r   handle_function_callr   p  s.   2 ...	??????""7#7i8888 	 	 	D	%D---:w=(\(\(\]^^^	666666K=}^e^kiklllll 	 	 	D	 N** 0=/HmmNgO&}-  FF &}#  F	666666K(M^dnun{y{||||| 	 	 	D	  D D D@}@@A@@	Yz7I.UCCCCCCCCCDss    
,,!C) A+ *C) +
A85C) 7A88AC) >C C) 
C$!C) #C$$C) )
E3AD?9E?Ec                  (    t          j                    S )z!Return all registered tool names.)r   get_all_tool_namesr   r   r   r   r     s    &(((r   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   rx   c                 ,    t          j        |           S )z.Return (available_toolsets, unavailable_info).rw   )r   check_tool_availabilityrw   s    r   r   r     s    +%8888r   )NNF)NNN)F)6__doc__r   r   loggingr(   typingr   r   r   r   r   tools.registryr   r   r	   r
   	getLogger__name__r7   r   Lockr   localr   r   r   r2   r<   tools.mcp_toolr=   r6   r;   debugr   r>   get_tool_to_toolset_mapr?   r   __annotations__get_toolset_requirementsr@   dictrA   r   boolr   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#O #O #OL    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  "#)-ED EDEDS>ED c]ED }	ED
 DI&ED 	ED ED ED EDX)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   7B B-B((B-1C C'C""C'