Skip to content

Commit b97cb15

Browse files
committed
fix(deepseek): backfill v4 reasoning for proxy models
1 parent 8faf133 commit b97cb15

3 files changed

Lines changed: 51 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
6161
- Channels/iMessage: wire `action: "reply"` attachments through `imsg send-rich --file` when the installed imsg build advertises that capability (probed once via `imsg send-rich --help` and cached on the private-API status). Reply now hydrates `media`/`mediaUrl`/`fileUrl`/`mediaUrls[0]`/`filePath`/`path`/base64 `buffer`+`filename` through the shared outbound resolver, stages buffers via the existing `withTempFile` helper, rejects `http(s)://` URL attachments with a targeted error pointing callers at `send`'s full attachment-resolver pipeline, and falls back to the explicit `imsg#114 not landed yet` error on older imsg builds. Depends on the upstream `openclaw/imsg#114` capability landing in an installable release; until then the new path stays gated and users see the same explicit fallback `#79822` introduced. (#79864) Thanks @omarshahine.
6262
- Telegram: preserve the first-preview debounce while appending true partial-stream deltas, so edited draft previews no longer duplicate earlier text when providers emit incremental output. (#80045) Thanks @TurboTheTurtle.
6363
- Volcengine/Kimi: strip provider-unsupported tool schema length and item constraint keywords for direct and coding-plan models so hosted Kimi runs do not reject message tools with `minLength`. Fixes #38817.
64+
- DeepSeek: backfill V4 `reasoning_content` replay fields for unowned OpenAI-compatible proxy providers, preventing follow-up request failures outside the bundled DeepSeek and OpenRouter routes. Fixes #79608.
6465

6566
## 2026.5.9
6667

src/agents/pi-embedded-runner-extraparams.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,33 @@ describe("applyExtraParamsToAgent", () => {
678678
expect(payloads[0]?.thinking).toEqual({ type: "disabled" });
679679
});
680680

681+
it("fills DeepSeek V4 reasoning_content for unowned OpenAI-compatible proxy models", () => {
682+
const payload = runResponsesPayloadMutationCase({
683+
applyProvider: "opencode",
684+
applyModelId: "deepseek-v4-pro",
685+
thinkingLevel: "high",
686+
model: {
687+
api: "openai-completions",
688+
provider: "opencode",
689+
id: "deepseek-v4-pro",
690+
} as Model<"openai-completions">,
691+
payload: {
692+
messages: [
693+
{ role: "user", content: "continue" },
694+
{ role: "assistant", content: "I used a tool" },
695+
{ role: "tool", content: "ok" },
696+
],
697+
},
698+
});
699+
700+
const messages = payload.messages as Array<Record<string, unknown>>;
701+
expect(payload.thinking).toEqual({ type: "enabled" });
702+
expect(payload.reasoning_effort).toBe("high");
703+
expect(messages[0]).not.toHaveProperty("reasoning_content");
704+
expect(messages[1]).toHaveProperty("reasoning_content", "");
705+
expect(messages[2]).not.toHaveProperty("reasoning_content");
706+
});
707+
681708
it("strips xai Responses reasoning payload fields", () => {
682709
const payload = runResponsesPayloadMutationCase({
683710
applyProvider: "xai",

src/agents/pi-embedded-runner/extra-params.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { streamSimple } from "@mariozechner/pi-ai";
44
import type { SettingsManager } from "@mariozechner/pi-coding-agent";
55
import type { ThinkLevel } from "../../auto-reply/thinking.js";
66
import type { OpenClawConfig } from "../../config/types.openclaw.js";
7+
import { createDeepSeekV4OpenAICompatibleThinkingWrapper } from "../../plugin-sdk/provider-stream-shared.js";
78
import {
89
prepareProviderExtraParams as prepareProviderExtraParamsRuntime,
910
type ProviderRuntimePluginHandle,
@@ -689,6 +690,12 @@ function applyPostPluginStreamWrappers(
689690
ctx.agent.streamFn = createOpenAICompletionsToolsCompatWrapper(ctx.agent.streamFn);
690691

691692
if (!ctx.providerWrapperHandled) {
693+
ctx.agent.streamFn = createDeepSeekV4OpenAICompatibleThinkingWrapper({
694+
baseStreamFn: ctx.agent.streamFn,
695+
thinkingLevel: ctx.thinkingLevel,
696+
shouldPatchModel: isDeepSeekV4OpenAICompatibleModel,
697+
});
698+
692699
// Guard Google-family payloads against invalid negative thinking budgets
693700
// emitted by upstream model-ID heuristics for Gemini 3.1 variants.
694701
ctx.agent.streamFn = createGoogleThinkingPayloadWrapper(ctx.agent.streamFn, ctx.thinkingLevel);
@@ -752,6 +759,22 @@ function applyPostPluginStreamWrappers(
752759
log.warn(`ignoring invalid parallel_tool_calls param: ${summary}`);
753760
}
754761

762+
function normalizeDeepSeekV4CandidateId(modelId: unknown): string | undefined {
763+
if (typeof modelId !== "string") {
764+
return undefined;
765+
}
766+
const withoutSuffix = modelId.trim().toLowerCase().split(":", 1)[0];
767+
return withoutSuffix.split("/").pop();
768+
}
769+
770+
function isDeepSeekV4OpenAICompatibleModel(model: Parameters<StreamFn>[0]): boolean {
771+
const normalizedModelId = normalizeDeepSeekV4CandidateId(model.id);
772+
return (
773+
model.api === "openai-completions" &&
774+
(normalizedModelId === "deepseek-v4-flash" || normalizedModelId === "deepseek-v4-pro")
775+
);
776+
}
777+
755778
/**
756779
* Apply extra params (like temperature) to an agent's streamFn.
757780
* Also applies verified provider-specific request wrappers, such as OpenRouter attribution.

0 commit comments

Comments
 (0)