
    j`                        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%y)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                    t        | t        j                        r$t        | j                  t        j                        sy| j                  j
                  }t        |t        j                        xrP |j                  dk(  xr? t        |j                  t        j                        xr |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     /home/cube/projects/richard/traning coach/.omo/evidence/nutricoach-v150-combined/st_01a0560c-r63-installed-wheel-first-claim-qa/venv/lib/python3.12/site-packages/tools/registry.py_is_registry_register_callr      s    dCHH%Z

CHH-M::??D4' 	(II#	(tzz388,	( JJMMZ'	    module_pathc                     	 | j                  d      }t        j                  |t        |             }t        d |j                  D              S # t        t
        f$ r Y yw xY w)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   2   K   | ]  }t        |        y wN)r   ).0stmts     r   	<genexpr>z*_module_registers_tools.<locals>.<genexpr>6   s     FID)$/Is   )	read_textr   parsestrOSErrorSyntaxErroranybody)r   sourcetrees      r   _module_registers_toolsr-   *   s_    &&&8yy#k*:; FDIIFFF [! s   2A A"!A"	tools_dirc                    | t        |       n&t        t              j                         j                  }t	        |j                  d            D cg c]*  }|j                  dvrt        |      rd|j                   , }}g }|D ])  }	 t        j                  |       |j                  |       + |S c c}w # t        $ r!}t        j                  d||       Y d}~Xd}~ww xY w)zLImport built-in self-registering tool modules and return their module names.Nz*.py>   __init__.pymcp_tool.pyregistry.pyztools.z#Could not import tool module %s: %s)r   __file__resolveparentsortedglobnamer-   stem	importlibimport_moduleappend	Exceptionloggerwarning)r.   
tools_pathpathmodule_namesimportedmod_namees          r   discover_builtin_toolsrF   9   s    $-$9itH~?U?U?W?^?^J :??6233D99II#D) 3   H 	O##H-OOH% ! O  	ONN@(ANN	Os   /B4
&B99	C#CC#c                       e Zd ZdZdZ	 ddZy)	ToolEntryz&Metadata for a single registered tool.r8   toolsetschemahandlercheck_fnrequires_envis_asyncdescriptionemojimax_result_size_charsdynamic_schema_overridesNc                     || _         || _        || _        || _        || _        || _        || _        || _        |	| _        |
| _	        || _
        y r    rI   )selfr8   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   s               r   __init__zToolEntry.__init__V   sX     	 ( &
%:" )A%r   )NN)__name__
__module____qualname____doc__	__slots__rV    r   r   rH   rH   M   s    0I GKAr   rH   g      >@_check_fn_cachefnc                 \   t        j                         }t        5  t        j	                  |       }||\  }}||z
  t
        k  r|cddd       S ddd       	 t         |              }t        5  ||ft        | <   ddd       |S # 1 sw Y   6xY w# t        $ r d}Y 5w xY w# 1 sw Y   |S xY w)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_cachedri   ~   s    
..
C	 $$R(IBRx// 
		RT
 
"El 
L 
	  	Ls)   *BB .B!BBB!B+c                  b    t         5  t        j                          ddd       y# 1 sw Y   yxY w)zDrop all cached ``check_fn`` results. Call after config changes that
    affect tool availability (e.g. ``hermes tools enable``).N)rb   r]   clearr\   r   r   invalidate_check_fn_cacherl      s     
 
		s   %.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	 	 	 	 	 	 	 	 d0d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d1dee	   d edee   fd!Zde	d"ede	fd#Zd2d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#d3d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)d1d efd/Z*y	)4ToolRegistryzISingleton registry that collects tool schemas + handlers from tool files.c                 n    i | _         i | _        i | _        t        j                         | _        d| _        y )Nr   )_tools_toolset_checks_toolset_aliases	threadingRLock_lock_generationrU   s    r   rV   zToolRegistry.__init__   s3    ,.4602 __&
 !"r   r
   c                     | j                   5  t        | j                  j                               t	        | j
                        fcddd       S # 1 sw Y   yxY w)zBReturn a coherent snapshot of registry entries and toolset checks.N)ru   listrp   valuesdictrq   rw   s    r   _snapshot_statezToolRegistry._snapshot_state   s8    ZZ**,-tD4H4H/II ZZs   8AAc                 (    | j                         d   S )z4Return a stable snapshot of registered tool entries.r   r|   rw   s    r   _snapshot_entrieszToolRegistry._snapshot_entries       ##%a((r   c                 (    | j                         d   S )z8Return a stable snapshot of toolset availability checks.   r~   rw   s    r   _snapshot_toolset_checksz%ToolRegistry._snapshot_toolset_checks   r   r   rJ   checkNc                 t    |sy	 t         |             S # t        $ r t        j                  d|       Y yw xY w)zQRun a toolset check, treating missing or failing checks as unavailable/available.Tz,Toolset %s check raised; marking unavailableF)re   r=   r>   debugrU   rJ   r   s      r   _evaluate_toolset_checkz$ToolRegistry._evaluate_toolset_check   s:    	=  	LLGQ	s    77r8   c                 |    | j                   5  | j                  j                  |      cddd       S # 1 sw Y   yxY w)z0Return a registered tool entry by name, or None.N)ru   rp   rc   )rU   r8   s     r   	get_entryzToolRegistry.get_entry   s#    ZZ;;??4( ZZ   2;c                 n    t        | j                         D ch c]  }|j                   c}      S c c}w )z;Return sorted unique toolset names present in the registry.)r6   r   rJ   rU   entrys     r   get_registered_toolset_namesz)ToolRegistry.get_registered_toolset_names   s.    $2H2H2JK2Ju}}2JKLLKs   2c                 H    t        fd| j                         D              S )z:Return sorted tool names registered under a given toolset.c              3   T   K   | ]  }|j                   k(  r|j                   ! y wr    )rJ   r8   )r!   r   rJ   s     r   r#   z:ToolRegistry.get_tool_names_for_toolset.<locals>.<genexpr>   s)      
$<5}}' JJ$<s   %(r6   r   )rU   rJ   s    `r   get_tool_names_for_toolsetz'ToolRegistry.get_tool_names_for_toolset   s&     
$($:$:$<
 
 	
r   aliasc                    | j                   5  | j                  j                  |      }|r||k7  rt        j	                  d|||       || j                  |<   | xj
                  dz  c_        ddd       y# 1 sw Y   yxY w)z8Register an explicit alias for a canonical toolset name.z4Toolset alias collision: '%s' (%s) overwritten by %sr   N)ru   rr   rc   r>   r?   rv   )rU   r   rJ   existings       r   register_toolset_aliasz#ToolRegistry.register_toolset_alias   sl    ZZ,,007HH/J8W ,3D!!%(! ZZs   AA55A>c                 p    | j                   5  t        | j                        cddd       S # 1 sw Y   yxY w)z=Return a snapshot of ``{alias: canonical_toolset}`` mappings.N)ru   r{   rr   rw   s    r   get_registered_toolset_aliasesz+ToolRegistry.get_registered_toolset_aliases   s!    ZZ--. ZZs   ,5c                 |    | j                   5  | j                  j                  |      cddd       S # 1 sw Y   yxY w)z8Return the canonical toolset name for an alias, or None.N)ru   rr   rc   )rU   r   s     r   get_toolset_alias_targetz%ToolRegistry.get_toolset_alias_target   s'    ZZ((,,U3 ZZr   rK   rL   rM   rN   rO   rP   rQ   rR   rS   overridec                    | j                   5  | j                  j                  |      }|r|j                  |k7  r|j                  j	                  d      xr |j	                  d      }|r#t
        j                  d|||j                         nQ|r#t
        j                  d|||j                         n,t
        j                  d|||j                         	 ddd       yt        ||||||xs g ||xs |j                  dd      |	|
|      | j                  |<   |r|| j                  vr|| j                  |<   | xj                  d	z  c_        ddd       y# 1 sw Y   yxY w)
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.
        zmcp-z8Tool '%s': MCP toolset '%s' overwriting MCP toolset '%s'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.NrP    rI   r   )ru   rp   rc   rJ   
startswithr>   r   infoerrorrH   rq   rv   )rU   r8   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   r   r   both_mcps                  r   r   zToolRegistry.register   s[   . ZZ{{t,HH,,7 $$//7 3**62  LLRgx'7'7  KK1gx'7'7 LLN gx'7'7 A ZB !*!)/R!'H6::mR+H&;)A!DKK G4+?+??08$$W-!a ZZs   CEA/EEc                 &   | j                   5  | j                  j                  |d      
	 ddd       yt        fd| j                  j	                         D              }|sh| j
                  j                  j                  d       | j                  j                         D ci c]  \  }}|j                  k7  r|| c}}| _        | xj                  dz  c_	        ddd       t        j                  d|       yc c}}w # 1 sw Y   &xY w)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   P   K   | ]  }|j                   j                   k(    y wr    )rJ   )r!   rE   r   s     r   r#   z*ToolRegistry.deregister.<locals>.<genexpr>@  s#      '4Hq		U]]*4Hs   #&r   zDeregistered tool: %s)ru   rp   popr)   rz   rq   rJ   rr   itemsrv   r>   r   )rU   r8   toolset_still_existsr   targetr   s        @r   
deregisterzToolRegistry.deregister3  s     ZZKKOOD$/E} Z $' '48KK4F4F4H' $  ($$((= *.)>)>)D)D)F))Fv. 6M)F)%
 !! " 	,d3) Zs#    DA1D(DDDD
tool_namesquietc                    g }i }| j                         D ci c]  }|j                  | }}t        |      D ]  }|j                  |      }|s|j                  rX|j                  |vr"t        |j                        ||j                  <   ||j                     s|st        j                  d|       {i |j                  d|j                  i}|j                  2	 |j                         }	t        |	t              r|j                  |	       |j                  d|d        |S c c}w # t        $ r!}
t        j                  d||
       Y d}
~
Bd}
~
ww xY w)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.
        z"Tool %s unavailable (check failed)r8   NzCdynamic_schema_overrides for tool %s raised %s; using static schemafunction)typer   )r   r8   r6   rc   rM   ri   r>   r   rK   rS   r   r{   updater=   r?   r<   )rU   r   r   resultcheck_resultsr   entries_by_namer8   schema_with_name	overridesexcs              r   get_definitionszToolRegistry.get_definitionsQ  sI     /1:>:P:P:RS:R5::u,:RS:&D#''-E~~>>64DU^^4TM%..1$U^^4 %I4PC%,,C

C --9	 % > > @I!)T2(//	: MM:;KLM; '< ? T0 ! NN.c s   D%1D**	E3EEargsc                    | j                  |      }|st        j                  dd| i      S 	 |j                  rddlm}  | |j                  |fi |      S  |j                  |fi |S # t        $ rt}t        j                  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)
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: )_sanitize_tool_errorN)r   jsondumpsrO   model_toolsr   rL   r=   r>   	exceptionr   rW   r   )
rU   r8   r   kwargsr   r   rE   rawr   	sanitizeds
             r   dispatchzToolRegistry.dispatch  s     t$::w.(?@AA	4~~2!-%--"?"?@@ 5==000 	494C ,DG,<,<+=RsCC <05	  	 ::w	233	4sG   *A- A- -	C*62C%)B87C%8CC%CC%C*%C*defaultc                 p    | j                  |      }|r|j                  |j                  S ||S ddlm} |S )zBReturn per-tool max result size, or *default* (or global default).r   )DEFAULT_RESULT_SIZE_CHARS)r   rR   tools.budget_configr   )rU   r8   r   r   r   s        r   get_max_result_sizez ToolRegistry.get_max_result_size  s>    t$U00<...NA((r   c                 B    t        d | j                         D              S )z0Return sorted list of all registered tool names.c              3   4   K   | ]  }|j                     y wr    )r8   )r!   r   s     r   r#   z2ToolRegistry.get_all_tool_names.<locals>.<genexpr>  s     G.FUejj.Fs   r   rw   s    r   get_all_tool_nameszToolRegistry.get_all_tool_names  s    Gd.D.D.FGGGr   c                 D    | j                  |      }|r|j                  S 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   rK   rU   r8   r   s      r   
get_schemazToolRegistry.get_schema  s#     t$$u||.$.r   c                 D    | j                  |      }|r|j                  S dS )z.Return the toolset a tool belongs to, or None.N)r   rJ   r   s      r   get_toolset_for_toolz!ToolRegistry.get_toolset_for_tool  s!    t$ %u}}/4/r   c                 \    | j                  |      }|r|j                  r|j                  S |S )z3Return the emoji for a tool, or *default* if unset.)r   rQ   )rU   r8   r   r   s       r   	get_emojizToolRegistry.get_emoji  s'    t$$B'Br   c                 r    | j                         D ci c]  }|j                  |j                   c}S c c}w )z?Return ``{tool_name: toolset_name}`` for every registered tool.)r   r8   rJ   r   s     r   get_tool_to_toolset_mapz$ToolRegistry.get_tool_to_toolset_map  s2    7;7M7M7OP7Oe

EMM)7OPPPs   4c                     | j                   5  | j                  j                  |      }ddd       | j                  |      S # 1 sw Y   xY w)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)ru   rq   rc   r   r   s      r   is_toolset_availablez!ToolRegistry.is_toolset_available  sA     ZZ((,,W5E ++GU;; Zs   AAc           
          | j                         \  }}t        |D ch c]  }|j                   c}      }|D ci c]$  }|| j                  ||j	                  |            & c}S c c}w c c}w )z7Return ``{toolset: available_bool}`` for every toolset.)r|   r6   rJ   r   rc   )rU   entriestoolset_checksr   toolsetsrJ   s         r   check_toolset_requirementsz'ToolRegistry.check_toolset_requirements  s{    "&"6"6"8g>gU5==g>? $
# T11'>;M;Mg;VWW#
 	
 ?
s
   A')A,c                 r   i }| j                         \  }}|D ]  }|j                  }||vr)| j                  ||j                  |            g dg d||<   ||   d   j	                  |j
                         |j                  sj|j                  D ]$  }|||   d   vs||   d   j	                  |       &  |S )z'Return toolset metadata for UI display.r   )	availabletoolsrP   requirementsr   r   )r|   rJ   r   rc   r<   r8   rN   )rU   r   r   r   r   rh   envs          r   get_available_toolsetsz#ToolRegistry.get_available_toolsets  s    $&"&"6"6"8EB!!%!=!=N..r2"  #%$&  RL!((4!! --C(2,~">> ^4;;C@ .   r   c                 b   i }| j                         \  }}|D ]  }|j                  }||vr|g |j                  |      dg d||<   |j                  ||   d   vr!||   d   j	                  |j                         |j
                  D ]$  }|||   d   vs||   d   j	                  |       &  |S )zABuild a TOOLSET_REQUIREMENTS-compatible dict for backward compat.N)r8   env_varsrM   	setup_urlr   r   r   )r|   rJ   rc   r8   r<   rN   )rU   r   r   r   r   rh   r   s          r   get_toolset_requirementsz%ToolRegistry.get_toolset_requirements  s    "$"&"6"6"8EB " . 2 22 6!%r
 zzG!44r
7#**5::6))fRj442Jz*11#6 *  r   c           
         g }g }t               }| j                         \  }}|D ]  }|j                  }||v r|j                  |       | j	                  ||j                  |            r|j                  |       X|j                  ||j                  |D 	cg c]  }	|	j                  |k(  s|	j                    c}	d        ||fS c c}	w )zDReturn (available_toolsets, unavailable_info) like the old function.)r8   r   r   )	setr|   rJ   addr   rc   r<   rN   r8   )
rU   r   r   unavailableseenr   r   r   rh   rE   s
             r   check_tool_availabilityz$ToolRegistry.check_tool_availability	  s    	u"&"6"6"8EBTzHHRL++B0B0B20FG  $"" % 2 2.5IgbaffgI$   +%% Js   C	,C	)NNFr   r   NNF)Fr    )u   ⚡)+rW   rX   rY   rZ   rV   tupler   rH   r   r&   r   r|   r   r   re   r   r   r   r   r   r   r   r   r{   ry   intfloatr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r\   r   r   rn   rn      s   S"JtIS(]8K'K!L J
)4	? ))$sH}*= )s 8d? t )c )hy&9 )
Md3i M
# 
$s) 

"C 
"# 
"$ 
"/S#X /
4c 4hsm 4 "!48-1G"G" G" 	G"
 G" G" G" G" G" G"  #U{T1G" #+G" G"R4s 4t 4</#c( /4 /DQUJ /j4S 4 43 4@) )cEkD6H )TWZ_T_ )HDI H/s /x~ /0 0# 0
Cc CC CC C
Qc3h Q<C <D <
DdO 
S$Y ,$sDy/ *&T &r   rn   c                 p    dt        |       i}|r|j                  |       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&   r   r   r   )messageextrar   s      r   
tool_errorr   3  s2     s7|$Fe::f511r   c                 b    | 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"}'
    Fr   )r   r   )datar   s     r   tool_resultr   A  s,     zz$U33::f511r   r    )r
   N)&rZ   r   r:   r   loggingrs   r`   pathlibr   typingr   r   r   r   r   	getLoggerrW   r>   ASTre   r   r-   r&   rF   rH   rd   r]   r   r   __annotations__Lockrb   ri   rl   rn   r   r   r   r\   r   r   <module>r      s           6 6			8	$
SWW 
 
G G$ Ghtn S	 (A AX  68heTk 223 8%y~~'  d $ E& E&R >&2C 22 2r   