§
    ÷žyjü-  ã                  óè   — 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/ragecks/.hermes/hermes-agent/agent/web_search_registry.pyÚregister_providerr   0   sa  € õ �hÕ 1Ñ2Ô2ð 
Ýð-Ý˜‘>”>Ô*ð-ð -ñ
ô 
ð 	
ð Œ=€DÝ�d�CÑ Ô ð 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   €  q¤v€ ó    )Ú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š   € å�d�CÑ Ô ð ØˆtÝ	ð ,ð ,Ý�~Š~˜dŸjšj™lœlÑ+Ô+ð,ð ,ð ,ð ,ñ ,ô ,ð ,ð ,ð ,ð ,ð ,ð ,øøøð ,ð ,ð ,ð ,ð ,ð ,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_config_readonlyNz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Ø:Ð:Ð:Ð:Ð:Ð:à"Ð"Ñ$Ô$ˆØˆØð 	#ð 	#ˆGÝ˜c¥4Ñ(Ô(ð Ø�t�tØ—'’'˜'Ñ"Ô"ˆCˆCÝ�c�3ÑÔð 	 C§I¢I¡K¤Kð 	Ø—9’9‘;”;ÐøøÝð Jð Jð JÝ�ŠÐ3°S·X²X¸d±^´^À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   € ð	Ý˜ŸšÑ(Ô(Ñ)Ô)Ð)øÝð 	ð 	ð 	Ý�LŠLÐ?ÀÄÈÑMÔMÐMØ�5�5�5�5�5øøøøð	øøø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   ø€ ð ð ð ØØˆ8�A‰;Œ;ðà-Ð-¨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Í  øøø€ õ< 
ð $ð $Ý�
Ñ#Ô#ˆð$ð $ð $ñ $ô $ð $ð $ð $ð $ð $ð $øøøð $ð $ð $ð $ðð ð ð ð ð ðð ð ð ð ð Ø—<’< 
Ñ+Ô+ˆØÐ H H¨XÑ$6Ô$6ÐØˆOØÐÝ�LŠLØNØñô ð ð õ
 �LŠLØUØ˜Jñô ð ðð ð ð ð Ø—?’?Ñ$Ô$ðñ ô €Hõ ˆ8�}„}˜ÒÐØ˜Œ{Ðå$ð ð ˆØ—<’< Ñ'Ô'ˆàÐ Ø�˜Ñ"Ô"ð !à"Ð" 8Ñ,Ô,ð !ð ˆOˆOˆOøàˆ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*   € Ø�wŠw‰yŒy�ŠÑ Ô ×(Ò(¨¨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ð ð 
˜*Ð(=Ð=Ð=å˜U zÐ$;Ð$;Ð$;Ñ<Ô<ð 2Ý  yÑ1Ô1ð 	ð ð Øˆtàˆ5�ÑÔ€DðCØ9Ð9Ð9Ð9Ð9Ð9àÐÑ!Ô!ˆØœ;×,Ò,Ñ.Ô.ð 		ð 		‰KˆC�Ý˜c¥3Ñ'Ô'ð ¨s¯~ª~¸fÑ/EÔ/Eð ØØŒ~ð ØØŒ|Ð4Ò4Ð4ØØ—Y’Y˜s AÑ&Ô& qÔ)ˆFØˆu�V‰}Œ} Ò$Ð$Ø�
�
�
ð %ð		øõ ð 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 )zÄResolve 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Ð^Õ<LÈUÐT]Ñ<^Ô<^€HÝ�H¨Ð2Ñ2Ô2Ð2r#   c                 ód   — t          dd¦  «        pt          dd¦  «        } t          | d¬¦  «        S )zÆResolve 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Ð_Õ=MÈeÐU^Ñ=_Ô=_€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#   