diff --git a/docs/providers/deepseek.md b/docs/providers/deepseek.md index b3219b58236c..9ab3f4e81c13 100644 --- a/docs/providers/deepseek.md +++ b/docs/providers/deepseek.md @@ -80,8 +80,9 @@ available to that process (for example, in `~/.openclaw/.env` or via | Model ref | Name | Input | Context | Max output | Notes | | --------------------------------------- | --------------------------------------- | ----------- | --------- | ---------- | -------------------------------- | +| `deepseek/deepseek-flash` | DeepSeek V4.1 Flash | text, image | 1,000,000 | 384,000 | Canonical Flash model | | `deepseek/deepseek-v4-flash` | DeepSeek V4 Flash | text | 1,000,000 | 384,000 | Fast V4 thinking-capable surface | -| `deepseek/deepseek-v4-pro` | DeepSeek V4 Pro | text | 1,000,000 | 384,000 | Default; strongest V4 model | +| `deepseek/deepseek-v4-pro` | DeepSeek V4 Pro | text | 1,000,000 | 384,000 | Onboarding default | | `deepseek/deepseek-v4-flash-vision-exp` | DeepSeek V4 Flash Vision (Experimental) | text, image | 1,000,000 | 384,000 | Experimental image understanding | @@ -90,19 +91,24 @@ DeepSeek retired `deepseek-chat` and `deepseek-reasoner` on July 24, 2026 at to `deepseek/deepseek-v4-flash` or `deepseek/deepseek-v4-pro`. -OpenClaw's local costs are estimates. The vision model's bundled estimate uses -DeepSeek's peak rates; its published off-peak rates are half those amounts. +OpenClaw's local costs are estimates. Canonical Flash uses DeepSeek's peak +rates: $0.30 per million input tokens, $1.20 per million output tokens, and +$0.006 per million cached input tokens. Published off-peak rates are half those amounts. +Legacy rows retain their earlier bundled metadata. DeepSeek still accepts +`deepseek-v4-flash` and `deepseek-v4-flash-vision-exp`, routes them to V4.1 Flash, +and bills them at current Flash rates. Existing explicit selections remain valid in OpenClaw. DeepSeek can change rates; its [Models & Pricing](https://api-docs.deepseek.com/quick_start/pricing/) page is authoritative for billing. -For image inputs, select `deepseek/deepseek-v4-flash-vision-exp`. The regular -Flash and Pro models are text-only. DeepSeek's experimental vision model accepts +For image inputs, select `deepseek/deepseek-flash`. The legacy +`deepseek/deepseek-v4-flash-vision-exp` selection also retains image support. +Canonical Flash accepts PNG, JPEG, GIF, and WebP images through the same API and API key. See [DeepSeek vision](https://api-docs.deepseek.com/guides/vision) for image limits. -V4 models support DeepSeek's `thinking` control. OpenClaw also replays +Canonical Flash and V4 models support DeepSeek's `thinking` control. OpenClaw also replays DeepSeek `reasoning_content` on follow-up turns so thinking sessions with tool calls can continue. Use `/think xhigh` or `/think max` with DeepSeek V4 models to request DeepSeek's @@ -114,7 +120,7 @@ maximum `reasoning_effort`; both map to `"max"`. DeepSeek V4 thinking sessions require replayed assistant messages from a thinking-enabled turn to include `reasoning_content` on follow-up requests. OpenClaw's DeepSeek plugin backfills that field automatically, so normal -multi-turn tool use works on `deepseek/deepseek-v4-flash`, +multi-turn tool use works on `deepseek/deepseek-flash`, `deepseek/deepseek-v4-flash`, `deepseek/deepseek-v4-flash-vision-exp`, and `deepseek/deepseek-v4-pro` even when history came from another OpenAI-compatible provider (no native `reasoning_content`) or from a plain assistant message. No `/new` required after switching providers mid-session. @@ -123,9 +129,11 @@ When thinking is disabled (including the UI **None** selection), OpenClaw sends `thinking: { type: "disabled" }` and strips replayed `reasoning_content` from outgoing history, keeping the session on the non-thinking DeepSeek path. -Fresh onboarding selects the stronger `deepseek/deepseek-v4-pro` model. Use -`deepseek/deepseek-v4-flash` when lower cost or latency matters more than -maximum capability. +Fresh onboarding selects `deepseek/deepseek-v4-pro`. To select canonical Flash: + +```bash +openclaw models set deepseek/deepseek-flash +``` ## Live testing diff --git a/extensions/deepseek/index.test.ts b/extensions/deepseek/index.test.ts index 86a84278792b..828f1744e4bf 100644 --- a/extensions/deepseek/index.test.ts +++ b/extensions/deepseek/index.test.ts @@ -170,6 +170,7 @@ describe("deepseek provider plugin", () => { "deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp", + "deepseek-flash", ]); const flashModel = catalogProvider.models?.find((model) => model.id === "deepseek-v4-flash"); expect(flashModel?.reasoning).toBe(true); @@ -189,6 +190,11 @@ describe("deepseek provider plugin", () => { ]), ), ).toEqual({ + "deepseek-flash": { + contextWindow: 1_000_000, + maxTokens: 384_000, + cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0 }, + }, "deepseek-v4-flash": { contextWindow: 1_000_000, maxTokens: 384_000, @@ -308,6 +314,7 @@ describe("deepseek provider plugin", () => { const expectedV4Levels = ["off", "minimal", "low", "medium", "high", "xhigh", "max"]; for (const modelId of [ + "deepseek-flash", "deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp", @@ -324,60 +331,62 @@ describe("deepseek provider plugin", () => { ).toBe(undefined); }); - it.each(["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp"])( - "maps thinking levels to %s payload controls", - async (modelId) => { - let capturedPayload: Record | undefined; - const baseStreamFn = ( - _model: Model<"openai-completions">, - _context: Context, - options?: { onPayload?: (payload: unknown) => unknown }, - ) => { - capturedPayload = { - model: "deepseek-v4-pro", - reasoning_effort: "high", - }; - options?.onPayload?.(capturedPayload); - const stream = createAssistantMessageEventStream(); - queueMicrotask(() => stream.end()); - return stream; + it.each([ + "deepseek-flash", + "deepseek-v4-flash", + "deepseek-v4-pro", + "deepseek-v4-flash-vision-exp", + ])("maps thinking levels to %s payload controls", async (modelId) => { + let capturedPayload: Record | undefined; + const baseStreamFn = ( + _model: Model<"openai-completions">, + _context: Context, + options?: { onPayload?: (payload: unknown) => unknown }, + ) => { + capturedPayload = { + model: "deepseek-v4-pro", + reasoning_effort: "high", }; + options?.onPayload?.(capturedPayload); + const stream = createAssistantMessageEventStream(); + queueMicrotask(() => stream.end()); + return stream; + }; - const wrapThinkingOff = expectDefined( - createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "off"), - "DeepSeek thinking wrapper for off", - ); - await wrapThinkingOff( - { - provider: "deepseek", - id: modelId, - api: "openai-completions", - } as never, - { messages: [] } as never, - {}, - ); + const wrapThinkingOff = expectDefined( + createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "off"), + "DeepSeek thinking wrapper for off", + ); + await wrapThinkingOff( + { + provider: "deepseek", + id: modelId, + api: "openai-completions", + } as never, + { messages: [] } as never, + {}, + ); - expect(readThinking(capturedPayload)?.type).toBe("disabled"); - expect(capturedPayload).not.toHaveProperty("reasoning_effort"); + expect(readThinking(capturedPayload)?.type).toBe("disabled"); + expect(capturedPayload).not.toHaveProperty("reasoning_effort"); - const wrapThinkingXhigh = expectDefined( - createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "xhigh"), - "DeepSeek thinking wrapper for xhigh", - ); - await wrapThinkingXhigh( - { - provider: "deepseek", - id: modelId, - api: "openai-completions", - } as never, - { messages: [] } as never, - {}, - ); + const wrapThinkingXhigh = expectDefined( + createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "xhigh"), + "DeepSeek thinking wrapper for xhigh", + ); + await wrapThinkingXhigh( + { + provider: "deepseek", + id: modelId, + api: "openai-completions", + } as never, + { messages: [] } as never, + {}, + ); - expect(readThinking(capturedPayload)?.type).toBe("enabled"); - expect(capturedPayload?.reasoning_effort).toBe("max"); - }, - ); + expect(readThinking(capturedPayload)?.type).toBe("enabled"); + expect(capturedPayload?.reasoning_effort).toBe("max"); + }); it.each(["deepseek-v4-flash", "deepseek-v4-flash-vision-exp"])( "preserves replayed reasoning_content for %s", @@ -406,40 +415,43 @@ describe("deepseek provider plugin", () => { }, ); - it("keeps image input in requests for the bundled vision model", () => { - const provider = buildDeepSeekProvider(); - const entry = provider.models?.find((model) => model.id === "deepseek-v4-flash-vision-exp"); - expect(entry).toMatchObject({ reasoning: true, input: ["text", "image"] }); - const model = { - ...entry, - provider: "deepseek", - baseUrl: provider.baseUrl, - api: "openai-completions", - } as OpenAICompletionsModel; - const payload = buildOpenAICompletionsParams( - model, - { - messages: [ - { - role: "user", - content: [ - { type: "text", text: "Describe this image." }, - { type: "image", data: "fixture-image", mimeType: "image/png" }, - ], - timestamp: 1, - }, + it.each(["deepseek-flash", "deepseek-v4-flash-vision-exp"])( + "keeps image input in requests for %s", + (modelId) => { + const provider = buildDeepSeekProvider(); + const entry = provider.models?.find((model) => model.id === modelId); + expect(entry).toMatchObject({ reasoning: true, input: ["text", "image"] }); + const model = { + ...entry, + provider: "deepseek", + baseUrl: provider.baseUrl, + api: "openai-completions", + } as OpenAICompletionsModel; + const payload = buildOpenAICompletionsParams( + model, + { + messages: [ + { + role: "user", + content: [ + { type: "text", text: "Describe this image." }, + { type: "image", data: "fixture-image", mimeType: "image/png" }, + ], + timestamp: 1, + }, + ], + }, + {}, + ); + expect(payload.messages).toContainEqual({ + role: "user", + content: [ + { type: "text", text: "Describe this image." }, + { type: "image_url", image_url: { url: "data:image/png;base64,fixture-image" } }, ], - }, - {}, - ); - expect(payload.messages).toContainEqual({ - role: "user", - content: [ - { type: "text", text: "Describe this image." }, - { type: "image_url", image_url: { url: "data:image/png;base64,fixture-image" } }, - ], - }); - }); + }); + }, + ); it("adds blank reasoning_content for replayed tool calls from non-DeepSeek turns", async () => { const capture: PayloadCapture = {}; diff --git a/extensions/deepseek/models.ts b/extensions/deepseek/models.ts index 243150d588a6..7ee830365873 100644 --- a/extensions/deepseek/models.ts +++ b/extensions/deepseek/models.ts @@ -12,7 +12,9 @@ export const DEEPSEEK_MODEL_CATALOG: ModelDefinitionConfig[] = buildManifestMode }).models.map((model) => Object.assign(model, { api: "openai-completions" })); const DEEPSEEK_V4_MODEL_IDS = new Set( - DEEPSEEK_MODEL_CATALOG.map((model) => model.id).filter((id) => id.startsWith("deepseek-v4-")), + DEEPSEEK_MODEL_CATALOG.map((model) => model.id).filter( + (id) => id === "deepseek-flash" || id.startsWith("deepseek-v4-"), + ), ); export function isDeepSeekV4ModelId(modelId: string): boolean { diff --git a/extensions/deepseek/openclaw.plugin.json b/extensions/deepseek/openclaw.plugin.json index 70b49efafae6..b280305559d9 100644 --- a/extensions/deepseek/openclaw.plugin.json +++ b/extensions/deepseek/openclaw.plugin.json @@ -92,6 +92,26 @@ "maxTokensField": "max_tokens", "codeMode": "preferred" } + }, + { + "id": "deepseek-flash", + "name": "DeepSeek V4.1 Flash", + "reasoning": true, + "input": ["text", "image"], + "contextWindow": 1000000, + "maxTokens": 384000, + "cost": { + "input": 0.3, + "output": 1.2, + "cacheRead": 0.006, + "cacheWrite": 0 + }, + "compat": { + "supportsUsageInStreaming": true, + "supportsReasoningEffort": true, + "maxTokensField": "max_tokens", + "codeMode": "preferred" + } } ] }