
    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ZddZddZddZdS )u  
Browser Provider Registry
=========================

Central map of registered cloud browser providers. Populated by plugins at
import-time via :meth:`PluginContext.register_browser_provider`; consumed by
:func:`tools.browser_tool._get_cloud_provider` to route each cloud-mode
``browser_*`` tool call to the active backend.

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

1. ``browser.cloud_provider`` in ``config.yaml`` (explicit override).
2. Legacy preference order — ``browser-use`` → ``browserbase`` — filtered by
   availability. Matches the historic auto-detect order in
   :func:`tools.browser_tool._get_cloud_provider` (Browser Use checked first
   because it covers both the managed Nous gateway and direct API key path;
   Browserbase as the older direct-credentials fallback). ``firecrawl`` is
   intentionally NOT in the legacy walk — users only get Firecrawl as a
   cloud browser when they explicitly set ``browser.cloud_provider:
   firecrawl``, matching pre-migration behaviour where Firecrawl was never
   auto-selected.
3. Otherwise ``None`` — the dispatcher falls back to local browser mode.

The explicit-config branch (rule 1) intentionally ignores ``is_available()``
so the dispatcher surfaces a typed "X_API_KEY is not set" error to the user
instead of silently switching backends. Matches the legacy
:func:`tools.browser_tool._get_cloud_provider` behaviour for configured names.

Note: there is no "capability" split here (unlike the web subsystem, which
has search/extract/crawl). Every browser provider implements the full
:class:`agent.browser_provider.BrowserProvider` lifecycle; the registry's
job is purely selection, not capability routing.
    )annotationsN)DictListOptional)BrowserProviderzDict[str, BrowserProvider]
_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 cloud browser 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 BrowserProvider instance, got z1Browser provider .name must be a non-empty stringNz,Browser provider '%s' re-registered (was %r)z%Registered browser provider '%s' (%s))
isinstancer   	TypeErrortype__name__namestrstrip
ValueError_lockr   getloggerdebug)r	   r   existings      :/home/kuhnn/.hermes/hermes-agent/agent/browser_registry.pyregister_providerr   4   s`    h00 
->>*- -
 
 	
 =DdC   N

 NLMMM	 $ $>>$''#
4$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ :$x..)	
 	
 	
 	
 	

 	3$x..)	
 	
 	
 	
 	
s    %B11B58B5List[BrowserProvider]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>V   s    qv     )key)r   listr   valuessorted)itemss    r   list_providersr(   R   s    	 * *Z&&(())* * * * * * * * * * * * * * *%--....s   ';??r   r   Optional[BrowserProvider]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+   Y   s    dC   t	 , ,~~djjll++, , , , , , , , , , , , , , , , , ,s   ,AAA)zbrowser-usebrowserbase
configuredOptional[str]c                P   t           5  t          t                    }ddd           n# 1 swxY w Y   d	d}| dk    rdS | r4|                    |           }||S t                              d|            t          D ](}|                    |          }| ||          r|c S )dS )
u  Resolve the active browser provider.

    Resolution rules (in order):

    1. **Explicit "local".** Returns None — the dispatcher disables cloud
       mode entirely. Mirrors legacy short-circuit in
       :func:`tools.browser_tool._get_cloud_provider`.
    2. **Explicit config wins, ignoring availability.** If ``configured``
       names a registered provider, return it even if its
       :meth:`is_available` returns False — the dispatcher will surface a
       precise "X_API_KEY is not set" error instead of silently routing
       somewhere else.
    3. **Legacy preference walk, filtered by availability.** Walk
       :data:`_LEGACY_PREFERENCE` (``browser-use`` → ``browserbase``) looking
       for a provider whose ``is_available()`` is True.

    There is intentionally NO "single-eligible shortcut" rule here (unlike
    :func:`agent.web_search_registry._resolve`). Pre-migration, the
    auto-detect branch in ``tools.browser_tool._get_cloud_provider`` only
    considered Browser Use and Browserbase; Firecrawl was reachable only
    via an explicit ``browser.cloud_provider: firecrawl`` config key.
    Preserving that gate matters because Firecrawl shares its API key with
    the *web* extract plugin (``plugins/web/firecrawl/``), so users who set
    ``FIRECRAWL_API_KEY`` for web extract must NOT get silently routed to a
    paid cloud browser on a fresh install. Third-party browser-provider
    plugins added under ``~/.hermes/plugins/browser/<vendor>/`` are subject
    to the same gate — they must be explicitly configured to take effect.

    Returns None when no provider is configured AND no available provider
    matches the legacy preference; the dispatcher then falls back to local
    browser mode.
    Nr    r   r
   boolc                    	 t          |                                           S # t          $ r.}t                              d| j        |d           Y d}~dS d}~ww xY w)zDWrap ``is_available()`` so a buggy provider doesn't kill resolution.uH   Browser provider %s.is_available() raised %s — treating as unavailableT)exc_infoNF)r0   is_available	Exceptionr   warningr   )r    excs     r   _is_available_safez$_resolve.<locals>._is_available_safe   st    	(())) 	 	 	NNZd     55555	s    # 
A#AAlocalzVbrowser cloud_provider '%s' configured but not registered; falling back to auto-detect)r    r   r
   r0   )r   dictr   r   r   r   _LEGACY_PREFERENCE)r-   snapshotr7   r	   legacys        r   _resolver=   q   s   B 
 $ $
##$ $ $ $ $ $ $ $ $ $ $ $ $ $ $	 	 	 	 Wt
  
<<
++O*	
 	
 	
 %  <<''$6$6x$@$@OOO4s   )--c                    	 ddl m}   |             }|                    di           }n4# t          $ r'}t                              d|           i }Y d}~nd}~ww xY wd}t          |t                    r^d|v rZ	 ddlm	}  ||                    d                    }n4# t          $ r'}t                              d|           d}Y d}~nd}~ww xY wt          |          S )	zResolve the currently-active cloud browser provider.

    Reads ``browser.cloud_provider`` from config.yaml; falls back per the
    module docstring. Returns None for local mode or when no provider is
    available.
    r   )read_raw_configbrowserz!Could not read browser config: %sNcloud_provider) normalize_browser_cloud_providerz+normalize_browser_cloud_provider failed: %s)hermes_cli.configr?   r   r4   r   r   r   r9   tools.tool_backend_helpersrB   r=   )r?   cfgbrowser_cfgr6   r-   rB   s         r   get_active_browser_providerrG      s2   555555oggi,,   8#>>> !%J+t$$ 	)9[)H)H	SSSSSS99 011 JJ  	 	 	LLFLLLJJJJJJ	 Js,   &) 
AAA9$B 
C(C

C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   clear 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)   )r
   r   )__doc__
__future__r   logging	threadingtypingr   r   r   agent.browser_providerr   	getLoggerr   r   r   __annotations__Lockr   r   r(   r+   r:   r=   rG   rK   rJ   r"   r   <module>rU      s1  " " "H # " " " " "      ' ' ' ' ' ' ' ' ' ' 2 2 2 2 2 2		8	$	$ *,
 + + + +	
 
 
 
</ / / /, , , ,$ I I I IX       >     r"   