Skip to content

Commit 4b45a4e

Browse files
Support new LLM provider: Doubao (#6345)
Co-authored-by: Daniel Riccio <[email protected]>
1 parent 4015a58 commit 4b45a4e

File tree

29 files changed

+244
-0
lines changed

29 files changed

+244
-0
lines changed

packages/types/src/provider-settings.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const providerNames = [
2424
"mistral",
2525
"moonshot",
2626
"deepseek",
27+
"doubao",
2728
"unbound",
2829
"requesty",
2930
"human-relay",
@@ -194,6 +195,11 @@ const deepSeekSchema = apiModelIdProviderModelSchema.extend({
194195
deepSeekApiKey: z.string().optional(),
195196
})
196197

198+
const doubaoSchema = apiModelIdProviderModelSchema.extend({
199+
doubaoBaseUrl: z.string().optional(),
200+
doubaoApiKey: z.string().optional(),
201+
})
202+
197203
const moonshotSchema = apiModelIdProviderModelSchema.extend({
198204
moonshotBaseUrl: z
199205
.union([z.literal("https://api.moonshot.ai/v1"), z.literal("https://api.moonshot.cn/v1")])
@@ -266,6 +272,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
266272
openAiNativeSchema.merge(z.object({ apiProvider: z.literal("openai-native") })),
267273
mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })),
268274
deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })),
275+
doubaoSchema.merge(z.object({ apiProvider: z.literal("doubao") })),
269276
moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })),
270277
unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })),
271278
requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })),
@@ -297,6 +304,7 @@ export const providerSettingsSchema = z.object({
297304
...openAiNativeSchema.shape,
298305
...mistralSchema.shape,
299306
...deepSeekSchema.shape,
307+
...doubaoSchema.shape,
300308
...moonshotSchema.shape,
301309
...unboundSchema.shape,
302310
...requestySchema.shape,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { ModelInfo } from "../model.js"
2+
3+
export const doubaoDefaultModelId = "doubao-seed-1-6-250615"
4+
5+
export const doubaoModels = {
6+
"doubao-seed-1-6-250615": {
7+
maxTokens: 32_768,
8+
contextWindow: 128_000,
9+
supportsImages: true,
10+
supportsPromptCache: true,
11+
inputPrice: 0.0001, // $0.0001 per million tokens (cache miss)
12+
outputPrice: 0.0004, // $0.0004 per million tokens
13+
cacheWritesPrice: 0.0001, // $0.0001 per million tokens (cache miss)
14+
cacheReadsPrice: 0.00002, // $0.00002 per million tokens (cache hit)
15+
description: `Doubao Seed 1.6 is a powerful model designed for high-performance tasks with extensive context handling.`,
16+
},
17+
"doubao-seed-1-6-thinking-250715": {
18+
maxTokens: 32_768,
19+
contextWindow: 128_000,
20+
supportsImages: true,
21+
supportsPromptCache: true,
22+
inputPrice: 0.0002, // $0.0002 per million tokens
23+
outputPrice: 0.0008, // $0.0008 per million tokens
24+
cacheWritesPrice: 0.0002, // $0.0002 per million
25+
cacheReadsPrice: 0.00004, // $0.00004 per million tokens (cache hit)
26+
description: `Doubao Seed 1.6 Thinking is optimized for reasoning tasks, providing enhanced performance in complex problem-solving scenarios.`,
27+
},
28+
"doubao-seed-1-6-flash-250715": {
29+
maxTokens: 32_768,
30+
contextWindow: 128_000,
31+
supportsImages: true,
32+
supportsPromptCache: true,
33+
inputPrice: 0.00015, // $0.00015 per million tokens
34+
outputPrice: 0.0006, // $0.0006 per million tokens
35+
cacheWritesPrice: 0.00015, // $0.00015 per million
36+
cacheReadsPrice: 0.00003, // $0.00003 per million tokens (cache hit)
37+
description: `Doubao Seed 1.6 Flash is tailored for speed and efficiency, making it ideal for applications requiring rapid responses.`,
38+
},
39+
} as const satisfies Record<string, ModelInfo>
40+
41+
export const doubaoDefaultModelInfo: ModelInfo = doubaoModels[doubaoDefaultModelId]
42+
43+
export const DOUBAO_API_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
44+
export const DOUBAO_API_CHAT_PATH = "/chat/completions"

packages/types/src/providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export * from "./unbound.js"
2020
export * from "./vertex.js"
2121
export * from "./vscode-llm.js"
2222
export * from "./xai.js"
23+
export * from "./doubao.js"

src/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
LiteLLMHandler,
3232
ClaudeCodeHandler,
3333
SambaNovaHandler,
34+
DoubaoHandler,
3435
} from "./providers"
3536

3637
export interface SingleCompletionHandler {
@@ -92,6 +93,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
9293
return new OpenAiNativeHandler(options)
9394
case "deepseek":
9495
return new DeepSeekHandler(options)
96+
case "doubao":
97+
return new DoubaoHandler(options)
9598
case "moonshot":
9699
return new MoonshotHandler(options)
97100
case "vscode-lm":

src/api/providers/doubao.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { OpenAiHandler } from "./openai"
2+
import type { ApiHandlerOptions } from "../../shared/api"
3+
import { DOUBAO_API_BASE_URL, doubaoDefaultModelId, doubaoModels } from "@roo-code/types"
4+
import { getModelParams } from "../transform/model-params"
5+
import { ApiStreamUsageChunk } from "../transform/stream"
6+
7+
// Core types for Doubao API
8+
interface ChatCompletionMessageParam {
9+
role: "system" | "user" | "assistant" | "developer"
10+
content:
11+
| string
12+
| Array<{
13+
type: "text" | "image_url"
14+
text?: string
15+
image_url?: { url: string }
16+
}>
17+
}
18+
19+
interface ChatCompletionParams {
20+
model: string
21+
messages: ChatCompletionMessageParam[]
22+
temperature?: number
23+
stream?: boolean
24+
stream_options?: { include_usage: boolean }
25+
max_completion_tokens?: number
26+
}
27+
28+
interface ChatCompletion {
29+
choices: Array<{
30+
message: {
31+
content: string
32+
}
33+
}>
34+
usage?: {
35+
prompt_tokens: number
36+
completion_tokens: number
37+
}
38+
}
39+
40+
interface ChatCompletionChunk {
41+
choices: Array<{
42+
delta: {
43+
content?: string
44+
}
45+
}>
46+
usage?: {
47+
prompt_tokens: number
48+
completion_tokens: number
49+
}
50+
}
51+
52+
export class DoubaoHandler extends OpenAiHandler {
53+
constructor(options: ApiHandlerOptions) {
54+
super({
55+
...options,
56+
openAiApiKey: options.doubaoApiKey ?? "not-provided",
57+
openAiModelId: options.apiModelId ?? doubaoDefaultModelId,
58+
openAiBaseUrl: options.doubaoBaseUrl ?? DOUBAO_API_BASE_URL,
59+
openAiStreamingEnabled: true,
60+
includeMaxTokens: true,
61+
})
62+
}
63+
64+
override getModel() {
65+
const id = this.options.apiModelId ?? doubaoDefaultModelId
66+
const info = doubaoModels[id as keyof typeof doubaoModels] || doubaoModels[doubaoDefaultModelId]
67+
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
68+
return { id, info, ...params }
69+
}
70+
71+
// Override to handle Doubao's usage metrics, including caching.
72+
protected override processUsageMetrics(usage: any): ApiStreamUsageChunk {
73+
return {
74+
type: "usage",
75+
inputTokens: usage?.prompt_tokens || 0,
76+
outputTokens: usage?.completion_tokens || 0,
77+
cacheWriteTokens: usage?.prompt_tokens_details?.cache_miss_tokens,
78+
cacheReadTokens: usage?.prompt_tokens_details?.cached_tokens,
79+
}
80+
}
81+
}

src/api/providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { AwsBedrockHandler } from "./bedrock"
44
export { ChutesHandler } from "./chutes"
55
export { ClaudeCodeHandler } from "./claude-code"
66
export { DeepSeekHandler } from "./deepseek"
7+
export { DoubaoHandler } from "./doubao"
78
export { MoonshotHandler } from "./moonshot"
89
export { FakeAIHandler } from "./fake-ai"
910
export { GeminiHandler } from "./gemini"

webview-ui/src/components/settings/ApiOptions.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
litellmDefaultModelId,
1616
openAiNativeDefaultModelId,
1717
anthropicDefaultModelId,
18+
doubaoDefaultModelId,
1819
claudeCodeDefaultModelId,
1920
geminiDefaultModelId,
2021
deepSeekDefaultModelId,
@@ -57,6 +58,7 @@ import {
5758
Chutes,
5859
ClaudeCode,
5960
DeepSeek,
61+
Doubao,
6062
Gemini,
6163
Glama,
6264
Groq,
@@ -292,6 +294,7 @@ const ApiOptions = ({
292294
"openai-native": { field: "apiModelId", default: openAiNativeDefaultModelId },
293295
gemini: { field: "apiModelId", default: geminiDefaultModelId },
294296
deepseek: { field: "apiModelId", default: deepSeekDefaultModelId },
297+
doubao: { field: "apiModelId", default: doubaoDefaultModelId },
295298
moonshot: { field: "apiModelId", default: moonshotDefaultModelId },
296299
mistral: { field: "apiModelId", default: mistralDefaultModelId },
297300
xai: { field: "apiModelId", default: xaiDefaultModelId },
@@ -475,6 +478,10 @@ const ApiOptions = ({
475478
<DeepSeek apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
476479
)}
477480

481+
{selectedProvider === "doubao" && (
482+
<Doubao apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
483+
)}
484+
478485
{selectedProvider === "moonshot" && (
479486
<Moonshot apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
480487
)}

webview-ui/src/components/settings/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import {
1414
groqModels,
1515
chutesModels,
1616
sambaNovaModels,
17+
doubaoModels,
1718
} from "@roo-code/types"
1819

1920
export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, ModelInfo>>> = {
2021
anthropic: anthropicModels,
2122
"claude-code": claudeCodeModels,
2223
bedrock: bedrockModels,
2324
deepseek: deepSeekModels,
25+
doubao: doubaoModels,
2426
moonshot: moonshotModels,
2527
gemini: geminiModels,
2628
mistral: mistralModels,
@@ -37,6 +39,7 @@ export const PROVIDERS = [
3739
{ value: "anthropic", label: "Anthropic" },
3840
{ value: "claude-code", label: "Claude Code" },
3941
{ value: "gemini", label: "Google Gemini" },
42+
{ value: "doubao", label: "Doubao" },
4043
{ value: "deepseek", label: "DeepSeek" },
4144
{ value: "moonshot", label: "Moonshot" },
4245
{ value: "openai-native", label: "OpenAI" },
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useCallback } from "react"
2+
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
3+
4+
import type { ProviderSettings } from "@roo-code/types"
5+
6+
import { useAppTranslation } from "@src/i18n/TranslationContext"
7+
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
8+
9+
import { inputEventTransform } from "../transforms"
10+
11+
type DoubaoProps = {
12+
apiConfiguration: ProviderSettings
13+
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
14+
}
15+
16+
export const Doubao = ({ apiConfiguration, setApiConfigurationField }: DoubaoProps) => {
17+
const { t } = useAppTranslation()
18+
19+
const handleInputChange = useCallback(
20+
<K extends keyof ProviderSettings, E>(
21+
field: K,
22+
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
23+
) =>
24+
(event: E | Event) => {
25+
setApiConfigurationField(field, transform(event as E))
26+
},
27+
[setApiConfigurationField],
28+
)
29+
30+
return (
31+
<>
32+
<VSCodeTextField
33+
value={apiConfiguration?.doubaoApiKey || ""}
34+
type="password"
35+
onInput={handleInputChange("doubaoApiKey")}
36+
placeholder={t("settings:placeholders.apiKey")}
37+
className="w-full">
38+
<label className="block font-medium mb-1">{t("settings:providers.doubaoApiKey")}</label>
39+
</VSCodeTextField>
40+
<div className="text-sm text-vscode-descriptionForeground -mt-2">
41+
{t("settings:providers.apiKeyStorageNotice")}
42+
</div>
43+
{!apiConfiguration?.doubaoApiKey && (
44+
<VSCodeButtonLink
45+
href="https://www.volcengine.com/experience/ark?model=doubao-1-5-thinking-vision-pro-250428"
46+
appearance="secondary">
47+
{t("settings:providers.getDoubaoApiKey")}
48+
</VSCodeButtonLink>
49+
)}
50+
</>
51+
)
52+
}

webview-ui/src/components/settings/providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { Bedrock } from "./Bedrock"
33
export { Chutes } from "./Chutes"
44
export { ClaudeCode } from "./ClaudeCode"
55
export { DeepSeek } from "./DeepSeek"
6+
export { Doubao } from "./Doubao"
67
export { Gemini } from "./Gemini"
78
export { Glama } from "./Glama"
89
export { Groq } from "./Groq"

0 commit comments

Comments
 (0)