feat(xai): add Grok 4.6 support

This commit is contained in:
Hannes Rudolph
2026-08-12 09:55:28 -06:00
parent ff8cb4ed7b
commit 87da8ed237
27 changed files with 137 additions and 83 deletions
+2 -2
View File
@@ -298,7 +298,7 @@ messages and normalizes `stats.cached` into `cacheRead`; legacy
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | OAuth: `xai/auto`; API key: `xai/grok-4.5` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | OAuth: `xai/auto`; API key: `xai/grok-4.6` |
| Xiaomi | `xiaomi` / `xiaomi-token-plan` | `XIAOMI_API_KEY` / `XIAOMI_TOKEN_PLAN_API_KEY` | `xiaomi/mimo-v2.5` / `xiaomi-token-plan/mimo-v2.5-pro` |
#### Quirks worth knowing
@@ -317,7 +317,7 @@ messages and normalizes `stats.cached` into `cacheRead`; legacy
Model ids use a `nvidia/<vendor>/<model>` namespace (for example `nvidia/nvidia/nemotron-...`); pickers preserve the literal `<provider>/<model-id>` composition while the canonical key sent to the API stays single-prefixed.
</Accordion>
<Accordion title="xAI">
Uses the xAI Responses path. The recommended path is SuperGrok/X Premium OAuth; fresh setup selects `xai/auto`, which follows xAI's authenticated default model without an OpenClaw update. Existing concrete model ids stay pinned. API keys still work via `XAI_API_KEY` or plugin config and use `grok-4.5` as the setup default. Grok `web_search` reuses the same auth profile before API-key fallback. Older `/fast` and `params.fastMode: true` configurations still resolve through xAI's Grok 4.3 compatibility redirects, but new configurations should select a current model directly. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
Uses the xAI Responses path. The recommended path is SuperGrok/X Premium OAuth; fresh setup selects `xai/auto`, which follows xAI's authenticated default model without an OpenClaw update. Existing concrete model ids stay pinned. API keys still work via `XAI_API_KEY` or plugin config and use `grok-4.6` as the setup default. Grok `web_search` reuses the same auth profile before API-key fallback. Older `/fast` and `params.fastMode: true` configurations still resolve through xAI's Grok 4.3 compatibility redirects, but new configurations should select a current model directly. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
</Accordion>
</AccordionGroup>
+1 -1
View File
@@ -396,7 +396,7 @@ cannot be read, account-wide exposure fails closed.
- `timeoutSeconds`: scrape request timeout in seconds (default: `60`).
- `plugins.entries.xai.config.xSearch`: xAI X Search (Grok web search) settings.
- `enabled`: enable the X Search provider.
- `model`: Grok model to use for search (e.g. `"grok-4.5"`).
- `model`: Grok model to use for search (e.g. `"grok-4.6"`).
- `plugins.entries.memory-core.config.dreaming`: memory dreaming settings. See [Dreaming](/concepts/dreaming) for phases and thresholds.
- `enabled`: master dreaming switch (default `false`).
- `frequency`: cron cadence for each full dreaming sweep (`"0 3 * * *"` by default).
+1 -1
View File
@@ -603,7 +603,7 @@ Live is opt-in, so there is no fixed "CI model list." `OPENCLAW_LIVE_MODELS=mode
| `openrouter/minimax/minimax-m2.7` | |
| `opencode-go/glm-5` | |
| `openrouter/ai21/jamba-large-1.7` | |
| `xai/grok-4.5` | |
| `xai/grok-4.6` | |
| `xai/grok-4.20-0309-reasoning` | |
| `zai/glm-5.1` | |
| `fireworks/accounts/fireworks/models/glm-5p1` | |
+12 -11
View File
@@ -56,7 +56,7 @@ OAuth client.
</Step>
<Step title="API-key path">
API-key setup still works for xAI Console keys and for media surfaces
that need key-backed provider config. It uses Grok 4.5 as the setup
that need key-backed provider config. It uses Grok 4.6 as the setup
default:
```bash
@@ -115,6 +115,7 @@ see [legacy compatibility and moving aliases](#legacy-compatibility-and-moving-a
| Family | Model ids |
| -------------- | ------------------------------------------------------------ |
| Grok 4.6 | `grok-4.6` |
| Grok 4.5 | `grok-4.5` (aliases: `grok-4.5-latest`, `grok-build-latest`) |
| Grok Build 0.1 | `grok-build-0.1` |
| Grok 4.3 | `grok-4.3` (aliases: `grok-4.3-latest`, `grok-latest`) |
@@ -122,9 +123,9 @@ see [legacy compatibility and moving aliases](#legacy-compatibility-and-moving-a
<Tip>
Use `xai/auto` to follow xAI's authenticated OAuth default, or select a concrete
id such as `xai/grok-4.5` to remain pinned. API-key setup uses Grok 4.5 by
default; `grok-build-0.1`, Grok 4.3, and both dated Grok 4.20 variants remain
selectable.
id such as `xai/grok-4.6` to remain pinned. API-key setup uses Grok 4.6 by
default; Grok 4.5, `grok-build-0.1`, Grok 4.3, and both dated Grok 4.20 variants
remain selectable.
</Tip>
Catalog context and token-cost metadata follows xAI's live
@@ -537,7 +538,7 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
| Key | Type | Default | Description |
| ----------------- | ------- | ------------------------- | ------------------------------------------------ |
| `enabled` | boolean | Automatic for xAI models | Disable, or opt in for a known non-xAI provider |
| `model` | string | `grok-4.5` | Model used for x_search requests |
| `model` | string | `grok-4.6` | Model used for x_search requests |
| `baseUrl` | string | - | xAI Responses base URL override |
| `inlineCitations` | boolean | - | Include inline citations in results |
| `maxTurns` | number | - | Maximum conversation turns |
@@ -552,7 +553,7 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
config: {
xSearch: {
enabled: true,
model: "grok-4.5",
model: "grok-4.6",
baseUrl: "https://api.x.ai/v1",
inlineCitations: true,
},
@@ -574,7 +575,7 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
| Key | Type | Default | Description |
| ---------------- | ------- | ------------------------ | ------------------------------------------------ |
| `enabled` | boolean | Automatic for xAI models | Disable, or opt in for a known non-xAI provider |
| `model` | string | `grok-4.5` | Model used for code execution requests |
| `model` | string | `grok-4.6` | Model used for code execution requests |
| `maxTurns` | number | - | Maximum conversation turns |
| `timeoutSeconds` | number | `30` | Request timeout in seconds |
@@ -590,7 +591,7 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
config: {
codeExecution: {
enabled: true,
model: "grok-4.5",
model: "grok-4.6",
},
},
},
@@ -628,8 +629,8 @@ stale context metadata on active 4.20 rows. It does not pin active 4.20
to disable it.
- The bundled xAI wrapper strips unsupported contains-count schema bounds
and unsupported reasoning *effort* payload keys before sending native
xAI requests. Grok 4.5 supports low, medium, and
high effort (default high). Grok 4.3 supports none, low, medium, and high
xAI requests. Grok 4.6 and Grok 4.5 support low, medium, and high effort
(default high). Grok 4.3 supports none, low, medium, and high
effort (default low). Other reasoning-capable xAI models do not expose a
configurable effort control, but still request
`include: ["reasoning.encrypted_content"]` so prior encrypted reasoning
@@ -659,7 +660,7 @@ OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_TEST_QUIET=1 pnpm test:live -- extensions/xai
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_XAI_VIDEO=1 pnpm test:live -- extensions/xai/xai.live.test.ts -t "classic Grok Imagine"
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_XAI_VIDEO=1 pnpm test:live -- extensions/xai/xai.live.test.ts -t "Grok Imagine Video 1.5"
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_TEST_QUIET=1 pnpm test:live -- extensions/xai/x-search.live.test.ts
OPENCLAW_LIVE_GATEWAY_MODELS="xai/grok-4.5,xai/grok-build-0.1,xai/grok-4.3,xai/grok-4.20-0309-reasoning,xai/grok-4.20-0309-non-reasoning" OPENCLAW_LIVE_GATEWAY_MAX_MODELS=0 OPENCLAW_LIVE_GATEWAY_SMOKE=0 pnpm test:live -- src/gateway/gateway-models.profiles.live.test.ts
OPENCLAW_LIVE_GATEWAY_MODELS="xai/grok-4.6,xai/grok-4.5,xai/grok-build-0.1,xai/grok-4.3,xai/grok-4.20-0309-reasoning,xai/grok-4.20-0309-non-reasoning" OPENCLAW_LIVE_GATEWAY_MAX_MODELS=0 OPENCLAW_LIVE_GATEWAY_SMOKE=0 pnpm test:live -- src/gateway/gateway-models.profiles.live.test.ts
OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_TEST_QUIET=1 OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS=xai pnpm test:live -- test/image-generation.runtime.live.test.ts
```
+2 -2
View File
@@ -21,7 +21,7 @@ registered by the bundled `xai` plugin under the `tools` contract.
| Tool name | `code_execution` |
| Provider plugin | `xai` (bundled, `enabledByDefault: true`) |
| Auth | xAI auth profile, `XAI_API_KEY`, or `plugins.entries.xai.config.webSearch.apiKey` |
| Default model | `grok-4.5` |
| Default model | `grok-4.6` |
| Default timeout | 30 seconds |
| Default `maxTurns` | unset (xAI applies its own internal limit) |
@@ -100,7 +100,7 @@ For local execution, use [`exec`](/tools/exec) instead.
config: {
codeExecution: {
enabled: true, // required for a known non-xAI model provider
model: "grok-4.5", // optional explicit model pin
model: "grok-4.6", // optional explicit model pin
maxTurns: 2, // optional cap on internal tool turns
timeoutSeconds: 30, // request timeout (default: 30)
},
+1 -1
View File
@@ -532,7 +532,7 @@ every provider. xAI credentials are always required.
config: {
xSearch: {
enabled: true, // required for a known non-xAI model provider
model: "grok-4.5",
model: "grok-4.6",
baseUrl: "https://api.x.ai/v1", // optional, overrides webSearch.baseUrl
inlineCitations: false,
maxTurns: 2,
+1 -1
View File
@@ -58,7 +58,7 @@ describe("xai api helpers", () => {
providerId: "xai",
ctx: {
provider,
modelId: "grok-4.5",
modelId: "grok-4.6",
modelRegistry: { find: () => null } as never,
providerConfig: { baseUrl: "", models: [] },
},
+1 -1
View File
@@ -155,7 +155,7 @@ describe("xai code_execution tool", () => {
expect(mockFetch).toHaveBeenCalled();
expect(firstFetchUrl(mockFetch)).toContain("api.x.ai/v1/responses");
const body = parseFirstRequestBody(mockFetch);
expect(body.model).toBe("grok-4.5");
expect(body.model).toBe("grok-4.6");
expect(body.store).toBe(false);
expect(body.reasoning).toEqual({ effort: "low" });
expect(body.max_turns).toBe(2);
+2 -2
View File
@@ -64,8 +64,8 @@ describe("xAI doctor contract", () => {
expect(result.changes).toHaveLength(4);
expect(result.config).not.toBe(config);
expect(result.config.plugins?.entries?.xai?.config).toEqual({
webSearch: { model: "grok-4.5", timeoutSeconds: 60 },
xSearch: { model: "grok-4.5", inlineCitations: true },
webSearch: { model: "grok-4.6", timeoutSeconds: 60 },
xSearch: { model: "grok-4.6", inlineCitations: true },
codeExecution: { model: "grok-build-0.1", maxTurns: 2 },
});
expect(result.config.tools?.web).toEqual(config.tools?.web);
+3 -1
View File
@@ -206,6 +206,7 @@ describe("xai provider plugin", () => {
const fetchGuard: LiveModelCatalogFetchGuard = vi.fn(async () => ({
response: Response.json({
data: [
{ id: "grok-4.6", object: "model" },
{ id: "grok-4.5", object: "model" },
{ id: "grok-4.20-0309-reasoning", object: "model" },
{ id: "grok-4.20-0309-non-reasoning", object: "model" },
@@ -222,6 +223,7 @@ describe("xai provider plugin", () => {
});
expect(provider.apiKey).toBe("xai-key");
expect(provider.models.map((model) => model.id)).toContain("grok-4.6");
expect(provider.models.map((model) => model.id)).toContain("grok-4.5");
expect(provider.models.map((model) => model.id)).toContain("grok-4.20-0309-reasoning");
expect(provider.models.map((model) => model.id)).toContain("grok-4.20-0309-non-reasoning");
@@ -802,7 +804,7 @@ describe("xai provider plugin", () => {
expect(
provider.isModernModelRef?.({
provider: "xai",
modelId: "grok-4.3",
modelId: "grok-4.6",
} as never),
).toBe(true);
expect(
+22 -14
View File
@@ -4,16 +4,16 @@ import {
asOptionalRecord,
normalizeOptionalLowercaseString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { normalizeXaiModelId } from "./model-id.js";
import { isXaiFrontierModelId, normalizeXaiModelId } from "./model-id.js";
export const XAI_BASE_URL = "https://api.x.ai/v1";
export const XAI_DEFAULT_IMAGE_MODEL = "grok-imagine-image";
export const XAI_IMAGE_MODELS = ["grok-imagine-image", "grok-imagine-image-quality"] as const;
export const XAI_DEFAULT_CONTEXT_WINDOW = 1_000_000;
const XAI_GROK_45_CONTEXT_WINDOW = 500_000;
const XAI_FRONTIER_CONTEXT_WINDOW = 500_000;
const XAI_CODE_CONTEXT_WINDOW = 256_000;
export const XAI_DEFAULT_MAX_TOKENS = 64_000;
export const XAI_DEFAULT_MODEL_ID = "grok-4.5";
export const XAI_DEFAULT_MODEL_ID = "grok-4.6";
type XaiCost = ModelDefinitionConfig["cost"];
@@ -54,7 +54,7 @@ const XAI_GROK_43_COST = {
cacheWrite: 0,
} satisfies XaiCost;
const XAI_GROK_45_COST = {
const XAI_FRONTIER_COST = {
input: 2,
output: 6,
cacheRead: 0.5,
@@ -69,12 +69,19 @@ const XAI_GROK_BUILD_COST = {
} satisfies XaiCost;
const XAI_MODEL_CATALOG_ROWS: readonly XaiCatalogModelRow[] = [
[
"grok-4.6",
"Grok 4.6",
true,
["text", "image"],
{ contextWindow: XAI_FRONTIER_CONTEXT_WINDOW, cost: XAI_FRONTIER_COST },
],
[
"grok-4.5",
"Grok 4.5",
true,
["text", "image"],
{ contextWindow: XAI_GROK_45_CONTEXT_WINDOW, cost: XAI_GROK_45_COST },
{ contextWindow: XAI_FRONTIER_CONTEXT_WINDOW, cost: XAI_FRONTIER_COST },
],
[
"grok-build-0.1",
@@ -128,6 +135,7 @@ const XAI_MODEL_CATALOG: readonly XaiCatalogEntry[] = XAI_MODEL_CATALOG_ROWS.map
);
const XAI_SELECTABLE_MODEL_IDS = new Set<string>([
"grok-4.6",
"grok-4.5",
"grok-build-0.1",
"grok-4.3",
@@ -254,12 +262,12 @@ export function buildXaiModelDefinition(): ModelDefinitionConfig {
return toModelDefinition(
XAI_MODEL_CATALOG.find((entry) => entry.id === XAI_DEFAULT_MODEL_ID) ?? {
id: XAI_DEFAULT_MODEL_ID,
name: "Grok 4.5",
name: "Grok 4.6",
reasoning: true,
input: ["text", "image"],
contextWindow: XAI_GROK_45_CONTEXT_WINDOW,
contextWindow: XAI_FRONTIER_CONTEXT_WINDOW,
maxTokens: XAI_DEFAULT_MAX_TOKENS,
cost: XAI_GROK_45_COST,
cost: XAI_FRONTIER_COST,
},
);
}
@@ -310,7 +318,7 @@ export function resolveXaiCatalogEntry(modelId: string) {
});
}
if (
lower.startsWith("grok-4.5") ||
isXaiFrontierModelId(lower) ||
lower.startsWith("grok-4.3") ||
lower.startsWith("grok-4.20") ||
lower.startsWith("grok-4-1") ||
@@ -321,17 +329,17 @@ export function resolveXaiCatalogEntry(modelId: string) {
name: trimmed,
reasoning: !lower.includes("non-reasoning"),
input: ["text", "image"],
contextWindow: lower.startsWith("grok-4.5")
? XAI_GROK_45_CONTEXT_WINDOW
contextWindow: isXaiFrontierModelId(lower)
? XAI_FRONTIER_CONTEXT_WINDOW
: XAI_DEFAULT_CONTEXT_WINDOW,
maxTokens:
lower.startsWith("grok-4.5") || lower.startsWith("grok-4.3")
isXaiFrontierModelId(lower) || lower.startsWith("grok-4.3")
? XAI_DEFAULT_MAX_TOKENS
: lower.startsWith("grok-4.20")
? 30_000
: XAI_DEFAULT_MAX_TOKENS,
cost: lower.startsWith("grok-4.5")
? XAI_GROK_45_COST
cost: isXaiFrontierModelId(lower)
? XAI_FRONTIER_COST
: lower.startsWith("grok-4.3")
? XAI_GROK_43_COST
: lower.startsWith("grok-4.20")
+9
View File
@@ -1,6 +1,7 @@
// Xai tests cover model id plugin behavior.
import { describe, expect, it } from "vitest";
import { normalizeXaiModelId } from "./api.js";
import { isXaiFrontierModelId } from "./model-id.js";
describe("normalizeXaiModelId", () => {
it("normalizes family-specific aliases but preserves the global alias", () => {
@@ -33,10 +34,18 @@ describe("normalizeXaiModelId", () => {
});
it("leaves current xai model ids unchanged", () => {
expect(normalizeXaiModelId("grok-4.6")).toBe("grok-4.6");
expect(normalizeXaiModelId("grok-4.20-beta-latest-reasoning")).toBe(
"grok-4.20-beta-latest-reasoning",
);
expect(normalizeXaiModelId("grok-4.20-0309-reasoning")).toBe("grok-4.20-0309-reasoning");
expect(normalizeXaiModelId("grok-4")).toBe("grok-4");
});
it("identifies current frontier model ids and aliases", () => {
expect(isXaiFrontierModelId("grok-4.6")).toBe(true);
expect(isXaiFrontierModelId("grok-4.5-latest")).toBe(true);
expect(isXaiFrontierModelId("grok-build-latest")).toBe(true);
expect(isXaiFrontierModelId("grok-4.3")).toBe(false);
});
});
+7
View File
@@ -1,6 +1,13 @@
// Xai plugin module implements model id behavior.
export const XAI_OAUTH_AUTO_MODEL_ID = "auto";
export function isXaiFrontierModelId(id: string): boolean {
const normalized = normalizeXaiModelId(id.trim().toLowerCase());
return ["grok-4.6", "grok-4.5"].some(
(prefix) => normalized === prefix || normalized.startsWith(`${prefix}-`),
);
}
export function normalizeXaiModelId(id: string): string {
if (id === "grok-4.3-latest") {
return "grok-4.3";
+3 -1
View File
@@ -22,7 +22,7 @@ describe("xai onboard", () => {
const cfg = applyXaiConfig({});
expect(cfg.models?.providers?.xai?.baseUrl).toBe("https://api.x.ai/v1");
expect(cfg.models?.providers?.xai?.api).toBe("openai-responses");
expect(XAI_DEFAULT_MODEL_REF).toBe("xai/grok-4.5");
expect(XAI_DEFAULT_MODEL_REF).toBe("xai/grok-4.6");
expect(resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model)).toBe(XAI_DEFAULT_MODEL_REF);
});
@@ -77,6 +77,7 @@ describe("xai onboard", () => {
"grok-3",
"grok-code-fast-1",
"grok-4.20-beta-latest-reasoning",
"grok-4.6",
"grok-4.5",
"grok-build-0.1",
"grok-4.3",
@@ -96,6 +97,7 @@ describe("xai onboard", () => {
expect(cfg.models?.providers?.xai?.baseUrl).toBe("https://api.x.ai/v1");
expect(cfg.models?.providers?.xai?.api).toBe("openai-responses");
expect(cfg.models?.providers?.xai?.models.map((m) => m.id)).toEqual([
"grok-4.6",
"grok-4.5",
"grok-build-0.1",
"grok-4.3",
+7 -1
View File
@@ -12,7 +12,13 @@ import { resolveXaiCatalogEntry, XAI_BASE_URL } from "./model-definitions.js";
import { normalizeXaiModelId, XAI_OAUTH_AUTO_MODEL_ID } from "./model-id.js";
import { applyXaiRuntimeModelCompat } from "./runtime-model-compat.js";
const XAI_MODERN_MODEL_PREFIXES = ["grok-4.5", "grok-build-0.1", "grok-4.3", "grok-4.20"] as const;
const XAI_MODERN_MODEL_PREFIXES = [
"grok-4.6",
"grok-4.5",
"grok-build-0.1",
"grok-4.3",
"grok-4.20",
] as const;
export function isModernXaiModel(modelId: string): boolean {
const normalized = normalizeXaiModelId(modelId.trim());
@@ -27,9 +27,11 @@ describe("xai provider thinking policy", () => {
});
it.each([
["xai", "grok-4.6"],
["xai", "grok-4.5"],
["xai", "grok-4.5-latest"],
["xai", "grok-build-latest"],
["x-ai", "grok-4.6"],
["x-ai", "grok-4.5"],
["x-ai", "grok-4.5-latest"],
["x-ai", "grok-build-latest"],
+2 -3
View File
@@ -4,7 +4,7 @@ import type {
ProviderThinkingProfile,
} from "openclaw/plugin-sdk/plugin-entry";
import { resolveXaiCatalogEntry } from "./model-definitions.js";
import { normalizeXaiModelId } from "./model-id.js";
import { isXaiFrontierModelId, normalizeXaiModelId } from "./model-id.js";
import { isXaiProviderId } from "./provider-id.js";
export function resolveThinkingProfile(
@@ -15,8 +15,7 @@ export function resolveThinkingProfile(
return { levels: [{ id: "off" }], defaultLevel: "off" };
}
const modelId = normalizeXaiModelId(ctx.modelId.trim().toLowerCase());
const isGrok45 = modelId === "grok-4.5" || modelId.startsWith("grok-4.5-");
if (isGrok45) {
if (isXaiFrontierModelId(modelId)) {
return {
levels: [{ id: "low" }, { id: "medium" }, { id: "high" }],
defaultLevel: "high",
+22 -19
View File
@@ -24,26 +24,29 @@ describe("xai runtime model compat", () => {
});
});
it("maps Grok 4.5 thinking levels to its supported reasoning efforts", () => {
const model = applyXaiRuntimeModelCompat({
id: "grok-4.5",
provider: "xai",
reasoning: true,
});
it.each(["grok-4.6", "grok-4.5"])(
"maps %s thinking levels to its supported reasoning efforts",
(modelId) => {
const model = applyXaiRuntimeModelCompat({
id: modelId,
provider: "xai",
reasoning: true,
});
expect(model.compat).toMatchObject({
supportsReasoningEffort: true,
supportedReasoningEfforts: ["low", "medium", "high"],
});
expect(model.thinkingLevelMap).toEqual({
off: null,
minimal: "low",
low: "low",
medium: "medium",
high: "high",
xhigh: "high",
});
});
expect(model.compat).toMatchObject({
supportsReasoningEffort: true,
supportedReasoningEfforts: ["low", "medium", "high"],
});
expect(model.thinkingLevelMap).toEqual({
off: null,
minimal: "low",
low: "low",
medium: "medium",
high: "high",
xhigh: "high",
});
},
);
it("suppresses reasoning efforts for non-reasoning models", () => {
const model = applyXaiRuntimeModelCompat({
+2 -1
View File
@@ -2,6 +2,7 @@
// Reasoning effort is configurable only for current flagship Grok models; encrypted reasoning
// include/replay is handled separately in stream.ts for every reasoning-capable xAI model.
import { applyXaiModelCompat } from "./model-compat.js";
import { isXaiFrontierModelId } from "./model-id.js";
type XaiRuntimeModelCompat = {
compat?: unknown;
@@ -43,7 +44,7 @@ function normalizeXaiCompatModelId(id: unknown): string {
function supportsConfigurableXaiReasoningEffort(model: XaiRuntimeModelCompat): boolean {
const id = normalizeXaiCompatModelId(model.id);
const isConfigurableModel = isGrok43Model(id) || id === "grok-4.5" || id.startsWith("grok-4.5-");
const isConfigurableModel = isGrok43Model(id) || isXaiFrontierModelId(id);
return model.reasoning === true && isConfigurableModel;
}
+2 -2
View File
@@ -108,7 +108,7 @@ function runXaiToolPayloadWrapper(params: {
async function captureXaiResponsesPayloadWithThinking(
reasoning: ModelThinkingLevel = "low",
modelId = "grok-4.5",
modelId = "grok-4.6",
): Promise<Record<string, unknown>> {
const model = applyXaiRuntimeModelCompat({
api: "openai-responses",
@@ -545,7 +545,7 @@ describe("xai stream wrappers", () => {
expect(payload.include).toEqual(["reasoning.encrypted_content"]);
}, 10_000);
it("clamps unsupported Grok 4.5 off reasoning to low", async () => {
it("clamps unsupported Grok 4.6 off reasoning to low", async () => {
const payload = await captureXaiResponsesPayloadWithThinking("off");
expect(payload.reasoning).toEqual({ effort: "low", summary: "auto" });
+17 -4
View File
@@ -244,7 +244,7 @@ describe("xai web search config resolution", () => {
it("prefers configured api keys and resolves grok scoped defaults", () => {
expect(resolveXaiWebSearchCredential({ grok: { apiKey: "xai-secret" } })).toBe("xai-secret");
expect(resolveXaiWebSearchModel()).toBe("grok-4.5");
expect(resolveXaiWebSearchModel()).toBe("grok-4.6");
expect(resolveXaiInlineCitations()).toBe(false);
});
@@ -644,8 +644,8 @@ describe("xai web search config resolution", () => {
});
it("uses default model when not specified", () => {
expect(resolveXaiWebSearchModel({})).toBe("grok-4.5");
expect(resolveXaiWebSearchModel(undefined)).toBe("grok-4.5");
expect(resolveXaiWebSearchModel({})).toBe("grok-4.6");
expect(resolveXaiWebSearchModel(undefined)).toBe("grok-4.6");
});
it("uses a Grok-specific 60s default timeout while preserving overrides", () => {
@@ -676,7 +676,7 @@ describe("xai web search config resolution", () => {
expect(firstFetchUrl(mockFetch)).toBe("https://api.x.ai/proxy/v1/responses");
expect(firstFetchBody(mockFetch)).toMatchObject({
model: "grok-4.5",
model: "grok-4.6",
store: false,
reasoning: { effort: "low" },
tools: [{ type: "web_search" }],
@@ -924,6 +924,7 @@ describe("xai web search response parsing", () => {
describe("xai provider models", () => {
it("publishes only current selectable chat models newest first", () => {
expect(buildXaiCatalogModels().map((model) => model.id)).toEqual([
"grok-4.6",
"grok-4.5",
"grok-build-0.1",
"grok-4.3",
@@ -932,6 +933,17 @@ describe("xai provider models", () => {
]);
});
it("publishes Grok 4.6 with its current metadata", () => {
expectCatalogEntry("grok-4.6", {
id: "grok-4.6",
reasoning: true,
input: ["text", "image"],
contextWindow: 500_000,
maxTokens: 64_000,
cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
});
});
it("publishes Grok 4.5 with its current metadata", () => {
expectCatalogEntry("grok-4.5", {
id: "grok-4.5",
@@ -1051,6 +1063,7 @@ describe("xai provider models", () => {
});
it("marks current Grok families as modern while excluding multi-agent ids", () => {
expect(isModernXaiModel("grok-4.6")).toBe(true);
expect(isModernXaiModel("grok-4.5")).toBe(true);
expect(isModernXaiModel("grok-4.3")).toBe(true);
expect(isModernXaiModel("grok-build-0.1")).toBe(true);
+1 -1
View File
@@ -67,7 +67,7 @@ describeLive("xai x_search live", () => {
expect(details.error, details.message).toBeUndefined();
expect(details.provider).toBe("xai");
expect(details.model).toBe("grok-4.5");
expect(details.model).toBe("grok-4.6");
expect(details.content?.trim().length ?? 0).toBeGreaterThan(0);
const citationCount =
+1 -1
View File
@@ -291,7 +291,7 @@ describe("xai x_search tool", () => {
expect(mockFetch).toHaveBeenCalled();
expect(firstFetchUrl(mockFetch)).toContain("api.x.ai/v1/responses");
const body = parseFirstRequestBody(mockFetch);
expect(body.model).toBe("grok-4.5");
expect(body.model).toBe("grok-4.6");
expect(body.store).toBe(false);
expect(body.reasoning).toEqual({ effort: "low" });
expect(body.max_turns).toBe(2);
+2 -2
View File
@@ -222,7 +222,7 @@ describeLive("xai plugin live", () => {
usedCodeExecution?: boolean;
};
expect(details.model).toBe("grok-4.5");
expect(details.model).toBe("grok-4.6");
expect(details.usedCodeExecution).toBe(true);
expect(details.content).toContain("42");
});
@@ -257,7 +257,7 @@ describeLive("xai plugin live", () => {
usedCodeExecution?: boolean;
};
expect(details.model).toBe("grok-4.5");
expect(details.model).toBe("grok-4.6");
expect(details.usedCodeExecution).toBe(true);
expect(details.content).toContain("5050");
});
+1 -1
View File
@@ -34,7 +34,7 @@ const HIGH_SIGNAL_LIVE_MODEL_PRIORITY = [
"openrouter/minimax/minimax-m2.7",
"opencode-go/glm-5",
"openrouter/ai21/jamba-large-1.7",
"xai/grok-4.5",
"xai/grok-4.6",
"xai/grok-4.20-0309-reasoning",
"zai/glm-5.1",
"fireworks/accounts/fireworks/models/glm-5p1",
+3 -2
View File
@@ -646,7 +646,8 @@ describe("isHighSignalLiveModelRef", () => {
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.20-0309-reasoning" })).toBe(
true,
);
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.5" })).toBe(true);
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.6" })).toBe(true);
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.5" })).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.3" })).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-3" })).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4-1-fast-non-reasoning" })).toBe(
@@ -707,7 +708,7 @@ describe("isPrioritizedHighSignalLiveModelRef", () => {
{ provider: "openrouter", id: "minimax/minimax-m2.7" },
{ provider: "opencode-go", id: "glm-5" },
{ provider: "openrouter", id: "ai21/jamba-large-1.7" },
{ provider: "xai", id: "grok-4.5" },
{ provider: "xai", id: "grok-4.6" },
{ provider: "xai", id: "grok-4.20-0309-reasoning" },
{ provider: "zai", id: "glm-5.1" },
{ provider: "fireworks", id: "accounts/fireworks/models/glm-5p1" },
+8 -8
View File
@@ -43,23 +43,23 @@ function getToolFunction(tool: Record<string, unknown>): Record<string, unknown>
return undefined;
}
function resolveLiveXaiModel(modelId: "grok-4.3" | "grok-4.5") {
const isGrok45 = modelId === "grok-4.5";
function resolveLiveXaiModel(modelId: "grok-4.3" | "grok-4.5" | "grok-4.6") {
const isFrontier = modelId === "grok-4.5" || modelId === "grok-4.6";
return {
id: modelId,
name: isGrok45 ? "Grok 4.5" : "Grok 4.3",
name: isFrontier ? `Grok ${modelId.slice("grok-".length)}` : "Grok 4.3",
api: "openai-responses",
provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: true,
input: ["text", "image"],
cost: isGrok45
cost: isFrontier
? { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 }
: { input: 1.25, output: 2.5, cacheRead: 0.2, cacheWrite: 0 },
contextWindow: isGrok45 ? 500_000 : 1_000_000,
contextWindow: isFrontier ? 500_000 : 1_000_000,
maxTokens: 64_000,
thinkingLevelMap: {
off: isGrok45 ? null : "none",
off: isFrontier ? null : "none",
minimal: "low",
low: "low",
medium: "medium",
@@ -123,7 +123,7 @@ async function collectDoneMessage(
}
describeLive("xai live", () => {
for (const modelId of ["grok-4.3", "grok-4.5"] as const) {
for (const modelId of ["grok-4.3", "grok-4.5", "grok-4.6"] as const) {
it(
`returns assistant text for ${modelId}`,
async () => {
@@ -150,7 +150,7 @@ describeLive("xai live", () => {
);
}
for (const modelId of ["grok-4.3", "grok-4.5"] as const) {
for (const modelId of ["grok-4.3", "grok-4.5", "grok-4.6"] as const) {
it(`sends wrapped ${modelId} tool payloads live`, async () => {
await runXaiLiveCase("tool-call", async () => {
const model = requireLiveValue(resolveLiveXaiModel(modelId), "xAI model");