feat: add moonshot provider (#6046)

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: CellenLee <99465814+CellenLee@users.noreply.github.com>
This commit is contained in:
Matt Rubens 2025-07-22 00:37:24 -04:00 committed by GitHub
parent b1bc085aa6
commit df6c57d293
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 522 additions and 0 deletions

View file

@ -159,6 +159,7 @@ export const SECRET_STATE_KEYS = [
"geminiApiKey",
"openAiNativeApiKey",
"deepSeekApiKey",
"moonshotApiKey",
"mistralApiKey",
"unboundApiKey",
"requestyApiKey",

View file

@ -22,6 +22,7 @@ export const providerNames = [
"gemini-cli",
"openai-native",
"mistral",
"moonshot",
"deepseek",
"unbound",
"requesty",
@ -187,6 +188,13 @@ const deepSeekSchema = apiModelIdProviderModelSchema.extend({
deepSeekApiKey: z.string().optional(),
})
const moonshotSchema = apiModelIdProviderModelSchema.extend({
moonshotBaseUrl: z
.union([z.literal("https://api.moonshot.ai/v1"), z.literal("https://api.moonshot.cn/v1")])
.optional(),
moonshotApiKey: z.string().optional(),
})
const unboundSchema = baseProviderSettingsSchema.extend({
unboundApiKey: z.string().optional(),
unboundModelId: z.string().optional(),
@ -241,6 +249,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") })),
moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })),
unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })),
requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })),
humanRelaySchema.merge(z.object({ apiProvider: z.literal("human-relay") })),
@ -269,6 +278,7 @@ export const providerSettingsSchema = z.object({
...openAiNativeSchema.shape,
...mistralSchema.shape,
...deepSeekSchema.shape,
...moonshotSchema.shape,
...unboundSchema.shape,
...requestySchema.shape,
...humanRelaySchema.shape,

View file

@ -9,6 +9,7 @@ export * from "./groq.js"
export * from "./lite-llm.js"
export * from "./lm-studio.js"
export * from "./mistral.js"
export * from "./moonshot.js"
export * from "./ollama.js"
export * from "./openai.js"
export * from "./openrouter.js"

View file

@ -0,0 +1,22 @@
import type { ModelInfo } from "../model.js"
// https://platform.moonshot.ai/
export type MoonshotModelId = keyof typeof moonshotModels
export const moonshotDefaultModelId: MoonshotModelId = "kimi-k2-0711-preview"
export const moonshotModels = {
"kimi-k2-0711-preview": {
maxTokens: 32_000,
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.6, // $0.60 per million tokens (cache miss)
outputPrice: 2.5, // $2.50 per million tokens
cacheWritesPrice: 0, // $0 per million tokens (cache miss)
cacheReadsPrice: 0.15, // $0.15 per million tokens (cache hit)
description: `Kimi K2 is a state-of-the-art mixture-of-experts (MoE) language model with 32 billion activated parameters and 1 trillion total parameters.`,
},
} as const satisfies Record<string, ModelInfo>
export const MOONSHOT_DEFAULT_TEMPERATURE = 0.6

View file

@ -17,6 +17,7 @@ import {
GeminiHandler,
OpenAiNativeHandler,
DeepSeekHandler,
MoonshotHandler,
MistralHandler,
VsCodeLmHandler,
UnboundHandler,
@ -89,6 +90,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new OpenAiNativeHandler(options)
case "deepseek":
return new DeepSeekHandler(options)
case "moonshot":
return new MoonshotHandler(options)
case "vscode-lm":
return new VsCodeLmHandler(options)
case "mistral":
@ -110,6 +113,7 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
case "litellm":
return new LiteLLMHandler(options)
default:
apiProvider satisfies "gemini-cli" | undefined
return new AnthropicHandler(options)
}
}

View file

@ -0,0 +1,297 @@
// Mocks must come first, before imports
const mockCreate = vi.fn()
vi.mock("openai", () => {
return {
__esModule: true,
default: vi.fn().mockImplementation(() => ({
chat: {
completions: {
create: mockCreate.mockImplementation(async (options) => {
if (!options.stream) {
return {
id: "test-completion",
choices: [
{
message: { role: "assistant", content: "Test response", refusal: null },
finish_reason: "stop",
index: 0,
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
cached_tokens: 2,
},
}
}
// Return async iterator for streaming
return {
[Symbol.asyncIterator]: async function* () {
yield {
choices: [
{
delta: { content: "Test response" },
index: 0,
},
],
usage: null,
}
yield {
choices: [
{
delta: {},
index: 0,
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
cached_tokens: 2,
},
}
},
}
}),
},
},
})),
}
})
import OpenAI from "openai"
import type { Anthropic } from "@anthropic-ai/sdk"
import { moonshotDefaultModelId } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../../shared/api"
import { MoonshotHandler } from "../moonshot"
describe("MoonshotHandler", () => {
let handler: MoonshotHandler
let mockOptions: ApiHandlerOptions
beforeEach(() => {
mockOptions = {
moonshotApiKey: "test-api-key",
apiModelId: "moonshot-chat",
moonshotBaseUrl: "https://api.moonshot.ai/v1",
}
handler = new MoonshotHandler(mockOptions)
vi.clearAllMocks()
})
describe("constructor", () => {
it("should initialize with provided options", () => {
expect(handler).toBeInstanceOf(MoonshotHandler)
expect(handler.getModel().id).toBe(mockOptions.apiModelId)
})
it.skip("should throw error if API key is missing", () => {
expect(() => {
new MoonshotHandler({
...mockOptions,
moonshotApiKey: undefined,
})
}).toThrow("Moonshot API key is required")
})
it("should use default model ID if not provided", () => {
const handlerWithoutModel = new MoonshotHandler({
...mockOptions,
apiModelId: undefined,
})
expect(handlerWithoutModel.getModel().id).toBe(moonshotDefaultModelId)
})
it("should use default base URL if not provided", () => {
const handlerWithoutBaseUrl = new MoonshotHandler({
...mockOptions,
moonshotBaseUrl: undefined,
})
expect(handlerWithoutBaseUrl).toBeInstanceOf(MoonshotHandler)
// The base URL is passed to OpenAI client internally
expect(OpenAI).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: "https://api.moonshot.ai/v1",
}),
)
})
it("should use chinese base URL if provided", () => {
const customBaseUrl = "https://api.moonshot.cn/v1"
const handlerWithCustomUrl = new MoonshotHandler({
...mockOptions,
moonshotBaseUrl: customBaseUrl,
})
expect(handlerWithCustomUrl).toBeInstanceOf(MoonshotHandler)
// The custom base URL is passed to OpenAI client
expect(OpenAI).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: customBaseUrl,
}),
)
})
it("should set includeMaxTokens to true", () => {
// Create a new handler and verify OpenAI client was called with includeMaxTokens
const _handler = new MoonshotHandler(mockOptions)
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: mockOptions.moonshotApiKey }))
})
})
describe("getModel", () => {
it("should return model info for valid model ID", () => {
const model = handler.getModel()
expect(model.id).toBe(mockOptions.apiModelId)
expect(model.info).toBeDefined()
expect(model.info.maxTokens).toBe(32_000)
expect(model.info.contextWindow).toBe(131_072)
expect(model.info.supportsImages).toBe(false)
expect(model.info.supportsPromptCache).toBe(true) // Should be true now
})
it("should return provided model ID with default model info if model does not exist", () => {
const handlerWithInvalidModel = new MoonshotHandler({
...mockOptions,
apiModelId: "invalid-model",
})
const model = handlerWithInvalidModel.getModel()
expect(model.id).toBe("invalid-model") // Returns provided ID
expect(model.info).toBeDefined()
// With the current implementation, it's the same object reference when using default model info
expect(model.info).toBe(handler.getModel().info)
// Should have the same base properties
expect(model.info.contextWindow).toBe(handler.getModel().info.contextWindow)
// And should have supportsPromptCache set to true
expect(model.info.supportsPromptCache).toBe(true)
})
it("should return default model if no model ID is provided", () => {
const handlerWithoutModel = new MoonshotHandler({
...mockOptions,
apiModelId: undefined,
})
const model = handlerWithoutModel.getModel()
expect(model.id).toBe(moonshotDefaultModelId)
expect(model.info).toBeDefined()
expect(model.info.supportsPromptCache).toBe(true)
})
it("should include model parameters from getModelParams", () => {
const model = handler.getModel()
expect(model).toHaveProperty("temperature")
expect(model).toHaveProperty("maxTokens")
})
})
describe("createMessage", () => {
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [
{
type: "text" as const,
text: "Hello!",
},
],
},
]
it("should handle streaming responses", async () => {
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
expect(chunks.length).toBeGreaterThan(0)
const textChunks = chunks.filter((chunk) => chunk.type === "text")
expect(textChunks).toHaveLength(1)
expect(textChunks[0].text).toBe("Test response")
})
it("should include usage information", async () => {
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
const usageChunks = chunks.filter((chunk) => chunk.type === "usage")
expect(usageChunks.length).toBeGreaterThan(0)
expect(usageChunks[0].inputTokens).toBe(10)
expect(usageChunks[0].outputTokens).toBe(5)
})
it("should include cache metrics in usage information", async () => {
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
const usageChunks = chunks.filter((chunk) => chunk.type === "usage")
expect(usageChunks.length).toBeGreaterThan(0)
expect(usageChunks[0].cacheWriteTokens).toBe(0)
expect(usageChunks[0].cacheReadTokens).toBe(2)
})
})
describe("processUsageMetrics", () => {
it("should correctly process usage metrics including cache information", () => {
// We need to access the protected method, so we'll create a test subclass
class TestMoonshotHandler extends MoonshotHandler {
public testProcessUsageMetrics(usage: any) {
return this.processUsageMetrics(usage)
}
}
const testHandler = new TestMoonshotHandler(mockOptions)
const usage = {
prompt_tokens: 100,
completion_tokens: 50,
total_tokens: 150,
cached_tokens: 20,
}
const result = testHandler.testProcessUsageMetrics(usage)
expect(result.type).toBe("usage")
expect(result.inputTokens).toBe(100)
expect(result.outputTokens).toBe(50)
expect(result.cacheWriteTokens).toBe(0)
expect(result.cacheReadTokens).toBe(20)
})
it("should handle missing cache metrics gracefully", () => {
class TestMoonshotHandler extends MoonshotHandler {
public testProcessUsageMetrics(usage: any) {
return this.processUsageMetrics(usage)
}
}
const testHandler = new TestMoonshotHandler(mockOptions)
const usage = {
prompt_tokens: 100,
completion_tokens: 50,
total_tokens: 150,
// No cached_tokens
}
const result = testHandler.testProcessUsageMetrics(usage)
expect(result.type).toBe("usage")
expect(result.inputTokens).toBe(100)
expect(result.outputTokens).toBe(50)
expect(result.cacheWriteTokens).toBe(0)
expect(result.cacheReadTokens).toBeUndefined()
})
})
})

View file

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

View file

@ -0,0 +1,39 @@
import { moonshotModels, moonshotDefaultModelId } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../shared/api"
import type { ApiStreamUsageChunk } from "../transform/stream"
import { getModelParams } from "../transform/model-params"
import { OpenAiHandler } from "./openai"
export class MoonshotHandler extends OpenAiHandler {
constructor(options: ApiHandlerOptions) {
super({
...options,
openAiApiKey: options.moonshotApiKey ?? "not-provided",
openAiModelId: options.apiModelId ?? moonshotDefaultModelId,
openAiBaseUrl: options.moonshotBaseUrl ?? "https://api.moonshot.ai/v1",
openAiStreamingEnabled: true,
includeMaxTokens: true,
})
}
override getModel() {
const id = this.options.apiModelId ?? moonshotDefaultModelId
const info = moonshotModels[id as keyof typeof moonshotModels] || moonshotModels[moonshotDefaultModelId]
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
return { id, info, ...params }
}
// Override to handle Moonshot'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: 0,
cacheReadTokens: usage?.cached_tokens,
}
}
}

View file

@ -53,6 +53,7 @@ describe("checkExistKey", () => {
geminiApiKey: undefined,
openAiNativeApiKey: undefined,
deepSeekApiKey: undefined,
moonshotApiKey: undefined,
mistralApiKey: undefined,
vsCodeLmModelSelector: undefined,
requestyApiKey: undefined,

View file

@ -18,6 +18,7 @@ import {
claudeCodeDefaultModelId,
geminiDefaultModelId,
deepSeekDefaultModelId,
moonshotDefaultModelId,
mistralDefaultModelId,
xaiDefaultModelId,
groqDefaultModelId,
@ -61,6 +62,7 @@ import {
LMStudio,
LiteLLM,
Mistral,
Moonshot,
Ollama,
OpenAI,
OpenAICompatible,
@ -287,6 +289,7 @@ const ApiOptions = ({
"openai-native": { field: "apiModelId", default: openAiNativeDefaultModelId },
gemini: { field: "apiModelId", default: geminiDefaultModelId },
deepseek: { field: "apiModelId", default: deepSeekDefaultModelId },
moonshot: { field: "apiModelId", default: moonshotDefaultModelId },
mistral: { field: "apiModelId", default: mistralDefaultModelId },
xai: { field: "apiModelId", default: xaiDefaultModelId },
groq: { field: "apiModelId", default: groqDefaultModelId },
@ -464,6 +467,10 @@ const ApiOptions = ({
<DeepSeek apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "moonshot" && (
<Moonshot apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "vscode-lm" && (
<VSCodeLM apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}

View file

@ -5,6 +5,7 @@ import {
bedrockModels,
claudeCodeModels,
deepSeekModels,
moonshotModels,
geminiModels,
mistralModels,
openAiNativeModels,
@ -19,6 +20,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
"claude-code": claudeCodeModels,
bedrock: bedrockModels,
deepseek: deepSeekModels,
moonshot: moonshotModels,
gemini: geminiModels,
mistral: mistralModels,
"openai-native": openAiNativeModels,
@ -34,6 +36,7 @@ export const PROVIDERS = [
{ value: "claude-code", label: "Claude Code" },
{ value: "gemini", label: "Google Gemini" },
{ value: "deepseek", label: "DeepSeek" },
{ value: "moonshot", label: "Moonshot" },
{ value: "openai-native", label: "OpenAI" },
{ value: "openai", label: "OpenAI Compatible" },
{ value: "vertex", label: "GCP Vertex AI" },

View file

@ -0,0 +1,73 @@
import { useCallback } from "react"
import { VSCodeTextField, VSCodeDropdown, VSCodeOption } 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"
import { cn } from "@/lib/utils"
type MoonshotProps = {
apiConfiguration: ProviderSettings
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
}
export const Moonshot = ({ apiConfiguration, setApiConfigurationField }: MoonshotProps) => {
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 (
<>
<div>
<label className="block font-medium mb-1">{t("settings:providers.moonshotBaseUrl")}</label>
<VSCodeDropdown
value={apiConfiguration.moonshotBaseUrl}
onChange={handleInputChange("moonshotBaseUrl")}
className={cn("w-full")}>
<VSCodeOption value="https://api.moonshot.ai/v1" className="p-2">
api.moonshot.ai
</VSCodeOption>
<VSCodeOption value="https://api.moonshot.cn/v1" className="p-2">
api.moonshot.cn
</VSCodeOption>
</VSCodeDropdown>
</div>
<div>
<VSCodeTextField
value={apiConfiguration?.moonshotApiKey || ""}
type="password"
onInput={handleInputChange("moonshotApiKey")}
placeholder={t("settings:placeholders.apiKey")}
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.moonshotApiKey")}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground">
{t("settings:providers.apiKeyStorageNotice")}
</div>
{!apiConfiguration?.moonshotApiKey && (
<VSCodeButtonLink
href={
apiConfiguration.moonshotBaseUrl === "https://api.moonshot.cn/v1"
? "https://platform.moonshot.cn/console/api-keys"
: "https://platform.moonshot.ai/console/api-keys"
}
appearance="secondary">
{t("settings:providers.getMoonshotApiKey")}
</VSCodeButtonLink>
)}
</div>
</>
)
}

View file

@ -8,6 +8,7 @@ export { Glama } from "./Glama"
export { Groq } from "./Groq"
export { LMStudio } from "./LMStudio"
export { Mistral } from "./Mistral"
export { Moonshot } from "./Moonshot"
export { Ollama } from "./Ollama"
export { OpenAI } from "./OpenAI"
export { OpenAICompatible } from "./OpenAICompatible"

View file

@ -8,6 +8,8 @@ import {
bedrockModels,
deepSeekDefaultModelId,
deepSeekModels,
moonshotDefaultModelId,
moonshotModels,
geminiDefaultModelId,
geminiModels,
mistralDefaultModelId,
@ -162,6 +164,11 @@ function getSelectedModel({
const info = deepSeekModels[id as keyof typeof deepSeekModels]
return { id, info }
}
case "moonshot": {
const id = apiConfiguration.apiModelId ?? moonshotDefaultModelId
const info = moonshotModels[id as keyof typeof moonshotModels]
return { id, info }
}
case "openai-native": {
const id = apiConfiguration.apiModelId ?? openAiNativeDefaultModelId
const info = openAiNativeModels[id as keyof typeof openAiNativeModels]
@ -211,6 +218,7 @@ function getSelectedModel({
// case "human-relay":
// case "fake-ai":
default: {
provider satisfies "anthropic" | "gemini-cli" | "human-relay" | "fake-ai"
const id = apiConfiguration.apiModelId ?? anthropicDefaultModelId
const info = anthropicModels[id as keyof typeof anthropicModels]
return { id, info }

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Obtenir clau API de Chutes",
"deepSeekApiKey": "Clau API de DeepSeek",
"getDeepSeekApiKey": "Obtenir clau API de DeepSeek",
"moonshotApiKey": "Clau API de Moonshot",
"getMoonshotApiKey": "Obtenir clau API de Moonshot",
"moonshotBaseUrl": "Punt d'entrada de Moonshot",
"geminiApiKey": "Clau API de Gemini",
"getGroqApiKey": "Obtenir clau API de Groq",
"groqApiKey": "Clau API de Groq",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API-Schlüssel erhalten",
"deepSeekApiKey": "DeepSeek API-Schlüssel",
"getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten",
"moonshotApiKey": "Moonshot API-Schlüssel",
"getMoonshotApiKey": "Moonshot API-Schlüssel erhalten",
"moonshotBaseUrl": "Moonshot-Einstiegspunkt",
"geminiApiKey": "Gemini API-Schlüssel",
"getGroqApiKey": "Groq API-Schlüssel erhalten",
"groqApiKey": "Groq API-Schlüssel",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Get Chutes API Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Get DeepSeek API Key",
"moonshotApiKey": "Moonshot API Key",
"getMoonshotApiKey": "Get Moonshot API Key",
"moonshotBaseUrl": "Moonshot Entrypoint",
"geminiApiKey": "Gemini API Key",
"getGroqApiKey": "Get Groq API Key",
"groqApiKey": "Groq API Key",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Obtener clave API de Chutes",
"deepSeekApiKey": "Clave API de DeepSeek",
"getDeepSeekApiKey": "Obtener clave API de DeepSeek",
"moonshotApiKey": "Clave API de Moonshot",
"getMoonshotApiKey": "Obtener clave API de Moonshot",
"moonshotBaseUrl": "Punto de entrada de Moonshot",
"geminiApiKey": "Clave API de Gemini",
"getGroqApiKey": "Obtener clave API de Groq",
"groqApiKey": "Clave API de Groq",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Obtenir la clé API Chutes",
"deepSeekApiKey": "Clé API DeepSeek",
"getDeepSeekApiKey": "Obtenir la clé API DeepSeek",
"moonshotApiKey": "Clé API Moonshot",
"getMoonshotApiKey": "Obtenir la clé API Moonshot",
"moonshotBaseUrl": "Point d'entrée Moonshot",
"geminiApiKey": "Clé API Gemini",
"getGroqApiKey": "Obtenir la clé API Groq",
"groqApiKey": "Clé API Groq",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API कुंजी प्राप्त करें",
"deepSeekApiKey": "DeepSeek API कुंजी",
"getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें",
"moonshotApiKey": "Moonshot API कुंजी",
"getMoonshotApiKey": "Moonshot API कुंजी प्राप्त करें",
"moonshotBaseUrl": "Moonshot प्रवेश बिंदु",
"geminiApiKey": "Gemini API कुंजी",
"getGroqApiKey": "Groq API कुंजी प्राप्त करें",
"groqApiKey": "Groq API कुंजी",

View file

@ -257,6 +257,9 @@
"getChutesApiKey": "Dapatkan Chutes API Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Dapatkan DeepSeek API Key",
"moonshotApiKey": "Kunci API Moonshot",
"getMoonshotApiKey": "Dapatkan Kunci API Moonshot",
"moonshotBaseUrl": "Titik Masuk Moonshot",
"geminiApiKey": "Gemini API Key",
"getGroqApiKey": "Dapatkan Groq API Key",
"groqApiKey": "Groq API Key",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Ottieni chiave API Chutes",
"deepSeekApiKey": "Chiave API DeepSeek",
"getDeepSeekApiKey": "Ottieni chiave API DeepSeek",
"moonshotApiKey": "Chiave API Moonshot",
"getMoonshotApiKey": "Ottieni chiave API Moonshot",
"moonshotBaseUrl": "Punto di ingresso Moonshot",
"geminiApiKey": "Chiave API Gemini",
"getGroqApiKey": "Ottieni chiave API Groq",
"groqApiKey": "Chiave API Groq",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes APIキーを取得",
"deepSeekApiKey": "DeepSeek APIキー",
"getDeepSeekApiKey": "DeepSeek APIキーを取得",
"moonshotApiKey": "Moonshot APIキー",
"getMoonshotApiKey": "Moonshot APIキーを取得",
"moonshotBaseUrl": "Moonshot エントリーポイント",
"geminiApiKey": "Gemini APIキー",
"getGroqApiKey": "Groq APIキーを取得",
"groqApiKey": "Groq APIキー",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API 키 받기",
"deepSeekApiKey": "DeepSeek API 키",
"getDeepSeekApiKey": "DeepSeek API 키 받기",
"moonshotApiKey": "Moonshot API 키",
"getMoonshotApiKey": "Moonshot API 키 받기",
"moonshotBaseUrl": "Moonshot 엔트리포인트",
"geminiApiKey": "Gemini API 키",
"getGroqApiKey": "Groq API 키 받기",
"groqApiKey": "Groq API 키",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API-sleutel ophalen",
"deepSeekApiKey": "DeepSeek API-sleutel",
"getDeepSeekApiKey": "DeepSeek API-sleutel ophalen",
"moonshotApiKey": "Moonshot API-sleutel",
"getMoonshotApiKey": "Moonshot API-sleutel ophalen",
"moonshotBaseUrl": "Moonshot-ingangspunt",
"geminiApiKey": "Gemini API-sleutel",
"getGroqApiKey": "Groq API-sleutel ophalen",
"groqApiKey": "Groq API-sleutel",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Uzyskaj klucz API Chutes",
"deepSeekApiKey": "Klucz API DeepSeek",
"getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek",
"moonshotApiKey": "Klucz API Moonshot",
"getMoonshotApiKey": "Uzyskaj klucz API Moonshot",
"moonshotBaseUrl": "Punkt wejścia Moonshot",
"geminiApiKey": "Klucz API Gemini",
"getGroqApiKey": "Uzyskaj klucz API Groq",
"groqApiKey": "Klucz API Groq",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Obter chave de API Chutes",
"deepSeekApiKey": "Chave de API DeepSeek",
"getDeepSeekApiKey": "Obter chave de API DeepSeek",
"moonshotApiKey": "Chave de API Moonshot",
"getMoonshotApiKey": "Obter chave de API Moonshot",
"moonshotBaseUrl": "Ponto de entrada Moonshot",
"geminiApiKey": "Chave de API Gemini",
"getGroqApiKey": "Obter chave de API Groq",
"groqApiKey": "Chave de API Groq",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Получить Chutes API-ключ",
"deepSeekApiKey": "DeepSeek API-ключ",
"getDeepSeekApiKey": "Получить DeepSeek API-ключ",
"moonshotApiKey": "Moonshot API-ключ",
"getMoonshotApiKey": "Получить Moonshot API-ключ",
"moonshotBaseUrl": "Точка входа Moonshot",
"geminiApiKey": "Gemini API-ключ",
"getGroqApiKey": "Получить Groq API-ключ",
"groqApiKey": "Groq API-ключ",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API Anahtarı Al",
"deepSeekApiKey": "DeepSeek API Anahtarı",
"getDeepSeekApiKey": "DeepSeek API Anahtarı Al",
"moonshotApiKey": "Moonshot API Anahtarı",
"getMoonshotApiKey": "Moonshot API Anahtarı Al",
"moonshotBaseUrl": "Moonshot Giriş Noktası",
"geminiApiKey": "Gemini API Anahtarı",
"getGroqApiKey": "Groq API Anahtarı Al",
"groqApiKey": "Groq API Anahtarı",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Lấy khóa API Chutes",
"deepSeekApiKey": "Khóa API DeepSeek",
"getDeepSeekApiKey": "Lấy khóa API DeepSeek",
"moonshotApiKey": "Khóa API Moonshot",
"getMoonshotApiKey": "Lấy khóa API Moonshot",
"moonshotBaseUrl": "Điểm vào Moonshot",
"geminiApiKey": "Khóa API Gemini",
"getGroqApiKey": "Lấy khóa API Groq",
"groqApiKey": "Khóa API Groq",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "获取 Chutes API 密钥",
"deepSeekApiKey": "DeepSeek API 密钥",
"getDeepSeekApiKey": "获取 DeepSeek API 密钥",
"moonshotApiKey": "Moonshot API 密钥",
"getMoonshotApiKey": "获取 Moonshot API 密钥",
"moonshotBaseUrl": "Moonshot 服务站点",
"geminiApiKey": "Gemini API 密钥",
"getGroqApiKey": "获取 Groq API 密钥",
"groqApiKey": "Groq API 密钥",

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "取得 Chutes API 金鑰",
"deepSeekApiKey": "DeepSeek API 金鑰",
"getDeepSeekApiKey": "取得 DeepSeek API 金鑰",
"moonshotApiKey": "Moonshot API 金鑰",
"getMoonshotApiKey": "取得 Moonshot API 金鑰",
"moonshotBaseUrl": "Moonshot 服務站點",
"geminiApiKey": "Gemini API 金鑰",
"getGroqApiKey": "取得 Groq API 金鑰",
"groqApiKey": "Groq API 金鑰",