diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index ec294b77..707f112a 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -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 diff --git a/packages/ai/src/api/openai-completions.ts b/packages/ai/src/api/openai-completions.ts index caeb8638..7da118f7 100644 --- a/packages/ai/src/api/openai-completions.ts +++ b/packages/ai/src/api/openai-completions.ts @@ -593,10 +593,10 @@ function buildParams( if (compat.thinkingFormat === "zai" && model.reasoning) { const zaiParams = params as Omit & { - 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; diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts index 927e34b3..ce23bdbe 100644 --- a/packages/ai/test/openai-completions-tool-choice.test.ts +++ b/packages/ai/test/openai-completions-tool-choice.test.ts @@ -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>; + 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;