hermes - ✅(Solved) Fix Mirror web-provider plugin migration for browser providers [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#25214Fetched 2026-05-14 03:48:00
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
labeled ×4cross-referenced ×1

Mirror the web-provider plugin migration from #25182 onto the browser-automation subsystem so the two pluggable-backend categories share the same architecture. Today, third-party web backends drop in as plugins; third-party browser backends require core-code edits.

After #25182, tools/web_providers/ is gone and every web backend lives at plugins/web/<vendor>/ registering through agent.web_search_registry. The browser subsystem still has the old shape — cloud browser providers (Browserbase, Browser Use, Firecrawl Cloud) live as in-tree modules under tools/browser_providers/ and are dispatched via a hardcoded class registry in tools/browser_tool.py.

The work in #25182 established the template; this issue tracks applying it to browser.

Root Cause

Total: roughly +1000 net LoC, ~20 files, ~12 focused commits. Significantly smaller than #25182 because:

  • ABC + registry templates exist and just need parameterization
  • Only 3 cloud providers vs 7 web providers
  • Browser subsystem has fewer external-test mocking sites to preserve

Fix Action

Fix / Workaround

After #25182, tools/web_providers/ is gone and every web backend lives at plugins/web/<vendor>/ registering through agent.web_search_registry. The browser subsystem still has the old shape — cloud browser providers (Browserbase, Browser Use, Firecrawl Cloud) live as in-tree modules under tools/browser_providers/ and are dispatched via a hardcoded class registry in tools/browser_tool.py.

  1. New registryagent/browser_registry.py mirroring agent/web_search_registry.py:

    • register_provider(provider) / get_provider(name) / list_providers()
    • get_active_browser_provider() reading config["browser"]["cloud_provider"]
    • Same Option B smart-fallback semantics: explicit config wins ignoring is_available() so the dispatcher surfaces typed credential errors instead of silently switching backends
  2. Dispatcher cutover — replace _PROVIDER_REGISTRY lookup in tools/browser_tool.py:_get_cloud_provider() with a registry call. The single-cached-provider singleton pattern stays; only the lookup changes from class-instantiation to registry-lookup.

PR fix notes

PR #25182: refactor(web): all web providers as image_gen-style plugins

Description (problem / solution / changelog)

Summary

Migrates every web provider (search + extract + crawl) out of tools/web_tools.py into the bundled-plugin architecture, matching the image_gen template. All seven providers now live under plugins/web/<vendor>/, registering through agent.web_search_registry instead of hardcoded if backend == ... chains in the dispatcher.

This unblocks user-added web providers (drop a directory under ~/.hermes/plugins/web/ and they appear in hermes tools automatically — same UX as image_gen / spotify / google_meet plugins).

Scope — what moved

ProviderCapabilitiesAsync extract?Async crawl?Auth
brave-freesearchsyncBRAVE_SEARCH_API_KEY
ddgssearchsyncnone (pip ddgs)
searxngsearchsyncSEARXNG_URL
exasearch + extractsyncEXA_API_KEY
parallelsearch + extractasyncPARALLEL_API_KEY
tavilysearch + extract + crawlsyncsyncTAVILY_API_KEY
firecrawlsearch + extract + crawlasyncasyncdirect (FIRECRAWL_API_KEY / FIRECRAWL_API_URL) or Nous Tool Gateway

Each plugin is plugins/web/<vendor>/{plugin.yaml,__init__.py,provider.py}.

What changes for users

Nothing. The setup wizard, hermes tools picker, and per-provider env vars all behave identically. The PR is a pure refactor preserving the legacy response shapes bit-for-bit. The only visible difference is that user-installed web plugins now light up in the picker the same way image_gen plugins do.

Architecture

Before:

  • 9 hardcoded TOOL_CATEGORIES["web"] rows in hermes_cli/tools_config.py
  • Per-vendor inline helpers in tools/web_tools.py (_get_firecrawl_client, _tavily_request, _parallel_search, etc. — ~600 LoC)
  • Per-vendor if backend == "exa": ... elif backend == "tavily": ... chains in all three dispatchers
  • Legacy in-tree ABCs at tools/web_providers/base.py

After:

  • 2 hardcoded TOOL_CATEGORIES["web"] rows ("Nous Subscription", "Firecrawl Self-Hosted" — the two non-provider UX setup flows for firecrawl); the other 7 come from _plugin_web_search_providers()
  • All vendor code lives in plugins/web/<vendor>/provider.py
  • Each dispatcher is a single registry lookup + inspect.iscoroutinefunction async/sync detection
  • One ABC at agent/web_search_provider.py with supports_search/extract/crawl capability flags

Registry resolution semantics (Option B — conservative smart fallback)

_resolve() in agent/web_search_registry.py follows three rules in order:

  1. Explicit config wins, ignoring availability. If web.<capability>_backend (or web.backend) names a registered + capable provider, return it — even if is_available() is False. The dispatcher then surfaces a precise "X_API_KEY is not set" error instead of silently switching backends.
  2. Single-provider shortcut. When only one registered provider matches the capability and is available, pick it.
  3. Legacy preference walk, filtered by availability. Walk _LEGACY_PREFERENCE (firecrawl → parallel → tavily → exa → searxng → brave-free → ddgs) and pick the first one that's capable AND available. 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.

When the configured name is registered but capability-incompatible (e.g. web.extract_backend: brave-free), the dispatcher surfaces a typed "X is a search-only backend and cannot extract URL content" error matching the legacy wording.

Async-or-sync extract dispatch

parallel and firecrawl declare async def extract(); exa and tavily are sync. The dispatcher detects via inspect.iscoroutinefunction(provider.extract) and either awaits the result or runs the sync call in asyncio.to_thread(...) so the gateway event loop is never blocked.

Backward compatibility

The names in tools.web_tools.* that tests/integration code reaches for are preserved as re-export shims from the plugins:

  • Firecrawl (lazy proxy), check_firecrawl_api_key, _get_firecrawl_client, _is_tool_gateway_ready, _has_direct_firecrawl_config, _get_direct_firecrawl_config, _get_firecrawl_gateway_url, _firecrawl_backend_help_suffix, _raise_web_backend_configuration_error, _extract_web_search_results, _extract_scrape_payload, _to_plain_object, _normalize_result_list
  • _tavily_request, _normalize_tavily_search_results, _normalize_tavily_documents
  • _get_parallel_client, _get_async_parallel_client, _get_exa_client
  • The cached client globals (_firecrawl_client, _parallel_client, _async_parallel_client, _exa_client) live on tools.web_tools so unit tests that reset them between cases keep working

The plugin implementations use import tools.web_tools as _wt indirection to read helpers like _wt._read_nous_access_token, _wt.Firecrawl, _wt.prefers_gateway so test patches that target the legacy paths reach the plugin code transparently.

Diff stat

39 files changed, 3459 insertions(+), 1534 deletions(-)

The big wins:

  • tools/web_tools.py: 2227 → 1511 lines (−716 lines / −32%)
  • tools/web_providers/ directory deleted entirely (−168 lines)
  • hermes_cli/tools_config.py: 7 hardcoded provider rows removed (−91 lines)

The added lines are split roughly: 60% new plugin code (one provider.py per vendor, ranges from ~90 lines for brave-free to ~750 lines for firecrawl which now also handles crawl), 25% new tests (tests/plugins/web/test_web_search_provider_plugins.py = 472 lines, 45 tests), 15% new ABC + registry + plugin glue (agent/web_search_provider.py + agent/web_search_registry.py + hermes_cli/plugins.py web context hook).

Testing

210/210 plugin + dispatcher tests pass (208 original + 2 new regression tests from behavior-parity verification):

  • tests/tools/test_web_providers.py (ABC contract — rewritten to test the new unified ABC)
  • tests/tools/test_web_providers_brave_free.py / _ddgs.py / _searxng.py
  • tests/tools/test_web_tools_config.py (backend selection, client config, error responses)
  • tests/tools/test_web_tools_tavily.py
  • tests/tools/test_website_policy.py (per-URL gate + redirect re-check)
  • tests/tools/test_config_null_guard.py
  • tests/plugins/web/test_web_search_provider_plugins.py (45 new tests covering ABC, registry, async detection, error shapes)

Plugin test classes (45 tests):

  • TestBundledPluginsRegister (16) — all seven plugins register, capability flags correct, picker schema valid
  • TestIsAvailable (7) — every plugin's is_available() correctly reflects env-var presence
  • TestRegistryResolution (4) — explicit-config-wins-when-unavailable, typo-falls-back, capability-incompatible-falls-back, no-config behavior
  • TestAsyncExtractDispatch (4) — inspect.iscoroutinefunction correctly distinguishes parallel/firecrawl (async) from exa/tavily (sync)
  • TestErrorResponseShapes (8) — plugins return typed error dicts when credentials are missing, never raise (including the new firecrawl crawl error test)

Full tests/tools/ regression sweep: 4910 passed, 44 skipped, 0 failures.

Behavior-parity verification (local E2E)

To make sure the plugin migration is a pure refactor, the three dispatchers were exercised in subprocesses pinned to (a) origin/main and (b) this PR's worktree, with all web-provider credentials cleared, across 14 config scenarios (no-config, plus every search_backend / extract_backend / crawl_backend override for each registered provider). For each scenario the JSON envelope shape is reduced to {success, has_error_top, has_data, n_results, per_result_has_error, error_category} and compared.

Initial run surfaced two observable behavior changes from the migration, both fixed in commit af2a7bfe6:

  1. web_search_tool on an unconfigured system. Main returns {"error": "Error searching web: Web tools are not configured..."} (via tool_error(), no success key). The migrated dispatcher was returning {"success": False, "error": "Web tools are not configured..."} because firecrawl.search() was catching ValueError from _get_firecrawl_client() itself instead of letting it propagate. Fix: pull _get_firecrawl_client() out of the broad try in the plugin's search(). Pre-flight errors propagate to the dispatcher's top-level except handler, matching main's legacy envelope. In-flight SDK errors still get wrapped per-result.

  2. web_crawl_tool on an unconfigured system. Main short-circuits on check_firecrawl_api_key() with {"success": False, "error": "web_crawl requires Firecrawl..."} at the top level. The migrated dispatcher was returning {"results": [{"url": "...", "error": "Web tools are not configured..."}]} — the firecrawl plugin's crawl() except ValueError was wrapping the pre-flight error as a per-page failure inside results[]. Models that check result.get("error") would miss the failure. Fix: mirror main's upstream availability gate in the dispatcher — when the resolved crawl provider is is_available()==False, short-circuit BEFORE delegating to the plugin with the same top-level error shape main emits.

Two regression tests in tests/tools/test_web_providers.py::TestUnconfiguredErrorEnvelopeParity lock the new behavior in so future plugin work can't re-introduce per-result error wrapping on pre-flight config errors.

Final state: 14/14 dispatcher scenarios match origin/main shape exactly. Plugin/dispatcher tests now 210/210 (208 + 2 new regression tests).

Self-review fix (commit 0dec60f7a)

Catches up the registry's _LEGACY_PREFERENCE order with the legacy tools.web_tools._get_backend() candidate tuple so direct callers of agent.web_search_registry.get_active_*_provider() (no _get_backend() interpose) don't return None for users who have credentials but no explicit web.backend config key. Verified empirically: TAVILY+EXA → tavily; EXA+PARALLEL → parallel; BRAVE+FIRECRAWL → firecrawl, all matching main. Plus four stale-docstring cleanups (crawl claims, ABC capabilities, registry comment block) and the removal of three dead plugin-level cache globals + their no-op reset helpers (rewritten to clear the canonical _wt._<name> slots, matching the pattern exa already used correctly).

Out of scope (clean follow-ups)

  • Browser provider migration — mirror this work onto tools/browser_providers/ so third-party browser backends drop in identically. Filed as #25214.

Commits

24 focused commits on kshitijk4poor/hermes-agent:spike/web-providers-plugin:

ABC + registry foundation
  c28f2df  feat(web): add WebSearchProvider ABC mirroring image_gen template
  7a92840  feat(web): add web search provider registry mirroring image_gen pattern
  4a49015  feat(plugins): add ctx.register_web_search_provider() facade

Per-plugin migrations (one commit each, in dependency order)
  ab968d0  feat(web): brave_free plugin
  724ce34  feat(web): ddgs plugin
  8f1aa2b  feat(web): searxng plugin
  bc58e8d  feat(web): exa plugin
  30998b6  feat(web): parallel plugin (first async-extract)
  674307Z  feat(web): tavily plugin (first three-capability incl. crawl)
  6dc81fa  feat(web): firecrawl plugin (largest migration; lazy proxy + dual auth)

Bridges / fixes
  6a34d6c  fix(plugins): filter resolution by is_available() in web + image_gen registries
  2f28b6d  feat(web): extend ABC with supports_crawl + async-extract semantics
  d873596  refactor(web): dispatch brave-free/ddgs/searxng via web_search_registry (interim)
  63829c7  feat(tools): mirror image_gen plugin-injection in Web Search picker
  dfaa611  refactor(web): remove legacy in-tree provider modules

Dispatcher cutover + cleanup
  a1c33bf  refactor(web): dispatch all three tools through web_search_registry
  cd7e142  fix(web): preserve firecrawl crawl + website-policy gate after migration
  d82fb49  refactor(web): delete inline vendor helpers, re-export from plugins (-614 LOC)
  1bb9bdb  refactor(tools): drop hardcoded web picker rows + skiplist
  3f07fda  refactor(web): delete legacy tools/web_providers/ directory + migrate ABC tests
  8114924  test(plugins): tests/plugins/web/ — 44-test coverage for the migration
  601a174  feat(web): firecrawl plugin natively supports crawl; delete legacy inline path (-254 LOC)

Self-review fixes
  0dec60f  fix(web): align _LEGACY_PREFERENCE with legacy 7-provider order + doc cleanup
  af2a7bf  fix(web): preserve top-level error envelope on unconfigured systems

Changed files

  • agent/image_gen_registry.py (modified, +31/-6)
  • agent/web_search_provider.py (added, +221/-0)
  • agent/web_search_registry.py (added, +262/-0)
  • hermes_cli/plugins.py (modified, +28/-0)
  • hermes_cli/tools_config.py (modified, +75/-62)
  • plugins/web/__init__.py (added, +7/-0)
  • plugins/web/brave_free/__init__.py (added, +14/-0)
  • plugins/web/brave_free/plugin.yaml (added, +7/-0)
  • plugins/web/brave_free/provider.py (renamed, +48/-41)
  • plugins/web/ddgs/__init__.py (added, +15/-0)
  • plugins/web/ddgs/plugin.yaml (added, +7/-0)
  • plugins/web/ddgs/provider.py (renamed, +43/-37)
  • plugins/web/exa/__init__.py (added, +15/-0)
  • plugins/web/exa/plugin.yaml (added, +7/-0)
  • plugins/web/exa/provider.py (added, +212/-0)
  • plugins/web/firecrawl/__init__.py (added, +28/-0)
  • plugins/web/firecrawl/plugin.yaml (added, +7/-0)
  • plugins/web/firecrawl/provider.py (added, +773/-0)
  • plugins/web/parallel/__init__.py (added, +16/-0)
  • plugins/web/parallel/plugin.yaml (added, +7/-0)
  • plugins/web/parallel/provider.py (added, +291/-0)
  • plugins/web/searxng/__init__.py (added, +15/-0)
  • plugins/web/searxng/plugin.yaml (added, +7/-0)
  • plugins/web/searxng/provider.py (renamed, +57/-49)
  • plugins/web/tavily/__init__.py (added, +15/-0)
  • plugins/web/tavily/plugin.yaml (added, +7/-0)
  • plugins/web/tavily/provider.py (added, +285/-0)
  • tests/plugins/web/__init__.py (added, +0/-0)
  • tests/plugins/web/test_web_search_provider_plugins.py (added, +475/-0)
  • tests/tools/test_web_providers.py (modified, +164/-24)
  • tests/tools/test_web_providers_brave_free.py (modified, +32/-32)
  • tests/tools/test_web_providers_ddgs.py (modified, +28/-28)
  • tests/tools/test_web_providers_searxng.py (modified, +35/-35)
  • tests/tools/test_web_tools_config.py (modified, +29/-8)
  • tests/tools/test_website_policy.py (modified, +34/-8)
  • tools/web_providers/ARCHITECTURE.md (removed, +0/-73)
  • tools/web_providers/__init__.py (removed, +0/-6)
  • tools/web_providers/base.py (removed, +0/-89)
  • tools/web_tools.py (modified, +256/-1036)

Code Example

class CloudBrowserProvider(ABC):
    provider_name() -> str
    is_configured() -> bool
    create_session(task_id) -> dict  # returns {session_name, bb_session_id, cdp_url, features}
    close_session(session_id) -> bool
    emergency_cleanup(session_id) -> None

---

_PROVIDER_REGISTRY: Dict[str, type] = {
    "browserbase": BrowserbaseProvider,
    "browser-use": BrowserUseProvider,
    "firecrawl": FirecrawlProvider,
}
RAW_BUFFERClick to expand / collapse

Summary

Mirror the web-provider plugin migration from #25182 onto the browser-automation subsystem so the two pluggable-backend categories share the same architecture. Today, third-party web backends drop in as plugins; third-party browser backends require core-code edits.

After #25182, tools/web_providers/ is gone and every web backend lives at plugins/web/<vendor>/ registering through agent.web_search_registry. The browser subsystem still has the old shape — cloud browser providers (Browserbase, Browser Use, Firecrawl Cloud) live as in-tree modules under tools/browser_providers/ and are dispatched via a hardcoded class registry in tools/browser_tool.py.

The work in #25182 established the template; this issue tracks applying it to browser.

Current state (what to migrate)

ABCtools/browser_providers/base.py

class CloudBrowserProvider(ABC):
    provider_name() -> str
    is_configured() -> bool
    create_session(task_id) -> dict  # returns {session_name, bb_session_id, cdp_url, features}
    close_session(session_id) -> bool
    emergency_cleanup(session_id) -> None

Providers — three implementations to migrate:

  • tools/browser_providers/browserbase.py (BrowserbaseProvider)
  • tools/browser_providers/browser_use.py (BrowserUseProvider)
  • tools/browser_providers/firecrawl.py (FirecrawlProvider — separate from web firecrawl)

Hardcoded class registrytools/browser_tool.py:394

_PROVIDER_REGISTRY: Dict[str, type] = {
    "browserbase": BrowserbaseProvider,
    "browser-use": BrowserUseProvider,
    "firecrawl": FirecrawlProvider,
}

Picker rowshermes_cli/tools_config.py:299 (TOOL_CATEGORIES["browser"]):

  • Local Browser (local agent-browser CLI; no CloudBrowserProvider)
  • Browserbase (cloud)
  • Browser Use (cloud)
  • Firecrawl (cloud)
  • Camofox (local anti-detection backend — bypasses the cloud-provider path via _is_camofox_mode())

What "applying the #25182 template" means concretely

  1. New ABCagent/browser_provider.py mirroring agent/web_search_provider.py:

    • name property (replaces provider_name())
    • display_name property (default = name)
    • is_available() (replaces is_configured())
    • Lifecycle methods kept as-is: create_session, close_session, emergency_cleanup
    • get_setup_schema() for picker integration
  2. New registryagent/browser_registry.py mirroring agent/web_search_registry.py:

    • register_provider(provider) / get_provider(name) / list_providers()
    • get_active_browser_provider() reading config["browser"]["cloud_provider"]
    • Same Option B smart-fallback semantics: explicit config wins ignoring is_available() so the dispatcher surfaces typed credential errors instead of silently switching backends
  3. Plugin facade — extend hermes_cli/plugins.py with ctx.register_browser_provider() (one-liner mirror of register_web_search_provider)

  4. Migrate providers to plugins — one commit each:

    • plugins/browser/browserbase/{plugin.yaml,__init__.py,provider.py}
    • plugins/browser/browser_use/{plugin.yaml,__init__.py,provider.py}
    • plugins/browser/firecrawl/{plugin.yaml,__init__.py,provider.py}
  5. Dispatcher cutover — replace _PROVIDER_REGISTRY lookup in tools/browser_tool.py:_get_cloud_provider() with a registry call. The single-cached-provider singleton pattern stays; only the lookup changes from class-instantiation to registry-lookup.

  6. Picker integration_plugin_browser_providers() in hermes_cli/tools_config.py mirroring _plugin_web_search_providers(), injected in _visible_providers() for cat["name"] == "Browser Automation".

  7. Keep non-cloud rows in TOOL_CATEGORIES["browser"]:

    • "Local Browser" — uses agent-browser CLI directly, no CloudBrowserProvider
    • "Camofox" — _is_camofox_mode() short-circuits the cloud path entirely
    • These are the analog of "Nous Subscription" / "Firecrawl Self-Hosted" in the web category — non-provider UX setup rows that don't need plugin migration.
  8. Backward-compat re-export shimstools.browser_tool.{Firecrawl,BrowserbaseProvider,...} likely doesn't have external consumers (this should be checked), but if any tests patch("tools.browser_tool._PROVIDER_REGISTRY", ...) they need migration to the registry.

  9. Tests:

    • Migrate existing tests in tests/tools/test_browser_*.py to use the new ABC at agent.browser_provider
    • Add tests/plugins/browser/test_browser_providers.py mirroring the 44-test pattern in tests/plugins/web/test_web_search_provider_plugins.py
    • Cover: registration, capability detection (cloud vs local), is_available() env-var sensitivity, Option B smart-fallback semantics
  10. Delete tools/browser_providers/ directory in the same PR (after the cutover lands).

Out of scope

  • agent-browser CLI itself (the npm package, installed via post_setup: "agent_browser") — that's the underlying browser automation runtime, not a provider. Stays as-is.
  • Camofox migration — it's local-only with a fundamentally different dispatch path (REST API to a local subprocess instead of CDP-over-WebSocket). Could be folded in if CloudBrowserProvider is renamed to just BrowserProvider and grows a is_local: bool flag, but that's a bigger architectural call. Recommend handling it as a follow-up after this issue ships.
  • browser_tool.py itself — should NOT shrink the way web_tools.py did (-614 LoC). Most of browser_tool.py is dispatcher / lifecycle / CDP plumbing that's independent of which provider backs it. Only the _PROVIDER_REGISTRY lookup site changes.

Architectural parity payoff

After this, both pluggable-backend subsystems work identically:

  • plugins/web/<vendor>/plugins/browser/<vendor>/
  • agent.web_search_registryagent.browser_registry
  • ctx.register_web_search_provider()ctx.register_browser_provider()

Third-party authors can drop a directory under ~/.hermes/plugins/web/ or ~/.hermes/plugins/browser/ and the new backend appears in hermes tools automatically — no core-code edits.

Estimated diff size

Modeling on #25182:

  • ~3 new plugin directories (browserbase / browser_use / firecrawl) × ~150-300 LoC each = +600-900 LoC plugin code
  • +250 LoC for ABC + registry + plugin facade (mirroring agent/web_search_*.py)
  • −150 LoC deleting tools/browser_providers/
  • ~−40 LoC in tools/browser_tool.py (replace _PROVIDER_REGISTRY + class instantiation with registry calls)
  • ~−40 LoC in hermes_cli/tools_config.py (3 picker rows replaced by _plugin_browser_providers())
  • +350 LoC new tests under tests/plugins/browser/

Total: roughly +1000 net LoC, ~20 files, ~12 focused commits. Significantly smaller than #25182 because:

  • ABC + registry templates exist and just need parameterization
  • Only 3 cloud providers vs 7 web providers
  • Browser subsystem has fewer external-test mocking sites to preserve

Acceptance

  • All tests/tools/test_browser_*.py tests pass against the new plugin-based architecture
  • tools/browser_providers/ deleted
  • New tests/plugins/browser/ coverage exists and passes
  • Picker shows: Local Browser, Camofox (hardcoded UX rows) + plugin-registered Browserbase, Browser Use, Firecrawl
  • Third-party browser provider plugins discovered automatically when dropped under ~/.hermes/plugins/browser/<name>/
  • Full test suite: no new regressions vs baseline

References

  • #25182 — web-provider migration this issue mirrors
  • agent/web_search_provider.py — ABC template
  • agent/web_search_registry.py — registry + Option B resolution template
  • plugins/web/firecrawl/ — most complex plugin template (lazy SDK proxy, dual auth, indirect lookup via tools.web_tools)
  • tests/plugins/web/test_web_search_provider_plugins.py — test layout template (44 tests across 5 test classes)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING