
    [jq                        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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dZi Zeeeeef         f         ed<   i Z eee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     0/home/ice/.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          }n# t          $ r Y dS w xY wd|vsd|vrdS 	 t          j        |t	          |                     }n# t
          $ r Y dS w xY wt          d |j        D                       S )u  Return 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.

    A cheap text prefilter avoids the ``ast.parse`` cost for files that do not
    mention both ``registry`` and ``register`` — a necessary condition for a
    top-level ``registry.register()`` call to exist.
    zutf-8)encodingFr   r   )filenamec              3   4   K   | ]}t          |          V  d S N)r   ).0stmts     r   	<genexpr>z*_module_registers_tools.<locals>.<genexpr>@   s+      FFD)$//FFFFFFr   )	read_textOSErrorr   parsestrSyntaxErroranybody)r   sourcetrees      r   _module_registers_toolsr-   +   s    &&&88   uu:V#;#;uy#k*:*:;;;   uu FFDIFFFFFFs    
''#A 
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>F   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   C   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_charsdynamic_schema_overridesNc                     || _         || _        || _        || _        || _        || _        || _        || _        |	| _        |
| _	        || _
        d S r    rK   )selfr4   rL   rM   rN   rO   rP   rQ   rR   rS   rT   rU   s               r   __init__zToolEntry.__init__`   s^     	 ( &
%:" )A%%%r   )NN)__name__
__module____qualname____doc__	__slots__rX    r   r   rJ   rJ   W   sB        00I GKA A A A A Ar   rJ   g      >@g      N@_check_fn_cache_check_fn_last_good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   d}	 t           |                       }n# t          $ r d}d}Y nw xY wt          5  |r$|t          | <   |dft          | <   	 ddd           dS t                              |           }|Q||z
  t          k     rCt                              dt          | d|           |rdndt                     	 ddd           dS t                              dt          | d|           |rdnd           |dft          | <   	 ddd           dS # 1 swxY w Y   dS )	a  Return bool(fn()), TTL-cached across calls.

    Exceptions are swallowed as False. A transient False/exception within
    ``_CHECK_FN_FAILURE_GRACE_SECONDS`` of the last True is suppressed (the
    last-good True is returned and the failure is NOT cached, so the next call
    re-probes) to keep flaky external checks (Docker daemon busy, socket
    contention, probe timeout) from silently stripping tools mid-session.
    NFTzicheck_fn %s failed (%s) within %.0fs of last success; treating as transient and keeping tool(s) availabler[   raisedzreturned Falsez=check_fn %s %s; dependent tools will be unavailable this turn)time	monotonic_check_fn_cache_lockr_   get_CHECK_FN_TTL_SECONDSboolr@   r`   _CHECK_FN_FAILURE_GRACE_SECONDSrA   rB   getattr)ra   nowcachedtsr   rc   	last_goods          r   _check_fn_cachedrp      s    .

C	   $$R((IBRx///                       FRRTT

    
   	&)##&+OB	        (++B//	 S9_7V%V%V NNFNB//"8(8/   %       , 	KB++4HH$4	
 	
 	

  #El9                 sB   1A%%A),A)3B BB&FA F:<FF
Fc                      t           5  t                                           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)rf   r_   clearr`   r^   r   r   invalidate_check_fn_cachers      s     
 $ $!!###$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s   3AAAc                   V   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	dee         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de	deddfdZde
dee	         fdZede	fd            Z	 	 	 	 	 	 	 	 d8d	e	de	dede
de
dedede	de	d eez  dz  d!e
d"efd#Zd	e	ddfd$Zd9d%e e	         d&edee         fd'Z!ed	e	fd(            Z"d	e	d)ede	ez  fd*Z#d:d	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'd;d	e	d+e	de	fd1Z(dee	e	f         fd2Z)de	defd3Z*dee	ef         fd4Z+dee	ef         fd5Z,dee	ef         fd6Z-d9d&efd7Z.dS )<ToolRegistryzISingleton registry that collects tool schemas + handlers from tool files.c                 |    i | _         i | _        i | _        i | _        t	          j                    | _        d| _        d S )Nr   )_tools_plugin_override_policy_toolset_checks_toolset_aliases	threadingRLock_lock_generationrW   s    r   rX   zToolRegistry.__init__   sD    ,. 9;$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)r}   listrw   valuesdictry   r   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   r   s    r   _snapshot_entrieszToolRegistry._snapshot_entries   s    ##%%a((r   rL   entriesc                     i }|D ]M}|j         |k    r|j        s dS |j        |vrt          |j                  ||j        <   ||j                 r dS NdS )a{  Return True when at least one tool in *toolset* would be exposed.

        Mirrors :meth:`get_tool_definitions` per-tool filtering so doctor,
        banners, and other toolset-level surfaces agree with runtime exposure.
        Mixed toolsets (e.g. ``terminal`` plus desktop-only ``read_terminal``)
        must not be gated solely by the first registered ``check_fn``.
        TF)rL   rO   rp   )rW   rL   r   check_resultsentrys        r   _toolset_has_exposable_toolsz)ToolRegistry._toolset_has_exposable_tools   s|     /1 	 	E}''> tt~]220@0P0Pen-U^, ttur   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)r}   rw   rg   )rW   r4   s     r   	get_entryzToolRegistry.get_entry  s    Z 	) 	);??4((	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	)   /33c                 X    t          d |                                 D                       S )z;Return sorted unique toolset names present in the registry.c                     h | ]	}|j         
S r^   rL   r!   r   s     r   	<setcomp>z<ToolRegistry.get_registered_toolset_names.<locals>.<setcomp>  s    KKKu}KKKr   r;   r   r   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   )rW   rL   s    `r   get_tool_names_for_toolsetz'ToolRegistry.get_tool_names_for_toolset  sG     
 
 
 
$($:$:$<$<
 
 
 
 
 	
r   aliasNc                     | 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 %s   N)r}   rz   rg   rA   rB   r~   )rW   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)r}   r   rz   r   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)r}   rz   rg   )rW   r   s     r   get_toolset_alias_targetz%ToolRegistry.get_toolset_alias_target3  s    Z 	4 	4(,,U33	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4r   module_namespaceallowedc                 t    | j         5  t          |          | j        |<   ddd           dS # 1 swxY w Y   dS )a  Bind a plugin module namespace to its operator opt-in for built-in
        override. Called once per plugin at load time. Durable: never cleared,
        so later (even threaded/delayed) register() calls from that module are
        still gated by the same policy.
        N)r}   ri   rx   )rW   r   r   s      r   register_plugin_override_policyz,ToolRegistry.register_plugin_override_policy<  s     Z 	K 	K=A']]D()9:	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	K 	Ks   -11rN   c                     	 |j                             dd          }n# t          $ r Y dS w xY w|| j        v r|S t	          |t
                    r|                    d          r|S dS )aS  Return the plugin module namespace that defined *handler*, or None
        if it was not defined in a loaded plugin module.

        Authorization is bound to where the handler was DEFINED
        (``handler.__globals__["__name__"]``), which is fixed at definition
        time and cannot drift with the call site, thread, or timing. Lambdas
        and nested functions inherit the defining module's globals, so a
        plugin cannot launder an override through a callback. Built-in/MCP
        handlers live outside the plugin namespace and return None (unchanged
        behavior).
        rY    Nhermes_plugins.)__globals__rg   AttributeErrorrx   r   r'   
startswith)rW   rN   mods      r   _plugin_owner_ofzToolRegistry._plugin_owner_ofE  s    	%))*b99CC 	 	 	44	$...J c3 	CNN3D$E$E 	Jts    
,,c                      	 t          j        d          } | j                            dd          pdS # t          $ r Y dS w xY w)u  Best-effort module name of whoever called the registry method that
        invoked this helper (two frames up: this helper, then the registry
        method itself, then the actual caller).

        ``deregister()`` takes only a tool name — unlike ``register()`` it has
        no handler argument to bind authorization to via ``_plugin_owner_of``.
        Frame inspection is the only way to know who is asking.
           rY   r   )sys	_getframe	f_globalsrg   r@   )frames    r   _caller_modulezToolRegistry._caller_module]  sU    	M!$$E?&&z266<"< 	 	 	22	s   03 
A AFr   rM   rO   rP   rQ   rR   rS   rT   rU   overridec                 |   | j         5  | j                            |          }|r|j        |k    r|j                            d          o|                    d          }|r#t
                              d|||j                   n|r|                     |          }|S| j                            |d          s8t
          	                    d|||j                   t          d|d|d          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 )a  Register a tool.  Called at module-import time by each tool file.

        ``override=True`` is an explicit opt-in for plugins that intend to
        replace an existing built-in tool implementation (e.g. swap the
        default browser tool for a headed-Chrome CDP backend). Without it,
        registrations that would shadow an existing tool from a different
        toolset are rejected to prevent accidental overwrites.
        mcp-z8Tool '%s': MCP toolset '%s' overwriting MCP toolset '%s'NFzTool registration REJECTED: plugin %r attempted to override built-in tool %r (existing toolset %r) without operator opt-in. Set plugins.entries.<plugin_id>.allow_tool_override: true in config.yaml to allow it.Plugin module z cannot override built-in tool z/ without operator opt-in (allow_tool_override).zOTool '%s': toolset '%s' overriding existing toolset '%s' (override=True opt-in)zTool registration REJECTED: '%s' (toolset '%s') would shadow existing tool from toolset '%s'. Pass override=True to register() if the replacement is intentional, or deregister the existing tool first.rR   r   rK   r   )r}   rw   rg   rL   r   rA   debugr   rx   errorPermissionErrorinforJ   ry   r~   )rW   r4   rL   rM   rN   rO   rP   rQ   rR   rS   rT   rU   r   r   both_mcp_owners                   r   r   zToolRegistry.registerm  s   . Z E	" E	"{t,,H -H,77 $//77 3**622   &LLRgx'7     !!227;;F)$2N2R2RSY[`2a2a):
 #D(*:   .6V 6 6$(6 6 6   KK1gx'7    LLN gx'7   _E	" E	" E	" E	" E	" E	" E	" E	"` !*!)/R!'H6::mR+H+H&;)A! ! !DK&  9G4+???08$W-!KE	" E	" E	" E	" E	" E	" E	" E	" E	" E	" E	" E	" E	" E	" E	" E	" E	" E	"s   D0F1AF11F58F5c           
      f   | j         5  | j                            |          	 ddd           dS j                            d          s|                                 }|                     j                  }d                    |	                    d          dd                   }|r0d                    |	                    d          dd                   nd}t          |o||k              }|                    d          r^|s\| j                            |d          sAt                              d||j        |           t          d	|d
|dj        d          | j        |= 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 )uT  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``.

        Gated by the same operator opt-in policy ``register(override=True)``
        enforces. Without this, a plugin could bypass that gate entirely by
        deregistering a tool it doesn't own and then calling plain
        ``register()`` over the now-empty slot — ``register()`` only runs its
        override check when an ``existing`` entry is present, so removing it
        first skips the check altogether. MCP toolsets (``mcp-*``) are exempt:
        dynamic tool discovery legitimately nukes-and-repaves its own tools on
        every refresh and has no plugin-override concept.
        Nr   .r   r   r   FzTool deregistration REJECTED: plugin %r attempted to remove tool %r (toolset %r) it does not own, without operator opt-in. Set plugins.entries.%s.allow_tool_override: true in config.yaml to allow it.r   z cannot deregister tool z
 (toolset z0) without operator opt-in (allow_tool_override).c              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)r}   rw   rg   rL   r   r   r   rN   joinsplitri   rx   rA   r   r   r)   r   ry   poprz   itemsr~   r   )	rW   r4   
caller_modownercaller_root
owner_rootsame_plugintoolset_still_existsr   s	           @r   
deregisterzToolRegistry.deregister  s     Z 0	" 0	"KOOD))E}0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	" =++F33 !0022
--em<< "hhz'7'7'<'<RaR'@AA?DLSXXekk#&6&6rr&:;;;"
"5#F[J-FGG))*;<<' !8<<[%PP
 LL3
 #D%-   *9 9 99 9-2]9 9 9  
 D! $' ' ' ' '48K4F4F4H4H' ' ' $ $  ( $((===) ) ) ))-)>)D)D)F)F) ) )%
 !a0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	" 0	"b 	,d33333s   HGHHH
tool_namesquietc                    g }i }d |                                  D             }t          |          D ]}|                    |          }|s|j        rP|j        |vrt	          |j                  ||j        <   ||j                 s|st
                              d|           ri |j        d|j        i}|j	        s	 |	                                }	t          |	t                    r|                    |	           n3# t          $ r&}
t
                              d||
           Y d}
~
nd}
~
ww xY w|                    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>"  s    SSS5:uSSSr   z"Tool %s unavailable (check failed)r4   NzCdynamic_schema_overrides for tool %s raised %s; using static schemafunction)typer   )r   r;   rg   rO   rp   rA   r   rM   r4   rU   r   r   updater@   rB   r?   )rW   r   r   resultr   entries_by_namer4   r   schema_with_name	overridesexcs              r   get_definitionszToolRegistry.get_definitions  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 -9	 % > > @ @I!)T22 ;(//	:::    NN.c        MM:;KLLMMMMs   >>C==
D-D((D-c                 x   t          |t                    r|S t          |t                    rA|                    d          du r*t          |                    d          t                    r|S t          |          j        }t                              d| |           t          j
        d| d| |dd	          S )
a|  Enforce the result shapes supported by the agent tool pipeline.

        Normal tool results are strings.  The sole structured exception is the
        multimodal envelope consumed by the agent executor.  Returning every
        other value as a string error keeps logging, hooks, budgeting, and
        persistence from receiving values they cannot safely slice or size.
        _multimodalTcontentz4Tool %s handler returned unsupported result type: %sz/Tool handler returned unsupported result type: tool_result_contract)r   
error_typetoolresult_typeFensure_ascii)r   r'   r   rg   r   r   rY   rA   r   jsondumps)r4   r   r   s      r   _normalize_handler_resultz&ToolRegistry._normalize_handler_resultG  s     fc"" 	Mvt$$	

=))T116::i00$77 2 M6ll+B	
 	
 	

 zT{TT0&	
 

    	r   argsc                    |                      |          }|st          j        dd| i          S 	 |j        rddlm}  | |j        |fi |          }n |j        |fi |}|                     ||          S # t          $ r{}t          
                    d||           dt          |          j         d| }	 ddlm}	  |	|          }
n# t          $ r |}
Y nw xY wt          j        d|
i          cY d	}~S d	}~ww xY w)
a_  Execute a tool handler by name.

        * Async handlers are bridged automatically via ``_run_async()``.
        * Handler results are normalized to a string or supported multimodal
          envelope before leaving the registry.
        * 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: )_sanitize_tool_errorN)r   r   r   rQ   model_toolsr   rN   r   r@   rA   	exceptionr   rY   r   )rW   r4   r   kwargsr   r   r   rG   rawr   	sanitizeds              r   dispatchzToolRegistry.dispatchf  s{    t$$ 	B:w(?(?(?@AAA	4~ 7222222#MEM$$A$A&$A$ABB&t66v6611$??? 	4 	4 	494CCC DDGG,<CCCCC <<<<<<0055		      			 :w	233333333	4sB   AA; ;
D 6C;<CC;CC;CC;5D ;D 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   )rW   r4   r   r   r   s        r   get_max_result_sizez ToolRegistry.get_max_result_size  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   r   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   rW   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   )rW   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   r   s    r   get_tool_to_toolset_mapz$ToolRegistry.get_tool_to_toolset_map  s"    PPt7M7M7O7OPPPPr   c                 \    |                                  \  }}|                     ||          S )zCheck if a toolset has at least one exposable tool.

        Returns False (rather than crashing) when a per-tool check raises
        an unexpected exception (e.g. network error, missing import, bad config).
        )r   r   )rW   rL   r   _s       r   is_toolset_availablez!ToolRegistry.is_toolset_available  s/     ))++
00'BBBr   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                 >    i | ]}|                     |          S r^   )r   )r!   rL   r   rW   s     r   r   z;ToolRegistry.check_toolset_requirements.<locals>.<dictcomp>  s;     
 
 
 T66wHH
 
 
r   )r   r;   )rW   r  toolsetsr   s   `  @r   check_toolset_requirementsz'ToolRegistry.check_toolset_requirements  se    ))++
>>g>>>??
 
 
 
 
#
 
 
 	
r   c                 d   i }|                                  \  }}|D ]}|j        }||vr|                     ||          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   r?   r4   rP   )rW   r  r   r  r   rn   envs          r   get_available_toolsetsz#ToolRegistry.get_available_toolsets  s    $&))++
 	A 	AEB!!!%!B!B2w!O!O#%$&	    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   rg   r4   r?   rP   )rW   r   r   toolset_checksr   rn   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                 R   g }g }|                                  \  }}t          d |D                       D ]nfd|D             }|                     |          r|                               <|                    |r|d         j        ng d |D             d           o||fS )zDReturn (available_toolsets, unavailable_info) like the old function.c                     h | ]	}|j         
S r^   r   r   s     r   r   z7ToolRegistry.check_tool_availability.<locals>.<setcomp>  s    ===E%-===r   c                 *    g | ]}|j         k    |S r^   r   )r!   r   rn   s     r   r7   z8ToolRegistry.check_tool_availability.<locals>.<listcomp>  s%    LLLE8K8K%8K8K8Kr   r   c                     g | ]	}|j         
S r^   r   r   s     r   r7   z8ToolRegistry.check_tool_availability.<locals>.<listcomp>  s    AAAUejAAAr   )r4   r  r  )r   r;   r   r?   rP   )rW   r   r  unavailabler   r  
ts_entriesrn   s          @r   check_tool_availabilityz$ToolRegistry.check_tool_availability  s    	))++
==W===>> 		 		BLLLLWLLLJ00W==   $$$$"">H P
1 : :bAAjAAA$ $    
 +%%r   )NNFr   r   NNF)Fr    )r  )/rY   rZ   r[   r\   rX   tupler   rJ   r   r'   r   r   r   ri   r   r   r   r   r   r   r   r   r   r   staticmethodr   r   r   intfloatr   r   r   r   r   r   r   r   r   r   r  r  r	  r  r  r  r   r^   r   r   ru   ru      sK       SS" " "*JtIS(]8K'K!L J J J J
)4	? ) ) ) ) i 
	   0)c )hy&9 ) ) ) )
Md3i M M M M
# 
$s) 
 
 
 

"C 
"# 
"$ 
" 
" 
" 
"/S#X / / / /
4c 4hsm 4 4 4 4K Kd KW[ K K K K Xc]    0 C    \* "!48-1\" \"\" \" 	\"
 \" \" \" \" \" \"  #U{T1\" #+\" \" \" \" \"|A4s A4t A4 A4 A4 A4N/ /#c( /4 /DQUJ / / / /j     \<4S 4 43: 4 4 4 4H) ) )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C CD C C C C
DdO 
 
 
 
S$Y    ($sDy/    *& &T & & & & & &r   ru   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   Fr   )r'   r   r   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   loggingr   r{   rd   pathlibr   typingr   r   r   r   r   	getLoggerrY   rA   ASTri   r   r-   r'   rH   rJ   rh   rj   r_   r!  r$  __annotations__r`   Lockrf   rp   rs   ru   r   r(  r+  r^   r   r   <module>r3     sl      


       



            6 6 6 6 6 6 6 6 6 6 6 6 6 6		8	$	$
SW 
 
 
 
 
G G$ G G G G0 htn S	    (A A A A A A A Ap   #' 68heTk 223 8 8 8-/ T(E/* / / /%y~'' 4 4d 4 4 4 4n$ $ $ $`& `& `& `& `& `& `& `&H <>>&2C 2 2 2 22 2 2 2 2 2 2 2r   