Skip to content

feat(web): add xAI Web Search provider plugin#29036

Closed
Jaaneek wants to merge 1 commit into
NousResearch:mainfrom
Jaaneek:xai-web-search
Closed

feat(web): add xAI Web Search provider plugin#29036
Jaaneek wants to merge 1 commit into
NousResearch:mainfrom
Jaaneek:xai-web-search

Conversation

@Jaaneek

@Jaaneek Jaaneek commented May 20, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a new bundled web search provider plugin backed by xAI's agentic Web Search tool on the Responses API.

Why this approach:

  • Reuses existing credential plumbing. Hermes already has tools/xai_http.py for the xAI image-gen / TTS / STT providers (OAuth-first via hermes auth login xai-oauth, env-var fallback via XAI_API_KEY). No new auth paths, no new env vars, no new setup wizard — the existing xai_grok post_setup hook handles credential prompts.
  • Plugin, not core. Lives entirely under plugins/web/xai/ mirroring the plugins/web/brave_free/ layout, registers via the standard register(ctx) plugin hook, and touches only 11 lines of tools/web_tools.py (one set-element + one if-branch in _is_backend_available).
  • Cheap is_available(). The ABC contract requires is_available() to be safe to call on every hermes tools repaint and at tool-registration time. The provider uses a new has_xai_credentials() helper that does an env-var check + single auth.json read — never invokes the OAuth resolver (which can trigger a network token refresh).

Related Issue

Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

File Status Lines Notes
plugins/web/xai/plugin.yaml new 7 Plugin manifest, kind: backend, provides_web_providers: [xai]
plugins/web/xai/__init__.py new 14 register(ctx) registration hook
plugins/web/xai/provider.py new 522 XAIWebSearchProvider(WebSearchProvider) implementation
tools/xai_http.py +40 New has_xai_credentials() cheap-probe helper
tools/web_tools.py +11 "xai" added to configured-backend set + _is_backend_available branch
tests/tools/test_web_providers_xai.py new 620 35-test suite covering identity, availability, search paths, error envelopes, OAuth resolution, and backend wiring

Provider behavior:

  • Sends a structured prompt to Grok with tools=[{"type": "web_search"}] enabled and include=["no_inline_citations"] so URLs come back from annotations / citations rather than inline markdown.
  • Primary parse path: extract a {"results": [...]} JSON block from output[*].content[*].text. Tolerates leading/trailing prose because reasoning models occasionally narrate.
  • Fallback 1: derive results from url_citation annotations + surrounding-text descriptions.
  • Fallback 2: derive from top-level citations list (URLs only).
  • HTTP 200 + {"error": {...}} envelopes (model-overload, refusal) are surfaced as failures rather than masked as success-with-empty-results.
  • allowed_domains / excluded_domains config keys map to xAI's domain filters; capped at 5 each per the API; mutual-exclusion enforced locally to skip a known-bad request.

Config (config.yaml):

web:
  backend: xai
  xai:
    model: grok-4.3              # optional, defaults to grok-4.3
    allowed_domains:             # optional, max 5 — mutually exclusive with excluded_domains
      - arxiv.org
    excluded_domains:            # optional, max 5
      - example-spam.com
    timeout: 90                  # optional, seconds

How to Test

  1. Run the new test suite:

    scripts/run_tests.sh tests/tools/test_web_providers_xai.py -v

    Expected: 35 passed.

  2. Sibling-provider regression check:

    scripts/run_tests.sh tests/tools/test_web_providers_brave_free.py \
                        tests/tools/test_web_providers_ddgs.py \
                        tests/tools/test_web_providers_searxng.py \
                        tests/tools/test_web_providers.py -q

    Expected: 79 passed, no regressions.

  3. Live smoke test (requires xAI credentials):

    # Option A: env-var path
    export XAI_API_KEY="sk-xai-…"
    
    # Option B: OAuth path (SuperGrok subscription)
    hermes auth login xai-oauth
    
    # Configure backend
    echo 'web:\n  backend: xai' >> ~/.hermes/config.yaml
    
    # Exercise via CLI
    hermes --toolsets web -q "Search the web for the latest xAI Grok release notes"

    Expected: agent calls web_search, results come back as {title, url, description, position} rows, no credential prompts, no errors.

  4. Verify is_available() is cheap (regression-guard test asserts the resolver is never called):

    scripts/run_tests.sh tests/tools/test_web_providers_xai.py::TestXAIProviderIsAvailable::test_is_available_does_not_call_resolver -v

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (feat(web):, fix(web/xai):)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run scripts/run_tests.sh and all tests pass (35 new + 79 adjacent green)
  • I've added tests for my changes (35-test suite)
  • I've tested on my platform: macOS 15.2 (darwin 24.6.0)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — provider has extensive docstrings; config keys documented in provider.py module docstring
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (web.xai.* keys are documented in the provider module docstring; no top-level schema change)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A (uses existing plugin + web-provider patterns)
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — pure-Python, only depends on httpx (already a dep); no POSIX-only primitives
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A (no tool schema changes; this is a backend plugin behind the existing web_search tool)

For New Skills

N/A — this PR adds a web search provider plugin, not a skill.

Screenshots / Logs

Test run output:

$ scripts/run_tests.sh tests/tools/test_web_providers_xai.py -q
4 workers [35 items]
....................................                                     [100%]
============================== 35 passed in 1.95s ==============================

Sibling-suite regression check:

$ scripts/run_tests.sh tests/tools/test_web_providers_brave_free.py \
                       tests/tools/test_web_providers_ddgs.py \
                       tests/tools/test_web_providers_searxng.py \
                       tests/tools/test_web_providers.py -q
........................................................................ [ 91%]
.......                                                                  [100%]
79 passed in 3.24s

Trust-model note (also captured in the provider class docstring): unlike index-backed providers (Brave / Tavily / Exa) which return verbatim search-engine results, this backend is an LLM that decides which URLs to surface, generates titles and descriptions itself, and is influenced by the content of the query. A maliciously crafted query (e.g. injected via untrusted upstream input the agent picked up) can in principle steer Grok into emitting attacker-chosen URLs. Callers that pipe untrusted text directly into web_search should treat returned URLs the same way they'd treat any model-generated link, validate before fetching.

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.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins tool/web Web search and extraction provider/xai xAI (Grok) labels May 20, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #29042 — your commit was cherry-picked onto current main with authorship preserved in git log. Thanks for the clean plugin layout, the ABC-honoring cheap is_available(), and the trust-model documentation in the class docstring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have provider/xai xAI (Grok) tool/web Web search and extraction type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants