Support new LLM provider: Doubao (#6345)

Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
This commit is contained in:
AntiMoron 2025-07-31 04:54:16 +08:00 committed by GitHub
parent 4015a58951
commit 4b45a4e7f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 244 additions and 0 deletions

View file

@ -24,6 +24,7 @@ export const providerNames = [
"mistral",
"moonshot",
"deepseek",
"doubao",
"unbound",
"requesty",
"human-relay",
@ -194,6 +195,11 @@ const deepSeekSchema = apiModelIdProviderModelSchema.extend({
deepSeekApiKey: z.string().optional(),
})
const doubaoSchema = apiModelIdProviderModelSchema.extend({
doubaoBaseUrl: z.string().optional(),
doubaoApiKey: z.string().optional(),
})
const moonshotSchema = apiModelIdProviderModelSchema.extend({
moonshotBaseUrl: z
.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
openAiNativeSchema.merge(z.object({ apiProvider: z.literal("openai-native") })),
mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })),
deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })),
doubaoSchema.merge(z.object({ apiProvider: z.literal("doubao") })),
moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })),
unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })),
requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })),
@ -297,6 +304,7 @@ export const providerSettingsSchema = z.object({
...openAiNativeSchema.shape,
...mistralSchema.shape,
...deepSeekSchema.shape,
...doubaoSchema.shape,
...moonshotSchema.shape,
...unboundSchema.shape,
...requestySchema.shape,

View file

@ -0,0 +1,44 @@
import type { ModelInfo } from "../model.js"
export const doubaoDefaultModelId = "doubao-seed-1-6-250615"
export const doubaoModels = {
"doubao-seed-1-6-250615": {
maxTokens: 32_768,
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.0001, // $0.0001 per million tokens (cache miss)
outputPrice: 0.0004, // $0.0004 per million tokens
cacheWritesPrice: 0.0001, // $0.0001 per million tokens (cache miss)
cacheReadsPrice: 0.00002, // $0.00002 per million tokens (cache hit)
description: `Doubao Seed 1.6 is a powerful model designed for high-performance tasks with extensive context handling.`,
},
"doubao-seed-1-6-thinking-250715": {
maxTokens: 32_768,
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.0002, // $0.0002 per million tokens
outputPrice: 0.0008, // $0.0008 per million tokens
cacheWritesPrice: 0.0002, // $0.0002 per million
cacheReadsPrice: 0.00004, // $0.00004 per million tokens (cache hit)
description: `Doubao Seed 1.6 Thinking is optimized for reasoning tasks, providing enhanced performance in complex problem-solving scenarios.`,
},
"doubao-seed-1-6-flash-250715": {
maxTokens: 32_768,
contextWindow: 128_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.00015, // $0.00015 per million tokens
outputPrice: 0.0006, // $0.0006 per million tokens
cacheWritesPrice: 0.00015, // $0.00015 per million
cacheReadsPrice: 0.00003, // $0.00003 per million tokens (cache hit)
description: `Doubao Seed 1.6 Flash is tailored for speed and efficiency, making it ideal for applications requiring rapid responses.`,
},
} as const satisfies Record<string, ModelInfo>
export const doubaoDefaultModelInfo: ModelInfo = doubaoModels[doubaoDefaultModelId]
export const DOUBAO_API_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
export const DOUBAO_API_CHAT_PATH = "/chat/completions"

View file

@ -20,3 +20,4 @@ export * from "./unbound.js"
export * from "./vertex.js"
export * from "./vscode-llm.js"
export * from "./xai.js"
export * from "./doubao.js"

View file

@ -31,6 +31,7 @@ import {
LiteLLMHandler,
ClaudeCodeHandler,
SambaNovaHandler,
DoubaoHandler,
} from "./providers"
export interface SingleCompletionHandler {
@ -92,6 +93,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new OpenAiNativeHandler(options)
case "deepseek":
return new DeepSeekHandler(options)
case "doubao":
return new DoubaoHandler(options)
case "moonshot":
return new MoonshotHandler(options)
case "vscode-lm":

View file

@ -0,0 +1,81 @@
import { OpenAiHandler } from "./openai"
import type { ApiHandlerOptions } from "../../shared/api"
import { DOUBAO_API_BASE_URL, doubaoDefaultModelId, doubaoModels } from "@roo-code/types"
import { getModelParams } from "../transform/model-params"
import { ApiStreamUsageChunk } from "../transform/stream"
// Core types for Doubao API
interface ChatCompletionMessageParam {
role: "system" | "user" | "assistant" | "developer"
content:
| string
| Array<{
type: "text" | "image_url"
text?: string
image_url?: { url: string }
}>
}
interface ChatCompletionParams {
model: string
messages: ChatCompletionMessageParam[]
temperature?: number
stream?: boolean
stream_options?: { include_usage: boolean }
max_completion_tokens?: number
}
interface ChatCompletion {
choices: Array<{
message: {
content: string
}
}>
usage?: {
prompt_tokens: number
completion_tokens: number
}
}
interface ChatCompletionChunk {
choices: Array<{
delta: {
content?: string
}
}>
usage?: {
prompt_tokens: number
completion_tokens: number
}
}
export class DoubaoHandler extends OpenAiHandler {
constructor(options: ApiHandlerOptions) {
super({
...options,
openAiApiKey: options.doubaoApiKey ?? "not-provided",
openAiModelId: options.apiModelId ?? doubaoDefaultModelId,
openAiBaseUrl: options.doubaoBaseUrl ?? DOUBAO_API_BASE_URL,
openAiStreamingEnabled: true,
includeMaxTokens: true,
})
}
override getModel() {
const id = this.options.apiModelId ?? doubaoDefaultModelId
const info = doubaoModels[id as keyof typeof doubaoModels] || doubaoModels[doubaoDefaultModelId]
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
return { id, info, ...params }
}
// Override to handle Doubao's usage metrics, including caching.
protected override processUsageMetrics(usage: any): ApiStreamUsageChunk {
return {
type: "usage",
inputTokens: usage?.prompt_tokens || 0,
outputTokens: usage?.completion_tokens || 0,
cacheWriteTokens: usage?.prompt_tokens_details?.cache_miss_tokens,
cacheReadTokens: usage?.prompt_tokens_details?.cached_tokens,
}
}
}

View file

@ -4,6 +4,7 @@ export { AwsBedrockHandler } from "./bedrock"
export { ChutesHandler } from "./chutes"
export { ClaudeCodeHandler } from "./claude-code"
export { DeepSeekHandler } from "./deepseek"
export { DoubaoHandler } from "./doubao"
export { MoonshotHandler } from "./moonshot"
export { FakeAIHandler } from "./fake-ai"
export { GeminiHandler } from "./gemini"

View file

@ -15,6 +15,7 @@ import {
litellmDefaultModelId,
openAiNativeDefaultModelId,
anthropicDefaultModelId,
doubaoDefaultModelId,
claudeCodeDefaultModelId,
geminiDefaultModelId,
deepSeekDefaultModelId,
@ -57,6 +58,7 @@ import {
Chutes,
ClaudeCode,
DeepSeek,
Doubao,
Gemini,
Glama,
Groq,
@ -292,6 +294,7 @@ const ApiOptions = ({
"openai-native": { field: "apiModelId", default: openAiNativeDefaultModelId },
gemini: { field: "apiModelId", default: geminiDefaultModelId },
deepseek: { field: "apiModelId", default: deepSeekDefaultModelId },
doubao: { field: "apiModelId", default: doubaoDefaultModelId },
moonshot: { field: "apiModelId", default: moonshotDefaultModelId },
mistral: { field: "apiModelId", default: mistralDefaultModelId },
xai: { field: "apiModelId", default: xaiDefaultModelId },
@ -475,6 +478,10 @@ const ApiOptions = ({
<DeepSeek apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "doubao" && (
<Doubao apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "moonshot" && (
<Moonshot apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}

View file

@ -14,6 +14,7 @@ import {
groqModels,
chutesModels,
sambaNovaModels,
doubaoModels,
} from "@roo-code/types"
export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, ModelInfo>>> = {
@ -21,6 +22,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
"claude-code": claudeCodeModels,
bedrock: bedrockModels,
deepseek: deepSeekModels,
doubao: doubaoModels,
moonshot: moonshotModels,
gemini: geminiModels,
mistral: mistralModels,
@ -37,6 +39,7 @@ export const PROVIDERS = [
{ value: "anthropic", label: "Anthropic" },
{ value: "claude-code", label: "Claude Code" },
{ value: "gemini", label: "Google Gemini" },
{ value: "doubao", label: "Doubao" },
{ value: "deepseek", label: "DeepSeek" },
{ value: "moonshot", label: "Moonshot" },
{ value: "openai-native", label: "OpenAI" },

View file

@ -0,0 +1,52 @@
import { useCallback } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
import { inputEventTransform } from "../transforms"
type DoubaoProps = {
apiConfiguration: ProviderSettings
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
}
export const Doubao = ({ apiConfiguration, setApiConfigurationField }: DoubaoProps) => {
const { t } = useAppTranslation()
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))
},
[setApiConfigurationField],
)
return (
<>
<VSCodeTextField
value={apiConfiguration?.doubaoApiKey || ""}
type="password"
onInput={handleInputChange("doubaoApiKey")}
placeholder={t("settings:placeholders.apiKey")}
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.doubaoApiKey")}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground -mt-2">
{t("settings:providers.apiKeyStorageNotice")}
</div>
{!apiConfiguration?.doubaoApiKey && (
<VSCodeButtonLink
href="https://www.volcengine.com/experience/ark?model=doubao-1-5-thinking-vision-pro-250428"
appearance="secondary">
{t("settings:providers.getDoubaoApiKey")}
</VSCodeButtonLink>
)}
</>
)
}

View file

@ -3,6 +3,7 @@ export { Bedrock } from "./Bedrock"
export { Chutes } from "./Chutes"
export { ClaudeCode } from "./ClaudeCode"
export { DeepSeek } from "./DeepSeek"
export { Doubao } from "./Doubao"
export { Gemini } from "./Gemini"
export { Glama } from "./Glama"
export { Groq } from "./Groq"

View file

@ -36,6 +36,8 @@ import {
claudeCodeModels,
sambaNovaModels,
sambaNovaDefaultModelId,
doubaoModels,
doubaoDefaultModelId,
} from "@roo-code/types"
import type { RouterModels } from "@roo/api"
@ -176,6 +178,11 @@ function getSelectedModel({
const info = deepSeekModels[id as keyof typeof deepSeekModels]
return { id, info }
}
case "doubao": {
const id = apiConfiguration.apiModelId ?? doubaoDefaultModelId
const info = doubaoModels[id as keyof typeof doubaoModels]
return { id, info }
}
case "moonshot": {
const id = apiConfiguration.apiModelId ?? moonshotDefaultModelId
const info = moonshotModels[id as keyof typeof moonshotModels]

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Obtenir clau API de Chutes",
"deepSeekApiKey": "Clau API de DeepSeek",
"getDeepSeekApiKey": "Obtenir clau API de DeepSeek",
"doubaoApiKey": "Clau API de Doubao",
"getDoubaoApiKey": "Obtenir clau API de Doubao",
"moonshotApiKey": "Clau API de Moonshot",
"getMoonshotApiKey": "Obtenir clau API de Moonshot",
"moonshotBaseUrl": "Punt d'entrada de Moonshot",

View file

@ -225,6 +225,8 @@
"awsCustomArnDesc": "Stellen Sie sicher, dass die Region in der ARN mit Ihrer oben ausgewählten AWS-Region übereinstimmt.",
"openRouterApiKey": "OpenRouter API-Schlüssel",
"getOpenRouterApiKey": "OpenRouter API-Schlüssel erhalten",
"doubaoApiKey": "Doubao API-Schlüssel",
"getDoubaoApiKey": "Doubao API-Schlüssel erhalten",
"apiKeyStorageNotice": "API-Schlüssel werden sicher im VSCode Secret Storage gespeichert",
"glamaApiKey": "Glama API-Schlüssel",
"getGlamaApiKey": "Glama API-Schlüssel erhalten",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Get Chutes API Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Get DeepSeek API Key",
"doubaoApiKey": "Doubao API Key",
"getDoubaoApiKey": "Get Doubao API Key",
"moonshotApiKey": "Moonshot API Key",
"getMoonshotApiKey": "Get Moonshot API Key",
"moonshotBaseUrl": "Moonshot Entrypoint",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Obtener clave API de Chutes",
"deepSeekApiKey": "Clave API de DeepSeek",
"getDeepSeekApiKey": "Obtener clave API de DeepSeek",
"doubaoApiKey": "Clave API de Doubao",
"getDoubaoApiKey": "Obtener clave API de Doubao",
"moonshotApiKey": "Clave API de Moonshot",
"getMoonshotApiKey": "Obtener clave API de Moonshot",
"moonshotBaseUrl": "Punto de entrada de Moonshot",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Obtenir la clé API Chutes",
"deepSeekApiKey": "Clé API DeepSeek",
"getDeepSeekApiKey": "Obtenir la clé API DeepSeek",
"doubaoApiKey": "Clé API Doubao",
"getDoubaoApiKey": "Obtenir la clé API Doubao",
"moonshotApiKey": "Clé API Moonshot",
"getMoonshotApiKey": "Obtenir la clé API Moonshot",
"moonshotBaseUrl": "Point d'entrée Moonshot",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Chutes API कुंजी प्राप्त करें",
"deepSeekApiKey": "DeepSeek API कुंजी",
"getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें",
"doubaoApiKey": "डौबाओ API कुंजी",
"getDoubaoApiKey": "डौबाओ API कुंजी प्राप्त करें",
"moonshotApiKey": "Moonshot API कुंजी",
"getMoonshotApiKey": "Moonshot API कुंजी प्राप्त करें",
"moonshotBaseUrl": "Moonshot प्रवेश बिंदु",

View file

@ -257,6 +257,8 @@
"getChutesApiKey": "Dapatkan Chutes API Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Dapatkan DeepSeek API Key",
"doubaoApiKey": "Kunci API Doubao",
"getDoubaoApiKey": "Dapatkan Kunci API Doubao",
"moonshotApiKey": "Kunci API Moonshot",
"getMoonshotApiKey": "Dapatkan Kunci API Moonshot",
"moonshotBaseUrl": "Titik Masuk Moonshot",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Ottieni chiave API Chutes",
"deepSeekApiKey": "Chiave API DeepSeek",
"getDeepSeekApiKey": "Ottieni chiave API DeepSeek",
"doubaoApiKey": "Chiave API Doubao",
"getDoubaoApiKey": "Ottieni chiave API Doubao",
"moonshotApiKey": "Chiave API Moonshot",
"getMoonshotApiKey": "Ottieni chiave API Moonshot",
"moonshotBaseUrl": "Punto di ingresso Moonshot",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Chutes APIキーを取得",
"deepSeekApiKey": "DeepSeek APIキー",
"getDeepSeekApiKey": "DeepSeek APIキーを取得",
"doubaoApiKey": "Doubao APIキー",
"getDoubaoApiKey": "Doubao APIキーを取得",
"moonshotApiKey": "Moonshot APIキー",
"getMoonshotApiKey": "Moonshot APIキーを取得",
"moonshotBaseUrl": "Moonshot エントリーポイント",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Chutes API 키 받기",
"deepSeekApiKey": "DeepSeek API 키",
"getDeepSeekApiKey": "DeepSeek API 키 받기",
"doubaoApiKey": "Doubao API 키",
"getDoubaoApiKey": "Doubao API 키 받기",
"moonshotApiKey": "Moonshot API 키",
"getMoonshotApiKey": "Moonshot API 키 받기",
"moonshotBaseUrl": "Moonshot 엔트리포인트",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Chutes API-sleutel ophalen",
"deepSeekApiKey": "DeepSeek API-sleutel",
"getDeepSeekApiKey": "DeepSeek API-sleutel ophalen",
"doubaoApiKey": "Doubao API-sleutel",
"getDoubaoApiKey": "Doubao API-sleutel ophalen",
"moonshotApiKey": "Moonshot API-sleutel",
"getMoonshotApiKey": "Moonshot API-sleutel ophalen",
"moonshotBaseUrl": "Moonshot-ingangspunt",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Uzyskaj klucz API Chutes",
"deepSeekApiKey": "Klucz API DeepSeek",
"getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek",
"doubaoApiKey": "Klucz API Doubao",
"getDoubaoApiKey": "Uzyskaj klucz API Doubao",
"moonshotApiKey": "Klucz API Moonshot",
"getMoonshotApiKey": "Uzyskaj klucz API Moonshot",
"moonshotBaseUrl": "Punkt wejścia Moonshot",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Obter chave de API Chutes",
"deepSeekApiKey": "Chave de API DeepSeek",
"getDeepSeekApiKey": "Obter chave de API DeepSeek",
"doubaoApiKey": "Chave de API Doubao",
"getDoubaoApiKey": "Obter chave de API Doubao",
"moonshotApiKey": "Chave de API Moonshot",
"getMoonshotApiKey": "Obter chave de API Moonshot",
"moonshotBaseUrl": "Ponto de entrada Moonshot",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Получить Chutes API-ключ",
"deepSeekApiKey": "DeepSeek API-ключ",
"getDeepSeekApiKey": "Получить DeepSeek API-ключ",
"doubaoApiKey": "Doubao API-ключ",
"getDoubaoApiKey": "Получить Doubao API-ключ",
"moonshotApiKey": "Moonshot API-ключ",
"getMoonshotApiKey": "Получить Moonshot API-ключ",
"moonshotBaseUrl": "Точка входа Moonshot",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Chutes API Anahtarı Al",
"deepSeekApiKey": "DeepSeek API Anahtarı",
"getDeepSeekApiKey": "DeepSeek API Anahtarı Al",
"doubaoApiKey": "Doubao API Anahtarı",
"getDoubaoApiKey": "Doubao API Anahtarı Al",
"moonshotApiKey": "Moonshot API Anahtarı",
"getMoonshotApiKey": "Moonshot API Anahtarı Al",
"moonshotBaseUrl": "Moonshot Giriş Noktası",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "Lấy khóa API Chutes",
"deepSeekApiKey": "Khóa API DeepSeek",
"getDeepSeekApiKey": "Lấy khóa API DeepSeek",
"doubaoApiKey": "Khóa API Doubao",
"getDoubaoApiKey": "Lấy khóa API Doubao",
"moonshotApiKey": "Khóa API Moonshot",
"getMoonshotApiKey": "Lấy khóa API Moonshot",
"moonshotBaseUrl": "Điểm vào Moonshot",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "获取 Chutes API 密钥",
"deepSeekApiKey": "DeepSeek API 密钥",
"getDeepSeekApiKey": "获取 DeepSeek API 密钥",
"doubaoApiKey": "豆包 API 密钥",
"getDoubaoApiKey": "获取豆包 API 密钥",
"moonshotApiKey": "Moonshot API 密钥",
"getMoonshotApiKey": "获取 Moonshot API 密钥",
"moonshotBaseUrl": "Moonshot 服务站点",

View file

@ -253,6 +253,8 @@
"getChutesApiKey": "取得 Chutes API 金鑰",
"deepSeekApiKey": "DeepSeek API 金鑰",
"getDeepSeekApiKey": "取得 DeepSeek API 金鑰",
"doubaoApiKey": "豆包 API 金鑰",
"getDoubaoApiKey": "取得豆包 API 金鑰",
"moonshotApiKey": "Moonshot API 金鑰",
"getMoonshotApiKey": "取得 Moonshot API 金鑰",
"moonshotBaseUrl": "Moonshot 服務站點",