@pai/xai-grok
Run Grok models with an explicitly authorized xAI subscription credential.
@pai/xai-grok is an experimental, server-only Vercel AI SDK provider for
local or personal PAI agents. It uses a Grok subscription credential rather
than an xAI API key.
The package delegates every protocol concern to @ai-sdk/xai. PAI owns only
the OAuth credential lifecycle and the authenticated transport. Unlike the
ChatGPT subscription provider, there is no separate subscription endpoint to
adapt to: the credential authorizes the same https://api.x.ai/v1 that an API
key does, so model selection, provider options, tools, streaming, and response
handling all behave exactly as they do upstream.
Install And Authenticate
pnpm add @pai/xai-grok ai zod
pnpm exec pai-xai-grok loginLogin is an RFC 8628 device
authorization: the CLI prints a short code and a verification link, opens the
link in a browser, and waits for approval. Because nothing listens on a
loopback port, the same command works over SSH, in a container, and on a
laptop. Pass --no-browser to print the link instead of opening it.
The default credential is plaintext at .pai/xai-grok/auth.json in the current
user's home directory, protected with owner-only permissions on Unix. Treat it
as a password. Windows mode bits are best effort; inject a keychain-backed
store when stronger at-rest protection is required.
Create A Provider
Authentication and model creation are deliberately explicit:
import { createXaiGrok, createXaiGrokAuth } from "@pai/xai-grok";
import { defineAgent } from "@pai/core";
const auth = createXaiGrokAuth();
const grok = createXaiGrok({ auth });
export const agent = defineAgent({
name: "coding-agent",
model: {
model: grok("grok-4.6"),
providerOptions: { xai: { reasoningEffort: "high" } },
},
instructions: "Help with the user's coding task.",
});Provider and model construction perform no filesystem, browser, listener, or network work. The first model request reads the credential.
Model Surface
The provider is the upstream XaiProvider, so responses, chat, image,
video, and files all work, credentialed by the subscription. Which model
IDs a given subscription exposes is decided by xAI, not by this package.
experimental_realtime is the one exception and throws
unsupported_transport. xAI's realtime API is a WebSocket that authenticates
from headers passed to the WebSocket constructor rather than through fetch,
so a subscription credential cannot reach it. The same applies to the
WebSocket-based streaming transcription path. Use @ai-sdk/xai with an API key
for those.
Model Discovery
listXaiGrokModels reads the models the signed-in account may actually use, so
a host can show real choices rather than a list PAI would have to curate:
import { listXaiGrokModels } from "@pai/xai-grok";
const models = await listXaiGrokModels(auth);
const coding = models.filter((model) => model.kind === "language");Each entry carries its id, any aliases xAI accepts for it, and a
contextLength where one is published. kind is derived from what the model
is billed for — a language model is charged for completion text, an image model
for images — rather than from an ID prefix, so it keeps working as xAI's naming
changes. The call requires a credential: the answer is account-specific, so an
unauthenticated list would be a guess.
Provider Options
Use the upstream xai namespace, matching @ai-sdk/xai metadata and tool
conventions. The provider adds, removes, and rewrites nothing. The option types
are re-exported under their upstream names, so typing them takes no direct
@ai-sdk/xai dependency:
import type { XaiResponsesProviderOptions } from "@pai/xai-grok";
const selection = {
model: grok("grok-4.6"),
providerOptions: {
xai: {
reasoningEffort: "high",
reasoningSummary: "auto",
} satisfies XaiResponsesProviderOptions,
},
};Prompt caching
xAI caches prompt prefixes on its own and reports hits in
usage.inputTokenDetails.cacheReadTokens. There is no per-thread cache key to
set, so unlike the ChatGPT subscription provider this one has nothing for a
caller to configure. Structure reusable context as stable message prefixes.
Credential Lifecycle
Access tokens last six hours, and xAI rotates the refresh token on every exchange: the previous one dies the moment a refresh succeeds. Two independent refreshes therefore race to spend the same token, and the loser is left holding a dead credential.
The auth manager exists to make that safe:
- Refreshes are serialized per credential store — including across separately constructed auth managers — so concurrent model calls collapse onto one rotation.
- A rotated refresh token is kept whenever it can be read, even if the rest of the response is unusable or the credential file cannot be written. An unwritable store leaves the new credential in memory so the process keeps working, and the write is retried until it succeeds. A response that cannot be read at all is the one case where a rotation is genuinely lost and a new login is needed.
- A credential written by something else —
pai-xai-grok loginin another process — takes over from one held only in memory, so the documented recovery works even mid-failure. - Rotations have a minimum interval. A badly wrong host clock makes every credential look expiring, and without a floor each request would retire another working refresh token.
Share one auth manager (and one provider) per credential; run one process per credential file.
A refresh xAI rejects with invalid_grant — an expired, reused, or revoked
token — is not retried against the same refresh token. It surfaces as
refresh_rejected and requires a new login. Failures carrying no OAuth error
code, including a 401 from a captive portal or proxy, are treated as transient
and retried.
Security Boundary
- The bearer token is attached only to the configured base URL. A request aimed
at any other origin, or at a path outside it, fails with
unsupported_requestbefore the credential is read. - Inbound
authorization,cookie,proxy-authorization, andx-api-keyheaders are dropped before the real credential is attached, so no second identity travels with the request. - Error messages never include tokens, device codes, or OAuth response bodies.
status()reports only whether a credential exists and when it expires.
Supply fetch only from a trusted source: it observes OAuth credentials and
bearer headers.
Errors
Every failure this package originates is an XaiGrokError with a stable
code. Two things travel unwrapped: a cancellation rejects with the caller's
own abort reason, and a custom credentialStore surfaces whatever it throws —
only the built-in file store maps storage failures onto credentials_io. The
codes are not_logged_in,
login_denied, login_timeout, login_cancelled, login_failed,
login_in_progress, refresh_failed, refresh_rejected,
credentials_corrupt, credentials_insecure, credentials_io,
unsupported_request, and unsupported_transport.
Scope
This is a personal-subscription integration for local or personal agents. A subscription authorizes one person's use; it is not a way to serve a multi-tenant product. Check your xAI subscription terms before relying on it.