mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: add direct DeepSeek API support for V3/3.2 models
- Add model aliases (deepseek-v3, deepseek-3.2) that map to deepseek-chat - Update DeepSeek handler to map alias model names to official API model names - Add tests to verify model alias mapping works correctly - Supports DeepSeek 3.2 models as requested in issue #9779
This commit is contained in:
parent
d48fb302aa
commit
8eb5abf7f8
3 changed files with 119 additions and 14 deletions
|
|
@ -1,22 +1,35 @@
|
|||
import type { ModelInfo } from "../model.js"
|
||||
|
||||
// https://platform.deepseek.com/docs/api
|
||||
// https://api-docs.deepseek.com/quick_start/pricing
|
||||
export type DeepSeekModelId = keyof typeof deepSeekModels
|
||||
|
||||
export const deepSeekDefaultModelId: DeepSeekModelId = "deepseek-chat"
|
||||
|
||||
// DeepSeek V3 model info (shared between deepseek-chat and aliases)
|
||||
const deepSeekV3Info: ModelInfo = {
|
||||
maxTokens: 8192, // 8K max output
|
||||
contextWindow: 128_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 0.56, // $0.56 per million tokens (cache miss) - Updated Sept 5, 2025
|
||||
outputPrice: 1.68, // $1.68 per million tokens - Updated Sept 5, 2025
|
||||
cacheWritesPrice: 0.56, // $0.56 per million tokens (cache miss) - Updated Sept 5, 2025
|
||||
cacheReadsPrice: 0.07, // $0.07 per million tokens (cache hit) - Updated Sept 5, 2025
|
||||
description: `DeepSeek-V3 achieves a significant breakthrough in inference speed over previous models. It tops the leaderboard among open-source models and rivals the most advanced closed-source models globally.`,
|
||||
}
|
||||
|
||||
export const deepSeekModels = {
|
||||
"deepseek-chat": {
|
||||
maxTokens: 8192, // 8K max output
|
||||
contextWindow: 128_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 0.56, // $0.56 per million tokens (cache miss) - Updated Sept 5, 2025
|
||||
outputPrice: 1.68, // $1.68 per million tokens - Updated Sept 5, 2025
|
||||
cacheWritesPrice: 0.56, // $0.56 per million tokens (cache miss) - Updated Sept 5, 2025
|
||||
cacheReadsPrice: 0.07, // $0.07 per million tokens (cache hit) - Updated Sept 5, 2025
|
||||
description: `DeepSeek-V3 achieves a significant breakthrough in inference speed over previous models. It tops the leaderboard among open-source models and rivals the most advanced closed-source models globally.`,
|
||||
"deepseek-chat": deepSeekV3Info,
|
||||
// Aliases for DeepSeek V3 - these all map to deepseek-chat when calling the API
|
||||
"deepseek-v3": {
|
||||
...deepSeekV3Info,
|
||||
description: `DeepSeek-V3 (alias for deepseek-chat). ${deepSeekV3Info.description}`,
|
||||
},
|
||||
"deepseek-3.2": {
|
||||
...deepSeekV3Info,
|
||||
description: `DeepSeek V3.2 (alias for deepseek-chat). ${deepSeekV3Info.description}`,
|
||||
},
|
||||
"deepseek-reasoner": {
|
||||
maxTokens: 65536, // 64K max output for reasoning mode
|
||||
|
|
@ -32,4 +45,12 @@ export const deepSeekModels = {
|
|||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
// Map of model aliases to their official API model names
|
||||
// The DeepSeek API uses specific model names, but users may use alternative names
|
||||
export const deepSeekModelAliases: Record<string, string> = {
|
||||
"deepseek-v3": "deepseek-chat",
|
||||
"deepseek-3.2": "deepseek-chat",
|
||||
"deepseek-3.2-exp": "deepseek-chat",
|
||||
}
|
||||
|
||||
export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6
|
||||
|
|
|
|||
|
|
@ -147,6 +147,44 @@ describe("DeepSeekHandler", () => {
|
|||
const _handler = new DeepSeekHandler(mockOptions)
|
||||
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: mockOptions.deepSeekApiKey }))
|
||||
})
|
||||
|
||||
it("should map deepseek-v3 alias to deepseek-chat for API calls", async () => {
|
||||
vi.clearAllMocks()
|
||||
const handlerWithV3 = new DeepSeekHandler({
|
||||
...mockOptions,
|
||||
apiModelId: "deepseek-v3",
|
||||
})
|
||||
const stream = handlerWithV3.createMessage("test", [])
|
||||
for await (const _chunk of stream) {
|
||||
// consume stream
|
||||
}
|
||||
// Verify the API was called with deepseek-chat (not deepseek-v3)
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "deepseek-chat",
|
||||
}),
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it("should map deepseek-3.2 alias to deepseek-chat for API calls", async () => {
|
||||
vi.clearAllMocks()
|
||||
const handlerWith32 = new DeepSeekHandler({
|
||||
...mockOptions,
|
||||
apiModelId: "deepseek-3.2",
|
||||
})
|
||||
const stream = handlerWith32.createMessage("test", [])
|
||||
for await (const _chunk of stream) {
|
||||
// consume stream
|
||||
}
|
||||
// Verify the API was called with deepseek-chat (not deepseek-3.2)
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "deepseek-chat",
|
||||
}),
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getModel", () => {
|
||||
|
|
@ -174,6 +212,32 @@ describe("DeepSeekHandler", () => {
|
|||
expect(model.info.supportsPromptCache).toBe(true)
|
||||
})
|
||||
|
||||
it("should return correct model info for deepseek-v3 alias", () => {
|
||||
const handlerWithV3 = new DeepSeekHandler({
|
||||
...mockOptions,
|
||||
apiModelId: "deepseek-v3",
|
||||
})
|
||||
const model = handlerWithV3.getModel()
|
||||
expect(model.id).toBe("deepseek-v3") // Returns user's model ID
|
||||
expect(model.info).toBeDefined()
|
||||
expect(model.info.maxTokens).toBe(8192) // Same as deepseek-chat
|
||||
expect(model.info.contextWindow).toBe(128_000)
|
||||
expect(model.info.supportsNativeTools).toBe(true)
|
||||
})
|
||||
|
||||
it("should return correct model info for deepseek-3.2 alias", () => {
|
||||
const handlerWith32 = new DeepSeekHandler({
|
||||
...mockOptions,
|
||||
apiModelId: "deepseek-3.2",
|
||||
})
|
||||
const model = handlerWith32.getModel()
|
||||
expect(model.id).toBe("deepseek-3.2") // Returns user's model ID
|
||||
expect(model.info).toBeDefined()
|
||||
expect(model.info.maxTokens).toBe(8192) // Same as deepseek-chat
|
||||
expect(model.info.contextWindow).toBe(128_000)
|
||||
expect(model.info.supportsNativeTools).toBe(true)
|
||||
})
|
||||
|
||||
it("should return provided model ID with default model info if model does not exist", () => {
|
||||
const handlerWithInvalidModel = new DeepSeekHandler({
|
||||
...mockOptions,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { deepSeekModels, deepSeekDefaultModelId } from "@roo-code/types"
|
||||
import { deepSeekModels, deepSeekDefaultModelId, deepSeekModelAliases } from "@roo-code/types"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
|
||||
|
|
@ -7,20 +7,40 @@ import { getModelParams } from "../transform/model-params"
|
|||
|
||||
import { OpenAiHandler } from "./openai"
|
||||
|
||||
/**
|
||||
* Maps a user-provided model ID to the official DeepSeek API model name.
|
||||
* The DeepSeek API uses specific model names (deepseek-chat, deepseek-reasoner),
|
||||
* but users may use alternative names like deepseek-v3, deepseek-3.2, etc.
|
||||
*/
|
||||
function getApiModelId(modelId: string): string {
|
||||
return deepSeekModelAliases[modelId] ?? modelId
|
||||
}
|
||||
|
||||
export class DeepSeekHandler extends OpenAiHandler {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
const userModelId = options.apiModelId ?? deepSeekDefaultModelId
|
||||
// Map the user's model ID to the official API model name
|
||||
const apiModelId = getApiModelId(userModelId)
|
||||
|
||||
super({
|
||||
...options,
|
||||
openAiApiKey: options.deepSeekApiKey ?? "not-provided",
|
||||
openAiModelId: options.apiModelId ?? deepSeekDefaultModelId,
|
||||
openAiModelId: apiModelId, // Use the mapped API model ID
|
||||
openAiBaseUrl: options.deepSeekBaseUrl ?? "https://api.deepseek.com",
|
||||
openAiStreamingEnabled: true,
|
||||
includeMaxTokens: true,
|
||||
})
|
||||
|
||||
// Store the original user model ID for getModel()
|
||||
this.userModelId = userModelId
|
||||
}
|
||||
|
||||
// Store the user's original model ID (before alias mapping)
|
||||
private userModelId: string
|
||||
|
||||
override getModel() {
|
||||
const id = this.options.apiModelId ?? deepSeekDefaultModelId
|
||||
// Use the user's original model ID for info lookup (so they see the model they selected)
|
||||
const id = this.userModelId
|
||||
const info = deepSeekModels[id as keyof typeof deepSeekModels] || deepSeekModels[deepSeekDefaultModelId]
|
||||
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
|
||||
return { id, info, ...params }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue