
    `gj-                        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y)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]
_providersc                   t        | t              s!t        dt        |       j                         | j
                  }t        |t              r|j                         st        d      t        5  t        j                  |      }| t        |<   ddd       +t        j                  d|t        |      j                         yt        j                  d|t        |       j                         y# 1 sw Y   axY w)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)providerr   existings      L/root/.hermes/venv/lib/python3.12/site-packages/agent/web_search_registry.pyregister_providerr   0   s     h 12>**+-
 	
 ==DdC 

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

 	/$x.))	
$ $s   /C..C7c                     t         5  t        t        j                               } ddd       t	         d       S # 1 sw Y   xY w)z0Return all registered providers, sorted by name.Nc                    | j                   S Nr   )ps    r   <lambda>z list_providers.<locals>.<lambda>R   s
    qvv     )key)r   listr   valuessorted)itemss    r   list_providersr&   N   s;    	 *Z&&()*%-..* *s	   ;Ac                    t        | t              syt        5  t        j	                  | j                               cddd       S # 1 sw Y   yxY w)z5Return the provider registered under *name*, or None.N)r
   r   r   r   r   r   r   s    r   get_providerr(   U   s9    dC 	 ,~~djjl+, , ,s   #AAc                 P   	 ddl m}  |       }|}| D ]%  }t        |t              s y|j	                  |      }' t        |t
              r |j                         r|j                         S y# t        $ r0}t        j                  ddj                  |       |       Y d}~yd}~ww xY w)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)pathr*   cfgcursegmentexcs         r   _read_config_keyr5   b   s    J1m 	#Gc4('''"C	# c3CIIK99;   J3SXXd^SIIJs   %A, AA, ,	B%5&B  B%)	firecrawlparalleltavilyexasearxngz
brave-freeddgsc                  t         5  t        t              }ddd       dfd}dd}| rMj                  |       }|
 ||      r|S |t        j                  d|        nt        j                  d|        j                         D cg c]  } ||      r
 ||      r| }}t        |      dk(  r|d   S t        D ]+  }|j                  |      }| ||      s  ||      s)|c S  y# 1 sw Y   xY wc c}w )	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.
    Nc                ~    dk(  rt        | j                               S dk(  rt        | j                               S y)NsearchextractF)boolsupports_searchsupports_extract)r   
capabilitys    r   _capablez_resolve.<locals>._capable   s>    !))+,,"**,--r    c                    	 t        | j                               S # t        $ r+}t        j	                  d| j
                  |       Y d}~yd}~ww xY w)zDWrap ``is_available()`` so a buggy provider doesn't kill resolution.z$provider %s.is_available() raised %sNF)r@   is_availabler.   r   r   r   )r   r4   s     r   _is_available_safez$_resolve.<locals>._is_available_safe   sA    	()) 	LL?M	s    	A!A

Az<web backend '%s' configured but not registered; falling backzCweb backend '%s' configured but does not support '%s'; falling back   r   )r   r   returnr@   )	r   r-   r   r   r   r   r#   len_LEGACY_PREFERENCE)	
configuredrC   snapshotrD   rG   r   r   eligiblelegacys	    `       r   _resolverP      s   < 
 $
#$ <<
+HX$6OLLN
 LLUJ ??$A;-a0 	
H  8}{$ <<' ""8,O q$ $Ps   C.C:.C7rC   c                  dd}| s!|dv rt        d| d      xs t        dd      } | sy ||       }	 ddlm}  |       }|j                  j	                         D ]h  \  }}t        |t              r|j                  d	      s(|j                  r5|j                  d
k7  rE|j                  dd      d   } ||      |k(  sf|c S  	 y# t        $ r }	t        j                  d|	       Y d}	~	yd}	~	ww xY w)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.
    c                ^    | j                         j                         j                  dd      S )N-_)r   lowerreplace)ss    r   _normz'_disabled_web_plugin_for.<locals>._norm   s"    wwy ((c22r    )r>   r?   web_backendbackendNr   )get_plugin_managerzweb/zdisabled via config/rH   z%disabled-web-plugin lookup failed: %s)rX   r   rI   r   )r5   hermes_cli.pluginsr]   _pluginsr%   r
   r   
startswithenablederrorsplitr.   r   r   )
rL   rC   rY   wantr]   pmr!   loadedvendorr4   s
             r   _disabled_web_plugin_forri      s   83 *(==Uzl($;< 2y1 	 DC9!;;,,. 		KCc3's~~f/E~~||44YYsA&q)FV}$
		   C<cBBCs$   BC C C 	C1C,,C1c                 P    t        dd      xs 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.
    rZ   search_backendr\   r>   rQ   r5   rP   explicits    r   get_active_search_providerro     s,      '78^<LUT]<^HH22r    c                 P    t        dd      xs 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.
    rZ   extract_backendr\   r?   rQ   rl   rm   s    r   get_active_extract_providerrr   #  s,      '89_=MeU^=_HH33r    c                 b    t         5  t        j                          ddd       y# 1 sw Y   yxY w)z"Clear the registry. **Test-only.**N)r   r   clear r    r   _reset_for_testsrv   -  s&    	   s   %.)r   r   rI   None)rI   zList[WebSearchProvider])r   r   rI   Optional[WebSearchProvider])r0   r   rI   Optional[str])rL   ry   rC   r   rI   rx   r   )rL   ry   rC   ry   rI   ry   )rI   rx   )rI   rw   )__doc__
__future__r   logging	threadingtypingr   r   r   agent.web_search_providerr   	getLoggerr   r   r   __annotations__Lockr   r   r&   r(   r5   rK   rP   ri   ro   rr   rv   ru   r    r   <module>r      s   @ #   ' ' 7			8	$ ,.
( -	
</,0 Vr8^b 8v34r    