diff --git a/tests/tools/test_web_providers.py b/tests/tools/test_web_providers.py index bd3cce8754aa..26b85e6aca30 100644 --- a/tests/tools/test_web_providers.py +++ b/tests/tools/test_web_providers.py @@ -189,6 +189,44 @@ def test_search_backend_ignored_when_not_available(self, monkeypatch): # Should fall back to firecrawl since exa isn't configured assert web_tools._get_search_backend() == "firecrawl" + def test_custom_plugin_backend_selected_when_registered(self, monkeypatch): + """Unknown backend names should consult plugin-registered providers.""" + from agent.web_search_provider import WebSearchProvider + from agent.web_search_registry import _reset_for_tests, register_provider + from tools import web_tools + + class CustomExtractProvider(WebSearchProvider): + @property + def name(self) -> str: + return "custom-extract" + + def is_available(self) -> bool: + return True + + def supports_search(self) -> bool: + return False + + def supports_extract(self) -> bool: + return True + + def extract(self, urls: List[str], **kwargs: Any) -> List[Dict[str, Any]]: + return [] + + _reset_for_tests() + register_provider(CustomExtractProvider()) + monkeypatch.setattr(web_tools, "_ensure_web_plugins_loaded", lambda: None) + monkeypatch.setattr(web_tools, "_load_web_config", lambda: { + "backend": "firecrawl", + "extract_backend": "custom-extract", + }) + + try: + assert web_tools._is_backend_available("custom-extract") is True + assert web_tools._get_extract_backend() == "custom-extract" + assert web_tools._is_backend_available("missing-custom") is False + finally: + _reset_for_tests() + def test_fully_backward_compatible_with_web_backend_only(self, monkeypatch): from tools import web_tools diff --git a/tools/web_tools.py b/tools/web_tools.py index 8f5275da2267..85bb23531215 100644 --- a/tools/web_tools.py +++ b/tools/web_tools.py @@ -218,6 +218,21 @@ def _is_backend_available(backend: str) -> bool: return has_xai_credentials() except Exception: return False + + # User-installed web backend plugins are not known to this static helper. + # After plugin discovery, trust the registered provider's own cheap + # availability check so custom backends can be selected via + # web.search_backend / web.extract_backend without hardcoding every + # provider name in core. + try: + _ensure_web_plugins_loaded() + from agent.web_search_registry import get_provider as _wsp_get_provider + + provider = _wsp_get_provider(backend) + if provider is not None: + return bool(provider.is_available()) + except Exception: + pass return False