From b610e603db39c97c8a8eabfabd91dc706dbde479 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:04:17 -0700 Subject: [PATCH] =?UTF-8?q?simplify(compat):=20plugins/platforms+web=20?= =?UTF-8?q?=E2=80=94=20drop=207=20re-exports=20+=203=20aliases,=20repoint?= =?UTF-8?q?=201=20caller=20+=2011=20tests,=20re-remove=20credential=5Fsumm?= =?UTF-8?q?ary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dingtalk: drop DINGTALK_TYPE_MAPPING/EXT_MAP re-exports. google_chat: card_spec_to_cards_v2 test -> .cards. matrix: drop module-level MAX_MESSAGE_LENGTH alias (no importers). teams: drop TeamsSummaryWriter re-export (teams_pipeline/runtime + tests -> summary_writer). wecom: drop WeComStreamExpiredError/STREAM_EXPIRED_ERRCODE/ MAX_INTERMEDIATE_FRAMES re-exports (tests -> .streaming). parallel: drop _get_parallel_client/_get_async_parallel_client aliases (tests -> _get_sync_client). email: drop stale 'alias' comment (_esecret_int is the only name). photon: re-remove credential_summary() (shim-only, cb9b7c36f37); its no-leak test now drives print_credential_summary. --- plugins/platforms/dingtalk/adapter.py | 8 +-- plugins/platforms/email/adapter.py | 1 - plugins/platforms/google_chat/adapter.py | 2 +- plugins/platforms/matrix/adapter.py | 2 - plugins/platforms/photon/auth.py | 33 ------------ plugins/platforms/teams/adapter.py | 3 +- plugins/platforms/wecom/adapter.py | 6 +-- plugins/teams_pipeline/runtime.py | 2 +- plugins/web/parallel/provider.py | 4 -- tests/gateway/test_google_chat.py | 4 +- tests/gateway/test_teams.py | 2 +- tests/gateway/test_teams_dotenv_isolation.py | 2 +- tests/gateway/test_wecom.py | 13 ++--- tests/gateway/test_wecom_per_turn.py | 6 ++- tests/gateway/test_wecom_stream_dup_fix.py | 7 +-- tests/plugins/platforms/photon/test_auth.py | 15 +++--- tests/plugins/test_teams_pipeline_plugin.py | 2 +- tests/tools/test_web_tools_config.py | 54 ++++++++++---------- 18 files changed, 60 insertions(+), 106 deletions(-) diff --git a/plugins/platforms/dingtalk/adapter.py b/plugins/platforms/dingtalk/adapter.py index 96813ef689..1239870f23 100644 --- a/plugins/platforms/dingtalk/adapter.py +++ b/plugins/platforms/dingtalk/adapter.py @@ -50,13 +50,7 @@ from gateway.config import Platform, PlatformConfig from gateway.platforms.helpers import MessageDeduplicator, compile_mention_patterns from gateway.platforms.base import BasePlatformAdapter, MessageEvent, SendResult from gateway.platforms._shared import get_scoped_secret as _get_scoped_secret -from plugins.platforms.dingtalk.inbound import ( # noqa: F401 — re-exported names - DINGTALK_TYPE_MAPPING, - EXT_MAP, - collect_download_codes, - extract_media, - extract_text, -) +from plugins.platforms.dingtalk.inbound import collect_download_codes, extract_media, extract_text logger = logging.getLogger(__name__) diff --git a/plugins/platforms/email/adapter.py b/plugins/platforms/email/adapter.py index 82271b56cf..5f68843c41 100644 --- a/plugins/platforms/email/adapter.py +++ b/plugins/platforms/email/adapter.py @@ -54,7 +54,6 @@ _AUTH_METHOD_RE = re.compile(r"\b(dmarc|dkim|spf)\s*=\s*([a-z]+)", re.IGNORECASE _AUTH_PROP_RE = re.compile(r"\b(header\.from|header\.d|smtp\.mailfrom|smtp\.from|envelope-from)\s*=\s*([^\s;]+)", re.IGNORECASE) -# Backwards-compatible alias for the name used by the original #59076 hunks. def _esecret_int(name: str, default: int) -> int: """Scope-aware integer read.""" return coerce_port(str(_get_secret(name, "")).strip() or default, default) diff --git a/plugins/platforms/google_chat/adapter.py b/plugins/platforms/google_chat/adapter.py index 2e8422c625..accfa1b263 100644 --- a/plugins/platforms/google_chat/adapter.py +++ b/plugins/platforms/google_chat/adapter.py @@ -26,7 +26,7 @@ from urllib.parse import urlparse from agent.secret_scope import is_multiplex_active from gateway.platforms._shared import get_scoped_secret as _get_scoped_secret -from .cards import card_spec_to_cards_v2, format_message as _format_message # noqa: F401 (re-exported) +from .cards import card_spec_to_cards_v2, format_message as _format_message def _adc_would_borrow_foreign_credentials() -> bool: diff --git a/plugins/platforms/matrix/adapter.py b/plugins/platforms/matrix/adapter.py index 6f435bd039..d97661413d 100644 --- a/plugins/platforms/matrix/adapter.py +++ b/plugins/platforms/matrix/adapter.py @@ -352,8 +352,6 @@ def _resolve_max_message_length(config) -> int: return max(500, min(value, MATRIX_MAX_MESSAGE_LENGTH_CEILING)) -MAX_MESSAGE_LENGTH = DEFAULT_MAX_MESSAGE_LENGTH # back-compat alias for importers of the module constant - # E2EE store dir is resolved per adapter in connect() (``_resolve_store_dir``), NOT at module scope: # the multiplex gateway imports this once and a module constant would collide every profile's Olm # identity in one crypto.db. diff --git a/plugins/platforms/photon/auth.py b/plugins/platforms/photon/auth.py index 390e7a9094..d402048e7a 100644 --- a/plugins/platforms/photon/auth.py +++ b/plugins/platforms/photon/auth.py @@ -713,36 +713,3 @@ def print_credential_summary(emit: Any = print) -> None: " my number : " + (phone if phone else "✗ missing (run `hermes photon setup --phone ...`)"), " assigned number : " + (assigned if assigned else "✗ missing (run `hermes photon setup`)")] emit("\n".join(rows)) - - -def credential_summary() -> Dict[str, str]: - """Return a fully pre-formatted credential status dict (no raw secrets).""" - def _present_token() -> str: - return ( - "✓ stored" if load_photon_token() - else "✗ missing (run `hermes photon setup`)" - ) - - def _present_project_id() -> str: - sid, _sec = load_project_credentials() - return sid or "✗ missing" - - def _present_secret() -> str: - _sid, sec = load_project_credentials() - return "✓ stored" if sec else "✗ missing" - - def _present_phone() -> str: - phone, _assigned = load_user_numbers() - return phone or "✗ missing (run `hermes photon setup --phone ...`)" - - def _present_assigned_phone() -> str: - _phone, assigned = load_user_numbers() - return assigned or "✗ missing (run `hermes photon setup`)" - - return { - "device_token": _present_token(), - "project_id": _present_project_id(), - "project_key": _present_secret(), - "phone_number": _present_phone(), - "assigned_phone_number": _present_assigned_phone(), - } diff --git a/plugins/platforms/teams/adapter.py b/plugins/platforms/teams/adapter.py index 990918a598..48a6295377 100644 --- a/plugins/platforms/teams/adapter.py +++ b/plugins/platforms/teams/adapter.py @@ -58,7 +58,6 @@ from gateway.platforms.base import ( gateway_trust_env, BasePlatformAdapter, MessageEvent, MessageType, SendResult, cache_image_from_url, cache_media_bytes_async, ) from gateway.platforms._shared import coerce_port, get_scoped_secret as _get_scoped_secret -from plugins.platforms.teams.summary_writer import TeamsSummaryWriter # noqa: F401 — re-exported for teams_pipeline logger = logging.getLogger(__name__) @@ -251,7 +250,7 @@ _SDK_IMPORTS = { "microsoft_teams.cards": ("AdaptiveCard", "ExecuteAction", "TextBlock")} -# Keep the old name as an alias so existing test imports don't break. NOTE: ``check_requirements`` is the +# NOTE: ``check_requirements`` is the # PASSIVE probe (registry ``check_fn``, status / unit tests) — it must never trigger a pip install. # ``check_teams_requirements`` is the ACTIVE lazy-installer, registered as ``ensure_deps_fn``: the # registry's ``create_adapter()`` runs it when the passive probe fails, right before the gateway connects diff --git a/plugins/platforms/wecom/adapter.py b/plugins/platforms/wecom/adapter.py index 89b591a16f..b60733b372 100644 --- a/plugins/platforms/wecom/adapter.py +++ b/plugins/platforms/wecom/adapter.py @@ -34,9 +34,9 @@ from utils import env_float from gateway.platforms._shared import get_scoped_secret as _get_scoped_secret from plugins.platforms.wecom.send_queue import ChatSendQueueMixin from plugins.platforms.wecom.media import WeComMediaMixin, APP_CMD_SEND -from plugins.platforms.wecom.streaming import ( # noqa: F401 — re-exported for tests/stream_consumer - WeComStreamMixin, WeComStreamExpiredError, ReplyQueue, StreamTurn, APP_CMD_RESPONSE, - STREAM_EXPIRED_ERRCODE, STREAM_NOT_SUBSCRIBED_ERRCODE, MAX_STREAM_CONTENT_LENGTH, MAX_INTERMEDIATE_FRAMES, +from plugins.platforms.wecom.streaming import ( + WeComStreamMixin, ReplyQueue, StreamTurn, APP_CMD_RESPONSE, + STREAM_NOT_SUBSCRIBED_ERRCODE, MAX_STREAM_CONTENT_LENGTH, STREAM_SAFE_DURATION_SECONDS, STREAM_KEEPALIVE_INTERVAL_SECONDS, STREAM_KEEPALIVE_ENABLED_DEFAULT, ) diff --git a/plugins/teams_pipeline/runtime.py b/plugins/teams_pipeline/runtime.py index 5d25afa99c..7091ec205d 100644 --- a/plugins/teams_pipeline/runtime.py +++ b/plugins/teams_pipeline/runtime.py @@ -53,7 +53,7 @@ def build_pipeline_runtime(gateway: Any) -> TeamsMeetingPipeline: pipeline_config = build_pipeline_runtime_config(gateway.config) if teams_config and teams_config.enabled and (pipeline_config.get("teams_delivery") or {}).get("enabled"): try: - from plugins.platforms.teams.adapter import TeamsSummaryWriter + from plugins.platforms.teams.summary_writer import TeamsSummaryWriter except ImportError: logger.debug("TeamsSummaryWriter unavailable; Teams outbound delivery remains disabled until the adapter layer is present.") else: diff --git a/plugins/web/parallel/provider.py b/plugins/web/parallel/provider.py index c01d8665f6..96a9725f59 100644 --- a/plugins/web/parallel/provider.py +++ b/plugins/web/parallel/provider.py @@ -37,10 +37,6 @@ def _get_async_client() -> Any: return _client("_async_parallel_client", "AsyncParallel") -# Names re-exported by tools.web_tools for existing tests/callers. -_get_parallel_client, _get_async_parallel_client = _get_sync_client, _get_async_client - - def _resolve_search_mode() -> str: mode = os.getenv("PARALLEL_SEARCH_MODE", "agentic").lower().strip() return mode if mode in {"fast", "one-shot", "agentic"} else "agentic" diff --git a/tests/gateway/test_google_chat.py b/tests/gateway/test_google_chat.py index c9f1f1584f..cd97dfe68d 100644 --- a/tests/gateway/test_google_chat.py +++ b/tests/gateway/test_google_chat.py @@ -135,9 +135,9 @@ from plugins.platforms.google_chat.adapter import ( # noqa: E402 _is_google_owned_host, _mime_for_message_type, _redact_sensitive, - card_spec_to_cards_v2, check_google_chat_requirements, ) +from plugins.platforms.google_chat.cards import card_spec_to_cards_v2 # noqa: E402 # --------------------------------------------------------------------------- @@ -1735,7 +1735,7 @@ class TestCronSchedulerRegistry: def test_google_chat_is_known_delivery_platform(self): self._ensure_registered() - from cron.scheduler import _is_known_delivery_platform + from cron.scheduler_delivery import _is_known_delivery_platform assert _is_known_delivery_platform("google_chat") is True diff --git a/tests/gateway/test_teams.py b/tests/gateway/test_teams.py index fe7ff4c8a7..160e48e40b 100644 --- a/tests/gateway/test_teams.py +++ b/tests/gateway/test_teams.py @@ -191,7 +191,7 @@ if _mt and _teams_mod.TypingActivityInput is None: _teams_mod.TypingActivityInput = _mt.TypingActivityInput TeamsAdapter = _teams_mod.TeamsAdapter -TeamsSummaryWriter = _teams_mod.TeamsSummaryWriter +from plugins.platforms.teams.summary_writer import TeamsSummaryWriter # noqa: E402 check_requirements = _teams_mod.check_requirements check_teams_requirements = _teams_mod.check_teams_requirements validate_config = _teams_mod.validate_config diff --git a/tests/gateway/test_teams_dotenv_isolation.py b/tests/gateway/test_teams_dotenv_isolation.py index 794d32ed9a..1fb86a1f2b 100644 --- a/tests/gateway/test_teams_dotenv_isolation.py +++ b/tests/gateway/test_teams_dotenv_isolation.py @@ -162,7 +162,7 @@ class TestTeamsAdapterImportDoesNotLeakDotenv: _install_fake_teams_sdk(monkeypatch) _purge_teams_adapter_modules() - from plugins.platforms.teams.adapter import TeamsSummaryWriter + from plugins.platforms.teams.summary_writer import TeamsSummaryWriter assert CANARY_KEY not in os.environ assert TeamsSummaryWriter is not None diff --git a/tests/gateway/test_wecom.py b/tests/gateway/test_wecom.py index 96886df995..622da346a1 100644 --- a/tests/gateway/test_wecom.py +++ b/tests/gateway/test_wecom.py @@ -920,7 +920,7 @@ class TestWeComNativeStreamingCapability: assert WeComAdapter.MAX_STREAM_CONTENT_LENGTH == 20480 def test_stream_expired_errcode_constant(self): - from plugins.platforms.wecom.adapter import STREAM_EXPIRED_ERRCODE + from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE assert STREAM_EXPIRED_ERRCODE == 846608 @@ -1078,7 +1078,8 @@ class TestSendStreamFrame: @pytest.mark.asyncio async def test_intermediate_frame_cap_drops_excess(self): """After MAX_INTERMEDIATE_FRAMES, further intermediate frames are dropped.""" - from plugins.platforms.wecom.adapter import WeComAdapter, MAX_INTERMEDIATE_FRAMES + from plugins.platforms.wecom.adapter import WeComAdapter + from plugins.platforms.wecom.streaming import MAX_INTERMEDIATE_FRAMES adapter = WeComAdapter(PlatformConfig(enabled=True)) adapter._last_chat_req_ids["chat-1"] = "req-1" @@ -1179,9 +1180,8 @@ class TestSendStreamFrameFailures: @pytest.mark.asyncio async def test_846608_marks_chat_expired_and_returns_false(self): """846608 on finalize frame marks the chat expired and returns False.""" - from plugins.platforms.wecom.adapter import ( - STREAM_EXPIRED_ERRCODE, WeComAdapter, - ) + from plugins.platforms.wecom.adapter import WeComAdapter + from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE adapter = WeComAdapter(PlatformConfig(enabled=True)) adapter._last_chat_req_ids["chat-1"] = "req-1" @@ -1392,7 +1392,8 @@ class TestSendClosesActiveStream: @pytest.mark.asyncio async def test_send_falls_through_when_stream_expired(self): - from plugins.platforms.wecom.adapter import STREAM_EXPIRED_ERRCODE, WeComAdapter + from plugins.platforms.wecom.adapter import WeComAdapter + from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE adapter = WeComAdapter(PlatformConfig(enabled=True)) adapter._last_chat_req_ids["chat-1"] = "req-1" diff --git a/tests/gateway/test_wecom_per_turn.py b/tests/gateway/test_wecom_per_turn.py index daae1adf2e..063075dd53 100644 --- a/tests/gateway/test_wecom_per_turn.py +++ b/tests/gateway/test_wecom_per_turn.py @@ -106,7 +106,8 @@ class TestPerTurnStreamIsolation: @pytest.mark.asyncio async def test_one_user_expired_others_unaffected(self): """User A hits stream expired; Users B and C continue normally.""" - from plugins.platforms.wecom.adapter import STREAM_EXPIRED_ERRCODE, WeComAdapter + from plugins.platforms.wecom.adapter import WeComAdapter + from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE adapter = WeComAdapter(PlatformConfig(enabled=True)) try: @@ -155,7 +156,8 @@ class TestPerTurnStreamIsolation: @pytest.mark.asyncio async def test_one_turn_expired_other_continues(self): """When one turn hits stream expired, other concurrent turns can continue.""" - from plugins.platforms.wecom.adapter import STREAM_EXPIRED_ERRCODE, WeComAdapter + from plugins.platforms.wecom.adapter import WeComAdapter + from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE adapter = WeComAdapter(PlatformConfig(enabled=True)) try: diff --git a/tests/gateway/test_wecom_stream_dup_fix.py b/tests/gateway/test_wecom_stream_dup_fix.py index 7329b2a145..6122a2191d 100644 --- a/tests/gateway/test_wecom_stream_dup_fix.py +++ b/tests/gateway/test_wecom_stream_dup_fix.py @@ -33,11 +33,8 @@ from unittest.mock import AsyncMock import pytest from gateway.config import PlatformConfig -from plugins.platforms.wecom.adapter import ( - WeComAdapter, - WeComStreamExpiredError, - STREAM_EXPIRED_ERRCODE, -) +from plugins.platforms.wecom.adapter import WeComAdapter +from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE, WeComStreamExpiredError CHAT_ID = "chat-dup" diff --git a/tests/plugins/platforms/photon/test_auth.py b/tests/plugins/platforms/photon/test_auth.py index 56e4c23d94..9a650e5830 100644 --- a/tests/plugins/platforms/photon/test_auth.py +++ b/tests/plugins/platforms/photon/test_auth.py @@ -340,16 +340,17 @@ def test_credential_summary_no_secret_leak( project_secret="secret-bbbbbbbbbbb", dashboard_project_id="dash-uuid", ) - summary = photon_auth.credential_summary() - blob = "\n".join(summary.values()) + lines: list[str] = [] + photon_auth.print_credential_summary(lines.append) + blob = "\n".join(lines) assert "token-aaaa" not in blob assert "secret-bbbb" not in blob - assert summary["device_token"].startswith("✓") - assert summary["project_key"].startswith("✓") + assert "device token : ✓" in blob + assert "project secret : ✓" in blob # Unified id: dashboard id == Spectrum id, surfaced as one project id. - assert summary["project_id"] == "sp-uuid" - assert summary["phone_number"].startswith("✗ missing") - assert summary["assigned_phone_number"].startswith("✗ missing") + assert "project id : sp-uuid" in blob + assert "my number : ✗ missing" in blob + assert "assigned number : ✗ missing" in blob # --------------------------------------------------------------------------- diff --git a/tests/plugins/test_teams_pipeline_plugin.py b/tests/plugins/test_teams_pipeline_plugin.py index d996c30ef7..0c4a6657ce 100644 --- a/tests/plugins/test_teams_pipeline_plugin.py +++ b/tests/plugins/test_teams_pipeline_plugin.py @@ -99,7 +99,7 @@ def test_build_pipeline_runtime_reuses_existing_teams_adapter_surface(monkeypatc monkeypatch.setattr(runtime_module, "build_graph_client", lambda: object()) monkeypatch.setattr(runtime_module, "resolve_teams_pipeline_store_path", lambda: tmp_path / "teams-store.json") - monkeypatch.setattr("plugins.platforms.teams.adapter.TeamsSummaryWriter", FakeWriter) + monkeypatch.setattr("plugins.platforms.teams.summary_writer.TeamsSummaryWriter", FakeWriter) gateway = SimpleNamespace( config=GatewayConfig( diff --git a/tests/tools/test_web_tools_config.py b/tests/tools/test_web_tools_config.py index 92f30ae57b..fdad7a0853 100644 --- a/tests/tools/test_web_tools_config.py +++ b/tests/tools/test_web_tools_config.py @@ -4,7 +4,7 @@ Coverage: _get_firecrawl_client() — configuration matrix, singleton caching, constructor failure recovery, return value verification, edge cases. _get_backend() — backend selection logic with env var combinations. - _get_parallel_client() — Parallel client configuration, singleton caching. + plugins.web.parallel.provider._get_sync_client() — Parallel client configuration, singleton caching. check_web_api_key() — unified availability check across all web backends. """ @@ -35,10 +35,10 @@ class TestFirecrawlClientConfig: ): os.environ.pop(key, None) # Enable managed tools by default for these tests — patch both the - # local web_tools import and the managed_tool_gateway import so the + # tool_backend_helpers definition and the managed_tool_gateway import so the # full firecrawl client init path sees True. self._managed_patchers = [ - patch("tools.web_tools.managed_nous_tools_enabled", return_value=True), + patch("tools.tool_backend_helpers.managed_nous_tools_enabled", return_value=True), patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True), ] for p in self._managed_patchers: @@ -65,18 +65,18 @@ class TestFirecrawlClientConfig: def test_no_config_raises_with_helpful_message(self): """Neither key nor URL → ValueError with guidance.""" - with patch("tools.web_tools.Firecrawl"): - with patch("tools.web_tools._read_nous_access_token", return_value=None): - from tools.web_tools import _get_firecrawl_client + with patch("plugins.web.firecrawl.provider.Firecrawl"): + with patch("tools.managed_tool_gateway.read_nous_access_token", return_value=None): + from plugins.web.firecrawl.provider import _get_firecrawl_client with pytest.raises(ValueError, match="FIRECRAWL_API_KEY"): _get_firecrawl_client() def test_tool_gateway_domain_builds_firecrawl_gateway_origin(self): """Shared gateway domain should derive the Firecrawl vendor hostname.""" with patch.dict(os.environ, {"TOOL_GATEWAY_DOMAIN": "nousresearch.com"}): - with patch("tools.web_tools._read_nous_access_token", return_value="nous-token"): - with patch("tools.web_tools.Firecrawl") as mock_fc: - from tools.web_tools import _get_firecrawl_client + with patch("tools.managed_tool_gateway.read_nous_access_token", return_value="nous-token"): + with patch("plugins.web.firecrawl.provider.Firecrawl") as mock_fc: + from plugins.web.firecrawl.provider import _get_firecrawl_client result = _get_firecrawl_client() mock_fc.assert_called_once_with( api_key="nous-token", @@ -92,9 +92,9 @@ class TestFirecrawlClientConfig: """If Firecrawl() raises, next call should retry (not return None).""" import tools.web_tools with patch.dict(os.environ, {"FIRECRAWL_API_KEY": "fc-test"}): - with patch("tools.web_tools.Firecrawl") as mock_fc: + with patch("plugins.web.firecrawl.provider.Firecrawl") as mock_fc: mock_fc.side_effect = [RuntimeError("init failed"), MagicMock()] - from tools.web_tools import _get_firecrawl_client + from plugins.web.firecrawl.provider import _get_firecrawl_client with pytest.raises(RuntimeError): _get_firecrawl_client() @@ -109,9 +109,9 @@ class TestFirecrawlClientConfig: def test_empty_string_key_no_url_raises(self): """FIRECRAWL_API_KEY='' with no URL → should raise.""" with patch.dict(os.environ, {"FIRECRAWL_API_KEY": ""}): - with patch("tools.web_tools.Firecrawl"): - with patch("tools.web_tools._read_nous_access_token", return_value=None): - from tools.web_tools import _get_firecrawl_client + with patch("plugins.web.firecrawl.provider.Firecrawl"): + with patch("tools.managed_tool_gateway.read_nous_access_token", return_value=None): + from plugins.web.firecrawl.provider import _get_firecrawl_client with pytest.raises(ValueError): _get_firecrawl_client() @@ -120,9 +120,9 @@ class TestFirecrawlClientConfig: from plugins.web.firecrawl import provider as firecrawl_provider with patch("tools.web_tools._load_web_config", return_value={"backend": "firecrawl"}): - with patch("tools.web_tools._read_nous_access_token", return_value=None): - with patch("tools.web_tools.Firecrawl", side_effect=AssertionError("SDK path should not run")): - from tools.web_tools import _get_firecrawl_client + with patch("tools.managed_tool_gateway.read_nous_access_token", return_value=None): + with patch("plugins.web.firecrawl.provider.Firecrawl", side_effect=AssertionError("SDK path should not run")): + from plugins.web.firecrawl.provider import _get_firecrawl_client result = _get_firecrawl_client() @@ -217,7 +217,7 @@ class TestBackendSelection: for key in self._ENV_KEYS: os.environ.pop(key, None) self._managed_patchers = [ - patch("tools.web_tools.managed_nous_tools_enabled", return_value=True), + patch("tools.tool_backend_helpers.managed_nous_tools_enabled", return_value=True), patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True), ] for p in self._managed_patchers: @@ -412,7 +412,7 @@ class TestParallelClientConfig: def test_creates_client_with_key(self): """PARALLEL_API_KEY set → creates Parallel client.""" with patch.dict(os.environ, {"PARALLEL_API_KEY": "test-key"}): - from tools.web_tools import _get_parallel_client + from plugins.web.parallel.provider import _get_sync_client as _get_parallel_client from parallel import Parallel client = _get_parallel_client() assert client is not None @@ -420,14 +420,14 @@ class TestParallelClientConfig: def test_no_key_raises_with_helpful_message(self): """No PARALLEL_API_KEY → ValueError with guidance.""" - from tools.web_tools import _get_parallel_client + from plugins.web.parallel.provider import _get_sync_client as _get_parallel_client with pytest.raises(ValueError, match="PARALLEL_API_KEY"): _get_parallel_client() def test_singleton_returns_same_instance(self): """Second call returns cached client.""" with patch.dict(os.environ, {"PARALLEL_API_KEY": "test-key"}): - from tools.web_tools import _get_parallel_client + from plugins.web.parallel.provider import _get_sync_client as _get_parallel_client client1 = _get_parallel_client() client2 = _get_parallel_client() assert client1 is client2 @@ -531,7 +531,7 @@ class TestCheckWebApiKey: for key in self._ENV_KEYS: os.environ.pop(key, None) self._managed_patchers = [ - patch("tools.web_tools.managed_nous_tools_enabled", return_value=True), + patch("tools.tool_backend_helpers.managed_nous_tools_enabled", return_value=True), patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True), # ddgs availability is package-presence driven and the plugin # registry can hold an available ddgs provider. Neutralize both @@ -571,7 +571,7 @@ class TestCheckWebApiKey: def test_configured_firecrawl_backend_accepts_managed_gateway(self): with patch("tools.web_tools._load_web_config", return_value={"backend": "firecrawl"}): - with patch("tools.web_tools._peek_nous_access_token", return_value="nous-token"): + with patch("tools.managed_tool_gateway.peek_nous_access_token", return_value="nous-token"): with patch.dict(os.environ, {"FIRECRAWL_GATEWAY_URL": "http://127.0.0.1:3002"}, clear=False): from tools.web_tools import check_web_api_key assert check_web_api_key() is True @@ -709,7 +709,7 @@ class TestNonBuiltinProviderAvailability: """With only a custom provider registered (no built-in creds), check_web_api_key() must return True.""" with patch("tools.web_tools._ddgs_package_importable", return_value=False), \ - patch("tools.web_tools._peek_nous_access_token", return_value=None): + patch("tools.managed_tool_gateway.peek_nous_access_token", return_value=None): from tools.web_tools import check_web_api_key assert check_web_api_key() is True @@ -717,7 +717,7 @@ class TestNonBuiltinProviderAvailability: """_get_backend() must return the custom provider name when it's the only available provider.""" with patch("tools.web_tools._ddgs_package_importable", return_value=False), \ - patch("tools.web_tools._peek_nous_access_token", return_value=None): + patch("tools.managed_tool_gateway.peek_nous_access_token", return_value=None): from tools.web_tools import _get_backend assert _get_backend() == "fake-plugin-prov" @@ -726,7 +726,7 @@ class TestNonBuiltinProviderAvailability: """Per-capability selection (_get_extract_backend) must resolve the custom provider when configured, instead of dead-ending — issue #32698.""" with patch("tools.web_tools._ddgs_package_importable", return_value=False), \ - patch("tools.web_tools._peek_nous_access_token", return_value=None), \ + patch("tools.managed_tool_gateway.peek_nous_access_token", return_value=None), \ patch("tools.web_tools._load_web_config", return_value={"extract_backend": "fake-plugin-prov"}): from tools.web_tools import _get_extract_backend @@ -736,7 +736,7 @@ class TestNonBuiltinProviderAvailability: """web_search and web_extract tool entries must remain in the registry when only a custom provider is available.""" with patch("tools.web_tools._ddgs_package_importable", return_value=False), \ - patch("tools.web_tools._peek_nous_access_token", return_value=None): + patch("tools.managed_tool_gateway.peek_nous_access_token", return_value=None): import tools.web_tools web_search_entry = tools.web_tools.registry.get_entry("web_search") web_extract_entry = tools.web_tools.registry.get_entry("web_extract")