diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index ac9471f2c6..fb7f35f776 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -273,6 +273,7 @@ export const SECRET_STATE_KEYS = [ "codebaseIndexVercelAiGatewayApiKey", "codebaseIndexOpenRouterApiKey", "sambaNovaApiKey", + "kymaApiKey", "zaiApiKey", "fireworksApiKey", "vercelAiGatewayApiKey", diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 1b1da702ff..43a0dbebd0 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -20,6 +20,7 @@ import { xaiModels, internationalZAiModels, minimaxModels, + kymaModels, } from "./providers/index.js" /** @@ -116,6 +117,7 @@ export const providerNames = [ "openai-native", "qwen-code", "sambanova", + "kyma", "vertex", "xai", "zai", @@ -355,6 +357,10 @@ const sambaNovaSchema = apiModelIdProviderModelSchema.extend({ sambaNovaApiKey: z.string().optional(), }) +const kymaSchema = apiModelIdProviderModelSchema.extend({ + kymaApiKey: z.string().optional(), +}) + export const zaiApiLineSchema = z.enum(["international_coding", "china_coding", "international_api", "china_api"]) export type ZaiApiLine = z.infer @@ -410,6 +416,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv basetenSchema.merge(z.object({ apiProvider: z.literal("baseten") })), litellmSchema.merge(z.object({ apiProvider: z.literal("litellm") })), sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })), + kymaSchema.merge(z.object({ apiProvider: z.literal("kyma") })), zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })), fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })), qwenCodeSchema.merge(z.object({ apiProvider: z.literal("qwen-code") })), @@ -443,6 +450,7 @@ export const providerSettingsSchema = z.object({ ...basetenSchema.shape, ...litellmSchema.shape, ...sambaNovaSchema.shape, + ...kymaSchema.shape, ...zaiSchema.shape, ...fireworksSchema.shape, ...qwenCodeSchema.shape, @@ -518,6 +526,7 @@ export const modelIdKeysByProvider: Record = { baseten: "apiModelId", litellm: "litellmModelId", sambanova: "apiModelId", + kyma: "apiModelId", zai: "apiModelId", fireworks: "apiModelId", "vercel-ai-gateway": "vercelAiGatewayModelId", @@ -611,6 +620,11 @@ export const MODELS_BY_PROVIDER: Record< label: "SambaNova", models: Object.keys(sambaNovaModels), }, + kyma: { + id: "kyma", + label: "Kyma API", + models: Object.keys(kymaModels), + }, vertex: { id: "vertex", label: "GCP Vertex AI", diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 243ecd8d4e..9280e5b6f2 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -24,6 +24,7 @@ export * from "./xai.js" export * from "./vercel-ai-gateway.js" export * from "./zai.js" export * from "./minimax.js" +export * from "./kyma.js" import { anthropicDefaultModelId } from "./anthropic.js" import { basetenDefaultModelId } from "./baseten.js" @@ -47,6 +48,7 @@ import { xaiDefaultModelId } from "./xai.js" import { vercelAiGatewayDefaultModelId } from "./vercel-ai-gateway.js" import { internationalZAiDefaultModelId, mainlandZAiDefaultModelId } from "./zai.js" import { minimaxDefaultModelId } from "./minimax.js" +import { kymaDefaultModelId } from "./kyma.js" // Import the ProviderName type from provider-settings to avoid duplication import type { ProviderName } from "../provider-settings.js" @@ -111,6 +113,8 @@ export function getProviderDefaultModelId( return unboundDefaultModelId case "vercel-ai-gateway": return vercelAiGatewayDefaultModelId + case "kyma": + return kymaDefaultModelId case "anthropic": case "gemini-cli": case "fake-ai": diff --git a/packages/types/src/providers/kyma.ts b/packages/types/src/providers/kyma.ts new file mode 100644 index 0000000000..d2623980eb --- /dev/null +++ b/packages/types/src/providers/kyma.ts @@ -0,0 +1,89 @@ +import type { ModelInfo } from "../model.js" + +// https://kymaapi.com — OpenAI-compatible LLM gateway with 20+ open-source models +export type KymaModelId = + | "qwen-3.6-plus" + | "deepseek-v3" + | "deepseek-r1" + | "kimi-k2.5" + | "gemma-4-31b" + | "llama-3.3-70b" + | "qwen-3-32b" + | "minimax-m2.5" + +export const kymaDefaultModelId: KymaModelId = "qwen-3.6-plus" + +export const kymaModels = { + "qwen-3.6-plus": { + maxTokens: 8192, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.4, + outputPrice: 1.6, + description: "Qwen 3.6 Plus — top-tier open model, best overall quality.", + }, + "deepseek-v3": { + maxTokens: 8192, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: true, + inputPrice: 0.26, + outputPrice: 0.38, + description: "DeepSeek V3 — GPT-4-class coding and reasoning at very low cost.", + }, + "deepseek-r1": { + maxTokens: 8192, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.5, + outputPrice: 2.18, + description: "DeepSeek R1 — strong open reasoning model, 96% cheaper than o1.", + }, + "kimi-k2.5": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 1.0, + outputPrice: 3.0, + description: "Kimi K2.5 — multimodal agentic model optimized for tool use.", + }, + "gemma-4-31b": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.0, + outputPrice: 0.0, + description: "Gemma 4 31B — Google's latest multimodal open model, free tier.", + }, + "llama-3.3-70b": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.23, + outputPrice: 0.4, + description: "Llama 3.3 70B — most popular open model, great balance of speed and quality.", + }, + "qwen-3-32b": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.2, + outputPrice: 0.6, + description: "Qwen 3 32B — top coding model, fast and accurate.", + }, + "minimax-m2.5": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.8, + outputPrice: 2.0, + description: "MiniMax M2.5 — SWE-bench 80.2%, strong coding and agentic tasks.", + }, +} as const satisfies Record diff --git a/src/api/index.ts b/src/api/index.ts index 40ba31f39a..eb71645cbd 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -33,6 +33,7 @@ import { VercelAiGatewayHandler, MiniMaxHandler, BasetenHandler, + KymaHandler, } from "./providers" import { NativeOllamaHandler } from "./providers/native-ollama" @@ -175,6 +176,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { return new MiniMaxHandler(options) case "baseten": return new BasetenHandler(options) + case "kyma": + return new KymaHandler(options) case "poe": return new PoeHandler(options) default: diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index b690a81234..0fca155df3 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -27,3 +27,4 @@ export { FireworksHandler } from "./fireworks" export { VercelAiGatewayHandler } from "./vercel-ai-gateway" export { MiniMaxHandler } from "./minimax" export { BasetenHandler } from "./baseten" +export { KymaHandler } from "./kyma" diff --git a/src/api/providers/kyma.ts b/src/api/providers/kyma.ts new file mode 100644 index 0000000000..77bc6d8388 --- /dev/null +++ b/src/api/providers/kyma.ts @@ -0,0 +1,18 @@ +import { type KymaModelId, kymaDefaultModelId, kymaModels } from "@roo-code/types" + +import type { ApiHandlerOptions } from "../../shared/api" + +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" + +export class KymaHandler extends BaseOpenAiCompatibleProvider { + constructor(options: ApiHandlerOptions) { + super({ + ...options, + providerName: "Kyma API", + baseURL: "https://kymaapi.com/v1", + apiKey: options.kymaApiKey, + defaultProviderModelId: kymaDefaultModelId, + providerModels: kymaModels, + }) + } +} diff --git a/src/shared/ProfileValidator.ts b/src/shared/ProfileValidator.ts index 7246a90177..3f5b5ed7e5 100644 --- a/src/shared/ProfileValidator.ts +++ b/src/shared/ProfileValidator.ts @@ -62,6 +62,7 @@ export class ProfileValidator { case "deepseek": case "xai": case "sambanova": + case "kyma": case "fireworks": return profile.apiModelId case "litellm": diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index d23bc6fa85..25ce8774f9 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -26,6 +26,7 @@ import { bedrockDefaultModelId, vertexDefaultModelId, sambaNovaDefaultModelId, + kymaDefaultModelId, internationalZAiDefaultModelId, mainlandZAiDefaultModelId, fireworksDefaultModelId, @@ -92,6 +93,7 @@ import { Fireworks, VercelAiGateway, MiniMax, + Kyma, } from "./providers" import { MODELS_BY_PROVIDER, PROVIDERS } from "./constants" @@ -349,6 +351,7 @@ const ApiOptions = ({ bedrock: { field: "apiModelId", default: bedrockDefaultModelId }, vertex: { field: "apiModelId", default: vertexDefaultModelId }, sambanova: { field: "apiModelId", default: sambaNovaDefaultModelId }, + kyma: { field: "apiModelId", default: kymaDefaultModelId }, zai: { field: "apiModelId", default: @@ -670,6 +673,10 @@ const ApiOptions = ({ /> )} + {selectedProvider === "kyma" && ( + + )} + {selectedProvider === "zai" && ( )} diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index 87fe828ce8..397bc88d9e 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -17,6 +17,7 @@ import { fireworksModels, minimaxModels, basetenModels, + kymaModels, } from "@roo-code/types" export const MODELS_BY_PROVIDER: Partial>> = { @@ -32,6 +33,7 @@ export const MODELS_BY_PROVIDER: Partial void +} + +export const Kyma = ({ apiConfiguration, setApiConfigurationField }: KymaProps) => { + 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?.kymaApiKey && ( + + {t("settings:providers.getKymaApiKey")} + + )} + + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts index 7badb54311..ec3d4da868 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -24,3 +24,4 @@ export { Fireworks } from "./Fireworks" export { VercelAiGateway } from "./VercelAiGateway" export { MiniMax } from "./MiniMax" export { Baseten } from "./Baseten" +export { Kyma } from "./Kyma" diff --git a/webview-ui/src/components/settings/utils/providerModelConfig.ts b/webview-ui/src/components/settings/utils/providerModelConfig.ts index 8631c5571d..6069577516 100644 --- a/webview-ui/src/components/settings/utils/providerModelConfig.ts +++ b/webview-ui/src/components/settings/utils/providerModelConfig.ts @@ -11,6 +11,7 @@ import { vertexDefaultModelId, xaiDefaultModelId, sambaNovaDefaultModelId, + kymaDefaultModelId, internationalZAiDefaultModelId, mainlandZAiDefaultModelId, fireworksDefaultModelId, @@ -37,6 +38,7 @@ export const PROVIDER_SERVICE_CONFIG: Partial> = vertex: vertexDefaultModelId, xai: xaiDefaultModelId, sambanova: sambaNovaDefaultModelId, + kyma: kymaDefaultModelId, zai: internationalZAiDefaultModelId, fireworks: fireworksDefaultModelId, minimax: minimaxDefaultModelId, diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index bf78236b82..a6eb1e426f 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -19,6 +19,7 @@ import { vscodeLlmDefaultModelId, openAiCodexModels, sambaNovaModels, + kymaModels, internationalZAiModels, mainlandZAiModels, fireworksModels, @@ -307,6 +308,11 @@ function getSelectedModel({ const info = sambaNovaModels[id as keyof typeof sambaNovaModels] return { id, info } } + case "kyma": { + const id = apiConfiguration.apiModelId ?? defaultModelId + const info = kymaModels[id as keyof typeof kymaModels] + return { id, info } + } case "fireworks": { const id = apiConfiguration.apiModelId ?? defaultModelId const info = fireworksModels[id as keyof typeof fireworksModels] diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 01b6cd3e91..9c76a07cb0 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -454,6 +454,8 @@ "geminiApiKey": "Gemini API Key", "getSambaNovaApiKey": "Get SambaNova API Key", "sambaNovaApiKey": "SambaNova API Key", + "getKymaApiKey": "Get Kyma API Key", + "kymaApiKey": "Kyma API Key", "getGeminiApiKey": "Get Gemini API Key", "openAiApiKey": "OpenAI API Key", "apiKey": "API Key",