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.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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<string, unknown> | 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<string, unknown> | undefined;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user