diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0a12853bf..f9cc9e18207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/agents/pi-embedded-runner-extraparams.test.ts b/src/agents/pi-embedded-runner-extraparams.test.ts index 9dc77b0954b..efc1b56ec94 100644 --- a/src/agents/pi-embedded-runner-extraparams.test.ts +++ b/src/agents/pi-embedded-runner-extraparams.test.ts @@ -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>; + 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", diff --git a/src/agents/pi-embedded-runner/extra-params.ts b/src/agents/pi-embedded-runner/extra-params.ts index cb330470aa7..66096587394 100644 --- a/src/agents/pi-embedded-runner/extra-params.ts +++ b/src/agents/pi-embedded-runner/extra-params.ts @@ -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[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.