Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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.

100 changes: 12 additions & 88 deletions src/api/providers/__tests__/deepseek.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
52 changes: 43 additions & 9 deletions src/api/providers/deepseek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DEEP_SEEK_DEFAULT_TEMPERATURE,
OPENAI_AZURE_AI_INFERENCE_PATH,
type ModelInfo,
DeepSeekModelId,
} from "@roo-code/types"

import type { ApiHandlerOptions } from "../../shared/api"
Expand All @@ -23,7 +24,7 @@ import { handleOpenAIError } from "./utils/error-handler"
// Custom interface for DeepSeek params to support thinking mode
type DeepSeekChatCompletionParams = Omit<OpenAI.Chat.ChatCompletionCreateParamsStreaming, "reasoning_effort"> & {
thinking?: { type: "enabled" | "disabled" }
reasoning_effort?: "high" | "max"
reasoning_effort?: "low" | "high" | "max"
}

const deepSeekV4ThinkingModels = new Set(["deepseek-v4-flash", "deepseek-v4-pro"])
Expand All @@ -37,16 +38,49 @@ const isDeepSeekThinkingEnabled = (modelId: string, options: ApiHandlerOptions)
return false
}

return modelId === "deepseek-reasoner" || supportsDeepSeekThinkingToggle(modelId)
return supportsDeepSeekThinkingToggle(modelId)
Comment thread
WHMHammer marked this conversation as resolved.
}

const normalizeDeepSeekReasoningEffort = (reasoningEffort?: string): "high" | "max" | undefined => {
if (!reasoningEffort || reasoningEffort === "disable") {
return undefined
// https://api-docs.deepseek.com/guides/thinking_mode/
const normalizeDeepSeekReasoningEffort = (
modelId: DeepSeekModelId,
reasoningEffort?: string,
): "low" | "high" | "max" | undefined => {
switch (modelId) {
case "deepseek-v4-flash":
switch (reasoningEffort) {
case "low":
return "low"

case "high":
return "high"

case "xhigh":
return "high"

case "max":
return "max"
}
break

case "deepseek-v4-pro":
switch (reasoningEffort) {
case "low":
return "high"

case "high":
return "high"

case "xhigh":
return "max"

case "max":
return "max"
}
break
}

// DeepSeek currently maps low/medium to high and xhigh to max in thinking mode.
return reasoningEffort === "xhigh" ? "max" : "high"
return undefined
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Use the computed maxTokens from getModelParams rather than raw model metadata.
Expand Down Expand Up @@ -92,7 +126,7 @@ export class DeepSeekHandler extends OpenAiHandler {
messages: Anthropic.Messages.MessageParam[],
metadata?: ApiHandlerCreateMessageMetadata,
): ApiStream {
const modelId = this.options.apiModelId ?? deepSeekDefaultModelId
const modelId = (this.options.apiModelId as DeepSeekModelId) ?? deepSeekDefaultModelId
const { info: modelInfo, temperature, reasoningEffort, maxTokens } = this.getModel()

const isThinkingModel = isDeepSeekThinkingEnabled(modelId, this.options)
Expand All @@ -101,7 +135,7 @@ export class DeepSeekHandler extends OpenAiHandler {
: isThinkingModel
? ({ type: "enabled" } as const)
: undefined
const deepSeekReasoningEffort = isThinkingModel ? normalizeDeepSeekReasoningEffort(reasoningEffort) : undefined
const deepSeekReasoningEffort = normalizeDeepSeekReasoningEffort(modelId, reasoningEffort)

// Convert messages to R1 format (merges consecutive same-role messages)
// This is required for DeepSeek which does not support successive messages with the same role
Expand Down
10 changes: 2 additions & 8 deletions src/core/config/__tests__/importExport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2510,21 +2510,15 @@ describe("importExport", () => {
{
testCase: "supportsReasoningBudget is false",
providerName: "deepseek-provider",
modelId: "deepseek-chat",
modelId: "deepseek-v4-flash",
providerId: "deepseek-id",
},
{
testCase: "requiredReasoningBudget is false",
providerName: "deepseek-provider-2",
modelId: "deepseek-coder",
modelId: "deepseek-v4-pro",
providerId: "deepseek-id-2",
},
{
testCase: "both supportsReasoningBudget and requiredReasoningBudget are false",
providerName: "deepseek-provider-3",
modelId: "deepseek-reasoner",
providerId: "deepseek-id-3",
},
])(
"should exclude modelMaxTokens and modelMaxThinkingTokens when $testCase",
async ({ providerName, modelId, providerId }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ describe("webviewMessageHandler - requestRouterModels provider filter", () => {

getModelsMock.mockImplementation(async (options: any) => {
if (options?.provider === "deepseek") {
return { "deepseek-chat": { contextWindow: 128000, supportsPromptCache: true } }
return { "deepseek-v4-flash": { contextWindow: 1_000_000, supportsPromptCache: true } }
}

switch (options?.provider) {
Expand Down Expand Up @@ -163,7 +163,7 @@ describe("webviewMessageHandler - requestRouterModels provider filter", () => {
)
expect(call).toBeTruthy()
expect(call[0].routerModels.deepseek).toEqual({
"deepseek-chat": { contextWindow: 128000, supportsPromptCache: true },
"deepseek-v4-flash": { contextWindow: 1_000_000, supportsPromptCache: true },
})
})

Expand Down
Loading