
    NQj-                        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d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``
   (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``) 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/alina/.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.configr0   r   dictr   r   r   	Exceptionr   r   join)r-   r0   cfgcursegmentexcs         r   _read_config_keyr:   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").

    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S )NsearchextractF)rD   supports_searchsupports_extract)r!   rB   s    r   _capablez_resolve.<locals>._capable   sR    !!))++,,,""**,,---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)rD   is_availabler4   r   r   r   )r!   r9   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  rO   ).0r!   rJ   rM   s     r   
<listcomp>z_resolve.<locals>.<listcomp>   sJ       8A;;--a00	  r#      r   )r!   r   r
   rD   )	r   r3   r   r   r   r   r&   len_LEGACY_PREFERENCE)rA   rB   snapshotr	   eligiblelegacyrJ   rM   s    `    @@r   _resolverX      s   < 
 $ $
##$ $ $ $ $ $ $ $ $ $ $ $ $ $ $          <<
++HHX$6$6OLLN   
 LLUJ      ??$$  H 8}}{$  <<'' "" !""8,, ! OOO4s   ,00rB   c                   dd}| s'|dv r#t          d| d          pt          dd          } | sd	S  ||           }	 d
dlm}  |            }|j                                        D ]s\  }}t          |t                    r|                    d          s0|j        r8|j	        dk    rD|
                    dd          d         } ||          |k    r|c S tn2# t          $ r%}	t                              d|	           Y d	}	~	nd	}	~	ww xY wd	S )u  Return the plugin key of a *disabled* bundled web plugin that would
    have provided the configured backend, or None.

    When a user sets ``web.extract_backend: firecrawl`` (or the search
    equivalent) but also lists ``web-firecrawl`` in ``plugins.disabled``,
    the provider never registers and the dispatcher would otherwise emit a
    misleading "No web extract provider configured. Set web.extract_backend
    to ..." error — even though the backend IS configured correctly. The
    real fix is to re-enable the plugin. This helper detects that case so
    the dispatcher can point the user at the actual cause (issue #40190
    follow-up: pi314's disabled-plugin symptom).

    Pass ``capability`` ("search" | "extract") to resolve the configured
    name straight from ``config.yaml`` (``web.<capability>_backend`` →
    ``web.backend``). This is more reliable than the resolved backend the
    dispatcher fell back to, since a disabled provider fails the
    ``_is_backend_available`` gate and the dispatcher silently drops to
    the shared default. An explicit ``configured`` name still wins when
    given.

    Matching is by convention: bundled web plugins live under the
    ``web/<vendor>`` key with the provider ``name`` differing only in
    hyphen/underscore (``brave-free`` provider ⇄ ``web/brave_free`` key,
    ``firecrawl`` ⇄ ``web/firecrawl``). We normalize both sides before
    comparing so every bundled provider is covered without hardcoding a
    per-vendor table.
    sr   r
   c                v    |                                                                                      dd          S )N-_)r   lowerreplace)r[   s    r   _normz'_disabled_web_plugin_for.<locals>._norm   s*    wwyy  ((c222r#   )rF   rG   web_backendbackendNr   )get_plugin_managerzweb/zdisabled via config/rR   z%disabled-web-plugin lookup failed: %s)r[   r   r
   r   )r:   hermes_cli.pluginsre   _pluginsr(   r   r   
startswithenablederrorsplitr4   r   r   )
rA   rB   ra   wantre   pmr$   loadedvendorr9   s
             r   _disabled_web_plugin_forrq      s   83 3 3 3  
*(===Uz$;$;$;<< 2y11 	  t5DC999999!!;,,.. 		 		KCc3'' s~~f/E/E ~ |444YYsA&&q)FuV}}$$


 %		  C C C<cBBBBBBBBC4s   BC C 
D&DD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.
    rb   search_backendrd   rF   rY   r:   rX   explicits    r   get_active_search_providerrw     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.
    rb   extract_backendrd   rG   rY   rt   ru   s    r   get_active_extract_providerrz   #  s8      '899_=MeU^=_=_HH3333r#   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   clearrO   r#   r   _reset_for_testsr}   -  s~    	                   s   /33)r	   r   r
   r   )r
   r   )r   r   r
   r*   )r-   r   r
   r.   )rA   r.   rB   r   r
   r*   r   )rA   r.   rB   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,   r:   rT   rX   rq   rw   rz   r}   rO   r#   r   <module>r      s    @ # " " " " "      ' ' ' ' ' ' ' ' ' ' 7 7 7 7 7 7		8	$	$ ,.
 - - - -	
 
 
 
</ / / /, , , ,   0 V V V Vr8^b 8 8 8 8 8 8v3 3 3 34 4 4 4     r#   