From c228dea58b55cbb47bc6fba132b4b330f7dc0168 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 20:29:22 -0700 Subject: [PATCH 01/19] feat(catalog): route paid xAI through Responses like SuperGrok Switch XAI_API_KEY models from Chat Completions to /v1/responses, default both xai and xai-oauth to grok-4.5, and include reasoning.encrypted_content. --- docs/provider-endpoint-constraints.md | 7 +- .../openai-completions-cache-affinity.test.ts | 54 +----------- .../openai-responses-cache-affinity.test.ts | 24 ++++++ packages/ai/test/stream.test.ts | 2 +- .../ai/test/xai-oauth-effort-strip.test.ts | 28 ++++++ packages/catalog/CHANGELOG.md | 7 ++ packages/catalog/src/compat/openai.ts | 28 +++--- packages/catalog/src/hosts.ts | 2 +- packages/catalog/src/models.json | 78 ++++++++--------- .../src/provider-models/descriptors.ts | 4 +- .../src/provider-models/openai-compat.ts | 19 ++-- packages/catalog/test/build.test.ts | 33 ++++++- .../test/xai-api-key-responses.test.ts | 25 ++++++ .../catalog/test/xai-oauth-bundle.test.ts | 86 +++++++++++++++++++ packages/coding-agent/CHANGELOG.md | 7 ++ 15 files changed, 288 insertions(+), 116 deletions(-) create mode 100644 packages/catalog/test/xai-api-key-responses.test.ts create mode 100644 packages/catalog/test/xai-oauth-bundle.test.ts diff --git a/docs/provider-endpoint-constraints.md b/docs/provider-endpoint-constraints.md index 0193702a2..99264c9b4 100644 --- a/docs/provider-endpoint-constraints.md +++ b/docs/provider-endpoint-constraints.md @@ -235,12 +235,13 @@ Reasoning fields are not interchangeable. - Compat needs both policies: disable reasoning for any tool choice, and disable reasoning only for forced tool choice. -### xAI Grok through Responses/SuperGrok +### xAI Grok through Responses (`xai` and `xai-oauth`) -Keep these independent: +Both the paid API-key provider (`xai` / `XAI_API_KEY`) and SuperGrok OAuth +(`xai-oauth`) chat over `https://api.x.ai/v1/responses`. Keep these independent: - omit `reasoning.effort` -- include or drop encrypted reasoning replay +- include `reasoning.encrypted_content` (request `include`) vs replay history - filter reasoning-history wrappers Some models reject only one of those fields; do not collapse them into one diff --git a/packages/ai/test/openai-completions-cache-affinity.test.ts b/packages/ai/test/openai-completions-cache-affinity.test.ts index 2506e031c..c081b4380 100644 --- a/packages/ai/test/openai-completions-cache-affinity.test.ts +++ b/packages/ai/test/openai-completions-cache-affinity.test.ts @@ -5,9 +5,6 @@ import type { AssistantMessage, Context, FetchImpl, Model, SimpleStreamOptions, import { buildOpenAICompat } from "@oh-my-pi/pi-catalog/compat/openai"; import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; -const model = getBundledModel<"openai-completions">("xai", "grok-code-fast-1"); -if (!model) throw new Error("Expected bundled xAI Grok model"); -if (model.api !== "openai-completions") throw new Error(`Expected Chat Completions model, received ${model.api}`); const context: Context = { messages: [{ role: "user", content: "hello", timestamp: 0 }] }; const openAI56ResponsesModel = getBundledModel<"openai-responses">("openai", "gpt-5.6"); @@ -44,7 +41,7 @@ function chatCompletionsSse(): Response { id: "chatcmpl-affinity", object: "chat.completion.chunk", created: 0, - model: model.id, + model: openAI56CompletionsModel.id, choices: [{ index: 0, delta, finish_reason: finishReason }], }); @@ -56,7 +53,7 @@ function chatCompletionsSse(): Response { async function captureRequest( options: OpenAICompletionsOptions, - requestModel: Model<"openai-completions"> = model, + requestModel: Model<"openai-completions"> = openAI56CompletionsModel, requestContext: Context = context, ): Promise<{ headers: Headers; body: Record }> { let requestHeaders: Headers | undefined; @@ -83,7 +80,7 @@ async function captureRequest( async function captureSimpleRequest( options: SimpleStreamOptions, - requestModel: Model<"openai-completions"> = model, + requestModel: Model<"openai-completions"> = openAI56CompletionsModel, requestContext: Context = context, ): Promise<{ headers: Headers; body: Record }> { let requestHeaders: Headers | undefined; @@ -104,51 +101,6 @@ async function captureSimpleRequest( return { headers: requestHeaders, body }; } -describe("openai-completions xAI cache affinity", () => { - const cases: Array<{ - name: string; - options: OpenAICompletionsOptions; - expectedHeader: string | null; - }> = [ - { - name: "uses sessionId when no prompt cache key is provided", - options: { sessionId: "session-fallback" }, - expectedHeader: "session-fallback", - }, - { - name: "keeps the prompt cache key stable across a distinct side-channel session", - options: { promptCacheKey: "stable-cache-key", sessionId: "side-channel-session" }, - expectedHeader: "stable-cache-key", - }, - { - name: "omits automatic affinity when caching is disabled", - options: { - promptCacheKey: "disabled-cache-key", - sessionId: "disabled-session", - cacheRetention: "none", - }, - expectedHeader: null, - }, - { - name: "preserves a caller-provided mixed-case affinity header", - options: { - promptCacheKey: "automatic-cache-key", - sessionId: "automatic-session", - headers: { "X-Grok-Conv-Id": "caller-affinity" }, - }, - expectedHeader: "caller-affinity", - }, - ]; - - for (const { name, options, expectedHeader } of cases) { - it(name, async () => { - const { headers } = await captureRequest(options); - - expect(headers.get("x-grok-conv-id")).toBe(expectedHeader); - }); - } -}); - describe("OpenAI Chat Completions explicit prompt cache policy", () => { const historicalContext: Context = { messages: [ diff --git a/packages/ai/test/openai-responses-cache-affinity.test.ts b/packages/ai/test/openai-responses-cache-affinity.test.ts index fd10d44cf..4eb32abca 100644 --- a/packages/ai/test/openai-responses-cache-affinity.test.ts +++ b/packages/ai/test/openai-responses-cache-affinity.test.ts @@ -57,6 +57,20 @@ const xaiOAuthResponsesModel: Model<"openai-responses"> = { reasoning: true, }), }; +const xaiApiKeyResponsesModel: Model<"openai-responses"> = { + ...model, + id: "grok-code-fast-1", + name: "Grok Code Fast 1", + provider: "xai", + baseUrl: "https://api.x.ai/v1", + compat: buildOpenAIResponsesCompat({ + id: "grok-code-fast-1", + name: "Grok Code Fast 1", + provider: "xai", + baseUrl: "https://api.x.ai/v1", + reasoning: true, + }), +}; const openAI56ResponsesModel: Model<"openai-responses"> = { ...model, @@ -675,6 +689,16 @@ describe("openai-responses cache affinity", () => { } }); + it("sets x-grok-conv-id cache affinity for paid xai Responses requests", async () => { + const captured = await captureDispatchedOpenAIResponseHeaders( + { sessionId: "session-fallback" }, + xaiApiKeyResponsesModel, + ); + + expect(getHeader(captured.headers, "x-grok-conv-id")).toBe("session-fallback"); + expect(captured.body?.prompt_cache_key).toBe("session-fallback"); + }); + it("sets OpenRouter Responses session_id from sessionId in the body", async () => { const captured = await captureOpenAIResponseHeaders( { sessionId: "workflow-123", promptCacheKey: "cache-key-123" }, diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index 0add12576..8017e13bd 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -1000,7 +1000,7 @@ describe("Generate E2E Tests", () => { ); }); - describe.skipIf(!e2eApiKey("XAI_API_KEY"))("xAI Provider (grok-code-fast-1 via OpenAI Completions)", () => { + describe.skipIf(!e2eApiKey("XAI_API_KEY"))("xAI Provider (grok-code-fast-1 via OpenAI Responses)", () => { const llm = getBundledModel("xai", "grok-code-fast-1"); it( diff --git a/packages/ai/test/xai-oauth-effort-strip.test.ts b/packages/ai/test/xai-oauth-effort-strip.test.ts index c84012057..428ac8d03 100644 --- a/packages/ai/test/xai-oauth-effort-strip.test.ts +++ b/packages/ai/test/xai-oauth-effort-strip.test.ts @@ -38,6 +38,23 @@ describe("effort-dial-less reasoner encoding (regression)", () => { expect(grokR.thinking).toBeUndefined(); }); + test("paid xai/grok-code-fast-1 reasons but carries no thinking config", () => { + const grokCodeFast = getBundledModel("xai", "grok-code-fast-1"); + if (!grokCodeFast) throw new Error("xai/grok-code-fast-1 must be in bundled models.json"); + expect(grokCodeFast.api).toBe("openai-responses"); + expect(grokCodeFast.reasoning).toBe(true); + expect(grokCodeFast.thinking).toBeUndefined(); + expect(getSupportedEfforts(grokCodeFast)).toEqual([]); + }); + + test("paid xai/grok-4.3 keeps its effort dial", () => { + const grok43 = getBundledModel("xai", "grok-4.3"); + if (!grok43) throw new Error("xai/grok-4.3 must be in bundled models.json"); + expect(grok43.api).toBe("openai-responses"); + expect(grok43.thinking).toBeDefined(); + expect(getSupportedEfforts(grok43).length).toBeGreaterThan(0); + }); + test("the no-dial encoding stays scoped to openai-responses*", () => { const claude = getBundledModel("anthropic", "claude-sonnet-4-6"); if (!claude) throw new Error("anthropic/claude-sonnet-4-6 must be in bundled models.json"); @@ -57,6 +74,7 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { const { params } = buildParams(grok45, singleUserContext, undefined, undefined); expect(params.reasoning).toBeUndefined(); + expect(params.include).toContain("reasoning.encrypted_content"); }); test("xai-oauth/grok-4.5 omits unsupported reasoning summary", () => { @@ -66,5 +84,15 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { const { params } = buildParams(grok45, singleUserContext, { reasoning: Effort.High }, undefined); expect(params.reasoning).toEqual({ effort: "high" }); + expect(params.include).toContain("reasoning.encrypted_content"); + }); + + test("paid xai/grok-4.5 requests encrypted reasoning content", () => { + const grok45 = getBundledModel<"openai-responses">("xai", "grok-4.5"); + if (!grok45) throw new Error("xai/grok-4.5 must be in bundled models.json"); + + const { params } = buildParams(grok45, singleUserContext, { reasoning: Effort.High }, undefined); + + expect(params.include).toContain("reasoning.encrypted_content"); }); }); diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 9f7534918..8c03fcdf9 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -2,6 +2,13 @@ ## [Unreleased] +### Changed + +- Switched the paid xAI provider (`xai` / `XAI_API_KEY`) from Chat Completions to the OpenAI Responses API (`POST https://api.x.ai/v1/responses`), matching SuperGrok `xai-oauth`. Prompt-cache affinity (`x-grok-conv-id`), reasoning-effort allowlisting, and encrypted-reasoning replay rules are now shared across both first-party xAI hosts. +- Changed the paid xAI (`XAI_API_KEY`) default model from `grok-4-fast-non-reasoning` to `grok-4.5`. +- Changed the SuperGrok (`xai-oauth`) default model from `grok-4.3` to `grok-4.5`. +- Requested `reasoning.encrypted_content` on first-party xAI Responses calls (`xai` and `xai-oauth`) via the `include` parameter. + ## [17.3.4] - 2026-08-14 ### Added diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index ad7ba06b0..940568814 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -684,24 +684,28 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol const isLocalServingBackend = (!PROXY_OPENAI_COMPAT_PROVIDERS.has(spec.provider) && LOCAL_OPENAI_COMPAT_PROVIDERS.has(spec.provider)) || hasLocalLoopbackBaseUrl(baseUrl); + const isXaiHost = modelMatchesHost({ provider: spec.provider, baseUrl }, "xai"); const compat: ResolvedOpenAIResponsesCompat = { supportsDeveloperRole: isAzure || isOpenAIUrl || hostMatchesUrl(baseUrl, "githubCopilot"), supportsStrictMode: isAzure || detectStrictModeSupport(spec.provider, baseUrl), - supportsReasoningEffort: spec.provider !== "xai-oauth" || isGrokReasoningEffortCapable(id), + // Paid `xai` and SuperGrok `xai-oauth` share api.x.ai `/v1/responses`. + // Only the Grok effort-capable allowlist accepts `reasoning.effort`; + // other reasoners (grok-build, grok-code-fast-1, …) 400 if it is sent. + supportsReasoningEffort: !isXaiHost || isGrokReasoningEffortCapable(id), supportsLongPromptCacheRetention: isOpenAIUrl, supportsPromptCacheBreakpoints, promptCacheBreakpointTtl: supportsPromptCacheBreakpoints ? "30m" : undefined, // Azure OpenAI and GitHub Copilot Responses paths require tool results // to strictly match prior tool calls when building Responses inputs. strictResponsesPairing: isAzure || spec.provider === "github-copilot", - // GitHub Copilot and xAI OAuth reject `detail: "original"` (400 / 422). - // Every other host preserves native-resolution frames (snapcompact relies - // on `original`). Detect Copilot by provider id or base-URL host so a - // model pointed at the Copilot host under a different provider id still - // clamps; xai-oauth is provider-id only (same host family as paid `xai`). + // GitHub Copilot and first-party xAI `/v1/responses` reject + // `detail: "original"` (400 / 422). Every other host preserves + // native-resolution frames (snapcompact relies on `original`). Detect + // Copilot by provider id or base-URL host so a model pointed at the + // Copilot host under a different provider id still clamps. supportsImageDetailOriginal: - spec.provider !== "xai-oauth" && !modelMatchesHost({ provider: spec.provider, baseUrl }, "githubCopilot"), + !isXaiHost && !modelMatchesHost({ provider: spec.provider, baseUrl }, "githubCopilot"), reasoningEffortMap: {}, supportsReasoningParams: true, // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit @@ -710,8 +714,12 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol thinkingFormat, reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat), omitReasoningEffort: false, - includeEncryptedReasoning: spec.provider !== "xai-oauth", - filterReasoningHistory: spec.provider === "xai-oauth" || (isOpenRouter && isAnthropicModel), + // Ask xAI `/v1/responses` for `reasoning.encrypted_content` the same way + // first-party OpenAI Responses does. History still drops `type: + // "reasoning"` wrappers (`filterReasoningHistory`) independently — + // those two flags must not be collapsed. + includeEncryptedReasoning: true, + filterReasoningHistory: isXaiHost || (isOpenRouter && isAnthropicModel), disableReasoningOnForcedToolChoice: isKimiModel, disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter, supportsToolChoice: true, @@ -752,7 +760,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol MINIMAX_PROVIDER_OR_ID_PATTERN.test(spec.provider) || (id ? MINIMAX_PROVIDER_OR_ID_PATTERN.test(id) : false), emptyLengthFinishIsContextError: spec.provider === "ollama", usesOpenAIToolCallIdLimit: spec.provider === "openai", - promptCacheSessionHeader: spec.provider === "xai-oauth" ? "x-grok-conv-id" : undefined, + promptCacheSessionHeader: isXaiHost ? "x-grok-conv-id" : undefined, streamFirstEventTimeoutMs: isLocalServingBackend ? 0 : spec.compat?.streamFirstEventTimeoutMs, streamIdleTimeoutMs: isLocalServingBackend ? LOCAL_OPENAI_COMPAT_STREAM_IDLE_TIMEOUT_MS diff --git a/packages/catalog/src/hosts.ts b/packages/catalog/src/hosts.ts index 176f77125..5c7f3e2fb 100644 --- a/packages/catalog/src/hosts.ts +++ b/packages/catalog/src/hosts.ts @@ -47,7 +47,7 @@ export const KNOWN_HOSTS = { }, umans: { providers: ["umans"], urlMarkers: ["api.code.umans.ai"] }, xiaomi: { providers: ["xiaomi"], providerPrefixes: ["xiaomi-token-plan-"], urlMarkers: ["xiaomimimo.com"] }, - xai: { providers: ["xai"], urlMarkers: ["api.x.ai"] }, + xai: { providers: ["xai", "xai-oauth"], urlMarkers: ["api.x.ai"] }, mistral: { providers: ["mistral"], urlMarkers: ["mistral.ai"] }, together: { providers: ["together"], urlMarkers: ["api.together.xyz"] }, baseten: { providers: ["baseten"], urlMarkers: ["baseten.co"] }, diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index 5dc7d1f9b..67e785669 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -103716,7 +103716,7 @@ "grok-2": { "id": "grok-2", "name": "Grok 2", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103735,7 +103735,7 @@ "grok-2-1212": { "id": "grok-2-1212", "name": "Grok 2 (1212)", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103754,7 +103754,7 @@ "grok-2-latest": { "id": "grok-2-latest", "name": "Grok 2 Latest", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103773,7 +103773,7 @@ "grok-2-vision": { "id": "grok-2-vision", "name": "Grok 2 Vision", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103793,7 +103793,7 @@ "grok-2-vision-1212": { "id": "grok-2-vision-1212", "name": "Grok 2 Vision (1212)", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103813,7 +103813,7 @@ "grok-2-vision-latest": { "id": "grok-2-vision-latest", "name": "Grok 2 Vision Latest", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103833,7 +103833,7 @@ "grok-3": { "id": "grok-3", "name": "Grok 3", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103852,7 +103852,7 @@ "grok-3-fast": { "id": "grok-3-fast", "name": "Grok 3 Fast", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103871,7 +103871,7 @@ "grok-3-fast-latest": { "id": "grok-3-fast-latest", "name": "Grok 3 Fast Latest", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103890,7 +103890,7 @@ "grok-3-latest": { "id": "grok-3-latest", "name": "Grok 3 Latest", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -103909,7 +103909,7 @@ "grok-3-mini": { "id": "grok-3-mini", "name": "Grok 3 Mini", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -103937,7 +103937,7 @@ "grok-3-mini-fast": { "id": "grok-3-mini-fast", "name": "Grok 3 Mini Fast", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -103965,7 +103965,7 @@ "grok-3-mini-fast-latest": { "id": "grok-3-mini-fast-latest", "name": "Grok 3 Mini Fast Latest", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -103993,7 +103993,7 @@ "grok-3-mini-latest": { "id": "grok-3-mini-latest", "name": "Grok 3 Mini Latest", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104021,7 +104021,7 @@ "grok-4": { "id": "grok-4", "name": "Grok 4", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104049,7 +104049,7 @@ "grok-4-1-fast": { "id": "grok-4-1-fast", "name": "Grok 4.1 Fast", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104078,7 +104078,7 @@ "grok-4-1-fast-non-reasoning": { "id": "grok-4-1-fast-non-reasoning", "name": "Grok 4.1 Fast (Non-Reasoning)", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -104098,7 +104098,7 @@ "grok-4-fast": { "id": "grok-4-fast", "name": "Grok 4 Fast", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104127,7 +104127,7 @@ "grok-4-fast-non-reasoning": { "id": "grok-4-fast-non-reasoning", "name": "Grok 4 Fast (Non-Reasoning)", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -104147,7 +104147,7 @@ "grok-4.20-0309-non-reasoning": { "id": "grok-4.20-0309-non-reasoning", "name": "Grok 4.20 (Non-Reasoning)", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -104167,7 +104167,7 @@ "grok-4.20-0309-reasoning": { "id": "grok-4.20-0309-reasoning", "name": "Grok 4.20 (Reasoning)", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104197,7 +104197,7 @@ "grok-4.20-beta-latest-non-reasoning": { "id": "grok-4.20-beta-latest-non-reasoning", "name": "Grok 4.20 Beta (Non-Reasoning)", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -104217,7 +104217,7 @@ "grok-4.20-beta-latest-reasoning": { "id": "grok-4.20-beta-latest-reasoning", "name": "Grok 4.20 Beta (Reasoning)", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104247,7 +104247,7 @@ "grok-4.20-multi-agent-beta-latest": { "id": "grok-4.20-multi-agent-beta-latest", "name": "Grok 4.20 Multi-Agent Beta", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104276,7 +104276,7 @@ "grok-4.3": { "id": "grok-4.3", "name": "Grok 4.3", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104305,7 +104305,7 @@ "grok-4.5": { "id": "grok-4.5", "name": "Grok 4.5", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104363,7 +104363,7 @@ "grok-beta": { "id": "grok-beta", "name": "Grok Beta", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -104382,7 +104382,7 @@ "grok-build-0.1": { "id": "grok-build-0.1", "name": "Grok Build 0.1", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104411,7 +104411,7 @@ "grok-code-fast-1": { "id": "grok-code-fast-1", "name": "Grok Code Fast 1", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104439,7 +104439,7 @@ "grok-vision-beta": { "id": "grok-vision-beta", "name": "Grok Vision Beta", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": false, @@ -104483,7 +104483,7 @@ "reasoningEffortMap": { "minimal": "low" }, - "includeEncryptedReasoning": false, + "includeEncryptedReasoning": true, "filterReasoningHistory": true, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, @@ -104515,7 +104515,7 @@ "reasoningEffortMap": { "minimal": "low" }, - "includeEncryptedReasoning": false, + "includeEncryptedReasoning": true, "filterReasoningHistory": true, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, @@ -104559,7 +104559,7 @@ "reasoningEffortMap": { "minimal": "low" }, - "includeEncryptedReasoning": false, + "includeEncryptedReasoning": true, "filterReasoningHistory": true, "supportsImageDetailOriginal": false, "omitReasoningEffort": false, @@ -104604,7 +104604,7 @@ "reasoningEffortMap": { "minimal": "low" }, - "includeEncryptedReasoning": false, + "includeEncryptedReasoning": true, "filterReasoningHistory": true, "supportsImageDetailOriginal": false, "omitReasoningEffort": false, @@ -104649,7 +104649,7 @@ "reasoningEffortMap": { "minimal": "low" }, - "includeEncryptedReasoning": false, + "includeEncryptedReasoning": true, "filterReasoningHistory": true, "supportsImageDetailOriginal": false, "omitReasoningEffort": false, @@ -104709,7 +104709,7 @@ "reasoningEffortMap": { "minimal": "low" }, - "includeEncryptedReasoning": false, + "includeEncryptedReasoning": true, "filterReasoningHistory": true, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, @@ -104741,7 +104741,7 @@ "reasoningEffortMap": { "minimal": "low" }, - "includeEncryptedReasoning": false, + "includeEncryptedReasoning": true, "filterReasoningHistory": true, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, @@ -104772,7 +104772,7 @@ "reasoningEffortMap": { "minimal": "low" }, - "includeEncryptedReasoning": false, + "includeEncryptedReasoning": true, "filterReasoningHistory": true, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, @@ -111996,4 +111996,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/catalog/src/provider-models/descriptors.ts b/packages/catalog/src/provider-models/descriptors.ts index 20af095aa..1cbb2c016 100644 --- a/packages/catalog/src/provider-models/descriptors.ts +++ b/packages/catalog/src/provider-models/descriptors.ts @@ -478,13 +478,13 @@ export const CATALOG_PROVIDERS = [ }, { id: "xai", - defaultModel: "grok-4-fast-non-reasoning", + defaultModel: "grok-4.5", envVars: ["XAI_API_KEY"], createModelManagerOptions: (config: ModelManagerConfig) => xaiModelManagerOptions(config), }, { id: "xai-oauth", - defaultModel: "grok-4.3", + defaultModel: "grok-4.5", envVars: ["XAI_OAUTH_TOKEN", "XAI_API_KEY"], createModelManagerOptions: (config: ModelManagerConfig) => xaiOAuthModelManagerOptions(config), catalogDiscovery: { diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index bf985c9a5..a5f90aeb8 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1257,8 +1257,8 @@ export interface XaiModelManagerConfig { fetch?: FetchImpl; } -export function xaiModelManagerOptions(config?: XaiModelManagerConfig): ModelManagerOptions<"openai-completions"> { - return createSimpleOpenAICompletionsOptions("xai", "https://api.x.ai/v1", config); +export function xaiModelManagerOptions(config?: XaiModelManagerConfig): ModelManagerOptions<"openai-responses"> { + return createSimpleOpenAIResponsesOptions("xai", "https://api.x.ai/v1", config); } export interface XaiOAuthModelManagerConfig { @@ -1352,7 +1352,7 @@ const XAI_NON_CHAT_PREFIXES = ["grok-imagine-", "grok-stt-", "grok-voice-"] as c function withXaiOAuthCompatDefaults(model: ModelSpec<"openai-responses">): ModelSpec<"openai-responses"> { const compat = { ...(model.compat ?? {}), - includeEncryptedReasoning: model.compat?.includeEncryptedReasoning ?? false, + includeEncryptedReasoning: model.compat?.includeEncryptedReasoning ?? true, filterReasoningHistory: model.compat?.filterReasoningHistory ?? true, supportsImageDetailOriginal: model.compat?.supportsImageDetailOriginal ?? false, omitReasoningEffort: model.compat?.omitReasoningEffort ?? !isGrokReasoningEffortCapable(model.id), @@ -1395,7 +1395,7 @@ function mergeCuratedIntoModel( const compat = { ...(base.compat ?? {}), reasoningEffortMap: { ...XAI_REASONING_EFFORT_MAP, ...(base.compat?.reasoningEffortMap ?? {}) }, - includeEncryptedReasoning: base.compat?.includeEncryptedReasoning ?? false, + includeEncryptedReasoning: base.compat?.includeEncryptedReasoning ?? true, filterReasoningHistory: base.compat?.filterReasoningHistory ?? true, supportsImageDetailOriginal: base.compat?.supportsImageDetailOriginal ?? false, omitReasoningEffort: !effortCapable, @@ -5713,6 +5713,15 @@ function openAiCompletionsDescriptor( return simpleModelsDevDescriptor(modelsDevKey, providerId, "openai-completions", baseUrl, options); } +function openAiResponsesDescriptor( + modelsDevKey: string, + providerId: string, + baseUrl: string, + options: Omit = {}, +): ModelsDevProviderDescriptor { + return simpleModelsDevDescriptor(modelsDevKey, providerId, "openai-responses", baseUrl, options); +} + function anthropicMessagesDescriptor( modelsDevKey: string, providerId: string, @@ -5837,7 +5846,7 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CORE: readonly ModelsDevProviderDescriptor defaultContextWindow: 131072, }), // --- xAI --- - openAiCompletionsDescriptor("xai", "xai", "https://api.x.ai/v1"), + openAiResponsesDescriptor("xai", "xai", "https://api.x.ai/v1"), // --- DeepSeek --- openAiCompletionsDescriptor("deepseek", "deepseek", "https://api.deepseek.com", { // Only ship the v4 family as built-ins; older deepseek-chat / deepseek-reasoner diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index cf9a672d2..9c5cbeb16 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -223,12 +223,15 @@ describe("buildModel", () => { }); }); -describe("xAI-OAuth Responses reasoning-effort suppression", () => { - const grokResponsesSpec = (id: string): ModelSpec<"openai-responses"> => ({ +describe("xAI Responses reasoning-effort suppression", () => { + const grokResponsesSpec = ( + id: string, + provider: "xai" | "xai-oauth" = "xai-oauth", + ): ModelSpec<"openai-responses"> => ({ id, name: id, api: "openai-responses", - provider: "xai-oauth", + provider, baseUrl: "https://api.x.ai/v1", reasoning: true, input: ["text"], @@ -248,6 +251,28 @@ describe("xAI-OAuth Responses reasoning-effort suppression", () => { expect(buildOpenAIResponsesCompat(grokResponsesSpec("grok-4.3")).supportsReasoningEffort).toBe(true); }); + it("applies the same Responses dialect to paid xai and xai-oauth", () => { + const paid = buildOpenAIResponsesCompat(grokResponsesSpec("grok-4.3", "xai")); + const oauth = buildOpenAIResponsesCompat(grokResponsesSpec("grok-4.3", "xai-oauth")); + expect(paid.promptCacheSessionHeader).toBe("x-grok-conv-id"); + expect(oauth.promptCacheSessionHeader).toBe("x-grok-conv-id"); + expect(paid.includeEncryptedReasoning).toBe(true); + expect(oauth.includeEncryptedReasoning).toBe(true); + expect(paid.filterReasoningHistory).toBe(true); + expect(oauth.filterReasoningHistory).toBe(true); + expect(paid.supportsImageDetailOriginal).toBe(false); + expect(oauth.supportsImageDetailOriginal).toBe(false); + expect(paid.supportsReasoningEffort).toBe(true); + expect(oauth.supportsReasoningEffort).toBe(true); + }); + + it("omits effort for paid xai models off the Grok allowlist", () => { + const compat = buildOpenAIResponsesCompat(grokResponsesSpec("grok-code-fast-1", "xai")); + expect(compat.supportsReasoningEffort).toBe(false); + expect(compat.omitReasoningEffort).toBe(true); + expect(buildModel(grokResponsesSpec("grok-code-fast-1", "xai")).thinking).toBeUndefined(); + }); + it("lets an explicit compat.supportsReasoningEffort override the allowlist default", () => { const compat = buildOpenAIResponsesCompat({ ...grokResponsesSpec("grok-build"), @@ -256,7 +281,7 @@ describe("xAI-OAuth Responses reasoning-effort suppression", () => { expect(compat.supportsReasoningEffort).toBe(true); }); - it("does not suppress effort for a non-xai-oauth provider with a grok-like id", () => { + it("does not suppress effort for a non-xAI provider with a grok-like id", () => { const compat = buildOpenAIResponsesCompat({ ...grokResponsesSpec("grok-build"), provider: "openai", diff --git a/packages/catalog/test/xai-api-key-responses.test.ts b/packages/catalog/test/xai-api-key-responses.test.ts new file mode 100644 index 000000000..5f605eb5c --- /dev/null +++ b/packages/catalog/test/xai-api-key-responses.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "bun:test"; +import { getBundledModels } from "@oh-my-pi/pi-catalog/models"; +import { CATALOG_PROVIDERS } from "@oh-my-pi/pi-catalog/provider-models/descriptors"; +import { xaiModelManagerOptions } from "@oh-my-pi/pi-catalog/provider-models/openai-compat"; + +describe("paid xai (XAI_API_KEY) Responses contract", () => { + it("registers xai on the catalog Responses discovery path", () => { + const entry = CATALOG_PROVIDERS.find(provider => provider.id === "xai"); + expect(entry, "xai catalog descriptor").toBeDefined(); + expect(entry!.defaultModel).toBe("grok-4.5"); + expect(entry!.envVars).toContain("XAI_API_KEY"); + const options = xaiModelManagerOptions({ apiKey: "test-key" }); + expect(options.providerId).toBe("xai"); + expect(options.fetchDynamicModels, "live /v1/models overlay").toBeTypeOf("function"); + }); + + it("bundles every paid xai chat model on openai-responses", () => { + const models = getBundledModels("xai"); + expect(models.length).toBeGreaterThan(0); + for (const model of models) { + expect(model.api, `${model.provider}/${model.id}`).toBe("openai-responses"); + expect(model.baseUrl).toBe("https://api.x.ai/v1"); + } + }); +}); diff --git a/packages/catalog/test/xai-oauth-bundle.test.ts b/packages/catalog/test/xai-oauth-bundle.test.ts new file mode 100644 index 000000000..534cc488c --- /dev/null +++ b/packages/catalog/test/xai-oauth-bundle.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from "bun:test"; +import MODELS_JSON from "@oh-my-pi/pi-catalog/models.json" with { type: "json" }; +import { CATALOG_PROVIDERS, DEFAULT_MODEL_PER_PROVIDER } from "@oh-my-pi/pi-catalog/provider-models/descriptors"; +import { buildXaiOAuthStaticSeed } from "@oh-my-pi/pi-catalog/provider-models/openai-compat"; +import type { ModelSpec } from "@oh-my-pi/pi-catalog/types"; + +// Pins the invariant: bundled `models.json` carries every entry the runtime +// curated catalog (XAI_OAUTH_CURATED_MODELS, surfaced via +// buildXaiOAuthStaticSeed) emits. Without this, editing the curated list +// without regenerating `models.json` silently regresses the boot-time +// default-model resolver — the registry sees the runtime seed only after +// `refresh()`, but interactive boot resolves the persisted default +// synchronously from `#loadModels()`, which reads only `models.json`. +// +// Failure here means: run `bun run gen:models` and commit the diff. +describe("xai-oauth bundled catalog (regression)", () => { + const bundled = + (MODELS_JSON as unknown as Record>>)["xai-oauth"] ?? {}; + const seed = buildXaiOAuthStaticSeed(); + + it("defaults SuperGrok selection to grok-4.5", () => { + const entry = CATALOG_PROVIDERS.find(provider => provider.id === "xai-oauth"); + expect(entry?.defaultModel).toBe("grok-4.5"); + expect(DEFAULT_MODEL_PER_PROVIDER["xai-oauth"]).toBe("grok-4.5"); + expect(bundled["grok-4.5"], "xai-oauth/grok-4.5 must be bundled for the default").toBeDefined(); + }); + + it("bundles every curated id", () => { + const seededIds = seed.map(model => model.id).sort(); + const bundledIds = Object.keys(bundled).sort(); + expect(bundledIds).toEqual(seededIds); + }); + + for (const seededModel of seed) { + it(`matches contract for ${seededModel.id}`, () => { + const bundledEntry = bundled[seededModel.id]; + expect(bundledEntry, `xai-oauth/${seededModel.id} missing from models.json`).toBeDefined(); + expect(bundledEntry.id).toBe(seededModel.id); + expect(bundledEntry.name).toBe(seededModel.name); + expect(bundledEntry.provider).toBe("xai-oauth"); + expect(bundledEntry.api).toBe("openai-responses"); + expect(bundledEntry.contextWindow).toBe(seededModel.contextWindow); + expect(bundledEntry.reasoning).toBe(seededModel.reasoning); + // Input modality must survive both the curated seed and the bundle. + // Without this the static fallback used on offline boot strips + // vision capability silently (Codex PR #1127 review). + expect(bundledEntry.input).toEqual(seededModel.input); + expect(bundledEntry.compat?.supportsReasoningEffort).toBe(seededModel.compat?.supportsReasoningEffort); + }); + } + + // Absolute contract for the user-specified SuperGrok addition. The parity + // loop above can't catch a value typo (e.g. 2_000_000) or a flipped + // reasoning flag — both sides regenerate from the same seed together — so + // pin the literal attributes here. + it("exposes grok-composer-2.5-fast as a non-reasoning 200K text model", () => { + const composer = seed.find(model => model.id === "grok-composer-2.5-fast"); + expect(composer, "grok-composer-2.5-fast must be in the SuperGrok curated seed").toBeDefined(); + expect(composer!.reasoning).toBe(false); + expect(composer!.contextWindow).toBe(200_000); + expect(composer!.input).toEqual(["text"]); + // The bundled models.json entry is byte-identical to the generator's + // deterministic xai-oauth output: gen:models pushes + // buildXaiOAuthStaticSeed() (offline — xai-oauth has no upstream catalog + // source) and applyGeneratedModelPolicies(), so a regen reproduces these + // exact bytes; only unrelated other-provider network churn was excluded + // to keep the diff scoped. Pin its zero-cost invariant (overlay-stable + // for the SuperGrok subscription), which the parity loop above never + // compares. (maxTokens is pinned by the maxTokens-equals-contextWindow + // test below.) + expect(bundled["grok-composer-2.5-fast"]?.cost).toEqual({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }); + }); + + // The OAuth surface's /v1/models reports no per-request output limit, so the + // curated catalog owns maxTokens — set to mirror each model's contextWindow + // (the openai-responses wire still clamps the actual request to + // OPENAI_MAX_OUTPUT_TOKENS). Pin maxTokens === contextWindow on both the + // static-seed and bundled paths so a null placeholder can + // never silently leak back into the bundle. + it("sets maxTokens equal to contextWindow for every xai-oauth model", () => { + for (const model of seed) { + expect(model.maxTokens, `seed ${model.id} maxTokens`).toBe(model.contextWindow); + expect(bundled[model.id]?.maxTokens, `bundled ${model.id} maxTokens`).toBe(model.contextWindow); + } + }); +}); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 5597d222b..6abd5a44f 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,13 @@ ## [Unreleased] +### Changed + +- Routed paid xAI models (`XAI_API_KEY` / `xai/…`) through the Responses API used by SuperGrok OAuth instead of Chat Completions. +- Changed the default model for `XAI_API_KEY` (`xai`) from `grok-4-fast-non-reasoning` to `grok-4.5`. +- Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`. +- Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. + ## [17.3.4] - 2026-08-14 ### Changed From 651f20957bff0bfcfde8f97f93b3c41d480d1d50 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 20:54:53 -0700 Subject: [PATCH 02/19] feat(catalog): replay xAI encrypted reasoning on later turns Stop stripping type=reasoning history for xai and xai-oauth so encrypted_content from include is sent back on the next Responses request. --- docs/provider-endpoint-constraints.md | 4 +- .../ai/test/xai-oauth-effort-strip.test.ts | 76 ++++++++++++++++++- packages/catalog/CHANGELOG.md | 7 ++ packages/catalog/src/compat/openai.ts | 9 +-- packages/catalog/src/models.json | 16 ++-- .../src/provider-models/openai-compat.ts | 4 +- packages/catalog/test/build.test.ts | 4 +- packages/coding-agent/CHANGELOG.md | 7 ++ 8 files changed, 107 insertions(+), 20 deletions(-) diff --git a/docs/provider-endpoint-constraints.md b/docs/provider-endpoint-constraints.md index 99264c9b4..605eee899 100644 --- a/docs/provider-endpoint-constraints.md +++ b/docs/provider-endpoint-constraints.md @@ -241,8 +241,8 @@ Both the paid API-key provider (`xai` / `XAI_API_KEY`) and SuperGrok OAuth (`xai-oauth`) chat over `https://api.x.ai/v1/responses`. Keep these independent: - omit `reasoning.effort` -- include `reasoning.encrypted_content` (request `include`) vs replay history -- filter reasoning-history wrappers +- include `reasoning.encrypted_content` on the request +- replay encrypted reasoning items on later turns Some models reject only one of those fields; do not collapse them into one "Grok mode" branch. diff --git a/packages/ai/test/xai-oauth-effort-strip.test.ts b/packages/ai/test/xai-oauth-effort-strip.test.ts index 428ac8d03..24ef3aced 100644 --- a/packages/ai/test/xai-oauth-effort-strip.test.ts +++ b/packages/ai/test/xai-oauth-effort-strip.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { buildParams } from "@oh-my-pi/pi-ai/providers/openai-responses"; -import type { Context } from "@oh-my-pi/pi-ai/types"; +import type { AssistantMessage, Context, Model } from "@oh-my-pi/pi-ai/types"; import { Effort } from "@oh-my-pi/pi-catalog/effort"; import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking"; import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; @@ -95,4 +95,78 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { expect(params.include).toContain("reasoning.encrypted_content"); }); + + test("xai-oauth/grok-4.5 replays encrypted reasoning on the next turn", () => { + const grok45 = getBundledModel<"openai-responses">("xai-oauth", "grok-4.5"); + if (!grok45) throw new Error("xai-oauth/grok-4.5 must be in bundled models.json"); + + const { params } = buildParams(grok45, followUpContextWithEncryptedReasoning(grok45), undefined, undefined); + + expect(params.include).toContain("reasoning.encrypted_content"); + expect(findEncryptedReasoning(params.input)).toEqual({ + type: "reasoning", + id: "rs_xai_next_turn", + encrypted_content: "enc_next_turn", + }); + }); + + test("paid xai/grok-4.5 replays encrypted reasoning on the next turn", () => { + const grok45 = getBundledModel<"openai-responses">("xai", "grok-4.5"); + if (!grok45) throw new Error("xai/grok-4.5 must be in bundled models.json"); + + const { params } = buildParams(grok45, followUpContextWithEncryptedReasoning(grok45), undefined, undefined); + + expect(findEncryptedReasoning(params.input)).toEqual({ + type: "reasoning", + id: "rs_xai_next_turn", + encrypted_content: "enc_next_turn", + }); + }); }); + +function followUpContextWithEncryptedReasoning(model: Model<"openai-responses">): Context { + const assistant: AssistantMessage = { + role: "assistant", + content: [ + { + type: "thinking", + thinking: "internal plan", + thinkingSignature: JSON.stringify({ + type: "reasoning", + id: "rs_xai_next_turn", + encrypted_content: "enc_next_turn", + }), + }, + { type: "text", text: "done" }, + ], + api: "openai-responses", + provider: model.provider, + model: model.id, + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "stop", + timestamp: 1, + }; + return { + messages: [ + { role: "user", content: "first", timestamp: 0 }, + assistant, + { role: "user", content: "continue", timestamp: 2 }, + ], + }; +} + +function findEncryptedReasoning(input: unknown): Record | undefined { + if (!Array.isArray(input)) return undefined; + return input.find(item => { + if (!item || typeof item !== "object") return false; + const candidate = item as { type?: unknown; encrypted_content?: unknown }; + return candidate.type === "reasoning" && typeof candidate.encrypted_content === "string"; + }) as Record | undefined; +} diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 8c03fcdf9..3ef4a4a36 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -135,6 +135,13 @@ - Fixed GitHub Copilot dynamic discovery retaining stale bundled prices for default-context models instead of using the provider's reported default-tier prices. ## [17.2.5] - 2026-08-03 +### Changed + +- Switched the paid xAI provider (`xai` / `XAI_API_KEY`) from Chat Completions to the OpenAI Responses API (`POST https://api.x.ai/v1/responses`), matching SuperGrok `xai-oauth`. Prompt-cache affinity (`x-grok-conv-id`), reasoning-effort allowlisting, and encrypted-reasoning replay rules are now shared across both first-party xAI hosts. +- Changed the paid xAI (`XAI_API_KEY`) default model from `grok-4-fast-non-reasoning` to `grok-4.5`. +- Changed the SuperGrok (`xai-oauth`) default model from `grok-4.3` to `grok-4.5`. +- Requested `reasoning.encrypted_content` on first-party xAI Responses calls (`xai` and `xai-oauth`) via the `include` parameter. +- Replayed xAI encrypted reasoning items on later Responses turns instead of stripping `type: "reasoning"` history. ### Fixed diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index 940568814..b25df9526 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -714,12 +714,11 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol thinkingFormat, reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat), omitReasoningEffort: false, - // Ask xAI `/v1/responses` for `reasoning.encrypted_content` the same way - // first-party OpenAI Responses does. History still drops `type: - // "reasoning"` wrappers (`filterReasoningHistory`) independently — - // those two flags must not be collapsed. + // Ask xAI `/v1/responses` for `reasoning.encrypted_content` and replay + // those items on later turns. OpenRouter Anthropic still filters + // reasoning wrappers independently. includeEncryptedReasoning: true, - filterReasoningHistory: isXaiHost || (isOpenRouter && isAnthropicModel), + filterReasoningHistory: isOpenRouter && isAnthropicModel, disableReasoningOnForcedToolChoice: isKimiModel, disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter, supportsToolChoice: true, diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index 67e785669..7bd63429a 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -104484,7 +104484,7 @@ "minimal": "low" }, "includeEncryptedReasoning": true, - "filterReasoningHistory": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, "supportsReasoningEffort": false @@ -104516,7 +104516,7 @@ "minimal": "low" }, "includeEncryptedReasoning": true, - "filterReasoningHistory": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, "supportsReasoningEffort": false @@ -104560,7 +104560,7 @@ "minimal": "low" }, "includeEncryptedReasoning": true, - "filterReasoningHistory": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, "omitReasoningEffort": false, "supportsReasoningEffort": true @@ -104605,7 +104605,7 @@ "minimal": "low" }, "includeEncryptedReasoning": true, - "filterReasoningHistory": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, "omitReasoningEffort": false, "supportsReasoningEffort": true @@ -104650,7 +104650,7 @@ "minimal": "low" }, "includeEncryptedReasoning": true, - "filterReasoningHistory": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, "omitReasoningEffort": false, "supportsReasoningEffort": true @@ -104710,7 +104710,7 @@ "minimal": "low" }, "includeEncryptedReasoning": true, - "filterReasoningHistory": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, "supportsReasoningEffort": false @@ -104742,7 +104742,7 @@ "minimal": "low" }, "includeEncryptedReasoning": true, - "filterReasoningHistory": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, "supportsReasoningEffort": false @@ -104773,7 +104773,7 @@ "minimal": "low" }, "includeEncryptedReasoning": true, - "filterReasoningHistory": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, "omitReasoningEffort": true, "supportsReasoningEffort": false diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index a5f90aeb8..59dd61514 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1353,7 +1353,7 @@ function withXaiOAuthCompatDefaults(model: ModelSpec<"openai-responses">): Model const compat = { ...(model.compat ?? {}), includeEncryptedReasoning: model.compat?.includeEncryptedReasoning ?? true, - filterReasoningHistory: model.compat?.filterReasoningHistory ?? true, + filterReasoningHistory: model.compat?.filterReasoningHistory ?? false, supportsImageDetailOriginal: model.compat?.supportsImageDetailOriginal ?? false, omitReasoningEffort: model.compat?.omitReasoningEffort ?? !isGrokReasoningEffortCapable(model.id), }; @@ -1396,7 +1396,7 @@ function mergeCuratedIntoModel( ...(base.compat ?? {}), reasoningEffortMap: { ...XAI_REASONING_EFFORT_MAP, ...(base.compat?.reasoningEffortMap ?? {}) }, includeEncryptedReasoning: base.compat?.includeEncryptedReasoning ?? true, - filterReasoningHistory: base.compat?.filterReasoningHistory ?? true, + filterReasoningHistory: false, supportsImageDetailOriginal: base.compat?.supportsImageDetailOriginal ?? false, omitReasoningEffort: !effortCapable, supportsReasoningEffort: effortCapable, diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index 9c5cbeb16..d2638b367 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -258,8 +258,8 @@ describe("xAI Responses reasoning-effort suppression", () => { expect(oauth.promptCacheSessionHeader).toBe("x-grok-conv-id"); expect(paid.includeEncryptedReasoning).toBe(true); expect(oauth.includeEncryptedReasoning).toBe(true); - expect(paid.filterReasoningHistory).toBe(true); - expect(oauth.filterReasoningHistory).toBe(true); + expect(paid.filterReasoningHistory).toBe(false); + expect(oauth.filterReasoningHistory).toBe(false); expect(paid.supportsImageDetailOriginal).toBe(false); expect(oauth.supportsImageDetailOriginal).toBe(false); expect(paid.supportsReasoningEffort).toBe(true); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 6abd5a44f..27d7ffc7f 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -388,6 +388,13 @@ - Exposed the script-driven computer schema to all models, including those with provider-native Computer Use support. - Reduced omp --help cold-start latency and memory usage by rendering lightweight command metadata. +- Exposed the script-driven `computer` schema to every model, including models with provider-native Computer Use support, because native action declarations cannot express persistent desktop sessions or accessibility handles. +- Reduced `omp --help` cold-start latency and memory use by rendering lightweight command metadata without loading every runtime command and provider graph. +- Routed paid xAI models (`XAI_API_KEY` / `xai/…`) through the Responses API used by SuperGrok OAuth instead of Chat Completions. +- Changed the default model for `XAI_API_KEY` (`xai`) from `grok-4-fast-non-reasoning` to `grok-4.5`. +- Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`. +- Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. +- Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`. ### Fixed From bd44ff190ccbee854fc1e4ed90ddadc23ff9e63b Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 21:14:11 -0700 Subject: [PATCH 03/19] docs: keep xAI changelog entries under Unreleased after 17.2.5 Rebase onto origin/main landed those notes in the released section; move them back and drop duplicated 17.2.5 Changed bullets. --- packages/catalog/CHANGELOG.md | 2 ++ packages/coding-agent/CHANGELOG.md | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 3ef4a4a36..3e5f80841 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -143,6 +143,8 @@ - Requested `reasoning.encrypted_content` on first-party xAI Responses calls (`xai` and `xai-oauth`) via the `include` parameter. - Replayed xAI encrypted reasoning items on later Responses turns instead of stripping `type: "reasoning"` history. +## [17.2.5] - 2026-08-03 + ### Fixed - Fixed an issue where newly advertised chat models were dropped during dynamic discovery for the `alibaba-token-plan` provider. diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 27d7ffc7f..a4951c68f 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -364,6 +364,13 @@ - Fixed issues with `/btw` branch promotion where branches could park behind active turns, cut from outdated session leaves, or leave rejected branch keys indistinguishable from composer input. - Fixed database bloat by ensuring archived main and nested session rows are properly cleaned up from `stats.db` during garbage collection. - Fixed startup hanging during local model discovery when a timed-out transport left its request pending, which blocked the CLI before OAuth login could finish ([#7482](https://github.com/can1357/oh-my-pi/issues/7482)). +### Changed + +- Routed paid xAI models (`XAI_API_KEY` / `xai/…`) through the Responses API used by SuperGrok OAuth instead of Chat Completions. +- Changed the default model for `XAI_API_KEY` (`xai`) from `grok-4-fast-non-reasoning` to `grok-4.5`. +- Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`. +- Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. +- Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`. ## [17.2.5] - 2026-08-03 @@ -388,13 +395,6 @@ - Exposed the script-driven computer schema to all models, including those with provider-native Computer Use support. - Reduced omp --help cold-start latency and memory usage by rendering lightweight command metadata. -- Exposed the script-driven `computer` schema to every model, including models with provider-native Computer Use support, because native action declarations cannot express persistent desktop sessions or accessibility handles. -- Reduced `omp --help` cold-start latency and memory use by rendering lightweight command metadata without loading every runtime command and provider graph. -- Routed paid xAI models (`XAI_API_KEY` / `xai/…`) through the Responses API used by SuperGrok OAuth instead of Chat Completions. -- Changed the default model for `XAI_API_KEY` (`xai`) from `grok-4-fast-non-reasoning` to `grok-4.5`. -- Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`. -- Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. -- Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`. ### Fixed From ef7759782d1ee9660b95aba58fe89ba6dc42af36 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 21:23:06 -0700 Subject: [PATCH 04/19] fix(catalog): drop stale xAI Chat Completions model-cache rows Invalidate cached paid-xAI ids on static fingerprint mismatch so the Responses migration is not stuck behind a fresh completions cache overlay. --- packages/catalog/CHANGELOG.md | 4 + .../src/provider-models/openai-compat.ts | 10 ++- .../test/xai-api-key-responses.test.ts | 78 +++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 3e5f80841..0ea3cb284 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -143,6 +143,10 @@ - Requested `reasoning.encrypted_content` on first-party xAI Responses calls (`xai` and `xai-oauth`) via the `include` parameter. - Replayed xAI encrypted reasoning items on later Responses turns instead of stripping `type: "reasoning"` history. +### Fixed + +- Invalidated stale paid-xAI model-cache rows written under Chat Completions so the Responses migration takes effect immediately instead of waiting for TTL expiry. + ## [17.2.5] - 2026-08-03 ### Fixed diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index 59dd61514..8c9fe3384 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1258,7 +1258,15 @@ export interface XaiModelManagerConfig { } export function xaiModelManagerOptions(config?: XaiModelManagerConfig): ModelManagerOptions<"openai-responses"> { - return createSimpleOpenAIResponsesOptions("xai", "https://api.x.ai/v1", config); + return { + ...createSimpleOpenAIResponsesOptions("xai", "https://api.x.ai/v1", config), + // Completions → Responses migration: a fresh authoritative cache written + // by the old resolver stores `api: "openai-completions"` for these ids. + // Without a drop list, `online-if-uncached` skips the network and + // `mergeDynamicModel` lets the cached api win over the new static + // Responses entries until TTL expiry. + dropCachedModelIdsOnStaticMismatch: getBundledModels("xai").map(model => model.id), + }; } export interface XaiOAuthModelManagerConfig { diff --git a/packages/catalog/test/xai-api-key-responses.test.ts b/packages/catalog/test/xai-api-key-responses.test.ts index 5f605eb5c..c11849cec 100644 --- a/packages/catalog/test/xai-api-key-responses.test.ts +++ b/packages/catalog/test/xai-api-key-responses.test.ts @@ -1,7 +1,37 @@ import { describe, expect, it } from "bun:test"; +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { resolveProviderModels } from "@oh-my-pi/pi-catalog/model-manager"; import { getBundledModels } from "@oh-my-pi/pi-catalog/models"; import { CATALOG_PROVIDERS } from "@oh-my-pi/pi-catalog/provider-models/descriptors"; import { xaiModelManagerOptions } from "@oh-my-pi/pi-catalog/provider-models/openai-compat"; +import type { ModelSpec } from "@oh-my-pi/pi-catalog/types"; + +const XAI_RESPONSES_SPEC: ModelSpec<"openai-responses"> = { + 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: 2, output: 6, cacheRead: 0.3, cacheWrite: 0 }, + contextWindow: 500_000, + maxTokens: 500_000, +}; +const XAI_COMPLETIONS_SPEC: ModelSpec<"openai-completions"> = { + id: "grok-4.5", + name: "Grok 4.5", + api: "openai-completions", + provider: "xai", + baseUrl: "https://api.x.ai/v1", + reasoning: true, + input: ["text", "image"], + cost: { input: 2, output: 6, cacheRead: 0.3, cacheWrite: 0 }, + contextWindow: 500_000, + maxTokens: 500_000, +}; describe("paid xai (XAI_API_KEY) Responses contract", () => { it("registers xai on the catalog Responses discovery path", () => { @@ -12,6 +42,8 @@ describe("paid xai (XAI_API_KEY) Responses contract", () => { const options = xaiModelManagerOptions({ apiKey: "test-key" }); expect(options.providerId).toBe("xai"); expect(options.fetchDynamicModels, "live /v1/models overlay").toBeTypeOf("function"); + expect(options.dropCachedModelIdsOnStaticMismatch).toEqual(getBundledModels("xai").map(model => model.id)); + expect(options.dropCachedModelIdsOnStaticMismatch).toContain("grok-4.5"); }); it("bundles every paid xai chat model on openai-responses", () => { @@ -22,4 +54,50 @@ describe("paid xai (XAI_API_KEY) Responses contract", () => { expect(model.baseUrl).toBe("https://api.x.ai/v1"); } }); + + it("drops stale Chat Completions cache rows so Responses takes effect immediately", async () => { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-catalog-xai-completions-cache-")); + const dbPath = path.join(tempDir, "models.db"); + try { + await resolveProviderModels( + { + providerId: "xai", + staticModels: [XAI_COMPLETIONS_SPEC], + fetchDynamicModels: async () => [XAI_COMPLETIONS_SPEC], + cacheDbPath: dbPath, + }, + "online", + ); + + let fetches = 0; + const migrated = await resolveProviderModels( + { + ...xaiModelManagerOptions(), + staticModels: [XAI_RESPONSES_SPEC], + cacheDbPath: dbPath, + fetchDynamicModels: async () => { + fetches += 1; + return [XAI_RESPONSES_SPEC]; + }, + }, + "online-if-uncached", + ); + + expect(fetches).toBe(1); + expect(migrated.models.find(model => model.id === "grok-4.5")?.api).toBe("openai-responses"); + + const offline = await resolveProviderModels( + { + ...xaiModelManagerOptions(), + staticModels: [XAI_RESPONSES_SPEC], + cacheDbPath: dbPath, + fetchDynamicModels: async () => null, + }, + "offline", + ); + expect(offline.models.find(model => model.id === "grok-4.5")?.api).toBe("openai-responses"); + } finally { + await fs.rm(tempDir, { recursive: true, force: true }); + } + }); }); From 7a3a558895fd10f2df917374c04659bd9d1ce2ae Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 21:51:00 -0700 Subject: [PATCH 05/19] fix(catalog): clamp paid xAI Responses minimal effort to low MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Grok 4.5 on XAI_API_KEY kept a minimal dial without SuperGrok's minimal→low wire map, which can 400 on /v1/responses. --- .../ai/test/xai-oauth-effort-strip.test.ts | 18 ++++++++++++++++++ packages/catalog/CHANGELOG.md | 1 + packages/catalog/src/compat/openai.ts | 10 +++++++++- packages/catalog/test/build.test.ts | 2 ++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/ai/test/xai-oauth-effort-strip.test.ts b/packages/ai/test/xai-oauth-effort-strip.test.ts index 24ef3aced..97dbc949f 100644 --- a/packages/ai/test/xai-oauth-effort-strip.test.ts +++ b/packages/ai/test/xai-oauth-effort-strip.test.ts @@ -96,6 +96,24 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { expect(params.include).toContain("reasoning.encrypted_content"); }); + test("paid xai/grok-4.5 clamps minimal reasoning effort to low", () => { + const grok45 = getBundledModel<"openai-responses">("xai", "grok-4.5"); + if (!grok45) throw new Error("xai/grok-4.5 must be in bundled models.json"); + + const { params } = buildParams(grok45, singleUserContext, { reasoning: Effort.Minimal }, undefined); + + expect(params.reasoning).toMatchObject({ effort: "low" }); + }); + + test("xai-oauth/grok-4.5 clamps minimal reasoning effort to low", () => { + const grok45 = getBundledModel<"openai-responses">("xai-oauth", "grok-4.5"); + if (!grok45) throw new Error("xai-oauth/grok-4.5 must be in bundled models.json"); + + const { params } = buildParams(grok45, singleUserContext, { reasoning: Effort.Minimal }, undefined); + + expect(params.reasoning).toMatchObject({ effort: "low" }); + }); + test("xai-oauth/grok-4.5 replays encrypted reasoning on the next turn", () => { const grok45 = getBundledModel<"openai-responses">("xai-oauth", "grok-4.5"); if (!grok45) throw new Error("xai-oauth/grok-4.5 must be in bundled models.json"); diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 0ea3cb284..1b1d60206 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -146,6 +146,7 @@ ### Fixed - Invalidated stale paid-xAI model-cache rows written under Chat Completions so the Responses migration takes effect immediately instead of waiting for TTL expiry. +- Clamped paid xAI Responses `minimal` reasoning effort to `low` (same wire map as SuperGrok) so `xai/grok-4.5` does not 400. ## [17.2.5] - 2026-08-03 diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index b25df9526..626b6baa4 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -177,6 +177,11 @@ const MIMO_REASONING_EFFORT_MAP: NonNullable xhigh: "high", }; +/** xAI `/v1/responses` accepts `low|medium|high` (and `xhigh` on some SKUs), not `minimal`. */ +const XAI_RESPONSES_REASONING_EFFORT_MAP: NonNullable = { + minimal: "low", +}; + function mergeModelReasoningEffortMap( compat: ResolvedOpenAISharedCompat, modelId: string, @@ -706,7 +711,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol // Copilot host under a different provider id still clamps. supportsImageDetailOriginal: !isXaiHost && !modelMatchesHost({ provider: spec.provider, baseUrl }, "githubCopilot"), - reasoningEffortMap: {}, + reasoningEffortMap: isXaiHost ? { ...XAI_RESPONSES_REASONING_EFFORT_MAP } : {}, supportsReasoningParams: true, // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit // temperature/top_p/… with a 400 on every serving host (#5606). @@ -766,6 +771,9 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol : spec.compat?.streamIdleTimeoutMs, }; applyCompatOverrides(compat, spec.compat); + if (isXaiHost) { + compat.reasoningEffortMap = { ...XAI_RESPONSES_REASONING_EFFORT_MAP, ...compat.reasoningEffortMap }; + } if (spec.compat?.reasoningDisableMode === undefined) { compat.reasoningDisableMode = resolveReasoningDisableMode(compat.thinkingFormat); } diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index d2638b367..d8dd398ee 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -264,6 +264,8 @@ describe("xAI Responses reasoning-effort suppression", () => { expect(oauth.supportsImageDetailOriginal).toBe(false); expect(paid.supportsReasoningEffort).toBe(true); expect(oauth.supportsReasoningEffort).toBe(true); + expect(paid.reasoningEffortMap).toEqual({ minimal: "low" }); + expect(oauth.reasoningEffortMap).toEqual({ minimal: "low" }); }); it("omits effort for paid xai models off the Grok allowlist", () => { From ca2fa4f5f616ae0d7be83c4ece4755991225d58b Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 22:03:40 -0700 Subject: [PATCH 06/19] fix(ai): do not treat XAI_API_KEY as SuperGrok availability Paid-key-only setups were marked signed in for xai-oauth, so the shared grok-4.5 default picker preferred SuperGrok over xai/grok-4.5. --- packages/ai/CHANGELOG.md | 3 ++ packages/ai/src/auth-storage.ts | 20 ++++++++- packages/ai/test/xai-login.test.ts | 43 +++++++++++++++++++ packages/coding-agent/CHANGELOG.md | 1 + .../coding-agent/test/model-resolver.test.ts | 30 +++++++++++++ 5 files changed, 95 insertions(+), 2 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 08c36e86c..c9810157d 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -148,6 +148,9 @@ ### Fixed - Fixed an issue where Ollama requests without a user-role message would fail to generate output or silently fail with a misleading error. +### Fixed + +- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. ## [17.2.5] - 2026-08-03 diff --git a/packages/ai/src/auth-storage.ts b/packages/ai/src/auth-storage.ts index 625f6b6d4..538a2ce78 100644 --- a/packages/ai/src/auth-storage.ts +++ b/packages/ai/src/auth-storage.ts @@ -2687,7 +2687,7 @@ export class AuthStorage { if (this.#runtimeOverrides.has(provider)) return true; if (this.#configOverrides.has(provider)) return true; if (this.#getCredentialsForProvider(provider).length > 0) return true; - if (getEnvApiKey(provider)) return true; + if (this.#hasDedicatedEnvAuth(provider)) return true; if (this.#fallbackResolver?.(provider)) return true; return false; } @@ -2711,6 +2711,22 @@ export class AuthStorage { return false; } + /** + * Env auth that belongs to this provider, not a cross-provider alias. + * + * `getEnvApiKey("xai-oauth")` also accepts `XAI_API_KEY` so an explicit + * `xai-oauth/…` stream can still borrow the paid key. Availability and + * origin must not: otherwise an API-key-only setup marks SuperGrok as + * signed in and `pickDefaultAvailableModel` prefers `xai-oauth/grok-4.5` + * over paid `xai/grok-4.5`. + */ + #hasDedicatedEnvAuth(provider: string): boolean { + if (provider === "xai-oauth") { + return Boolean($env.XAI_OAUTH_TOKEN?.trim()); + } + return Boolean(getEnvApiKey(provider)); + } + /** * Classify where a provider's auth comes from, following the same precedence * as {@link AuthStorage.getApiKey}: runtime override → config override → @@ -2727,7 +2743,7 @@ export class AuthStorage { if (stored.some(credential => credential.type === "api_key" && credential.source === "login")) { return { kind: "api_key" }; } - if (getEnvApiKey(provider)) return { kind: "env", envVar: getEnvApiKeyName(provider) }; + if (this.#hasDedicatedEnvAuth(provider)) return { kind: "env", envVar: getEnvApiKeyName(provider) }; if (stored.some(credential => credential.type === "api_key")) return { kind: "api_key" }; if (this.#fallbackResolver?.(provider)) return { kind: "fallback" }; return undefined; diff --git a/packages/ai/test/xai-login.test.ts b/packages/ai/test/xai-login.test.ts index 1cdd2ca1d..0d0ad19f4 100644 --- a/packages/ai/test/xai-login.test.ts +++ b/packages/ai/test/xai-login.test.ts @@ -29,6 +29,49 @@ describe("xAI API login wiring", () => { expect(getEnvApiKey("xai")).toBe("xai-env-key"); }); + test("XAI_API_KEY alone does not mark SuperGrok as available", async () => { + const originalOauthToken = Bun.env.XAI_OAUTH_TOKEN; + Bun.env.XAI_API_KEY = "xai-env-key"; + delete Bun.env.XAI_OAUTH_TOKEN; + const store = new SqliteAuthCredentialStore(new Database(":memory:")); + const storage = new AuthStorage(store); + await storage.reload(); + try { + expect(storage.hasAuth("xai")).toBe(true); + expect(storage.hasAuth("xai-oauth")).toBe(false); + expect(storage.getCredentialOrigin("xai")).toEqual({ kind: "env", envVar: "XAI_API_KEY" }); + expect(storage.getCredentialOrigin("xai-oauth")).toBeUndefined(); + } finally { + if (originalOauthToken === undefined) { + delete Bun.env.XAI_OAUTH_TOKEN; + } else { + Bun.env.XAI_OAUTH_TOKEN = originalOauthToken; + } + store.close(); + } + }); + + test("XAI_OAUTH_TOKEN marks SuperGrok available without a paid API key", async () => { + const originalOauthToken = Bun.env.XAI_OAUTH_TOKEN; + delete Bun.env.XAI_API_KEY; + Bun.env.XAI_OAUTH_TOKEN = "xai-oauth-env"; + const store = new SqliteAuthCredentialStore(new Database(":memory:")); + const storage = new AuthStorage(store); + await storage.reload(); + try { + expect(storage.hasAuth("xai")).toBe(false); + expect(storage.hasAuth("xai-oauth")).toBe(true); + expect(storage.getCredentialOrigin("xai-oauth")).toEqual({ kind: "env" }); + } finally { + if (originalOauthToken === undefined) { + delete Bun.env.XAI_OAUTH_TOKEN; + } else { + Bun.env.XAI_OAUTH_TOKEN = originalOauthToken; + } + store.close(); + } + }); + test("AuthStorage.login('xai') validates against /models and stores the pasted key", async () => { const fetchCalls: Array<{ url: string; init: RequestInit | undefined }> = []; const fetchMock: FetchImpl = vi.fn(async (input: string | URL | Request, init?: RequestInit) => { diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a4951c68f..2c7bb22cb 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -371,6 +371,7 @@ - Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`. - Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. - Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`. +- Kept automatic model selection on paid `xai/grok-4.5` when only `XAI_API_KEY` is set, instead of preferring SuperGrok `xai-oauth/grok-4.5`. ## [17.2.5] - 2026-08-03 diff --git a/packages/coding-agent/test/model-resolver.test.ts b/packages/coding-agent/test/model-resolver.test.ts index f8f665441..6d0e2d86d 100644 --- a/packages/coding-agent/test/model-resolver.test.ts +++ b/packages/coding-agent/test/model-resolver.test.ts @@ -437,6 +437,36 @@ describe("pickDefaultAvailableModel", () => { expect(result?.provider).toBe("zhipu-coding-plan"); expect(result?.id).toBe("glm-5.1"); }); + + test("prefers SuperGrok over paid xAI when both defaults are present", () => { + const paid = buildModel({ + 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: 2, output: 6, cacheRead: 0.3, cacheWrite: 0 }, + contextWindow: 500000, + maxTokens: 500000, + }); + const oauth = buildModel({ + id: "grok-4.5", + name: "Grok 4.5", + api: "openai-responses", + provider: "xai-oauth", + baseUrl: "https://api.x.ai/v1", + reasoning: true, + input: ["text", "image"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 500000, + maxTokens: 500000, + }); + + expect(pickDefaultAvailableModel([paid, oauth])?.provider).toBe("xai-oauth"); + expect(pickDefaultAvailableModel([paid])?.provider).toBe("xai"); + }); }); describe("parseModelPattern", () => { From 3fb57803f301a5e1cc925986b9c07dd3cc0313be Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 22:14:50 -0700 Subject: [PATCH 07/19] fix(catalog): omit penalty and stop params on xAI reasoning models Grok 4.5 rejects presencePenalty, frequencyPenalty, and stop. After the paid default moved off a non-reasoning model, configured penalties 400ed. --- .../ai/src/providers/openai-completions.ts | 20 ++++++++++--------- packages/ai/src/providers/openai-shared.ts | 8 +++++--- .../ai/test/issue-967-vision-guard.test.ts | 1 + .../ai/test/openai-completions-compat.test.ts | 1 + ...nai-completions-tool-result-images.test.ts | 1 + .../ai/test/xai-oauth-effort-strip.test.ts | 15 ++++++++++++++ packages/catalog/CHANGELOG.md | 1 + packages/catalog/src/compat/openai.ts | 5 +++++ packages/catalog/src/types.ts | 8 ++++++++ packages/catalog/test/build.test.ts | 10 ++++++++++ packages/coding-agent/CHANGELOG.md | 1 + 11 files changed, 59 insertions(+), 12 deletions(-) diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index d60c1004c..2b262b130 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -1575,17 +1575,19 @@ function buildParams( if (options?.minP !== undefined) { params.min_p = options.minP; } - if (options?.presencePenalty !== undefined) { - params.presence_penalty = options.presencePenalty; - } - if (options?.repetitionPenalty !== undefined) { - params.repetition_penalty = options.repetitionPenalty; - } - if (options?.frequencyPenalty !== undefined) { - params.frequency_penalty = options.frequencyPenalty; + if (initialCompat.supportsPenaltyAndStopParams) { + if (options?.presencePenalty !== undefined) { + params.presence_penalty = options.presencePenalty; + } + if (options?.repetitionPenalty !== undefined) { + params.repetition_penalty = options.repetitionPenalty; + } + if (options?.frequencyPenalty !== undefined) { + params.frequency_penalty = options.frequencyPenalty; + } } } - if (options?.stopSequences?.length) { + if (options?.stopSequences?.length && initialCompat.supportsPenaltyAndStopParams) { const seqs = options.stopSequences; params.stop = seqs.length === 1 ? seqs[0] : seqs.slice(0, 4); } diff --git a/packages/ai/src/providers/openai-shared.ts b/packages/ai/src/providers/openai-shared.ts index 9d4f87861..f7b16f00e 100644 --- a/packages/ai/src/providers/openai-shared.ts +++ b/packages/ai/src/providers/openai-shared.ts @@ -3308,7 +3308,7 @@ export function applyCommonResponsesSamplingParams

& { - compat: Pick; + compat: Pick; }, ): void { if (options?.maxTokens && !model.omitMaxOutputTokens) { @@ -3325,8 +3325,10 @@ export function applyCommonResponsesSamplingParams

{ toolStrictMode: "none", supportsReasoningParams: true, supportsSamplingParams: true, + supportsPenaltyAndStopParams: true, alwaysSendMaxTokens: false, isOpenRouterHost: false, isVercelGatewayHost: false, diff --git a/packages/ai/test/openai-completions-tool-result-images.test.ts b/packages/ai/test/openai-completions-tool-result-images.test.ts index 1219f8e30..8abf761b5 100644 --- a/packages/ai/test/openai-completions-tool-result-images.test.ts +++ b/packages/ai/test/openai-completions-tool-result-images.test.ts @@ -51,6 +51,7 @@ const compat: ResolvedOpenAICompat = { toolStrictMode: "none", supportsReasoningParams: true, supportsSamplingParams: true, + supportsPenaltyAndStopParams: true, alwaysSendMaxTokens: false, isOpenRouterHost: false, isVercelGatewayHost: false, diff --git a/packages/ai/test/xai-oauth-effort-strip.test.ts b/packages/ai/test/xai-oauth-effort-strip.test.ts index 97dbc949f..6d052f9d1 100644 --- a/packages/ai/test/xai-oauth-effort-strip.test.ts +++ b/packages/ai/test/xai-oauth-effort-strip.test.ts @@ -96,6 +96,21 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { expect(params.include).toContain("reasoning.encrypted_content"); }); + test("paid xai/grok-4.5 omits presence_penalty on reasoning models", () => { + const grok45 = getBundledModel<"openai-responses">("xai", "grok-4.5"); + if (!grok45) throw new Error("xai/grok-4.5 must be in bundled models.json"); + + const { params } = buildParams( + grok45, + singleUserContext, + { reasoning: Effort.High, presencePenalty: 0.4, temperature: 0.2 }, + undefined, + ); + + expect(params).not.toHaveProperty("presence_penalty"); + expect(params.temperature).toBe(0.2); + }); + test("paid xai/grok-4.5 clamps minimal reasoning effort to low", () => { const grok45 = getBundledModel<"openai-responses">("xai", "grok-4.5"); if (!grok45) throw new Error("xai/grok-4.5 must be in bundled models.json"); diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 1b1d60206..5fbe994d8 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -147,6 +147,7 @@ - Invalidated stale paid-xAI model-cache rows written under Chat Completions so the Responses migration takes effect immediately instead of waiting for TTL expiry. - Clamped paid xAI Responses `minimal` reasoning effort to `low` (same wire map as SuperGrok) so `xai/grok-4.5` does not 400. +- Suppressed presence/frequency penalties and stop sequences on xAI reasoning models so a configured `presencePenalty` does not 400 after the `grok-4.5` default change. ## [17.2.5] - 2026-08-03 diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index 626b6baa4..4e6bfb4e7 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -476,6 +476,8 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit // temperature/top_p/… with a 400 on every serving host (#5606). supportsSamplingParams: !isOpenAISamplingRestrictedModelId(spec.id), + // xAI reasoning models 400 on presence/frequency penalties and stop. + supportsPenaltyAndStopParams: !(isGrok && Boolean(spec.reasoning)), reasoningEffortMap: {}, supportsUsageInStreaming: !isCerebras, // Kimi (including via OpenRouter and Fireworks router-form IDs such as @@ -716,6 +718,9 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit // temperature/top_p/… with a 400 on every serving host (#5606). supportsSamplingParams: !isOpenAISamplingRestrictedModelId(id), + // xAI reasoning models 400 on presence/frequency penalties and stop + // (https://docs.x.ai/developers/model-capabilities/text/reasoning). + supportsPenaltyAndStopParams: !(isXaiHost && reasoningCapable), thinkingFormat, reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat), omitReasoningEffort: false, diff --git a/packages/catalog/src/types.ts b/packages/catalog/src/types.ts index 729ab4d89..648a628fa 100644 --- a/packages/catalog/src/types.ts +++ b/packages/catalog/src/types.ts @@ -363,6 +363,12 @@ export interface OpenAICompat { * model id. Default: true. Issue #5606. */ supportsSamplingParams?: boolean; + /** + * Whether presence/frequency penalties and stop sequences may be sent. + * xAI reasoning models reject `presencePenalty`, `frequencyPenalty`, and + * `stop` with a 400. When unset, auto-detected. Default: true. + */ + supportsPenaltyAndStopParams?: boolean; /** Always send a max-token field when the caller did not provide one. Default: auto-detected (Kimi-family models derive TPM limits from max_tokens). */ alwaysSendMaxTokens?: boolean; /** Whether Responses-API tool-call/result history must be strictly paired. Default: auto-detected (Azure OpenAI, GitHub Copilot). */ @@ -578,6 +584,7 @@ export interface ResolvedOpenAISharedCompat { reasoningEffortMap: Partial>; supportsReasoningParams: boolean; supportsSamplingParams: boolean; + supportsPenaltyAndStopParams: boolean; thinkingFormat: OpenAIReasoningFormat; /** Kimi Code transport selected by live per-model protocol metadata. */ kimiApiFormat?: OpenAICompat["kimiApiFormat"]; @@ -643,6 +650,7 @@ export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & | "reasoningEffortMap" | "supportsReasoningParams" | "supportsSamplingParams" + | "supportsPenaltyAndStopParams" | "thinkingFormat" | "kimiApiFormat" | "reasoningDisableMode" diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index d8dd398ee..5ff48ae01 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -266,6 +266,16 @@ describe("xAI Responses reasoning-effort suppression", () => { expect(oauth.supportsReasoningEffort).toBe(true); expect(paid.reasoningEffortMap).toEqual({ minimal: "low" }); expect(oauth.reasoningEffortMap).toEqual({ minimal: "low" }); + expect(paid.supportsPenaltyAndStopParams).toBe(false); + expect(oauth.supportsPenaltyAndStopParams).toBe(false); + }); + + it("keeps penalty and stop params on non-reasoning paid xAI models", () => { + const compat = buildOpenAIResponsesCompat({ + ...grokResponsesSpec("grok-4-fast-non-reasoning", "xai"), + reasoning: false, + }); + expect(compat.supportsPenaltyAndStopParams).toBe(true); }); it("omits effort for paid xai models off the Grok allowlist", () => { diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 2c7bb22cb..61ccd6e52 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -372,6 +372,7 @@ - Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. - Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`. - Kept automatic model selection on paid `xai/grok-4.5` when only `XAI_API_KEY` is set, instead of preferring SuperGrok `xai-oauth/grok-4.5`. +- Stopped sending presence/frequency penalties and stop sequences to xAI reasoning models such as `grok-4.5`, which reject them. ## [17.2.5] - 2026-08-03 From b49b5b88d233cda1db197567702ddf4fbd704e37 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 22:47:15 -0700 Subject: [PATCH 08/19] fix(catalog): strip stale xAI Responses effort dials from generated rows Paid xAI models.dev regeneration still emitted Completions-era thinking dials for off-allowlist reasoners. Bake the no-dial policy into the resolver/generator and refresh the exported catalog snapshot. --- packages/ai/CHANGELOG.md | 7 +- packages/catalog/CHANGELOG.md | 23 +- .../catalog/scripts/generated-policies.ts | 5 + packages/catalog/src/models.json | 349 +++++++++++++----- .../src/provider-models/openai-compat.ts | 34 +- .../catalog/test/generated-policies.test.ts | 43 +++ .../xai-responses-thinking-policy.test.ts | 122 ++++++ packages/coding-agent/CHANGELOG.md | 12 +- 8 files changed, 479 insertions(+), 116 deletions(-) create mode 100644 packages/catalog/test/xai-responses-thinking-policy.test.ts diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index c9810157d..24e74327b 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. + ## [17.3.4] - 2026-08-14 ### Fixed @@ -148,9 +152,6 @@ ### Fixed - Fixed an issue where Ollama requests without a user-role message would fail to generate output or silently fail with a misleading error. -### Fixed - -- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. ## [17.2.5] - 2026-08-03 diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 5fbe994d8..4abde4e75 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -8,6 +8,14 @@ - Changed the paid xAI (`XAI_API_KEY`) default model from `grok-4-fast-non-reasoning` to `grok-4.5`. - Changed the SuperGrok (`xai-oauth`) default model from `grok-4.3` to `grok-4.5`. - Requested `reasoning.encrypted_content` on first-party xAI Responses calls (`xai` and `xai-oauth`) via the `include` parameter. +- Replayed xAI encrypted reasoning items on later Responses turns instead of stripping `type: "reasoning"` history. + +### Fixed + +- Invalidated stale paid-xAI model-cache rows written under Chat Completions so the Responses migration takes effect immediately instead of waiting for TTL expiry. +- Clamped paid xAI Responses `minimal` reasoning effort to `low` (same wire map as SuperGrok) so `xai/grok-4.5` does not 400. +- Suppressed presence/frequency penalties and stop sequences on xAI reasoning models so a configured `presencePenalty` does not 400 after the `grok-4.5` default change. +- Stopped emitting stale `thinking.efforts` dials on paid xAI Responses catalog rows that reject `reasoning.effort` (`grok-code-fast-1`, `grok-build-0.1`, `grok-4.20-0309-reasoning`, and other off-allowlist reasoners). ## [17.3.4] - 2026-08-14 @@ -134,21 +142,6 @@ - Fixed dynamic discovery for the `deepseek-v4` model family (such as `deepseek-v4-flash-0731`) under `alibaba-token-plan` missing reasoning configuration and maximum thinking effort. - Fixed GitHub Copilot dynamic discovery retaining stale bundled prices for default-context models instead of using the provider's reported default-tier prices. -## [17.2.5] - 2026-08-03 -### Changed - -- Switched the paid xAI provider (`xai` / `XAI_API_KEY`) from Chat Completions to the OpenAI Responses API (`POST https://api.x.ai/v1/responses`), matching SuperGrok `xai-oauth`. Prompt-cache affinity (`x-grok-conv-id`), reasoning-effort allowlisting, and encrypted-reasoning replay rules are now shared across both first-party xAI hosts. -- Changed the paid xAI (`XAI_API_KEY`) default model from `grok-4-fast-non-reasoning` to `grok-4.5`. -- Changed the SuperGrok (`xai-oauth`) default model from `grok-4.3` to `grok-4.5`. -- Requested `reasoning.encrypted_content` on first-party xAI Responses calls (`xai` and `xai-oauth`) via the `include` parameter. -- Replayed xAI encrypted reasoning items on later Responses turns instead of stripping `type: "reasoning"` history. - -### Fixed - -- Invalidated stale paid-xAI model-cache rows written under Chat Completions so the Responses migration takes effect immediately instead of waiting for TTL expiry. -- Clamped paid xAI Responses `minimal` reasoning effort to `low` (same wire map as SuperGrok) so `xai/grok-4.5` does not 400. -- Suppressed presence/frequency penalties and stop sequences on xAI reasoning models so a configured `presencePenalty` does not 400 after the `grok-4.5` default change. - ## [17.2.5] - 2026-08-03 ### Fixed diff --git a/packages/catalog/scripts/generated-policies.ts b/packages/catalog/scripts/generated-policies.ts index 2b97e0dfb..0002dab3e 100644 --- a/packages/catalog/scripts/generated-policies.ts +++ b/packages/catalog/scripts/generated-policies.ts @@ -22,6 +22,7 @@ import { isOllamaCloudOutputCapped, OLLAMA_CLOUD_MAX_OUTPUT_TOKENS } from "../sr import { ALIBABA_TOKEN_PLAN_STATIC_MODELS, OPENAI_GPT_56_LONG_CONTEXT_COSTS, + applyXaiResponsesThinkingPolicy, resolveWaferServerlessThinkingFormat, } from "../src/provider-models/openai-compat"; import type { Api, LongContextTokenCost, Model, ModelSpec } from "../src/types"; @@ -353,6 +354,10 @@ export function applyOllamaCloudOutputCap(models: ModelSpec[]): void { } function applyGeneratedModelPolicy(model: ModelSpec): void { + if (model.provider === "xai" && model.api === "openai-responses") { + const updated = applyXaiResponsesThinkingPolicy(model as ModelSpec<"openai-responses">); + model.compat = updated.compat; + } const copilotLimits = model.provider === "github-copilot" ? COPILOT_GENERATED_LIMITS[model.id] : undefined; if (copilotLimits) { model.contextWindow = copilotLimits.contextWindow; diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index 7bd63429a..c28196f6c 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -103730,7 +103730,14 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-2-1212": { "id": "grok-2-1212", @@ -103749,7 +103756,14 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-2-latest": { "id": "grok-2-latest", @@ -103768,7 +103782,14 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-2-vision": { "id": "grok-2-vision", @@ -103788,7 +103809,14 @@ "cacheWrite": 0 }, "contextWindow": 8192, - "maxTokens": 4096 + "maxTokens": 4096, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-2-vision-1212": { "id": "grok-2-vision-1212", @@ -103808,7 +103836,14 @@ "cacheWrite": 0 }, "contextWindow": 8192, - "maxTokens": 4096 + "maxTokens": 4096, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-2-vision-latest": { "id": "grok-2-vision-latest", @@ -103828,7 +103863,14 @@ "cacheWrite": 0 }, "contextWindow": 8192, - "maxTokens": 4096 + "maxTokens": 4096, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-3": { "id": "grok-3", @@ -103847,7 +103889,14 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-3-fast": { "id": "grok-3-fast", @@ -103866,7 +103915,14 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-3-fast-latest": { "id": "grok-3-fast-latest", @@ -103885,7 +103941,14 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-3-latest": { "id": "grok-3-latest", @@ -103904,7 +103967,14 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 8192 + "maxTokens": 8192, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-3-mini": { "id": "grok-3-mini", @@ -103930,8 +104000,19 @@ "minimal", "low", "medium", - "high" - ] + "high", + "xhigh" + ], + "effortMap": { + "minimal": "low" + } + }, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": true, + "omitReasoningEffort": false } }, "grok-3-mini-fast": { @@ -103958,8 +104039,19 @@ "minimal", "low", "medium", - "high" - ] + "high", + "xhigh" + ], + "effortMap": { + "minimal": "low" + } + }, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": true, + "omitReasoningEffort": false } }, "grok-3-mini-fast-latest": { @@ -103986,8 +104078,19 @@ "minimal", "low", "medium", - "high" - ] + "high", + "xhigh" + ], + "effortMap": { + "minimal": "low" + } + }, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": true, + "omitReasoningEffort": false } }, "grok-3-mini-latest": { @@ -104014,8 +104117,19 @@ "minimal", "low", "medium", - "high" - ] + "high", + "xhigh" + ], + "effortMap": { + "minimal": "low" + } + }, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": true, + "omitReasoningEffort": false } }, "grok-4": { @@ -104036,14 +104150,12 @@ }, "contextWindow": 256000, "maxTokens": 64000, - "thinking": { - "mode": "effort", - "efforts": [ - "minimal", - "low", - "medium", - "high" - ] + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true } }, "grok-4-1-fast": { @@ -104065,14 +104177,12 @@ }, "contextWindow": 2000000, "maxTokens": 30000, - "thinking": { - "mode": "effort", - "efforts": [ - "minimal", - "low", - "medium", - "high" - ] + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true } }, "grok-4-1-fast-non-reasoning": { @@ -104093,7 +104203,14 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 30000 + "maxTokens": 30000, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-4-fast": { "id": "grok-4-fast", @@ -104114,14 +104231,12 @@ }, "contextWindow": 2000000, "maxTokens": 30000, - "thinking": { - "mode": "effort", - "efforts": [ - "minimal", - "low", - "medium", - "high" - ] + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true } }, "grok-4-fast-non-reasoning": { @@ -104142,7 +104257,14 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 30000 + "maxTokens": 30000, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-4.20-0309-non-reasoning": { "id": "grok-4.20-0309-non-reasoning", @@ -104162,7 +104284,14 @@ "cacheWrite": 0 }, "contextWindow": 1000000, - "maxTokens": 30000 + "maxTokens": 30000, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-4.20-0309-reasoning": { "id": "grok-4.20-0309-reasoning", @@ -104183,15 +104312,12 @@ }, "contextWindow": 1000000, "maxTokens": 30000, - "thinking": { - "mode": "effort", - "efforts": [ - "minimal", - "low", - "medium", - "high" - ], - "requiresEffort": true + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true } }, "grok-4.20-beta-latest-non-reasoning": { @@ -104212,7 +104338,14 @@ "cacheWrite": 0 }, "contextWindow": 2000000, - "maxTokens": 30000 + "maxTokens": 30000, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-4.20-beta-latest-reasoning": { "id": "grok-4.20-beta-latest-reasoning", @@ -104233,15 +104366,12 @@ }, "contextWindow": 2000000, "maxTokens": 30000, - "thinking": { - "mode": "effort", - "efforts": [ - "minimal", - "low", - "medium", - "high" - ], - "requiresEffort": true + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true } }, "grok-4.20-multi-agent-beta-latest": { @@ -104269,8 +104399,19 @@ "minimal", "low", "medium", - "high" - ] + "high", + "xhigh" + ], + "effortMap": { + "minimal": "low" + } + }, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": true, + "omitReasoningEffort": false } }, "grok-4.3": { @@ -104298,8 +104439,19 @@ "minimal", "low", "medium", - "high" - ] + "high", + "xhigh" + ], + "effortMap": { + "minimal": "low" + } + }, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": true, + "omitReasoningEffort": false } }, "grok-4.5": { @@ -104327,8 +104479,19 @@ "minimal", "low", "medium", - "high" - ] + "high", + "xhigh" + ], + "effortMap": { + "minimal": "low" + } + }, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": true, + "omitReasoningEffort": false } }, "grok-4.6": { @@ -104377,7 +104540,14 @@ "cacheWrite": 0 }, "contextWindow": 131072, - "maxTokens": 4096 + "maxTokens": 4096, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } }, "grok-build-0.1": { "id": "grok-build-0.1", @@ -104398,14 +104568,12 @@ }, "contextWindow": 256000, "maxTokens": 256000, - "thinking": { - "mode": "effort", - "efforts": [ - "minimal", - "low", - "medium", - "high" - ] + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true } }, "grok-code-fast-1": { @@ -104426,14 +104594,12 @@ }, "contextWindow": 256000, "maxTokens": 10000, - "thinking": { - "mode": "effort", - "efforts": [ - "minimal", - "low", - "medium", - "high" - ] + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true } }, "grok-vision-beta": { @@ -104454,7 +104620,14 @@ "cacheWrite": 0 }, "contextWindow": 8192, - "maxTokens": 4096 + "maxTokens": 4096, + "compat": { + "reasoningEffortMap": { + "minimal": "low" + }, + "supportsReasoningEffort": false, + "omitReasoningEffort": true + } } }, "xai-oauth": { diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index 8c9fe3384..1edf057e3 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1376,6 +1376,36 @@ function withXaiOAuthCompatDefaults(model: ModelSpec<"openai-responses">): Model // of the omitReasoningEffort gate in pi-ai's stream.ts. const XAI_REASONING_EFFORT_MAP = { minimal: "low" } as const; +/** + * Bake first-party xAI Responses effort-dial metadata onto a catalog spec. + * + * models.dev marks many Grok SKUs as reasoners and the thinking rebake would + * otherwise emit a default `minimal/low/medium/high` dial. api.x.ai only + * accepts `reasoning.effort` for {@link isGrokReasoningEffortCapable} ids — + * off-allowlist reasoners (`grok-code-fast-1`, `grok-build-0.1`, + * `grok-4.20-0309-reasoning`, …) 400 if the param is sent. SuperGrok + * (`xai-oauth`) already curates this via {@link mergeCuratedIntoModel}; paid + * `xai` rows come from stencil.so and need the same wire facts in the exported + * `models.json` so direct catalog readers do not present an unsupported dial. + * + * Explicit `compat.supportsReasoningEffort` / `omitReasoningEffort` win. + */ +export function applyXaiResponsesThinkingPolicy(model: ModelSpec<"openai-responses">): ModelSpec<"openai-responses"> { + const effortCapable = model.compat?.supportsReasoningEffort ?? isGrokReasoningEffortCapable(model.id); + return { + ...model, + compat: { + ...(model.compat ?? {}), + reasoningEffortMap: { + ...XAI_REASONING_EFFORT_MAP, + ...(model.compat?.reasoningEffortMap ?? {}), + }, + supportsReasoningEffort: effortCapable, + omitReasoningEffort: model.compat?.omitReasoningEffort ?? !effortCapable, + }, + }; +} + // xai-oauth's /v1/models exposes no per-request output limit on the OAuth // (Grok Build / SuperGrok) surface, so the curated catalog owns `maxTokens` // like it owns `contextWindow`: each entry mirrors its context window. The @@ -5854,7 +5884,9 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CORE: readonly ModelsDevProviderDescriptor defaultContextWindow: 131072, }), // --- xAI --- - openAiResponsesDescriptor("xai", "xai", "https://api.x.ai/v1"), + openAiResponsesDescriptor("xai", "xai", "https://api.x.ai/v1", { + transformModel: model => applyXaiResponsesThinkingPolicy(model as ModelSpec<"openai-responses">), + }), // --- DeepSeek --- openAiCompletionsDescriptor("deepseek", "deepseek", "https://api.deepseek.com", { // Only ship the v4 family as built-ins; older deepseek-chat / deepseek-reasoner diff --git a/packages/catalog/test/generated-policies.test.ts b/packages/catalog/test/generated-policies.test.ts index 3c88063ba..f7ae34900 100644 --- a/packages/catalog/test/generated-policies.test.ts +++ b/packages/catalog/test/generated-policies.test.ts @@ -467,6 +467,49 @@ describe("generated model policies", () => { expect(models[2]?.applyPatchToolType).toBeUndefined(); expect(models[3]?.applyPatchToolType).toBeUndefined(); }); + + it("strips paid xAI Responses effort dials for off-allowlist reasoners", () => { + const models: ModelSpec<"openai-responses">[] = [ + createSpec({ + id: "grok-code-fast-1", + api: "openai-responses", + provider: "xai", + thinking: { mode: "effort", efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High] }, + }), + createSpec({ + id: "grok-4.5", + api: "openai-responses", + provider: "xai", + thinking: { mode: "effort", efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High] }, + }), + createSpec({ + id: "grok-code-fast-1", + api: "openai-responses", + provider: "openrouter", + thinking: { mode: "effort", efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High] }, + }), + ]; + + applyGeneratedModelPolicies(models); + + expect(models[0]?.thinking).toBeUndefined(); + expect(models[0]?.compat).toMatchObject({ + supportsReasoningEffort: false, + omitReasoningEffort: true, + reasoningEffortMap: { minimal: "low" }, + }); + expect(models[1]?.thinking?.efforts).toEqual([ + Effort.Minimal, + Effort.Low, + Effort.Medium, + Effort.High, + Effort.XHigh, + ]); + expect(models[1]?.compat?.supportsReasoningEffort).toBe(true); + // Non-xAI hosts are outside this policy — no baked no-dial compat. + expect(models[2]?.thinking).toBeDefined(); + expect(models[2]?.compat?.supportsReasoningEffort).toBeUndefined(); + }); }); describe("applyOllamaCloudOutputCap", () => { diff --git a/packages/catalog/test/xai-responses-thinking-policy.test.ts b/packages/catalog/test/xai-responses-thinking-policy.test.ts new file mode 100644 index 000000000..ed51571ed --- /dev/null +++ b/packages/catalog/test/xai-responses-thinking-policy.test.ts @@ -0,0 +1,122 @@ +import { describe, expect, it } from "bun:test"; +import { Effort } from "@oh-my-pi/pi-catalog/effort"; +import MODELS_JSON from "@oh-my-pi/pi-catalog/models.json" with { type: "json" }; +import { + MODELS_DEV_PROVIDER_DESCRIPTORS, + mapModelsDevToModels, +} from "@oh-my-pi/pi-catalog/provider-models/openai-compat"; +import type { ModelSpec } from "@oh-my-pi/pi-catalog/types"; +import { applyGeneratedModelPolicies } from "../scripts/generated-policies"; + +const XAI_MODELS_DEV_FIXTURE = { + xai: { + models: { + "grok-4.5": { + name: "Grok 4.5", + tool_call: true, + reasoning: true, + modalities: { input: ["text", "image"] }, + limit: { context: 500_000, output: 500_000 }, + cost: { input: 2, output: 6, cache_read: 0.3 }, + }, + "grok-code-fast-1": { + name: "Grok Code Fast 1", + tool_call: true, + reasoning: true, + modalities: { input: ["text"] }, + limit: { context: 256_000, output: 10_000 }, + cost: { input: 0.2, output: 1.5 }, + }, + "grok-build-0.1": { + name: "Grok Build 0.1", + tool_call: true, + reasoning: true, + modalities: { input: ["text", "image"] }, + limit: { context: 256_000, output: 256_000 }, + cost: { input: 0, output: 0 }, + }, + "grok-4.20-0309-reasoning": { + name: "Grok 4.20 (Reasoning)", + tool_call: true, + reasoning: true, + modalities: { input: ["text", "image"] }, + limit: { context: 2_000_000, output: 64_000 }, + cost: { input: 2, output: 6 }, + }, + "grok-2": { + name: "Grok 2", + tool_call: true, + reasoning: false, + modalities: { input: ["text"] }, + limit: { context: 131_072, output: 8192 }, + cost: { input: 2, output: 10 }, + }, + }, + }, +}; + +describe("paid xAI Responses thinking policy", () => { + it("bakes the effort-dial allowlist on stencil.so → openai-responses mapping", () => { + const mapped = mapModelsDevToModels(XAI_MODELS_DEV_FIXTURE, MODELS_DEV_PROVIDER_DESCRIPTORS).filter( + model => model.provider === "xai", + ); + const byId = Object.fromEntries(mapped.map(model => [model.id, model])); + + expect(byId["grok-4.5"]?.api).toBe("openai-responses"); + expect(byId["grok-4.5"]?.compat).toMatchObject({ + supportsReasoningEffort: true, + omitReasoningEffort: false, + reasoningEffortMap: { minimal: "low" }, + }); + for (const id of ["grok-code-fast-1", "grok-build-0.1", "grok-4.20-0309-reasoning"] as const) { + expect(byId[id]?.reasoning, id).toBe(true); + expect(byId[id]?.compat, id).toMatchObject({ + supportsReasoningEffort: false, + omitReasoningEffort: true, + reasoningEffortMap: { minimal: "low" }, + }); + } + expect(byId["grok-2"]?.compat).toMatchObject({ + supportsReasoningEffort: false, + omitReasoningEffort: true, + }); + }); + + it("strips stale thinking dials from off-allowlist paid xAI reasoners during generation", () => { + const mapped = mapModelsDevToModels(XAI_MODELS_DEV_FIXTURE, MODELS_DEV_PROVIDER_DESCRIPTORS).filter( + model => model.provider === "xai", + ); + // Snapshot-era Completions rows still carry a default effort ladder after the + // api flip; the generator must not re-emit that dial for Responses. + const snapshotStale = mapped.find(model => model.id === "grok-code-fast-1"); + expect(snapshotStale).toBeDefined(); + snapshotStale!.thinking = { mode: "effort", efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High] }; + + applyGeneratedModelPolicies(mapped); + const byId = Object.fromEntries(mapped.map(model => [model.id, model])); + + expect(byId["grok-4.5"]?.thinking).toEqual({ + mode: "effort", + efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh], + effortMap: { minimal: "low" }, + }); + for (const id of ["grok-code-fast-1", "grok-build-0.1", "grok-4.20-0309-reasoning"] as const) { + expect(byId[id]?.reasoning, id).toBe(true); + expect(byId[id]?.thinking, id).toBeUndefined(); + expect(byId[id]?.compat, id).toMatchObject({ supportsReasoningEffort: false }); + } + }); + + it("exports no-dial rows in the bundled models.json snapshot", () => { + const bundled = + (MODELS_JSON as unknown as Record>>).xai ?? {}; + for (const id of ["grok-code-fast-1", "grok-build-0.1", "grok-4.20-0309-reasoning"] as const) { + expect(bundled[id], `xai/${id} missing from models.json`).toBeDefined(); + expect(bundled[id]?.reasoning, id).toBe(true); + expect(bundled[id]?.thinking, id).toBeUndefined(); + expect(bundled[id]?.compat?.supportsReasoningEffort, id).toBe(false); + } + expect(bundled["grok-4.5"]?.thinking?.efforts).toContain(Effort.XHigh); + expect(bundled["grok-4.5"]?.compat?.supportsReasoningEffort).toBe(true); + }); +}); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 61ccd6e52..829b041b2 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -8,6 +8,9 @@ - Changed the default model for `XAI_API_KEY` (`xai`) from `grok-4-fast-non-reasoning` to `grok-4.5`. - Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`. - Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. +- Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`. +- Kept automatic model selection on paid `xai/grok-4.5` when only `XAI_API_KEY` is set, instead of preferring SuperGrok `xai-oauth/grok-4.5`. +- Stopped sending presence/frequency penalties and stop sequences to xAI reasoning models such as `grok-4.5`, which reject them. ## [17.3.4] - 2026-08-14 @@ -364,15 +367,6 @@ - Fixed issues with `/btw` branch promotion where branches could park behind active turns, cut from outdated session leaves, or leave rejected branch keys indistinguishable from composer input. - Fixed database bloat by ensuring archived main and nested session rows are properly cleaned up from `stats.db` during garbage collection. - Fixed startup hanging during local model discovery when a timed-out transport left its request pending, which blocked the CLI before OAuth login could finish ([#7482](https://github.com/can1357/oh-my-pi/issues/7482)). -### Changed - -- Routed paid xAI models (`XAI_API_KEY` / `xai/…`) through the Responses API used by SuperGrok OAuth instead of Chat Completions. -- Changed the default model for `XAI_API_KEY` (`xai`) from `grok-4-fast-non-reasoning` to `grok-4.5`. -- Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`. -- Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. -- Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`. -- Kept automatic model selection on paid `xai/grok-4.5` when only `XAI_API_KEY` is set, instead of preferring SuperGrok `xai-oauth/grok-4.5`. -- Stopped sending presence/frequency penalties and stop sequences to xAI reasoning models such as `grok-4.5`, which reject them. ## [17.2.5] - 2026-08-03 From 01db5b04eed851504b51acda8ed31d91831d7c89 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 2 Aug 2026 22:58:13 -0700 Subject: [PATCH 09/19] fix(catalog): omit unsupported reasoning.summary on paid xAI Responses First-party xAI /v1/responses rejects reasoning.summary. Bake supportsReasoningSummary=false for both xai and xai-oauth so paid grok-4.5 effort requests send only reasoning.effort, matching SuperGrok. --- docs/provider-endpoint-constraints.md | 3 ++- packages/ai/CHANGELOG.md | 4 ++++ packages/ai/src/providers/openai-responses.ts | 11 +++++------ packages/ai/test/xai-oauth-effort-strip.test.ts | 14 ++++++++++++-- packages/catalog/CHANGELOG.md | 17 +++++++++++++++++ packages/catalog/src/compat/openai.ts | 3 +++ packages/catalog/src/types.ts | 6 ++++++ packages/catalog/test/build.test.ts | 2 ++ 8 files changed, 51 insertions(+), 9 deletions(-) diff --git a/docs/provider-endpoint-constraints.md b/docs/provider-endpoint-constraints.md index 605eee899..2a3188f7b 100644 --- a/docs/provider-endpoint-constraints.md +++ b/docs/provider-endpoint-constraints.md @@ -240,7 +240,8 @@ Reasoning fields are not interchangeable. Both the paid API-key provider (`xai` / `XAI_API_KEY`) and SuperGrok OAuth (`xai-oauth`) chat over `https://api.x.ai/v1/responses`. Keep these independent: -- omit `reasoning.effort` +- omit `reasoning.effort` unless the model is on the Grok effort-capable allowlist +- omit `reasoning.summary` (the host rejects it; do not fall back to `"auto"`) - include `reasoning.encrypted_content` on the request - replay encrypted reasoning items on later turns diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 24e74327b..f182ad8ed 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -152,6 +152,10 @@ ### Fixed - Fixed an issue where Ollama requests without a user-role message would fail to generate output or silently fail with a misleading error. +### Fixed + +- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. +- Omitted unsupported `reasoning.summary` on paid xAI Responses requests (`xai/grok-4.5`), matching SuperGrok, so a thinking level no longer serializes `summary: "auto"`. ## [17.2.5] - 2026-08-03 diff --git a/packages/ai/src/providers/openai-responses.ts b/packages/ai/src/providers/openai-responses.ts index 21b587e7e..61055b2ab 100644 --- a/packages/ai/src/providers/openai-responses.ts +++ b/packages/ai/src/providers/openai-responses.ts @@ -1285,12 +1285,11 @@ export function buildParams( filterReasoningHistory: options?.filterReasoningHistory, omitReasoningEffort: options?.omitReasoningEffort, }); - const reasoningSummary = - model.provider === "xai-oauth" - ? options?.reasoning === undefined - ? undefined - : null - : options?.reasoningSummary; + const reasoningSummary = model.compat.supportsReasoningSummary + ? options?.reasoningSummary + : options?.reasoning === undefined + ? undefined + : null; applyResponsesCompatPolicy(params, reasoningPolicy, { reasoningSummary, forceReasoningOff: options?.forceReasoningOff, diff --git a/packages/ai/test/xai-oauth-effort-strip.test.ts b/packages/ai/test/xai-oauth-effort-strip.test.ts index 6d052f9d1..824f011d0 100644 --- a/packages/ai/test/xai-oauth-effort-strip.test.ts +++ b/packages/ai/test/xai-oauth-effort-strip.test.ts @@ -87,6 +87,16 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { expect(params.include).toContain("reasoning.encrypted_content"); }); + test("paid xai/grok-4.5 omits unsupported reasoning summary", () => { + const grok45 = getBundledModel<"openai-responses">("xai", "grok-4.5"); + if (!grok45) throw new Error("xai/grok-4.5 must be in bundled models.json"); + + const { params } = buildParams(grok45, singleUserContext, { reasoning: Effort.High }, undefined); + + expect(params.reasoning).toEqual({ effort: "high" }); + expect(params.include).toContain("reasoning.encrypted_content"); + }); + test("paid xai/grok-4.5 requests encrypted reasoning content", () => { const grok45 = getBundledModel<"openai-responses">("xai", "grok-4.5"); if (!grok45) throw new Error("xai/grok-4.5 must be in bundled models.json"); @@ -117,7 +127,7 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { const { params } = buildParams(grok45, singleUserContext, { reasoning: Effort.Minimal }, undefined); - expect(params.reasoning).toMatchObject({ effort: "low" }); + expect(params.reasoning).toEqual({ effort: "low" }); }); test("xai-oauth/grok-4.5 clamps minimal reasoning effort to low", () => { @@ -126,7 +136,7 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { const { params } = buildParams(grok45, singleUserContext, { reasoning: Effort.Minimal }, undefined); - expect(params.reasoning).toMatchObject({ effort: "low" }); + expect(params.reasoning).toEqual({ effort: "low" }); }); test("xai-oauth/grok-4.5 replays encrypted reasoning on the next turn", () => { diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 4abde4e75..e40d7b8ff 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -142,6 +142,23 @@ - Fixed dynamic discovery for the `deepseek-v4` model family (such as `deepseek-v4-flash-0731`) under `alibaba-token-plan` missing reasoning configuration and maximum thinking effort. - Fixed GitHub Copilot dynamic discovery retaining stale bundled prices for default-context models instead of using the provider's reported default-tier prices. +## [17.2.5] - 2026-08-03 +### Changed + +- Switched the paid xAI provider (`xai` / `XAI_API_KEY`) from Chat Completions to the OpenAI Responses API (`POST https://api.x.ai/v1/responses`), matching SuperGrok `xai-oauth`. Prompt-cache affinity (`x-grok-conv-id`), reasoning-effort allowlisting, and encrypted-reasoning replay rules are now shared across both first-party xAI hosts. +- Changed the paid xAI (`XAI_API_KEY`) default model from `grok-4-fast-non-reasoning` to `grok-4.5`. +- Changed the SuperGrok (`xai-oauth`) default model from `grok-4.3` to `grok-4.5`. +- Requested `reasoning.encrypted_content` on first-party xAI Responses calls (`xai` and `xai-oauth`) via the `include` parameter. +- Replayed xAI encrypted reasoning items on later Responses turns instead of stripping `type: "reasoning"` history. + +### Fixed + +- Invalidated stale paid-xAI model-cache rows written under Chat Completions so the Responses migration takes effect immediately instead of waiting for TTL expiry. +- Clamped paid xAI Responses `minimal` reasoning effort to `low` (same wire map as SuperGrok) so `xai/grok-4.5` does not 400. +- Suppressed presence/frequency penalties and stop sequences on xAI reasoning models so a configured `presencePenalty` does not 400 after the `grok-4.5` default change. +- Stopped emitting stale `thinking.efforts` dials on paid xAI Responses catalog rows that reject `reasoning.effort` (`grok-code-fast-1`, `grok-build-0.1`, `grok-4.20-0309-reasoning`, and other off-allowlist reasoners). +- Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. + ## [17.2.5] - 2026-08-03 ### Fixed diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index 4e6bfb4e7..12177dcfe 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -713,6 +713,8 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol // Copilot host under a different provider id still clamps. supportsImageDetailOriginal: !isXaiHost && !modelMatchesHost({ provider: spec.provider, baseUrl }, "githubCopilot"), + // api.x.ai rejects `reasoning.summary` (SuperGrok and paid key alike). + supportsReasoningSummary: !isXaiHost, reasoningEffortMap: isXaiHost ? { ...XAI_RESPONSES_REASONING_EFFORT_MAP } : {}, supportsReasoningParams: true, // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit @@ -796,6 +798,7 @@ function pickResponsesOnly(compat: ResolvedOpenAIResponsesCompat): ResponsesOnly strictResponsesPairing: compat.strictResponsesPairing, supportsImageDetailOriginal: compat.supportsImageDetailOriginal, supportsObfuscationOptOut: compat.supportsObfuscationOptOut, + supportsReasoningSummary: compat.supportsReasoningSummary, isVercelGatewayHost: compat.isVercelGatewayHost, } satisfies ResponsesOnlyCompat; } diff --git a/packages/catalog/src/types.ts b/packages/catalog/src/types.ts index 648a628fa..c8320d108 100644 --- a/packages/catalog/src/types.ts +++ b/packages/catalog/src/types.ts @@ -719,6 +719,12 @@ export interface ResolvedOpenAIResponsesCompat extends ResolvedOpenAISharedCompa strictResponsesPairing: boolean; supportsImageDetailOriginal: boolean; supportsObfuscationOptOut: boolean; + /** + * Whether `reasoning.summary` may be sent. First-party xAI `/v1/responses` + * rejects the field; handlers pass `null` so the wire omits it instead of + * filling `"auto"`. + */ + supportsReasoningSummary: boolean; streamIdleTimeoutMs?: number; vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"]; /** The model sits behind Vercel AI Gateway's Responses endpoint. */ diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index 5ff48ae01..c46f98c09 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -268,6 +268,8 @@ describe("xAI Responses reasoning-effort suppression", () => { expect(oauth.reasoningEffortMap).toEqual({ minimal: "low" }); expect(paid.supportsPenaltyAndStopParams).toBe(false); expect(oauth.supportsPenaltyAndStopParams).toBe(false); + expect(paid.supportsReasoningSummary).toBe(false); + expect(oauth.supportsReasoningSummary).toBe(false); }); it("keeps penalty and stop params on non-reasoning paid xAI models", () => { From 4c150d2a050ed2b238b48a9366d4d5ce14541d32 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sat, 8 Aug 2026 17:04:22 -0700 Subject: [PATCH 10/19] docs: keep xAI changelog entries under Unreleased after 17.2.12 Rebase onto origin/main replayed the Unreleased bullets into already released 17.2.x sections. Move them back to the top. --- packages/ai/CHANGELOG.md | 5 +---- packages/catalog/CHANGELOG.md | 18 +----------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index f182ad8ed..e1fdf7ceb 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -90,6 +90,7 @@ - Fixed the AWS credential resolver ignoring `role_arn` profiles: shared-config role chaining (`source_profile` recursion, `web_identity_token_file`, `credential_source`) now resolves via STS `AssumeRole`/`AssumeRoleWithWebIdentity`, honoring `role_session_name`/`duration_seconds`/`external_id`, so Bedrock is detected on EKS/IRSA and multi-account setups instead of reporting "No models available" ([#8209](https://github.com/can1357/oh-my-pi/issues/8209)). - Fixed Bedrock availability being under-detected on Nitro/EKS hosts: the EC2 metadata probe now recognizes Nitro DMI markers (`board_asset_tag` instance ids, `Amazon EC2` vendor fields) in addition to the Xen `ec2` UUID prefix ([#8209](https://github.com/can1357/oh-my-pi/issues/8209)). - Fixed DeepSeek Responses targets (opencode-go) rejecting a thinking-mode continuation with `400 The reasoning_text in the thinking mode must be passed back to the API` after a prewalk hand-off plus mid-run compaction: the Responses input builder re-encoded replayed assistant turns without a reasoning item, so the request enabled reasoning but shipped no `reasoning_text`. The encoder now synthesizes a `reasoning_text` reasoning item for every replayed assistant turn when the target requires reasoning replay in thinking mode (`requiresReasoningContentForAllAssistantTurns` / `requiresReasoningContentForToolCalls`), mirroring the chat-completions `reasoning_content` safety net ([#8248](https://github.com/can1357/oh-my-pi/issues/8248)). +- Omitted unsupported `reasoning.summary` on paid xAI Responses requests (`xai/grok-4.5`), matching SuperGrok, so a thinking level no longer serializes `summary: "auto"`. ## [17.2.12] - 2026-08-08 @@ -152,10 +153,6 @@ ### Fixed - Fixed an issue where Ollama requests without a user-role message would fail to generate output or silently fail with a misleading error. -### Fixed - -- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. -- Omitted unsupported `reasoning.summary` on paid xAI Responses requests (`xai/grok-4.5`), matching SuperGrok, so a thinking level no longer serializes `summary: "auto"`. ## [17.2.5] - 2026-08-03 diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index e40d7b8ff..e9d23573a 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -93,6 +93,7 @@ - Marked `meta/muse-spark-1.2` and `muse-spark-1.2-contributor` as image-capable (`input: ["text", "image"]`) with the same Responses reasoning, thinking, and cost metadata as `muse-spark-1.1` (contributor uses its discounted 0.1/0.2 pricing), so `omp models` no longer lists them as text-only. - Fixed GLM-5.2 thinking levels across Baseten, CoreWeave, HuggingFace, and other uppercase-ID resellers, which were getting the generic `xhigh` effort ladder instead of the GLM-5.2-specific tiers. Also added Baseten `zai-org/GLM-5.2-Fast` and Fireworks `glm-5.2-fast` as reasoning models ([#8200](https://github.com/can1357/oh-my-pi/pull/8200) by [@jcfrancisco](https://github.com/jcfrancisco)). +- Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. ## [17.2.12] - 2026-08-08 @@ -142,23 +143,6 @@ - Fixed dynamic discovery for the `deepseek-v4` model family (such as `deepseek-v4-flash-0731`) under `alibaba-token-plan` missing reasoning configuration and maximum thinking effort. - Fixed GitHub Copilot dynamic discovery retaining stale bundled prices for default-context models instead of using the provider's reported default-tier prices. -## [17.2.5] - 2026-08-03 -### Changed - -- Switched the paid xAI provider (`xai` / `XAI_API_KEY`) from Chat Completions to the OpenAI Responses API (`POST https://api.x.ai/v1/responses`), matching SuperGrok `xai-oauth`. Prompt-cache affinity (`x-grok-conv-id`), reasoning-effort allowlisting, and encrypted-reasoning replay rules are now shared across both first-party xAI hosts. -- Changed the paid xAI (`XAI_API_KEY`) default model from `grok-4-fast-non-reasoning` to `grok-4.5`. -- Changed the SuperGrok (`xai-oauth`) default model from `grok-4.3` to `grok-4.5`. -- Requested `reasoning.encrypted_content` on first-party xAI Responses calls (`xai` and `xai-oauth`) via the `include` parameter. -- Replayed xAI encrypted reasoning items on later Responses turns instead of stripping `type: "reasoning"` history. - -### Fixed - -- Invalidated stale paid-xAI model-cache rows written under Chat Completions so the Responses migration takes effect immediately instead of waiting for TTL expiry. -- Clamped paid xAI Responses `minimal` reasoning effort to `low` (same wire map as SuperGrok) so `xai/grok-4.5` does not 400. -- Suppressed presence/frequency penalties and stop sequences on xAI reasoning models so a configured `presencePenalty` does not 400 after the `grok-4.5` default change. -- Stopped emitting stale `thinking.efforts` dials on paid xAI Responses catalog rows that reject `reasoning.effort` (`grok-code-fast-1`, `grok-build-0.1`, `grok-4.20-0309-reasoning`, and other off-allowlist reasoners). -- Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. - ## [17.2.5] - 2026-08-03 ### Fixed From 6c0f458279ffd132c3440ca99898c2f60368b979 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sat, 8 Aug 2026 17:07:48 -0700 Subject: [PATCH 11/19] fix(catalog): rebuild paid xAI Responses discovery after helper rename origin/main replaced createSimpleOpenAIResponsesOptions with the shared OpenAI-compatible manager builder. Point xaiModelManagerOptions at that same helper so XAI_API_KEY still discovers via /v1/responses. --- packages/catalog/src/provider-models/openai-compat.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index 1edf057e3..a87ad2bfe 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1259,7 +1259,14 @@ export interface XaiModelManagerConfig { export function xaiModelManagerOptions(config?: XaiModelManagerConfig): ModelManagerOptions<"openai-responses"> { return { - ...createSimpleOpenAIResponsesOptions("xai", "https://api.x.ai/v1", config), + ...createOpenAICompatibleModelManagerOptions({ + api: "openai-responses", + providerId: "xai", + defaultBaseUrl: "https://api.x.ai/v1", + config, + requireApiKey: true, + mapModel: mapWithBundledReference, + }), // Completions → Responses migration: a fresh authoritative cache written // by the old resolver stores `api: "openai-completions"` for these ids. // Without a drop list, `online-if-uncached` skips the network and From 09830d2bd657756d83cb5571930fa3fbfc86a3a4 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 9 Aug 2026 22:34:42 -0700 Subject: [PATCH 12/19] fix(catalog): drop unsupported xhigh effort from first-party Grok api.x.ai accepts low/medium/high (and clamps minimal to low). Stop advertising xhigh on paid xai and SuperGrok Responses rows, and map leftover xhigh/max requests to high. --- packages/ai/CHANGELOG.md | 2 +- packages/catalog/CHANGELOG.md | 3 +- packages/catalog/src/compat/openai.ts | 4 +- packages/catalog/src/model-thinking.ts | 6 + packages/catalog/src/models.json | 150 ++++++++++++------ .../src/provider-models/openai-compat.ts | 11 +- packages/catalog/test/build.test.ts | 4 +- .../catalog/test/generated-policies.test.ts | 8 +- packages/catalog/test/model-thinking.test.ts | 19 +++ .../xai-responses-thinking-policy.test.ts | 5 +- 10 files changed, 141 insertions(+), 71 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index e1fdf7ceb..ebb27ae90 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. +- Omitted unsupported `reasoning.summary` on paid xAI Responses requests (`xai/grok-4.5`), matching SuperGrok, so a thinking level no longer serializes `summary: "auto"`. ## [17.3.4] - 2026-08-14 @@ -90,7 +91,6 @@ - Fixed the AWS credential resolver ignoring `role_arn` profiles: shared-config role chaining (`source_profile` recursion, `web_identity_token_file`, `credential_source`) now resolves via STS `AssumeRole`/`AssumeRoleWithWebIdentity`, honoring `role_session_name`/`duration_seconds`/`external_id`, so Bedrock is detected on EKS/IRSA and multi-account setups instead of reporting "No models available" ([#8209](https://github.com/can1357/oh-my-pi/issues/8209)). - Fixed Bedrock availability being under-detected on Nitro/EKS hosts: the EC2 metadata probe now recognizes Nitro DMI markers (`board_asset_tag` instance ids, `Amazon EC2` vendor fields) in addition to the Xen `ec2` UUID prefix ([#8209](https://github.com/can1357/oh-my-pi/issues/8209)). - Fixed DeepSeek Responses targets (opencode-go) rejecting a thinking-mode continuation with `400 The reasoning_text in the thinking mode must be passed back to the API` after a prewalk hand-off plus mid-run compaction: the Responses input builder re-encoded replayed assistant turns without a reasoning item, so the request enabled reasoning but shipped no `reasoning_text`. The encoder now synthesizes a `reasoning_text` reasoning item for every replayed assistant turn when the target requires reasoning replay in thinking mode (`requiresReasoningContentForAllAssistantTurns` / `requiresReasoningContentForToolCalls`), mirroring the chat-completions `reasoning_content` safety net ([#8248](https://github.com/can1357/oh-my-pi/issues/8248)). -- Omitted unsupported `reasoning.summary` on paid xAI Responses requests (`xai/grok-4.5`), matching SuperGrok, so a thinking level no longer serializes `summary: "auto"`. ## [17.2.12] - 2026-08-08 diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index e9d23573a..556fd7fed 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -16,6 +16,8 @@ - Clamped paid xAI Responses `minimal` reasoning effort to `low` (same wire map as SuperGrok) so `xai/grok-4.5` does not 400. - Suppressed presence/frequency penalties and stop sequences on xAI reasoning models so a configured `presencePenalty` does not 400 after the `grok-4.5` default change. - Stopped emitting stale `thinking.efforts` dials on paid xAI Responses catalog rows that reject `reasoning.effort` (`grok-code-fast-1`, `grok-build-0.1`, `grok-4.20-0309-reasoning`, and other off-allowlist reasoners). +- Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. +- Removed unsupported `xhigh` (and `max`) thinking tiers from first-party Grok Responses catalog rows; leftover `xhigh`/`max` requests clamp to `high`. ## [17.3.4] - 2026-08-14 @@ -93,7 +95,6 @@ - Marked `meta/muse-spark-1.2` and `muse-spark-1.2-contributor` as image-capable (`input: ["text", "image"]`) with the same Responses reasoning, thinking, and cost metadata as `muse-spark-1.1` (contributor uses its discounted 0.1/0.2 pricing), so `omp models` no longer lists them as text-only. - Fixed GLM-5.2 thinking levels across Baseten, CoreWeave, HuggingFace, and other uppercase-ID resellers, which were getting the generic `xhigh` effort ladder instead of the GLM-5.2-specific tiers. Also added Baseten `zai-org/GLM-5.2-Fast` and Fireworks `glm-5.2-fast` as reasoning models ([#8200](https://github.com/can1357/oh-my-pi/pull/8200) by [@jcfrancisco](https://github.com/jcfrancisco)). -- Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. ## [17.2.12] - 2026-08-08 diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index 12177dcfe..285998ab1 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -177,9 +177,11 @@ const MIMO_REASONING_EFFORT_MAP: NonNullable xhigh: "high", }; -/** xAI `/v1/responses` accepts `low|medium|high` (and `xhigh` on some SKUs), not `minimal`. */ +/** xAI `/v1/responses` accepts `low|medium|high`, not `minimal`/`xhigh`/`max`. */ const XAI_RESPONSES_REASONING_EFFORT_MAP: NonNullable = { minimal: "low", + xhigh: "high", + max: "high", }; function mergeModelReasoningEffortMap( diff --git a/packages/catalog/src/model-thinking.ts b/packages/catalog/src/model-thinking.ts index be0ba1151..5e50224af 100644 --- a/packages/catalog/src/model-thinking.ts +++ b/packages/catalog/src/model-thinking.ts @@ -395,6 +395,12 @@ function getModelDefinedEfforts( // Baseten's gpt-oss router mirrors its GLM route: high/max only. return HIGH_MAX_REASONING_EFFORTS; } + // api.x.ai accepts `low|medium|high` (and clamps `minimal` → `low`). It + // rejects `xhigh`/`max`, so first-party Grok Responses rows must not + // advertise those tiers. + if (modelMatchesHost({ provider: spec.provider, baseUrl: spec.baseUrl ?? "" }, "xai")) { + return DEFAULT_REASONING_EFFORTS; + } return isOpenAICompatReasoningApi(spec.api) && (isMinimaxM2FamilyModelId(spec.id) || isOpenAIGptOssModelId(spec.id) || diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index c28196f6c..ec12711b7 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -103733,7 +103733,9 @@ "maxTokens": 8192, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103759,7 +103761,9 @@ "maxTokens": 8192, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103785,7 +103789,9 @@ "maxTokens": 8192, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103812,7 +103818,9 @@ "maxTokens": 4096, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103839,7 +103847,9 @@ "maxTokens": 4096, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103866,7 +103876,9 @@ "maxTokens": 4096, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103892,7 +103904,9 @@ "maxTokens": 8192, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103918,7 +103932,9 @@ "maxTokens": 8192, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103944,7 +103960,9 @@ "maxTokens": 8192, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -103970,7 +103988,9 @@ "maxTokens": 8192, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104000,8 +104020,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104009,7 +104028,9 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104039,8 +104060,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104048,7 +104068,9 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104078,8 +104100,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104087,7 +104108,9 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104117,8 +104140,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104126,7 +104148,9 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104152,7 +104176,9 @@ "maxTokens": 64000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104179,7 +104205,9 @@ "maxTokens": 30000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104206,7 +104234,9 @@ "maxTokens": 30000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104233,7 +104263,9 @@ "maxTokens": 30000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104260,7 +104292,9 @@ "maxTokens": 30000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104287,7 +104321,9 @@ "maxTokens": 30000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104314,7 +104350,9 @@ "maxTokens": 30000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104341,7 +104379,9 @@ "maxTokens": 30000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104368,7 +104408,9 @@ "maxTokens": 30000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104399,8 +104441,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104408,7 +104449,9 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104439,8 +104482,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104448,7 +104490,9 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104479,8 +104523,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104488,7 +104531,9 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104543,7 +104588,9 @@ "maxTokens": 4096, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104570,7 +104617,9 @@ "maxTokens": 256000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104596,7 +104645,9 @@ "maxTokens": 10000, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104623,7 +104674,9 @@ "maxTokens": 4096, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "supportsReasoningEffort": false, "omitReasoningEffort": true @@ -104719,8 +104772,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104764,8 +104816,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" @@ -104809,8 +104860,7 @@ "minimal", "low", "medium", - "high", - "xhigh" + "high" ], "effortMap": { "minimal": "low" diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index a87ad2bfe..ae5c60ec0 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1375,13 +1375,10 @@ function withXaiOAuthCompatDefaults(model: ModelSpec<"openai-responses">): Model return { ...model, compat }; } -// Hermes-agent parity: only the `minimal -> low` clamp is applied (see -// hermes-agent/agent/transports/codex.py:92 `_effort_clamp = {"minimal": -// "low"}`). Hermes sends `xhigh` to xAI verbatim and we match that contract -// — let xAI decide if the level is valid for the specific Grok model. -// `resolveModelThinking` folds this into `model.thinking.effortMap`, downstream -// of the omitReasoningEffort gate in pi-ai's stream.ts. -const XAI_REASONING_EFFORT_MAP = { minimal: "low" } as const; +// Hermes-agent parity for `minimal -> low` (see hermes-agent/agent/transports/ +// codex.py:92). api.x.ai also rejects `xhigh`/`max`, so those clamp to `high`. +// `resolveModelThinking` folds this into `model.thinking.effortMap`. +const XAI_REASONING_EFFORT_MAP = { minimal: "low", xhigh: "high", max: "high" } as const; /** * Bake first-party xAI Responses effort-dial metadata onto a catalog spec. diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index c46f98c09..51e802125 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -264,8 +264,8 @@ describe("xAI Responses reasoning-effort suppression", () => { expect(oauth.supportsImageDetailOriginal).toBe(false); expect(paid.supportsReasoningEffort).toBe(true); expect(oauth.supportsReasoningEffort).toBe(true); - expect(paid.reasoningEffortMap).toEqual({ minimal: "low" }); - expect(oauth.reasoningEffortMap).toEqual({ minimal: "low" }); + expect(paid.reasoningEffortMap).toEqual({ minimal: "low", xhigh: "high", max: "high" }); + expect(oauth.reasoningEffortMap).toEqual({ minimal: "low", xhigh: "high", max: "high" }); expect(paid.supportsPenaltyAndStopParams).toBe(false); expect(oauth.supportsPenaltyAndStopParams).toBe(false); expect(paid.supportsReasoningSummary).toBe(false); diff --git a/packages/catalog/test/generated-policies.test.ts b/packages/catalog/test/generated-policies.test.ts index f7ae34900..e26115a41 100644 --- a/packages/catalog/test/generated-policies.test.ts +++ b/packages/catalog/test/generated-policies.test.ts @@ -498,13 +498,7 @@ describe("generated model policies", () => { omitReasoningEffort: true, reasoningEffortMap: { minimal: "low" }, }); - expect(models[1]?.thinking?.efforts).toEqual([ - Effort.Minimal, - Effort.Low, - Effort.Medium, - Effort.High, - Effort.XHigh, - ]); + expect(models[1]?.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]); expect(models[1]?.compat?.supportsReasoningEffort).toBe(true); // Non-xAI hosts are outside this policy — no baked no-dial compat. expect(models[2]?.thinking).toBeDefined(); diff --git a/packages/catalog/test/model-thinking.test.ts b/packages/catalog/test/model-thinking.test.ts index e05ec9865..86fc64cb6 100644 --- a/packages/catalog/test/model-thinking.test.ts +++ b/packages/catalog/test/model-thinking.test.ts @@ -884,6 +884,25 @@ describe("model thinking runtime helpers", () => { expect(() => requireSupportedEffort(opus46, Effort.XHigh)).toThrow(/not supported/); }); + it("does not expose xhigh on first-party xAI Grok Responses models", () => { + const paid = createModel({ + id: "grok-4.5", + api: "openai-responses", + provider: "xai", + baseUrl: "https://api.x.ai/v1", + }); + const oauth = createModel({ + id: "grok-4.5", + api: "openai-responses", + provider: "xai-oauth", + baseUrl: "https://api.x.ai/v1", + }); + + expect(paid.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]); + expect(oauth.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]); + expect(() => requireSupportedEffort(paid, Effort.XHigh)).toThrow(/not supported/); + }); + it("rejects effort requests against un-built reasoning specs", () => { const spec = { id: "broken-reasoner", diff --git a/packages/catalog/test/xai-responses-thinking-policy.test.ts b/packages/catalog/test/xai-responses-thinking-policy.test.ts index ed51571ed..26b8824f4 100644 --- a/packages/catalog/test/xai-responses-thinking-policy.test.ts +++ b/packages/catalog/test/xai-responses-thinking-policy.test.ts @@ -97,7 +97,7 @@ describe("paid xAI Responses thinking policy", () => { expect(byId["grok-4.5"]?.thinking).toEqual({ mode: "effort", - efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh], + efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High], effortMap: { minimal: "low" }, }); for (const id of ["grok-code-fast-1", "grok-build-0.1", "grok-4.20-0309-reasoning"] as const) { @@ -116,7 +116,8 @@ describe("paid xAI Responses thinking policy", () => { expect(bundled[id]?.thinking, id).toBeUndefined(); expect(bundled[id]?.compat?.supportsReasoningEffort, id).toBe(false); } - expect(bundled["grok-4.5"]?.thinking?.efforts).toContain(Effort.XHigh); + expect(bundled["grok-4.5"]?.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]); + expect(bundled["grok-4.5"]?.thinking?.efforts).not.toContain(Effort.XHigh); expect(bundled["grok-4.5"]?.compat?.supportsReasoningEffort).toBe(true); }); }); From 76faddf886cf9430eeff4db8220d106a9e61a321 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 9 Aug 2026 22:44:06 -0700 Subject: [PATCH 13/19] fix(catalog): omit reasoningEffortMap on no-dial xAI rows The clamp map is only used when reasoning.effort is sent. Drop it from catalog rows that set omitReasoningEffort so the exported snapshot does not advertise a dead mapping. --- packages/catalog/CHANGELOG.md | 1 + .../catalog/scripts/generated-policies.ts | 2 +- packages/catalog/src/models.json | 142 ++---------------- .../src/provider-models/openai-compat.ts | 36 +++-- .../catalog/test/generated-policies.test.ts | 2 +- .../xai-responses-thinking-policy.test.ts | 3 +- 6 files changed, 35 insertions(+), 151 deletions(-) diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 556fd7fed..eab557183 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -18,6 +18,7 @@ - Stopped emitting stale `thinking.efforts` dials on paid xAI Responses catalog rows that reject `reasoning.effort` (`grok-code-fast-1`, `grok-build-0.1`, `grok-4.20-0309-reasoning`, and other off-allowlist reasoners). - Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. - Removed unsupported `xhigh` (and `max`) thinking tiers from first-party Grok Responses catalog rows; leftover `xhigh`/`max` requests clamp to `high`. +- Stopped baking `reasoningEffortMap` on first-party xAI catalog rows that omit `reasoning.effort` (`omitReasoningEffort: true`). ## [17.3.4] - 2026-08-14 diff --git a/packages/catalog/scripts/generated-policies.ts b/packages/catalog/scripts/generated-policies.ts index 0002dab3e..1d1737155 100644 --- a/packages/catalog/scripts/generated-policies.ts +++ b/packages/catalog/scripts/generated-policies.ts @@ -354,7 +354,7 @@ export function applyOllamaCloudOutputCap(models: ModelSpec[]): void { } function applyGeneratedModelPolicy(model: ModelSpec): void { - if (model.provider === "xai" && model.api === "openai-responses") { + if ((model.provider === "xai" || model.provider === "xai-oauth") && model.api === "openai-responses") { const updated = applyXaiResponsesThinkingPolicy(model as ModelSpec<"openai-responses">); model.compat = updated.compat; } diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index ec12711b7..dcb91951b 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -103732,11 +103732,6 @@ "contextWindow": 131072, "maxTokens": 8192, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103760,11 +103755,6 @@ "contextWindow": 131072, "maxTokens": 8192, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103788,11 +103778,6 @@ "contextWindow": 131072, "maxTokens": 8192, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103817,11 +103802,6 @@ "contextWindow": 8192, "maxTokens": 4096, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103846,11 +103826,6 @@ "contextWindow": 8192, "maxTokens": 4096, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103875,11 +103850,6 @@ "contextWindow": 8192, "maxTokens": 4096, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103903,11 +103873,6 @@ "contextWindow": 131072, "maxTokens": 8192, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103931,11 +103896,6 @@ "contextWindow": 131072, "maxTokens": 8192, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103959,11 +103919,6 @@ "contextWindow": 131072, "maxTokens": 8192, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -103987,11 +103942,6 @@ "contextWindow": 131072, "maxTokens": 8192, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104175,11 +104125,6 @@ "contextWindow": 256000, "maxTokens": 64000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104204,11 +104149,6 @@ "contextWindow": 2000000, "maxTokens": 30000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104233,11 +104173,6 @@ "contextWindow": 2000000, "maxTokens": 30000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104262,11 +104197,6 @@ "contextWindow": 2000000, "maxTokens": 30000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104291,11 +104221,6 @@ "contextWindow": 2000000, "maxTokens": 30000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104320,11 +104245,6 @@ "contextWindow": 1000000, "maxTokens": 30000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104349,11 +104269,6 @@ "contextWindow": 1000000, "maxTokens": 30000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104378,11 +104293,6 @@ "contextWindow": 2000000, "maxTokens": 30000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104407,11 +104317,6 @@ "contextWindow": 2000000, "maxTokens": 30000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104587,11 +104492,6 @@ "contextWindow": 131072, "maxTokens": 4096, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104616,11 +104516,6 @@ "contextWindow": 256000, "maxTokens": 256000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104644,11 +104539,6 @@ "contextWindow": 256000, "maxTokens": 10000, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104673,11 +104563,6 @@ "contextWindow": 8192, "maxTokens": 4096, "compat": { - "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" - }, "supportsReasoningEffort": false, "omitReasoningEffort": true } @@ -104706,9 +104591,6 @@ "supportsComputerUse": false, "supportsComputerUseConfig": false, "compat": { - "reasoningEffortMap": { - "minimal": "low" - }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, "supportsImageDetailOriginal": false, @@ -104738,9 +104620,6 @@ "supportsComputerUse": false, "supportsComputerUseConfig": false, "compat": { - "reasoningEffortMap": { - "minimal": "low" - }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, "supportsImageDetailOriginal": false, @@ -104782,7 +104661,9 @@ "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, @@ -104826,7 +104707,9 @@ "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, @@ -104870,7 +104753,9 @@ "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { - "minimal": "low" + "minimal": "low", + "xhigh": "high", + "max": "high" }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, @@ -104929,9 +104814,6 @@ "supportsComputerUse": false, "supportsComputerUseConfig": false, "compat": { - "reasoningEffortMap": { - "minimal": "low" - }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, "supportsImageDetailOriginal": false, @@ -104961,9 +104843,6 @@ "supportsComputerUse": false, "supportsComputerUseConfig": false, "compat": { - "reasoningEffortMap": { - "minimal": "low" - }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, "supportsImageDetailOriginal": false, @@ -104992,9 +104871,6 @@ "supportsComputerUse": false, "supportsComputerUseConfig": false, "compat": { - "reasoningEffortMap": { - "minimal": "low" - }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, "supportsImageDetailOriginal": false, diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index ae5c60ec0..0a2e12022 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1396,18 +1396,20 @@ const XAI_REASONING_EFFORT_MAP = { minimal: "low", xhigh: "high", max: "high" } */ export function applyXaiResponsesThinkingPolicy(model: ModelSpec<"openai-responses">): ModelSpec<"openai-responses"> { const effortCapable = model.compat?.supportsReasoningEffort ?? isGrokReasoningEffortCapable(model.id); - return { - ...model, - compat: { - ...(model.compat ?? {}), - reasoningEffortMap: { - ...XAI_REASONING_EFFORT_MAP, - ...(model.compat?.reasoningEffortMap ?? {}), - }, - supportsReasoningEffort: effortCapable, - omitReasoningEffort: model.compat?.omitReasoningEffort ?? !effortCapable, - }, + const compat = { + ...(model.compat ?? {}), + supportsReasoningEffort: effortCapable, + omitReasoningEffort: model.compat?.omitReasoningEffort ?? !effortCapable, }; + if (effortCapable) { + compat.reasoningEffortMap = { + ...XAI_REASONING_EFFORT_MAP, + ...(model.compat?.reasoningEffortMap ?? {}), + }; + } else { + delete compat.reasoningEffortMap; + } + return { ...model, compat }; } // xai-oauth's /v1/models exposes no per-request output limit on the OAuth @@ -1423,9 +1425,9 @@ export function applyXaiResponsesThinkingPolicy(model: ModelSpec<"openai-respons // reasoning metadata and fetchOpenAICompatibleModels defaults reasoning to // false). Caller supplies a `base` Model (either a freshly synthesised seed // or a dynamic-fetched entry); the helper layers curated fields on top. -// The `minimal -> low` effort clamp (XAI_REASONING_EFFORT_MAP) is always -// merged in so dynamic-fetched models — which arrive without curated -// compat keys — still get the clamp applyResponsesReasoningParams expects. +// The `minimal -> low` effort clamp (XAI_REASONING_EFFORT_MAP) is merged +// only onto effort-capable rows. Off-allowlist reasoners omit the wire +// param, so a map on those specs is dead weight. // The effort-dial pair (`supportsReasoningEffort`/`omitReasoningEffort`) is // authoritative: a stale flag on `base` (previous snapshot or dynamic fetch) // must not outlive an allowlist change in identity/family.ts. @@ -1436,13 +1438,17 @@ function mergeCuratedIntoModel( const effortCapable = curated.supportsReasoningEffort ?? isGrokReasoningEffortCapable(curated.id); const compat = { ...(base.compat ?? {}), - reasoningEffortMap: { ...XAI_REASONING_EFFORT_MAP, ...(base.compat?.reasoningEffortMap ?? {}) }, includeEncryptedReasoning: base.compat?.includeEncryptedReasoning ?? true, filterReasoningHistory: false, supportsImageDetailOriginal: base.compat?.supportsImageDetailOriginal ?? false, omitReasoningEffort: !effortCapable, supportsReasoningEffort: effortCapable, }; + if (effortCapable) { + compat.reasoningEffortMap = { ...XAI_REASONING_EFFORT_MAP, ...(base.compat?.reasoningEffortMap ?? {}) }; + } else { + delete compat.reasoningEffortMap; + } return { ...base, contextWindow: curated.contextWindow, diff --git a/packages/catalog/test/generated-policies.test.ts b/packages/catalog/test/generated-policies.test.ts index e26115a41..fcb33cdcf 100644 --- a/packages/catalog/test/generated-policies.test.ts +++ b/packages/catalog/test/generated-policies.test.ts @@ -496,8 +496,8 @@ describe("generated model policies", () => { expect(models[0]?.compat).toMatchObject({ supportsReasoningEffort: false, omitReasoningEffort: true, - reasoningEffortMap: { minimal: "low" }, }); + expect(models[0]?.compat).not.toHaveProperty("reasoningEffortMap"); expect(models[1]?.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]); expect(models[1]?.compat?.supportsReasoningEffort).toBe(true); // Non-xAI hosts are outside this policy — no baked no-dial compat. diff --git a/packages/catalog/test/xai-responses-thinking-policy.test.ts b/packages/catalog/test/xai-responses-thinking-policy.test.ts index 26b8824f4..0217a5271 100644 --- a/packages/catalog/test/xai-responses-thinking-policy.test.ts +++ b/packages/catalog/test/xai-responses-thinking-policy.test.ts @@ -73,13 +73,14 @@ describe("paid xAI Responses thinking policy", () => { expect(byId[id]?.compat, id).toMatchObject({ supportsReasoningEffort: false, omitReasoningEffort: true, - reasoningEffortMap: { minimal: "low" }, }); + expect(byId[id]?.compat, id).not.toHaveProperty("reasoningEffortMap"); } expect(byId["grok-2"]?.compat).toMatchObject({ supportsReasoningEffort: false, omitReasoningEffort: true, }); + expect(byId["grok-2"]?.compat).not.toHaveProperty("reasoningEffortMap"); }); it("strips stale thinking dials from off-allowlist paid xAI reasoners during generation", () => { From 86866b8bbfd8482fba562a7c1f63f51593509d6d Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 9 Aug 2026 22:55:10 -0700 Subject: [PATCH 14/19] fix(catalog): omit Responses penalties on all first-party xAI models xAI's /v1/responses rejects presence/frequency penalties for every Grok model, not only reasoners. Gate supportsPenaltyAndStopParams on isXaiHost so xai/grok-2 no longer serializes presence_penalty. --- docs/provider-endpoint-constraints.md | 1 + packages/ai/CHANGELOG.md | 1 + packages/ai/test/xai-oauth-effort-strip.test.ts | 10 ++++++++++ packages/catalog/CHANGELOG.md | 1 + packages/catalog/src/compat/openai.ts | 6 +++--- packages/catalog/src/types.ts | 5 +++-- packages/catalog/test/build.test.ts | 10 ++++++---- 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/provider-endpoint-constraints.md b/docs/provider-endpoint-constraints.md index 2a3188f7b..44dbabe05 100644 --- a/docs/provider-endpoint-constraints.md +++ b/docs/provider-endpoint-constraints.md @@ -242,6 +242,7 @@ Both the paid API-key provider (`xai` / `XAI_API_KEY`) and SuperGrok OAuth - omit `reasoning.effort` unless the model is on the Grok effort-capable allowlist - omit `reasoning.summary` (the host rejects it; do not fall back to `"auto"`) +- omit presence/frequency penalties (`/v1/responses` rejects them for every Grok model) - include `reasoning.encrypted_content` on the request - replay encrypted reasoning items on later turns diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index ebb27ae90..4bda16769 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -6,6 +6,7 @@ - Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. - Omitted unsupported `reasoning.summary` on paid xAI Responses requests (`xai/grok-4.5`), matching SuperGrok, so a thinking level no longer serializes `summary: "auto"`. +- Omitted presence/frequency penalties on all first-party xAI Responses models, including non-reasoning ids such as `xai/grok-2`. ## [17.3.4] - 2026-08-14 diff --git a/packages/ai/test/xai-oauth-effort-strip.test.ts b/packages/ai/test/xai-oauth-effort-strip.test.ts index 824f011d0..6b7f99d89 100644 --- a/packages/ai/test/xai-oauth-effort-strip.test.ts +++ b/packages/ai/test/xai-oauth-effort-strip.test.ts @@ -121,6 +121,16 @@ describe("xAI OAuth Responses reasoning payload (regression)", () => { expect(params.temperature).toBe(0.2); }); + test("paid xai/grok-2 omits presence_penalty on non-reasoning Responses models", () => { + const grok2 = getBundledModel<"openai-responses">("xai", "grok-2"); + if (!grok2) throw new Error("xai/grok-2 must be in bundled models.json"); + + const { params } = buildParams(grok2, singleUserContext, { presencePenalty: 0.4, temperature: 0.2 }, undefined); + + expect(params).not.toHaveProperty("presence_penalty"); + expect(params.temperature).toBe(0.2); + }); + test("paid xai/grok-4.5 clamps minimal reasoning effort to low", () => { const grok45 = getBundledModel<"openai-responses">("xai", "grok-4.5"); if (!grok45) throw new Error("xai/grok-4.5 must be in bundled models.json"); diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index eab557183..90c7bc374 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -19,6 +19,7 @@ - Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. - Removed unsupported `xhigh` (and `max`) thinking tiers from first-party Grok Responses catalog rows; leftover `xhigh`/`max` requests clamp to `high`. - Stopped baking `reasoningEffortMap` on first-party xAI catalog rows that omit `reasoning.effort` (`omitReasoningEffort: true`). +- Suppressed presence/frequency penalties on every first-party xAI Responses model, including non-reasoning ids such as `grok-2`; xAI's `/v1/responses` marks those fields unsupported. ## [17.3.4] - 2026-08-14 diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index 285998ab1..f68f6cb03 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -722,9 +722,9 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit // temperature/top_p/… with a 400 on every serving host (#5606). supportsSamplingParams: !isOpenAISamplingRestrictedModelId(id), - // xAI reasoning models 400 on presence/frequency penalties and stop - // (https://docs.x.ai/developers/model-capabilities/text/reasoning). - supportsPenaltyAndStopParams: !(isXaiHost && reasoningCapable), + // xAI `/v1/responses` rejects presence/frequency penalties for every + // model, not only reasoners (https://docs.x.ai/developers/rest-api-reference/inference/chat). + supportsPenaltyAndStopParams: !isXaiHost, thinkingFormat, reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat), omitReasoningEffort: false, diff --git a/packages/catalog/src/types.ts b/packages/catalog/src/types.ts index c8320d108..5760208f9 100644 --- a/packages/catalog/src/types.ts +++ b/packages/catalog/src/types.ts @@ -365,8 +365,9 @@ export interface OpenAICompat { supportsSamplingParams?: boolean; /** * Whether presence/frequency penalties and stop sequences may be sent. - * xAI reasoning models reject `presencePenalty`, `frequencyPenalty`, and - * `stop` with a 400. When unset, auto-detected. Default: true. + * First-party xAI `/v1/responses` rejects penalty fields for every model. + * xAI reasoning models also reject them (and `stop`) on chat completions. + * When unset, auto-detected. Default: true. */ supportsPenaltyAndStopParams?: boolean; /** Always send a max-token field when the caller did not provide one. Default: auto-detected (Kimi-family models derive TPM limits from max_tokens). */ diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index 51e802125..f5b8c4493 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -272,12 +272,14 @@ describe("xAI Responses reasoning-effort suppression", () => { expect(oauth.supportsReasoningSummary).toBe(false); }); - it("keeps penalty and stop params on non-reasoning paid xAI models", () => { - const compat = buildOpenAIResponsesCompat({ - ...grokResponsesSpec("grok-4-fast-non-reasoning", "xai"), + it("suppresses penalty params on every first-party xAI Responses model", () => { + const reasoning = buildOpenAIResponsesCompat(grokResponsesSpec("grok-4.5", "xai")); + const nonReasoning = buildOpenAIResponsesCompat({ + ...grokResponsesSpec("grok-2", "xai"), reasoning: false, }); - expect(compat.supportsPenaltyAndStopParams).toBe(true); + expect(reasoning.supportsPenaltyAndStopParams).toBe(false); + expect(nonReasoning.supportsPenaltyAndStopParams).toBe(false); }); it("omits effort for paid xai models off the Grok allowlist", () => { From 72168a69aead2ecb4c76db6ca056aadada19eb19 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 9 Aug 2026 23:04:54 -0700 Subject: [PATCH 15/19] fix(catalog): keep xhigh on Grok multi-agent Responses models grok-4.20-multi-agent uses reasoning.effort for agent count, and xhigh is the 16-agent mode. Leave that tier advertised and unmapped while Grok 4.5 still clamps leftover xhigh/max to high. --- packages/catalog/CHANGELOG.md | 2 +- packages/catalog/src/compat/openai.ts | 26 ++++++++++++++++--- packages/catalog/src/identity/family.ts | 9 +++++++ packages/catalog/src/model-thinking.ts | 8 +++--- packages/catalog/src/models.json | 14 +++++----- .../src/provider-models/openai-compat.ts | 16 +++++------- packages/catalog/test/build.test.ts | 3 +++ packages/catalog/test/identity-family.test.ts | 15 +++++++++++ packages/catalog/test/model-thinking.test.ts | 22 +++++++++++++++- .../xai-responses-thinking-policy.test.ts | 24 +++++++++++++++++ 10 files changed, 112 insertions(+), 27 deletions(-) diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 90c7bc374..71792a843 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -17,7 +17,7 @@ - Suppressed presence/frequency penalties and stop sequences on xAI reasoning models so a configured `presencePenalty` does not 400 after the `grok-4.5` default change. - Stopped emitting stale `thinking.efforts` dials on paid xAI Responses catalog rows that reject `reasoning.effort` (`grok-code-fast-1`, `grok-build-0.1`, `grok-4.20-0309-reasoning`, and other off-allowlist reasoners). - Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. -- Removed unsupported `xhigh` (and `max`) thinking tiers from first-party Grok Responses catalog rows; leftover `xhigh`/`max` requests clamp to `high`. +- Removed unsupported `xhigh` (and `max`) thinking tiers from first-party Grok 4.5 / 4.3 / 3-mini Responses rows; leftover `xhigh`/`max` requests clamp to `high`. `grok-4.20-multi-agent*` still advertises unmapped `xhigh` (16-agent mode). - Stopped baking `reasoningEffortMap` on first-party xAI catalog rows that omit `reasoning.effort` (`omitReasoningEffort: true`). - Suppressed presence/frequency penalties on every first-party xAI Responses model, including non-reasoning ids such as `grok-2`; xAI's `/v1/responses` marks those fields unsupported. diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index f68f6cb03..52817e433 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -15,6 +15,7 @@ import { isClaudeModelId, isDeepseekModelIdOrName, isGlm52ReasoningEffortModelId, + isGrokMultiAgentModelId, isGrokReasoningEffortCapable, isKimiK3ModelId, isKimiK26ModelId, @@ -177,13 +178,22 @@ const MIMO_REASONING_EFFORT_MAP: NonNullable xhigh: "high", }; -/** xAI `/v1/responses` accepts `low|medium|high`, not `minimal`/`xhigh`/`max`. */ -const XAI_RESPONSES_REASONING_EFFORT_MAP: NonNullable = { +/** Shared `minimal → low` clamp. Multi-agent Grok keeps `xhigh` unmapped. */ +const XAI_RESPONSES_MINIMAL_EFFORT_MAP: NonNullable = { + minimal: "low", +}; +/** Non-multi-agent Grok: leftover `xhigh`/`max` clamp to `high` (4.5 has no 16-agent mode). */ +const XAI_RESPONSES_CLAMPED_EFFORT_MAP: NonNullable = { minimal: "low", xhigh: "high", max: "high", }; +/** Wire effort remap for first-party xAI Responses. */ +export function xaiResponsesReasoningEffortMap(modelId: string): NonNullable { + return isGrokMultiAgentModelId(modelId) ? XAI_RESPONSES_MINIMAL_EFFORT_MAP : XAI_RESPONSES_CLAMPED_EFFORT_MAP; +} + function mergeModelReasoningEffortMap( compat: ResolvedOpenAISharedCompat, modelId: string, @@ -717,7 +727,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol !isXaiHost && !modelMatchesHost({ provider: spec.provider, baseUrl }, "githubCopilot"), // api.x.ai rejects `reasoning.summary` (SuperGrok and paid key alike). supportsReasoningSummary: !isXaiHost, - reasoningEffortMap: isXaiHost ? { ...XAI_RESPONSES_REASONING_EFFORT_MAP } : {}, + reasoningEffortMap: isXaiHost ? { ...xaiResponsesReasoningEffortMap(id) } : {}, supportsReasoningParams: true, // OpenAI proprietary reasoning models (o-series, gpt-5+) reject explicit // temperature/top_p/… with a 400 on every serving host (#5606). @@ -781,7 +791,15 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol }; applyCompatOverrides(compat, spec.compat); if (isXaiHost) { - compat.reasoningEffortMap = { ...XAI_RESPONSES_REASONING_EFFORT_MAP, ...compat.reasoningEffortMap }; + const canonical = xaiResponsesReasoningEffortMap(id); + compat.reasoningEffortMap = { ...compat.reasoningEffortMap, ...canonical }; + // Multi-agent Grok advertises unmapped `xhigh`; drop a stale clamp from + // previous snapshots so 16-agent mode is not rewritten to `high`. + for (const key of ["xhigh", "max"] as const) { + if (!(key in canonical)) { + delete compat.reasoningEffortMap[key]; + } + } } if (spec.compat?.reasoningDisableMode === undefined) { compat.reasoningDisableMode = resolveReasoningDisableMode(compat.thinkingFormat); diff --git a/packages/catalog/src/identity/family.ts b/packages/catalog/src/identity/family.ts index 289f80821..216293b0b 100644 --- a/packages/catalog/src/identity/family.ts +++ b/packages/catalog/src/identity/family.ts @@ -123,6 +123,15 @@ export const isGrokReasoningEffortCapable = memo((modelId: string): boolean => { return GROK_EFFORT_CAPABLE_PREFIXES.some(prefix => bare.startsWith(prefix)); }); +/** + * `grok-4.20-multi-agent*` uses `reasoning.effort` to pick agent count + * (`xhigh` is the 16-agent mode). Other first-party Grok effort SKUs stay on + * `low|medium|high` (https://docs.x.ai/developers/model-capabilities/text/reasoning). + */ +export const isGrokMultiAgentModelId = memo((modelId: string): boolean => { + return bareModelId(modelId).trim().toLowerCase().startsWith("grok-4.20-multi-agent"); +}); + /** * MiniMax M2-generation family (M2, M2.1, M2.5, M2.7, including `-highspeed`/ * `-lightning`/`-her`/`-turbo` variants, dotless aliases like `minimax-m21`, diff --git a/packages/catalog/src/model-thinking.ts b/packages/catalog/src/model-thinking.ts index 5e50224af..0f042bf45 100644 --- a/packages/catalog/src/model-thinking.ts +++ b/packages/catalog/src/model-thinking.ts @@ -26,6 +26,7 @@ import { isDeepseekModelIdOrName, isDeepseekV4FlashModelId, isGlm52ReasoningEffortModelId, + isGrokMultiAgentModelId, isKimiK3ModelId, isMimoModelIdOrName, isMinimaxM2FamilyModelId, @@ -395,11 +396,10 @@ function getModelDefinedEfforts( // Baseten's gpt-oss router mirrors its GLM route: high/max only. return HIGH_MAX_REASONING_EFFORTS; } - // api.x.ai accepts `low|medium|high` (and clamps `minimal` → `low`). It - // rejects `xhigh`/`max`, so first-party Grok Responses rows must not - // advertise those tiers. + // First-party Grok: `grok-4.20-multi-agent*` advertises `xhigh` (16-agent + // mode). Other effort-capable SKUs stay on `minimal/low/medium/high`. if (modelMatchesHost({ provider: spec.provider, baseUrl: spec.baseUrl ?? "" }, "xai")) { - return DEFAULT_REASONING_EFFORTS; + return isGrokMultiAgentModelId(spec.id) ? DEFAULT_REASONING_EFFORTS_WITH_XHIGH : DEFAULT_REASONING_EFFORTS; } return isOpenAICompatReasoningApi(spec.api) && (isMinimaxM2FamilyModelId(spec.id) || diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index dcb91951b..78597d4d2 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -104346,7 +104346,8 @@ "minimal", "low", "medium", - "high" + "high", + "xhigh" ], "effortMap": { "minimal": "low" @@ -104354,9 +104355,7 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" + "minimal": "low" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104651,7 +104650,8 @@ "minimal", "low", "medium", - "high" + "high", + "xhigh" ], "effortMap": { "minimal": "low" @@ -104661,9 +104661,7 @@ "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" + "minimal": "low" }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index 0a2e12022..a5629279e 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1,5 +1,6 @@ import { USER_AGENT } from "@oh-my-pi/pi-utils"; import * as logger from "@oh-my-pi/pi-utils/logger"; +import { xaiResponsesReasoningEffortMap } from "../compat/openai"; import { DEFAULT_OPENAI_COMPATIBLE_DISCOVERY_TIMEOUT_MS, fetchOpenAICompatibleModels, @@ -1376,9 +1377,9 @@ function withXaiOAuthCompatDefaults(model: ModelSpec<"openai-responses">): Model } // Hermes-agent parity for `minimal -> low` (see hermes-agent/agent/transports/ -// codex.py:92). api.x.ai also rejects `xhigh`/`max`, so those clamp to `high`. +// codex.py:92). Multi-agent Grok keeps `xhigh` unmapped (agent-count mode); +// other first-party SKUs clamp leftover `xhigh`/`max` to `high`. // `resolveModelThinking` folds this into `model.thinking.effortMap`. -const XAI_REASONING_EFFORT_MAP = { minimal: "low", xhigh: "high", max: "high" } as const; /** * Bake first-party xAI Responses effort-dial metadata onto a catalog spec. @@ -1402,10 +1403,7 @@ export function applyXaiResponsesThinkingPolicy(model: ModelSpec<"openai-respons omitReasoningEffort: model.compat?.omitReasoningEffort ?? !effortCapable, }; if (effortCapable) { - compat.reasoningEffortMap = { - ...XAI_REASONING_EFFORT_MAP, - ...(model.compat?.reasoningEffortMap ?? {}), - }; + compat.reasoningEffortMap = { ...xaiResponsesReasoningEffortMap(model.id) }; } else { delete compat.reasoningEffortMap; } @@ -1425,7 +1423,7 @@ export function applyXaiResponsesThinkingPolicy(model: ModelSpec<"openai-respons // reasoning metadata and fetchOpenAICompatibleModels defaults reasoning to // false). Caller supplies a `base` Model (either a freshly synthesised seed // or a dynamic-fetched entry); the helper layers curated fields on top. -// The `minimal -> low` effort clamp (XAI_REASONING_EFFORT_MAP) is merged +// The effort remap from {@link xaiResponsesReasoningEffortMap} is merged // only onto effort-capable rows. Off-allowlist reasoners omit the wire // param, so a map on those specs is dead weight. // The effort-dial pair (`supportsReasoningEffort`/`omitReasoningEffort`) is @@ -1445,7 +1443,7 @@ function mergeCuratedIntoModel( supportsReasoningEffort: effortCapable, }; if (effortCapable) { - compat.reasoningEffortMap = { ...XAI_REASONING_EFFORT_MAP, ...(base.compat?.reasoningEffortMap ?? {}) }; + compat.reasoningEffortMap = { ...xaiResponsesReasoningEffortMap(curated.id) }; } else { delete compat.reasoningEffortMap; } @@ -1550,7 +1548,7 @@ export function buildXaiOAuthStaticSeed(baseUrl?: string): ModelSpec<"openai-res cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: curated.contextWindow, maxTokens: curated.contextWindow, - compat: { reasoningEffortMap: XAI_REASONING_EFFORT_MAP }, + compat: { reasoningEffortMap: xaiResponsesReasoningEffortMap(curated.id) }, }; return mergeCuratedIntoModel(base, curated); }); diff --git a/packages/catalog/test/build.test.ts b/packages/catalog/test/build.test.ts index f5b8c4493..db22929aa 100644 --- a/packages/catalog/test/build.test.ts +++ b/packages/catalog/test/build.test.ts @@ -266,6 +266,9 @@ describe("xAI Responses reasoning-effort suppression", () => { expect(oauth.supportsReasoningEffort).toBe(true); expect(paid.reasoningEffortMap).toEqual({ minimal: "low", xhigh: "high", max: "high" }); expect(oauth.reasoningEffortMap).toEqual({ minimal: "low", xhigh: "high", max: "high" }); + expect( + buildOpenAIResponsesCompat(grokResponsesSpec("grok-4.20-multi-agent-0309", "xai")).reasoningEffortMap, + ).toEqual({ minimal: "low" }); expect(paid.supportsPenaltyAndStopParams).toBe(false); expect(oauth.supportsPenaltyAndStopParams).toBe(false); expect(paid.supportsReasoningSummary).toBe(false); diff --git a/packages/catalog/test/identity-family.test.ts b/packages/catalog/test/identity-family.test.ts index ffba1b095..c920b04c2 100644 --- a/packages/catalog/test/identity-family.test.ts +++ b/packages/catalog/test/identity-family.test.ts @@ -5,6 +5,7 @@ import { isGeminiModelId, isGlmVisionModelId, isGrokModelId, + isGrokMultiAgentModelId, isGrokReasoningEffortCapable, isKimiK26ModelId, isKimiModelId, @@ -349,3 +350,17 @@ describe("isGrokReasoningEffortCapable", () => { expect(isGrokReasoningEffortCapable("")).toBe(false); }); }); + +describe("isGrokMultiAgentModelId", () => { + test("matches grok-4.20-multi-agent SKUs across namespaces", () => { + expect(isGrokMultiAgentModelId("grok-4.20-multi-agent")).toBe(true); + expect(isGrokMultiAgentModelId("grok-4.20-multi-agent-0309")).toBe(true); + expect(isGrokMultiAgentModelId("xai/grok-4.20-multi-agent-beta-latest")).toBe(true); + }); + + test("rejects other Grok ids", () => { + expect(isGrokMultiAgentModelId("grok-4.5")).toBe(false); + expect(isGrokMultiAgentModelId("grok-4.20-0309-reasoning")).toBe(false); + expect(isGrokMultiAgentModelId("")).toBe(false); + }); +}); diff --git a/packages/catalog/test/model-thinking.test.ts b/packages/catalog/test/model-thinking.test.ts index 86fc64cb6..e11c512a9 100644 --- a/packages/catalog/test/model-thinking.test.ts +++ b/packages/catalog/test/model-thinking.test.ts @@ -884,7 +884,7 @@ describe("model thinking runtime helpers", () => { expect(() => requireSupportedEffort(opus46, Effort.XHigh)).toThrow(/not supported/); }); - it("does not expose xhigh on first-party xAI Grok Responses models", () => { + it("does not expose xhigh on first-party Grok 4.5 Responses models", () => { const paid = createModel({ id: "grok-4.5", api: "openai-responses", @@ -903,6 +903,26 @@ describe("model thinking runtime helpers", () => { expect(() => requireSupportedEffort(paid, Effort.XHigh)).toThrow(/not supported/); }); + it("exposes xhigh on first-party Grok multi-agent Responses models", () => { + const paid = createModel({ + id: "grok-4.20-multi-agent-beta-latest", + api: "openai-responses", + provider: "xai", + baseUrl: "https://api.x.ai/v1", + }); + const oauth = createModel({ + id: "grok-4.20-multi-agent-0309", + api: "openai-responses", + provider: "xai-oauth", + baseUrl: "https://api.x.ai/v1", + }); + + expect(paid.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh]); + expect(oauth.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh]); + expect(requireSupportedEffort(paid, Effort.XHigh)).toBe(Effort.XHigh); + expect(paid.compat.reasoningEffortMap?.xhigh).toBeUndefined(); + }); + it("rejects effort requests against un-built reasoning specs", () => { const spec = { id: "broken-reasoner", diff --git a/packages/catalog/test/xai-responses-thinking-policy.test.ts b/packages/catalog/test/xai-responses-thinking-policy.test.ts index 0217a5271..06637f1b6 100644 --- a/packages/catalog/test/xai-responses-thinking-policy.test.ts +++ b/packages/catalog/test/xai-responses-thinking-policy.test.ts @@ -51,6 +51,14 @@ const XAI_MODELS_DEV_FIXTURE = { limit: { context: 131_072, output: 8192 }, cost: { input: 2, output: 10 }, }, + "grok-4.20-multi-agent-beta-latest": { + name: "Grok 4.20 (Multi-Agent)", + tool_call: true, + reasoning: true, + modalities: { input: ["text"] }, + limit: { context: 2_000_000, output: 64_000 }, + cost: { input: 2, output: 6 }, + }, }, }, }; @@ -101,6 +109,18 @@ describe("paid xAI Responses thinking policy", () => { efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High], effortMap: { minimal: "low" }, }); + expect(byId["grok-4.20-multi-agent-beta-latest"]?.thinking).toEqual({ + mode: "effort", + efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh], + effortMap: { minimal: "low" }, + }); + expect(byId["grok-4.20-multi-agent-beta-latest"]?.compat).toMatchObject({ + supportsReasoningEffort: true, + reasoningEffortMap: { minimal: "low" }, + }); + expect(byId["grok-4.20-multi-agent-beta-latest"]?.compat).not.toMatchObject({ + reasoningEffortMap: { xhigh: "high" }, + }); for (const id of ["grok-code-fast-1", "grok-build-0.1", "grok-4.20-0309-reasoning"] as const) { expect(byId[id]?.reasoning, id).toBe(true); expect(byId[id]?.thinking, id).toBeUndefined(); @@ -120,5 +140,9 @@ describe("paid xAI Responses thinking policy", () => { expect(bundled["grok-4.5"]?.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]); expect(bundled["grok-4.5"]?.thinking?.efforts).not.toContain(Effort.XHigh); expect(bundled["grok-4.5"]?.compat?.supportsReasoningEffort).toBe(true); + expect(bundled["grok-4.20-multi-agent-beta-latest"]?.thinking?.efforts).toContain(Effort.XHigh); + expect(bundled["grok-4.20-multi-agent-beta-latest"]?.compat).not.toMatchObject({ + reasoningEffortMap: { xhigh: "high" }, + }); }); }); From d14c028aeec4f36b277d075b1c1493a624ea27c8 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Sun, 9 Aug 2026 23:12:07 -0700 Subject: [PATCH 16/19] fix(ai): allow explicit xai-oauth selectors with XAI_API_KEY Keep hasAuth() dedicated so SuperGrok is not auto-selected from a paid key. Explicit preflight uses hasResolvableAuth() so xai-oauth/grok-4.5 can still borrow XAI_API_KEY. --- packages/ai/CHANGELOG.md | 2 +- packages/ai/src/auth-storage.ts | 18 ++++++++++++++++-- packages/ai/test/xai-login.test.ts | 3 +++ packages/coding-agent/CHANGELOG.md | 2 +- .../coding-agent/src/config/model-registry.ts | 9 +++++++-- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 4bda16769..feedcbda3 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. +- Stopped treating `XAI_API_KEY` as SuperGrok (`xai-oauth`) sign-in for availability, so paid-key-only setups default to `xai/grok-4.5` instead of the zero-cost SuperGrok catalog path. Explicit `xai-oauth/…` selectors still accept the paid key via the existing env fallback. - Omitted unsupported `reasoning.summary` on paid xAI Responses requests (`xai/grok-4.5`), matching SuperGrok, so a thinking level no longer serializes `summary: "auto"`. - Omitted presence/frequency penalties on all first-party xAI Responses models, including non-reasoning ids such as `xai/grok-2`. diff --git a/packages/ai/src/auth-storage.ts b/packages/ai/src/auth-storage.ts index 538a2ce78..4748f28b1 100644 --- a/packages/ai/src/auth-storage.ts +++ b/packages/ai/src/auth-storage.ts @@ -2680,8 +2680,10 @@ export class AuthStorage { } /** - * Check if any form of auth is configured for a provider. - * Unlike getApiKey(), this doesn't refresh OAuth tokens. + * Dedicated auth for default-model availability (picker / `getAvailable`). + * Unlike {@link getApiKey}, this does not refresh OAuth tokens, and unlike + * {@link hasResolvableAuth} it ignores cross-provider env aliases so + * `XAI_API_KEY` does not auto-select SuperGrok (`xai-oauth`). */ hasAuth(provider: string): boolean { if (this.#runtimeOverrides.has(provider)) return true; @@ -2692,6 +2694,18 @@ export class AuthStorage { return false; } + /** + * Whether a request could resolve a key for this provider, including + * cross-provider env aliases (`xai-oauth` borrowing `XAI_API_KEY`). + * Use this for explicit model preflight (`xai-oauth/grok-4.5`); use + * {@link hasAuth} for auto-availability so the default picker stays on + * paid `xai` when only `XAI_API_KEY` is set. + */ + hasResolvableAuth(provider: string): boolean { + if (this.hasAuth(provider)) return true; + return Boolean(getEnvApiKey(provider)); + } + /** * True iff a dedicated, non-env credential source is configured for this * provider — i.e. anything in the cascade EXCEPT `getEnvApiKey(provider)`. diff --git a/packages/ai/test/xai-login.test.ts b/packages/ai/test/xai-login.test.ts index 0d0ad19f4..3571b531a 100644 --- a/packages/ai/test/xai-login.test.ts +++ b/packages/ai/test/xai-login.test.ts @@ -39,6 +39,9 @@ describe("xAI API login wiring", () => { try { expect(storage.hasAuth("xai")).toBe(true); expect(storage.hasAuth("xai-oauth")).toBe(false); + expect(storage.hasResolvableAuth("xai")).toBe(true); + expect(storage.hasResolvableAuth("xai-oauth")).toBe(true); + expect(getEnvApiKey("xai-oauth")).toBe("xai-env-key"); expect(storage.getCredentialOrigin("xai")).toEqual({ kind: "env", envVar: "XAI_API_KEY" }); expect(storage.getCredentialOrigin("xai-oauth")).toBeUndefined(); } finally { diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 829b041b2..f61f4fc3d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -9,7 +9,7 @@ - Changed the default model for SuperGrok OAuth (`xai-oauth`) from `grok-4.3` to `grok-4.5`. - Included `reasoning.encrypted_content` in Responses `include` for paid xAI and SuperGrok OAuth models. - Replayed encrypted xAI reasoning on follow-up Responses turns for `xai` and `xai-oauth`. -- Kept automatic model selection on paid `xai/grok-4.5` when only `XAI_API_KEY` is set, instead of preferring SuperGrok `xai-oauth/grok-4.5`. +- Kept automatic model selection on paid `xai/grok-4.5` when only `XAI_API_KEY` is set, instead of preferring SuperGrok `xai-oauth/grok-4.5`. Explicit `xai-oauth/grok-4.5` still works with that paid key. - Stopped sending presence/frequency penalties and stop sequences to xAI reasoning models such as `grok-4.5`, which reject them. ## [17.3.4] - 2026-08-14 diff --git a/packages/coding-agent/src/config/model-registry.ts b/packages/coding-agent/src/config/model-registry.ts index f2dd6f1b5..50aa6e713 100644 --- a/packages/coding-agent/src/config/model-registry.ts +++ b/packages/coding-agent/src/config/model-registry.ts @@ -1667,17 +1667,22 @@ export class ModelRegistry { * * Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as * configured by its presence alone — the program is NOT executed — and OAuth - * tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the + * tokens are NOT refreshed (`authStorage.hasResolvableAuth`). This is what keeps the * model-switch pre-flight off the event loop's hot path; the real key * (command execution + OAuth refresh) is resolved lazily per request via * {@link ModelRegistry.resolver}. + * + * Cross-provider env aliases count here (`xai-oauth` can borrow `XAI_API_KEY`) + * so an explicit `xai-oauth/…` selector does not fail with "No API key". + * Default-model availability still uses {@link AuthStorage.hasAuth}, which + * ignores that alias so SuperGrok is not auto-selected from a paid key. */ hasConfiguredAuth(model: Model): boolean { const keyConfig = this.#customProviderApiKeys.get(model.provider); return ( isCommandConfigValue(keyConfig) || this.#keylessProviders.has(model.provider) || - this.authStorage.hasAuth(model.provider) + this.authStorage.hasResolvableAuth(model.provider) ); } From 02eaee09bd07be30ed3ee2bb2f78616c93cfe242 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Fri, 14 Aug 2026 22:04:53 -0700 Subject: [PATCH 17/19] style(catalog): sort generated-policies imports after rebase Keep applyXaiResponsesThinkingPolicy in the existing openai-compat import so biome organizeImports stays clean on origin/main. --- packages/catalog/scripts/generated-policies.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog/scripts/generated-policies.ts b/packages/catalog/scripts/generated-policies.ts index 1d1737155..f0ab0dc1b 100644 --- a/packages/catalog/scripts/generated-policies.ts +++ b/packages/catalog/scripts/generated-policies.ts @@ -21,8 +21,8 @@ import { resolveModelThinking } from "../src/model-thinking"; import { isOllamaCloudOutputCapped, OLLAMA_CLOUD_MAX_OUTPUT_TOKENS } from "../src/provider-models/ollama"; import { ALIBABA_TOKEN_PLAN_STATIC_MODELS, - OPENAI_GPT_56_LONG_CONTEXT_COSTS, applyXaiResponsesThinkingPolicy, + OPENAI_GPT_56_LONG_CONTEXT_COSTS, resolveWaferServerlessThinkingFormat, } from "../src/provider-models/openai-compat"; import type { Api, LongContextTokenCost, Model, ModelSpec } from "../src/types"; From a7ac5d9fd356d6d4c90e9fa6b825288c1d9edbe2 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Fri, 14 Aug 2026 22:07:10 -0700 Subject: [PATCH 18/19] fix(catalog): route main's grok-4.6 through first-party Responses origin/main added grok-4.6 as Chat Completions on paid xai and as an uncurated SuperGrok row. Keep the Responses migration complete by allowlisting the id, seeding xai-oauth, and baking the same 4-tier effort map as grok-4.5. --- packages/catalog/CHANGELOG.md | 1 + packages/catalog/src/identity/family.ts | 8 +++- packages/catalog/src/models.json | 40 ++++++++++++++++--- .../src/provider-models/openai-compat.ts | 1 + packages/catalog/test/identity-family.test.ts | 1 + 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 71792a843..25cac79f6 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -20,6 +20,7 @@ - Removed unsupported `xhigh` (and `max`) thinking tiers from first-party Grok 4.5 / 4.3 / 3-mini Responses rows; leftover `xhigh`/`max` requests clamp to `high`. `grok-4.20-multi-agent*` still advertises unmapped `xhigh` (16-agent mode). - Stopped baking `reasoningEffortMap` on first-party xAI catalog rows that omit `reasoning.effort` (`omitReasoningEffort: true`). - Suppressed presence/frequency penalties on every first-party xAI Responses model, including non-reasoning ids such as `grok-2`; xAI's `/v1/responses` marks those fields unsupported. +- Routed `grok-4.6` (added on main) through first-party xAI Responses with the same 4-tier effort allowlist as `grok-4.5`, instead of leaving the paid row on Chat Completions. ## [17.3.4] - 2026-08-14 diff --git a/packages/catalog/src/identity/family.ts b/packages/catalog/src/identity/family.ts index 216293b0b..25b185a00 100644 --- a/packages/catalog/src/identity/family.ts +++ b/packages/catalog/src/identity/family.ts @@ -110,7 +110,13 @@ export const isGrokModelId = memo((modelId: string): boolean => { return /(?:^|[./_-])grok(?:[-.]|$)/i.test(modelId); }); -const GROK_EFFORT_CAPABLE_PREFIXES = ["grok-3-mini", "grok-4.20-multi-agent", "grok-4.3", "grok-4.5"] as const; +const GROK_EFFORT_CAPABLE_PREFIXES = [ + "grok-3-mini", + "grok-4.20-multi-agent", + "grok-4.3", + "grok-4.5", + "grok-4.6", +] as const; /** * Grok SKUs that expose the wire `reasoning.effort` dial. Other Grok reasoners diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index 78597d4d2..e75455e90 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -104446,7 +104446,7 @@ "grok-4.6": { "id": "grok-4.6", "name": "Grok 4.6", - "api": "openai-completions", + "api": "openai-responses", "provider": "xai", "baseUrl": "https://api.x.ai/v1", "reasoning": true, @@ -104469,7 +104469,19 @@ "low", "medium", "high" - ] + ], + "effortMap": { + "minimal": "low" + } + }, + "compat": { + "reasoningEffortMap": { + "minimal": "low", + "xhigh": "high", + "max": "high" + }, + "supportsReasoningEffort": true, + "omitReasoningEffort": false } }, "grok-beta": { @@ -104781,13 +104793,31 @@ }, "contextWindow": 500000, "maxTokens": 500000, + "thinking": { + "mode": "effort", + "efforts": [ + "minimal", + "low", + "medium", + "high" + ], + "effortMap": { + "minimal": "low" + } + }, "supportsComputerUse": false, "supportsComputerUseConfig": false, "compat": { - "includeEncryptedReasoning": false, - "filterReasoningHistory": true, + "reasoningEffortMap": { + "minimal": "low", + "xhigh": "high", + "max": "high" + }, + "includeEncryptedReasoning": true, + "filterReasoningHistory": false, "supportsImageDetailOriginal": false, - "omitReasoningEffort": true + "omitReasoningEffort": false, + "supportsReasoningEffort": true } }, "grok-build": { diff --git a/packages/catalog/src/provider-models/openai-compat.ts b/packages/catalog/src/provider-models/openai-compat.ts index a5629279e..347811244 100644 --- a/packages/catalog/src/provider-models/openai-compat.ts +++ b/packages/catalog/src/provider-models/openai-compat.ts @@ -1332,6 +1332,7 @@ export const XAI_OAUTH_CURATED_MODELS: readonly XAICuratedModel[] = [ }, { id: "grok-4.3", contextWindow: 1_000_000, name: "Grok 4.3", input: ["text", "image"] }, { id: "grok-4.5", contextWindow: 500_000, name: "Grok 4.5", input: ["text", "image"] }, + { id: "grok-4.6", contextWindow: 500_000, name: "Grok 4.6", input: ["text", "image"] }, // grok-4.20-multi-agent-0309 is text-only per the bundled catalog; omit `input` for the default. { id: "grok-4.20-multi-agent-0309", contextWindow: 2_000_000, name: "Grok 4.20 (Multi-Agent)" }, { diff --git a/packages/catalog/test/identity-family.test.ts b/packages/catalog/test/identity-family.test.ts index c920b04c2..1cfc0d2fe 100644 --- a/packages/catalog/test/identity-family.test.ts +++ b/packages/catalog/test/identity-family.test.ts @@ -340,6 +340,7 @@ describe("isGrokReasoningEffortCapable", () => { expect(isGrokReasoningEffortCapable("grok-4.20-multi-agent")).toBe(true); expect(isGrokReasoningEffortCapable("xai-oauth/grok-4.3")).toBe(true); expect(isGrokReasoningEffortCapable("xai-oauth/grok-4.5")).toBe(true); + expect(isGrokReasoningEffortCapable("xai-oauth/grok-4.6")).toBe(true); expect(isGrokReasoningEffortCapable("openrouter/xai/grok-3-mini")).toBe(true); }); From d02aa3c85f2f834f456e8a59ca1dad1221c2daac Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Fri, 14 Aug 2026 22:41:26 -0700 Subject: [PATCH 19/19] fix(catalog): advertise xhigh on first-party grok-4.6 Responses xAI documents xhigh on grok-4.6. Keep 4.5/4.3/3-mini on the 4-tier ladder and leave leftover xhigh unmapped for 4.6, matching multi-agent. --- packages/catalog/CHANGELOG.md | 4 ++-- packages/catalog/src/compat/openai.ts | 12 +++++----- packages/catalog/src/identity/family.ts | 14 ++++++++++- packages/catalog/src/model-thinking.ts | 8 +++---- packages/catalog/src/models.json | 14 +++++------ packages/catalog/test/identity-family.test.ts | 19 +++++++++++++++ packages/catalog/test/model-thinking.test.ts | 20 ++++++++++++++++ .../xai-responses-thinking-policy.test.ts | 24 +++++++++++++++++++ 8 files changed, 94 insertions(+), 21 deletions(-) diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 25cac79f6..0ed710b09 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -17,10 +17,10 @@ - Suppressed presence/frequency penalties and stop sequences on xAI reasoning models so a configured `presencePenalty` does not 400 after the `grok-4.5` default change. - Stopped emitting stale `thinking.efforts` dials on paid xAI Responses catalog rows that reject `reasoning.effort` (`grok-code-fast-1`, `grok-build-0.1`, `grok-4.20-0309-reasoning`, and other off-allowlist reasoners). - Marked first-party xAI Responses hosts (`xai` and `xai-oauth`) as not supporting `reasoning.summary`, so paid `xai/grok-4.5` effort requests omit the unsupported field instead of sending `summary: "auto"`. -- Removed unsupported `xhigh` (and `max`) thinking tiers from first-party Grok 4.5 / 4.3 / 3-mini Responses rows; leftover `xhigh`/`max` requests clamp to `high`. `grok-4.20-multi-agent*` still advertises unmapped `xhigh` (16-agent mode). +- Removed unsupported `xhigh` (and `max`) thinking tiers from first-party Grok 4.5 / 4.3 / 3-mini Responses rows; leftover `xhigh`/`max` requests clamp to `high`. `grok-4.6*` and `grok-4.20-multi-agent*` advertise unmapped `xhigh`. - Stopped baking `reasoningEffortMap` on first-party xAI catalog rows that omit `reasoning.effort` (`omitReasoningEffort: true`). - Suppressed presence/frequency penalties on every first-party xAI Responses model, including non-reasoning ids such as `grok-2`; xAI's `/v1/responses` marks those fields unsupported. -- Routed `grok-4.6` (added on main) through first-party xAI Responses with the same 4-tier effort allowlist as `grok-4.5`, instead of leaving the paid row on Chat Completions. +- Routed `grok-4.6` (added on main) through first-party xAI Responses and advertised its documented `xhigh` effort tier (4.5 stays 4-tier). ## [17.3.4] - 2026-08-14 diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index 52817e433..2eeec29f1 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -15,8 +15,8 @@ import { isClaudeModelId, isDeepseekModelIdOrName, isGlm52ReasoningEffortModelId, - isGrokMultiAgentModelId, isGrokReasoningEffortCapable, + isGrokXHighEffortCapable, isKimiK3ModelId, isKimiK26ModelId, isKimiModelId, @@ -178,11 +178,11 @@ const MIMO_REASONING_EFFORT_MAP: NonNullable xhigh: "high", }; -/** Shared `minimal → low` clamp. Multi-agent Grok keeps `xhigh` unmapped. */ +/** Shared `minimal → low` clamp. xhigh-capable Grok keeps `xhigh` unmapped. */ const XAI_RESPONSES_MINIMAL_EFFORT_MAP: NonNullable = { minimal: "low", }; -/** Non-multi-agent Grok: leftover `xhigh`/`max` clamp to `high` (4.5 has no 16-agent mode). */ +/** Grok 4.5 / 4.3 / 3-mini: leftover `xhigh`/`max` clamp to `high`. */ const XAI_RESPONSES_CLAMPED_EFFORT_MAP: NonNullable = { minimal: "low", xhigh: "high", @@ -191,7 +191,7 @@ const XAI_RESPONSES_CLAMPED_EFFORT_MAP: NonNullable { - return isGrokMultiAgentModelId(modelId) ? XAI_RESPONSES_MINIMAL_EFFORT_MAP : XAI_RESPONSES_CLAMPED_EFFORT_MAP; + return isGrokXHighEffortCapable(modelId) ? XAI_RESPONSES_MINIMAL_EFFORT_MAP : XAI_RESPONSES_CLAMPED_EFFORT_MAP; } function mergeModelReasoningEffortMap( @@ -793,8 +793,8 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol if (isXaiHost) { const canonical = xaiResponsesReasoningEffortMap(id); compat.reasoningEffortMap = { ...compat.reasoningEffortMap, ...canonical }; - // Multi-agent Grok advertises unmapped `xhigh`; drop a stale clamp from - // previous snapshots so 16-agent mode is not rewritten to `high`. + // xhigh-capable Grok advertises unmapped `xhigh`; drop a stale clamp + // from previous snapshots so 4.6 / 16-agent mode is not rewritten to `high`. for (const key of ["xhigh", "max"] as const) { if (!(key in canonical)) { delete compat.reasoningEffortMap[key]; diff --git a/packages/catalog/src/identity/family.ts b/packages/catalog/src/identity/family.ts index 25b185a00..c322405fd 100644 --- a/packages/catalog/src/identity/family.ts +++ b/packages/catalog/src/identity/family.ts @@ -132,12 +132,24 @@ export const isGrokReasoningEffortCapable = memo((modelId: string): boolean => { /** * `grok-4.20-multi-agent*` uses `reasoning.effort` to pick agent count * (`xhigh` is the 16-agent mode). Other first-party Grok effort SKUs stay on - * `low|medium|high` (https://docs.x.ai/developers/model-capabilities/text/reasoning). + * `low|medium|high` unless {@link isGrokXHighEffortCapable} (currently + * `grok-4.6*` plus multi-agent). + * https://docs.x.ai/developers/model-capabilities/text/reasoning */ export const isGrokMultiAgentModelId = memo((modelId: string): boolean => { return bareModelId(modelId).trim().toLowerCase().startsWith("grok-4.20-multi-agent"); }); +/** + * First-party Grok SKUs whose Responses wire accepts `reasoning.effort: "xhigh"`. + * `grok-4.6*` documents xhigh as a reasoning depth; multi-agent uses it as + * 16-agent mode. `grok-4.5` / `grok-4.3` / `grok-3-mini` do not. + */ +export const isGrokXHighEffortCapable = memo((modelId: string): boolean => { + if (isGrokMultiAgentModelId(modelId)) return true; + return bareModelId(modelId).trim().toLowerCase().startsWith("grok-4.6"); +}); + /** * MiniMax M2-generation family (M2, M2.1, M2.5, M2.7, including `-highspeed`/ * `-lightning`/`-her`/`-turbo` variants, dotless aliases like `minimax-m21`, diff --git a/packages/catalog/src/model-thinking.ts b/packages/catalog/src/model-thinking.ts index 0f042bf45..652a4f361 100644 --- a/packages/catalog/src/model-thinking.ts +++ b/packages/catalog/src/model-thinking.ts @@ -26,7 +26,7 @@ import { isDeepseekModelIdOrName, isDeepseekV4FlashModelId, isGlm52ReasoningEffortModelId, - isGrokMultiAgentModelId, + isGrokXHighEffortCapable, isKimiK3ModelId, isMimoModelIdOrName, isMinimaxM2FamilyModelId, @@ -396,10 +396,10 @@ function getModelDefinedEfforts( // Baseten's gpt-oss router mirrors its GLM route: high/max only. return HIGH_MAX_REASONING_EFFORTS; } - // First-party Grok: `grok-4.20-multi-agent*` advertises `xhigh` (16-agent - // mode). Other effort-capable SKUs stay on `minimal/low/medium/high`. + // First-party Grok: `grok-4.6*` and `grok-4.20-multi-agent*` advertise + // `xhigh`. Other effort-capable SKUs stay on `minimal/low/medium/high`. if (modelMatchesHost({ provider: spec.provider, baseUrl: spec.baseUrl ?? "" }, "xai")) { - return isGrokMultiAgentModelId(spec.id) ? DEFAULT_REASONING_EFFORTS_WITH_XHIGH : DEFAULT_REASONING_EFFORTS; + return isGrokXHighEffortCapable(spec.id) ? DEFAULT_REASONING_EFFORTS_WITH_XHIGH : DEFAULT_REASONING_EFFORTS; } return isOpenAICompatReasoningApi(spec.api) && (isMinimaxM2FamilyModelId(spec.id) || diff --git a/packages/catalog/src/models.json b/packages/catalog/src/models.json index e75455e90..641503f7f 100644 --- a/packages/catalog/src/models.json +++ b/packages/catalog/src/models.json @@ -104468,7 +104468,8 @@ "minimal", "low", "medium", - "high" + "high", + "xhigh" ], "effortMap": { "minimal": "low" @@ -104476,9 +104477,7 @@ }, "compat": { "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" + "minimal": "low" }, "supportsReasoningEffort": true, "omitReasoningEffort": false @@ -104799,7 +104798,8 @@ "minimal", "low", "medium", - "high" + "high", + "xhigh" ], "effortMap": { "minimal": "low" @@ -104809,9 +104809,7 @@ "supportsComputerUseConfig": false, "compat": { "reasoningEffortMap": { - "minimal": "low", - "xhigh": "high", - "max": "high" + "minimal": "low" }, "includeEncryptedReasoning": true, "filterReasoningHistory": false, diff --git a/packages/catalog/test/identity-family.test.ts b/packages/catalog/test/identity-family.test.ts index 1cfc0d2fe..52b57dedd 100644 --- a/packages/catalog/test/identity-family.test.ts +++ b/packages/catalog/test/identity-family.test.ts @@ -7,6 +7,7 @@ import { isGrokModelId, isGrokMultiAgentModelId, isGrokReasoningEffortCapable, + isGrokXHighEffortCapable, isKimiK26ModelId, isKimiModelId, isMinimaxM2FamilyModelId, @@ -361,7 +362,25 @@ describe("isGrokMultiAgentModelId", () => { test("rejects other Grok ids", () => { expect(isGrokMultiAgentModelId("grok-4.5")).toBe(false); + expect(isGrokMultiAgentModelId("grok-4.6")).toBe(false); expect(isGrokMultiAgentModelId("grok-4.20-0309-reasoning")).toBe(false); expect(isGrokMultiAgentModelId("")).toBe(false); }); }); + +describe("isGrokXHighEffortCapable", () => { + test("matches grok-4.6 and multi-agent SKUs across namespaces", () => { + expect(isGrokXHighEffortCapable("grok-4.6")).toBe(true); + expect(isGrokXHighEffortCapable("xai/grok-4.6")).toBe(true); + expect(isGrokXHighEffortCapable("xai-oauth/grok-4.6")).toBe(true); + expect(isGrokXHighEffortCapable("grok-4.20-multi-agent-0309")).toBe(true); + }); + + test("rejects Grok SKUs that clamp leftover xhigh to high", () => { + expect(isGrokXHighEffortCapable("grok-4.5")).toBe(false); + expect(isGrokXHighEffortCapable("grok-4.3")).toBe(false); + expect(isGrokXHighEffortCapable("grok-3-mini")).toBe(false); + expect(isGrokXHighEffortCapable("grok-build")).toBe(false); + expect(isGrokXHighEffortCapable("")).toBe(false); + }); +}); diff --git a/packages/catalog/test/model-thinking.test.ts b/packages/catalog/test/model-thinking.test.ts index e11c512a9..e55e767c0 100644 --- a/packages/catalog/test/model-thinking.test.ts +++ b/packages/catalog/test/model-thinking.test.ts @@ -903,6 +903,26 @@ describe("model thinking runtime helpers", () => { expect(() => requireSupportedEffort(paid, Effort.XHigh)).toThrow(/not supported/); }); + it("exposes xhigh on first-party Grok 4.6 Responses models", () => { + const paid = createModel({ + id: "grok-4.6", + api: "openai-responses", + provider: "xai", + baseUrl: "https://api.x.ai/v1", + }); + const oauth = createModel({ + id: "grok-4.6", + api: "openai-responses", + provider: "xai-oauth", + baseUrl: "https://api.x.ai/v1", + }); + + expect(paid.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh]); + expect(oauth.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh]); + expect(requireSupportedEffort(paid, Effort.XHigh)).toBe(Effort.XHigh); + expect(paid.compat.reasoningEffortMap?.xhigh).toBeUndefined(); + }); + it("exposes xhigh on first-party Grok multi-agent Responses models", () => { const paid = createModel({ id: "grok-4.20-multi-agent-beta-latest", diff --git a/packages/catalog/test/xai-responses-thinking-policy.test.ts b/packages/catalog/test/xai-responses-thinking-policy.test.ts index 06637f1b6..2843552dc 100644 --- a/packages/catalog/test/xai-responses-thinking-policy.test.ts +++ b/packages/catalog/test/xai-responses-thinking-policy.test.ts @@ -19,6 +19,14 @@ const XAI_MODELS_DEV_FIXTURE = { limit: { context: 500_000, output: 500_000 }, cost: { input: 2, output: 6, cache_read: 0.3 }, }, + "grok-4.6": { + name: "Grok 4.6", + tool_call: true, + reasoning: true, + modalities: { input: ["text", "image"] }, + limit: { context: 500_000, output: 500_000 }, + cost: { input: 2, output: 6, cache_read: 0.5 }, + }, "grok-code-fast-1": { name: "Grok Code Fast 1", tool_call: true, @@ -109,6 +117,18 @@ describe("paid xAI Responses thinking policy", () => { efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High], effortMap: { minimal: "low" }, }); + expect(byId["grok-4.6"]?.thinking).toEqual({ + mode: "effort", + efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh], + effortMap: { minimal: "low" }, + }); + expect(byId["grok-4.6"]?.compat).toMatchObject({ + supportsReasoningEffort: true, + reasoningEffortMap: { minimal: "low" }, + }); + expect(byId["grok-4.6"]?.compat).not.toMatchObject({ + reasoningEffortMap: { xhigh: "high" }, + }); expect(byId["grok-4.20-multi-agent-beta-latest"]?.thinking).toEqual({ mode: "effort", efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh], @@ -140,6 +160,10 @@ describe("paid xAI Responses thinking policy", () => { expect(bundled["grok-4.5"]?.thinking?.efforts).toEqual([Effort.Minimal, Effort.Low, Effort.Medium, Effort.High]); expect(bundled["grok-4.5"]?.thinking?.efforts).not.toContain(Effort.XHigh); expect(bundled["grok-4.5"]?.compat?.supportsReasoningEffort).toBe(true); + expect(bundled["grok-4.6"]?.thinking?.efforts).toContain(Effort.XHigh); + expect(bundled["grok-4.6"]?.compat).not.toMatchObject({ + reasoningEffortMap: { xhigh: "high" }, + }); expect(bundled["grok-4.20-multi-agent-beta-latest"]?.thinking?.efforts).toContain(Effort.XHigh); expect(bundled["grok-4.20-multi-agent-beta-latest"]?.compat).not.toMatchObject({ reasoningEffortMap: { xhigh: "high" },