Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 13 additions & 37 deletions packages/types/src/providers/deepseek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,56 +14,32 @@ export const deepSeekModels = {
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "low", "medium", "high", "xhigh"],
supportsReasoningEffort: ["disable", "low", "high", "max"], // Updated 2026-08-01
preserveReasoning: true,
reasoningEffort: "high",
inputPrice: 0.14, // $0.14 per million tokens (cache miss) - Updated Apr 29, 2026
outputPrice: 0.28, // $0.28 per million tokens - Updated Apr 29, 2026
cacheWritesPrice: 0.14, // $0.14 per million tokens (cache miss) - Updated Apr 29, 2026
cacheReadsPrice: 0.0028, // $0.0028 per million tokens (cache hit) - Updated Apr 29, 2026
inputPrice: 0, // the inputs are priced as cache read/write, so `inputPrice` should be 0
// the peak/off-peak pricing policy has not been implemented yet - Updated 2026-08-01
outputPrice: 0.28, // $0.28 per million tokens - Updated 2026-08-01
cacheWritesPrice: 0.14, // $0.14 per million tokens (cache miss) - Updated 2026-08-01
cacheReadsPrice: 0.0028, // $0.0028 per million tokens (cache hit) - Updated 2026-08-01
description: `DeepSeek-V4-Flash is DeepSeek's fast, cost-efficient V4 model. It supports thinking and non-thinking modes, JSON output, tool calls, chat prefix completion (beta), and FIM completion (beta) in non-thinking mode.`,
},
"deepseek-v4-pro": {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
maxTokens: 384_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "low", "medium", "high", "xhigh"],
supportsReasoningEffort: ["disable", "high", "max"], // Updated 2026-08-01
preserveReasoning: true,
reasoningEffort: "high",
// TODO(deepseek): Re-check V4 Pro discounted prices after DeepSeek's 2026-05-31 discount end date.
inputPrice: 0.435, // $0.435 per million tokens (cache miss, discounted) - Updated Apr 29, 2026
outputPrice: 0.87, // $0.87 per million tokens (discounted) - Updated Apr 29, 2026
cacheWritesPrice: 0.435, // $0.435 per million tokens (cache miss, discounted) - Updated Apr 29, 2026
cacheReadsPrice: 0.003625, // $0.003625 per million tokens (cache hit, discounted) - Updated Apr 29, 2026
inputPrice: 0, // the inputs are priced as cache read/write, so `inputPrice` should be 0
// the peak/off-peak pricing policy has not been implemented yet - Updated 2026-08-01
outputPrice: 0.87, // $0.87 per million tokens - Updated 2026-08-01
cacheWritesPrice: 0.435, // $0.435 per million tokens (cache miss) - Updated 2026-08-01
cacheReadsPrice: 0.003625, // $0.003625 per million tokens (cache hit) - Updated 2026-08-01
description: `DeepSeek-V4-Pro is DeepSeek's strongest V4 model for reasoning, coding, long-context, and agentic workloads. It supports thinking and non-thinking modes, JSON output, tool calls, chat prefix completion (beta), and FIM completion (beta) in non-thinking mode.`,
},
// TODO(deepseek): Remove this compatibility alias after DeepSeek's 2026-07-24 retirement date.
"deepseek-chat": {
maxTokens: 8192, // 8K max output
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025
outputPrice: 0.42, // $0.42 per million tokens - Updated Dec 9, 2025
cacheWritesPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025
cacheReadsPrice: 0.028, // $0.028 per million tokens (cache hit) - Updated Dec 9, 2025
description: `Legacy compatibility alias for the non-thinking mode of deepseek-v4-flash. DeepSeek plans to deprecate this model name on 2026-07-24.`,
},
// TODO(deepseek): Remove this compatibility alias after DeepSeek's 2026-07-24 retirement date.
"deepseek-reasoner": {
maxTokens: 8192, // 8K max output
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: true,
preserveReasoning: true,
inputPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025
outputPrice: 0.42, // $0.42 per million tokens - Updated Dec 9, 2025
cacheWritesPrice: 0.28, // $0.28 per million tokens (cache miss) - Updated Dec 9, 2025
cacheReadsPrice: 0.028, // $0.028 per million tokens (cache hit) - Updated Dec 9, 2025
description: `Legacy compatibility alias for the thinking mode of deepseek-v4-flash. DeepSeek plans to deprecate this model name on 2026-07-24.`,
},
} as const satisfies Record<string, ModelInfo>

// https://api-docs.deepseek.com/quick_start/parameter_settings
export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.3
export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about setting this to 0 completely. I know their docs say 0 for coding, but I think you still need a bit of variance there for 'creativity', 0.3 felt right to me.

@WHMHammer WHMHammer Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can remove this change if you insist. But according to https://api-docs.deepseek.com/guides/thinking_mode:

Thinking mode does not support the temperature, top_p, presence_penalty, or frequency_penalty parameters.

So it's only going to affect the case when the reasoning effor is set to "disable" anyway. Besides, this is just the default value. The users can always adjust the actual temperature in the extension.

171 changes: 81 additions & 90 deletions src/api/providers/__tests__/deepseek.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ vi.mock("openai", () => {
import OpenAI from "openai"
import type { Anthropic } from "@anthropic-ai/sdk"

import { deepSeekDefaultModelId, DEEP_SEEK_DEFAULT_TEMPERATURE, type ModelInfo } from "@roo-code/types"
import { deepSeekDefaultModelId, DEEP_SEEK_DEFAULT_TEMPERATURE, type ModelInfo, DeepSeekModelId } from "@roo-code/types"

import type { ApiHandlerOptions } from "../../../shared/api"

import { DeepSeekHandler } from "../deepseek"
import { DeepSeekHandler, normalizeDeepSeekReasoningEffort } from "../deepseek"

describe("DeepSeekHandler", () => {
let handler: DeepSeekHandler
Expand All @@ -137,7 +137,7 @@ describe("DeepSeekHandler", () => {
beforeEach(() => {
mockOptions = {
deepSeekApiKey: "test-api-key",
apiModelId: "deepseek-chat",
apiModelId: "deepseek-v4-flash",
deepSeekBaseUrl: "https://api.deepseek.com",
}
handler = new DeepSeekHandler(mockOptions)
Expand Down Expand Up @@ -208,11 +208,11 @@ describe("DeepSeekHandler", () => {
const model = handler.getModel()
expect(model.id).toBe(mockOptions.apiModelId)
expect(model.info).toBeDefined()
expect(model.info.maxTokens).toBe(8192) // deepseek-chat legacy alias has 8K max
expect(model.info.contextWindow).toBe(128_000)
expect(model.info.supportsImages).toBe(false)
expect(model.info.maxTokens).toBe(384_000)
expect(model.info.contextWindow).toBe(1_000_000)
expect(model.info.supportsImages).toBe(true)
expect(model.info.supportsPromptCache).toBe(true) // Should be true now
expect((model.info as ModelInfo).preserveReasoning).toBeUndefined()
expect((model.info as ModelInfo).preserveReasoning).toBe(true)
})

it("should use deepseek-v4-flash as the default model ID for new configs", () => {
Expand All @@ -226,21 +226,7 @@ describe("DeepSeekHandler", () => {
expect(model.info.maxTokens).toBe(384_000)
expect(model.info.contextWindow).toBe(1_000_000)
expect(model.info.supportsImages).toBe(true)
expect((model.info as ModelInfo).supportsReasoningEffort).toContain("xhigh")
})

it("should return correct model info for deepseek-reasoner", () => {
const handlerWithReasoner = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-reasoner",
})
const model = handlerWithReasoner.getModel()
expect(model.id).toBe("deepseek-reasoner")
expect(model.info).toBeDefined()
expect(model.info.maxTokens).toBe(8192) // deepseek-reasoner has 8K max
expect(model.info.contextWindow).toBe(128_000)
expect(model.info.supportsImages).toBe(false)
expect(model.info.supportsPromptCache).toBe(true)
expect((model.info as ModelInfo).supportsReasoningEffort).toContain("max")
})

it("should return correct model info for deepseek-v4-pro", () => {
Expand All @@ -259,31 +245,6 @@ describe("DeepSeekHandler", () => {
expect((model.info as ModelInfo).reasoningEffort).toBe("high")
})

it("should have preserveReasoning enabled for deepseek-reasoner to support interleaved thinking", () => {
// This is critical for DeepSeek's interleaved thinking mode with tool calls.
// See: https://api-docs.deepseek.com/guides/thinking_mode
// The reasoning_content needs to be passed back during tool call continuation
// within the same turn for the model to continue reasoning properly.
const handlerWithReasoner = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-reasoner",
})
const model = handlerWithReasoner.getModel()
// Cast to ModelInfo to access preserveReasoning which is an optional property
expect((model.info as ModelInfo).preserveReasoning).toBe(true)
})

it("should NOT have preserveReasoning enabled for deepseek-chat", () => {
// deepseek-chat doesn't use thinking mode, so no need to preserve reasoning
const chatHandler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-chat",
})
const model = chatHandler.getModel()
// Cast to ModelInfo to access preserveReasoning which is an optional property
expect((model.info as ModelInfo).preserveReasoning).toBeUndefined()
})

it("should return provided model ID with default model info if model does not exist", () => {
const handlerWithInvalidModel = new DeepSeekHandler({
...mockOptions,
Expand Down Expand Up @@ -540,10 +501,10 @@ describe("DeepSeekHandler", () => {
},
]

it("should handle reasoning_content in streaming responses for deepseek-reasoner", async () => {
it("should handle reasoning_content in streaming responses for deepseek-v4-pro", async () => {
const reasonerHandler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-reasoner",
apiModelId: "deepseek-v4-pro",
})

const stream = reasonerHandler.createMessage(systemPrompt, messages)
Expand All @@ -559,10 +520,10 @@ describe("DeepSeekHandler", () => {
expect(reasoningChunks[1].text).toBe(" I'll analyze step by step.")
})

it("should pass thinking parameter for deepseek-reasoner model", async () => {
it("should pass thinking parameter for deepseek-v4-pro model", async () => {
const reasonerHandler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-reasoner",
apiModelId: "deepseek-v4-pro",
})

const stream = reasonerHandler.createMessage(systemPrompt, messages)
Expand All @@ -579,7 +540,7 @@ describe("DeepSeekHandler", () => {
{}, // Empty path options for non-Azure URLs
)
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.reasoning_effort).toBeUndefined()
expect(callArgs.reasoning_effort).toBe("high")
})

it("should enable thinking by default for deepseek-v4-flash", async () => {
Expand Down Expand Up @@ -619,27 +580,6 @@ describe("DeepSeekHandler", () => {
expect(callArgs.max_completion_tokens).toBe(32_000)
})

it("should map xhigh reasoning effort to DeepSeek max effort", async () => {
const v4Handler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-v4-pro",
reasoningEffort: "xhigh",
})

const stream = v4Handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
// Consume the stream
}

expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
thinking: { type: "enabled" },
reasoning_effort: "max",
}),
{},
)
})

it("should disable thinking for deepseek-v4 models when reasoning is disabled", async () => {
const v4Handler = new DeepSeekHandler({
...mockOptions,
Expand Down Expand Up @@ -674,26 +614,10 @@ describe("DeepSeekHandler", () => {
expect(callArgs.temperature).toBe(DEEP_SEEK_DEFAULT_TEMPERATURE)
})

it("should NOT pass thinking parameter for deepseek-chat model", async () => {
const chatHandler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-chat",
})

const stream = chatHandler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
// Consume the stream
}

// Verify that the thinking parameter was NOT passed to the API
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.thinking).toBeUndefined()
})

it("should handle tool calls with reasoning_content", async () => {
const reasonerHandler = new DeepSeekHandler({
...mockOptions,
apiModelId: "deepseek-reasoner",
apiModelId: "deepseek-v4-pro",
})

const tools: any[] = [
Expand Down Expand Up @@ -723,4 +647,71 @@ describe("DeepSeekHandler", () => {
expect(toolCallChunks[0].name).toBe("get_weather")
})
})

describe("normalizeDeepSeekReasoningEffort", () => {
// https://api-docs.deepseek.com/guides/thinking_mode/
it("should map acceptable reasoning efforts the same way as stated by the official documentation", async () => {
const mappings: {
modelId: DeepSeekModelId
rawReasoningEffort: string
mappedReasoningEffort: string | undefined
}[] = [
{
modelId: "deepseek-v4-flash",
rawReasoningEffort: "disable",
mappedReasoningEffort: undefined,
},
{
modelId: "deepseek-v4-flash",
rawReasoningEffort: "low",
mappedReasoningEffort: "low",
},
{
modelId: "deepseek-v4-flash",
rawReasoningEffort: "high",
mappedReasoningEffort: "high",
},
{
modelId: "deepseek-v4-flash",
rawReasoningEffort: "xhigh",
mappedReasoningEffort: "high",
},
{
modelId: "deepseek-v4-flash",
rawReasoningEffort: "max",
mappedReasoningEffort: "max",
},
{
modelId: "deepseek-v4-pro",
rawReasoningEffort: "disable",
mappedReasoningEffort: undefined,
},
{
modelId: "deepseek-v4-pro",
rawReasoningEffort: "low",
mappedReasoningEffort: "high",
},
{
modelId: "deepseek-v4-pro",
rawReasoningEffort: "high",
mappedReasoningEffort: "high",
},
{
modelId: "deepseek-v4-pro",
rawReasoningEffort: "xhigh",
mappedReasoningEffort: "max",
},
{
modelId: "deepseek-v4-pro",
rawReasoningEffort: "max",
mappedReasoningEffort: "max",
},
]

for (const { modelId, rawReasoningEffort, mappedReasoningEffort } of mappings) {
const result = normalizeDeepSeekReasoningEffort(modelId, rawReasoningEffort)
expect(result).toBe(mappedReasoningEffort)
}
})
})
})
Loading
Loading