feat(xai): add Grok 4.5 support (#102316)

* feat(xai): add Grok 4.5 support

* chore: leave release notes to release automation
This commit is contained in:
Peter Steinberger 2026-07-09 01:39:03 +01:00 committed by GitHub
parent aad99747cd
commit eae3fc1a79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 229 additions and 39 deletions

View file

@ -326,7 +326,7 @@ messages and normalizes `stats.cached` into `cacheRead`; legacy
Model ids use a `nvidia/<vendor>/<model>` namespace (for example `nvidia/nvidia/nemotron-...` alongside `nvidia/moonshotai/kimi-k2.5`); 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; API keys still work via `XAI_API_KEY` or plugin config, and Grok `web_search` reuses the same auth profile before API-key fallback. `grok-4.3` is the bundled default chat model, and `grok-build-0.1` is selectable for build/coding-focused work. `/fast` or `params.fastMode: true` rewrites `grok-3`, `grok-3-mini`, `grok-4`, and `grok-4-0709` to their `*-fast` variants. `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; API keys still work via `XAI_API_KEY` or plugin config, and Grok `web_search` reuses the same auth profile before API-key fallback. Grok 4.5 is selectable for chat, coding, and agentic work where available; `grok-4.3` remains the regional-safe bundled default. `/fast` or `params.fastMode: true` rewrites `grok-3`, `grok-3-mini`, `grok-4`, and `grok-4-0709` to their `*-fast` variants. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
</Accordion>
</AccordionGroup>

View file

@ -403,7 +403,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.3` | |
| `xai/grok-4.5` | |
| `zai/glm-5.1` | |
| `fireworks/accounts/fireworks/models/glm-5p1` | |
| `minimax-portal/minimax-m3` | |

View file

@ -110,13 +110,15 @@ see [legacy compatibility aliases](#legacy-compatibility-aliases).
| Family | Model ids |
| -------------- | ------------------------------------------------------------------------ |
| 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` |
| Grok 4.20 Beta | `grok-4.20-beta-latest-reasoning`, `grok-4.20-beta-latest-non-reasoning` |
<Tip>
Use `grok-4.3` for general chat and `grok-build-0.1` for build/coding-focused
workloads unless you need a Grok 4.20 beta alias.
Use `grok-4.5` for general chat, coding, and agentic work where it is available.
Grok 4.3 remains the regional-safe setup default; `grok-build-0.1` and the Grok
4.20 beta aliases remain selectable.
</Tip>
## Feature coverage
@ -493,9 +495,9 @@ Legacy aliases normalize to the canonical bundled ids:
`agents.defaults.models["xai/<model>"].params.tool_stream` to `false`
to disable it.
- The bundled xAI wrapper strips unsupported strict tool-schema flags and
reasoning *effort* payload keys before sending native xAI requests. Only
`grok-4.3` / `grok-4.3-*` advertise configurable reasoning effort; all
other reasoning-capable xAI models still request
reasoning *effort* payload keys before sending native xAI requests. Grok
4.5 supports low, medium, and high effort (default high); Grok 4.3 also
supports disabling reasoning. All other reasoning-capable xAI models request
`include: ["reasoning.encrypted_content"]` so prior encrypted reasoning
can be replayed on follow-up turns.
- `web_search`, `x_search`, and `code_execution` are exposed as OpenClaw

View file

@ -108,7 +108,7 @@ describe("xai provider plugin", () => {
const fetchGuard: LiveModelCatalogFetchGuard = vi.fn(async () => ({
response: Response.json({
data: [
{ id: "grok-4.3", object: "model" },
{ id: "grok-4.5", object: "model" },
{ id: "not-in-manifest", object: "model" },
],
}),
@ -122,7 +122,7 @@ describe("xai provider plugin", () => {
});
expect(provider.apiKey).toBe("xai-key");
expect(provider.models.map((model) => model.id)).toContain("grok-4.3");
expect(provider.models.map((model) => model.id)).toContain("grok-4.5");
expect(provider.models.map((model) => model.id)).not.toContain("not-in-manifest");
const fetchParams = vi.mocked(fetchGuard).mock.calls[0]?.[0];
expect(fetchParams?.url).toBe("https://api.x.ai/v1/models");
@ -550,6 +550,19 @@ describe("xai provider plugin", () => {
expect(resolved?.reasoning).toBe(true);
expect(resolved?.input).toEqual(["text", "image"]);
expect(resolved?.contextWindow).toBe(1_000_000);
const buildAlias = provider.resolveDynamicModel?.({
provider: "xai",
modelId: "grok-build-latest",
modelRegistry: { find: () => null } as never,
providerConfig: {
api: "openai-responses",
baseUrl: "https://api.x.ai/v1",
},
} as never);
expect(buildAlias?.id).toBe("grok-4.5");
expect(buildAlias?.reasoning).toBe(true);
expect(buildAlias?.contextWindow).toBe(500_000);
});
it("marks modern Grok refs without accepting multi-agent ids", async () => {

View file

@ -7,6 +7,7 @@ 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_LARGE_CONTEXT_WINDOW = 2_000_000;
const XAI_GROK_45_CONTEXT_WINDOW = 500_000;
const XAI_GROK_4_CONTEXT_WINDOW = 256_000;
const XAI_CODE_CONTEXT_WINDOW = 256_000;
export const XAI_DEFAULT_MAX_TOKENS = 64_000;
@ -54,6 +55,13 @@ const XAI_GROK_43_COST = {
cacheWrite: 0,
} satisfies XaiCost;
const XAI_GROK_45_COST = {
input: 2,
output: 6,
cacheRead: 0.5,
cacheWrite: 0,
} satisfies XaiCost;
const XAI_GROK_BUILD_COST = {
input: 1,
output: 2,
@ -69,6 +77,15 @@ const XAI_CODE_FAST_COST = {
} satisfies XaiCost;
const XAI_MODEL_CATALOG = [
{
id: "grok-4.5",
name: "Grok 4.5",
reasoning: true,
input: ["text", "image"],
contextWindow: XAI_GROK_45_CONTEXT_WINDOW,
maxTokens: XAI_DEFAULT_MAX_TOKENS,
cost: XAI_GROK_45_COST,
},
{
id: "grok-build-0.1",
name: "Grok Build 0.1",
@ -197,6 +214,7 @@ const XAI_MODEL_CATALOG = [
] as const satisfies readonly XaiCatalogEntry[];
const XAI_SELECTABLE_MODEL_IDS = new Set<string>([
"grok-4.5",
"grok-build-0.1",
"grok-4.3",
"grok-4.20-beta-latest-reasoning",
@ -216,6 +234,9 @@ const XAI_RETIRED_BUILTIN_MODEL_IDS = new Set<string>(
function normalizeXaiCatalogModelId(modelId: string): string {
const lower = normalizeOptionalLowercaseString(modelId) ?? "";
const unprefixed = lower.startsWith("xai/") ? lower.slice("xai/".length) : lower;
if (unprefixed === "grok-build-latest") {
return "grok-4.5";
}
if (XAI_GROK_BUILD_ALIASES.has(unprefixed)) {
return "grok-build-0.1";
}
@ -310,6 +331,7 @@ export function resolveXaiCatalogEntry(modelId: string) {
});
}
if (
lower.startsWith("grok-4.5") ||
lower.startsWith("grok-4.3") ||
lower.startsWith("grok-4.20") ||
lower.startsWith("grok-4-1") ||
@ -320,15 +342,22 @@ export function resolveXaiCatalogEntry(modelId: string) {
name: trimmed,
reasoning: !lower.includes("non-reasoning"),
input: ["text", "image"],
contextWindow: lower.startsWith("grok-4.3")
? XAI_DEFAULT_CONTEXT_WINDOW
: XAI_LARGE_CONTEXT_WINDOW,
maxTokens: lower.startsWith("grok-4.3") ? XAI_DEFAULT_MAX_TOKENS : 30_000,
cost: lower.startsWith("grok-4.3")
? XAI_GROK_43_COST
: lower.startsWith("grok-4.20")
? XAI_GROK_420_COST
: XAI_FAST_COST,
contextWindow: lower.startsWith("grok-4.5")
? XAI_GROK_45_CONTEXT_WINDOW
: lower.startsWith("grok-4.3")
? XAI_DEFAULT_CONTEXT_WINDOW
: XAI_LARGE_CONTEXT_WINDOW,
maxTokens:
lower.startsWith("grok-4.5") || lower.startsWith("grok-4.3")
? XAI_DEFAULT_MAX_TOKENS
: 30_000,
cost: lower.startsWith("grok-4.5")
? XAI_GROK_45_COST
: lower.startsWith("grok-4.3")
? XAI_GROK_43_COST
: lower.startsWith("grok-4.20")
? XAI_GROK_420_COST
: XAI_FAST_COST,
});
}
if (lower.startsWith("grok-4")) {

View file

@ -3,6 +3,10 @@ import { describe, expect, it } from "vitest";
import { normalizeXaiModelId } from "./api.js";
describe("normalizeXaiModelId", () => {
it("normalizes the current Grok Build alias", () => {
expect(normalizeXaiModelId("grok-build-latest")).toBe("grok-4.5");
});
it("maps deprecated grok 4.20 beta ids to GA ids", () => {
expect(normalizeXaiModelId("grok-4.20-experimental-beta-0304-reasoning")).toBe(
"grok-4.20-beta-latest-reasoning",

View file

@ -1,5 +1,8 @@
// Xai plugin module implements model id behavior.
export function normalizeXaiModelId(id: string): string {
if (id === "grok-build-latest") {
return "grok-4.5";
}
if (id === "grok-code-fast-1" || id === "grok-code-fast" || id === "grok-code-fast-1-0825") {
return "grok-build-0.1";
}

View file

@ -58,6 +58,7 @@ describe("xai onboard", () => {
expect(cfg.models?.providers?.xai?.apiKey).toBe("old-key");
expect(cfg.models?.providers?.xai?.models.map((m) => m.id)).toEqual([
"custom-model",
"grok-4.5",
"grok-build-0.1",
"grok-4.3",
"grok-4.20-beta-latest-reasoning",
@ -71,6 +72,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.5",
"grok-build-0.1",
"grok-4.3",
"grok-4.20-beta-latest-reasoning",

View file

@ -10,6 +10,7 @@
"providers": {
"xai": {
"aliases": {
"grok-build-latest": "grok-4.5",
"grok-4.20-experimental-beta-0304-reasoning": "grok-4.20-beta-latest-reasoning",
"grok-4.20-experimental-beta-0304-non-reasoning": "grok-4.20-beta-latest-non-reasoning",
"grok-4.20-reasoning": "grok-4.20-beta-latest-reasoning",

View file

@ -4,6 +4,9 @@ import { describe, expect, it } from "vitest";
const manifest = JSON.parse(
readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"),
) as {
modelIdNormalization?: {
providers?: Record<string, { aliases?: Record<string, string> }>;
};
modelCatalog?: {
suppressions?: Array<{ provider?: string; model?: string }>;
};
@ -20,6 +23,12 @@ const XAI_MULTI_AGENT_MODELS = [
] as const;
describe("xAI plugin manifest", () => {
it("normalizes the Grok Build latest alias to Grok 4.5", () => {
expect(manifest.modelIdNormalization?.providers?.xai?.aliases?.["grok-build-latest"]).toBe(
"grok-4.5",
);
});
it("suppresses the unsupported multi-agent model aliases", () => {
const suppressionRefs = new Set(
(manifest.modelCatalog?.suppressions ?? []).map(

View file

@ -9,7 +9,7 @@ import { resolveXaiCatalogEntry, XAI_BASE_URL } from "./model-definitions.js";
import { normalizeXaiModelId } from "./model-id.js";
import { applyXaiRuntimeModelCompat } from "./runtime-model-compat.js";
const XAI_MODERN_MODEL_PREFIXES = ["grok-build-0.1", "grok-4.3", "grok-4.20"] as const;
const XAI_MODERN_MODEL_PREFIXES = ["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());

View file

@ -19,6 +19,21 @@ describe("xai provider thinking policy", () => {
]);
});
it.each(["grok-4.5", "grok-4.5-latest", "grok-build-latest"])(
"uses xAI's high reasoning default for %s",
(modelId) => {
const profile = resolveThinkingProfile({
provider: "xai",
modelId,
});
expect(profile).toEqual({
levels: [{ id: "low" }, { id: "medium" }, { id: "high" }],
defaultLevel: "high",
});
},
);
it("keeps non-reasoning and non-xai routes off-only", () => {
expect(
resolveThinkingProfile({

View file

@ -12,6 +12,15 @@ export function resolveThinkingProfile(
if (ctx.provider !== "xai" || !reasoning) {
return { levels: [{ id: "off" }], defaultLevel: "off" };
}
const modelId = ctx.modelId.trim().toLowerCase();
const isGrok45 =
modelId === "grok-4.5" || modelId.startsWith("grok-4.5-") || modelId === "grok-build-latest";
if (isGrok45) {
return {
levels: [{ id: "low" }, { id: "medium" }, { id: "high" }],
defaultLevel: "high",
};
}
return {
levels: [{ id: "off" }, { id: "minimal" }, { id: "low" }, { id: "medium" }, { id: "high" }],
defaultLevel: "low",

View file

@ -24,6 +24,27 @@ 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,
});
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({
id: "grok-4-fast-non-reasoning",

View file

@ -1,6 +1,6 @@
// Xai plugin module implements runtime model compat behavior.
// Reasoning effort is configurable only for grok-4.3*; encrypted reasoning include/replay is
// handled separately in stream.ts for all reasoning-capable xAI models.
// 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";
type XaiRuntimeModelCompat = {
@ -39,7 +39,10 @@ function normalizeXaiCompatModelId(id: unknown): string {
function supportsConfigurableXaiReasoningEffort(model: XaiRuntimeModelCompat): boolean {
const id = normalizeXaiCompatModelId(model.id);
return model.reasoning === true && (id === "grok-4.3" || id.startsWith("grok-4.3-"));
const isConfigurableModel = ["grok-4.3", "grok-4.5"].some(
(prefix) => id === prefix || id.startsWith(`${prefix}-`),
);
return model.reasoning === true && isConfigurableModel;
}
function resolveXaiReasoningEffortCompat(model: XaiRuntimeModelCompat): Record<string, unknown> {

View file

@ -1,6 +1,12 @@
// Xai tests cover stream plugin behavior.
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
import { streamSimple, type Api, type Context, type Model } from "openclaw/plugin-sdk/llm";
import {
streamSimple,
type Api,
type Context,
type Model,
type ModelThinkingLevel,
} from "openclaw/plugin-sdk/llm";
import { describe, expect, it } from "vitest";
import { applyXaiRuntimeModelCompat } from "./runtime-model-compat.js";
import {
@ -95,16 +101,18 @@ function runXaiToolPayloadWrapper(params: {
);
}
async function captureXaiResponsesPayloadWithThinking(): Promise<Record<string, unknown>> {
async function captureXaiResponsesPayloadWithThinking(
reasoning: ModelThinkingLevel = "low",
): Promise<Record<string, unknown>> {
const model = applyXaiRuntimeModelCompat({
api: "openai-responses",
provider: "xai",
id: "grok-4.3",
id: "grok-4.5",
baseUrl: "https://api.x.ai/v1",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1_000_000,
cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
contextWindow: 500_000,
maxTokens: 64_000,
} as Model<"openai-responses">);
@ -119,7 +127,7 @@ async function captureXaiResponsesPayloadWithThinking(): Promise<Record<string,
{
apiKey: "test-api-key",
cacheRetention: "none",
reasoning: "low",
reasoning,
onPayload: (payload) => {
clearTimeout(timeout);
resolve(structuredClone(payload as Record<string, unknown>));
@ -359,7 +367,7 @@ describe("xai stream wrappers", () => {
reasoningEffort: "high",
reasoning_effort: "high",
};
runXaiToolPayloadWrapper({ payload, modelId: "grok-4.3" });
runXaiToolPayloadWrapper({ payload, modelId: "grok-4.5" });
expect(payload.reasoning).toEqual({ effort: "high" });
expect(payload.reasoningEffort).toBe("high");
@ -451,6 +459,12 @@ describe("xai stream wrappers", () => {
expect(payload.include).toEqual(["reasoning.encrypted_content"]);
});
it("clamps unsupported Grok 4.5 off reasoning to low", async () => {
const payload = await captureXaiResponsesPayloadWithThinking("off");
expect(payload.reasoning).toEqual({ effort: "low", summary: "auto" });
});
it("moves image-bearing tool results out of function_call_output payloads", () => {
const payload: Record<string, unknown> = {
input: [

View file

@ -243,7 +243,7 @@ export function createXaiToolPayloadCompatibilityWrapper(
}
normalizeXaiResponsesToolResultPayload(payloadObj, model);
if (!supportsReasoningControls(model)) {
// Only grok-4.3* advertises configurable effort; drop effort fields elsewhere.
// Only current flagship Grok models advertise configurable effort.
delete payloadObj.reasoning;
delete payloadObj.reasoningEffort;
delete payloadObj.reasoning_effort;

View file

@ -1019,6 +1019,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.5",
"grok-build-0.1",
"grok-4.3",
"grok-4.20-beta-latest-reasoning",
@ -1026,7 +1027,29 @@ describe("xai provider models", () => {
]);
});
it("publishes Grok 4.3 as the default chat model", () => {
it("publishes Grok 4.5 with its current metadata", () => {
expectCatalogEntry("grok-4.5", {
id: "grok-4.5",
reasoning: true,
input: ["text", "image"],
contextWindow: 500_000,
maxTokens: 64_000,
cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
});
});
it("resolves the Grok Build latest alias to Grok 4.5", () => {
expectCatalogEntry("grok-build-latest", {
id: "grok-4.5",
reasoning: true,
input: ["text", "image"],
contextWindow: 500_000,
maxTokens: 64_000,
cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
});
});
it("keeps Grok 4.3 selectable with its published metadata", () => {
expectCatalogEntry("grok-4.3", {
id: "grok-4.3",
reasoning: true,
@ -1106,9 +1129,11 @@ describe("xai provider models", () => {
});
it("marks current Grok families as modern while excluding multi-agent ids", () => {
expect(isModernXaiModel("grok-4.5")).toBe(true);
expect(isModernXaiModel("grok-4.3")).toBe(true);
expect(isModernXaiModel("grok-build-0.1")).toBe(true);
expect(isModernXaiModel("grok-4.20-beta-latest-reasoning")).toBe(true);
expect(isModernXaiModel("grok-build-latest")).toBe(true);
expect(isModernXaiModel("grok-code-fast-1")).toBe(true);
expect(isModernXaiModel("grok-3-mini-fast")).toBe(false);
expect(isModernXaiModel("grok-4.20-multi-agent-experimental-beta-0304")).toBe(false);
@ -1151,6 +1176,18 @@ describe("xai provider models", () => {
},
},
});
const grok45Alias = resolveXaiForwardCompatModel({
providerId: "xai",
ctx: {
provider: "xai",
modelId: "grok-4.5-latest",
modelRegistry: { find: () => null } as never,
providerConfig: {
api: "openai-responses",
baseUrl: "https://api.x.ai/v1",
},
},
});
const grok3Mini = resolveXaiForwardCompatModel({
providerId: "xai",
ctx: {
@ -1172,6 +1209,23 @@ describe("xai provider models", () => {
expect(grok41?.contextWindow).toBe(2_000_000);
expect(grok41?.maxTokens).toBe(30_000);
expect(grok45Alias?.provider).toBe("xai");
expect(grok45Alias?.id).toBe("grok-4.5-latest");
expect(grok45Alias?.api).toBe("openai-responses");
expect(grok45Alias?.baseUrl).toBe("https://api.x.ai/v1");
expect(grok45Alias?.reasoning).toBe(true);
expect(grok45Alias?.thinkingLevelMap).toEqual({
off: null,
minimal: "low",
low: "low",
medium: "medium",
high: "high",
xhigh: "high",
});
expect(grok45Alias?.input).toEqual(["text", "image"]);
expect(grok45Alias?.contextWindow).toBe(500_000);
expect(grok45Alias?.maxTokens).toBe(64_000);
expect(grok43Alias?.provider).toBe("xai");
expect(grok43Alias?.id).toBe("grok-4.3-latest");
expect(grok43Alias?.api).toBe("openai-responses");

View file

@ -32,7 +32,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.3",
"xai/grok-4.5",
"zai/glm-5.1",
"fireworks/accounts/fireworks/models/glm-5p1",
"minimax-portal/minimax-m3",

View file

@ -619,7 +619,8 @@ describe("isHighSignalLiveModelRef", () => {
it("keeps only curated xAI routes in the default live matrix", () => {
providerRuntimeMocks.resolveProviderModernModelRef.mockReturnValue(true);
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.3" })).toBe(true);
expect(isHighSignalLiveModelRef({ provider: "xai", id: "grok-4.5" })).toBe(true);
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(
false,
@ -676,7 +677,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.3" },
{ provider: "xai", id: "grok-4.5" },
{ provider: "zai", id: "glm-5.1" },
{ provider: "fireworks", id: "accounts/fireworks/models/glm-5p1" },
{ provider: "minimax-portal", id: "minimax-m3" },

View file

@ -46,16 +46,24 @@ function getToolFunction(tool: Record<string, unknown>): Record<string, unknown>
function resolveLiveXaiModel() {
return {
id: "grok-4.3",
name: "Grok 4.3",
id: "grok-4.5",
name: "Grok 4.5",
api: "openai-responses",
provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: true,
input: ["text", "image"],
cost: { input: 1.25, output: 2.5, cacheRead: 0.2, cacheWrite: 0 },
contextWindow: 1_000_000,
cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
contextWindow: 500_000,
maxTokens: 64_000,
thinkingLevelMap: {
off: null,
minimal: "low",
low: "low",
medium: "medium",
high: "high",
xhigh: "high",
},
} satisfies Model<"openai-responses">;
}
@ -103,7 +111,7 @@ async function collectDoneMessage(
describeLive("xai live", () => {
it(
"returns assistant text for Grok 4.3",
"returns assistant text for Grok 4.5",
async () => {
await runXaiLiveCase("complete", async () => {
const model = requireLiveValue(resolveLiveXaiModel(), "xAI model");
@ -148,6 +156,7 @@ describeLive("xai live", () => {
{
apiKey: XAI_KEY,
maxTokens: 128,
reasoning: "low",
onPayload: (payload) => {
capturedPayload = payload as Record<string, unknown>;
},
@ -159,6 +168,7 @@ describeLive("xai live", () => {
);
expect(Array.isArray(doneMessage.content)).toBe(true);
const payload = requireLiveValue(capturedPayload, "captured xAI payload");
expect(payload.reasoning).toMatchObject({ effort: "low" });
if ("tool_stream" in payload) {
expect(payload.tool_stream).toBe(true);
}