文件最后提交记录最后更新时间
feat(web): exa plugin — first multi-capability migration (search + extract) Migrates Exa from the inline _exa_search() / _exa_extract() helpers in tools/web_tools.py to a bundled plugin at plugins/web/exa/. This is the first plugin in this PR to advertise supports_extract=True, exercising the multi-capability ABC path that the initial three migrations (brave_free, ddgs, searxng — all search-only) did not cover. Both Exa methods are sync — the SDK is sync-only. The web_extract_tool dispatcher in tools/web_tools.py will continue to call them inline until Task "dispatch-extract-all" cuts it over to the registry. Behaviour preserved bit-for-bit aside from the ABC method-name change: - is_configured() -> is_available() - provider_name() -> name (property) - "exa" stays as the registered name - Module-level _exa_client cache + lazy from exa_py import Exa preserved at the new location. - Errors (ValueError for missing API key, ImportError for missing SDK, generic Exception) caught and surfaced as {"success": False, "error": ...} instead of raising. Adds "exa" to _WEB_PLUGIN_SKIPLIST in hermes_cli/tools_config.py so the hardcoded TOOL_CATEGORIES["web"] row and the plugin-injected row don't duplicate during the spike. The skip-list goes away in the cleanup phase along with the hardcoded row. The legacy inline _exa_search / _exa_extract / _get_exa_client / _exa_client in tools/web_tools.py are NOT deleted yet — the dispatcher still references them. They go away in the next dispatcher-cutover commit. E2E verified: - Plugin discovers + registers - .supports_search/.supports_extract/.supports_crawl = (True, True, False) - .get_setup_schema() returns the picker row shape - resolve(): explicit exa + EXA_API_KEY -> exa; without key -> exa (registered but unavailable, dispatcher surfaces "EXA_API_KEY not set" error) 22 天前
feat(web): exa plugin — first multi-capability migration (search + extract) Migrates Exa from the inline _exa_search() / _exa_extract() helpers in tools/web_tools.py to a bundled plugin at plugins/web/exa/. This is the first plugin in this PR to advertise supports_extract=True, exercising the multi-capability ABC path that the initial three migrations (brave_free, ddgs, searxng — all search-only) did not cover. Both Exa methods are sync — the SDK is sync-only. The web_extract_tool dispatcher in tools/web_tools.py will continue to call them inline until Task "dispatch-extract-all" cuts it over to the registry. Behaviour preserved bit-for-bit aside from the ABC method-name change: - is_configured() -> is_available() - provider_name() -> name (property) - "exa" stays as the registered name - Module-level _exa_client cache + lazy from exa_py import Exa preserved at the new location. - Errors (ValueError for missing API key, ImportError for missing SDK, generic Exception) caught and surfaced as {"success": False, "error": ...} instead of raising. Adds "exa" to _WEB_PLUGIN_SKIPLIST in hermes_cli/tools_config.py so the hardcoded TOOL_CATEGORIES["web"] row and the plugin-injected row don't duplicate during the spike. The skip-list goes away in the cleanup phase along with the hardcoded row. The legacy inline _exa_search / _exa_extract / _get_exa_client / _exa_client in tools/web_tools.py are NOT deleted yet — the dispatcher still references them. They go away in the next dispatcher-cutover commit. E2E verified: - Plugin discovers + registers - .supports_search/.supports_extract/.supports_crawl = (True, True, False) - .get_setup_schema() returns the picker row shape - resolve(): explicit exa + EXA_API_KEY -> exa; without key -> exa (registered but unavailable, dispatcher surfaces "EXA_API_KEY not set" error) 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 天前