
    iT                        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ZddlmZ ddl	m
Z
mZmZmZmZ  ej        e          Zdej        defdZdedefd	Zdd
ee         dee         fdZ G d d          ZdZi Zee
eeef         f         ed<    ej                    Zde
defdZ ddZ! G d d          Z" e"            Z#defdZ$ddefdZ%dS )aV  Central registry for all hermes-agent tools.

Each tool file calls ``registry.register()`` at module level to declare its
schema, handler, toolset membership, and availability check.  ``model_tools.py``
queries the registry instead of maintaining its own parallel data structures.

Import chain (circular-import safe):
    tools/registry.py  (no imports from model_tools or tool files)
           ^
    tools/*.py  (import from tools.registry at module level)
           ^
    model_tools.py  (imports tools.registry + all tool modules)
           ^
    run_agent.py, cli.py, batch_runner.py, etc.
    N)Path)CallableDictListOptionalSetnodereturnc                 8   t          | t          j                  rt          | j        t          j                  sdS | j        j        }t          |t          j                  o9|j        dk    o.t          |j        t          j                  o|j        j	        dk    S )zHReturn True when *node* is a ``registry.register(...)`` call expression.Fregisterregistry)

isinstanceastExprvalueCallfunc	AttributeattrNameid)r	   r   s     3/home/ubuntu/.hermes/hermes-agent/tools/registry.py_is_registry_register_callr      s    dCH%% Z
CH-M-M u:?D4'' 	(I#	(tz38,,	( JMZ'	    module_pathc                     	 |                      d          }t          j        |t          |                     }n# t          t
          f$ r Y dS w xY wt          d |j        D                       S )zReturn True when the module contains a top-level ``registry.register(...)`` call.

    Only inspects module-body statements so that helper modules which happen
    to call ``registry.register()`` inside a function are not picked up.
    zutf-8)encoding)filenameFc              3   4   K   | ]}t          |          V  d S N)r   ).0stmts     r   	<genexpr>z*_module_registers_tools.<locals>.<genexpr>6   s+      FFD)$//FFFFFFr   )	read_textr   parsestrOSErrorSyntaxErroranybody)r   sourcetrees      r   _module_registers_toolsr-   *   s    &&&88y#k*:*:;;;[!   uu FFDIFFFFFFs   9< AA	tools_dirc                    | t          |           n*t          t                                                    j        }d t	          |                    d                    D             }g }|D ]_}	 t          j        |           |                    |           -# t          $ r&}t                              d||           Y d}~Xd}~ww xY w|S )zLImport built-in self-registering tool modules and return their module names.Nc                 R    g | ]$}|j         d vt          |          d|j         %S )>   __init__.pymcp_tool.pyregistry.pyztools.)namer-   stem)r!   paths     r   
<listcomp>z*discover_builtin_tools.<locals>.<listcomp><   sK       9III#D)) J 	IIIr   z*.pyz#Could not import tool module %s: %s)r   __file__resolveparentsortedglob	importlibimport_moduleappend	Exceptionloggerwarning)r.   
tools_pathmodule_namesimportedmod_namees         r   discover_builtin_toolsrH   9   s    $-$9itH~~?U?U?W?W?^J :??62233  L H  O O	O#H---OOH%%%% 	O 	O 	ONN@(ANNNNNNNN	OOs   0)B
C
$CC
c                        e Zd ZdZdZ	 ddZdS )	ToolEntryz&Metadata for a single registered tool.
r4   toolsetschemahandlercheck_fnrequires_envis_asyncdescriptionemojimax_result_size_charsNc                     || _         || _        || _        || _        || _        || _        || _        || _        |	| _        |
| _	        d S r    rK   )selfr4   rL   rM   rN   rO   rP   rQ   rR   rS   rT   s              r   __init__zToolEntry.__init__V   sS     	 ( &
%:"""r   r    )__name__
__module____qualname____doc__	__slots__rW    r   r   rJ   rJ   M   s:        00I (,; ; ; ; ; ;r   rJ   g      >@_check_fn_cachefnc                    t          j                    }t          5  t                              |           }|!|\  }}||z
  t
          k     r|cddd           S ddd           n# 1 swxY w Y   	 t           |                       }n# t          $ r d}Y nw xY wt          5  ||ft          | <   ddd           n# 1 swxY w Y   |S )zIReturn bool(fn()), TTL-cached across calls. Swallows exceptions as False.NF)time	monotonic_check_fn_cache_lockr^   get_CHECK_FN_TTL_SECONDSboolr@   )r_   nowcachedtsr   s        r   _check_fn_cachedrj   v   sq   
.

C	   $$R((IBRx///                      RRTT

   	 + +"El+ + + + + + + + + + + + + + +Ls5   1A%%A),A)1B	 	BB"B;;B?B?c                  x    t           5  t                                           ddd           dS # 1 swxY w Y   dS )zDrop all cached ``check_fn`` results. Call after config changes that
    affect tool availability (e.g. ``hermes tools enable``).N)rc   r^   clearr]   r   r   invalidate_check_fn_cacherm      s     
                                          /33c                      e Zd ZdZd Zdeee         ee	e
f         f         fdZdee         fdZdee	e
f         fdZde	de
d	z  defd
Zde	dee         fdZdee	         fdZde	dee	         fdZde	de	dd	fdZdee	e	f         fdZde	dee	         fdZ	 	 	 	 	 	 d1de	de	dede
de
dedede	de	deez  d	z  fdZde	dd	fdZd2dee	         d edee         fd!Zde	d"ede	fd#Zd3de	d$eez  d	z  deez  fd%Z dee	         fd&Z!de	dee         fd'Z"de	dee	         fd(Z#d4de	d$e	de	fd*Z$dee	e	f         fd+Z%de	defd,Z&dee	ef         fd-Z'dee	ef         fd.Z(dee	ef         fd/Z)d2d efd0Z*d	S )5ToolRegistryzISingleton registry that collects tool schemas + handlers from tool files.c                 n    i | _         i | _        i | _        t          j                    | _        d| _        d S )Nr   )_tools_toolset_checks_toolset_aliases	threadingRLock_lock_generationrV   s    r   rW   zToolRegistry.__init__   s:    ,.4602 _&&
 !"r   r
   c                     | j         5  t          | j                                                  t	          | j                  fcddd           S # 1 swxY w Y   dS )zBReturn a coherent snapshot of registry entries and toolset checks.N)rw   listrr   valuesdictrs   ry   s    r   _snapshot_statezToolRegistry._snapshot_state   s    Z 	J 	J**,,--tD4H/I/II	J 	J 	J 	J 	J 	J 	J 	J 	J 	J 	J 	J 	J 	J 	J 	J 	J 	Js   :AAAc                 6    |                                  d         S )z4Return a stable snapshot of registered tool entries.r   r~   ry   s    r   _snapshot_entrieszToolRegistry._snapshot_entries       ##%%a((r   c                 6    |                                  d         S )z8Return a stable snapshot of toolset availability checks.   r   ry   s    r   _snapshot_toolset_checksz%ToolRegistry._snapshot_toolset_checks   r   r   rL   checkNc                     |sdS 	 t           |                      S # t          $ r t                              d|           Y dS w xY w)zQRun a toolset check, treating missing or failing checks as unavailable/available.Tz,Toolset %s check raised; marking unavailableF)rf   r@   rA   debugrV   rL   r   s      r   _evaluate_toolset_checkz$ToolRegistry._evaluate_toolset_check   s]     	4	==  	 	 	LLGQQQ55	s    %AAr4   c                 x    | j         5  | j                            |          cddd           S # 1 swxY w Y   dS )z0Return a registered tool entry by name, or None.N)rw   rr   rd   )rV   r4   s     r   	get_entryzToolRegistry.get_entry   s    Z 	) 	);??4((	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	)rn   c                 X    t          d |                                 D                       S )z;Return sorted unique toolset names present in the registry.c                     h | ]	}|j         
S r]   rL   r!   entrys     r   	<setcomp>z<ToolRegistry.get_registered_toolset_names.<locals>.<setcomp>   s    KKKu}KKKr   r;   r   ry   s    r   get_registered_toolset_namesz)ToolRegistry.get_registered_toolset_names   s+    KK$2H2H2J2JKKKLLLr   c                 ^    t          fd|                                 D                       S )z:Return sorted tool names registered under a given toolset.c              3   <   K   | ]}|j         k    |j        V  d S r    rL   r4   )r!   r   rL   s     r   r#   z:ToolRegistry.get_tool_names_for_toolset.<locals>.<genexpr>   s>       
 
 }'' J''''
 
r   r   )rV   rL   s    `r   get_tool_names_for_toolsetz'ToolRegistry.get_tool_names_for_toolset   sG     
 
 
 
$($:$:$<$<
 
 
 
 
 	
r   aliasc                     | j         5  | j                            |          }|r#||k    rt                              d|||           || j        |<   | xj        dz  c_        ddd           dS # 1 swxY w Y   dS )z8Register an explicit alias for a canonical toolset name.z4Toolset alias collision: '%s' (%s) overwritten by %sr   N)rw   rt   rd   rA   rB   rx   )rV   r   rL   existings       r   register_toolset_aliasz#ToolRegistry.register_toolset_alias   s    Z 	" 	",0077H H//J8W   ,3D!%(!	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"s   AA//A36A3c                 l    | j         5  t          | j                  cddd           S # 1 swxY w Y   dS )z=Return a snapshot of ``{alias: canonical_toolset}`` mappings.N)rw   r}   rt   ry   s    r   get_registered_toolset_aliasesz+ToolRegistry.get_registered_toolset_aliases   s|    Z 	/ 	/-..	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/s   )--c                 x    | j         5  | j                            |          cddd           S # 1 swxY w Y   dS )z8Return the canonical toolset name for an alias, or None.N)rw   rt   rd   )rV   r   s     r   get_toolset_alias_targetz%ToolRegistry.get_toolset_alias_target   s    Z 	4 	4(,,U33	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4rn   F rM   rN   rO   rP   rQ   rR   rS   rT   c                 X   | j         5  | j                            |          }|r|j        |k    r|j                            d          o|                    d          }|r#t
                              d|||j                   n0t
                              d|||j                   	 ddd           dS t          ||||||pg ||p|                    dd          |	|

  
        | j        |<   |r|| j	        vr
|| j	        |<   | xj
        dz  c_
        ddd           dS # 1 swxY w Y   dS )	zARegister a tool.  Called at module-import time by each tool file.zmcp-z8Tool '%s': MCP toolset '%s' overwriting MCP toolset '%s'zTool registration REJECTED: '%s' (toolset '%s') would shadow existing tool from toolset '%s'. Deregister the existing tool first if this is intentional.NrR   r   rK   r   )rw   rr   rd   rL   
startswithrA   r   errorrJ   rs   rx   )rV   r4   rL   rM   rN   rO   rP   rQ   rR   rS   rT   r   both_mcps                r   r   zToolRegistry.register   s    Z &	" &	"{t,,H H,77 $//77 3**622   LLRgx'7    LLF gx'7	   /&	" &	" &	" &	" &	" &	" &	" &	"0 !*!)/R!'H6::mR+H+H&;! ! !DK  9G4+???08$W-!M&	" &	" &	" &	" &	" &	" &	" &	" &	" &	" &	" &	" &	" &	" &	" &	" &	" &	"s   BD4ADD#&D#c                    | j         5  | j                            |d          	 ddd           dS t          fd| j                                        D                       }|sJ| j                            j        d           fd| j                                        D             | _        | xj	        dz  c_	        ddd           n# 1 swxY w Y   t                              d|           dS )a  Remove a tool from the registry.

        Also cleans up the toolset check if no other tools remain in the
        same toolset.  Used by MCP dynamic tool discovery to nuke-and-repave
        when a server sends ``notifications/tools/list_changed``.
        Nc              3   8   K   | ]}|j         j         k    V  d S r    r   )r!   rG   r   s     r   r#   z*ToolRegistry.deregister.<locals>.<genexpr>%  s=       ' '/0	U]*' ' ' ' ' 'r   c                 2    i | ]\  }}|j         k    ||S r]   r   )r!   r   targetr   s      r   
<dictcomp>z+ToolRegistry.deregister.<locals>.<dictcomp>*  s6     ) ) )%v.. 6...r   r   zDeregistered tool: %s)rw   rr   popr)   r|   rs   rL   rt   itemsrx   rA   r   )rV   r4   toolset_still_existsr   s      @r   
deregisterzToolRegistry.deregister  s    Z 	" 	"KOOD$//E}	" 	" 	" 	" 	" 	" 	" 	" $' ' ' ' '48K4F4F4H4H' ' ' $ $  ( $((===) ) ) ))-)>)D)D)F)F) ) )%
 !!	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"" 	,d33333s   CBCCC
tool_namesquietc                    g }i }d |                                  D             }t          |          D ]}|                    |          }|s|j        rP|j        |vrt	          |j                  ||j        <   ||j                 s|st
                              d|           qi |j        d|j        i}|	                    d|d           |S )a"  Return OpenAI-format tool schemas for the requested tool names.

        Only tools whose ``check_fn()`` returns True (or have no check_fn)
        are included. ``check_fn()`` results are cached for ~30 s via
        :func:`_check_fn_cached` to amortize repeat probes (check_terminal_
        requirements probes modal/docker, browser checks probe playwright,
        etc.); TTL chosen so env-var changes (``hermes tools enable foo``)
        still take effect in near-real-time without forcing a full cache
        flush on every call.
        c                     i | ]
}|j         |S r]   r4   r   s     r   r   z0ToolRegistry.get_definitions.<locals>.<dictcomp>F  s    SSS5:uSSSr   z"Tool %s unavailable (check failed)r4   function)typer   )
r   r;   rd   rO   rj   rA   r   rM   r4   r?   )	rV   r   r   resultcheck_resultsentries_by_namer4   r   schema_with_names	            r   get_definitionszToolRegistry.get_definitions6  s      /1SS$:P:P:R:RSSS:&& 	N 	ND#''--E ~ >664DU^4T4TM%.1$U^4   Q%I4PPPC%,C
CCMM:;KLLMMMMr   argsc                    |                      |          }|st          j        dd| i          S 	 |j        rddlm}  | |j        |fi |          S  |j        |fi |S # t          $ rT}t          	                    d||           t          j        ddt          |          j         d| i          cY d}~S d}~ww xY w)	zExecute a tool handler by name.

        * Async handlers are bridged automatically via ``_run_async()``.
        * All exceptions are caught and returned as ``{"error": "..."}``
          for consistent error format.
        r   zUnknown tool: r   )
_run_asynczTool %s dispatch error: %szTool execution failed: z: N)r   jsondumpsrQ   model_toolsr   rN   r@   rA   	exceptionr   rX   )rV   r4   r   kwargsr   r   rG   s          r   dispatchzToolRegistry.dispatch[  s    t$$ 	B:w(?(?(?@AAA	\~ A222222!z-%-"?"?"?"?@@@ 5=00000 	\ 	\ 	\94CCC:w(Y$q''BR(Y(YVW(Y(YZ[[[[[[[[	\s$   #A$ A$ $
C.A	B=7C=Cdefaultc                 d    |                      |          }|r|j        |j        S ||S ddlm} |S )zBReturn per-tool max result size, or *default* (or global default).Nr   )DEFAULT_RESULT_SIZE_CHARS)r   rT   tools.budget_configr   )rV   r4   r   r   r   s        r   get_max_result_sizez ToolRegistry.get_max_result_sizer  sO    t$$ 	/U0<..NAAAAAA((r   c                 X    t          d |                                 D                       S )z0Return sorted list of all registered tool names.c              3   $   K   | ]}|j         V  d S r    r   r   s     r   r#   z2ToolRegistry.get_all_tool_names.<locals>.<genexpr>~  s$      GGUejGGGGGGr   r   ry   s    r   get_all_tool_nameszToolRegistry.get_all_tool_names|  s+    GGd.D.D.F.FGGGGGGr   c                 B    |                      |          }|r|j        ndS )u   Return a tool's raw schema dict, bypassing check_fn filtering.

        Useful for token estimation and introspection where availability
        doesn't matter — only the schema content does.
        N)r   rM   rV   r4   r   s      r   
get_schemazToolRegistry.get_schema  s&     t$$$.u||$.r   c                 B    |                      |          }|r|j        ndS )z.Return the toolset a tool belongs to, or None.N)r   rL   r   s      r   get_toolset_for_toolz!ToolRegistry.get_toolset_for_tool  s$    t$$ %/u}}4/r      ⚡c                 P    |                      |          }|r|j        r|j        n|S )z3Return the emoji for a tool, or *default* if unset.)r   rS   )rV   r4   r   r   s       r   	get_emojizToolRegistry.get_emoji  s+    t$$$AA'Br   c                 >    d |                                  D             S )z?Return ``{tool_name: toolset_name}`` for every registered tool.c                 (    i | ]}|j         |j        S r]   )r4   rL   r   s     r   r   z8ToolRegistry.get_tool_to_toolset_map.<locals>.<dictcomp>  s    PPPe
EMPPPr   )r   ry   s    r   get_tool_to_toolset_mapz$ToolRegistry.get_tool_to_toolset_map  s"    PPt7M7M7O7OPPPPr   c                     | j         5  | j                            |          }ddd           n# 1 swxY w Y   |                     ||          S )zCheck if a toolset's requirements are met.

        Returns False (rather than crashing) when the check function raises
        an unexpected exception (e.g. network error, missing import, bad config).
        N)rw   rs   rd   r   r   s      r   is_toolset_availablez!ToolRegistry.is_toolset_available  s     Z 	6 	6(,,W55E	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6++GU;;;s   /33c                                                        \  }t          d |D                       } fd|D             S )z7Return ``{toolset: available_bool}`` for every toolset.c                     h | ]	}|j         
S r]   r   r   s     r   r   z:ToolRegistry.check_toolset_requirements.<locals>.<setcomp>  s    >>>U5=>>>r   c           	      d    i | ],}|                     |                    |                    -S r]   )r   rd   )r!   rL   rV   toolset_checkss     r   r   z;ToolRegistry.check_toolset_requirements.<locals>.<dictcomp>  sI     
 
 
 T11'>;M;Mg;V;VWW
 
 
r   )r~   r;   )rV   entriestoolsetsr   s   `  @r   check_toolset_requirementsz'ToolRegistry.check_toolset_requirements  sf    "&"6"6"8"8>>g>>>??
 
 
 
 
#
 
 
 	
r   c                    i }|                                  \  }}|D ]}|j        }||vr1|                     ||                    |                    g dg d||<   ||         d                             |j                   |j        r;|j        D ]3}|||         d         vr!||         d                             |           4|S )z'Return toolset metadata for UI display.r   )	availabletoolsrR   requirementsr   r   )r~   rL   r   rd   r?   r4   rP   )rV   r   r   r   r   ri   envs          r   get_available_toolsetsz#ToolRegistry.get_available_toolsets  s    $&"&"6"6"8"8 	A 	AEB!!!%!=!=N..r22" "  #%$&    RL!((444! A - A AC(2,~">>> ^4;;C@@@r   c                    i }|                                  \  }}|D ]}|j        }||vr|g |                    |          dg d||<   |j        ||         d         vr&||         d                             |j                   |j        D ]3}|||         d         vr!||         d                             |           4|S )zABuild a TOOLSET_REQUIREMENTS-compatible dict for backward compat.N)r4   env_varsrO   	setup_urlr   r   r   )r~   rL   rd   r4   r?   rP   )rV   r   r   r   r   ri   r   s          r   get_toolset_requirementsz%ToolRegistry.get_toolset_requirements  s    "$"&"6"6"8"8 	7 	7EB " . 2 22 6 6!% r
 zG!444r
7#**5:666) 7 7fRj4442Jz*11#6667 r   c                 |   g }g }t                      }|                                 \  }}|D ]}|j        |v r|                               |                     |                                        r|                               b|                    |j        fd|D             d           ||fS )zDReturn (available_toolsets, unavailable_info) like the old function.c                 4    g | ]}|j         k    |j        S r]   r   )r!   rG   ri   s     r   r7   z8ToolRegistry.check_tool_availability.<locals>.<listcomp>  s"    IIIbafr   )r4   r   r   )setr~   rL   addr   rd   r?   rP   )	rV   r   r   unavailableseenr   r   r   ri   s	           @r   check_tool_availabilityz$ToolRegistry.check_tool_availability  s    	uu"&"6"6"8"8 	 	EBTzzHHRLLL++B0B0B20F0FGG   $$$$"" % 2IIIIgIII$ $    
 +%%r   )NNFr   r   N)Fr    )r   )+rX   rY   rZ   r[   rW   tupler   rJ   r   r&   r   r~   r   r   rf   r   r   r   r   r   r   r   r   r}   r{   intfloatr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r]   r   r   rp   rp      s       SS" " "JtIS(]8K'K!L J J J J
)4	? ) ) ) ))$sH}*= ) ) ) )s 8d? t    )c )hy&9 ) ) ) )
Md3i M M M M
# 
$s) 
 
 
 

"C 
"# 
"$ 
" 
" 
" 
"/S#X / / / /
4c 4hsm 4 4 4 4 "!484" 4"4" 4" 	4"
 4" 4" 4" 4" 4" 4"  #U{T14" 4" 4" 4"l4s 4t 4 4 4 4< #c( 4 DQUJ    J\S \ \3 \ \ \ \.) ) )cEkD6H )TWZ_T_ ) ) ) )HDI H H H H/s /x~ / / / /0 0# 0 0 0 0
C Cc CC CC C C C C
Qc3h Q Q Q Q<C <D < < < <
DdO 
 
 
 
S$Y    ,$sDy/    *& &T & & & & & &r   rp   c                 ~    dt          |           i}|r|                    |           t          j        |d          S )zReturn a JSON error string for tool handlers.

    >>> tool_error("file not found")
    '{"error": "file not found"}'
    >>> tool_error("bad input", success=False)
    '{"error": "bad input", "success": false}'
    r   Fensure_ascii)r&   updater   r   )messageextrar   s      r   
tool_errorr     sC     s7||$F e:f51111r   c                 ^    | t          j        | d          S t          j        |d          S )a  Return a JSON result string for tool handlers.

    Accepts a dict positional arg *or* keyword arguments (not both):

    >>> tool_result(success=True, count=42)
    '{"success": true, "count": 42}'
    >>> tool_result({"key": "value"})
    '{"key": "value"}'
    NFr   )r   r   )datar   s     r   tool_resultr     s4     z$U3333:f51111r   r    )r
   N)&r[   r   r=   r   loggingru   ra   pathlibr   typingr   r   r   r   r   	getLoggerrX   rA   ASTrf   r   r-   r&   rH   rJ   re   r^   r   r   __annotations__Lockrc   rj   rm   rp   r   r   r   r]   r   r   <module>r     s8      


                  6 6 6 6 6 6 6 6 6 6 6 6 6 6		8	$	$
SW 
 
 
 
 
G G$ G G G G htn S	    (; ; ; ; ; ; ; ;H  68heTk 223 8 8 8%y~''  d    $       Y& Y& Y& Y& Y& Y& Y& Y&z
 <>>&2C 2 2 2 22 2 2 2 2 2 2 2r   