Skip to content

xAI provider

Standalone — usable in any Node project, no @warlock.js/core required.

@warlock.js/ai-xai is the provider adapter for xAI’s Grok models. xAI speaks the OpenAI Chat Completions protocol, so XaiSDK is a thin wrapper over the OpenAI adapter: it constructs one internal OpenAISDK pointed at https://api.x.ai/v1 and labeled provider: "xai", then delegates everything to it — while injecting xAI’s own capability inference so Grok model names resolve to the right vision and reasoning flags.

You get a vendor-neutral ModelContract — the same shape every agent, workflow, and supervisor already speaks.

Terminal window
npm install @warlock.js/ai @warlock.js/ai-xai

XaiSDK is a class holding a long-lived wrapped client. Build it once at boot and reuse it everywhere — every model it produces shares the same connection pool, auth, and rate-limit state.

import { XaiSDK } from "@warlock.js/ai-xai";
const xai = new XaiSDK({ apiKey: process.env.XAI_API_KEY! });
  • baseURL defaults to https://api.x.ai/v1 (the xAI OpenAI-compatible endpoint). Override only to target a proxy/gateway.
  • provider defaults to "xai" and flows through to ModelContract.provider, AgentReport.model.provider, and logs. Set it to relabel a gateway upstream.
  • Every other upstream client option (timeout, maxRetries, defaultHeaders, fetch, …) is forwarded verbatim to the wrapped client.
const xai = new XaiSDK({
apiKey: process.env.XAI_API_KEY!,
baseURL: "https://my-gateway.internal/xai",
provider: "xai-gateway",
});

Build a model, hand it to an agent, run it. execute() never throws — failures land in error as a typed AIError.

import { ai } from "@warlock.js/ai";
import { XaiSDK } from "@warlock.js/ai-xai";
const xai = new XaiSDK({ apiKey: process.env.XAI_API_KEY! });
const assistant = ai.agent({
model: xai.model({ name: "grok-4" }),
systemPrompt: "You are a concise senior TypeScript engineer.",
});
const { text, usage, error } = await assistant.execute("Why use generics?");
if (error) {
console.warn(error.code, error.category);
} else {
console.log(text, usage.total);
}
xai.model({ name: "grok-4" }); // vision + reasoning auto-true
xai.model({ name: "grok-3-mini" }); // reasoning auto-true, vision false
xai.model({ name: "grok-2-vision" }); // vision auto-true
xai.model({ name: "grok-4", temperature: 0.2 }); // sampling controls
xai.model({ name: "grok-3", vision: true }); // explicit capability override

Returns a ModelContract you pass straight into any agent, workflow, or supervisor.

This adapter ships xAI’s own name lists — Grok ids don’t match OpenAI’s gpt-* / o* prefixes. Capabilities are injected per model before delegating; an explicit value always wins over inference.

Modelvisionreasoning
grok-4truetrue (reasoning-first)
grok-3falsefalse
grok-3-minifalsetrue (the “think” variant)
grok-2-visiontruefalse
grok-2falsefalse
  • vision — inferred true for the grok-4 and grok-2-vision prefixes; false otherwise.
  • reasoning — inferred true for grok-4 and grok-3-mini; drives whether reasoning.effort is forwarded as reasoning_effort on the wire.
  • structuredOutput / promptCaching and the image/PDF/audio wire mapping all come from the wrapped OpenAI adapter unchanged.

Override vision, reasoning, or structuredOutput explicitly via .model({ name, vision?, reasoning?, structuredOutput? }).

grok-4 and grok-3-mini are reasoning-capable. reasoning.effort ("low" | "medium" | "high" | "none") maps verbatim to the OpenAI-compatible reasoning_effort param. When capabilities.reasoning is false (e.g. grok-3), the option is dropped — the adapter never forwards it. Pin reasoning: true to force it for a custom/gateway id.

const model = xai.model({ name: "grok-4" }); // reasoning auto-true
await model.complete(messages, { reasoning: { effort: "high" } }); // → reasoning_effort: "high"

Image attachments reach the wire only on a vision-capable model (grok-4, grok-2-vision); the agent’s modality gate throws otherwise. Mapping to OpenAI image_url parts is identical to the OpenAI adapter.

const model = xai.model({ name: "grok-4" });
const response = await model.complete([
{
role: "user",
content: [
{ type: "text", text: "What's in this photo?" },
{ type: "image", source: { url: "https://example.com/cat.jpg" } },
],
},
]);

Every response reports token usage (input, output, total, cachedTokens) — on model.complete() and the streaming done chunk too. prompt_tokens contains the cached tokens (inputIncludesCache: true).

xAI has no built-in price table: every model on xAI’s pricing page (checked 2026-10-17) bills all tokens at a higher long-context rate once the prompt reaches 200k tokens, which a flat rate cannot express. Attach a pricing registry — keyed by model name, in USD per million tokens (input, output, optional cachedInput; xAI has no cache-write charge). Resolution: per-model pricing > SDK registry > undefined (no cost computed).

import { ai, totalCost } from "@warlock.js/ai";
import { XaiSDK } from "@warlock.js/ai-xai";
const xai = new XaiSDK({
apiKey: process.env.XAI_API_KEY ?? "",
pricing: {
// Below-200k rates; long prompts cost more.
"grok-4.3": { input: 1.25, output: 2.5, cachedInput: 0.2 },
},
});
const { usage } = await ai.agent({ model: xai.model({ name: "grok-4.3" }) }).execute("hi");
console.log(totalCost(usage.cost)); // undefined when unpriced

Prompt caching. xAI’s Chat Completions accepts prompt_cache_key, used as the x-grok-conv-id cache key (maximizing cache hits). Set promptCacheKey on the model — sent while the SDK keeps its default "xai" provider label:

const model = xai.model({ name: "grok-4.3", promptCacheKey: "conversation:42" });

An overloaded endpoint (HTTP 503) surfaces as ProviderOverloadedError (code: "PROVIDER_OVERLOADED", retryable: true); every provider error carries status, retryable and billed (false for 401 / 402 / 403, otherwise "unknown").

Need an offline token estimate first? xai.count(text) returns a fast character-heuristic approximation — good for budgeting and quota guards, not for billing.

xAI does not currently expose a public embeddings endpoint. xai.embedder({...}) is wired for protocol parity, but a call to embed() / embedMany() fails upstream — point an embedder at a dedicated provider like OpenAI for vectors.

xai.image({...}) delegates to the wrapped OpenAI Images adapter, which only recognizes gpt-image-* / dall-e-* ids and rejects anything else at construction. xAI’s image generation (“Grok Imagine”) is a separate, non- OpenAI-Images-compatible surface, so use the OpenAI provider’s image() for OpenAI-Images generation.

  • OpenAI provider — the wrapped adapter; full wire / streaming / structured-output / multipart surface applies here.
  • @warlock.js/ai — the agent, tool, and system-prompt surface these models plug into.
  • For provider-specific notes and the latest model ids, see the warlock-js-ai-xai skill.

This adapter delegates Chat Completions to the OpenAI adapter, so providerOptions.openai.body adds native Chat request fields. Other provider keys are ignored. Plain-object fields merge one level deep with neutral options; caller-provided keys win, while arrays, scalars and null replace the field. The OpenAI adapter’s protected conversation, tool and transport fields remain protected here too. Send only fields supported by this provider; do not reuse OpenAI-only bodies across a mixed-provider fallback chain.

Models from this adapter have no native countTokens method. Check model.countTokens before using an optional native counting capability; sdk.count(text) is a separate offline estimate.

xAI’s official web-search guide (verified 2026-10-18) sends { type: "web_search" } to POST /v1/responses. XaiSDK currently wraps Chat Completions, so native web_search and x_search provider tools are explicitly unsupported and throw InvalidRequestError before transport, explaining that the vendor’s Responses API is required. Use xAI’s own Responses SDK for native search. The framework’s api: "responses" adapter remains direct-OpenAI-only; passing a tool definition does not change this transport. No xAI hosted-search request count or list fee is guessed. See compatible native search.