diff --git a/docs/providers/xai.md b/docs/providers/xai.md index cafbfd9df729..836285a2e132 100644 --- a/docs/providers/xai.md +++ b/docs/providers/xai.md @@ -504,9 +504,10 @@ Legacy aliases still normalize to the canonical bundled ids: sign-in URL. xAI decides which accounts can receive OAuth API tokens, and the consent page may show Grok Build even though OpenClaw does not require the Grok Build app. - - `grok-4.20-multi-agent-experimental-beta-0304` is not supported on the - normal xAI provider path because it requires a different upstream API - surface than the standard OpenClaw xAI transport. + - OpenClaw does not currently expose the xAI multi-agent model family. xAI + serves these models through the Responses API, but they do not accept the + client-side or custom tools used by OpenClaw's shared agent loop. See the + [xAI multi-agent limitations](https://docs.x.ai/developers/model-capabilities/text/multi-agent#limitations). - xAI Realtime voice is not registered as an OpenClaw provider yet. It needs a different bidirectional voice session contract than batch STT or streaming transcription. diff --git a/extensions/xai/index.ts b/extensions/xai/index.ts index 0a13fb30d4d4..a8ea0d68bc91 100644 --- a/extensions/xai/index.ts +++ b/extensions/xai/index.ts @@ -262,12 +262,6 @@ export default defineSingleProviderPluginEntry({ resolveThinkingProfile, isModernModelRef: ({ modelId }) => isModernXaiModel(modelId), classifyFailoverReason: ({ errorMessage }) => classifyXaiFailoverReason(errorMessage), - buildUnknownModelHint: ({ modelId }) => { - if (modelId.toLowerCase().includes("multi-agent")) { - return "xAI multi-agent models are not supported on the standard xAI API-key provider path. See https://docs.openclaw.ai/providers/xai for supported models."; - } - return undefined; - }, }, register(api) { api.registerWebSearchProvider(createXaiWebSearchProvider()); diff --git a/extensions/xai/openclaw.plugin.json b/extensions/xai/openclaw.plugin.json index bd4141880618..9f28d1f10109 100644 --- a/extensions/xai/openclaw.plugin.json +++ b/extensions/xai/openclaw.plugin.json @@ -31,6 +31,45 @@ } } }, + "modelCatalog": { + "suppressions": [ + { + "provider": "xai", + "model": "grok-4.20-multi-agent-0309", + "reason": "OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai." + }, + { + "provider": "xai", + "model": "grok-4.20-multi-agent", + "reason": "OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai." + }, + { + "provider": "xai", + "model": "grok-4.20-multi-agent-latest", + "reason": "OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai." + }, + { + "provider": "xai", + "model": "grok-4.20-multi-agent-beta-latest", + "reason": "OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai." + }, + { + "provider": "xai", + "model": "grok-4.20-multi-agent-experimental-beta-0304", + "reason": "OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai." + }, + { + "provider": "xai", + "model": "grok-4.20-multi-agent-experimental-beta-latest", + "reason": "OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai." + }, + { + "provider": "xai", + "model": "grok-4.20-multi-agent-beta-0309", + "reason": "OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai." + } + ] + }, "syntheticAuthRefs": ["xai"], "setup": { "providers": [ diff --git a/extensions/xai/openclaw.plugin.test.ts b/extensions/xai/openclaw.plugin.test.ts new file mode 100644 index 000000000000..05aa914bd398 --- /dev/null +++ b/extensions/xai/openclaw.plugin.test.ts @@ -0,0 +1,34 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const manifest = JSON.parse( + readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"), +) as { + modelCatalog?: { + suppressions?: Array<{ provider?: string; model?: string }>; + }; +}; + +const XAI_MULTI_AGENT_MODELS = [ + "grok-4.20-multi-agent-0309", + "grok-4.20-multi-agent", + "grok-4.20-multi-agent-latest", + "grok-4.20-multi-agent-beta-latest", + "grok-4.20-multi-agent-experimental-beta-0304", + "grok-4.20-multi-agent-experimental-beta-latest", + "grok-4.20-multi-agent-beta-0309", +] as const; + +describe("xAI plugin manifest", () => { + it("suppresses the unsupported multi-agent model aliases", () => { + const suppressionRefs = new Set( + (manifest.modelCatalog?.suppressions ?? []).map( + (suppression) => `${suppression.provider}/${suppression.model}`, + ), + ); + + for (const model of XAI_MULTI_AGENT_MODELS) { + expect(suppressionRefs).toContain(`xai/${model}`); + } + }); +}); diff --git a/src/agents/embedded-agent-runner/model.test.ts b/src/agents/embedded-agent-runner/model.test.ts index 319c4ebe81fd..9ddbb21e3f01 100644 --- a/src/agents/embedded-agent-runner/model.test.ts +++ b/src/agents/embedded-agent-runner/model.test.ts @@ -61,6 +61,10 @@ vi.mock("../model-suppression.js", () => { return undefined; } + function isUnsupportedXaiMultiAgentModel(provider?: string, id?: string): boolean { + return provider === "xai" && id?.trim().toLowerCase() === "grok-4.20-multi-agent-0309"; + } + return { shouldSuppressBuiltInModel: ({ provider, @@ -79,6 +83,9 @@ vi.mock("../model-suppression.js", () => { ) { return true; } + if (isUnsupportedXaiMultiAgentModel(provider, id)) { + return true; + } return ( (provider === "qwen" || provider === "modelstudio") && id?.trim().toLowerCase() === "qwen3.6-plus" && @@ -92,7 +99,7 @@ vi.mock("../model-suppression.js", () => { ) { return true; } - return false; + return isUnsupportedXaiMultiAgentModel(provider, id); }, buildSuppressedBuiltInModelError: ({ provider, @@ -116,6 +123,9 @@ vi.mock("../model-suppression.js", () => { ) { return `Unknown model: ${provider}/gpt-5.3-codex-spark. gpt-5.3-codex-spark is available only through ChatGPT/Codex OAuth. Run \`openclaw models auth login --provider openai\` and use openai/gpt-5.3-codex-spark with that OAuth profile; OpenAI API-key auth cannot use this model.`; } + if (isUnsupportedXaiMultiAgentModel(provider, id)) { + return "Unknown model: xai/grok-4.20-multi-agent-0309. OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai."; + } return undefined; }, }; @@ -3451,6 +3461,27 @@ describe("resolveModel", () => { ); }); + it("does not build a configured fallback for unsupported xAI multi-agent models", () => { + const cfg = { + models: { + providers: { + xai: { + baseUrl: "https://api.x.ai/v1", + api: "openai-completions", + models: [], + }, + }, + }, + } as unknown as OpenClawConfig; + + const result = resolveModelForTest("xai", "grok-4.20-multi-agent-0309", "/tmp/agent", cfg); + + expect(result.model).toBeUndefined(); + expect(result.error).toBe( + "Unknown model: xai/grok-4.20-multi-agent-0309. OpenClaw does not currently support xAI multi-agent models; choose another xAI model. See https://docs.openclaw.ai/providers/xai.", + ); + }); + it("rejects stale openai gpt-5.3-codex-spark discovery rows", () => { mockDiscoveredModel(discoverModels, { provider: "openai", diff --git a/src/agents/embedded-agent-runner/model.ts b/src/agents/embedded-agent-runner/model.ts index 5e6ec9b23e2f..9af7a6d61b77 100644 --- a/src/agents/embedded-agent-runner/model.ts +++ b/src/agents/embedded-agent-runner/model.ts @@ -522,7 +522,6 @@ function mergeStaticCatalogInlineModel( } function hasConfiguredFallbackSurface(params: { - provider?: string; providerConfig: InlineProviderConfig | undefined; configuredModel: ReturnType; modelId: string; @@ -533,18 +532,6 @@ function hasConfiguredFallbackSurface(params: { if (params.configuredModel) { return true; } - // xAI's catalog explicitly excludes multi-agent model ids (they require a - // different upstream API surface than the standard API-key provider path). - // If the provider rejected this model via its dynamic resolution, do not - // synthesize a configured-provider fallback that will silently 400. - // See: https://docs.openclaw.ai/providers/xai - if ( - params.provider && - normalizeProviderId(params.provider) === "xai" && - params.modelId.toLowerCase().includes("multi-agent") - ) { - return false; - } const baseUrl = params.providerConfig?.baseUrl?.trim(); return Boolean(baseUrl); } @@ -1258,7 +1245,7 @@ function resolveConfiguredFallbackModel(params: { const providerConfig = resolveConfiguredProviderConfig(cfg, provider); const requestTimeoutMs = resolveProviderRequestTimeoutMs(providerConfig?.timeoutSeconds); const configuredModel = findConfiguredProviderModel(providerConfig, provider, modelId); - if (!hasConfiguredFallbackSurface({ provider, providerConfig, configuredModel, modelId })) { + if (!hasConfiguredFallbackSurface({ providerConfig, configuredModel, modelId })) { return undefined; } const staticCatalogModel = resolveBundledStaticCatalogModel({