fix(deepseek): backfill v4 reasoning for proxy models

This commit is contained in:
Peter Steinberger
2026-05-10 05:47:50 +01:00
parent 8faf133620
commit b97cb15b07
3 changed files with 51 additions and 0 deletions

View File

@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
- 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.
- 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.
- 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.
- 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.
## 2026.5.9

View File

@@ -678,6 +678,33 @@ describe("applyExtraParamsToAgent", () => {
expect(payloads[0]?.thinking).toEqual({ type: "disabled" });
});
it("fills DeepSeek V4 reasoning_content for unowned OpenAI-compatible proxy models", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "opencode",
applyModelId: "deepseek-v4-pro",
thinkingLevel: "high",
model: {
api: "openai-completions",
provider: "opencode",
id: "deepseek-v4-pro",
} as Model<"openai-completions">,
payload: {
messages: [
{ role: "user", content: "continue" },
{ role: "assistant", content: "I used a tool" },
{ role: "tool", content: "ok" },
],
},
});
const messages = payload.messages as Array<Record<string, unknown>>;
expect(payload.thinking).toEqual({ type: "enabled" });
expect(payload.reasoning_effort).toBe("high");
expect(messages[0]).not.toHaveProperty("reasoning_content");
expect(messages[1]).toHaveProperty("reasoning_content", "");
expect(messages[2]).not.toHaveProperty("reasoning_content");
});
it("strips xai Responses reasoning payload fields", () => {
const payload = runResponsesPayloadMutationCase({
applyProvider: "xai",

View File

@@ -4,6 +4,7 @@ import { streamSimple } from "@mariozechner/pi-ai";
import type { SettingsManager } from "@mariozechner/pi-coding-agent";
import type { ThinkLevel } from "../../auto-reply/thinking.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { createDeepSeekV4OpenAICompatibleThinkingWrapper } from "../../plugin-sdk/provider-stream-shared.js";
import {
prepareProviderExtraParams as prepareProviderExtraParamsRuntime,
type ProviderRuntimePluginHandle,
@@ -689,6 +690,12 @@ function applyPostPluginStreamWrappers(
ctx.agent.streamFn = createOpenAICompletionsToolsCompatWrapper(ctx.agent.streamFn);
if (!ctx.providerWrapperHandled) {
ctx.agent.streamFn = createDeepSeekV4OpenAICompatibleThinkingWrapper({
baseStreamFn: ctx.agent.streamFn,
thinkingLevel: ctx.thinkingLevel,
shouldPatchModel: isDeepSeekV4OpenAICompatibleModel,
});
// Guard Google-family payloads against invalid negative thinking budgets
// emitted by upstream model-ID heuristics for Gemini 3.1 variants.
ctx.agent.streamFn = createGoogleThinkingPayloadWrapper(ctx.agent.streamFn, ctx.thinkingLevel);
@@ -752,6 +759,22 @@ function applyPostPluginStreamWrappers(
log.warn(`ignoring invalid parallel_tool_calls param: ${summary}`);
}
function normalizeDeepSeekV4CandidateId(modelId: unknown): string | undefined {
if (typeof modelId !== "string") {
return undefined;
}
const withoutSuffix = modelId.trim().toLowerCase().split(":", 1)[0];
return withoutSuffix.split("/").pop();
}
function isDeepSeekV4OpenAICompatibleModel(model: Parameters<StreamFn>[0]): boolean {
const normalizedModelId = normalizeDeepSeekV4CandidateId(model.id);
return (
model.api === "openai-completions" &&
(normalizedModelId === "deepseek-v4-flash" || normalizedModelId === "deepseek-v4-pro")
);
}
/**
* Apply extra params (like temperature) to an agent's streamFn.
* Also applies verified provider-specific request wrappers, such as OpenRouter attribution.