Skip to content

feat(ai-providers): add xAI as an AI provider#1865

Merged
datlechin merged 2 commits into
mainfrom
feat/xai-provider
Jul 13, 2026
Merged

feat(ai-providers): add xAI as an AI provider#1865
datlechin merged 2 commits into
mainfrom
feat/xai-provider

Conversation

@datlechin

Copy link
Copy Markdown
Member

Adds xAI (Grok) as an AI provider. One provider, two auth paths, chosen by whether an API key is present (the same either/or shape Cursor already uses):

  • API key: paste a key from the xAI Console. Calls api.x.ai Responses API and bills xAI API credits. Models Grok 4.5 and Grok 4.3.
  • Sign in with xAI: leave the key blank to use a SuperGrok or X Premium+ subscription with no key. OAuth to auth.x.ai, then inference through cli-chat-proxy.grok.com.

Design

The API-key path reuses OpenAIResponsesProvider with a new dialect parameter (default .openAI, so existing OpenAI behavior is unchanged). The subscription path is a full OAuth stack in TablePro/Core/AI/XAI/, mirroring the existing ChatGPT Codex provider (browser-redirect PKCE, loopback callback server, actor token store). The stream parser gained response.reasoning_text.delta so Grok's reasoning renders in chat; this is additive and inert for OpenAI and Copilot.

Adding the provider is otherwise a descriptor registration: the Add Provider menu, Keychain, and settings storage need no changes because they are all descriptor-driven.

What the research changed

Model IDs and OAuth constants were verified against live sources, not memory:

  • grok-4, grok-4-0709, grok-3, grok-3-mini, and grok-code-fast-1 were retired on 2026-05-15 and now silently redirect to grok-4.3, billing at its rate instead of erroring. Shipping one would be an invisible cost regression, so the curated list is only the two current models. A test asserts none of the retired slugs are curated.
  • grok-4.5 always reasons and rejects reasoning_effort values outside low/medium/high; the effort picker and the wire mapping both clamp to that set.

Auth risk, stated plainly

The subscription path reuses xAI's own first-party Grok Build OAuth client, because xAI publishes no third-party client registration. Two consequences, both handled rather than hidden:

  • The browser consent screen shows "Grok Build", not TablePro. The sign-in row footer says so.
  • It sends X-XAI-Token-Auth: xai-grok-cli and x-grok-client-version: 0.2.93, a version floor xAI can bump. Every fragile constant lives in one file (XAI.swift) so a bump is a one-line change, and the API-key path stays fully working as a fallback.

Error handling keeps the two surfaces distinct: a subscription-exhausted 403 from the inference proxy stays a server error (it is a billing signal), while a 403 from the token endpoint is treated as an auth failure so a dead session is cleared.

Tests

New deterministic unit tests (no network, Keychain, or pasteboard): registration and capabilities, PKCE, OAuth authorize/exchange/refresh encoding, token store refresh and single-flight, the Grok CLI request headers and body, the .xai reasoning body shape, and the reasoning_text.delta parser case. A new test also asserts every AIProviderType has a registered descriptor.

Docs

docs/features/ai-assistant.mdx (provider count, add list, an xAI subsection, tool-calling support) and docs/customization/settings.mdx.

Still needs a live key before merge

Three things the public docs do not fully pin down, to confirm with a real key:

  1. Whether api.x.ai/v1/responses accepts the .xai reasoning body (reasoning: {effort} only).
  2. Whether the CLI proxy accepts grok-4.5/grok-4.3 via x-grok-model-override, or only its own catalog (grok-build, grok-composer-2.5-fast, grok-4.5-build-free). If it rejects them, the model picker should be gated by auth mode.
  3. The exact 401 vs 403 response bodies from both surfaces.

Built and linted locally with swiftlint --strict. Not yet run through a full Xcode build.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2bb8762fcd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +90 to +94
throw AIProviderError.mapHTTPError(
statusCode: httpResponse.statusCode,
body: body,
treatForbiddenAsAuthFailure: true
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat invalid_grant as an auth failure

If an xAI refresh token is expired or revoked and the token endpoint returns the standard OAuth 400 {"error":"invalid_grant"} response, this maps it to .serverError; XAITokenStore.refresh only clears stored credentials on .authenticationFailed, so TablePro leaves a dead xAI session in Keychain and every later request retries the same bad refresh token instead of prompting the user to sign in again. Please map token-endpoint invalid_grant/auth 400s to an authentication failure here.

Useful? React with 👍 / 👎.

Comment on lines +103 to +104
if let effort = options.reasoningEffort {
body["reasoning"] = ["effort": effort.xaiReasoningEffort]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate reasoning by the selected Grok model

When the no-key subscription path is used with grok-composer-2.5-fast from XAI.subscriptionModelIDs and a reasoning effort is set on the provider (for example after using Grok 4.5 or an inline model override), this unconditional block sends reasoning.effort to Composer, which does not accept configurable reasoning effort and will reject the request. Gate this field to the Grok models that support it, or clear/ignore the effort when switching to non-reasoning subscription models.

Useful? React with 👍 / 👎.

@mintlify

mintlify Bot commented Jul 13, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jul 13, 2026, 1:26 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@datlechin datlechin merged commit 861b571 into main Jul 13, 2026
2 checks passed
@datlechin datlechin deleted the feat/xai-provider branch July 13, 2026 13:58
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.

1 participant