diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 3697ef35bb..f1e31404f2 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -246,6 +246,7 @@ export const SECRET_STATE_KEYS = [ "featherlessApiKey", "ioIntelligenceApiKey", "vercelAiGatewayApiKey", + "basetenApiKey", ] as const // Global secrets that are part of GlobalSettings (not ProviderSettings) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 7a84e6d2de..967b10d11f 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -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 = { 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: [] }, diff --git a/packages/types/src/providers/baseten.ts b/packages/types/src/providers/baseten.ts new file mode 100644 index 0000000000..aad9f8a154 --- /dev/null +++ b/packages/types/src/providers/baseten.ts @@ -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 + +export type BasetenModelId = keyof typeof basetenModels + +export const basetenDefaultModelId = "zai-org/GLM-4.6" satisfies BasetenModelId diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 3db2c7fb10..5a0e577eab 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -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": diff --git a/src/api/index.ts b/src/api/index.ts index 05c7493078..dd9208aa96 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -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) diff --git a/src/api/providers/baseten.ts b/src/api/providers/baseten.ts new file mode 100644 index 0000000000..d33f059ab6 --- /dev/null +++ b/src/api/providers/baseten.ts @@ -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 { + 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, + } + } + } + } +} diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index 533023d037..34bc119e61 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -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" diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index adf312dea6..8769635099 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -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 = ({ )} + {selectedProvider === "baseten" && ( + + )} + {selectedProvider === "bedrock" && ( >> = { @@ -42,6 +43,7 @@ export const MODELS_BY_PROVIDER: Partial a.label.localeCompare(b.label)) diff --git a/webview-ui/src/components/settings/providers/Baseten.tsx b/webview-ui/src/components/settings/providers/Baseten.tsx new file mode 100644 index 0000000000..f4b26beab4 --- /dev/null +++ b/webview-ui/src/components/settings/providers/Baseten.tsx @@ -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( + ( + field: K, + transform: (event: E) => ProviderSettings[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.basetenApiKey && ( + + {t("settings:providers.getBasetenApiKey")} + + )} + + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts index baf6ccba2e..fbd9bc4eb2 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -32,3 +32,4 @@ export { Featherless } from "./Featherless" export { VercelAiGateway } from "./VercelAiGateway" export { DeepInfra } from "./DeepInfra" export { MiniMax } from "./MiniMax" +export { Baseten } from "./Baseten" diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index c2a57942d2..3df7236713 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -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] diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 635252e5a5..b77e3e0abf 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -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", diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index d15f82e4ca..947b18ac3b 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -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