mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
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:
parent
aad99747cd
commit
eae3fc1a79
21 changed files with 229 additions and 39 deletions
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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` | |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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")) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue