fix(browser): self-review pass — dead-import, log levels, future-proofing

Addresses findings from two self-review passes pre-merge.

First pass (3-agent parallel review):

1. plugins/browser/browser_use/provider.py: drop the
   ``_ = managed_nous_tools_enabled`` dead-import-hider in
   _get_config_or_none(). The import was actively misleading — the
   helper IS used in _get_config() (separate method, separate import),
   not here. The "keep static analysis happy" comment was wrong about
   what the helper does in this scope.

2. agent/browser_provider.py: drop ``pragma: no cover`` from
   is_configured() / provider_name() backward-compat aliases. They ARE
   covered by ``TestLegacyAbcAliases`` — the pragma would have masked
   future regressions.

3. tools/browser_tool.py: refactor _is_legacy_provider_registry_overridden()
   to compare against a module-frozen _DEFAULT_PROVIDER_REGISTRY snapshot
   instead of hardcoded set of 3 keys. Future maintainers adding a 4th
   built-in provider now just extend _PROVIDER_REGISTRY; the override
   detection adapts automatically. Previously the hardcoded
   ``set(...) != {"browserbase", "browser-use", "firecrawl"}`` would flip
   True forever on any 4-key registry, silently routing every install
   onto the legacy fixture path.

4. tools/browser_tool.py: when explicit ``browser.cloud_provider`` is set
   but the registry has no matching plugin (typo, uninstalled plugin,
   discovery failure), emit a WARNING with actionable text instead of
   silently falling through to auto-detect. Legacy code surfaced a typed
   credentials error via direct class instantiation; this log restores
   the signal in the post-migration path.

5. agent/browser_registry.py: trim the triple-redundant _LEGACY_PREFERENCE
   documentation. Module docstring + 13-line block-comment + 5-line
   inline comment was repeating the same point. Kept the docstring and
   trimmed the block-comment to 5 lines.

6. agent/browser_registry.py: upgrade is_available()-raised logging from
   DEBUG to WARNING with exc_info=True. A provider's availability check
   throwing is unusual enough that users debugging "no cloud provider"
   need the traceback in logs.

7. tests/plugins/browser/check_parity_vs_main.py: drop dead top-level
   imports (os, shutil, tempfile — only referenced inside the
   SUBPROCESS_SCRIPT string literal that runs in a child process).

Second pass (architecture + claim-verification review):

8. tools/browser_tool.py: rewrite the inline comment in _get_cloud_provider
   auto-detect branch. Prior text claimed it "routes through the plugin
   registry's legacy preference walk so third-party plugins still get a
   chance to be selected when they're explicitly configured" — false on
   both counts. The branch uses module-level legacy class aliases
   (BrowserUseProvider / BrowserbaseProvider) directly; third-party
   plugins are intentionally reachable only via explicit
   ``browser.cloud_provider``. Corrected comment now matches behaviour
   and cross-references _LEGACY_PREFERENCE for the firecrawl gate
   rationale.

9. tools/browser_tool.py + tests/tools/test_managed_browserbase_and_modal.py:
   drop the unused ``get_active_browser_provider as
   _registry_get_active_browser_provider`` alias from the
   ``from agent.browser_registry import ...`` block. It was never
   referenced; matching test-stub line in the agent.browser_registry
   SimpleNamespace also dropped. ``get_provider`` is still imported (used
   by the explicit-config dispatch path at line 535).

10. plugins/browser/firecrawl/provider.py: align emergency_cleanup()
    with the early-guard pattern used in browserbase + browser_use
    plugins. Previously firecrawl tried the DELETE and relied on
    ``_headers()`` raising ValueError to trip a "missing credentials"
    warning; same final outcome but a different control flow that read
    like a bug to a maintainer skimming the three modules. Now: if
    is_available() is False, log+return early — identical shape to the
    other two providers.

Verification: 54/54 unit tests + 13/13 parity scenarios still pass.
This commit is contained in:
kshitijk4poor
2026-05-14 14:45:29 +05:30
committed by Teknium
parent 1bb6f03724
commit c74ff2c8ef
7 changed files with 62 additions and 57 deletions

View File

@ -166,10 +166,10 @@ class BrowserProvider(abc.ABC):
# and :attr:`name`; they may override ``is_configured`` / ``provider_name`` # and :attr:`name`; they may override ``is_configured`` / ``provider_name``
# for compatibility with the legacy ABC but it is not required. # for compatibility with the legacy ABC but it is not required.
def is_configured(self) -> bool: # pragma: no cover - trivial delegation def is_configured(self) -> bool:
"""Backward-compat alias for :meth:`is_available`.""" """Backward-compat alias for :meth:`is_available`."""
return self.is_available() return self.is_available()
def provider_name(self) -> str: # pragma: no cover - trivial delegation def provider_name(self) -> str:
"""Backward-compat alias returning :attr:`display_name`.""" """Backward-compat alias returning :attr:`display_name`."""
return self.display_name return self.display_name

View File

@ -99,19 +99,11 @@ def get_provider(name: str) -> Optional[BrowserProvider]:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Legacy preference order — preserves behaviour for users who set no # Legacy auto-detect order — used when no ``browser.cloud_provider`` is set.
# ``browser.cloud_provider`` config key. Matches the historic auto-detect # Matches the pre-migration walk in :func:`tools.browser_tool._get_cloud_provider`.
# order in :func:`tools.browser_tool._get_cloud_provider` (Browser Use first # Firecrawl is intentionally absent so users with ``FIRECRAWL_API_KEY`` set
# because it covers both managed Nous gateway and direct API key; Browserbase # for web-extract don't get silently routed to a paid cloud browser. See
# second as the older direct-credentials fallback). Filtered by # :func:`_resolve` for the full rationale.
# ``is_available()`` at walk time so we don't surface a provider the user
# has no credentials for.
#
# Note: ``firecrawl`` is intentionally absent. Pre-migration, the auto-detect
# branch only considered Browser Use → Browserbase; Firecrawl was reachable
# only via an explicit ``browser.cloud_provider: firecrawl`` config key.
# Preserving that gate prevents users with a ``FIRECRAWL_API_KEY`` set for
# web-extract from accidentally getting routed to a (paid) cloud browser.
_LEGACY_PREFERENCE = ( _LEGACY_PREFERENCE = (
"browser-use", "browser-use",
"browserbase", "browserbase",
@ -159,7 +151,10 @@ def _resolve(configured: Optional[str]) -> Optional[BrowserProvider]:
try: try:
return bool(p.is_available()) return bool(p.is_available())
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
logger.debug("provider %s.is_available() raised %s", p.name, exc) logger.warning(
"Browser provider %s.is_available() raised %s — treating as unavailable",
p.name, exc, exc_info=True,
)
return False return False
# 1. Explicit "local" short-circuit. # 1. Explicit "local" short-circuit.

View File

@ -130,9 +130,10 @@ class BrowserUseBrowserProvider(BrowserProvider):
# managed_tool_gateway pulls in the Nous auth stack which can be # managed_tool_gateway pulls in the Nous auth stack which can be
# heavy and is not needed for direct-API-key users. # heavy and is not needed for direct-API-key users.
from tools.managed_tool_gateway import resolve_managed_tool_gateway from tools.managed_tool_gateway import resolve_managed_tool_gateway
from tools.tool_backend_helpers import managed_nous_tools_enabled, prefers_gateway from tools.tool_backend_helpers import prefers_gateway
# 1. Direct API key path (unless user explicitly prefers gateway). # Direct API key wins unless the user has explicitly opted into the
# managed Nous gateway via ``tool_gateway.browser: gateway``.
api_key = os.environ.get("BROWSER_USE_API_KEY") api_key = os.environ.get("BROWSER_USE_API_KEY")
if api_key and not prefers_gateway("browser"): if api_key and not prefers_gateway("browser"):
return { return {
@ -141,16 +142,10 @@ class BrowserUseBrowserProvider(BrowserProvider):
"managed_mode": False, "managed_mode": False,
} }
# 2. Managed Nous gateway path.
managed = resolve_managed_tool_gateway("browser-use") managed = resolve_managed_tool_gateway("browser-use")
if managed is None: if managed is None:
return None return None
# Hold reference to managed_nous_tools_enabled so static analysis
# doesn't flag the import as unused — the helper is consulted by
# _get_config() below to compose a more accurate error message.
_ = managed_nous_tools_enabled
return { return {
"api_key": managed.nous_user_token, "api_key": managed.nous_user_token,
"base_url": managed.gateway_origin.rstrip("/"), "base_url": managed.gateway_origin.rstrip("/"),

View File

@ -130,17 +130,18 @@ class FirecrawlBrowserProvider(BrowserProvider):
return False return False
def emergency_cleanup(self, session_id: str) -> None: def emergency_cleanup(self, session_id: str) -> None:
if not self.is_available():
logger.warning(
"Cannot emergency-cleanup Firecrawl session %s — missing credentials",
session_id,
)
return
try: try:
requests.delete( requests.delete(
f"{self._api_url()}/v2/browser/{session_id}", f"{self._api_url()}/v2/browser/{session_id}",
headers=self._headers(), headers=self._headers(),
timeout=5, timeout=5,
) )
except ValueError:
logger.warning(
"Cannot emergency-cleanup Firecrawl session %s — missing credentials",
session_id,
)
except Exception as e: except Exception as e:
logger.debug( logger.debug(
"Emergency cleanup failed for Firecrawl session %s: %s", session_id, e "Emergency cleanup failed for Firecrawl session %s: %s", session_id, e

View File

@ -19,11 +19,8 @@ Run from the PR worktree:
from __future__ import annotations from __future__ import annotations
import json import json
import os
import shutil
import subprocess import subprocess
import sys import sys
import tempfile
from pathlib import Path from pathlib import Path

View File

@ -106,7 +106,6 @@ def _install_fake_tools_package():
BrowserProvider=_StubBrowserProvider, BrowserProvider=_StubBrowserProvider,
) )
sys.modules["agent.browser_registry"] = types.SimpleNamespace( sys.modules["agent.browser_registry"] = types.SimpleNamespace(
get_active_browser_provider=lambda: None,
get_provider=lambda name: None, get_provider=lambda name: None,
list_providers=lambda: [], list_providers=lambda: [],
register_provider=lambda provider: None, register_provider=lambda provider: None,

View File

@ -90,7 +90,6 @@ except Exception:
# shims for callers that import them from this module. # shims for callers that import them from this module.
from agent.browser_provider import BrowserProvider as CloudBrowserProvider # noqa: F401 (legacy alias) from agent.browser_provider import BrowserProvider as CloudBrowserProvider # noqa: F401 (legacy alias)
from agent.browser_registry import ( # noqa: F401 (test-patchable surface) from agent.browser_registry import ( # noqa: F401 (test-patchable surface)
get_active_browser_provider as _registry_get_active_browser_provider,
get_provider as _registry_get_browser_provider, get_provider as _registry_get_browser_provider,
) )
from plugins.browser.browserbase.provider import ( # noqa: F401 (legacy import surface) from plugins.browser.browserbase.provider import ( # noqa: F401 (legacy import surface)
@ -425,6 +424,10 @@ _PROVIDER_REGISTRY: Dict[str, type] = {
"browser-use": BrowserUseProvider, "browser-use": BrowserUseProvider,
"firecrawl": FirecrawlProvider, "firecrawl": FirecrawlProvider,
} }
# Frozen copy of the import-time _PROVIDER_REGISTRY, used by
# ``_is_legacy_provider_registry_overridden`` to detect test-time
# monkeypatching. NEVER mutate this dict.
_DEFAULT_PROVIDER_REGISTRY: Dict[str, type] = dict(_PROVIDER_REGISTRY)
_cached_cloud_provider: Optional[CloudBrowserProvider] = None _cached_cloud_provider: Optional[CloudBrowserProvider] = None
_cloud_provider_resolved = False _cloud_provider_resolved = False
@ -442,25 +445,23 @@ _browser_engine_resolved = False
def _is_legacy_provider_registry_overridden() -> bool: def _is_legacy_provider_registry_overridden() -> bool:
"""Return True when a test has patched ``_PROVIDER_REGISTRY`` to a custom value. """Return True when a test has patched ``_PROVIDER_REGISTRY`` to a custom value.
Detected by comparing identity with the module-level defaults dict Detected by spotting any registered class that *isn't* the canonical
populated above. Tests that ``monkeypatch.setattr(browser_tool, plugin-backed class for that name. Tests that
"_PROVIDER_REGISTRY", ...)`` swap in a new object; identity differs ``monkeypatch.setattr(browser_tool, "_PROVIDER_REGISTRY", ...)`` install
even when the contents happen to match. Used by ``_get_cloud_provider`` custom factories (`exploding_factory`, `lambda: fake_provider`, etc.);
to honour test-time overrides (which expect a factory-callable shape) those entries fail the canonical-class identity check below.
instead of routing through the plugin registry.
Note: a future maintainer adding a 4th built-in provider only needs to
extend ``_DEFAULT_PROVIDER_REGISTRY`` below — they do NOT need to update
a hardcoded set of keys here. The detection just compares each registered
value against the corresponding canonical class.
""" """
# The module-level _PROVIDER_REGISTRY is built once at import time. A test
# that swaps it via monkeypatch creates a new dict; we detect that via
# the registered class identities, not by ``is`` on the dict itself
# (the patch may install a dict whose values happen to be the same
# classes; treat that as "not overridden").
try: try:
return ( for key, default_cls in _DEFAULT_PROVIDER_REGISTRY.items():
_PROVIDER_REGISTRY.get("browserbase") is not BrowserbaseProvider if _PROVIDER_REGISTRY.get(key) is not default_cls:
or _PROVIDER_REGISTRY.get("browser-use") is not BrowserUseProvider return True
or _PROVIDER_REGISTRY.get("firecrawl") is not FirecrawlProvider # Extra keys not in the default registry → also an override.
or set(_PROVIDER_REGISTRY.keys()) != {"browserbase", "browser-use", "firecrawl"} return len(_PROVIDER_REGISTRY) != len(_DEFAULT_PROVIDER_REGISTRY)
)
except Exception: except Exception:
return False return False
@ -532,6 +533,20 @@ def _get_cloud_provider() -> Optional[CloudBrowserProvider]:
# populated. Idempotent — cheap on subsequent calls. # populated. Idempotent — cheap on subsequent calls.
_ensure_browser_plugins_loaded() _ensure_browser_plugins_loaded()
resolved = _registry_get_browser_provider(provider_key) resolved = _registry_get_browser_provider(provider_key)
if resolved is None:
# Explicit config name unknown to the registry —
# might be a typo, an uninstalled plugin, or a
# registry-population failure. Warn the user
# (legacy code would have surfaced a typed
# credentials error via direct class instantiation;
# post-migration we surface this WARNING instead).
logger.warning(
"browser.cloud_provider=%r is not a registered "
"browser plugin; falling back to auto-detect "
"(install the corresponding plugin or fix the "
"config key spelling).",
provider_key,
)
except Exception: except Exception:
logger.warning( logger.warning(
"Failed to instantiate explicit cloud_provider %r; will retry on next call", "Failed to instantiate explicit cloud_provider %r; will retry on next call",
@ -545,12 +560,15 @@ def _get_cloud_provider() -> Optional[CloudBrowserProvider]:
logger.debug("Could not read cloud_provider from config: %s", e) logger.debug("Could not read cloud_provider from config: %s", e)
if resolved is None: if resolved is None:
# Auto-detect path. When tests have patched the per-class names # Auto-detect path: Browser Use first (managed Nous gateway or
# on this module (BrowserUseProvider / BrowserbaseProvider), honour # direct API key), then Browserbase (direct credentials). Uses
# them — the test_browser_cloud_provider_cache test relies on this. # the legacy class names imported at the top of this module so
# Otherwise route through the plugin registry's legacy preference # tests that ``monkeypatch.setattr(browser_tool, "BrowserUseProvider", ...)``
# walk so third-party plugins still get a chance to be selected # keep driving this branch deterministically. Third-party browser
# when they're explicitly configured. # plugins are intentionally NOT reachable from auto-detect — they
# participate only via explicit ``browser.cloud_provider: <name>``,
# mirroring the firecrawl gate documented on
# :data:`agent.browser_registry._LEGACY_PREFERENCE`.
try: try:
fallback_provider = BrowserUseProvider() fallback_provider = BrowserUseProvider()
if fallback_provider.is_configured(): if fallback_provider.is_configured():