Compare commits

...

5 Commits

Author SHA1 Message Date
celestial-vault f890503780 fix type issue 2025-06-10 19:07:28 -07:00
celestial-vault eb76d02c2c Merge branch 'main' into migrate-apiConfiguration-protobus 2025-06-10 18:45:24 -07:00
celestial-vault 5db3fd7238 merge conflicts 2025-06-06 21:23:28 -07:00
Elephant Lumps 2820f94e12 merge conflicts 2025-06-06 09:41:12 -07:00
Elephant Lumps bb06ff9dd5 migrate apiConfiguration 2025-06-05 23:24:46 -07:00
9 changed files with 684 additions and 33 deletions
+152 -7
View File
@@ -22,19 +22,21 @@ service ModelsService {
rpc refreshRequestyModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Subscribe to OpenRouter models updates
rpc subscribeToOpenRouterModels(EmptyRequest) returns (stream OpenRouterCompatibleModelInfo);
// Updates API configuration
rpc updateApiConfigurationProto(UpdateApiConfigurationRequest) returns (Empty);
}
// List of VS Code LM models
message VsCodeLmModelsArray {
repeated VsCodeLmModel models = 1;
repeated LanguageModelChatSelector models = 1;
}
// Structure representing a VS Code LM model
message VsCodeLmModel {
string vendor = 1;
string family = 2;
string version = 3;
string id = 4;
// Structure representing a language model chat selector
message LanguageModelChatSelector {
optional string vendor = 1;
optional string family = 2;
optional string version = 3;
optional string id = 4;
}
// Price tier for tiered pricing models
@@ -86,3 +88,146 @@ message OpenAiModelsRequest {
string baseUrl = 2;
string apiKey = 3;
}
// Request for updating API configuration
message UpdateApiConfigurationRequest {
Metadata metadata = 1;
ModelsApiConfiguration api_configuration = 2;
}
// API Provider enumeration
enum ApiProvider {
ANTHROPIC = 0;
OPENROUTER = 1;
BEDROCK = 2;
VERTEX = 3;
OPENAI = 4;
OLLAMA = 5;
LMSTUDIO = 6;
GEMINI = 7;
OPENAI_NATIVE = 8;
REQUESTY = 9;
TOGETHER = 10;
DEEPSEEK = 11;
QWEN = 12;
DOUBAO = 13;
MISTRAL = 14;
VSCODE_LM = 15;
CLINE = 16;
LITELLM = 17;
NEBIUS = 18;
FIREWORKS = 19;
ASKSAGE = 20;
XAI = 21;
SAMBANOVA = 22;
CEREBRAS = 23;
}
// Model info for OpenAI-compatible models
message OpenAiCompatibleModelInfo {
optional int32 max_tokens = 1;
optional int32 context_window = 2;
optional bool supports_images = 3;
bool supports_prompt_cache = 4;
optional double input_price = 5;
optional double output_price = 6;
optional ThinkingConfig thinking_config = 7;
optional bool supports_global_endpoint = 8;
optional double cache_writes_price = 9;
optional double cache_reads_price = 10;
optional string description = 11;
repeated ModelTier tiers = 12;
optional double temperature = 13;
optional bool is_r1_format_required = 14;
}
// Model info for LiteLLM models
message LiteLLMModelInfo {
optional int32 max_tokens = 1;
optional int32 context_window = 2;
optional bool supports_images = 3;
bool supports_prompt_cache = 4;
optional double input_price = 5;
optional double output_price = 6;
optional ThinkingConfig thinking_config = 7;
optional bool supports_global_endpoint = 8;
optional double cache_writes_price = 9;
optional double cache_reads_price = 10;
optional string description = 11;
repeated ModelTier tiers = 12;
optional double temperature = 13;
}
// Main ApiConfiguration message
message ModelsApiConfiguration {
// From ApiHandlerOptions (excluding onRetryAttempt function)
optional string api_model_id = 1;
optional string api_key = 2;
optional string cline_api_key = 3;
optional string task_id = 4;
optional string lite_llm_base_url = 5;
optional string lite_llm_model_id = 6;
optional string lite_llm_api_key = 7;
optional bool lite_llm_use_prompt_cache = 8;
map<string, string> open_ai_headers = 9;
optional LiteLLMModelInfo lite_llm_model_info = 10;
optional string anthropic_base_url = 11;
optional string open_router_api_key = 12;
optional string open_router_model_id = 13;
optional OpenRouterModelInfo open_router_model_info = 14;
optional string open_router_provider_sorting = 15;
optional string aws_access_key = 16;
optional string aws_secret_key = 17;
optional string aws_session_token = 18;
optional string aws_region = 19;
optional bool aws_use_cross_region_inference = 20;
optional bool aws_bedrock_use_prompt_cache = 21;
optional bool aws_use_profile = 22;
optional string aws_profile = 23;
optional string aws_bedrock_endpoint = 24;
optional bool aws_bedrock_custom_selected = 25;
optional string aws_bedrock_custom_model_base_id = 26;
optional string vertex_project_id = 27;
optional string vertex_region = 28;
optional string open_ai_base_url = 29;
optional string open_ai_api_key = 30;
optional string open_ai_model_id = 31;
optional OpenAiCompatibleModelInfo open_ai_model_info = 32;
optional string ollama_model_id = 33;
optional string ollama_base_url = 34;
optional string ollama_api_options_ctx_num = 35;
optional string lm_studio_model_id = 36;
optional string lm_studio_base_url = 37;
optional string gemini_api_key = 38;
optional string gemini_base_url = 39;
optional string open_ai_native_api_key = 40;
optional string deep_seek_api_key = 41;
optional string requesty_api_key = 42;
optional string requesty_model_id = 43;
optional OpenRouterModelInfo requesty_model_info = 44;
optional string together_api_key = 45;
optional string together_model_id = 46;
optional string fireworks_api_key = 47;
optional string fireworks_model_id = 48;
optional int32 fireworks_model_max_completion_tokens = 49;
optional int32 fireworks_model_max_tokens = 50;
optional string qwen_api_key = 51;
optional string doubao_api_key = 52;
optional string mistral_api_key = 53;
optional string azure_api_version = 54;
optional LanguageModelChatSelector vs_code_lm_model_selector = 55;
optional string qwen_api_line = 56;
optional string nebius_api_key = 57;
optional string asksage_api_url = 58;
optional string asksage_api_key = 59;
optional string xai_api_key = 60;
optional int32 thinking_budget_tokens = 61;
optional string reasoning_effort = 62;
optional string sambanova_api_key = 63;
optional string cerebras_api_key = 64;
optional int32 request_timeout_ms = 65;
// From ApiConfiguration (additional fields)
optional ApiProvider api_provider = 66;
repeated string favorited_model_ids = 67;
}
+1 -9
View File
@@ -204,15 +204,7 @@ export class Controller {
await this.setUserInfo(message.user || undefined)
await this.postStateToWebview()
break
case "apiConfiguration":
if (message.apiConfiguration) {
await updateApiConfiguration(this.context, message.apiConfiguration)
if (this.task) {
this.task.api = buildApiHandler(message.apiConfiguration)
}
}
await this.postStateToWebview()
break
case "fetchUserCreditsData": {
await this.fetchUserCreditsData()
break
@@ -0,0 +1,43 @@
import type { Controller } from "../index"
import { Empty } from "@shared/proto/common"
import { UpdateApiConfigurationRequest } from "@shared/proto/models"
import { updateApiConfiguration } from "../../storage/state"
import { buildApiHandler } from "@api/index"
import { convertProtoToApiConfiguration } from "@shared/proto-conversions/models/api-configuration-conversion"
/**
* Updates API configuration
* @param controller The controller instance
* @param request The update API configuration request
* @returns Empty response
*/
export async function updateApiConfigurationProto(
controller: Controller,
request: UpdateApiConfigurationRequest,
): Promise<Empty> {
try {
if (!request.apiConfiguration) {
console.log("[APICONFIG: updateApiConfigurationProto] API configuration is required")
throw new Error("API configuration is required")
}
// Convert proto ApiConfiguration to application ApiConfiguration
const appApiConfiguration = convertProtoToApiConfiguration(request.apiConfiguration)
// Update the API configuration in storage
await updateApiConfiguration(controller.context, appApiConfiguration)
// Update the task's API handler if there's an active task
if (controller.task) {
controller.task.api = buildApiHandler(appApiConfiguration)
}
// Post updated state to webview
await controller.postStateToWebview()
return Empty.create()
} catch (error) {
console.error(`Failed to update API configuration: ${error}`)
throw error
}
}
-1
View File
@@ -8,7 +8,6 @@ import { McpViewTab } from "./mcp"
export interface WebviewMessage {
type:
| "apiConfiguration"
| "requestVsCodeLmModels"
| "authStateChanged"
| "fetchMcpMarketplace"
@@ -0,0 +1,442 @@
import {
ApiConfiguration,
ApiProvider,
BedrockModelId,
ModelInfo,
OpenAiCompatibleModelInfo as AppOpenAiCompatibleModelInfo,
LiteLLMModelInfo as AppLiteLLMModelInfo,
} from "../../api"
import {
ModelsApiConfiguration as ProtoApiConfiguration,
ApiProvider as ProtoApiProvider,
LiteLLMModelInfo,
OpenAiCompatibleModelInfo,
OpenRouterModelInfo,
ThinkingConfig,
} from "../../proto/models"
// Convert application ThinkingConfig to proto ThinkingConfig
function convertThinkingConfigToProto(config: ModelInfo["thinkingConfig"]): ThinkingConfig | undefined {
if (!config) {
return undefined
}
return {
maxBudget: config.maxBudget,
outputPrice: config.outputPrice,
outputPriceTiers: config.outputPriceTiers || [], // Provide empty array if undefined
}
}
// Convert proto ThinkingConfig to application ThinkingConfig
function convertProtoToThinkingConfig(config: ThinkingConfig | undefined): ModelInfo["thinkingConfig"] | undefined {
if (!config) {
return undefined
}
return {
maxBudget: config.maxBudget,
outputPrice: config.outputPrice,
outputPriceTiers: config.outputPriceTiers.length > 0 ? config.outputPriceTiers : undefined,
}
}
// Convert application ModelInfo to proto OpenRouterModelInfo
function convertModelInfoToProtoOpenRouter(info: ModelInfo | undefined): OpenRouterModelInfo | undefined {
if (!info) {
return undefined
}
return {
maxTokens: info.maxTokens,
contextWindow: info.contextWindow,
supportsImages: info.supportsImages,
supportsPromptCache: info.supportsPromptCache ?? false,
inputPrice: info.inputPrice,
outputPrice: info.outputPrice,
cacheWritesPrice: info.cacheWritesPrice,
cacheReadsPrice: info.cacheReadsPrice,
description: info.description,
thinkingConfig: convertThinkingConfigToProto(info.thinkingConfig),
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
tiers: info.tiers || [],
}
}
// Convert proto OpenRouterModelInfo to application ModelInfo
function convertProtoToModelInfo(info: OpenRouterModelInfo | undefined): ModelInfo | undefined {
if (!info) {
return undefined
}
return {
maxTokens: info.maxTokens,
contextWindow: info.contextWindow,
supportsImages: info.supportsImages,
supportsPromptCache: info.supportsPromptCache,
inputPrice: info.inputPrice,
outputPrice: info.outputPrice,
cacheWritesPrice: info.cacheWritesPrice,
cacheReadsPrice: info.cacheReadsPrice,
description: info.description,
thinkingConfig: convertProtoToThinkingConfig(info.thinkingConfig),
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
tiers: info.tiers.length > 0 ? info.tiers : undefined,
}
}
// Convert application LiteLLMModelInfo to proto LiteLLMModelInfo
function convertLiteLLMModelInfoToProto(info: AppLiteLLMModelInfo | undefined): LiteLLMModelInfo | undefined {
if (!info) {
return undefined
}
return {
maxTokens: info.maxTokens,
contextWindow: info.contextWindow,
supportsImages: info.supportsImages,
supportsPromptCache: info.supportsPromptCache ?? false,
inputPrice: info.inputPrice,
outputPrice: info.outputPrice,
thinkingConfig: convertThinkingConfigToProto(info.thinkingConfig),
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
cacheWritesPrice: info.cacheWritesPrice,
cacheReadsPrice: info.cacheReadsPrice,
description: info.description,
tiers: info.tiers || [],
temperature: info.temperature,
}
}
// Convert proto LiteLLMModelInfo to application LiteLLMModelInfo
function convertProtoToLiteLLMModelInfo(info: LiteLLMModelInfo | undefined): AppLiteLLMModelInfo | undefined {
if (!info) {
return undefined
}
return {
maxTokens: info.maxTokens,
contextWindow: info.contextWindow,
supportsImages: info.supportsImages,
supportsPromptCache: info.supportsPromptCache,
inputPrice: info.inputPrice,
outputPrice: info.outputPrice,
thinkingConfig: convertProtoToThinkingConfig(info.thinkingConfig),
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
cacheWritesPrice: info.cacheWritesPrice,
cacheReadsPrice: info.cacheReadsPrice,
description: info.description,
tiers: info.tiers.length > 0 ? info.tiers : undefined,
temperature: info.temperature,
}
}
// Convert application OpenAiCompatibleModelInfo to proto OpenAiCompatibleModelInfo
function convertOpenAiCompatibleModelInfoToProto(
info: AppOpenAiCompatibleModelInfo | undefined,
): OpenAiCompatibleModelInfo | undefined {
if (!info) {
return undefined
}
return {
maxTokens: info.maxTokens,
contextWindow: info.contextWindow,
supportsImages: info.supportsImages,
supportsPromptCache: info.supportsPromptCache ?? false,
inputPrice: info.inputPrice,
outputPrice: info.outputPrice,
thinkingConfig: convertThinkingConfigToProto(info.thinkingConfig),
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
cacheWritesPrice: info.cacheWritesPrice,
cacheReadsPrice: info.cacheReadsPrice,
description: info.description,
tiers: info.tiers || [],
temperature: info.temperature,
isR1FormatRequired: info.isR1FormatRequired,
}
}
// Convert proto OpenAiCompatibleModelInfo to application OpenAiCompatibleModelInfo
function convertProtoToOpenAiCompatibleModelInfo(
info: OpenAiCompatibleModelInfo | undefined,
): AppOpenAiCompatibleModelInfo | undefined {
if (!info) {
return undefined
}
return {
maxTokens: info.maxTokens,
contextWindow: info.contextWindow,
supportsImages: info.supportsImages,
supportsPromptCache: info.supportsPromptCache,
inputPrice: info.inputPrice,
outputPrice: info.outputPrice,
thinkingConfig: convertProtoToThinkingConfig(info.thinkingConfig),
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
cacheWritesPrice: info.cacheWritesPrice,
cacheReadsPrice: info.cacheReadsPrice,
description: info.description,
tiers: info.tiers.length > 0 ? info.tiers : undefined,
temperature: info.temperature,
isR1FormatRequired: info.isR1FormatRequired,
}
}
// Convert application ApiProvider to proto ApiProvider
function convertApiProviderToProto(provider: string | undefined): ProtoApiProvider {
switch (provider) {
case "anthropic":
return ProtoApiProvider.ANTHROPIC
case "openrouter":
return ProtoApiProvider.OPENROUTER
case "bedrock":
return ProtoApiProvider.BEDROCK
case "vertex":
return ProtoApiProvider.VERTEX
case "openai":
return ProtoApiProvider.OPENAI
case "ollama":
return ProtoApiProvider.OLLAMA
case "lmstudio":
return ProtoApiProvider.LMSTUDIO
case "gemini":
return ProtoApiProvider.GEMINI
case "openai-native":
return ProtoApiProvider.OPENAI_NATIVE
case "requesty":
return ProtoApiProvider.REQUESTY
case "together":
return ProtoApiProvider.TOGETHER
case "deepseek":
return ProtoApiProvider.DEEPSEEK
case "qwen":
return ProtoApiProvider.QWEN
case "doubao":
return ProtoApiProvider.DOUBAO
case "mistral":
return ProtoApiProvider.MISTRAL
case "vscode-lm":
return ProtoApiProvider.VSCODE_LM
case "cline":
return ProtoApiProvider.CLINE
case "litellm":
return ProtoApiProvider.LITELLM
case "nebius":
return ProtoApiProvider.NEBIUS
case "fireworks":
return ProtoApiProvider.FIREWORKS
case "asksage":
return ProtoApiProvider.ASKSAGE
case "xai":
return ProtoApiProvider.XAI
case "sambanova":
return ProtoApiProvider.SAMBANOVA
case "cerebras":
return ProtoApiProvider.CEREBRAS
default:
return ProtoApiProvider.ANTHROPIC
}
}
// Convert proto ApiProvider to application ApiProvider
function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvider {
switch (provider) {
case ProtoApiProvider.ANTHROPIC:
return "anthropic"
case ProtoApiProvider.OPENROUTER:
return "openrouter"
case ProtoApiProvider.BEDROCK:
return "bedrock"
case ProtoApiProvider.VERTEX:
return "vertex"
case ProtoApiProvider.OPENAI:
return "openai"
case ProtoApiProvider.OLLAMA:
return "ollama"
case ProtoApiProvider.LMSTUDIO:
return "lmstudio"
case ProtoApiProvider.GEMINI:
return "gemini"
case ProtoApiProvider.OPENAI_NATIVE:
return "openai-native"
case ProtoApiProvider.REQUESTY:
return "requesty"
case ProtoApiProvider.TOGETHER:
return "together"
case ProtoApiProvider.DEEPSEEK:
return "deepseek"
case ProtoApiProvider.QWEN:
return "qwen"
case ProtoApiProvider.DOUBAO:
return "doubao"
case ProtoApiProvider.MISTRAL:
return "mistral"
case ProtoApiProvider.VSCODE_LM:
return "vscode-lm"
case ProtoApiProvider.CLINE:
return "cline"
case ProtoApiProvider.LITELLM:
return "litellm"
case ProtoApiProvider.NEBIUS:
return "nebius"
case ProtoApiProvider.FIREWORKS:
return "fireworks"
case ProtoApiProvider.ASKSAGE:
return "asksage"
case ProtoApiProvider.XAI:
return "xai"
case ProtoApiProvider.SAMBANOVA:
return "sambanova"
case ProtoApiProvider.CEREBRAS:
return "cerebras"
default:
return "anthropic"
}
}
// Converts application ApiConfiguration to proto ApiConfiguration
export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoApiConfiguration {
return {
apiModelId: config.apiModelId,
apiKey: config.apiKey,
clineApiKey: config.clineApiKey,
taskId: config.taskId,
liteLlmBaseUrl: config.liteLlmBaseUrl,
liteLlmModelId: config.liteLlmModelId,
liteLlmApiKey: config.liteLlmApiKey,
liteLlmUsePromptCache: config.liteLlmUsePromptCache,
openAiHeaders: config.openAiHeaders || {},
liteLlmModelInfo: convertLiteLLMModelInfoToProto(config.liteLlmModelInfo),
anthropicBaseUrl: config.anthropicBaseUrl,
openRouterApiKey: config.openRouterApiKey,
openRouterModelId: config.openRouterModelId,
openRouterModelInfo: convertModelInfoToProtoOpenRouter(config.openRouterModelInfo),
openRouterProviderSorting: config.openRouterProviderSorting,
awsAccessKey: config.awsAccessKey,
awsSecretKey: config.awsSecretKey,
awsSessionToken: config.awsSessionToken,
awsRegion: config.awsRegion,
awsUseCrossRegionInference: config.awsUseCrossRegionInference,
awsBedrockUsePromptCache: config.awsBedrockUsePromptCache,
awsUseProfile: config.awsUseProfile,
awsProfile: config.awsProfile,
awsBedrockEndpoint: config.awsBedrockEndpoint,
awsBedrockCustomSelected: config.awsBedrockCustomSelected,
awsBedrockCustomModelBaseId: config.awsBedrockCustomModelBaseId as string | undefined,
vertexProjectId: config.vertexProjectId,
vertexRegion: config.vertexRegion,
openAiBaseUrl: config.openAiBaseUrl,
openAiApiKey: config.openAiApiKey,
openAiModelId: config.openAiModelId,
openAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.openAiModelInfo),
ollamaModelId: config.ollamaModelId,
ollamaBaseUrl: config.ollamaBaseUrl,
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
lmStudioModelId: config.lmStudioModelId,
lmStudioBaseUrl: config.lmStudioBaseUrl,
geminiApiKey: config.geminiApiKey,
geminiBaseUrl: config.geminiBaseUrl,
openAiNativeApiKey: config.openAiNativeApiKey,
deepSeekApiKey: config.deepSeekApiKey,
requestyApiKey: config.requestyApiKey,
requestyModelId: config.requestyModelId,
requestyModelInfo: convertModelInfoToProtoOpenRouter(config.requestyModelInfo),
togetherApiKey: config.togetherApiKey,
togetherModelId: config.togetherModelId,
fireworksApiKey: config.fireworksApiKey,
fireworksModelId: config.fireworksModelId,
fireworksModelMaxCompletionTokens: config.fireworksModelMaxCompletionTokens,
fireworksModelMaxTokens: config.fireworksModelMaxTokens,
qwenApiKey: config.qwenApiKey,
doubaoApiKey: config.doubaoApiKey,
mistralApiKey: config.mistralApiKey,
azureApiVersion: config.azureApiVersion,
vsCodeLmModelSelector: config.vsCodeLmModelSelector,
qwenApiLine: config.qwenApiLine,
nebiusApiKey: config.nebiusApiKey,
asksageApiUrl: config.asksageApiUrl,
asksageApiKey: config.asksageApiKey,
xaiApiKey: config.xaiApiKey,
thinkingBudgetTokens: config.thinkingBudgetTokens,
reasoningEffort: config.reasoningEffort,
sambanovaApiKey: config.sambanovaApiKey,
cerebrasApiKey: config.cerebrasApiKey,
requestTimeoutMs: config.requestTimeoutMs,
apiProvider: config.apiProvider ? convertApiProviderToProto(config.apiProvider) : undefined,
favoritedModelIds: config.favoritedModelIds || [],
}
}
// Converts proto ApiConfiguration to application ApiConfiguration
export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguration): ApiConfiguration {
return {
apiModelId: protoConfig.apiModelId,
apiKey: protoConfig.apiKey,
clineApiKey: protoConfig.clineApiKey,
taskId: protoConfig.taskId,
liteLlmBaseUrl: protoConfig.liteLlmBaseUrl,
liteLlmModelId: protoConfig.liteLlmModelId,
liteLlmApiKey: protoConfig.liteLlmApiKey,
liteLlmUsePromptCache: protoConfig.liteLlmUsePromptCache,
openAiHeaders: Object.keys(protoConfig.openAiHeaders).length > 0 ? protoConfig.openAiHeaders : undefined,
liteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.liteLlmModelInfo),
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
openRouterApiKey: protoConfig.openRouterApiKey,
openRouterModelId: protoConfig.openRouterModelId,
openRouterModelInfo: convertProtoToModelInfo(protoConfig.openRouterModelInfo),
openRouterProviderSorting: protoConfig.openRouterProviderSorting,
awsAccessKey: protoConfig.awsAccessKey,
awsSecretKey: protoConfig.awsSecretKey,
awsSessionToken: protoConfig.awsSessionToken,
awsRegion: protoConfig.awsRegion,
awsUseCrossRegionInference: protoConfig.awsUseCrossRegionInference,
awsBedrockUsePromptCache: protoConfig.awsBedrockUsePromptCache,
awsUseProfile: protoConfig.awsUseProfile,
awsProfile: protoConfig.awsProfile,
awsBedrockEndpoint: protoConfig.awsBedrockEndpoint,
awsBedrockCustomSelected: protoConfig.awsBedrockCustomSelected,
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as BedrockModelId | undefined,
vertexProjectId: protoConfig.vertexProjectId,
vertexRegion: protoConfig.vertexRegion,
openAiBaseUrl: protoConfig.openAiBaseUrl,
openAiApiKey: protoConfig.openAiApiKey,
openAiModelId: protoConfig.openAiModelId,
openAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.openAiModelInfo),
ollamaModelId: protoConfig.ollamaModelId,
ollamaBaseUrl: protoConfig.ollamaBaseUrl,
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
lmStudioModelId: protoConfig.lmStudioModelId,
lmStudioBaseUrl: protoConfig.lmStudioBaseUrl,
geminiApiKey: protoConfig.geminiApiKey,
geminiBaseUrl: protoConfig.geminiBaseUrl,
openAiNativeApiKey: protoConfig.openAiNativeApiKey,
deepSeekApiKey: protoConfig.deepSeekApiKey,
requestyApiKey: protoConfig.requestyApiKey,
requestyModelId: protoConfig.requestyModelId,
requestyModelInfo: convertProtoToModelInfo(protoConfig.requestyModelInfo),
togetherApiKey: protoConfig.togetherApiKey,
togetherModelId: protoConfig.togetherModelId,
fireworksApiKey: protoConfig.fireworksApiKey,
fireworksModelId: protoConfig.fireworksModelId,
fireworksModelMaxCompletionTokens: protoConfig.fireworksModelMaxCompletionTokens,
fireworksModelMaxTokens: protoConfig.fireworksModelMaxTokens,
qwenApiKey: protoConfig.qwenApiKey,
doubaoApiKey: protoConfig.doubaoApiKey,
mistralApiKey: protoConfig.mistralApiKey,
azureApiVersion: protoConfig.azureApiVersion,
vsCodeLmModelSelector: protoConfig.vsCodeLmModelSelector,
qwenApiLine: protoConfig.qwenApiLine,
nebiusApiKey: protoConfig.nebiusApiKey,
asksageApiUrl: protoConfig.asksageApiUrl,
asksageApiKey: protoConfig.asksageApiKey,
xaiApiKey: protoConfig.xaiApiKey,
thinkingBudgetTokens: protoConfig.thinkingBudgetTokens,
reasoningEffort: protoConfig.reasoningEffort,
sambanovaApiKey: protoConfig.sambanovaApiKey,
cerebrasApiKey: protoConfig.cerebrasApiKey,
requestTimeoutMs: protoConfig.requestTimeoutMs,
apiProvider: protoConfig.apiProvider !== undefined ? convertProtoToApiProvider(protoConfig.apiProvider) : undefined,
favoritedModelIds: protoConfig.favoritedModelIds.length > 0 ? protoConfig.favoritedModelIds : undefined,
}
}
@@ -1,4 +1,4 @@
import { VsCodeLmModel } from "../../proto/models"
import { LanguageModelChatSelector } from "../../proto/models"
/**
* Represents a VS Code language model in the native VS Code format
@@ -13,7 +13,7 @@ export interface VsCodeNativeModel {
/**
* Converts VS Code native model format to protobuf format
*/
export function convertVsCodeNativeModelsToProtoModels(models: VsCodeNativeModel[]): VsCodeLmModel[] {
export function convertVsCodeNativeModelsToProtoModels(models: VsCodeNativeModel[]): LanguageModelChatSelector[] {
return (models || []).map((model) => ({
vendor: model.vendor || "",
family: model.family || "",
@@ -6,7 +6,7 @@ import Thumbnails from "@/components/common/Thumbnails"
import Tooltip from "@/components/common/Tooltip"
import ApiOptions, { normalizeApiConfiguration } from "@/components/settings/ApiOptions"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { FileServiceClient, StateServiceClient } from "@/services/grpc-client"
import { FileServiceClient, StateServiceClient, ModelsServiceClient } from "@/services/grpc-client"
import {
ContextMenuOptionType,
getContextMenuOptions,
@@ -34,6 +34,8 @@ import { mentionRegex, mentionRegexGlobal } from "@shared/context-mentions"
import { ExtensionMessage } from "@shared/ExtensionMessage"
import { EmptyRequest, StringRequest } from "@shared/proto/common"
import { FileSearchRequest, RelativePathsRequest } from "@shared/proto/file"
import { UpdateApiConfigurationRequest } from "@shared/proto/models"
import { convertApiConfigurationToProto } from "@shared/proto-conversions/models/api-configuration-conversion"
import { PlanActMode, TogglePlanActModeRequest } from "@shared/proto/state"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
@@ -962,12 +964,20 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
)
// Separate the API config submission logic
const submitApiConfig = useCallback(() => {
const submitApiConfig = useCallback(async () => {
const apiValidationResult = validateApiConfiguration(apiConfiguration)
const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels)
if (!apiValidationResult && !modelIdValidationResult) {
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
if (!apiValidationResult && !modelIdValidationResult && apiConfiguration) {
try {
await ModelsServiceClient.updateApiConfigurationProto(
UpdateApiConfigurationRequest.create({
apiConfiguration: convertApiConfigurationToProto(apiConfiguration),
}),
)
} catch (error) {
console.error("Failed to update API configuration:", error)
}
} else {
StateServiceClient.getLatestState(EmptyRequest.create())
.then(() => {
@@ -48,7 +48,8 @@ import {
xaiModels,
} from "@shared/api"
import { EmptyRequest, StringRequest } from "@shared/proto/common"
import { OpenAiModelsRequest } from "@shared/proto/models"
import { OpenAiModelsRequest, UpdateApiConfigurationRequest } from "@shared/proto/models"
import { convertApiConfigurationToProto } from "@shared/proto-conversions/models/api-configuration-conversion"
import {
VSCodeButton,
VSCodeCheckbox,
@@ -192,12 +193,19 @@ const ApiOptions = ({
if (saveImmediately && field === "apiProvider") {
// Use apiConfiguration from the full extensionState context to send the most complete data
const currentFullApiConfig = extensionState.apiConfiguration
vscode.postMessage({
type: "apiConfiguration",
apiConfiguration: {
...currentFullApiConfig, // Send the most complete config available
apiProvider: newValue, // Override with the new provider
},
// Convert to proto format and send via gRPC
const updatedConfig = {
...currentFullApiConfig,
apiProvider: newValue,
}
const protoConfig = convertApiConfigurationToProto(updatedConfig)
ModelsServiceClient.updateApiConfigurationProto(
UpdateApiConfigurationRequest.create({
apiConfiguration: protoConfig,
}),
).catch((error) => {
console.error("Failed to update API configuration:", error)
})
}
}
@@ -5,8 +5,10 @@ import { validateApiConfiguration } from "@/utils/validate"
import { vscode } from "@/utils/vscode"
import ApiOptions from "@/components/settings/ApiOptions"
import ClineLogoWhite from "@/assets/ClineLogoWhite"
import { AccountServiceClient } from "@/services/grpc-client"
import { AccountServiceClient, ModelsServiceClient } from "@/services/grpc-client"
import { EmptyRequest } from "@shared/proto/common"
import { UpdateApiConfigurationRequest } from "@shared/proto/models"
import { convertApiConfigurationToProto } from "@shared/proto-conversions/models/api-configuration-conversion"
const WelcomeView = memo(() => {
const { apiConfiguration } = useExtensionState()
@@ -21,8 +23,18 @@ const WelcomeView = memo(() => {
)
}
const handleSubmit = () => {
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
const handleSubmit = async () => {
if (apiConfiguration) {
try {
await ModelsServiceClient.updateApiConfigurationProto(
UpdateApiConfigurationRequest.create({
apiConfiguration: convertApiConfigurationToProto(apiConfiguration),
}),
)
} catch (error) {
console.error("Failed to update API configuration:", error)
}
}
}
useEffect(() => {