Skip to content

Commit 7d1891e

Browse files
authored
feat(deepseek): support v4 models
Add DeepSeek V4 Flash/Pro support, update Pi packages to 0.70.2, and handle disabled thinking/None by stripping replayed reasoning content.
1 parent 4f4288e commit 7d1891e

27 files changed

Lines changed: 429 additions & 129 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Docs: https://docs.openclaw.ai
2121
- Agents/subagents: add optional forked context for native `sessions_spawn` runs so agents can let a child inherit the requester transcript when needed, while keeping clean isolated sessions as the default; includes prompt guidance, context-engine hook metadata, docs, and QA coverage.
2222
- Codex harness: add structured debug logging for embedded harness selection decisions so `/status` stays simple while gateway logs explain auto-selection and Pi fallback reasons. (#70760) Thanks @100yenadmin.
2323
- Plugin SDK/Codex harness: add provider-owned transport/auth/follow-up seams and harness result classification so Codex-style runtimes can participate in fallback policy without core special-casing. (#70772) Thanks @100yenadmin.
24-
- Dependencies/Pi: update bundled Pi packages to `0.70.0`, use Pi's upstream `gpt-5.5` catalog metadata for OpenAI and OpenAI Codex, and keep only local `gpt-5.5-pro` forward-compat handling.
24+
- Dependencies/Pi: update bundled Pi packages to `0.70.2`, use Pi's upstream `gpt-5.5` and DeepSeek V4 catalog metadata, and keep only local `gpt-5.5-pro` forward-compat handling.
2525
- Models/CLI: speed up `openclaw models list --all --provider <id>` for bundled providers with safe static catalogs while keeping live and third-party providers on registry discovery. (#70632) Thanks @shakkernerd.
2626
- Models/CLI: avoid broad registry enumeration for default `openclaw models list`, reducing default listing latency while preserving configured-row output. (#70883) Thanks @shakkernerd.
2727
- Models/CLI: split `openclaw models list` row-source orchestration and registry loading into narrower helpers without changing list output behavior. (#70867) Thanks @shakkernerd.
@@ -35,13 +35,15 @@ Docs: https://docs.openclaw.ai
3535
- Providers/OpenRouter: add image generation and reference-image editing through `image_generate`, so OpenRouter image models work with `OPENROUTER_API_KEY`. Fixes #55066 via #67668. Thanks @notamicrodose.
3636
- Image generation: let agents request provider-supported quality and output format hints, and pass OpenAI-specific background, moderation, compression, and user hints through the `image_generate` tool. (#70503) Thanks @ottodeng.
3737
- Plugins/Google Meet: let realtime Meet sessions consult the full OpenClaw agent for deeper answers while staying in the live voice loop.
38+
- Providers/DeepSeek: add DeepSeek V4 Flash and V4 Pro to the bundled catalog and make V4 Flash the onboarding default.
3839

3940
### Fixes
4041

4142
- Plugins/onboarding: record local plugin install source metadata without duplicating raw absolute local paths in persisted `plugins.installs`, while preserving linked load-path cleanup. (#70970) Thanks @vincentkoc.
4243
- Browser/tool: tell agents not to pass per-call `timeoutMs` on existing-session type, evaluate, and other Chrome MCP actions that reject timeout overrides.
4344
- Codex/GPT-5.4: harden fallback, auth-profile, tool-schema, and replay edge cases across native and embedded runtime paths. (#70743) Thanks @100yenadmin.
4445
- Voice-call/Telnyx: preserve inbound/outbound callback metadata and read transcription text from Telnyx's current `transcription_data` payload.
46+
- Providers/DeepSeek: wire V4 thinking controls and OpenAI-compatible replay policy so follow-up turns preserve DeepSeek `reasoning_content`, while the None/off thinking path strips replayed reasoning fields. Fixes #70931.
4547
- Codex harness: send verbose tool progress to chat channels for native app-server runs, matching the Pi harness `/verbose on` and `/verbose full` behavior. (#70966) Thanks @jalehman.
4648
- Codex models: fetch paginated Codex app-server model catalogs, mark truncated `/codex models` output, and keep ChatGPT OAuth defaults on the `openai-codex/gpt-5.5` route instead of the OpenAI API-key route.
4749
- Codex harness: route native `request_user_input` prompts back to the originating chat, preserve queued follow-up answers, and honor newer app-server command approval amendment decisions.

docs/concepts/model-providers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
235235
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
236236
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
237237
| Cloudflare AI Gateway | `cloudflare-ai-gateway` | `CLOUDFLARE_AI_GATEWAY_API_KEY` ||
238+
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
238239
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` ||
239240
| Groq | `groq` | `GROQ_API_KEY` ||
240241
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |

docs/pi.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ OpenClaw uses the pi SDK to embed an AI coding agent into its messaging gateway
2323

2424
```json
2525
{
26-
"@mariozechner/pi-agent-core": "0.68.1",
27-
"@mariozechner/pi-ai": "0.68.1",
28-
"@mariozechner/pi-coding-agent": "0.68.1",
29-
"@mariozechner/pi-tui": "0.68.1"
26+
"@mariozechner/pi-agent-core": "0.70.2",
27+
"@mariozechner/pi-ai": "0.70.2",
28+
"@mariozechner/pi-coding-agent": "0.70.2",
29+
"@mariozechner/pi-tui": "0.70.2"
3030
}
3131
```
3232

docs/providers/deepseek.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ read_when:
2626
openclaw onboard --auth-choice deepseek-api-key
2727
```
2828

29-
This will prompt for your API key and set `deepseek/deepseek-chat` as the default model.
29+
This will prompt for your API key and set `deepseek/deepseek-v4-flash` as the default model.
3030

3131
</Step>
3232
<Step title="Verify models are available">
@@ -60,13 +60,17 @@ is available to that process (for example, in `~/.openclaw/.env` or via
6060

6161
## Built-in catalog
6262

63-
| Model ref | Name | Input | Context | Max output | Notes |
64-
| ---------------------------- | ----------------- | ----- | ------- | ---------- | ------------------------------------------------- |
65-
| `deepseek/deepseek-chat` | DeepSeek Chat | text | 131,072 | 8,192 | Default model; DeepSeek V3.2 non-thinking surface |
66-
| `deepseek/deepseek-reasoner` | DeepSeek Reasoner | text | 131,072 | 65,536 | Reasoning-enabled V3.2 surface |
63+
| Model ref | Name | Input | Context | Max output | Notes |
64+
| ---------------------------- | ----------------- | ----- | --------- | ---------- | ------------------------------------------ |
65+
| `deepseek/deepseek-v4-flash` | DeepSeek V4 Flash | text | 1,000,000 | 384,000 | Default model; V4 thinking-capable surface |
66+
| `deepseek/deepseek-v4-pro` | DeepSeek V4 Pro | text | 1,000,000 | 384,000 | V4 thinking-capable surface |
67+
| `deepseek/deepseek-chat` | DeepSeek Chat | text | 131,072 | 8,192 | DeepSeek V3.2 non-thinking surface |
68+
| `deepseek/deepseek-reasoner` | DeepSeek Reasoner | text | 131,072 | 65,536 | Reasoning-enabled V3.2 surface |
6769

6870
<Tip>
69-
Both bundled models currently advertise streaming usage compatibility in source.
71+
V4 models support DeepSeek's `thinking` control. OpenClaw also replays
72+
DeepSeek `reasoning_content` on follow-up turns so thinking sessions with tool
73+
calls can continue.
7074
</Tip>
7175

7276
## Config example
@@ -76,7 +80,7 @@ Both bundled models currently advertise streaming usage compatibility in source.
7680
env: { DEEPSEEK_API_KEY: "sk-..." },
7781
agents: {
7882
defaults: {
79-
model: { primary: "deepseek/deepseek-chat" },
83+
model: { primary: "deepseek/deepseek-v4-flash" },
8084
},
8185
},
8286
}

extensions/amazon-bedrock-mantle/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"dependencies": {
88
"@anthropic-ai/sdk": "0.90.0",
99
"@aws/bedrock-token-generator": "^1.1.0",
10-
"@mariozechner/pi-ai": "0.70.0"
10+
"@mariozechner/pi-ai": "0.70.2"
1111
},
1212
"devDependencies": {
1313
"@openclaw/plugin-sdk": "workspace:*"

extensions/anthropic/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"description": "OpenClaw Anthropic provider plugin",
66
"type": "module",
77
"dependencies": {
8-
"@mariozechner/pi-ai": "0.70.0"
8+
"@mariozechner/pi-ai": "0.70.2"
99
},
1010
"devDependencies": {
1111
"@openclaw/plugin-sdk": "workspace:*"

extensions/codex/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "OpenClaw Codex harness and model provider plugin",
55
"type": "module",
66
"dependencies": {
7-
"@mariozechner/pi-coding-agent": "0.70.0",
7+
"@mariozechner/pi-coding-agent": "0.70.2",
88
"ajv": "^8.18.0",
99
"ws": "^8.20.0",
1010
"zod": "^4.3.6"

extensions/deepseek/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export {
44
DEEPSEEK_MODEL_CATALOG,
55
} from "./models.js";
66
export { buildDeepSeekProvider } from "./provider-catalog.js";
7+
export { createDeepSeekV4ThinkingWrapper } from "./stream.js";

extensions/deepseek/index.test.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import type { Context, Model } from "@mariozechner/pi-ai";
2+
import { createAssistantMessageEventStream } from "@mariozechner/pi-ai";
13
import { describe, expect, it } from "vitest";
4+
import { buildOpenAICompletionsParams } from "../../src/agents/openai-transport-stream.js";
25
import { resolveProviderPluginChoice } from "../../src/plugins/provider-auth-choice.runtime.js";
36
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
47
import { runSingleProviderCatalog } from "../test-support/provider-model-test-helpers.js";
58
import deepseekPlugin from "./index.js";
9+
import { createDeepSeekV4ThinkingWrapper } from "./stream.js";
610

711
describe("deepseek provider plugin", () => {
812
it("registers DeepSeek with api-key auth wizard metadata", async () => {
@@ -28,14 +32,181 @@ describe("deepseek provider plugin", () => {
2832
expect(catalogProvider.api).toBe("openai-completions");
2933
expect(catalogProvider.baseUrl).toBe("https://api.deepseek.com");
3034
expect(catalogProvider.models?.map((model) => model.id)).toEqual([
35+
"deepseek-v4-flash",
36+
"deepseek-v4-pro",
3137
"deepseek-chat",
3238
"deepseek-reasoner",
3339
]);
40+
expect(catalogProvider.models?.find((model) => model.id === "deepseek-v4-flash")).toMatchObject(
41+
{
42+
reasoning: true,
43+
contextWindow: 1_000_000,
44+
maxTokens: 384_000,
45+
compat: expect.objectContaining({
46+
supportsReasoningEffort: true,
47+
maxTokensField: "max_tokens",
48+
}),
49+
},
50+
);
3451
expect(
3552
catalogProvider.models?.find((model) => model.id === "deepseek-reasoner")?.reasoning,
3653
).toBe(true);
3754
});
3855

56+
it("owns OpenAI-compatible replay policy", async () => {
57+
const provider = await registerSingleProviderPlugin(deepseekPlugin);
58+
59+
expect(provider.buildReplayPolicy?.({ modelApi: "openai-completions" } as never)).toMatchObject(
60+
{
61+
sanitizeToolCallIds: true,
62+
toolCallIdMode: "strict",
63+
validateGeminiTurns: true,
64+
validateAnthropicTurns: true,
65+
},
66+
);
67+
});
68+
69+
it("maps thinking levels to DeepSeek V4 payload controls", async () => {
70+
let capturedPayload: Record<string, unknown> | undefined;
71+
const baseStreamFn = (
72+
_model: Model<"openai-completions">,
73+
_context: Context,
74+
options?: { onPayload?: (payload: unknown) => unknown },
75+
) => {
76+
capturedPayload = {
77+
model: "deepseek-v4-pro",
78+
reasoning_effort: "high",
79+
};
80+
options?.onPayload?.(capturedPayload);
81+
const stream = createAssistantMessageEventStream();
82+
queueMicrotask(() => stream.end());
83+
return stream;
84+
};
85+
86+
const wrapThinkingOff = createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "off");
87+
expect(wrapThinkingOff).toBeDefined();
88+
wrapThinkingOff?.(
89+
{
90+
provider: "deepseek",
91+
id: "deepseek-v4-pro",
92+
api: "openai-completions",
93+
} as never,
94+
{ messages: [] } as never,
95+
{},
96+
);
97+
98+
expect(capturedPayload).toMatchObject({ thinking: { type: "disabled" } });
99+
expect(capturedPayload).not.toHaveProperty("reasoning_effort");
100+
101+
const wrapThinkingXhigh = createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "xhigh");
102+
expect(wrapThinkingXhigh).toBeDefined();
103+
wrapThinkingXhigh?.(
104+
{
105+
provider: "deepseek",
106+
id: "deepseek-v4-pro",
107+
api: "openai-completions",
108+
} as never,
109+
{ messages: [] } as never,
110+
{},
111+
);
112+
113+
expect(capturedPayload).toMatchObject({
114+
thinking: { type: "enabled" },
115+
reasoning_effort: "max",
116+
});
117+
});
118+
119+
it("strips replayed reasoning_content when DeepSeek V4 thinking is disabled", async () => {
120+
let capturedPayload: Record<string, unknown> | undefined;
121+
const model = {
122+
provider: "deepseek",
123+
id: "deepseek-v4-flash",
124+
name: "DeepSeek V4 Flash",
125+
api: "openai-completions",
126+
baseUrl: "https://api.deepseek.com",
127+
reasoning: true,
128+
input: ["text"],
129+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
130+
contextWindow: 1_000_000,
131+
maxTokens: 384_000,
132+
compat: {
133+
supportsUsageInStreaming: true,
134+
supportsReasoningEffort: true,
135+
maxTokensField: "max_tokens",
136+
},
137+
} as Model<"openai-completions">;
138+
const context = {
139+
messages: [
140+
{ role: "user", content: "hi", timestamp: 1 },
141+
{
142+
role: "assistant",
143+
api: "openai-completions",
144+
provider: "deepseek",
145+
model: "deepseek-v4-flash",
146+
content: [
147+
{
148+
type: "thinking",
149+
thinking: "call reasoning",
150+
thinkingSignature: "reasoning_content",
151+
},
152+
{ type: "toolCall", id: "call_1", name: "read", arguments: {} },
153+
],
154+
usage: {
155+
input: 0,
156+
output: 0,
157+
cacheRead: 0,
158+
cacheWrite: 0,
159+
totalTokens: 0,
160+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
161+
},
162+
stopReason: "toolUse",
163+
timestamp: 2,
164+
},
165+
{
166+
role: "toolResult",
167+
toolCallId: "call_1",
168+
toolName: "read",
169+
content: [{ type: "text", text: "ok" }],
170+
isError: false,
171+
timestamp: 3,
172+
},
173+
],
174+
tools: [
175+
{
176+
name: "read",
177+
description: "Read data",
178+
parameters: { type: "object", properties: {}, required: [], additionalProperties: false },
179+
},
180+
],
181+
} as Context;
182+
const baseStreamFn = (
183+
streamModel: Model<"openai-completions">,
184+
streamContext: Context,
185+
options?: { onPayload?: (payload: unknown, model: unknown) => unknown },
186+
) => {
187+
capturedPayload = buildOpenAICompletionsParams(streamModel, streamContext, {
188+
reasoning: "high",
189+
} as never);
190+
options?.onPayload?.(capturedPayload, streamModel);
191+
const stream = createAssistantMessageEventStream();
192+
queueMicrotask(() => stream.end());
193+
return stream;
194+
};
195+
196+
const wrapThinkingNone = createDeepSeekV4ThinkingWrapper(
197+
baseStreamFn as never,
198+
"none" as never,
199+
);
200+
expect(wrapThinkingNone).toBeDefined();
201+
wrapThinkingNone?.(model, context, {});
202+
203+
expect(capturedPayload).toMatchObject({ thinking: { type: "disabled" } });
204+
expect(capturedPayload).not.toHaveProperty("reasoning_effort");
205+
expect((capturedPayload?.messages as Array<Record<string, unknown>>)[1]).not.toHaveProperty(
206+
"reasoning_content",
207+
);
208+
});
209+
39210
it("publishes configured DeepSeek models through plugin-owned catalog augmentation", async () => {
40211
const provider = await registerSingleProviderPlugin(deepseekPlugin);
41212

extensions/deepseek/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { readConfiguredProviderCatalogEntries } from "openclaw/plugin-sdk/provider-catalog-shared";
22
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
3+
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
34
import { applyDeepSeekConfig, DEEPSEEK_DEFAULT_MODEL_REF } from "./onboard.js";
45
import { buildDeepSeekProvider } from "./provider-catalog.js";
6+
import { createDeepSeekV4ThinkingWrapper } from "./stream.js";
57

68
const PROVIDER_ID = "deepseek";
79

@@ -42,5 +44,7 @@ export default defineSingleProviderPluginEntry({
4244
}),
4345
matchesContextOverflowError: ({ errorMessage }) =>
4446
/\bdeepseek\b.*(?:input.*too long|context.*exceed)/i.test(errorMessage),
47+
...buildProviderReplayFamilyHooks({ family: "openai-compatible" }),
48+
wrapStreamFn: (ctx) => createDeepSeekV4ThinkingWrapper(ctx.streamFn, ctx.thinkingLevel),
4549
},
4650
});

0 commit comments

Comments
 (0)