
    i:                        U d 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
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 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*i Z+ee,e	ee$ef                  f         e%d<   dBdZ-	 	 	 dCde	e$         d e	e$         d!e.de	ee$ef                  fd"Z/	 	 	 dCde	e$         d e	e$         d!e.de	ee$ef                  fd#Z0h d$Z1d%d&hZ2d'e$d(ee$ef         dee$ef         fd)Z3dDd*e$d+e(dz  fd,Z4d+e(dz  de.fd-Z5d*e$d.e6fd/Z7dEd*e$d0e.fd1Z8d*e$fd2Z9	 	 	 	 	 	 dFd3e$d4ee$ef         d5e
e$         d6e
e$         d7e
e$         d8e
e$         d9e
e	e$                  d:e.de$fd;Z:de	e$         fd<Z;d'e$de
e$         fd=Z<dee$e(f         fd>Z=dee$e.f         fd?Z>dEd@e.dee	e$         e	e(         f         fdAZ?dS )Ga  
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     0/home/ubuntu/.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                 l   	 	 t          j                    }n# t          $ r d}Y nw xY w|r|                                rddl}d	t          j                     	fd}|j                            d          }|	                    |          }	 |
                    d          |                    d	           S # |j        j        $ r]                     d
          rE	C	 t          j        	          D ]}	                    |j                   n# t          $ r Y nw xY w w xY w# |                    d	           w xY w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   c            	         t          j                                                     	 t          j                                                 	 t          j                  } | D ]}|                                 | r$                    t          j        | ddi           n# t          $ r Y nw xY w	                                 S # 	 t          j                  } | D ]}|                                 | r$                    t          j        | ddi           n# t          $ r Y nw xY w	                                 w xY w)Nreturn_exceptionsT)
r   r   setr   run_until_complete	all_taskscancelgather	Exceptionclose)pendingtcoro
loop_readyworker_loops     r   _run_in_workerz"_run_async.<locals>._run_in_workerx   s   !022KNN$&{333"55d;; &/<<G$ # #



 #66#NGLtLL   !   D!!#### &/<<G$ # #



 #66#NGLtLL   !   D!!####sC   (C AB''
B43B4EAD"!E"
D/,E.D//E   )max_workersi,  )timeoutF)waitg      ?)r   get_running_loopRuntimeError
is_runningconcurrent.futures	threadingEventfuturesThreadPoolExecutorsubmitresultshutdownTimeoutErrorr/   r!   call_soon_threadsafer"   current_threadmain_threadr   r    r   )
r(   r   
concurrentr+   poolfuturer'   	tool_loopr)   r*   s
   `       @@r   
_run_asyncrC   R   s   ,'))     4&!! 4& 	"!!!;?_&&
	$ 	$ 	$ 	$ 	$ 	$ 	$, !444CC^,,	&===--  MMuM%%%% !. 
	 
	 
	 s++ 0G$.{;; C C#88BBBBC#   D 
	 MMuM%%%% !!)>)@)@@@&((--d333  I''---sH    ((C   'D,(1DD,
D'$D,&D''D,,D/ /E)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_tools_tool_defs_cachereturnc                  8    t                                            dS )zDrop memoized get_tool_definitions() results. Called when dynamic
    schema dependencies change (e.g. discord capability cache reset,
    execute_code sandbox reconfigured).N)rv   clearr   r   r   _clear_tool_defs_cacherz     s     r   Fenabled_toolsetsdisabled_toolsets
quiet_modec                    |r	 ddl m}  |            }|                                }|j        |j        f}n# t
          t          t          f$ r d}Y nw xY w| t          |           nd|rt          |          ndt          j
        |f}t                              |          }|d |D             at          |          S t          | ||          }	|r|	t          |<   t          |	          S |	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.
    r   )get_config_pathNc                 *    g | ]}|d          d         S functionnamer   .0r'   s     r   
<listcomp>z(get_tool_definitions.<locals>.<listcomp><  s!    (O(O(O1:v)>(O(O(Or   )hermes_cli.configr   statst_mtime_nsst_sizeFileNotFoundErrorOSErrorImportError	frozensetr	   _generationrv   getrG   list_compute_tool_definitions)
r{   r|   r}   r   cfg_pathcfg_statcfg_fp	cache_keycachedr9   s
             r   get_tool_definitionsr     s1   4   	999999&((H}}H*H,<=FF!7K8 	 	 	FFF	 ,<+GI&'''T,=GI'(((4 	
	 "%%i00 )P(O(O(O(O% <<&'79JJWWF 	 '-#F||Ms   27 AAc           	         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          |                     %|r|D ]}t          |          rSt          |          }|	                    |           |s,t	          d
| d|rd                    |          nd            d|t          v rMt          |         }|	                    |           |s(t	          d| dd                    |                      |st	          d|            t          j        ||          }	d |	D             }
d|
v rpddlm}m}m} ||
z  } || |                      }t!          |	          D ]<\  }}|                    di                               d          dk    r
d|d|	|<    n=ddd}|D ]|
v r	 ddlm} t)          ||                   } |            }n# t*          $ r d}Y nw xY w|$fd|	D             }	|
                               ft!          |	          D ]<\  }}|                    di                               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 t1          |	           d!d                    |                      nt	          d"           d# |	D             a	 dd$lm}  ||	          }	n2# t*          $ r%}t8                              d%|           Y d}~nd}~ww xY w|	S )&z8Uncached implementation of :func:`get_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 r   r   r   s     r   	<setcomp>z,_compute_tool_definitions.<locals>.<setcomp>  s!    JJJaAjM&1JJJr   execute_code)SANDBOX_ALLOWED_TOOLSbuild_execute_code_schema_get_execution_mode)moder   r   )typer   get_dynamic_schema_coreget_dynamic_schema_admin)discorddiscord_admin)discord_toolc                 n    g | ]1}|                     d i                                d          k    /|2S r   )r   )r   r'   discord_tool_names     r   r   z-_compute_tool_definitions.<locals>.<listcomp>  sK     " " "uuZ,,0088<MMM MMMr   rQ   rH   rI   description zV For simple information retrieval, prefer web_search or web_extract (faster, cheaper).c                 *    g | ]}|d          d         S r   r   r   s     r   r   z-_compute_tool_definitions.<locals>.<listcomp>  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 r   r   r   s     r   r   z-_compute_tool_definitions.<locals>.<listcomp>  s!     O O O1:v!6 O O Or   )sanitize_tool_schemaszSchema sanitization skipped: %s)r   r   r
   updateprintjoin_LEGACY_TOOLSET_MAPtoolsetsr   difference_updater	   get_definitionstools.code_execution_toolr   r   r   	enumerater   toolsr   r   r$   discardreplacelenrG   tools.schema_sanitizerr   loggerwarning)r{   r|   r}   tools_to_includetoolset_nameresolvedlegacy_toolsr   ts_namefiltered_toolsavailable_tool_namesr   r   r   sandbox_enableddynamic_schemaitd_discord_schema_fns_dt	schema_fndynamicweb_tools_availabledesc
tool_namesr   er   s                              @r   r   r   O  sh     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 	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 -.>jQQQN KJ>JJJ ---ssssssssss/2FF22?I\I\I^I^___~.. 	 	EArvvj"%%))&11^CC-7^$T$Tq! D -3  1   444555555#C)<=N)OPP	#)++   " " " "-" " " %,,->????&~66  EArvvj"--11&99=NNN5?W,U,Uq) O 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=@@@@@@..~>> = = =8!<<<<<<<<= s*   &J77KK5Q 
Q6Q11Q6>   todomemorydelegate_tasksession_searchrf   ri   	tool_nameargsc                    |rt          |t                    s|S t          j        |           }|s|S |                    d          pi                     d          }|s|S |                                D ]u\  }}t          |t                    s|                    |          }|s3|                    d          }|st          |          sZt          |||          }||ur|||<   v|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
propertiesr   schema)	
isinstancedictr	   
get_schemar   itemsstr_schema_allows_null_coerce_value)	r   r   r   r   keyvalueprop_schemaexpectedcoerceds	            r   coerce_tool_argsr     s     z$--  ++F **\**0b55lCCJ jjll    
U%%% 	 nnS)) 	??6** 	 3K @ @ 	xDDD%DIKr   r   r   c                    t          |          r,|                                                                 dk    rdS t          |t                    r!|D ]}t          | ||          }|| ur|c S | S |dv rt          | |dk              S |dk    rt          |           S |dk    rt          | t                    S |d	k    rt          | t                    S |dk    r,|                                                                 dk    rdS | S )
zAttempt to coerce a string *value* to *expected_type*.

    Returns the original string when coercion is not applicable or fails.
    nullNr   )integernumberr   )integer_onlybooleanarrayobject)
r   striplowerr   r   r   _coerce_number_coerce_boolean_coerce_jsonr   )r   expected_typer   r'   r9   s        r   r   r     s0   
 6"" u{{}}':':'<'<'F'Ft-&&  	 	A"5!F;;;FU"" #---e=I3MOOOO	!!u%%%E4(((  E4(((5;;==#6#6#8#8F#B#BtLr   c                    t          | t                    sdS |                     d          }|dk    rdS t          |t                    rd|v rdS |                     d          du rdS dD ]d}|                     |          }t          |t                    s-|D ]4}t          |t                    r|                    d          dk    r  dS 5edS )z@Return True when a JSON Schema fragment explicitly permits null.Fr   r   Tnullable)anyOfoneOf)r   r   r   r   )r   schema_type	union_keyvariantsvariants        r   r   r   ;  s    fd## u**V$$Kft+t$$ ;)>)>tzz*%%t'  	::i(((D)) 	 	 	G'4(( W[[-@-@F-J-Jttt	 5r   expected_python_typec                     	 t          j        |           }n# t          t          f$ r | cY S w xY wt	          ||          r"t
                              d|j                   |S | S )aJ  Parse *value* as JSON when the schema expects an array or object.

    Handles model output drift where a complex oneOf/discriminated-union schema
    causes the LLM to emit the array/object as a JSON string instead of a native
    structure.  Returns the original string if parsing fails or yields the wrong
    Python type.
    z5coerce_tool_args: coerced string to %s via json.loads)jsonloads
ValueError	TypeErrorr   r   debug__name__)r   r  parseds      r   r   r   S  s    E""	"   &.// C )	
 	
 	
 Ls    --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)floatr  OverflowErrorint)r   r   fs      r   r   r   h  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)r   r   )r   lows     r   r   r   z  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 |sJd}	 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 | t          vr%	 dd
lm	}
  |
|pd           n# t          $ r Y nw xY wt          j                    }| dk    r$||nt          }t          j        | |||          }nt          j        | |||          }t          t          j                    |z
  dz            }	 ddlm}  |d| |||pd|pd|pd|           n# t          $ r Y nw xY w	 ddlm}  |d| |||pd|pd|pd|          }|D ]}t#          |t$                    r|} n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)notify_other_tool_calldefaultr   )r  r  )r  r  i  )invoke_hookpost_tool_call)r   r   r9   r  r  r  duration_mstransform_tool_resultzError executing z: )r   _AGENT_LOOP_TOOLSr  dumpshermes_cli.pluginsr  r$   _READ_SEARCH_TOOLStools.file_toolsr!  time	monotonicrG   r	   dispatchr  r#  r   r   r   	exception)r  r  r  r  r  r  r  r  block_messager  r!  _dispatch_startr   r9   r%  r#  hook_resultshook_resultr   	error_msgs                       r   handle_function_callr5    s~   6 %]MBBMnD---:w=(\(\(\]^^^ ' 	P+/M
NNNNNN ? ?!!#Mr)/R!-!3! ! !     (z7M":OOOO  222CCCCCC&&w';)<<<<    .**N** 0=/HmmNgO&}-  FF &}#  F
 4>++o=EFF	666666K '"2%+)/R'	 	 	 	 	  	 	 	D		666666&;''"2%+)/R'	 	 	L  ,  k3// (FE  	 	 	D	  D D D@}@@A@@	###z7I.UCCCCCCCCCDs   !F+ F+ A F+ 
A# F+ "A##F+  	F+ 
B F+ 
B+(F+ *B++A>F+ *E
 	F+ 

EF+ EF+ =F F+ 
F&#F+ %F&&F+ +
H5AH;HHc                  (    t          j                    S )z!Return all registered tool names.)r	   get_all_tool_namesr   r   r   r7  r7    s    &(((r   c                 *    t          j        |           S )z%Return the toolset a tool belongs to.)r	   get_toolset_for_tool)r   s    r   r9  r9    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   r   c                 ,    t          j        |           S )z.Return (available_toolsets, unavailable_info).r   )r	   check_tool_availabilityr   s    r   r?  r?  *  s    +%8888r   )rw   N)NNF)N)F)NNNNNF)@__doc__r  r   loggingr4   r,  typingr   r   r   r   r   tools.registryr   r	   r   r
   r   	getLoggerr	  r   r   Lockr   localr   r   r   rC   r)  rD   r$   r   r  get_tool_to_toolset_maprE   r   __annotations__get_toolset_requirementsrF   r   rG   r   rv   tuplerz   boolr   r   r'  r*  r   r   r   r   r   r   r   r5  r7  r9  r;  r=  r?  r   r   r   <module>rL     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((     ,[. [. [.D      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"#-  R 79 $ud4S>223 8 8 8    #'#'= =3i=Cy= = 
$sCx.	= = = =B #'#'U U3iUCyU U 
$sCx.	U U U U@ JII !>2 $ $4S> $d38n $ $ $ $N  TD[    :t     0 4    * # T    $3     ""& $#)-$)KD KDKDS>KD c]KD 3-	KD
 KD }KD DI&KD "KD 	KD KD KD KDd)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 9s   :B B0B++B0