文件最后提交记录最后更新时间
refactor(web): remove legacy in-tree provider modules Deletes tools/web_providers/{brave_free,ddgs,searxng}.py — the three providers that moved to plugins/web/ in prior commits. tools/web_tools.py no longer imports them (registry dispatch as of d8735963f), so removing them is purely a cleanup pass. Also migrates the existing tests to the new import paths: tests/tools/test_web_providers_brave_free.py tests/tools/test_web_providers_ddgs.py tests/tools/test_web_providers_searxng.py Mechanical rewrites: - from tools.web_providers.X import YSearchProvider -> from plugins.web.X.provider import YWebSearchProvider - .is_configured() -> .is_available() (legacy method -> new method) - .provider_name() -> .name (legacy method -> new property) - from tools.web_providers.base import WebSearchProvider -> from agent.web_search_provider import WebSearchProvider (the subclass-check asserts membership in the new plugin-facing ABC) - sys.modules.delitem("tools.web_providers.ddgs") updated to point at plugins.web.ddgs.provider (cache-busting for lazy ddgs imports) The TestXBackendWiring / TestXSearchOnlyErrors classes (covering _is_backend_available, _get_backend, check_web_api_key, and the "search-only" error paths in web_extract/web_crawl) are untouched — those still test web_tools.py's backend-selection logic, which continues to recognize the names "brave-free" / "ddgs" / "searxng" even after the modules behind them moved to plugins. tools/web_providers/base.py is intentionally NOT deleted by this commit — it's the parent ABC of the legacy modules and shares its name with agent/web_search_provider.py::WebSearchProvider. Removing it surfaces the naming collision (see PR description Finding 0); the real migration PR deletes it in the same commit that drops the _WEB_PLUGIN_SKIPLIST guards in hermes_cli/tools_config.py. Test results: bash scripts/run_tests.sh tests/tools/test_web_providers_*.py -> 65 passed in 3.41s (all rewritten unit tests + unchanged integration tests) bash scripts/run_tests.sh tests/tools/test_web_*.py -> 141 passed in 4.70s (full web test set, post-deletion) 22 天前
refactor(tools): drop hardcoded web picker rows + skiplist; plugins are sole source Removes the seven hardcoded TOOL_CATEGORIES["web"] provider rows that duplicated the plugin-registered providers, and deletes the _WEB_PLUGIN_SKIPLIST that existed to prevent duplicate picker rows during the migration. The Web Search & Extract category now derives its provider rows entirely from agent.web_search_registry via _plugin_web_search_providers(), matching how Spotify, Google Meet, and the image_gen plugins are surfaced. Removed (deduplicated against plugin schemas): - Firecrawl Cloud → plugins.web.firecrawl - Exa → plugins.web.exa - Parallel → plugins.web.parallel - Tavily → plugins.web.tavily - SearXNG → plugins.web.searxng - Brave Search (Free Tier) → plugins.web.brave_free - DuckDuckGo (ddgs) → plugins.web.ddgs (post_setup hook preserved) Retained in TOOL_CATEGORIES["web"]: - Nous Subscription — requires requires_nous_auth + managed_nous_feature + override_env_vars to drive the managed-gateway UX. Not a provider — a different *setup flow* for the firecrawl backend. - Firecrawl Self-Hosted — points firecrawl at a private Docker URL via FIRECRAWL_API_URL only. Same reason: UX setup-flow row, not a provider. These two rows describe alternative auth/billing paths for the firecrawl backend; they intentionally share web_backend="firecrawl" with the plugin row but light up different env-var prompts. Plugin schema extensions ------------------------ - ddgs plugin's get_setup_schema() now emits post_setup: "ddgs" so selection still triggers the pip-install hook in _run_post_setup(). - _plugin_web_search_providers() passes post_setup through verbatim when present in the schema (other future plugins like camofox / a hypothetical playwright-web plugin can opt in the same way). - Picker rows now carry both web_backend (legacy field consumed by setup + selection helpers) and web_search_plugin_name (informational marker), so behavior is identical between hardcoded and plugin-registered rows. Net diff -------- - hermes_cli/tools_config.py: -141/+50 lines (~91 lines net) - plugins/web/ddgs/provider.py: +7/-4 (post_setup field + badge polish) Verified -------- - Compile-clean for both files - Picker shows: 2 hardcoded rows (Nous Subscription, Firecrawl Self-Hosted) + 7 plugin rows (alphabetically: Brave Search, DuckDuckGo, Exa, Firecrawl, Parallel, SearXNG, Tavily). DuckDuckGo row carries post_setup="ddgs" for first-time install. - 173 web-specific tests still pass. 22 天前
fix(web): align _LEGACY_PREFERENCE with legacy 7-provider order + doc cleanup Self-review of the plugin migration surfaced one warning and a handful of doc/dead-code cleanups. None affect production behaviour through the main dispatcher (which always calls tools.web_tools._get_backend() first and preserves the full 7-provider walk), but direct callers of agent.web_search_registry.get_active_*_provider() previously diverged from the legacy order and could return None for users with credentials but no explicit web.backend config key. Changes ------- 1. _LEGACY_PREFERENCE was shipped as a 4-tuple ("brave-free", "firecrawl", "searxng", "ddgs") while the PR description and the legacy _get_backend() candidate order both call for the 7-tuple (firecrawl, parallel, tavily, exa, searxng, brave-free, ddgs). Replaced with the 7-tuple. Verified empirically: with TAVILY+EXA keys and no config, get_active_search_provider() now returns tavily (was None); with EXA+PARALLEL it returns parallel (was None); with BRAVE+FIRECRAWL it returns firecrawl (was brave-free). 2. agent/web_search_registry.py — module docstring, _resolve step-3 docstring, and inline comment all listed the old 4-tuple and claimed "brave-free first because it was the shipped default". The legacy default is "firecrawl". Rewritten to match the new ordering and reference tools.web_tools._get_backend() as the source of truth. 3. agent/web_search_registry.pyget_active_crawl_provider docstring said "only Tavily implements it among built-in providers". Firecrawl also advertises supports_crawl=True after the previous commit. Updated to "Tavily and Firecrawl". 4. plugins/web/tavily/provider.py — module docstring said "Tavily is the only built-in backend that natively crawls". Updated. 5. agent/web_search_provider.py — ABC docstring mentioned only search / extract capabilities. Added crawl for accuracy. 6. plugins/web/{firecrawl,parallel,exa}/provider.py — dead plugin-level cache globals (_firecrawl_client, _parallel_client, _async_parallel_client, _exa_client) were declared but never read (all reads/writes go through _wt.* per the `extracting-inline- helpers-to-plugins` recipe). Removed the dead declarations; the reset-for-tests helpers in firecrawl + parallel now clear the canonical _wt._<name> slots, matching the pattern exa already used. Tests ----- 218/218 web-targeted tests still pass (no test changes needed). 4910/4910 in tests/tools/ still green. 22 天前
Revert "feat(firecrawl): add integration tag for Hermes usage in browser and web providers" (#28862) This reverts commit 273ff5c4a47af4499bbe5e3b1139efd313995554.17 天前
fix(web): align _LEGACY_PREFERENCE with legacy 7-provider order + doc cleanup Self-review of the plugin migration surfaced one warning and a handful of doc/dead-code cleanups. None affect production behaviour through the main dispatcher (which always calls tools.web_tools._get_backend() first and preserves the full 7-provider walk), but direct callers of agent.web_search_registry.get_active_*_provider() previously diverged from the legacy order and could return None for users with credentials but no explicit web.backend config key. Changes ------- 1. _LEGACY_PREFERENCE was shipped as a 4-tuple ("brave-free", "firecrawl", "searxng", "ddgs") while the PR description and the legacy _get_backend() candidate order both call for the 7-tuple (firecrawl, parallel, tavily, exa, searxng, brave-free, ddgs). Replaced with the 7-tuple. Verified empirically: with TAVILY+EXA keys and no config, get_active_search_provider() now returns tavily (was None); with EXA+PARALLEL it returns parallel (was None); with BRAVE+FIRECRAWL it returns firecrawl (was brave-free). 2. agent/web_search_registry.py — module docstring, _resolve step-3 docstring, and inline comment all listed the old 4-tuple and claimed "brave-free first because it was the shipped default". The legacy default is "firecrawl". Rewritten to match the new ordering and reference tools.web_tools._get_backend() as the source of truth. 3. agent/web_search_registry.pyget_active_crawl_provider docstring said "only Tavily implements it among built-in providers". Firecrawl also advertises supports_crawl=True after the previous commit. Updated to "Tavily and Firecrawl". 4. plugins/web/tavily/provider.py — module docstring said "Tavily is the only built-in backend that natively crawls". Updated. 5. agent/web_search_provider.py — ABC docstring mentioned only search / extract capabilities. Added crawl for accuracy. 6. plugins/web/{firecrawl,parallel,exa}/provider.py — dead plugin-level cache globals (_firecrawl_client, _parallel_client, _async_parallel_client, _exa_client) were declared but never read (all reads/writes go through _wt.* per the `extracting-inline- helpers-to-plugins` recipe). Removed the dead declarations; the reset-for-tests helpers in firecrawl + parallel now clear the canonical _wt._<name> slots, matching the pattern exa already used. Tests ----- 218/218 web-targeted tests still pass (no test changes needed). 4910/4910 in tests/tools/ still green. 22 天前
refactor(web): remove legacy in-tree provider modules Deletes tools/web_providers/{brave_free,ddgs,searxng}.py — the three providers that moved to plugins/web/ in prior commits. tools/web_tools.py no longer imports them (registry dispatch as of d8735963f), so removing them is purely a cleanup pass. Also migrates the existing tests to the new import paths: tests/tools/test_web_providers_brave_free.py tests/tools/test_web_providers_ddgs.py tests/tools/test_web_providers_searxng.py Mechanical rewrites: - from tools.web_providers.X import YSearchProvider -> from plugins.web.X.provider import YWebSearchProvider - .is_configured() -> .is_available() (legacy method -> new method) - .provider_name() -> .name (legacy method -> new property) - from tools.web_providers.base import WebSearchProvider -> from agent.web_search_provider import WebSearchProvider (the subclass-check asserts membership in the new plugin-facing ABC) - sys.modules.delitem("tools.web_providers.ddgs") updated to point at plugins.web.ddgs.provider (cache-busting for lazy ddgs imports) The TestXBackendWiring / TestXSearchOnlyErrors classes (covering _is_backend_available, _get_backend, check_web_api_key, and the "search-only" error paths in web_extract/web_crawl) are untouched — those still test web_tools.py's backend-selection logic, which continues to recognize the names "brave-free" / "ddgs" / "searxng" even after the modules behind them moved to plugins. tools/web_providers/base.py is intentionally NOT deleted by this commit — it's the parent ABC of the legacy modules and shares its name with agent/web_search_provider.py::WebSearchProvider. Removing it surfaces the naming collision (see PR description Finding 0); the real migration PR deletes it in the same commit that drops the _WEB_PLUGIN_SKIPLIST guards in hermes_cli/tools_config.py. Test results: bash scripts/run_tests.sh tests/tools/test_web_providers_*.py -> 65 passed in 3.41s (all rewritten unit tests + unchanged integration tests) bash scripts/run_tests.sh tests/tools/test_web_*.py -> 141 passed in 4.70s (full web test set, post-deletion) 22 天前
fix(web): align _LEGACY_PREFERENCE with legacy 7-provider order + doc cleanup Self-review of the plugin migration surfaced one warning and a handful of doc/dead-code cleanups. None affect production behaviour through the main dispatcher (which always calls tools.web_tools._get_backend() first and preserves the full 7-provider walk), but direct callers of agent.web_search_registry.get_active_*_provider() previously diverged from the legacy order and could return None for users with credentials but no explicit web.backend config key. Changes ------- 1. _LEGACY_PREFERENCE was shipped as a 4-tuple ("brave-free", "firecrawl", "searxng", "ddgs") while the PR description and the legacy _get_backend() candidate order both call for the 7-tuple (firecrawl, parallel, tavily, exa, searxng, brave-free, ddgs). Replaced with the 7-tuple. Verified empirically: with TAVILY+EXA keys and no config, get_active_search_provider() now returns tavily (was None); with EXA+PARALLEL it returns parallel (was None); with BRAVE+FIRECRAWL it returns firecrawl (was brave-free). 2. agent/web_search_registry.py — module docstring, _resolve step-3 docstring, and inline comment all listed the old 4-tuple and claimed "brave-free first because it was the shipped default". The legacy default is "firecrawl". Rewritten to match the new ordering and reference tools.web_tools._get_backend() as the source of truth. 3. agent/web_search_registry.pyget_active_crawl_provider docstring said "only Tavily implements it among built-in providers". Firecrawl also advertises supports_crawl=True after the previous commit. Updated to "Tavily and Firecrawl". 4. plugins/web/tavily/provider.py — module docstring said "Tavily is the only built-in backend that natively crawls". Updated. 5. agent/web_search_provider.py — ABC docstring mentioned only search / extract capabilities. Added crawl for accuracy. 6. plugins/web/{firecrawl,parallel,exa}/provider.py — dead plugin-level cache globals (_firecrawl_client, _parallel_client, _async_parallel_client, _exa_client) were declared but never read (all reads/writes go through _wt.* per the `extracting-inline- helpers-to-plugins` recipe). Removed the dead declarations; the reset-for-tests helpers in firecrawl + parallel now clear the canonical _wt._<name> slots, matching the pattern exa already used. Tests ----- 218/218 web-targeted tests still pass (no test changes needed). 4910/4910 in tests/tools/ still green. 22 天前
feat(web): add xAI Web Search provider plugin Adds a new bundled web search provider plugin backed by xAI's agentic Web Search tool (server-side web_search on the Responses API). Slots in alongside the existing Firecrawl / Tavily / Exa / Brave / SearXNG / DDGS providers; opt in via web.backend: xai (or auto-selected by the registry's single-provider shortcut when it's the only available web provider, matching every other backend's behavior). Reuses the existing xAI HTTP credential plumbing (tools/xai_http.py) so it works with both hermes auth login xai-oauth (SuperGrok OAuth) and XAI_API_KEY — no new credential paths, no new env vars, no new setup-wizard prompts. The existing xai_grok post_setup hook handles credential collection. Reference: https://docs.x.ai/developers/tools/web-search Provider behavior ----------------- - Sends a structured prompt to Grok with tools=[{"type": "web_search"}] enabled and include=["no_inline_citations"], then parses results from a {"results": [...]} JSON block (primary), falling back to url_citation annotations (secondary) and the top-level citations list (last-ditch). Annotation fallback falls through to citations when no rows are extractable, so future annotation types xAI may add don't silently mask real data. - HTTP 200 + {"error": {...}} envelopes (model-overload, refusal) are surfaced as failures rather than masked as success-with-empty- results. - HTTP 401 on the OAuth path triggers a single force_refresh=True retry — closes two gaps the resolver's proactive JWT-exp shortcut doesn't cover: opaque (non-JWT) access tokens and mid-window revocation. Env-var (XAI_API_KEY) credentials never retry; they can't be refreshed and an immediate retry would just burn quota. - is_available() is a cheap probe (env var OR auth.json read), never invokes the OAuth resolver — required by the ABC contract because it runs on every hermes tools repaint and at tool-registration time. - Class docstring documents the LLM-in-a-trench-coat trust model so callers piping untrusted input into web_search know returned URLs are model-generated and should be validated before fetching. Config (config.yaml): web: backend: xai xai: model: grok-4.3 # optional, defaults to grok-4.3 allowed_domains: # optional, max 5 — mutex with excluded_domains - arxiv.org excluded_domains: # optional, max 5 - example-spam.com timeout: 90 # optional, seconds Files ----- - plugins/web/xai/plugin.yaml (new) plugin manifest - plugins/web/xai/__init__.py (new) register(ctx) hook - plugins/web/xai/provider.py (new) XAIWebSearchProvider impl - tools/xai_http.py (+47) has_xai_credentials() cheap-probe helper + keyword-only force_refresh arg on resolve_xai_http_ credentials() (backwards compatible; all 9 other call sites unaffected) - tools/web_tools.py (+11) "xai" added to configured- backend set + branch in _is_backend_available() - tests/tools/test_web_providers_xai.py (new, 39 tests) covers identity, cheap-probe semantics, JSON / annotation / citations parse paths, request payload shape, error envelopes, OAuth force-refresh-on-401 retry, env-var-no-retry guard, 500-not- retried guard, refresh-returns- same-token guard, OAuth runtime resolution, and backend wiring. Tests ----- - 39 xai-suite passes - 79 sibling web-provider tests (brave-free, ddgs, searxng, base) pass - 119 cross-suite tests for other xai_http callers (transcription, x_search, tts) pass — verifies the new keyword-only arg is BC - scripts/check-windows-footguns.py: clean on all 5 modified files No edits to run_agent.py, cli.py, gateway/, toolsets, config schema, plugin core, or auth core. 16 天前
feat(web): brave_free plugin (first migration from tools/web_providers/) Adds plugins/web/brave_free/ as the first plugin built against the new WebSearchProvider ABC. Mirrors the plugins/image_gen/openai/ layout exactly: plugins/web/brave_free/ plugin.yaml kind: backend, provides_web_providers: [brave-free] __init__.py register(ctx) -> ctx.register_web_search_provider(...) provider.py BraveFreeWebSearchProvider(WebSearchProvider) Behavior preserved: same name ("brave-free" with hyphen), same env var (BRAVE_SEARCH_API_KEY), same HTTP request shape, same response normalization. The legacy tools/web_providers/brave_free.py is left in place — the dispatcher in tools/web_tools.py still references it. Task 7 cuts over the dispatcher to the new registry; Task 10 deletes the legacy file. E2E verified: HERMES_PLUGINS_DEBUG=1 python -c " from hermes_cli.plugins import _ensure_plugins_discovered _ensure_plugins_discovered() from agent.web_search_registry import list_providers print([p.name for p in list_providers()]) " # -> ['brave-free'] 22 天前