diff --git a/CHANGELOG.md b/CHANGELOG.md index 07fb59bcd6c..eb6b9e62d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -102,6 +102,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Providers/xAI: stop sending OpenAI-style reasoning effort controls to native Grok Responses models, so `xai/grok-4.3` no longer fails live Docker/Gateway runs with `Invalid reasoning effort`. - Discord/gateway: measure heartbeat ACK timeouts from the actual heartbeat send, preventing late initial heartbeats from triggering false reconnect loops while the channel is still awaiting readiness. Fixes #77668. (#78087) Thanks @bryce-d-greybeard and @NikolaFC. - Discord/guilds: route plain text control commands such as `/steer` through the normal authorization and mention gate instead of silently dropping them before an agent session can see them. Fixes #78080. Thanks @ramitrkar-hash. - Control UI/Sessions: make the compaction count a compact `N Checkpoint(s)` disclosure and show expanded session-level details with modern checkpoint history cards across responsive table layouts. Thanks @BunsDev. diff --git a/extensions/xai/index.test.ts b/extensions/xai/index.test.ts index 347da642897..458e345cb53 100644 --- a/extensions/xai/index.test.ts +++ b/extensions/xai/index.test.ts @@ -221,7 +221,14 @@ describe("xai provider plugin", () => { model: createProviderModel({ id: "grok-4-1-fast" }), } as never), ).toMatchObject({ - thinkingLevelMap: { off: null }, + thinkingLevelMap: { + off: null, + minimal: null, + low: null, + medium: null, + high: null, + xhigh: null, + }, compat: { toolSchemaProfile: "xai", nativeWebSearchTool: true, diff --git a/extensions/xai/runtime-model-compat.ts b/extensions/xai/runtime-model-compat.ts index c70b2cc57bf..ba2a4b11d82 100644 --- a/extensions/xai/runtime-model-compat.ts +++ b/extensions/xai/runtime-model-compat.ts @@ -7,13 +7,22 @@ type XaiRuntimeModelCompat = { >; }; +const XAI_UNSUPPORTED_REASONING_EFFORTS = { + off: null, + minimal: null, + low: null, + medium: null, + high: null, + xhigh: null, +} satisfies NonNullable; + export function applyXaiRuntimeModelCompat(model: T): T { const withCompat = applyXaiModelCompat(model); return { ...withCompat, thinkingLevelMap: { ...withCompat.thinkingLevelMap, - off: null, + ...XAI_UNSUPPORTED_REASONING_EFFORTS, }, }; } diff --git a/extensions/xai/stream.test.ts b/extensions/xai/stream.test.ts index 520f800b229..115083a144a 100644 --- a/extensions/xai/stream.test.ts +++ b/extensions/xai/stream.test.ts @@ -1,6 +1,8 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import type { Api, Context, Model } from "@mariozechner/pi-ai"; +import { streamSimpleOpenAIResponses } from "@mariozechner/pi-ai/openai-responses"; import { describe, expect, it } from "vitest"; +import { applyXaiRuntimeModelCompat } from "./runtime-model-compat.js"; import { createXaiFastModeWrapper, createXaiToolPayloadCompatibilityWrapper, @@ -65,6 +67,44 @@ function runXaiToolPayloadWrapper(params: { ); } +async function captureXaiResponsesPayloadWithThinking(): Promise> { + const model = applyXaiRuntimeModelCompat({ + api: "openai-responses", + provider: "xai", + id: "grok-4.3", + baseUrl: "https://api.x.ai/v1", + reasoning: true, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1_000_000, + maxTokens: 64_000, + } as Model<"openai-responses">); + + const payloadPromise = new Promise>((resolve, reject) => { + const timeout = setTimeout( + () => reject(new Error("provider payload callback was not invoked")), + 1_000, + ); + const stream = streamSimpleOpenAIResponses( + model, + { messages: [{ role: "user", content: "hello", timestamp: 0 }] }, + { + apiKey: "test-api-key", + cacheRetention: "none", + reasoning: "low", + onPayload: (payload) => { + clearTimeout(timeout); + resolve(structuredClone(payload as Record)); + throw new Error("stop after payload capture"); + }, + }, + ); + void stream.result(); + }); + + return await payloadPromise; +} + describe("xai stream wrappers", () => { it("rewrites supported Grok models to fast variants when fast mode is enabled", () => { expect(captureWrappedModelId({ modelId: "grok-3", fastMode: true })).toBe("grok-3-fast"); @@ -139,6 +179,13 @@ describe("xai stream wrappers", () => { expect(payload).not.toHaveProperty("reasoning_effort"); }); + it("marks native xAI Responses thinking efforts unsupported before pi-ai builds payloads", async () => { + const payload = await captureXaiResponsesPayloadWithThinking(); + + expect(payload).not.toHaveProperty("reasoning"); + expect(payload).not.toHaveProperty("include"); + }); + it("moves image-bearing tool results out of function_call_output payloads", () => { const payload: Record = { input: [ diff --git a/extensions/xai/web-search.test.ts b/extensions/xai/web-search.test.ts index 12503d57b3b..5ffec8f3431 100644 --- a/extensions/xai/web-search.test.ts +++ b/extensions/xai/web-search.test.ts @@ -585,7 +585,14 @@ describe("xai provider models", () => { api: "openai-responses", baseUrl: "https://api.x.ai/v1", reasoning: true, - thinkingLevelMap: { off: null }, + thinkingLevelMap: { + off: null, + minimal: null, + low: null, + medium: null, + high: null, + xhigh: null, + }, input: ["text", "image"], contextWindow: 1_000_000, maxTokens: 64_000,