feat: add SambaNova provider integration

- Add SambaNova types with supported models
- Create SambaNova handler extending BaseOpenAiCompatibleProvider
- Add SambaNova UI component for settings
- Update provider configurations and exports
- Add translations for all supported locales
- Add comprehensive tests for SambaNova provider

Closes #6077
This commit is contained in:
Roo Code 2025-07-22 20:48:55 +00:00
parent 5629199d51
commit bd001ab23e
29 changed files with 432 additions and 0 deletions

View file

@ -32,6 +32,7 @@ export const providerNames = [
"groq",
"chutes",
"litellm",
"sambanova",
] as const
export const providerNamesSchema = z.enum(providerNames)
@ -229,6 +230,10 @@ const litellmSchema = baseProviderSettingsSchema.extend({
litellmModelId: z.string().optional(),
})
const sambaNovaSchema = apiModelIdProviderModelSchema.extend({
sambaNovaApiKey: z.string().optional(),
})
const defaultSchema = z.object({
apiProvider: z.undefined(),
})
@ -258,6 +263,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
groqSchema.merge(z.object({ apiProvider: z.literal("groq") })),
chutesSchema.merge(z.object({ apiProvider: z.literal("chutes") })),
litellmSchema.merge(z.object({ apiProvider: z.literal("litellm") })),
sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })),
defaultSchema,
])
@ -287,6 +293,7 @@ export const providerSettingsSchema = z.object({
...groqSchema.shape,
...chutesSchema.shape,
...litellmSchema.shape,
...sambaNovaSchema.shape,
...codebaseIndexProviderSchema.shape,
})

View file

@ -14,6 +14,7 @@ export * from "./ollama.js"
export * from "./openai.js"
export * from "./openrouter.js"
export * from "./requesty.js"
export * from "./sambanova.js"
export * from "./unbound.js"
export * from "./vertex.js"
export * from "./vscode-llm.js"

View file

@ -0,0 +1,141 @@
import type { ModelInfo } from "../model.js"
// https://docs.sambanova.ai/cloud/docs/get-started/supported-models
export type SambaNovaModelId =
| "Meta-Llama-3.1-8B-Instruct"
| "Meta-Llama-3.1-70B-Instruct"
| "Meta-Llama-3.1-405B-Instruct"
| "Meta-Llama-3.2-1B-Instruct"
| "Meta-Llama-3.2-3B-Instruct"
| "Meta-Llama-3.3-70B-Instruct"
| "Llama-3.2-11B-Vision-Instruct"
| "Llama-3.2-90B-Vision-Instruct"
| "QwQ-32B-Preview"
| "Qwen2.5-72B-Instruct"
| "Qwen2.5-Coder-32B-Instruct"
| "deepseek-r1"
| "deepseek-r1-distill-llama-70b"
export const sambaNovaDefaultModelId: SambaNovaModelId = "Meta-Llama-3.3-70B-Instruct"
export const sambaNovaModels = {
"Meta-Llama-3.1-8B-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.1,
outputPrice: 0.2,
description: "Meta Llama 3.1 8B Instruct model with 128K context window.",
},
"Meta-Llama-3.1-70B-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.64,
outputPrice: 0.8,
description: "Meta Llama 3.1 70B Instruct model with 128K context window.",
},
"Meta-Llama-3.1-405B-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 3.0,
outputPrice: 15.0,
description: "Meta Llama 3.1 405B Instruct model with 128K context window.",
},
"Meta-Llama-3.2-1B-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.04,
outputPrice: 0.04,
description: "Meta Llama 3.2 1B Instruct model with 128K context window.",
},
"Meta-Llama-3.2-3B-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.06,
outputPrice: 0.06,
description: "Meta Llama 3.2 3B Instruct model with 128K context window.",
},
"Meta-Llama-3.3-70B-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.64,
outputPrice: 0.8,
description: "Meta Llama 3.3 70B Instruct model with 128K context window.",
},
"Llama-3.2-11B-Vision-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.18,
outputPrice: 0.2,
description: "Meta Llama 3.2 11B Vision Instruct model with image support.",
},
"Llama-3.2-90B-Vision-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.9,
outputPrice: 1.1,
description: "Meta Llama 3.2 90B Vision Instruct model with image support.",
},
"QwQ-32B-Preview": {
maxTokens: 32768,
contextWindow: 32768,
supportsImages: false,
supportsPromptCache: false,
supportsReasoningBudget: true,
inputPrice: 0.15,
outputPrice: 0.15,
description: "Alibaba QwQ 32B Preview reasoning model.",
},
"Qwen2.5-72B-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.59,
outputPrice: 0.79,
description: "Alibaba Qwen 2.5 72B Instruct model with 128K context window.",
},
"Qwen2.5-Coder-32B-Instruct": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.29,
outputPrice: 0.39,
description: "Alibaba Qwen 2.5 Coder 32B Instruct model optimized for coding tasks.",
},
"deepseek-r1": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
supportsReasoningBudget: true,
inputPrice: 0.55,
outputPrice: 2.19,
description: "DeepSeek R1 reasoning model with 128K context window.",
},
"deepseek-r1-distill-llama-70b": {
maxTokens: 8192,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.27,
outputPrice: 1.08,
description: "DeepSeek R1 distilled Llama 70B model with 128K context window.",
},
} as const satisfies Record<string, ModelInfo>

View file

@ -29,6 +29,7 @@ import {
ChutesHandler,
LiteLLMHandler,
ClaudeCodeHandler,
SambaNovaHandler,
} from "./providers"
export interface SingleCompletionHandler {
@ -112,6 +113,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new ChutesHandler(options)
case "litellm":
return new LiteLLMHandler(options)
case "sambanova":
return new SambaNovaHandler(options)
default:
apiProvider satisfies "gemini-cli" | undefined
return new AnthropicHandler(options)

View file

@ -0,0 +1,163 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import OpenAI from "openai"
import { Anthropic } from "@anthropic-ai/sdk"
import { type SambaNovaModelId, sambaNovaModels } from "@roo-code/types"
import { SambaNovaHandler } from "../sambanova"
// Mock OpenAI
vi.mock("openai", () => {
const mockCreate = vi.fn()
return {
default: vi.fn(() => ({
chat: {
completions: {
create: mockCreate,
},
},
})),
}
})
describe("SambaNovaHandler", () => {
let handler: SambaNovaHandler
let mockCreate: any
beforeEach(() => {
vi.clearAllMocks()
mockCreate = (OpenAI as unknown as any)().chat.completions.create
handler = new SambaNovaHandler({ sambaNovaApiKey: "test-sambanova-api-key" })
})
it("should use the correct SambaNova base URL", () => {
new SambaNovaHandler({ sambaNovaApiKey: "test-sambanova-api-key" })
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.sambanova.ai/v1" }))
})
it("should use the provided API key", () => {
const sambaNovaApiKey = "test-sambanova-api-key"
new SambaNovaHandler({ sambaNovaApiKey })
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: sambaNovaApiKey }))
})
it("should throw an error if API key is not provided", () => {
expect(() => new SambaNovaHandler({} as any)).toThrow("API key is required")
})
it("should use the specified model when provided", () => {
const testModelId: SambaNovaModelId = "Meta-Llama-3.3-70B-Instruct"
const handlerWithModel = new SambaNovaHandler({
apiModelId: testModelId,
sambaNovaApiKey: "test-sambanova-api-key",
})
const model = handlerWithModel.getModel()
expect(model.id).toBe(testModelId)
expect(model.info).toEqual(sambaNovaModels[testModelId])
})
it("should use the default model when no model is specified", () => {
const model = handler.getModel()
expect(model.id).toBe("Meta-Llama-3.3-70B-Instruct")
expect(model.info).toEqual(sambaNovaModels["Meta-Llama-3.3-70B-Instruct"])
})
describe("createMessage", () => {
it("should create a streaming chat completion with correct parameters", async () => {
const systemPrompt = "You are a helpful assistant"
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: "Hello",
},
]
mockCreate.mockImplementation(() => {
const chunks = [
{
choices: [{ delta: { content: "Hi there!" } }],
},
{
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 5 },
},
]
return {
[Symbol.asyncIterator]: async function* () {
for (const chunk of chunks) {
yield chunk
}
},
}
})
const stream = handler.createMessage(systemPrompt, messages)
const results = []
for await (const chunk of stream) {
results.push(chunk)
}
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "Meta-Llama-3.3-70B-Instruct",
max_tokens: 8192,
temperature: 0.7,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: "Hello" },
],
stream: true,
stream_options: { include_usage: true },
}),
)
expect(results).toEqual([
{ type: "text", text: "Hi there!" },
{ type: "usage", inputTokens: 10, outputTokens: 5 },
])
})
})
describe("completePrompt", () => {
it("should complete a prompt successfully", async () => {
const prompt = "Test prompt"
const expectedResponse = "Test response"
mockCreate.mockResolvedValue({
choices: [{ message: { content: expectedResponse } }],
})
const result = await handler.completePrompt(prompt)
expect(mockCreate).toHaveBeenCalledWith({
model: "Meta-Llama-3.3-70B-Instruct",
messages: [{ role: "user", content: prompt }],
})
expect(result).toBe(expectedResponse)
})
it("should handle errors properly", async () => {
const prompt = "Test prompt"
const errorMessage = "API Error"
mockCreate.mockRejectedValue(new Error(errorMessage))
await expect(handler.completePrompt(prompt)).rejects.toThrow(`SambaNova completion error: ${errorMessage}`)
})
})
describe("model selection", () => {
it.each(Object.keys(sambaNovaModels) as SambaNovaModelId[])("should correctly handle model %s", (modelId) => {
const modelInfo = sambaNovaModels[modelId]
const handlerWithModel = new SambaNovaHandler({
apiModelId: modelId,
sambaNovaApiKey: "test-sambanova-api-key",
})
const model = handlerWithModel.getModel()
expect(model.id).toBe(modelId)
expect(model.info).toEqual(modelInfo)
})
})
})

View file

@ -18,6 +18,7 @@ export { OpenAiNativeHandler } from "./openai-native"
export { OpenAiHandler } from "./openai"
export { OpenRouterHandler } from "./openrouter"
export { RequestyHandler } from "./requesty"
export { SambaNovaHandler } from "./sambanova"
export { UnboundHandler } from "./unbound"
export { VertexHandler } from "./vertex"
export { VsCodeLmHandler } from "./vscode-lm"

View file

@ -0,0 +1,19 @@
import { type SambaNovaModelId, sambaNovaDefaultModelId, sambaNovaModels } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../shared/api"
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
export class SambaNovaHandler extends BaseOpenAiCompatibleProvider<SambaNovaModelId> {
constructor(options: ApiHandlerOptions) {
super({
...options,
providerName: "SambaNova",
baseURL: "https://api.sambanova.ai/v1",
apiKey: options.sambaNovaApiKey,
defaultProviderModelId: sambaNovaDefaultModelId,
providerModels: sambaNovaModels,
defaultTemperature: 0.7,
})
}
}

View file

@ -25,6 +25,7 @@ import {
chutesDefaultModelId,
bedrockDefaultModelId,
vertexDefaultModelId,
sambaNovaDefaultModelId,
} from "@roo-code/types"
import { vscode } from "@src/utils/vscode"
@ -68,6 +69,7 @@ import {
OpenAICompatible,
OpenRouter,
Requesty,
SambaNova,
Unbound,
Vertex,
VSCodeLM,
@ -296,6 +298,7 @@ const ApiOptions = ({
chutes: { field: "apiModelId", default: chutesDefaultModelId },
bedrock: { field: "apiModelId", default: bedrockDefaultModelId },
vertex: { field: "apiModelId", default: vertexDefaultModelId },
sambanova: { field: "apiModelId", default: sambaNovaDefaultModelId },
openai: { field: "openAiModelId" },
ollama: { field: "ollamaModelId" },
lmstudio: { field: "lmStudioModelId" },
@ -500,6 +503,10 @@ const ApiOptions = ({
/>
)}
{selectedProvider === "sambanova" && (
<SambaNova apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "human-relay" && (
<>
<div className="text-sm text-vscode-descriptionForeground">

View file

@ -13,6 +13,7 @@ import {
xaiModels,
groqModels,
chutesModels,
sambaNovaModels,
} from "@roo-code/types"
export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, ModelInfo>>> = {
@ -28,6 +29,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
xai: xaiModels,
groq: groqModels,
chutes: chutesModels,
sambanova: sambaNovaModels,
}
export const PROVIDERS = [
@ -53,4 +55,5 @@ export const PROVIDERS = [
{ value: "groq", label: "Groq" },
{ value: "chutes", label: "Chutes AI" },
{ value: "litellm", label: "LiteLLM" },
{ value: "sambanova", label: "SambaNova" },
].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 SambaNovaProps = {
apiConfiguration: ProviderSettings
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
}
export const SambaNova = ({ apiConfiguration, setApiConfigurationField }: SambaNovaProps) => {
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?.sambaNovaApiKey || ""}
type="password"
onInput={handleInputChange("sambaNovaApiKey")}
placeholder={t("settings:placeholders.apiKey")}
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.sambaNovaApiKey")}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground -mt-2">
{t("settings:providers.apiKeyStorageNotice")}
</div>
{!apiConfiguration?.sambaNovaApiKey && (
<VSCodeButtonLink href="https://cloud.sambanova.ai/apis" appearance="secondary">
{t("settings:providers.getSambaNovaApiKey")}
</VSCodeButtonLink>
)}
</>
)
}

View file

@ -14,6 +14,7 @@ export { OpenAI } from "./OpenAI"
export { OpenAICompatible } from "./OpenAICompatible"
export { OpenRouter } from "./OpenRouter"
export { Requesty } from "./Requesty"
export { SambaNova } from "./SambaNova"
export { Unbound } from "./Unbound"
export { Vertex } from "./Vertex"
export { VSCodeLM } from "./VSCodeLM"

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Clau API de Gemini",
"getGroqApiKey": "Obtenir clau API de Groq",
"groqApiKey": "Clau API de Groq",
"getSambaNovaApiKey": "Obtenir clau API de SambaNova",
"sambaNovaApiKey": "Clau API de SambaNova",
"getGeminiApiKey": "Obtenir clau API de Gemini",
"openAiApiKey": "Clau API d'OpenAI",
"apiKey": "Clau API",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API-Schlüssel",
"getGroqApiKey": "Groq API-Schlüssel erhalten",
"groqApiKey": "Groq API-Schlüssel",
"getSambaNovaApiKey": "SambaNova API-Schlüssel erhalten",
"sambaNovaApiKey": "SambaNova API-Schlüssel",
"getGeminiApiKey": "Gemini API-Schlüssel erhalten",
"openAiApiKey": "OpenAI API-Schlüssel",
"apiKey": "API-Schlüssel",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API Key",
"getGroqApiKey": "Get Groq API Key",
"groqApiKey": "Groq API Key",
"getSambaNovaApiKey": "Get SambaNova API Key",
"sambaNovaApiKey": "SambaNova API Key",
"getGeminiApiKey": "Get Gemini API Key",
"openAiApiKey": "OpenAI API Key",
"apiKey": "API Key",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Clave API de Gemini",
"getGroqApiKey": "Obtener clave API de Groq",
"groqApiKey": "Clave API de Groq",
"getSambaNovaApiKey": "Obtener clave API de SambaNova",
"sambaNovaApiKey": "Clave API de SambaNova",
"getGeminiApiKey": "Obtener clave API de Gemini",
"openAiApiKey": "Clave API de OpenAI",
"apiKey": "Clave API",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Clé API Gemini",
"getGroqApiKey": "Obtenir la clé API Groq",
"groqApiKey": "Clé API Groq",
"getSambaNovaApiKey": "Obtenir la clé API SambaNova",
"sambaNovaApiKey": "Clé API SambaNova",
"getGeminiApiKey": "Obtenir la clé API Gemini",
"openAiApiKey": "Clé API OpenAI",
"apiKey": "Clé API",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API कुंजी",
"getGroqApiKey": "Groq API कुंजी प्राप्त करें",
"groqApiKey": "Groq API कुंजी",
"getSambaNovaApiKey": "SambaNova API कुंजी प्राप्त करें",
"sambaNovaApiKey": "SambaNova API कुंजी",
"getGeminiApiKey": "Gemini API कुंजी प्राप्त करें",
"openAiApiKey": "OpenAI API कुंजी",
"apiKey": "API कुंजी",

View file

@ -263,6 +263,8 @@
"geminiApiKey": "Gemini API Key",
"getGroqApiKey": "Dapatkan Groq API Key",
"groqApiKey": "Groq API Key",
"getSambaNovaApiKey": "Dapatkan SambaNova API Key",
"sambaNovaApiKey": "SambaNova API Key",
"getGeminiApiKey": "Dapatkan Gemini API Key",
"openAiApiKey": "OpenAI API Key",
"apiKey": "API Key",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Chiave API Gemini",
"getGroqApiKey": "Ottieni chiave API Groq",
"groqApiKey": "Chiave API Groq",
"getSambaNovaApiKey": "Ottieni chiave API SambaNova",
"sambaNovaApiKey": "Chiave API SambaNova",
"getGeminiApiKey": "Ottieni chiave API Gemini",
"openAiApiKey": "Chiave API OpenAI",
"apiKey": "Chiave API",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini APIキー",
"getGroqApiKey": "Groq APIキーを取得",
"groqApiKey": "Groq APIキー",
"getSambaNovaApiKey": "SambaNova APIキーを取得",
"sambaNovaApiKey": "SambaNova APIキー",
"getGeminiApiKey": "Gemini APIキーを取得",
"openAiApiKey": "OpenAI APIキー",
"apiKey": "APIキー",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API 키",
"getGroqApiKey": "Groq API 키 받기",
"groqApiKey": "Groq API 키",
"getSambaNovaApiKey": "SambaNova API 키 받기",
"sambaNovaApiKey": "SambaNova API 키",
"getGeminiApiKey": "Gemini API 키 받기",
"apiKey": "API 키",
"openAiApiKey": "OpenAI API 키",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API-sleutel",
"getGroqApiKey": "Groq API-sleutel ophalen",
"groqApiKey": "Groq API-sleutel",
"getSambaNovaApiKey": "SambaNova API-sleutel ophalen",
"sambaNovaApiKey": "SambaNova API-sleutel",
"getGeminiApiKey": "Gemini API-sleutel ophalen",
"apiKey": "API-sleutel",
"openAiApiKey": "OpenAI API-sleutel",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Klucz API Gemini",
"getGroqApiKey": "Uzyskaj klucz API Groq",
"groqApiKey": "Klucz API Groq",
"getSambaNovaApiKey": "Uzyskaj klucz API SambaNova",
"sambaNovaApiKey": "Klucz API SambaNova",
"getGeminiApiKey": "Uzyskaj klucz API Gemini",
"apiKey": "Klucz API",
"openAiApiKey": "Klucz API OpenAI",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Chave de API Gemini",
"getGroqApiKey": "Obter chave de API Groq",
"groqApiKey": "Chave de API Groq",
"getSambaNovaApiKey": "Obter chave de API SambaNova",
"sambaNovaApiKey": "Chave de API SambaNova",
"getGeminiApiKey": "Obter chave de API Gemini",
"apiKey": "Chave de API",
"openAiApiKey": "Chave de API OpenAI",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API-ключ",
"getGroqApiKey": "Получить Groq API-ключ",
"groqApiKey": "Groq API-ключ",
"getSambaNovaApiKey": "Получить SambaNova API-ключ",
"sambaNovaApiKey": "SambaNova API-ключ",
"getGeminiApiKey": "Получить Gemini API-ключ",
"apiKey": "API-ключ",
"openAiApiKey": "OpenAI API-ключ",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API Anahtarı",
"getGroqApiKey": "Groq API Anahtarı Al",
"groqApiKey": "Groq API Anahtarı",
"getSambaNovaApiKey": "SambaNova API Anahtarı Al",
"sambaNovaApiKey": "SambaNova API Anahtarı",
"getGeminiApiKey": "Gemini API Anahtarı Al",
"openAiApiKey": "OpenAI API Anahtarı",
"apiKey": "API Anahtarı",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Khóa API Gemini",
"getGroqApiKey": "Lấy khóa API Groq",
"groqApiKey": "Khóa API Groq",
"getSambaNovaApiKey": "Lấy khóa API SambaNova",
"sambaNovaApiKey": "Khóa API SambaNova",
"getGeminiApiKey": "Lấy khóa API Gemini",
"openAiApiKey": "Khóa API OpenAI",
"apiKey": "Khóa API",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API 密钥",
"getGroqApiKey": "获取 Groq API 密钥",
"groqApiKey": "Groq API 密钥",
"getSambaNovaApiKey": "获取 SambaNova API 密钥",
"sambaNovaApiKey": "SambaNova API 密钥",
"getGeminiApiKey": "获取 Gemini API 密钥",
"openAiApiKey": "OpenAI API 密钥",
"apiKey": "API 密钥",

View file

@ -259,6 +259,8 @@
"geminiApiKey": "Gemini API 金鑰",
"getGroqApiKey": "取得 Groq API 金鑰",
"groqApiKey": "Groq API 金鑰",
"getSambaNovaApiKey": "取得 SambaNova API 金鑰",
"sambaNovaApiKey": "SambaNova API 金鑰",
"getGeminiApiKey": "取得 Gemini API 金鑰",
"openAiApiKey": "OpenAI API 金鑰",
"apiKey": "API 金鑰",