fix(deepseek): backfill v4 assistant reasoning replay

This commit is contained in:
Peter Steinberger
2026-04-28 10:07:15 +01:00
parent 0876ff481b
commit 62997f7fce
9 changed files with 176 additions and 15 deletions

View File

@@ -2,6 +2,7 @@ import type { StreamFn } from "@mariozechner/pi-agent-core";
import { describe, expect, it } from "vitest";
import {
buildCopilotDynamicHeaders,
createDeepSeekV4OpenAICompatibleThinkingWrapper,
createHtmlEntityToolCallArgumentDecodingWrapper,
createAnthropicThinkingPrefillPayloadWrapper,
createPayloadPatchStreamWrapper,
@@ -104,6 +105,37 @@ describe("isOpenAICompatibleThinkingEnabled", () => {
});
});
describe("createDeepSeekV4OpenAICompatibleThinkingWrapper", () => {
it("backfills reasoning_content on every replayed assistant message when thinking is enabled", () => {
const payload = {
messages: [
{ role: "user", content: "read file" },
{ role: "assistant", tool_calls: [{ id: "call_1", name: "read" }] },
{ role: "tool", content: "ok" },
{ role: "assistant", content: "done" },
{ role: "assistant", content: "kept", reasoning_content: "native reasoning" },
],
};
const baseStreamFn: StreamFn = (_model, _context, options) => {
options?.onPayload?.(payload as never, _model as never);
return {} as ReturnType<StreamFn>;
};
const wrapped = createDeepSeekV4OpenAICompatibleThinkingWrapper({
baseStreamFn,
thinkingLevel: "high",
shouldPatchModel: () => true,
});
void wrapped?.({} as never, {} as never, {});
expect(payload.messages[0]).not.toHaveProperty("reasoning_content");
expect(payload.messages[1]).toHaveProperty("reasoning_content", "");
expect(payload.messages[2]).not.toHaveProperty("reasoning_content");
expect(payload.messages[3]).toHaveProperty("reasoning_content", "");
expect(payload.messages[4]).toHaveProperty("reasoning_content", "native reasoning");
});
});
describe("buildCopilotDynamicHeaders", () => {
it("matches Copilot IDE-style request headers without the legacy Openai-Intent", () => {
expect(

View File

@@ -259,7 +259,7 @@ function stripDeepSeekV4ReasoningContent(payload: Record<string, unknown>): void
}
}
function ensureDeepSeekV4ToolCallReasoningContent(payload: Record<string, unknown>): void {
function ensureDeepSeekV4AssistantReasoningContent(payload: Record<string, unknown>): void {
if (!Array.isArray(payload.messages)) {
return;
}
@@ -268,7 +268,7 @@ function ensureDeepSeekV4ToolCallReasoningContent(payload: Record<string, unknow
continue;
}
const record = message as Record<string, unknown>;
if (record.role !== "assistant" || !Array.isArray(record.tool_calls)) {
if (record.role !== "assistant") {
continue;
}
if (!("reasoning_content" in record)) {
@@ -302,7 +302,7 @@ export function createDeepSeekV4OpenAICompatibleThinkingWrapper(params: {
payload.thinking = { type: "enabled" };
payload.reasoning_effort = resolveDeepSeekV4ReasoningEffort(params.thinkingLevel);
ensureDeepSeekV4ToolCallReasoningContent(payload);
ensureDeepSeekV4AssistantReasoningContent(payload);
});
};
}