fix(ai): preserve Z.AI thinking content

closes #6083
This commit is contained in:
Vegard Stikbakke
2026-06-29 08:40:20 +02:00
parent 54113731b2
commit b91bdd5a3e
3 changed files with 64 additions and 3 deletions
+1
View File
@@ -15,6 +15,7 @@
- Fixed `streamSimple()` to send a context-aware max-token cap so providers that count input and output against one context window do not reject long requests ([#5595](https://github.com/earendil-works/pi/issues/5595)).
- Fixed OpenAI Responses streams to preserve reasoning replay state when output items finish out of order ([#6009](https://github.com/earendil-works/pi/issues/6009)).
- Fixed retry classification for provider errors that explicitly tell callers to retry the request ([#6019](https://github.com/earendil-works/pi/issues/6019)).
- Fixed Z.AI preserved thinking requests to send `thinking.clear_thinking: false` when thinking is enabled, allowing replayed `reasoning_content` to participate in provider caching ([#6083](https://github.com/earendil-works/pi/issues/6083)).
## [0.80.2] - 2026-06-23
+2 -2
View File
@@ -593,10 +593,10 @@ function buildParams(
if (compat.thinkingFormat === "zai" && model.reasoning) {
const zaiParams = params as Omit<typeof params, "reasoning_effort"> & {
thinking?: { type: "enabled" | "disabled" };
thinking?: { type: "enabled" | "disabled"; clear_thinking?: boolean };
reasoning_effort?: string;
};
zaiParams.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
zaiParams.thinking = options?.reasoningEffort ? { type: "enabled", clear_thinking: false } : { type: "disabled" };
if (options?.reasoningEffort && compat.supportsReasoningEffort) {
const mappedEffort = model.thinkingLevelMap?.[options.reasoningEffort];
const effort = mappedEffort === undefined ? options.reasoningEffort : mappedEffort;
@@ -343,11 +343,71 @@ describe("openai-completions tool_choice", () => {
).result();
const params = (payload ?? mockState.lastParams) as { thinking?: unknown; reasoning_effort?: string };
expect(params.thinking).toEqual({ type: "enabled" });
expect(params.thinking).toEqual({ type: "enabled", clear_thinking: false });
expect(params.reasoning_effort).toBe(testCase.effort);
}
});
it("preserves z.ai thinking when replaying reasoning_content", async () => {
const model = getModel("zai", "glm-5.2")!;
const assistantMessage: AssistantMessage = {
role: "assistant",
api: "openai-completions",
provider: "zai",
model: "glm-5.2",
content: [
{ type: "thinking", thinking: "prior reasoning", thinkingSignature: "reasoning_content" },
{ type: "toolCall", id: "call_1", name: "read", arguments: { path: "README.md" } },
],
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: Date.now(),
};
const toolResult: ToolResultMessage = {
role: "toolResult",
toolCallId: "call_1",
toolName: "read",
content: [{ type: "text", text: "contents" }],
isError: false,
timestamp: Date.now(),
};
let payload: unknown;
await streamSimple(
model,
{
messages: [
{ role: "user", content: "Read README.md", timestamp: Date.now() },
assistantMessage,
toolResult,
{ role: "user", content: "Continue", timestamp: Date.now() },
],
},
{
apiKey: "test",
reasoning: "high",
onPayload: (params: unknown) => {
payload = params;
},
},
).result();
const params = (payload ?? mockState.lastParams) as {
messages?: Array<Record<string, unknown>>;
thinking?: unknown;
};
const replayedAssistant = params.messages?.find((message) => message.role === "assistant");
expect(replayedAssistant).toMatchObject({ reasoning_content: "prior reasoning" });
expect(params.thinking).toEqual({ type: "enabled", clear_thinking: false });
});
it("omits z.ai GLM-5.2 reasoning_effort when thinking is off", async () => {
const model = getModel("zai", "glm-5.2")!;
let payload: unknown;