
    PL
j&                        U d Z ddlmZ ddlZddlZddlmZmZmZ ddl	m
Z
  ej        e          Zi Zded<    ej                    ZddZddZd dZd!dZdZd"dZd#dZd#dZd#dZd$dZdS )%u[  
Web Search Provider Registry
============================

Central map of registered web providers. Populated by plugins at import-time
via :meth:`PluginContext.register_web_search_provider`; consumed by the
``web_search`` and ``web_extract`` tool wrappers in :mod:`tools.web_tools` to
dispatch each call to the active backend.

Active selection
----------------
The active provider is chosen by configuration with this precedence:

1. ``web.search_backend`` / ``web.extract_backend`` / ``web.crawl_backend``
   (per-capability override).
2. ``web.backend`` (shared fallback).
3. If exactly one capability-eligible provider is registered AND available,
   use it.
4. Legacy preference order — ``firecrawl`` → ``parallel`` → ``tavily`` →
   ``exa`` → ``searxng`` → ``brave-free`` → ``ddgs`` — filtered by
   availability. Matches the historic ``tools.web_tools._get_backend()``
   candidate order so installs that never set a config key keep landing
   on the same provider they did before the plugin migration.
5. Otherwise ``None`` — the tool surfaces a helpful error pointing at
   ``hermes tools``.

The capability filter (``supports_search`` / ``supports_extract`` /
``supports_crawl``) is applied at every step so a search-only provider
(``brave-free``) configured as ``web.extract_backend`` correctly falls
through to an extract-capable backend.
    )annotationsN)DictListOptional)WebSearchProviderzDict[str, WebSearchProvider]
_providersproviderr   returnNonec                <   t          | t                    s$t          dt          |           j                   | j        }t          |t                    r|                                st          d          t          5  t                              |          }| t          |<   ddd           n# 1 swxY w Y   |0t                              d|t          |          j                   dS t                              d|t          |           j                   dS )u   Register a web search/extract provider.

    Re-registration (same ``name``) overwrites the previous entry and logs
    a debug message — makes hot-reload scenarios (tests, dev loops) behave
    predictably.
    z>register_provider() expects a WebSearchProvider instance, got z-Web provider .name must be a non-empty stringNz(Web provider '%s' re-registered (was %r)z!Registered web provider '%s' (%s))
isinstancer   	TypeErrortype__name__namestrstrip
ValueError_lockr   getloggerdebug)r	   r   existings      =/home/kuhnn/.hermes/hermes-agent/agent/web_search_registry.pyregister_providerr   0   sa    h 122 
->>*- -
 
 	
 =DdC   J

 JHIII	 $ $>>$''#
4$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ 6$x..)	
 	
 	
 	
 	

 	/$x..)	
 	
 	
 	
 	
s    %B11B58B5List[WebSearchProvider]c                     t           5  t          t                                                    } ddd           n# 1 swxY w Y   t	          | d           S )z0Return all registered providers, sorted by name.Nc                    | j         S )Nr   )ps    r   <lambda>z list_providers.<locals>.<lambda>R   s    qv     )key)r   listr   valuessorted)itemss    r   list_providersr(   N   s    	 * *Z&&(())* * * * * * * * * * * * * * *%--....s   ';??r   r   Optional[WebSearchProvider]c                    t          | t                    sdS t          5  t                              |                                           cddd           S # 1 swxY w Y   dS )z5Return the provider registered under *name*, or None.N)r   r   r   r   r   r   r   s    r   get_providerr+   U   s    dC   t	 , ,~~djjll++, , , , , , , , , , , , , , , , , ,s   ,AAApathOptional[str]c                    	 ddl m}  |            }|}| D ]/}t          |t                    s dS |                    |          }0t          |t
                    r(|                                r|                                S nF# t          $ r9}t          	                    dd
                    |           |           Y d}~nd}~ww xY wdS )zGResolve a dotted config key from ``config.yaml``. Returns None on miss.r   )load_configNzCould not read config %s: %s.)hermes_cli.configr/   r   dictr   r   r   	Exceptionr   r   join)r,   r/   cfgcursegmentexcs         r   _read_config_keyr9   b   s    J111111kmm 	# 	#Gc4(( tt'''""CCc3 	CIIKK 	99;; J J J3SXXd^^SIIIIIIIIJ4s   ,B AB 
C/CC)	firecrawlparalleltavilyexasearxngz
brave-freeddgs
configured
capabilityc               4   t           5  t          t                    }ddd           n# 1 swxY w Y   dfddd| r^|                    |           }| |          r|S |t                              d|            nt                              d	|            fd
|                                D             }t          |          dk    r|d         S t          D ]3}|                    |          }| |          r |          r|c S 4dS )u%  Resolve the active provider for a capability ("search" | "extract" | "crawl").

    Resolution rules (in order):

    1. **Explicit config wins, ignoring availability.** If
       ``web.{capability}_backend`` or ``web.backend`` names a registered
       provider that supports *capability*, return it even if its
       :meth:`is_available` returns False — the dispatcher will surface a
       precise "X_API_KEY is not set" error to the user instead of silently
       routing somewhere else. Matches legacy
       :func:`tools.web_tools._get_backend` behavior for configured names.

    2. **Single-provider shortcut.** When only one registered provider
       supports *capability* AND ``is_available()`` reports True, return it.

    3. **Legacy preference walk, filtered by availability.** Walk the
       :data:`_LEGACY_PREFERENCE` order (firecrawl → parallel → tavily →
       exa → searxng → brave-free → ddgs) looking for a provider whose
       ``supports_<capability>()`` is True AND whose ``is_available()`` is
       True. Matches the historic ``tools.web_tools._get_backend()``
       candidate order so users with credentials but no explicit config
       key keep landing on the same provider as pre-migration. This is
       the path that fires when no config key is set — pick the
       highest-priority backend the user actually has credentials for.

    Returns None when no provider is configured AND no available provider
    matches the legacy preference; the dispatcher then returns a "set up a
    provider" error to the user.
    Nr    r   r
   boolc                    dk    r!t          |                                           S dk    r!t          |                                           S dk    r!t          |                                           S dS )NsearchextractcrawlF)rC   supports_searchsupports_extractsupports_crawl)r    rA   s    r   _capablez_resolve.<locals>._capable   sw    !!))++,,,""**,,---  ((**+++ur"   c                    	 t          |                                           S # t          $ r,}t                              d| j        |           Y d}~dS d}~ww xY w)zDWrap ``is_available()`` so a buggy provider doesn't kill resolution.z$provider %s.is_available() raised %sNF)rC   is_availabler3   r   r   r   )r    r8   s     r   _is_available_safez$_resolve.<locals>._is_available_safe   sd    	(())) 	 	 	LL?MMM55555	s    # 
A!AAz<web backend '%s' configured but not registered; falling backzCweb backend '%s' configured but does not support '%s'; falling backc                @    g | ]} |           |          |S  rP   ).0r    rK   rN   s     r   
<listcomp>z_resolve.<locals>.<listcomp>   sJ       8A;;--a00	  r"      r   )r    r   r
   rC   )	r   r2   r   r   r   r   r%   len_LEGACY_PREFERENCE)r@   rA   snapshotr	   eligiblelegacyrK   rN   s    `    @@r   _resolverY      s   < 
 $ $
##$ $ $ $ $ $ $ $ $ $ $ $ $ $ $          <<
++HHX$6$6OLLN   
 LLUJ      ??$$  H 8}}{$  <<'' "" !""8,, ! OOO4s   ,00c                 d    t          dd          pt          dd          } t          | d          S )zResolve the currently-active web search provider.

    Reads ``web.search_backend`` (preferred) or ``web.backend`` (shared
    fallback) from config.yaml; falls back per the module docstring.
    websearch_backendbackendrE   rA   r9   rY   explicits    r   get_active_search_providerrb      s8      '788^<LUT]<^<^HH2222r"   c                 d    t          dd          pt          dd          } t          | d          S )zResolve the currently-active web extract provider.

    Reads ``web.extract_backend`` (preferred) or ``web.backend`` (shared
    fallback) from config.yaml; falls back per the module docstring.
    r[   extract_backendr]   rF   r^   r_   r`   s    r   get_active_extract_providerre      s8      '899_=MeU^=_=_HH3333r"   c                 d    t          dd          pt          dd          } t          | d          S )u  Resolve the currently-active web crawl provider.

    Reads ``web.crawl_backend`` (preferred) or ``web.backend`` (shared
    fallback) from config.yaml; falls back per the module docstring.

    Crawl is a niche capability — among built-in providers only Tavily and
    Firecrawl implement it. Callers should expect ``None`` and fall back to
    a different strategy (e.g. summarize-via-LLM) when neither is
    configured.
    r[   crawl_backendr]   rG   r^   r_   r`   s    r   get_active_crawl_providerrh      s7      77];KES\;];]HH1111r"   c                 x    t           5  t                                           ddd           dS # 1 swxY w Y   dS )z"Clear the registry. **Test-only.**N)r   r   clearrP   r"   r   _reset_for_testsrk     s~    	                   s   /33)r	   r   r
   r   )r
   r   )r   r   r
   r)   )r,   r   r
   r-   )r@   r-   rA   r   r
   r)   )r
   r)   )r
   r   )__doc__
__future__r   logging	threadingtypingr   r   r   agent.web_search_providerr   	getLoggerr   r   r   __annotations__Lockr   r   r(   r+   r9   rU   rY   rb   re   rh   rk   rP   r"   r   <module>ru      sm    @ # " " " " "      ' ' ' ' ' ' ' ' ' ' 7 7 7 7 7 7		8	$	$ ,.
 - - - -	
 
 
 
</ / / /, , , ,   0 X X X Xv3 3 3 34 4 4 42 2 2 2     r"   