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