Skip to content

feat(xai): enable hosted web search for Grok OAuth#71

Merged
amondnet merged 3 commits into
mainfrom
amondnet/61
Jul 13, 2026
Merged

feat(xai): enable hosted web search for Grok OAuth#71
amondnet merged 3 commits into
mainfrom
amondnet/61

Conversation

@amondnet

@amondnet amondnet commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • distinguish the Grok CLI subscription proxy from the xAI developer API at the Responses flavor boundary
  • forward hosted web_search tools and forced choices only for the verified Grok OAuth surface
  • translate Grok web_search_call output, results, and URL annotations into Anthropic server-tool, result, and citation events
  • document the separate hosted-search charge and the unverified api.x.ai behavior

Testing

  • cargo test --all-features --workspace
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo fmt --all --check
  • git diff --check

Live validation

A local live probe was not run because neither XAI_API_KEY nor a Grok OAuth credential is available in this worktree. The request and response wire behavior is covered by focused fixtures based on the live event sequence recorded in the issue.

Closes #61


Summary by cubic

Enables hosted web_search for Grok OAuth (Grok CLI proxy) and translates Grok search calls, results, and URL citations into Anthropic tool and citation events. Keeps the api.x.ai developer API unchanged and documents separate hosted-search billing; also hardens the streaming mapper for web search edge cases. Addresses #61.

  • New Features

    • Added ResponsesFlavor::Grok (detected when auth = xai_oauth targets grok.com); kept Xai for api.x.ai.
    • Forward hosted web_search tool and forced choice only for grok; drop on xai and downgrade choice to auto.
    • Translate web_search_call into server_tool_use and web_search_tool_result; map response.output_text.annotation.added url_citation to Anthropic citation deltas, propagating encrypted_index from results and opening a text block if needed.
    • Skip OpenAI-Beta: responses=experimental for both Xai and Grok.
    • Updated docs to clarify proxy-only capability and separate billing; added tests for search, citations, and header behavior.
  • Bug Fixes

    • Hardened web search blocks: treat non-array results as []; support id/call_id and action/input.
    • Preserve encrypted_contentencrypted_index mapping per-URL; omit encrypted_index when missing.
    • Emit citations even if no text block is open.

Written for commit 4bf9df6. Summary will update on new commits.

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.60000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/model/responses.rs 97.36% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the Grok CLI subscription proxy (the Grok flavor) to enable end-to-end hosted web search tool capabilities, translating Grok's search calls, results, and citations into Anthropic-compatible formats. It updates documentation, configuration, request translation, and adds corresponding integration tests. The code review feedback suggests improving annotation_added in src/model/responses.rs by ensuring a text block is active before appending citations to prevent potential protocol errors.

Comment thread src/model/responses.rs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files

Architecture diagram
sequenceDiagram
    participant Client as Anthropic Client
    participant GW as shunt Gateway
    participant Cfg as Config (flavor detection)
    participant Req as Request Translator
    participant SSE as SSE State Machine
    participant Grok as Grok CLI Proxy (grok.com)
    participant Xai as xAI API (api.x.ai)

    Note over Client,Xai: Hosted web_search flow for Grok OAuth subscription

    Client->>GW: POST /responses (tools: [web_search], tool_choice: forced web_search)
    GW->>Cfg: responses_flavor(auth=xai_oauth)
    Cfg-->>GW: ResponsesFlavor::Grok
    alt Grok flavor (OAuth subscription)
        GW->>Req: translate_request (flavor=Grok, include web_search)
        Req->>Req: Keep web_search tool, map tool_choice to {"type": "web_search"}
        Req->>Req: Skip OpenAi-Beta header
        Req-->>GW: Shaped request with hosted web_search tool
        GW->>Grok: Forward request (with x-grok-cli identity headers)
        Grok-->>GW: SSE events: web_search_call, url_citation, text...
        GW->>SSE: apply events
        SSE->>SSE: web_search_call -> server_tool_use + web_search_tool_result blocks
        SSE->>SSE: url_citation -> citations_delta with encrypted_index mapping
        SSE-->>GW: Translated Anthropic SSE events
        GW-->>Client: Anthropic-formatted response
    else Xai flavor (api.x.ai, API key)
        GW->>Req: translate_request (flavor=Xai, drop web_search)
        Req->>Req: Remove web_search tool from tools array
        Req->>Req: Downgrade forced web_search tool_choice to "auto"
        Req->>Req: Skip OpenAi-Beta header
        Req-->>GW: Shaped request without hosted tools
        GW->>Xai: Forward request
        Xai-->>GW: SSE events (no web_search_call)
        GW->>SSE: apply events
        SSE-->>GW: Standard text/reasoning events only
        GW-->>Client: Anthropic response (no search tool)
    end

    Note over SSE: Encrypted index mapping from web_search results to url_citation annotations
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/config.rs Outdated
Comment thread src/model/responses.rs
@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces a dedicated ResponsesFlavor::Grok for the Grok CLI subscription proxy and wires up end-to-end hosted web search support: forwarding the web_search tool, translating web_search_call output items into Anthropic server_tool_use/web_search_tool_result blocks, and mapping url_citation annotations into citations_delta events with correct encrypted_index propagation.

  • New ResponsesFlavor::Grok is detected when auth = xai_oauth targets grok.com; the previously used Xai flavor is retained for api.x.ai API-key users, keeping web search disabled there.
  • SSE translation in AnthropicSseMachine gains annotation_added and web_search_done handlers, plus text_citations and web_search_indexes state fields to correlate citations with their upstream encrypted content.
  • Request shaping in responses_request.rs now passes the hosted web search tool and {"type":"web_search"} tool-choice through for Grok, while continuing to drop both for Xai; reasoning and text-verbosity suppression is shared across both xAI flavors.

Confidence Score: 5/5

Safe to merge — the flavor split is table-driven and narrowly scoped to grok.com OAuth hosts, leaving all other providers untouched.

The ResponsesFlavor::Grok detection correctly keys on both auth mode and host, the SSE translation handles edge cases (orphan citations, non-array results, missing encrypted_index) with dedicated tests for each, and index management in web_search_done is consistent with the existing block lifecycle. No behavioral changes for OpenAI, ChatGPT, or the xAI developer API.

No files require special attention.

Important Files Changed

Filename Overview
src/model/responses.rs Core SSE translation machine: adds annotation_added and web_search_done handlers, text_citations/web_search_indexes state, and correct encrypted_index propagation with null-pruning; index management and buffer clearing are correct throughout.
src/config.rs Adds ResponsesFlavor::Grok variant and refines responses_flavor() to key on both auth mode and host (grok.com), fixing the previous coarse XaiOauth match; test assertion updated to reflect the new variant.
src/model/responses_request.rs Extends tools() and tool_choice() to pass web search through for the Grok flavor (via the existing _ => wildcard arm) and shares reasoning/text-verbosity suppression between Xai and Grok via matches!; logic is unchanged for other flavors.
tests/responses_translate.rs Three new integration tests cover Grok web search round-trip, orphan citation with missing encrypted_index, and non-array results fallback; all scenarios from the PR description are exercised.
src/adapters/responses.rs Extends the OpenAI-Beta header guard to suppress the header for both Xai and Grok using a matches! macro; no other behavioral changes.
docs/m6-xai-provider.md Documentation updated to distinguish Grok from Xai flavors, document the separate hosted-search billing, and note api.x.ai web-search status as unverified; accurate and clear.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CC as Claude Code
    participant SH as shunt (Grok flavor)
    participant GK as Grok CLI Proxy

    CC->>SH: POST /v1/messages (with web_search tool)
    SH->>GK: POST /responses (web_search tool forwarded)

    GK-->>SH: response.output_item.added (web_search_call)
    Note over SH: ignored (processing deferred to done)

    GK-->>SH: response.output_item.done (web_search_call + results)
    SH->>SH: close_any(), build web_search_indexes map
    SH-->>CC: content_block_start (server_tool_use)
    SH-->>CC: content_block_stop
    SH-->>CC: content_block_start (web_search_tool_result)
    SH-->>CC: content_block_stop

    GK-->>SH: response.output_item.added (message)
    SH-->>CC: content_block_start (text)

    GK-->>SH: response.output_text.delta
    SH-->>CC: content_block_delta (text_delta)

    GK-->>SH: response.output_text.annotation.added (url_citation)
    SH->>SH: lookup encrypted_index from web_search_indexes
    SH-->>CC: content_block_delta (citations_delta)

    GK-->>SH: response.output_text.done
    SH-->>CC: content_block_stop

    GK-->>SH: response.completed (usage)
    SH-->>CC: message_delta (stop_reason: end_turn)
    SH-->>CC: message_stop
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CC as Claude Code
    participant SH as shunt (Grok flavor)
    participant GK as Grok CLI Proxy

    CC->>SH: POST /v1/messages (with web_search tool)
    SH->>GK: POST /responses (web_search tool forwarded)

    GK-->>SH: response.output_item.added (web_search_call)
    Note over SH: ignored (processing deferred to done)

    GK-->>SH: response.output_item.done (web_search_call + results)
    SH->>SH: close_any(), build web_search_indexes map
    SH-->>CC: content_block_start (server_tool_use)
    SH-->>CC: content_block_stop
    SH-->>CC: content_block_start (web_search_tool_result)
    SH-->>CC: content_block_stop

    GK-->>SH: response.output_item.added (message)
    SH-->>CC: content_block_start (text)

    GK-->>SH: response.output_text.delta
    SH-->>CC: content_block_delta (text_delta)

    GK-->>SH: response.output_text.annotation.added (url_citation)
    SH->>SH: lookup encrypted_index from web_search_indexes
    SH-->>CC: content_block_delta (citations_delta)

    GK-->>SH: response.output_text.done
    SH-->>CC: content_block_stop

    GK-->>SH: response.completed (usage)
    SH-->>CC: message_delta (stop_reason: end_turn)
    SH-->>CC: message_stop
Loading

Reviews (2): Last reviewed commit: "fix(xai): harden web search response blo..." | Re-trigger Greptile

Comment thread src/model/responses.rs
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the Grok CLI subscription proxy (ResponsesFlavor::Grok), enabling end-to-end translation of the hosted web_search tool and its associated citations into Anthropic-compatible blocks (server_tool_use, web_search_tool_result, and citation blocks). The review feedback highlights two robustness issues in src/model/responses.rs: first, removing null title or cited_text fields from citations could violate the Anthropic API spec, so defaulting them to empty strings is recommended; second, the content field in web_search_tool_result could receive a null value if the search results are null, so a fallback to an empty array should be enforced.

Comment thread src/model/responses.rs
Comment thread src/model/responses.rs
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@sonarqubecloud

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 풀 리퀘스트는 Grok CLI 구독 프록시를 지원하기 위해 새로운 Grok ResponsesFlavor를 도입하고, 호스팅된 web_search 도구 및 URL 인용(citation) 데이터를 Anthropic 규격(server_tool_use, web_search_tool_result, 인용 블록)으로 변환하는 기능을 구현합니다. 이를 위해 AnthropicSseMachine에 인용 및 웹 검색 인덱싱 상태 관리 로직을 추가하고, 요청 변환 시 Grok flavor에 대해 웹 검색 도구를 유지하도록 처리했습니다. 관련 문서와 통합 테스트도 충실히 추가되었습니다. 제공된 리뷰 댓글이 없으므로 코드 변경 사항에 대한 추가 피드백은 없습니다.

@amondnet amondnet merged commit 908a195 into main Jul 13, 2026
14 checks passed
@amondnet amondnet deleted the amondnet/61 branch July 13, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(xai): pass hosted web_search through on the grok subscription surface

1 participant