Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 28551d4

Browse files
committed
feat: add DeepSeek V4 model support and fix thinking mode detection
- Add deepseek-v4-0324 model to the DeepSeek provider model list with thinking mode, vision support, and pricing info - Fix thinking mode detection to use preserveReasoning flag from ModelInfo instead of hardcoding model name check for "deepseek-reasoner" - This allows any model with preserveReasoning=true to automatically get thinking mode enabled, including v4 and future models - Add tests for v4 model info and thinking mode behavior Addresses #12174
1 parent 96d6e43 commit 28551d4

3 files changed

Lines changed: 80 additions & 3 deletions

File tree

packages/types/src/providers/deepseek.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ export const deepSeekModels = {
3232
cacheReadsPrice: 0.028, // $0.028 per million tokens (cache hit) - Updated Dec 9, 2025
3333
description: `DeepSeek-V3.2 (Thinking Mode) achieves performance comparable to OpenAI-o1 across math, code, and reasoning tasks. Supports Chain of Thought reasoning with up to 8K output tokens. Supports JSON output, tool calls, and chat prefix completion (beta).`,
3434
},
35+
// DeepSeek V4 models - https://api-docs.deepseek.com/quick_start/pricing
36+
"deepseek-v4-0324": {
37+
maxTokens: 16_384, // 16K max output
38+
contextWindow: 128_000,
39+
supportsImages: true,
40+
supportsPromptCache: true,
41+
preserveReasoning: true,
42+
inputPrice: 2.19, // $2.19 per million tokens (cache miss)
43+
outputPrice: 8.87, // $8.87 per million tokens
44+
cacheWritesPrice: 2.19, // $2.19 per million tokens (cache miss)
45+
cacheReadsPrice: 0.219, // $0.219 per million tokens (cache hit)
46+
description: `DeepSeek-V4-0324 is the latest flagship reasoning model with significantly improved performance across math, code, and complex reasoning tasks. Features enhanced thinking mode with interleaved reasoning and supports vision, JSON output, tool calls, and extended context.`,
47+
},
3548
} as const satisfies Record<string, ModelInfo>
3649

3750
// https://api-docs.deepseek.com/quick_start/parameter_settings

src/api/providers/__tests__/deepseek.spec.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ vi.mock("openai", () => {
3030
}
3131

3232
// Check if this is a reasoning_content test by looking at model
33-
const isReasonerModel = options.model?.includes("deepseek-reasoner")
33+
const isReasonerModel =
34+
options.model?.includes("deepseek-reasoner") || options.model?.includes("deepseek-v4")
3435
const isToolCallTest = options.tools?.length > 0
3536

3637
// Return async iterator for streaming
@@ -247,6 +248,29 @@ describe("DeepSeekHandler", () => {
247248
expect((model.info as ModelInfo).preserveReasoning).toBeUndefined()
248249
})
249250

251+
it("should return correct model info for deepseek-v4-0324", () => {
252+
const handlerWithV4 = new DeepSeekHandler({
253+
...mockOptions,
254+
apiModelId: "deepseek-v4-0324",
255+
})
256+
const model = handlerWithV4.getModel()
257+
expect(model.id).toBe("deepseek-v4-0324")
258+
expect(model.info).toBeDefined()
259+
expect(model.info.maxTokens).toBe(16_384)
260+
expect(model.info.contextWindow).toBe(128_000)
261+
expect(model.info.supportsImages).toBe(true)
262+
expect(model.info.supportsPromptCache).toBe(true)
263+
})
264+
265+
it("should have preserveReasoning enabled for deepseek-v4-0324", () => {
266+
const handlerWithV4 = new DeepSeekHandler({
267+
...mockOptions,
268+
apiModelId: "deepseek-v4-0324",
269+
})
270+
const model = handlerWithV4.getModel()
271+
expect((model.info as ModelInfo).preserveReasoning).toBe(true)
272+
})
273+
250274
it("should return provided model ID with default model info if model does not exist", () => {
251275
const handlerWithInvalidModel = new DeepSeekHandler({
252276
...mockOptions,
@@ -475,6 +499,44 @@ describe("DeepSeekHandler", () => {
475499
expect(callArgs.thinking).toBeUndefined()
476500
})
477501

502+
it("should pass thinking parameter for deepseek-v4-0324 model", async () => {
503+
const v4Handler = new DeepSeekHandler({
504+
...mockOptions,
505+
apiModelId: "deepseek-v4-0324",
506+
})
507+
508+
const stream = v4Handler.createMessage(systemPrompt, messages)
509+
for await (const _chunk of stream) {
510+
// Consume the stream
511+
}
512+
513+
// Verify that the thinking parameter was passed to the API for v4 model
514+
expect(mockCreate).toHaveBeenCalledWith(
515+
expect.objectContaining({
516+
thinking: { type: "enabled" },
517+
}),
518+
{},
519+
)
520+
})
521+
522+
it("should handle reasoning_content in streaming responses for deepseek-v4-0324", async () => {
523+
const v4Handler = new DeepSeekHandler({
524+
...mockOptions,
525+
apiModelId: "deepseek-v4-0324",
526+
})
527+
528+
const stream = v4Handler.createMessage(systemPrompt, messages)
529+
const chunks: any[] = []
530+
for await (const chunk of stream) {
531+
chunks.push(chunk)
532+
}
533+
534+
// Should have reasoning chunks since v4 model has preserveReasoning
535+
const reasoningChunks = chunks.filter((chunk) => chunk.type === "reasoning")
536+
expect(reasoningChunks.length).toBeGreaterThan(0)
537+
expect(reasoningChunks[0].text).toBe("Let me think about this...")
538+
})
539+
478540
it("should handle tool calls with reasoning_content", async () => {
479541
const reasonerHandler = new DeepSeekHandler({
480542
...mockOptions,

src/api/providers/deepseek.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
deepSeekDefaultModelId,
77
DEEP_SEEK_DEFAULT_TEMPERATURE,
88
OPENAI_AZURE_AI_INFERENCE_PATH,
9+
type ModelInfo,
910
} from "@roo-code/types"
1011

1112
import type { ApiHandlerOptions } from "../../shared/api"
@@ -55,8 +56,9 @@ export class DeepSeekHandler extends OpenAiHandler {
5556
const modelId = this.options.apiModelId ?? deepSeekDefaultModelId
5657
const { info: modelInfo } = this.getModel()
5758

58-
// Check if this is a thinking-enabled model (deepseek-reasoner)
59-
const isThinkingModel = modelId.includes("deepseek-reasoner")
59+
// Check if this is a thinking-enabled model via the preserveReasoning flag
60+
// This covers deepseek-reasoner and newer v4 models that support thinking mode
61+
const isThinkingModel = (modelInfo as ModelInfo).preserveReasoning === true
6062

6163
// Convert messages to R1 format (merges consecutive same-role messages)
6264
// This is required for DeepSeek which does not support successive messages with the same role

0 commit comments

Comments
 (0)