Compare commits

...
Author SHA1 Message Date
Saoud Rizwan 7f3ac183f7 Create little-pens-switch.md 2025-07-15 01:13:57 -07:00
Saoud Rizwan a8cc02317d Add Moonshot AI provider 2025-07-15 00:44:13 -07:00
14 changed files with 218 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add Moonshot AI provider
+2
View File
@@ -122,6 +122,7 @@ enum ApiProvider {
CEREBRAS = 23;
SAPAICORE = 24;
CLAUDE_CODE = 25;
MOONSHOT = 26;
}
// Model info for OpenAI-compatible models
@@ -237,4 +238,5 @@ message ModelsApiConfiguration {
optional string claude_code_path = 73;
optional string aws_authentication = 74;
optional string aws_bedrock_api_key = 75;
optional string moonshot_api_key = 76;
}
+3
View File
@@ -236,4 +236,7 @@ message ApiConfiguration {
// Extension fields for Bedrock Api Keys
optional string aws_authentication = 78;
optional string aws_bedrock_api_key = 79;
// Moonshot
optional string moonshot_api_key = 80;
}
+6
View File
@@ -27,6 +27,7 @@ import { SambanovaHandler } from "./providers/sambanova"
import { CerebrasHandler } from "./providers/cerebras"
import { SapAiCoreHandler } from "./providers/sapaicore"
import { ClaudeCodeHandler } from "./providers/claude-code"
import { MoonshotHandler } from "./providers/moonshot"
export interface ApiHandler {
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
@@ -187,6 +188,11 @@ function createHandlerForProvider(apiProvider: string | undefined, options: Omit
liteLlmUsePromptCache: options.liteLlmUsePromptCache,
taskId: options.taskId,
})
case "moonshot":
return new MoonshotHandler({
moonshotApiKey: options.moonshotApiKey,
apiModelId: options.apiModelId,
})
case "nebius":
return new NebiusHandler({
nebiusApiKey: options.nebiusApiKey,
+88
View File
@@ -0,0 +1,88 @@
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { withRetry } from "../retry"
import { ApiHandler } from "../index"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import { ModelInfo, MoonshotModelId, moonshotModels, moonshotDefaultModelId } from "@/shared/api"
interface MoonshotHandlerOptions {
moonshotApiKey?: string
apiModelId?: string
}
export class MoonshotHandler implements ApiHandler {
private client: OpenAI | undefined
constructor(private readonly options: MoonshotHandlerOptions) {}
private ensureClient(): OpenAI {
if (!this.client) {
if (!this.options.moonshotApiKey) {
throw new Error("Moonshot API key is required")
}
try {
this.client = new OpenAI({
baseURL: "https://api.moonshot.ai/v1",
apiKey: this.options.moonshotApiKey,
})
} catch (error) {
throw new Error(`Error creating Moonshot client: ${error.message}`)
}
}
return this.client
}
@withRetry()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const client = this.ensureClient()
const model = this.getModel()
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
]
const stream = await client.chat.completions.create({
model: model.id,
messages: openAiMessages,
temperature: 0,
stream: true,
stream_options: { include_usage: true },
})
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {
yield {
type: "text",
text: delta.content,
}
}
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
yield {
type: "reasoning",
reasoning: (delta.reasoning_content as string | undefined) || "",
}
}
if (chunk.usage) {
yield {
type: "usage",
inputTokens: chunk.usage.prompt_tokens || 0,
outputTokens: chunk.usage.completion_tokens || 0,
}
}
}
}
getModel(): { id: MoonshotModelId; info: ModelInfo } {
const modelId = this.options.apiModelId
if (modelId && modelId in moonshotModels) {
const id = modelId as MoonshotModelId
return { id, info: moonshotModels[id] }
}
return { id: moonshotDefaultModelId, info: moonshotModels[moonshotDefaultModelId] }
}
}
+1
View File
@@ -20,6 +20,7 @@ export type SecretKey =
| "authNonce"
| "asksageApiKey"
| "xaiApiKey"
| "moonshotApiKey"
| "nebiusApiKey"
| "sambanovaApiKey"
| "cerebrasApiKey"
+6
View File
@@ -166,6 +166,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
xaiApiKey,
sambanovaApiKey,
cerebrasApiKey,
moonshotApiKey,
nebiusApiKey,
planActSeparateModelsSettingRaw,
favoritedModelIds,
@@ -241,6 +242,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getSecret(context, "xaiApiKey") as Promise<string | undefined>,
getSecret(context, "sambanovaApiKey") as Promise<string | undefined>,
getSecret(context, "cerebrasApiKey") as Promise<string | undefined>,
getSecret(context, "moonshotApiKey") as Promise<string | undefined>,
getSecret(context, "nebiusApiKey") as Promise<string | undefined>,
getGlobalState(context, "planActSeparateModelsSetting") as Promise<boolean | undefined>,
getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
@@ -437,6 +439,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
xaiApiKey,
sambanovaApiKey,
cerebrasApiKey,
moonshotApiKey,
nebiusApiKey,
favoritedModelIds,
requestTimeoutMs,
@@ -547,6 +550,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
clineAccountId,
sambanovaApiKey,
cerebrasApiKey,
moonshotApiKey,
nebiusApiKey,
favoritedModelIds,
fireworksApiKey,
@@ -643,6 +647,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
xaiApiKey,
sambanovaApiKey,
cerebrasApiKey,
moonshotApiKey,
nebiusApiKey,
sapAiCoreClientId,
sapAiCoreClientSecret,
@@ -686,6 +691,7 @@ export async function resetGlobalState(context: vscode.ExtensionContext) {
"xaiApiKey",
"sambanovaApiKey",
"cerebrasApiKey",
"moonshotApiKey",
"nebiusApiKey",
]
for (const key of secretKeys) {
+33
View File
@@ -20,6 +20,7 @@ export type ApiProvider =
| "vscode-lm"
| "cline"
| "litellm"
| "moonshot"
| "nebius"
| "fireworks"
| "asksage"
@@ -88,6 +89,7 @@ export interface ApiHandlerOptions {
azureApiVersion?: string
vsCodeLmModelSelector?: LanguageModelChatSelector
qwenApiLine?: string
moonshotApiKey?: string
nebiusApiKey?: string
asksageApiUrl?: string
asksageApiKey?: string
@@ -2552,3 +2554,34 @@ export const sapAiCoreModels = {
description: sapAiCoreModelDescription,
},
} as const satisfies Record<string, ModelInfo>
// Moonshot AI Studio
// https://platform.moonshot.ai/docs/pricing/chat
export const moonshotModels = {
"kimi-k2-0711-preview": {
maxTokens: 131_072,
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.6,
outputPrice: 2.5,
},
"moonshot-v1-128k-vision-preview": {
maxTokens: 131_072,
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 2,
outputPrice: 5,
},
"kimi-thinking-preview": {
maxTokens: 131_072,
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 30,
outputPrice: 30,
},
} as const satisfies Record<string, ModelInfo>
export type MoonshotModelId = keyof typeof moonshotModels
export const moonshotDefaultModelId = "kimi-k2-0711-preview" satisfies MoonshotModelId
@@ -222,6 +222,8 @@ function convertApiProviderToProto(provider: string | undefined): ProtoApiProvid
return ProtoApiProvider.CLINE
case "litellm":
return ProtoApiProvider.LITELLM
case "moonshot":
return ProtoApiProvider.MOONSHOT
case "nebius":
return ProtoApiProvider.NEBIUS
case "fireworks":
@@ -282,6 +284,8 @@ function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvider {
return "cline"
case ProtoApiProvider.LITELLM:
return "litellm"
case ProtoApiProvider.MOONSHOT:
return "moonshot"
case ProtoApiProvider.NEBIUS:
return "nebius"
case ProtoApiProvider.FIREWORKS:
@@ -364,6 +368,7 @@ export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoA
azureApiVersion: config.azureApiVersion,
vsCodeLmModelSelector: config.vsCodeLmModelSelector,
qwenApiLine: config.qwenApiLine,
moonshotApiKey: config.moonshotApiKey,
nebiusApiKey: config.nebiusApiKey,
asksageApiUrl: config.asksageApiUrl,
asksageApiKey: config.asksageApiKey,
@@ -445,6 +450,7 @@ export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguratio
azureApiVersion: protoConfig.azureApiVersion,
vsCodeLmModelSelector: protoConfig.vsCodeLmModelSelector,
qwenApiLine: protoConfig.qwenApiLine,
moonshotApiKey: protoConfig.moonshotApiKey,
nebiusApiKey: protoConfig.nebiusApiKey,
asksageApiUrl: protoConfig.asksageApiUrl,
asksageApiKey: protoConfig.asksageApiKey,
@@ -30,6 +30,7 @@ export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfig
qwenApiKey: config.qwenApiKey,
doubaoApiKey: config.doubaoApiKey,
mistralApiKey: config.mistralApiKey,
moonshotApiKey: config.moonshotApiKey,
nebiusApiKey: config.nebiusApiKey,
asksageApiKey: config.asksageApiKey,
xaiApiKey: config.xaiApiKey,
@@ -152,6 +153,7 @@ export function convertProtoApiConfigurationToApiConfiguration(protoConfig: Prot
qwenApiKey: protoConfig.qwenApiKey,
doubaoApiKey: protoConfig.doubaoApiKey,
mistralApiKey: protoConfig.mistralApiKey,
moonshotApiKey: protoConfig.moonshotApiKey,
nebiusApiKey: protoConfig.nebiusApiKey,
asksageApiKey: protoConfig.asksageApiKey,
xaiApiKey: protoConfig.xaiApiKey,
@@ -31,6 +31,7 @@ import { OllamaProvider } from "./providers/OllamaProvider"
import { ClaudeCodeProvider } from "./providers/ClaudeCodeProvider"
import { SapAiCoreProvider } from "./providers/SapAiCoreProvider"
import { BedrockProvider } from "./providers/BedrockProvider"
import { MoonshotProvider } from "./providers/MoonshotProvider"
import { NebiusProvider } from "./providers/NebiusProvider"
import { LiteLlmProvider } from "./providers/LiteLlmProvider"
import { VSCodeLmProvider } from "./providers/VSCodeLmProvider"
@@ -146,6 +147,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
<VSCodeOption value="lmstudio">LM Studio</VSCodeOption>
<VSCodeOption value="ollama">Ollama</VSCodeOption>
<VSCodeOption value="litellm">LiteLLM</VSCodeOption>
<VSCodeOption value="moonshot">Moonshot AI</VSCodeOption>
<VSCodeOption value="nebius">Nebius AI Studio</VSCodeOption>
<VSCodeOption value="asksage">AskSage</VSCodeOption>
<VSCodeOption value="xai">xAI</VSCodeOption>
@@ -241,6 +243,10 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
<OllamaProvider showModelOptions={showModelOptions} isPopup={isPopup} />
)}
{apiConfiguration && selectedProvider === "moonshot" && (
<MoonshotProvider showModelOptions={showModelOptions} isPopup={isPopup} />
)}
{apiConfiguration && selectedProvider === "nebius" && (
<NebiusProvider showModelOptions={showModelOptions} isPopup={isPopup} />
)}
@@ -0,0 +1,51 @@
import { moonshotModels } from "@shared/api"
import { ApiKeyField } from "../common/ApiKeyField"
import { ModelSelector } from "../common/ModelSelector"
import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
/**
* Props for the MoonshotProvider component
*/
interface MoonshotProviderProps {
showModelOptions: boolean
isPopup?: boolean
}
/**
* The Moonshot AI Studio provider configuration component
*/
export const MoonshotProvider = ({ showModelOptions, isPopup }: MoonshotProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
return (
<div>
<ApiKeyField
initialValue={apiConfiguration?.moonshotApiKey || ""}
onChange={(value) => handleFieldChange("moonshotApiKey", value)}
providerName="Moonshot"
signupUrl="https://platform.moonshot.ai/console/api-keys"
helpText="This key is stored locally and only used to make API requests from this extension."
/>
{showModelOptions && (
<>
<ModelSelector
models={moonshotModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
label="Model"
/>
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
</>
)}
</div>
)
}
@@ -34,6 +34,8 @@ import {
doubaoModels,
doubaoDefaultModelId,
liteLlmModelInfoSaneDefaults,
moonshotModels,
moonshotDefaultModelId,
nebiusModels,
nebiusDefaultModelId,
cerebrasModels,
@@ -168,6 +170,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
}
case "xai":
return getProviderData(xaiModels, xaiDefaultModelId)
case "moonshot":
return getProviderData(moonshotModels, moonshotDefaultModelId)
case "nebius":
return getProviderData(nebiusModels, nebiusDefaultModelId)
case "sambanova":
+5
View File
@@ -98,6 +98,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
return "You must provide a valid model selector."
}
break
case "moonshot":
if (!apiConfiguration.moonshotApiKey) {
return "You must provide a valid API key or choose a different provider."
}
break
case "nebius":
if (!apiConfiguration.nebiusApiKey) {
return "You must provide a valid API key or choose a different provider."