From 73079d1cb1fceeb7fb3b9d69fac716a8abf9b396 Mon Sep 17 00:00:00 2001 From: huntharo Date: Fri, 20 Mar 2026 00:51:01 -0400 Subject: [PATCH] xAI: normalize deprecated Grok 4.20 ids --- extensions/xai/src/web-search-shared.ts | 3 ++- extensions/xai/web-search.test.ts | 13 +++++++++++++ src/agents/model-id-normalization.test.ts | 18 ++++++++++++++++++ src/agents/model-id-normalization.ts | 10 ++++++++++ src/agents/model-selection.test.ts | 9 +++++++++ src/agents/model-selection.ts | 5 ++++- src/agents/models-config.providers.ts | 4 ++-- src/agents/tools/web-search.test.ts | 9 +++++++++ src/gateway/model-pricing-cache.test.ts | 19 ++++++++++++++++--- src/gateway/model-pricing-cache.ts | 5 ++++- src/plugin-sdk/provider-models.ts | 1 + 11 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/agents/model-id-normalization.test.ts diff --git a/extensions/xai/src/web-search-shared.ts b/extensions/xai/src/web-search-shared.ts index 47616bcf13c..85ea11aa49d 100644 --- a/extensions/xai/src/web-search-shared.ts +++ b/extensions/xai/src/web-search-shared.ts @@ -1,3 +1,4 @@ +import { normalizeXaiModelId } from "openclaw/plugin-sdk/provider-models"; import { postTrustedWebToolsJson, wrapWebContent } from "openclaw/plugin-sdk/provider-web-search"; export const XAI_WEB_SEARCH_ENDPOINT = "https://api.x.ai/v1/responses"; @@ -79,7 +80,7 @@ export function resolveXaiSearchConfig(searchConfig?: Record): export function resolveXaiWebSearchModel(searchConfig?: Record): string { const config = resolveXaiSearchConfig(searchConfig); return typeof config.model === "string" && config.model.trim() - ? config.model.trim() + ? normalizeXaiModelId(config.model.trim()) : XAI_DEFAULT_WEB_SEARCH_MODEL; } diff --git a/extensions/xai/web-search.test.ts b/extensions/xai/web-search.test.ts index 29433ec7efa..a6dfff40633 100644 --- a/extensions/xai/web-search.test.ts +++ b/extensions/xai/web-search.test.ts @@ -44,6 +44,19 @@ describe("xai web search config resolution", () => { ); }); + it("normalizes deprecated grok 4.20 beta model ids to GA ids", () => { + expect( + resolveXaiWebSearchModel({ + grok: { model: "grok-4.20-experimental-beta-0304-reasoning" }, + }), + ).toBe("grok-4.20-reasoning"); + expect( + resolveXaiWebSearchModel({ + grok: { model: "grok-4.20-experimental-beta-0304-non-reasoning" }, + }), + ).toBe("grok-4.20-non-reasoning"); + }); + it("defaults inlineCitations to false", () => { expect(resolveXaiInlineCitations({})).toBe(false); expect(resolveXaiInlineCitations(undefined)).toBe(false); diff --git a/src/agents/model-id-normalization.test.ts b/src/agents/model-id-normalization.test.ts new file mode 100644 index 00000000000..7ae0d1b736b --- /dev/null +++ b/src/agents/model-id-normalization.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { normalizeXaiModelId } from "./model-id-normalization.js"; + +describe("normalizeXaiModelId", () => { + it("maps deprecated grok 4.20 beta ids to GA ids", () => { + expect(normalizeXaiModelId("grok-4.20-experimental-beta-0304-reasoning")).toBe( + "grok-4.20-reasoning", + ); + expect(normalizeXaiModelId("grok-4.20-experimental-beta-0304-non-reasoning")).toBe( + "grok-4.20-non-reasoning", + ); + }); + + it("leaves current xai model ids unchanged", () => { + expect(normalizeXaiModelId("grok-4.20-reasoning")).toBe("grok-4.20-reasoning"); + expect(normalizeXaiModelId("grok-4")).toBe("grok-4"); + }); +}); diff --git a/src/agents/model-id-normalization.ts b/src/agents/model-id-normalization.ts index 9b0b27a7f01..8131c5a1d29 100644 --- a/src/agents/model-id-normalization.ts +++ b/src/agents/model-id-normalization.ts @@ -21,3 +21,13 @@ export function normalizeGoogleModelId(id: string): string { } return id; } + +export function normalizeXaiModelId(id: string): string { + if (id === "grok-4.20-experimental-beta-0304-reasoning") { + return "grok-4.20-reasoning"; + } + if (id === "grok-4.20-experimental-beta-0304-non-reasoning") { + return "grok-4.20-non-reasoning"; + } + return id; +} diff --git a/src/agents/model-selection.test.ts b/src/agents/model-selection.test.ts index e7d583d106f..5d81afc4970 100644 --- a/src/agents/model-selection.test.ts +++ b/src/agents/model-selection.test.ts @@ -194,6 +194,15 @@ describe("model-selection", () => { defaultProvider: "google", expected: { provider: "google", model: "gemini-3.1-flash-lite-preview" }, }, + { + name: "normalizes deprecated xai grok 4.20 beta ids", + variants: [ + "xai/grok-4.20-experimental-beta-0304-reasoning", + "grok-4.20-experimental-beta-0304-reasoning", + ], + defaultProvider: "xai", + expected: { provider: "xai", model: "grok-4.20-reasoning" }, + }, { name: "keeps OpenAI codex refs on the openai provider", variants: ["openai/gpt-5.3-codex", "gpt-5.3-codex"], diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index acc29a32bf9..7e654dd24f3 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -14,7 +14,7 @@ import { } from "./agent-scope.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js"; import type { ModelCatalogEntry } from "./model-catalog.js"; -import { normalizeGoogleModelId } from "./model-id-normalization.js"; +import { normalizeGoogleModelId, normalizeXaiModelId } from "./model-id-normalization.js"; import { splitTrailingAuthProfile } from "./model-ref-profile.js"; import { findNormalizedProviderKey, @@ -121,6 +121,9 @@ function normalizeProviderModelId(provider: string, model: string): string { if (provider === "google" || provider === "google-vertex") { return normalizeGoogleModelId(model); } + if (provider === "xai") { + return normalizeXaiModelId(model); + } // OpenRouter-native models (e.g. "openrouter/aurora-alpha") need the full // "openrouter/" as the model ID sent to the API. Models from external // providers already contain a slash (e.g. "anthropic/claude-sonnet-4-5") and diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index af9c3d6e34a..57f10206984 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -9,7 +9,7 @@ import { isRecord } from "../utils.js"; import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; import { ensureAuthProfileStore, listProfilesForProvider } from "./auth-profiles.js"; import { discoverBedrockModels } from "./bedrock-discovery.js"; -import { normalizeGoogleModelId } from "./model-id-normalization.js"; +import { normalizeGoogleModelId, normalizeXaiModelId } from "./model-id-normalization.js"; import { resolveOllamaApiBase } from "./models-config.providers.discovery.js"; export { buildKimiCodingProvider } from "../../extensions/kimi-coding/provider-catalog.js"; export { buildKilocodeProvider } from "../../extensions/kilocode/provider-catalog.js"; @@ -42,7 +42,7 @@ import { } from "./model-auth-markers.js"; import { resolveAwsSdkEnvVarName, resolveEnvApiKey } from "./model-auth.js"; export { resolveOllamaApiBase } from "./models-config.providers.discovery.js"; -export { normalizeGoogleModelId }; +export { normalizeGoogleModelId, normalizeXaiModelId }; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; diff --git a/src/agents/tools/web-search.test.ts b/src/agents/tools/web-search.test.ts index 9f3a6fe017c..5bb2585f3ed 100644 --- a/src/agents/tools/web-search.test.ts +++ b/src/agents/tools/web-search.test.ts @@ -341,6 +341,15 @@ describe("web_search grok config resolution", () => { expect(resolveGrokModel({ model: "grok-4-fast" })).toBe("grok-4-fast"); }); + it("normalizes deprecated grok 4.20 beta ids to GA ids", () => { + expect(resolveGrokModel({ model: "grok-4.20-experimental-beta-0304-reasoning" })).toBe( + "grok-4.20-reasoning", + ); + expect(resolveGrokModel({ model: "grok-4.20-experimental-beta-0304-non-reasoning" })).toBe( + "grok-4.20-non-reasoning", + ); + }); + it("falls back to default model", () => { expect(resolveGrokModel({})).toBe("grok-4-1-fast"); }); diff --git a/src/gateway/model-pricing-cache.test.ts b/src/gateway/model-pricing-cache.test.ts index 8ce128d4938..159211f7e8e 100644 --- a/src/gateway/model-pricing-cache.test.ts +++ b/src/gateway/model-pricing-cache.test.ts @@ -101,7 +101,7 @@ describe("model-pricing-cache", () => { ], }, hooks: { - mappings: [{ model: "xai/grok-4" }], + mappings: [{ model: "xai/grok-4.20-experimental-beta-0304-reasoning" }], }, tools: { subagents: { model: { primary: "zai/glm-5" } }, @@ -130,7 +130,7 @@ describe("model-pricing-cache", () => { }, }, { - id: "x-ai/grok-4", + id: "x-ai/grok-4.20-experimental-beta-0304-reasoning", pricing: { prompt: "0.000002", completion: "0.00001", @@ -172,12 +172,25 @@ describe("model-pricing-cache", () => { cacheRead: 0.3, cacheWrite: 0, }); - expect(getCachedGatewayModelPricing({ provider: "xai", model: "grok-4" })).toEqual({ + expect( + getCachedGatewayModelPricing({ + provider: "xai", + model: "grok-4.20-experimental-beta-0304-reasoning", + }), + ).toEqual({ input: 2, output: 10, cacheRead: 0, cacheWrite: 0, }); + expect(getCachedGatewayModelPricing({ provider: "xai", model: "grok-4.20-reasoning" })).toEqual( + { + input: 2, + output: 10, + cacheRead: 0, + cacheWrite: 0, + }, + ); expect(getCachedGatewayModelPricing({ provider: "zai", model: "glm-5" })).toEqual({ input: 1, output: 4, diff --git a/src/gateway/model-pricing-cache.ts b/src/gateway/model-pricing-cache.ts index 8a2e250f53f..ef05628d234 100644 --- a/src/gateway/model-pricing-cache.ts +++ b/src/gateway/model-pricing-cache.ts @@ -7,7 +7,7 @@ import { resolveModelRefFromString, type ModelRef, } from "../agents/model-selection.js"; -import { normalizeGoogleModelId } from "../agents/models-config.providers.js"; +import { normalizeGoogleModelId, normalizeXaiModelId } from "../agents/models-config.providers.js"; import type { OpenClawConfig } from "../config/config.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; @@ -155,6 +155,9 @@ function canonicalizeOpenRouterLookupId(id: string): string { if (provider === "google") { model = normalizeGoogleModelId(model); } + if (provider === "x-ai") { + model = normalizeXaiModelId(model); + } return `${provider}/${model}`; } diff --git a/src/plugin-sdk/provider-models.ts b/src/plugin-sdk/provider-models.ts index 7103147e91d..da71fc796aa 100644 --- a/src/plugin-sdk/provider-models.ts +++ b/src/plugin-sdk/provider-models.ts @@ -24,6 +24,7 @@ export { XAI_TOOL_SCHEMA_PROFILE, } from "../agents/model-compat.js"; export { normalizeProviderId } from "../agents/provider-id.js"; +export { normalizeXaiModelId } from "../agents/model-id-normalization.js"; export { cloneFirstTemplateModel } from "../plugins/provider-model-helpers.js"; export {