Add Baseten provider support

- Add Baseten model definitions (10 models) in packages/types/src/providers/baseten.ts
- Implement BasetenHandler extending BaseOpenAiCompatibleProvider
- Add Baseten UI component for API key input
- Add basetenApiKey to SECRET_STATE_KEYS (critical fix for welcome screen)
- Register Baseten in provider settings, API handlers, and UI components
- Add validation and translation support
- Baseten appears last in provider dropdown as requested
This commit is contained in:
AlexKer 2025-11-20 22:56:03 -05:00
parent 5c5a893b50
commit 6eb26eccfa
14 changed files with 296 additions and 0 deletions

View file

@ -246,6 +246,7 @@ export const SECRET_STATE_KEYS = [
"featherlessApiKey",
"ioIntelligenceApiKey",
"vercelAiGatewayApiKey",
"basetenApiKey",
] as const
// Global secrets that are part of GlobalSettings (not ProviderSettings)

View file

@ -4,6 +4,7 @@ import { modelInfoSchema, reasoningEffortSettingSchema, verbosityLevelsSchema, s
import { codebaseIndexProviderSchema } from "./codebase-index.js"
import {
anthropicModels,
basetenModels,
bedrockModels,
cerebrasModels,
claudeCodeModels,
@ -120,6 +121,7 @@ export const providerNames = [
...fauxProviders,
"anthropic",
"bedrock",
"baseten",
"cerebras",
"claude-code",
"doubao",
@ -424,6 +426,10 @@ const vercelAiGatewaySchema = baseProviderSettingsSchema.extend({
vercelAiGatewayModelId: z.string().optional(),
})
const basetenSchema = apiModelIdProviderModelSchema.extend({
basetenApiKey: z.string().optional(),
})
const defaultSchema = z.object({
apiProvider: z.undefined(),
})
@ -454,6 +460,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
fakeAiSchema.merge(z.object({ apiProvider: z.literal("fake-ai") })),
xaiSchema.merge(z.object({ apiProvider: z.literal("xai") })),
groqSchema.merge(z.object({ apiProvider: z.literal("groq") })),
basetenSchema.merge(z.object({ apiProvider: z.literal("baseten") })),
huggingFaceSchema.merge(z.object({ apiProvider: z.literal("huggingface") })),
chutesSchema.merge(z.object({ apiProvider: z.literal("chutes") })),
litellmSchema.merge(z.object({ apiProvider: z.literal("litellm") })),
@ -496,6 +503,7 @@ export const providerSettingsSchema = z.object({
...fakeAiSchema.shape,
...xaiSchema.shape,
...groqSchema.shape,
...basetenSchema.shape,
...huggingFaceSchema.shape,
...chutesSchema.shape,
...litellmSchema.shape,
@ -583,6 +591,7 @@ export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
requesty: "requestyModelId",
xai: "apiModelId",
groq: "apiModelId",
baseten: "apiModelId",
chutes: "apiModelId",
litellm: "litellmModelId",
huggingface: "huggingFaceModelId",
@ -715,6 +724,7 @@ export const MODELS_BY_PROVIDER: Record<
},
xai: { id: "xai", label: "xAI (Grok)", models: Object.keys(xaiModels) },
zai: { id: "zai", label: "Zai", models: Object.keys(internationalZAiModels) },
baseten: { id: "baseten", label: "BaseTen", models: Object.keys(basetenModels) },
// Dynamic providers; models pulled from remote APIs.
glama: { id: "glama", label: "Glama", models: [] },

View file

@ -0,0 +1,127 @@
import type { ModelInfo } from "../model.js"
// Baseten
// https://baseten.co/products/model-apis/
// Extended ModelInfo to include supportedFeatures, like tools
export interface BasetenModelInfo extends ModelInfo {
supportedFeatures?: string[]
}
export const basetenModels = {
"moonshotai/Kimi-K2-Thinking": {
maxTokens: 163_800,
contextWindow: 262_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.6,
outputPrice: 2.5,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "Kimi K2 Thinking - A model with enhanced reasoning capabilities from Kimi K2",
},
"zai-org/GLM-4.6": {
maxTokens: 200_000,
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.6,
outputPrice: 2.2,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "Frontier open model with advanced agentic, reasoning and coding capabilities",
},
"deepseek-ai/DeepSeek-R1": {
maxTokens: 131_072,
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 2.55,
outputPrice: 5.95,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "DeepSeek's first-generation reasoning model",
},
"deepseek-ai/DeepSeek-R1-0528": {
maxTokens: 131_072,
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 2.55,
outputPrice: 5.95,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "The latest revision of DeepSeek's first-generation reasoning model",
},
"deepseek-ai/DeepSeek-V3-0324": {
maxTokens: 131_072,
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.77,
outputPrice: 0.77,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "Fast general-purpose LLM with enhanced reasoning capabilities",
},
"deepseek-ai/DeepSeek-V3.1": {
maxTokens: 131_072,
contextWindow: 163_840,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.5,
outputPrice: 1.5,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description:
"Extremely capable general-purpose LLM with hybrid reasoning capabilities and advanced tool calling",
},
"Qwen/Qwen3-235B-A22B-Instruct-2507": {
maxTokens: 262_144,
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.22,
outputPrice: 0.8,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "Mixture-of-experts LLM with math and reasoning capabilities",
},
"Qwen/Qwen3-Coder-480B-A35B-Instruct": {
maxTokens: 262_144,
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.38,
outputPrice: 1.53,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "Mixture-of-experts LLM with advanced coding and reasoning capabilities",
},
"openai/gpt-oss-120b": {
maxTokens: 128_072,
contextWindow: 128_072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.1,
outputPrice: 0.5,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "Extremely capable general-purpose LLM with strong, controllable reasoning capabilities",
},
"moonshotai/Kimi-K2-Instruct-0905": {
maxTokens: 168_000,
contextWindow: 262_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.6,
outputPrice: 2.5,
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: "State of the art language model for agentic and coding tasks. Septemeber Update.",
},
} as const satisfies Record<string, ModelInfo>
export type BasetenModelId = keyof typeof basetenModels
export const basetenDefaultModelId = "zai-org/GLM-4.6" satisfies BasetenModelId

View file

@ -1,4 +1,5 @@
export * from "./anthropic.js"
export * from "./baseten.js"
export * from "./bedrock.js"
export * from "./cerebras.js"
export * from "./chutes.js"
@ -33,6 +34,7 @@ export * from "./deepinfra.js"
export * from "./minimax.js"
import { anthropicDefaultModelId } from "./anthropic.js"
import { basetenDefaultModelId } from "./baseten.js"
import { bedrockDefaultModelId } from "./bedrock.js"
import { cerebrasDefaultModelId } from "./cerebras.js"
import { chutesDefaultModelId } from "./chutes.js"
@ -93,6 +95,8 @@ export function getProviderDefaultModelId(
return "meta-llama/Llama-3.3-70B-Instruct"
case "chutes":
return chutesDefaultModelId
case "baseten":
return basetenDefaultModelId
case "bedrock":
return bedrockDefaultModelId
case "vertex":

View file

@ -42,6 +42,7 @@ import {
VercelAiGatewayHandler,
DeepInfraHandler,
MiniMaxHandler,
BasetenHandler,
} from "./providers"
import { NativeOllamaHandler } from "./providers/native-ollama"
@ -190,6 +191,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new VercelAiGatewayHandler(options)
case "minimax":
return new MiniMaxHandler(options)
case "baseten":
return new BasetenHandler(options)
default:
apiProvider satisfies "gemini-cli" | undefined
return new AnthropicHandler(options)

View file

@ -0,0 +1,75 @@
import { type BasetenModelId, basetenDefaultModelId, basetenModels } from "@roo-code/types"
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import type { ApiHandlerOptions } from "../../shared/api"
import type { ApiHandlerCreateMessageMetadata } from "../index"
import { ApiStream } from "../transform/stream"
import { calculateApiCostOpenAI } from "../../shared/cost"
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
export class BasetenHandler extends BaseOpenAiCompatibleProvider<BasetenModelId> {
constructor(options: ApiHandlerOptions) {
super({
...options,
providerName: "Baseten",
baseURL: "https://inference.baseten.co/v1",
apiKey: options.basetenApiKey,
defaultProviderModelId: basetenDefaultModelId,
providerModels: basetenModels,
defaultTemperature: 0.5,
})
}
override async *createMessage(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
metadata?: ApiHandlerCreateMessageMetadata,
): ApiStream {
const stream = await this.createStream(systemPrompt, messages, metadata)
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {
yield {
type: "text",
text: delta.content,
}
}
if (chunk.usage) {
// Check if usage has cached token details (some models support this)
const usage = chunk.usage as any
const inputTokens = usage?.prompt_tokens || 0
const outputTokens = usage?.completion_tokens || 0
// Check for cached tokens in various possible locations
const cacheReadTokens =
usage?.prompt_tokens_details?.cached_tokens || usage?.prompt_cache_hit_tokens || 0
// Baseten currently doesn't track cache writes
const cacheWriteTokens = 0
// Calculate cost using OpenAI-compatible cost calculation
const { totalCost } = calculateApiCostOpenAI(
this.getModel().info,
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
)
yield {
type: "usage",
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
totalCost,
}
}
}
}
}

View file

@ -35,3 +35,4 @@ export { FeatherlessHandler } from "./featherless"
export { VercelAiGatewayHandler } from "./vercel-ai-gateway"
export { DeepInfraHandler } from "./deepinfra"
export { MiniMaxHandler } from "./minimax"
export { BasetenHandler } from "./baseten"

View file

@ -26,6 +26,7 @@ import {
groqDefaultModelId,
cerebrasDefaultModelId,
chutesDefaultModelId,
basetenDefaultModelId,
bedrockDefaultModelId,
vertexDefaultModelId,
sambaNovaDefaultModelId,
@ -67,6 +68,7 @@ import {
import {
Anthropic,
Baseten,
Bedrock,
Cerebras,
Chutes,
@ -274,6 +276,7 @@ const ApiOptions = ({
const selectedProviderModels = useMemo(() => {
const models = MODELS_BY_PROVIDER[selectedProvider]
if (!models) return []
const filteredModels = filterModels(models, selectedProvider, organizationAllowList)
@ -355,6 +358,7 @@ const ApiOptions = ({
xai: { field: "apiModelId", default: xaiDefaultModelId },
groq: { field: "apiModelId", default: groqDefaultModelId },
chutes: { field: "apiModelId", default: chutesDefaultModelId },
baseten: { field: "apiModelId", default: basetenDefaultModelId },
bedrock: { field: "apiModelId", default: bedrockDefaultModelId },
vertex: { field: "apiModelId", default: vertexDefaultModelId },
sambanova: { field: "apiModelId", default: sambaNovaDefaultModelId },
@ -564,6 +568,10 @@ const ApiOptions = ({
<Mistral apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "baseten" && (
<Baseten apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "bedrock" && (
<Bedrock
apiConfiguration={apiConfiguration}

View file

@ -20,6 +20,7 @@ import {
fireworksModels,
featherlessModels,
minimaxModels,
basetenModels,
} from "@roo-code/types"
export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, ModelInfo>>> = {
@ -42,6 +43,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
fireworks: fireworksModels,
featherless: featherlessModels,
minimax: minimaxModels,
baseten: basetenModels,
}
export const PROVIDERS = [
@ -80,4 +82,5 @@ export const PROVIDERS = [
{ value: "roo", label: "Roo Code Cloud" },
{ value: "vercel-ai-gateway", label: "Vercel AI Gateway" },
{ value: "minimax", label: "MiniMax" },
{ value: "baseten", label: "Baseten" },
].sort((a, b) => a.label.localeCompare(b.label))

View file

@ -0,0 +1,50 @@
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 BasetenProps = {
apiConfiguration: ProviderSettings
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
}
export const Baseten = ({ apiConfiguration, setApiConfigurationField }: BasetenProps) => {
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?.basetenApiKey || ""}
type="password"
onInput={handleInputChange("basetenApiKey")}
placeholder={t("settings:placeholders.apiKey")}
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.basetenApiKey")}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground -mt-2">
{t("settings:providers.apiKeyStorageNotice")}
</div>
{!apiConfiguration?.basetenApiKey && (
<VSCodeButtonLink href="https://app.baseten.co/settings/api_keys" appearance="secondary">
{t("settings:providers.getBasetenApiKey")}
</VSCodeButtonLink>
)}
</>
)
}

View file

@ -32,3 +32,4 @@ export { Featherless } from "./Featherless"
export { VercelAiGateway } from "./VercelAiGateway"
export { DeepInfra } from "./DeepInfra"
export { MiniMax } from "./MiniMax"
export { Baseten } from "./Baseten"

View file

@ -25,6 +25,7 @@ import {
fireworksModels,
featherlessModels,
ioIntelligenceModels,
basetenModels,
qwenCodeModels,
BEDROCK_1M_CONTEXT_MODEL_IDS,
isDynamicProvider,
@ -196,6 +197,11 @@ function getSelectedModel({
const info = routerModels.chutes?.[id]
return { id, info }
}
case "baseten": {
const id = apiConfiguration.apiModelId ?? defaultModelId
const info = basetenModels[id as keyof typeof basetenModels]
return { id, info }
}
case "bedrock": {
const id = apiConfiguration.apiModelId ?? defaultModelId
const baseInfo = bedrockModels[id as keyof typeof bedrockModels]

View file

@ -284,6 +284,8 @@
"anthropic1MContextBetaDescription": "Extends context window to 1 million tokens for Claude Sonnet 4",
"awsBedrock1MContextBetaLabel": "Enable 1M context window (Beta)",
"awsBedrock1MContextBetaDescription": "Extends context window to 1 million tokens for Claude Sonnet 4",
"basetenApiKey": "Baseten API Key",
"getBasetenApiKey": "Get Baseten API Key",
"cerebrasApiKey": "Cerebras API Key",
"getCerebrasApiKey": "Get Cerebras API Key",
"chutesApiKey": "Chutes API Key",

View file

@ -156,6 +156,11 @@ function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): stri
return i18next.t("settings:validation.apiKey")
}
break
case "baseten":
if (!apiConfiguration.basetenApiKey) {
return i18next.t("settings:validation.apiKey")
}
break
}
return undefined