Compare commits

...

17 Commits

Author SHA1 Message Date
celestial-vault 5af5a261b3 use normalizeApiConfiguration for settings menu provider value to avoid undefined state error 2025-07-18 13:49:46 -07:00
celestial-vault 94739d7a44 add new providers to migration function 2025-07-18 13:49:02 -07:00
celestial-vault 5e91b075d9 Merge branch 'main' into separate-plan-act-model-settings 2025-07-18 13:26:43 -07:00
celestial-vault 7bf9c3138c move huggingface provider into the new schema 2025-07-18 13:25:48 -07:00
celestial-vault 74243ebc8b merge conflicts 2025-07-17 19:57:32 -07:00
celestial-vault 407268c1aa remove promise resolve 2025-07-17 12:07:04 -07:00
celestial-vault 9a95e509eb consolidate to Mode type 2025-07-17 10:38:24 -07:00
celestial-vault 28022f56fd fix code after merge conflicts; switch groq provider to plan/act paradigm 2025-07-16 19:27:22 -07:00
celestial-vault 28a71ecd85 merge conflicts 2025-07-16 18:29:26 -07:00
celestial-vault 34e2f0dc41 fix frontend types 2025-07-15 13:53:09 -07:00
celestial-vault 4b1429c667 fix type errors 2025-07-15 13:32:31 -07:00
celestial-vault a6eb8765c5 add in moonshotApiKey to settings-conversion after main merge 2025-07-15 11:28:52 -07:00
celestial-vault 0bb13b1a36 merge conflicts 2025-07-15 11:22:04 -07:00
celestial-vault 574f82a171 post merge issues fixed: bedrock and cline api build handlers; current mode reading in controller/index.ts; adding a couple fields to welcomeView migration 2025-07-15 10:07:14 -07:00
celestial-vault 21eaa754ac merge conflicts 2025-07-14 19:23:36 -07:00
celestial-vault 5d47aa5f8f use separate fields for plan and act mode for ephemeral model settings 2025-07-10 23:34:52 -07:00
celestial-vault f2c9b4f42b add provider handler options types and only pass those fields when building 2025-07-08 10:57:23 -05:00
72 changed files with 2711 additions and 1425 deletions
+115 -84
View File
@@ -168,88 +168,119 @@ message LiteLLMModelInfo {
// Main ApiConfiguration message
message ModelsApiConfiguration {
// From ApiHandlerOptions (excluding onRetryAttempt function)
optional string api_model_id = 1;
optional string api_key = 2;
optional string cline_account_id = 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;
optional ApiProvider api_provider = 66;
repeated string favorited_model_ids = 67;
optional string sap_ai_core_client_id = 68;
optional string sap_ai_core_client_secret = 69;
optional string sap_ai_resource_group = 70;
optional string sap_ai_core_token_url = 71;
optional string sap_ai_core_base_url = 72;
optional string claude_code_path = 73;
optional string aws_authentication = 74;
optional string aws_bedrock_api_key = 75;
optional string moonshot_api_key = 76;
optional string moonshot_api_line = 77;
optional string groq_api_key = 78;
optional string groq_model_id = 79;
optional OpenRouterModelInfo groq_model_info = 80;
optional string hugging_face_api_key = 81;
optional string hugging_face_model_id = 82;
optional OpenRouterModelInfo hugging_face_model_info = 83;
// Global configuration fields (not mode-specific)
optional string api_key = 1;
optional string cline_api_key = 2;
optional string task_id = 3;
optional string lite_llm_base_url = 4;
optional string lite_llm_api_key = 5;
optional bool lite_llm_use_prompt_cache = 6;
map<string, string> open_ai_headers = 7;
optional string anthropic_base_url = 8;
optional string open_router_api_key = 9;
optional string open_router_provider_sorting = 10;
optional string aws_access_key = 11;
optional string aws_secret_key = 12;
optional string aws_session_token = 13;
optional string aws_region = 14;
optional bool aws_use_cross_region_inference = 15;
optional bool aws_bedrock_use_prompt_cache = 16;
optional bool aws_use_profile = 17;
optional string aws_profile = 18;
optional string aws_bedrock_endpoint = 19;
optional string claude_code_path = 20;
optional string vertex_project_id = 21;
optional string vertex_region = 22;
optional string open_ai_base_url = 23;
optional string open_ai_api_key = 24;
optional string ollama_base_url = 25;
optional string ollama_api_options_ctx_num = 26;
optional string lm_studio_base_url = 27;
optional string gemini_api_key = 28;
optional string gemini_base_url = 29;
optional string open_ai_native_api_key = 30;
optional string deep_seek_api_key = 31;
optional string requesty_api_key = 32;
optional string together_api_key = 33;
optional string fireworks_api_key = 34;
optional int32 fireworks_model_max_completion_tokens = 35;
optional int32 fireworks_model_max_tokens = 36;
optional string qwen_api_key = 37;
optional string doubao_api_key = 38;
optional string mistral_api_key = 39;
optional string azure_api_version = 40;
optional string qwen_api_line = 41;
optional string nebius_api_key = 42;
optional string asksage_api_url = 43;
optional string asksage_api_key = 44;
optional string xai_api_key = 45;
optional string sambanova_api_key = 46;
optional string cerebras_api_key = 47;
optional int32 request_timeout_ms = 48;
optional string sap_ai_core_client_id = 49;
optional string sap_ai_core_client_secret = 50;
optional string sap_ai_resource_group = 51;
optional string sap_ai_core_token_url = 52;
optional string sap_ai_core_base_url = 53;
optional string moonshot_api_key = 54;
optional string moonshot_api_line = 55;
optional string aws_authentication = 56;
optional string aws_bedrock_api_key = 57;
optional string cline_account_id = 58;
optional string groq_api_key = 59;
optional string hugging_face_api_key = 60;
// Plan mode configurations
optional ApiProvider plan_mode_api_provider = 100;
optional string plan_mode_api_model_id = 101;
optional int32 plan_mode_thinking_budget_tokens = 102;
optional string plan_mode_reasoning_effort = 103;
optional LanguageModelChatSelector plan_mode_vs_code_lm_model_selector = 104;
optional bool plan_mode_aws_bedrock_custom_selected = 105;
optional string plan_mode_aws_bedrock_custom_model_base_id = 106;
optional string plan_mode_open_router_model_id = 107;
optional OpenRouterModelInfo plan_mode_open_router_model_info = 108;
optional string plan_mode_open_ai_model_id = 109;
optional OpenAiCompatibleModelInfo plan_mode_open_ai_model_info = 110;
optional string plan_mode_ollama_model_id = 111;
optional string plan_mode_lm_studio_model_id = 112;
optional string plan_mode_lite_llm_model_id = 113;
optional LiteLLMModelInfo plan_mode_lite_llm_model_info = 114;
optional string plan_mode_requesty_model_id = 115;
optional OpenRouterModelInfo plan_mode_requesty_model_info = 116;
optional string plan_mode_together_model_id = 117;
optional string plan_mode_fireworks_model_id = 118;
optional string plan_mode_sap_ai_core_model_id = 119;
optional string plan_mode_groq_model_id = 120;
optional OpenRouterModelInfo plan_mode_groq_model_info = 121;
optional string plan_mode_hugging_face_model_id = 122;
optional OpenRouterModelInfo plan_mode_hugging_face_model_info = 123;
// Act mode configurations
optional ApiProvider act_mode_api_provider = 200;
optional string act_mode_api_model_id = 201;
optional int32 act_mode_thinking_budget_tokens = 202;
optional string act_mode_reasoning_effort = 203;
optional LanguageModelChatSelector act_mode_vs_code_lm_model_selector = 204;
optional bool act_mode_aws_bedrock_custom_selected = 205;
optional string act_mode_aws_bedrock_custom_model_base_id = 206;
optional string act_mode_open_router_model_id = 207;
optional OpenRouterModelInfo act_mode_open_router_model_info = 208;
optional string act_mode_open_ai_model_id = 209;
optional OpenAiCompatibleModelInfo act_mode_open_ai_model_info = 210;
optional string act_mode_ollama_model_id = 211;
optional string act_mode_lm_studio_model_id = 212;
optional string act_mode_lite_llm_model_id = 213;
optional LiteLLMModelInfo act_mode_lite_llm_model_info = 214;
optional string act_mode_requesty_model_id = 215;
optional OpenRouterModelInfo act_mode_requesty_model_info = 216;
optional string act_mode_together_model_id = 217;
optional string act_mode_fireworks_model_id = 218;
optional string act_mode_sap_ai_core_model_id = 219;
optional string act_mode_groq_model_id = 220;
optional OpenRouterModelInfo act_mode_groq_model_info = 221;
optional string act_mode_hugging_face_model_id = 222;
optional OpenRouterModelInfo act_mode_hugging_face_model_info = 223;
repeated string favorited_model_ids = 300;
}
+102 -115
View File
@@ -118,126 +118,113 @@ message UpdateSettingsRequest {
// Complete API Configuration message
message ApiConfiguration {
// Core API fields
optional string api_provider = 1;
optional string api_model_id = 2;
optional string api_key = 3; // anthropic
optional string api_base_url = 4;
// Global configuration fields (not mode-specific)
optional string api_key = 1; // anthropic
optional string cline_api_key = 2;
optional string task_id = 3;
optional string lite_llm_base_url = 4;
optional string lite_llm_api_key = 5;
optional bool lite_llm_use_prompt_cache = 6;
optional string openai_headers = 7; // JSON string
optional string anthropic_base_url = 8;
optional string openrouter_api_key = 9;
optional string openrouter_provider_sorting = 10;
optional string aws_access_key = 11;
optional string aws_secret_key = 12;
optional string aws_session_token = 13;
optional string aws_region = 14;
optional bool aws_use_cross_region_inference = 15;
optional bool aws_bedrock_use_prompt_cache = 16;
optional bool aws_use_profile = 17;
optional string aws_profile = 18;
optional string aws_bedrock_endpoint = 19;
optional string claude_code_path = 20;
optional string vertex_project_id = 21;
optional string vertex_region = 22;
optional string openai_base_url = 23;
optional string openai_api_key = 24;
optional string ollama_base_url = 25;
optional string ollama_api_options_ctx_num = 26;
optional string lm_studio_base_url = 27;
optional string gemini_api_key = 28;
optional string gemini_base_url = 29;
optional string openai_native_api_key = 30;
optional string deep_seek_api_key = 31;
optional string requesty_api_key = 32;
optional string together_api_key = 33;
optional string fireworks_api_key = 34;
optional int64 fireworks_model_max_completion_tokens = 35;
optional int64 fireworks_model_max_tokens = 36;
optional string qwen_api_key = 37;
optional string doubao_api_key = 38;
optional string mistral_api_key = 39;
optional string azure_api_version = 40;
optional string qwen_api_line = 41;
optional string nebius_api_key = 42;
optional string asksage_api_url = 43;
optional string asksage_api_key = 44;
optional string xai_api_key = 45;
optional string sambanova_api_key = 46;
optional string cerebras_api_key = 47;
optional int64 request_timeout_ms = 48;
optional string sap_ai_core_client_id = 49;
optional string sap_ai_core_client_secret = 50;
optional string sap_ai_resource_group = 51;
optional string sap_ai_core_token_url = 52;
optional string sap_ai_core_base_url = 53;
optional string moonshot_api_key = 54;
optional string moonshot_api_line = 55;
// Provider-specific API keys
optional string cline_account_id = 5;
optional string openrouter_api_key = 6;
optional string anthropic_base_url = 7;
optional string openai_api_key = 8;
optional string openai_native_api_key = 9;
optional string gemini_api_key = 10;
optional string deepseek_api_key = 11;
optional string requesty_api_key = 12;
optional string together_api_key = 13;
optional string fireworks_api_key = 14;
optional string qwen_api_key = 15;
optional string doubao_api_key = 16;
optional string mistral_api_key = 17;
optional string nebius_api_key = 18;
optional string asksage_api_key = 19;
optional string xai_api_key = 20;
optional string sambanova_api_key = 21;
optional string cerebras_api_key = 22;
// Plan mode configurations
optional string plan_mode_api_provider = 100;
optional string plan_mode_api_model_id = 101;
optional int64 plan_mode_thinking_budget_tokens = 102;
optional string plan_mode_reasoning_effort = 103;
optional string plan_mode_vscode_lm_model_selector = 104; // JSON string
optional bool plan_mode_aws_bedrock_custom_selected = 105;
optional string plan_mode_aws_bedrock_custom_model_base_id = 106;
optional string plan_mode_openrouter_model_id = 107;
optional string plan_mode_openrouter_model_info = 108; // JSON string
optional string plan_mode_openai_model_id = 109;
optional string plan_mode_openai_model_info = 110; // JSON string
optional string plan_mode_ollama_model_id = 111;
optional string plan_mode_lm_studio_model_id = 112;
optional string plan_mode_lite_llm_model_id = 113;
optional string plan_mode_lite_llm_model_info = 114; // JSON string
optional string plan_mode_requesty_model_id = 115;
optional string plan_mode_requesty_model_info = 116; // JSON string
optional string plan_mode_together_model_id = 117;
optional string plan_mode_fireworks_model_id = 118;
optional string plan_mode_sap_ai_core_model_id = 119;
// Model IDs
optional string openrouter_model_id = 23;
optional string openai_model_id = 24;
optional string anthropic_model_id = 25;
optional string bedrock_model_id = 26;
optional string vertex_model_id = 27;
optional string gemini_model_id = 28;
optional string ollama_model_id = 29;
optional string lm_studio_model_id = 30;
optional string litellm_model_id = 31;
optional string requesty_model_id = 32;
optional string together_model_id = 33;
optional string fireworks_model_id = 34;
// AWS Bedrock fields
optional bool aws_bedrock_custom_selected = 35;
optional string aws_bedrock_custom_model_base_id = 36;
optional string aws_access_key = 37;
optional string aws_secret_key = 38;
optional string aws_session_token = 39;
optional string aws_region = 40;
optional bool aws_use_cross_region_inference = 41;
optional bool aws_bedrock_use_prompt_cache = 42;
optional bool aws_use_profile = 43;
optional string aws_profile = 44;
optional string aws_bedrock_endpoint = 45;
// Vertex AI fields
optional string vertex_project_id = 46;
optional string vertex_region = 47;
// Base URLs and endpoints
optional string openai_base_url = 48;
optional string ollama_base_url = 49;
optional string lm_studio_base_url = 50;
optional string gemini_base_url = 51;
optional string litellm_base_url = 52;
optional string asksage_api_url = 53;
// LiteLLM specific fields
optional string litellm_api_key = 54;
optional bool litellm_use_prompt_cache = 55;
// Model configuration
optional int64 thinking_budget_tokens = 56;
optional string reasoning_effort = 57;
optional int64 request_timeout_ms = 58;
// Fireworks specific
optional int64 fireworks_model_max_completion_tokens = 59;
optional int64 fireworks_model_max_tokens = 60;
// Azure specific
optional string azure_api_version = 61;
// Ollama specific
optional string ollama_api_options_ctx_num = 62;
// Qwen specific
optional string qwen_api_line = 63;
// OpenRouter specific
optional string openrouter_provider_sorting = 64;
// VSCode LM (stored as JSON string due to complex type)
optional string vscode_lm_model_selector = 65;
// Model info objects (stored as JSON strings)
optional string openrouter_model_info = 66;
optional string openai_model_info = 67;
optional string requesty_model_info = 68;
optional string litellm_model_info = 69;
// OpenAI headers (stored as JSON string)
optional string openai_headers = 70;
// Act mode configurations
optional string act_mode_api_provider = 200;
optional string act_mode_api_model_id = 201;
optional int64 act_mode_thinking_budget_tokens = 202;
optional string act_mode_reasoning_effort = 203;
optional string act_mode_vscode_lm_model_selector = 204; // JSON string
optional bool act_mode_aws_bedrock_custom_selected = 205;
optional string act_mode_aws_bedrock_custom_model_base_id = 206;
optional string act_mode_openrouter_model_id = 207;
optional string act_mode_openrouter_model_info = 208; // JSON string
optional string act_mode_openai_model_id = 209;
optional string act_mode_openai_model_info = 210; // JSON string
optional string act_mode_ollama_model_id = 211;
optional string act_mode_lm_studio_model_id = 212;
optional string act_mode_lite_llm_model_id = 213;
optional string act_mode_lite_llm_model_info = 214; // JSON string
optional string act_mode_requesty_model_id = 215;
optional string act_mode_requesty_model_info = 216; // JSON string
optional string act_mode_together_model_id = 217;
optional string act_mode_fireworks_model_id = 218;
optional string act_mode_sap_ai_core_model_id = 219;
// Favorited model IDs
repeated string favorited_model_ids = 71;
// SAP AI Core specific
optional string sap_ai_core_client_id = 72;
optional string sap_ai_core_client_secret = 73;
optional string sap_ai_core_base_url = 74;
optional string sap_ai_core_token_url = 75;
optional string sap_ai_resource_group = 76;
// Claude Code specific
optional string claude_code_path = 77;
repeated string favorited_model_ids = 300;
// Extension fields for Bedrock Api Keys
optional string aws_authentication = 78;
optional string aws_bedrock_api_key = 79;
optional string aws_authentication = 301;
optional string aws_bedrock_api_key = 302;
// Moonshot
optional string moonshot_api_key = 80;
optional string moonshot_api_line = 81;
optional string cline_account_id = 303;
}
+93 -64
View File
@@ -29,6 +29,7 @@ import { SapAiCoreHandler } from "./providers/sapaicore"
import { ClaudeCodeHandler } from "./providers/claude-code"
import { MoonshotHandler } from "./providers/moonshot"
import { GroqHandler } from "./providers/groq"
import { Mode } from "../shared/ChatSettings"
import { HuggingFaceHandler } from "./providers/huggingface"
export interface ApiHandler {
@@ -41,27 +42,33 @@ export interface SingleCompletionHandler {
completePrompt(prompt: string): Promise<string>
}
function createHandlerForProvider(apiProvider: string | undefined, options: Omit<ApiConfiguration, "apiProvider">): ApiHandler {
function createHandlerForProvider(
apiProvider: string | undefined,
options: Omit<ApiConfiguration, "apiProvider">,
mode: Mode,
): ApiHandler {
switch (apiProvider) {
case "anthropic":
return new AnthropicHandler({
apiKey: options.apiKey,
anthropicBaseUrl: options.anthropicBaseUrl,
apiModelId: options.apiModelId,
thinkingBudgetTokens: options.thinkingBudgetTokens,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
})
case "openrouter":
return new OpenRouterHandler({
openRouterApiKey: options.openRouterApiKey,
openRouterModelId: options.openRouterModelId,
openRouterModelInfo: options.openRouterModelInfo,
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
openRouterProviderSorting: options.openRouterProviderSorting,
reasoningEffort: options.reasoningEffort,
thinkingBudgetTokens: options.thinkingBudgetTokens,
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
})
case "bedrock":
return new AwsBedrockHandler({
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
awsAccessKey: options.awsAccessKey,
awsSecretKey: options.awsSecretKey,
awsSessionToken: options.awsSessionToken,
@@ -73,16 +80,20 @@ function createHandlerForProvider(apiProvider: string | undefined, options: Omit
awsUseProfile: options.awsUseProfile,
awsProfile: options.awsProfile,
awsBedrockEndpoint: options.awsBedrockEndpoint,
awsBedrockCustomSelected: options.awsBedrockCustomSelected,
awsBedrockCustomModelBaseId: options.awsBedrockCustomModelBaseId,
thinkingBudgetTokens: options.thinkingBudgetTokens,
awsBedrockCustomSelected:
mode === "plan" ? options.planModeAwsBedrockCustomSelected : options.actModeAwsBedrockCustomSelected,
awsBedrockCustomModelBaseId:
mode === "plan" ? options.planModeAwsBedrockCustomModelBaseId : options.actModeAwsBedrockCustomModelBaseId,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
})
case "vertex":
return new VertexHandler({
vertexProjectId: options.vertexProjectId,
vertexRegion: options.vertexRegion,
apiModelId: options.apiModelId,
thinkingBudgetTokens: options.thinkingBudgetTokens,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
geminiApiKey: options.geminiApiKey,
geminiBaseUrl: options.geminiBaseUrl,
taskId: options.taskId,
@@ -93,21 +104,21 @@ function createHandlerForProvider(apiProvider: string | undefined, options: Omit
openAiBaseUrl: options.openAiBaseUrl,
azureApiVersion: options.azureApiVersion,
openAiHeaders: options.openAiHeaders,
openAiModelId: options.openAiModelId,
openAiModelInfo: options.openAiModelInfo,
reasoningEffort: options.reasoningEffort,
openAiModelId: mode === "plan" ? options.planModeOpenAiModelId : options.actModeOpenAiModelId,
openAiModelInfo: mode === "plan" ? options.planModeOpenAiModelInfo : options.actModeOpenAiModelInfo,
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
})
case "ollama":
return new OllamaHandler({
ollamaBaseUrl: options.ollamaBaseUrl,
ollamaModelId: options.ollamaModelId,
ollamaModelId: mode === "plan" ? options.planModeOllamaModelId : options.actModeOllamaModelId,
ollamaApiOptionsCtxNum: options.ollamaApiOptionsCtxNum,
requestTimeoutMs: options.requestTimeoutMs,
})
case "lmstudio":
return new LmStudioHandler({
lmStudioBaseUrl: options.lmStudioBaseUrl,
lmStudioModelId: options.lmStudioModelId,
lmStudioModelId: mode === "plan" ? options.planModeLmStudioModelId : options.actModeLmStudioModelId,
})
case "gemini":
return new GeminiHandler({
@@ -115,78 +126,85 @@ function createHandlerForProvider(apiProvider: string | undefined, options: Omit
vertexRegion: options.vertexRegion,
geminiApiKey: options.geminiApiKey,
geminiBaseUrl: options.geminiBaseUrl,
thinkingBudgetTokens: options.thinkingBudgetTokens,
apiModelId: options.apiModelId,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
taskId: options.taskId,
})
case "openai-native":
return new OpenAiNativeHandler({
openAiNativeApiKey: options.openAiNativeApiKey,
reasoningEffort: options.reasoningEffort,
apiModelId: options.apiModelId,
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "deepseek":
return new DeepSeekHandler({
deepSeekApiKey: options.deepSeekApiKey,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "requesty":
return new RequestyHandler({
requestyApiKey: options.requestyApiKey,
reasoningEffort: options.reasoningEffort,
thinkingBudgetTokens: options.thinkingBudgetTokens,
requestyModelId: options.requestyModelId,
requestyModelInfo: options.requestyModelInfo,
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
requestyModelId: mode === "plan" ? options.planModeRequestyModelId : options.actModeRequestyModelId,
requestyModelInfo: mode === "plan" ? options.planModeRequestyModelInfo : options.actModeRequestyModelInfo,
})
case "fireworks":
return new FireworksHandler({
fireworksApiKey: options.fireworksApiKey,
fireworksModelId: options.fireworksModelId,
fireworksModelId: mode === "plan" ? options.planModeFireworksModelId : options.actModeFireworksModelId,
fireworksModelMaxCompletionTokens: options.fireworksModelMaxCompletionTokens,
fireworksModelMaxTokens: options.fireworksModelMaxTokens,
})
case "together":
return new TogetherHandler({
togetherApiKey: options.togetherApiKey,
togetherModelId: options.togetherModelId,
togetherModelId: mode === "plan" ? options.planModeTogetherModelId : options.actModeTogetherModelId,
})
case "qwen":
return new QwenHandler({
qwenApiKey: options.qwenApiKey,
qwenApiLine: options.qwenApiLine,
apiModelId: options.apiModelId,
thinkingBudgetTokens: options.thinkingBudgetTokens,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
})
case "doubao":
return new DoubaoHandler({
doubaoApiKey: options.doubaoApiKey,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "mistral":
return new MistralHandler({
mistralApiKey: options.mistralApiKey,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "vscode-lm":
return new VsCodeLmHandler({
vsCodeLmModelSelector: options.vsCodeLmModelSelector,
vsCodeLmModelSelector:
mode === "plan" ? options.planModeVsCodeLmModelSelector : options.actModeVsCodeLmModelSelector,
})
case "cline":
return new ClineHandler({
clineAccountId: options.clineAccountId,
taskId: options.taskId,
reasoningEffort: options.reasoningEffort,
thinkingBudgetTokens: options.thinkingBudgetTokens,
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
openRouterProviderSorting: options.openRouterProviderSorting,
openRouterModelId: options.openRouterModelId,
openRouterModelInfo: options.openRouterModelInfo,
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
})
case "litellm":
return new LiteLlmHandler({
liteLlmApiKey: options.liteLlmApiKey,
liteLlmBaseUrl: options.liteLlmBaseUrl,
liteLlmModelId: options.liteLlmModelId,
liteLlmModelInfo: options.liteLlmModelInfo,
thinkingBudgetTokens: options.thinkingBudgetTokens,
liteLlmModelId: mode === "plan" ? options.planModeLiteLlmModelId : options.actModeLiteLlmModelId,
liteLlmModelInfo: mode === "plan" ? options.planModeLiteLlmModelInfo : options.actModeLiteLlmModelInfo,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
liteLlmUsePromptCache: options.liteLlmUsePromptCache,
taskId: options.taskId,
})
@@ -194,46 +212,48 @@ function createHandlerForProvider(apiProvider: string | undefined, options: Omit
return new MoonshotHandler({
moonshotApiKey: options.moonshotApiKey,
moonshotApiLine: options.moonshotApiLine,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "huggingface":
return new HuggingFaceHandler({
huggingFaceApiKey: options.huggingFaceApiKey,
apiModelId: options.apiModelId,
huggingFaceModelId: mode === "plan" ? options.planModeHuggingFaceModelId : options.actModeHuggingFaceModelId,
huggingFaceModelInfo:
mode === "plan" ? options.planModeHuggingFaceModelInfo : options.actModeHuggingFaceModelInfo,
})
case "nebius":
return new NebiusHandler({
nebiusApiKey: options.nebiusApiKey,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "asksage":
return new AskSageHandler({
asksageApiKey: options.asksageApiKey,
asksageApiUrl: options.asksageApiUrl,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "xai":
return new XAIHandler({
xaiApiKey: options.xaiApiKey,
reasoningEffort: options.reasoningEffort,
apiModelId: options.apiModelId,
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "sambanova":
return new SambanovaHandler({
sambanovaApiKey: options.sambanovaApiKey,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "cerebras":
return new CerebrasHandler({
cerebrasApiKey: options.cerebrasApiKey,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "groq":
return new GroqHandler({
groqApiKey: options.groqApiKey,
groqModelId: options.groqModelId,
groqModelInfo: options.groqModelInfo,
apiModelId: options.apiModelId,
groqModelId: mode === "plan" ? options.planModeGroqModelId : options.actModeGroqModelId,
groqModelInfo: mode === "plan" ? options.planModeGroqModelInfo : options.actModeGroqModelInfo,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "sapaicore":
return new SapAiCoreHandler({
@@ -242,37 +262,46 @@ function createHandlerForProvider(apiProvider: string | undefined, options: Omit
sapAiCoreTokenUrl: options.sapAiCoreTokenUrl,
sapAiResourceGroup: options.sapAiResourceGroup,
sapAiCoreBaseUrl: options.sapAiCoreBaseUrl,
apiModelId: options.apiModelId,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
})
case "claude-code":
return new ClaudeCodeHandler({
claudeCodePath: options.claudeCodePath,
apiModelId: options.apiModelId,
thinkingBudgetTokens: options.thinkingBudgetTokens,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
})
default:
return new AnthropicHandler({
apiKey: options.apiKey,
anthropicBaseUrl: options.anthropicBaseUrl,
apiModelId: options.apiModelId,
thinkingBudgetTokens: options.thinkingBudgetTokens,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
})
}
}
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
const { apiProvider, ...options } = configuration
export function buildApiHandler(configuration: ApiConfiguration, mode: Mode): ApiHandler {
const { planModeApiProvider, actModeApiProvider, ...options } = configuration
const apiProvider = mode === "plan" ? planModeApiProvider : actModeApiProvider
// Validate thinking budget tokens against model's maxTokens to prevent API errors
// wrapped in a try-catch for safety, but this should never throw
try {
if (options.thinkingBudgetTokens && options.thinkingBudgetTokens > 0) {
const handler = createHandlerForProvider(apiProvider, options)
const thinkingBudgetTokens = mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens
if (thinkingBudgetTokens && thinkingBudgetTokens > 0) {
const handler = createHandlerForProvider(apiProvider, options, mode)
const modelInfo = handler.getModel().info
if (modelInfo.maxTokens && options.thinkingBudgetTokens > modelInfo.maxTokens) {
if (modelInfo.maxTokens && thinkingBudgetTokens > modelInfo.maxTokens) {
const clippedValue = modelInfo.maxTokens - 1
options.thinkingBudgetTokens = clippedValue
if (mode === "plan") {
options.planModeThinkingBudgetTokens = clippedValue
} else {
options.actModeThinkingBudgetTokens = clippedValue
}
} else {
return handler // don't rebuild unless its necessary
}
@@ -281,5 +310,5 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
console.error("buildApiHandler error:", error)
}
return createHandlerForProvider(apiProvider, options)
return createHandlerForProvider(apiProvider, options, mode)
}
+13 -13
View File
@@ -203,7 +203,7 @@ describe("AwsBedrockHandler", () => {
})
const mockOptions: ApiHandlerOptions = {
apiModelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
actModeApiModelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
awsRegion: "us-east-1",
awsAccessKey: "test-key",
awsSecretKey: "test-secret",
@@ -214,9 +214,9 @@ describe("AwsBedrockHandler", () => {
awsBedrockUsePromptCache: false,
awsUseCrossRegionInference: false,
awsBedrockEndpoint: "",
awsBedrockCustomSelected: false,
awsBedrockCustomModelBaseId: undefined,
thinkingBudgetTokens: 1600,
actModeAwsBedrockCustomSelected: false,
actModeAwsBedrockCustomModelBaseId: undefined,
actModeThinkingBudgetTokens: 1600,
}
const mockModelInfo = {
@@ -616,8 +616,8 @@ describe("AwsBedrockHandler", () => {
it("should return raw model ID for custom models", async () => {
const customOptions: ApiHandlerOptions = {
...mockOptions,
awsBedrockCustomSelected: true,
apiModelId:
actModeAwsBedrockCustomSelected: true,
actModeApiModelId:
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
}
const customHandler = new AwsBedrockHandler(customOptions)
@@ -631,8 +631,8 @@ describe("AwsBedrockHandler", () => {
it("should not encode custom model IDs with slashes", async () => {
const customOptions: ApiHandlerOptions = {
...mockOptions,
awsBedrockCustomSelected: true,
apiModelId: "my-namespace/my-custom-model",
actModeAwsBedrockCustomSelected: true,
actModeApiModelId: "my-namespace/my-custom-model",
}
const customHandler = new AwsBedrockHandler(customOptions)
@@ -680,8 +680,8 @@ describe("AwsBedrockHandler", () => {
it("should not apply cross-region prefix for custom models even when enabled", async () => {
const customCrossRegionOptions: ApiHandlerOptions = {
...mockOptions,
awsBedrockCustomSelected: true,
apiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
actModeAwsBedrockCustomSelected: true,
actModeApiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
awsUseCrossRegionInference: true,
}
const customCrossRegionHandler = new AwsBedrockHandler(customCrossRegionOptions)
@@ -693,10 +693,10 @@ describe("AwsBedrockHandler", () => {
it("should handle UltraThink model ARN correctly", async () => {
const ultraThinkOptions: ApiHandlerOptions = {
...mockOptions,
awsBedrockCustomSelected: true,
apiModelId:
actModeAwsBedrockCustomSelected: true,
actModeApiModelId:
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
awsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
actModeAwsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
}
const ultraThinkHandler = new AwsBedrockHandler(ultraThinkOptions)
+1 -1
View File
@@ -26,7 +26,7 @@ describe("OllamaHandler", () => {
beforeEach(() => {
options = {
ollamaModelId: "llama2",
actModeOllamaModelId: "llama2",
ollamaBaseUrl: "http://localhost:11434",
}
handler = new OllamaHandler(options)
+1 -1
View File
@@ -13,7 +13,7 @@ interface AnthropicHandlerOptions {
}
export class AnthropicHandler implements ApiHandler {
private options: ApiHandlerOptions
private options: AnthropicHandlerOptions
private client: Anthropic | undefined
constructor(options: AnthropicHandlerOptions) {
+1 -1
View File
@@ -44,7 +44,7 @@ interface GeminiHandlerOptions {
* 4. Separating immediate costs from ongoing costs to avoid double-counting
*/
export class GeminiHandler implements ApiHandler {
private options: ApiHandlerOptions
private options: GeminiHandlerOptions
private client: GoogleGenAI | undefined
constructor(options: GeminiHandlerOptions) {
+3 -2
View File
@@ -9,7 +9,8 @@ import { ApiStream } from "../transform/stream"
interface HuggingFaceHandlerOptions {
huggingFaceApiKey?: string
apiModelId?: string
huggingFaceModelId?: string
huggingFaceModelInfo?: ModelInfo
}
export class HuggingFaceHandler implements ApiHandler {
@@ -115,7 +116,7 @@ export class HuggingFaceHandler implements ApiHandler {
return this.cachedModel
}
const modelId = this.options.apiModelId
const modelId = this.options.huggingFaceModelId
// List all available models for debugging
const availableModels = Object.keys(huggingFaceModels)
+9
View File
@@ -19,6 +19,15 @@ interface OpenRouterHandlerOptions {
thinkingBudgetTokens?: number
}
interface OpenRouterHandlerOptions {
openRouterApiKey?: string
openRouterModelId?: string
openRouterModelInfo?: ModelInfo
openRouterProviderSorting?: string
reasoningEffort?: string
thinkingBudgetTokens?: number
}
export class OpenRouterHandler implements ApiHandler {
private options: OpenRouterHandlerOptions
private client: OpenAI | undefined
+6
View File
@@ -14,6 +14,12 @@ interface XAIHandlerOptions {
apiModelId?: string
}
interface XAIHandlerOptions {
xaiApiKey?: string
reasoningEffort?: string
apiModelId?: string
}
export class XAIHandler implements ApiHandler {
private options: XAIHandlerOptions
private client: OpenAI | undefined
+48 -160
View File
@@ -10,7 +10,7 @@ import { ClineAccountService } from "@services/account/ClineAccountService"
import { McpHub } from "@services/mcp/McpHub"
import { ApiProvider, ModelInfo } from "@shared/api"
import { ChatContent } from "@shared/ChatContent"
import { ChatSettings, StoredChatSettings } from "@shared/ChatSettings"
import { ChatSettings, Mode, StoredChatSettings } from "@shared/ChatSettings"
import { ClineRulesToggles } from "@shared/cline-rules"
import { ExtensionMessage, ExtensionState, Platform } from "@shared/ExtensionMessage"
import { HistoryItem } from "@shared/HistoryItem"
@@ -50,6 +50,7 @@ export class Controller {
private disposables: vscode.Disposable[] = []
task?: Task
workspaceTracker: WorkspaceTracker
mcpHub: McpHub
accountService: ClineAccountService
@@ -83,8 +84,8 @@ export class Controller {
})
}
private async getCurrentMode(): Promise<"plan" | "act"> {
return ((await getGlobalState(this.context, "mode")) as "plan" | "act" | undefined) || "act"
async getCurrentMode(): Promise<Mode> {
return ((await getGlobalState(this.context, "mode")) as Mode | undefined) || "act"
}
/*
@@ -112,7 +113,10 @@ export class Controller {
// TODO: update to clineAccountId and then move clineApiKey to a clear function.
await storeSecret(this.context, "clineAccountId", undefined)
await updateGlobalState(this.context, "userInfo", undefined)
await updateGlobalState(this.context, "apiProvider", "openrouter")
await Promise.all([
updateGlobalState(this.context, "planModeApiProvider", "openrouter"),
updateGlobalState(this.context, "actModeApiProvider", "openrouter"),
])
await this.postStateToWebview()
getHostBridgeProvider().windowClient.showMessage({
type: ShowMessageType.INFORMATION,
@@ -253,153 +257,10 @@ export class Controller {
// Capture mode switch telemetry | Capture regardless of if we know the taskId
telemetryService.captureModeSwitch(this.task?.taskId ?? "0", chatSettings.mode)
// Get previous model info that we will revert to after saving current mode api info
const {
apiConfiguration,
previousModeApiProvider: newApiProvider,
previousModeModelId: newModelId,
previousModeModelInfo: newModelInfo,
previousModeVsCodeLmModelSelector: newVsCodeLmModelSelector,
previousModeThinkingBudgetTokens: newThinkingBudgetTokens,
previousModeReasoningEffort: newReasoningEffort,
previousModeAwsBedrockCustomSelected: newAwsBedrockCustomSelected,
previousModeAwsBedrockCustomModelBaseId: newAwsBedrockCustomModelBaseId,
previousModeSapAiCoreModelId: newSapAiCoreModelId,
planActSeparateModelsSetting,
} = await getAllExtensionState(this.context)
const shouldSwitchModel = planActSeparateModelsSetting === true
if (shouldSwitchModel) {
// Save the last model used in this mode
await updateGlobalState(this.context, "previousModeApiProvider", apiConfiguration.apiProvider)
await updateGlobalState(this.context, "previousModeThinkingBudgetTokens", apiConfiguration.thinkingBudgetTokens)
await updateGlobalState(this.context, "previousModeReasoningEffort", apiConfiguration.reasoningEffort)
switch (apiConfiguration.apiProvider) {
case "anthropic":
case "vertex":
case "gemini":
case "asksage":
case "openai-native":
case "qwen":
case "deepseek":
case "xai":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
break
case "bedrock":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
await updateGlobalState(
this.context,
"previousModeAwsBedrockCustomSelected",
apiConfiguration.awsBedrockCustomSelected,
)
await updateGlobalState(
this.context,
"previousModeAwsBedrockCustomModelBaseId",
apiConfiguration.awsBedrockCustomModelBaseId,
)
break
case "openrouter":
case "cline":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.openRouterModelId)
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.openRouterModelInfo)
break
case "vscode-lm":
// Important we don't set modelId to this, as it's an object not string (webview expects model id to be a string)
await updateGlobalState(
this.context,
"previousModeVsCodeLmModelSelector",
apiConfiguration.vsCodeLmModelSelector,
)
break
case "openai":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.openAiModelId)
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.openAiModelInfo)
break
case "ollama":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.ollamaModelId)
break
case "lmstudio":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.lmStudioModelId)
break
case "litellm":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.liteLlmModelId)
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.liteLlmModelInfo)
break
case "requesty":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.requestyModelId)
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.requestyModelInfo)
break
case "sapaicore":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
await updateGlobalState(this.context, "previousModeSapAiCoreModelId", apiConfiguration.sapAiCoreModelId)
break
}
// Restore the model used in previous mode
if (
newApiProvider ||
newModelId ||
newThinkingBudgetTokens !== undefined ||
newReasoningEffort ||
newVsCodeLmModelSelector
) {
await updateGlobalState(this.context, "apiProvider", newApiProvider)
await updateGlobalState(this.context, "thinkingBudgetTokens", newThinkingBudgetTokens)
await updateGlobalState(this.context, "reasoningEffort", newReasoningEffort)
switch (newApiProvider) {
case "anthropic":
case "vertex":
case "gemini":
case "asksage":
case "openai-native":
case "qwen":
case "deepseek":
case "xai":
await updateGlobalState(this.context, "apiModelId", newModelId)
break
case "bedrock":
await updateGlobalState(this.context, "apiModelId", newModelId)
await updateGlobalState(this.context, "awsBedrockCustomSelected", newAwsBedrockCustomSelected)
await updateGlobalState(this.context, "awsBedrockCustomModelBaseId", newAwsBedrockCustomModelBaseId)
break
case "openrouter":
case "cline":
await updateGlobalState(this.context, "openRouterModelId", newModelId)
await updateGlobalState(this.context, "openRouterModelInfo", newModelInfo)
break
case "vscode-lm":
await updateGlobalState(this.context, "vsCodeLmModelSelector", newVsCodeLmModelSelector)
break
case "openai":
await updateGlobalState(this.context, "openAiModelId", newModelId)
await updateGlobalState(this.context, "openAiModelInfo", newModelInfo)
break
case "ollama":
await updateGlobalState(this.context, "ollamaModelId", newModelId)
break
case "lmstudio":
await updateGlobalState(this.context, "lmStudioModelId", newModelId)
break
case "litellm":
await updateGlobalState(this.context, "liteLlmModelId", newModelId)
await updateGlobalState(this.context, "liteLlmModelInfo", newModelInfo)
break
case "requesty":
await updateGlobalState(this.context, "requestyModelId", newModelId)
await updateGlobalState(this.context, "requestyModelInfo", newModelInfo)
break
case "sapaicore":
await updateGlobalState(this.context, "apiModelId", newModelId)
await updateGlobalState(this.context, "sapAiCoreModelId", newSapAiCoreModelId)
break
}
if (this.task) {
const { apiConfiguration: updatedApiConfiguration } = await getAllExtensionState(this.context)
this.task.api = buildApiHandler(updatedApiConfiguration)
}
}
// Update API handler with new mode (buildApiHandler now selects provider based on mode)
if (this.task) {
const { apiConfiguration } = await getAllExtensionState(this.context)
this.task.api = buildApiHandler(apiConfiguration, chatSettings.mode)
}
// Save only non-mode properties to global storage
@@ -463,19 +324,38 @@ export class Controller {
await this.authService.handleAuthCallback(customToken, provider ? provider : "google")
const clineProvider: ApiProvider = "cline"
await updateGlobalState(this.context, "apiProvider", clineProvider)
// Mark welcome view as completed since user has successfully logged in
await updateGlobalState(this.context, "welcomeViewCompleted", true)
// Get current settings to determine how to update providers
const { planActSeparateModelsSetting } = await getAllExtensionState(this.context)
const currentMode = await this.getCurrentMode()
if (planActSeparateModelsSetting) {
// Only update the current mode's provider
if (currentMode === "plan") {
await updateGlobalState(this.context, "planModeApiProvider", clineProvider)
} else {
await updateGlobalState(this.context, "actModeApiProvider", clineProvider)
}
} else {
// Update both modes to keep them in sync
await Promise.all([
updateGlobalState(this.context, "planModeApiProvider", clineProvider),
updateGlobalState(this.context, "actModeApiProvider", clineProvider),
])
}
// Get the updated API configuration (now includes the updated providers)
const { apiConfiguration } = await getAllExtensionState(this.context)
const updatedConfig = {
...apiConfiguration,
apiProvider: clineProvider,
}
// Mark welcome view as completed since user has successfully logged in
await updateGlobalState(this.context, "welcomeViewCompleted", true)
if (this.task) {
this.task.api = buildApiHandler(updatedConfig)
this.task.api = buildApiHandler(updatedConfig, currentMode)
}
await this.postStateToWebview()
@@ -630,14 +510,21 @@ export class Controller {
}
const openrouter: ApiProvider = "openrouter"
await updateGlobalState(this.context, "apiProvider", openrouter)
const currentMode = await this.getCurrentMode()
await Promise.all([
updateGlobalState(this.context, "planModeApiProvider", openrouter),
updateGlobalState(this.context, "actModeApiProvider", openrouter),
])
await storeSecret(this.context, "openRouterApiKey", apiKey)
await this.postStateToWebview()
if (this.task) {
this.task.api = buildApiHandler({
apiProvider: openrouter,
// Get the updated API configuration (now includes the updated providers)
const { apiConfiguration } = await getAllExtensionState(this.context)
const updatedConfig = {
...apiConfiguration,
openRouterApiKey: apiKey,
})
}
this.task.api = buildApiHandler(updatedConfig, currentMode)
}
// await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome
}
@@ -1013,9 +900,10 @@ Commit message:`
// Get the current API configuration
const { apiConfiguration } = await getAllExtensionState(this.context)
const currentMode = await this.getCurrentMode()
// Build the API handler
const apiHandler = buildApiHandler(apiConfiguration)
const apiHandler = buildApiHandler(apiConfiguration, currentMode)
// Create a system prompt
const systemPrompt =
@@ -29,7 +29,8 @@ export async function updateApiConfigurationProto(
// Update the task's API handler if there's an active task
if (controller.task) {
controller.task.api = buildApiHandler(appApiConfiguration)
const currentMode = await controller.getCurrentMode()
controller.task.api = buildApiHandler(appApiConfiguration, currentMode)
}
// Post updated state to webview
+2 -1
View File
@@ -21,7 +21,8 @@ export async function updateSettings(controller: Controller, request: UpdateSett
await updateApiConfiguration(controller.context, apiConfiguration)
if (controller.task) {
controller.task.api = buildApiHandler(apiConfiguration)
const currentMode = await controller.getCurrentMode()
controller.task.api = buildApiHandler(apiConfiguration, currentMode)
}
}
+65 -13
View File
@@ -30,25 +30,77 @@ export async function initializeWebview(controller: Controller, request: EmptyRe
handleModelsServiceRequest(controller, "refreshOpenRouterModels", EmptyRequest.create()).then(async (response) => {
if (response && response.models) {
// Update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
const { apiConfiguration } = await getAllExtensionState(controller.context)
if (apiConfiguration.openRouterModelId && response.models[apiConfiguration.openRouterModelId]) {
await updateGlobalState(
controller.context,
"openRouterModelInfo",
response.models[apiConfiguration.openRouterModelId],
)
await controller.postStateToWebview()
const { apiConfiguration, planActSeparateModelsSetting } = await getAllExtensionState(controller.context)
const currentMode = await controller.getCurrentMode()
if (planActSeparateModelsSetting) {
// Separate models: update only current mode
const modelIdField = currentMode === "plan" ? "planModeOpenRouterModelId" : "actModeOpenRouterModelId"
const modelInfoField = currentMode === "plan" ? "planModeOpenRouterModelInfo" : "actModeOpenRouterModelInfo"
const modelId = apiConfiguration[modelIdField]
if (modelId && response.models[modelId]) {
await updateGlobalState(controller.context, modelInfoField, response.models[modelId])
await controller.postStateToWebview()
}
} else {
// Shared models: update both plan and act modes
const planModelId = apiConfiguration.planModeOpenRouterModelId
const actModelId = apiConfiguration.actModeOpenRouterModelId
// Update plan mode model info if we have a model ID
if (planModelId && response.models[planModelId]) {
await updateGlobalState(controller.context, "planModeOpenRouterModelInfo", response.models[planModelId])
}
// Update act mode model info if we have a model ID
if (actModelId && response.models[actModelId]) {
await updateGlobalState(controller.context, "actModeOpenRouterModelInfo", response.models[actModelId])
}
// Post state update if we updated any model info
if ((planModelId && response.models[planModelId]) || (actModelId && response.models[actModelId])) {
await controller.postStateToWebview()
}
}
}
})
handleModelsServiceRequest(controller, "refreshGroqModels", EmptyRequest.create()).then(async (response) => {
if (response && response.models) {
// update model info in state for Groq
const { apiConfiguration } = await getAllExtensionState(controller.context)
if (apiConfiguration.groqModelId && response.models[apiConfiguration.groqModelId]) {
await updateGlobalState(controller.context, "groqModelInfo", response.models[apiConfiguration.groqModelId])
await controller.postStateToWebview()
// Update model info in state for Groq (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
const { apiConfiguration, planActSeparateModelsSetting } = await getAllExtensionState(controller.context)
const currentMode = await controller.getCurrentMode()
if (planActSeparateModelsSetting) {
// Separate models: update only current mode
const modelIdField = currentMode === "plan" ? "planModeGroqModelId" : "actModeGroqModelId"
const modelInfoField = currentMode === "plan" ? "planModeGroqModelInfo" : "actModeGroqModelInfo"
const modelId = apiConfiguration[modelIdField]
if (modelId && response.models[modelId]) {
await updateGlobalState(controller.context, modelInfoField, response.models[modelId])
await controller.postStateToWebview()
}
} else {
// Shared models: update both plan and act modes
const planModelId = apiConfiguration.planModeGroqModelId
const actModelId = apiConfiguration.actModeGroqModelId
// Update plan mode model info if we have a model ID
if (planModelId && response.models[planModelId]) {
await updateGlobalState(controller.context, "planModeGroqModelInfo", response.models[planModelId])
}
// Update act mode model info if we have a model ID
if (actModelId && response.models[actModelId]) {
await updateGlobalState(controller.context, "actModeGroqModelInfo", response.models[actModelId])
}
// Post state update if we updated any model info
if ((planModelId && response.models[planModelId]) || (actModelId && response.models[actModelId])) {
await controller.postStateToWebview()
}
}
}
})
+2 -1
View File
@@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
import * as diff from "diff"
import * as path from "path"
import { ClineIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/ClineIgnoreController"
import { Mode } from "@/shared/ChatSettings"
export const formatResponse = {
duplicateFileReadNotice: () =>
@@ -147,7 +148,7 @@ Otherwise, if you have not completed the task and do not need additional informa
},
taskResumption: (
mode: "plan" | "act",
mode: Mode,
agoText: string,
cwd: string,
wasRecent: boolean | 0 | undefined,
+50 -38
View File
@@ -43,12 +43,9 @@ export type GlobalStateKey =
| "lastShownAnnouncementId"
| "taskHistory"
| "openAiBaseUrl"
| "openAiModelId"
| "openAiModelInfo"
| "openAiHeaders"
| "ollamaBaseUrl"
| "ollamaApiOptionsCtxNum"
| "lmStudioModelId"
| "lmStudioBaseUrl"
| "anthropicBaseUrl"
| "geminiBaseUrl"
@@ -88,40 +85,55 @@ export type GlobalStateKey =
// Settings around plan/act and ephemeral model configuration
| "chatSettings"
| "mode"
// Current active model configuration (per workspace)
| "apiProvider"
| "apiModelId"
| "thinkingBudgetTokens"
| "reasoningEffort"
| "vsCodeLmModelSelector"
| "awsBedrockCustomSelected"
| "awsBedrockCustomModelBaseId"
| "openRouterModelId"
| "openRouterModelInfo"
| "openAiModelId"
| "openAiModelInfo"
| "ollamaModelId"
| "lmStudioModelId"
| "liteLlmModelId"
| "liteLlmModelInfo"
| "requestyModelId"
| "requestyModelInfo"
| "togetherModelId"
| "fireworksModelId"
| "huggingFaceModelId"
| "huggingFaceModelInfo"
| "sapAiCoreModelId"
// Previous mode saved configurations (per workspace)
| "previousModeApiProvider"
| "previousModeModelId"
| "previousModeModelInfo"
| "previousModeVsCodeLmModelSelector"
| "previousModeThinkingBudgetTokens"
| "previousModeReasoningEffort"
| "previousModeAwsBedrockCustomSelected"
| "previousModeAwsBedrockCustomModelBaseId"
| "previousModeSapAiCoreModelId"
| "groqModelId"
| "groqModelInfo"
// Plan mode configurations
| "planModeApiProvider"
| "planModeApiModelId"
| "planModeThinkingBudgetTokens"
| "planModeReasoningEffort"
| "planModeVsCodeLmModelSelector"
| "planModeAwsBedrockCustomSelected"
| "planModeAwsBedrockCustomModelBaseId"
| "planModeOpenRouterModelId"
| "planModeOpenRouterModelInfo"
| "planModeOpenAiModelId"
| "planModeOpenAiModelInfo"
| "planModeOllamaModelId"
| "planModeLmStudioModelId"
| "planModeLiteLlmModelId"
| "planModeLiteLlmModelInfo"
| "planModeRequestyModelId"
| "planModeRequestyModelInfo"
| "planModeTogetherModelId"
| "planModeFireworksModelId"
| "planModeSapAiCoreModelId"
| "planModeGroqModelId"
| "planModeGroqModelInfo"
| "planModeHuggingFaceModelId"
| "planModeHuggingFaceModelInfo"
// Act mode configurations
| "actModeApiProvider"
| "actModeApiModelId"
| "actModeThinkingBudgetTokens"
| "actModeReasoningEffort"
| "actModeVsCodeLmModelSelector"
| "actModeAwsBedrockCustomSelected"
| "actModeAwsBedrockCustomModelBaseId"
| "actModeOpenRouterModelId"
| "actModeOpenRouterModelInfo"
| "actModeOpenAiModelId"
| "actModeOpenAiModelInfo"
| "actModeOllamaModelId"
| "actModeLmStudioModelId"
| "actModeLiteLlmModelId"
| "actModeLiteLlmModelInfo"
| "actModeRequestyModelId"
| "actModeRequestyModelInfo"
| "actModeTogetherModelId"
| "actModeFireworksModelId"
| "actModeSapAiCoreModelId"
| "actModeGroqModelId"
| "actModeGroqModelInfo"
| "actModeHuggingFaceModelId"
| "actModeHuggingFaceModelInfo"
export type LocalStateKey = "localClineRulesToggles" | "localCursorRulesToggles" | "localWindsurfRulesToggles" | "workflowToggles"
+381 -5
View File
@@ -32,6 +32,10 @@ export async function migrateWorkspaceToGlobalStorage(context: vscode.ExtensionC
"togetherModelId",
"fireworksModelId",
"sapAiCoreModelId",
"groqModelId",
"groqModelInfo",
"huggingFaceModelId",
"huggingFaceModelInfo",
// Previous mode settings
"previousModeApiProvider",
@@ -53,8 +57,8 @@ export async function migrateWorkspaceToGlobalStorage(context: vscode.ExtensionC
if (workspaceValue !== undefined && globalValue === undefined) {
console.log(`[Storage Migration] migrating key: ${key} to global storage. Current value: ${workspaceValue}`)
// Move to global storage
await updateGlobalState(context, key as GlobalStateKey, workspaceValue)
// Move to global storage using raw VSCode method to avoid type errors
await context.globalState.update(key, workspaceValue)
// Remove from workspace storage
await context.workspaceState.update(key, undefined)
const newWorkspaceValue = await context.workspaceState.get(key)
@@ -169,6 +173,375 @@ export async function migrateModeFromWorkspaceStorageToControllerState(context:
}
}
export async function migrateLegacyApiConfigurationToModeSpecific(context: vscode.ExtensionContext) {
try {
// Check if migration is needed - if planModeApiProvider already exists, skip migration
const planModeApiProvider = await context.globalState.get("planModeApiProvider")
if (planModeApiProvider !== undefined) {
console.log("Legacy API configuration migration already completed, skipping...")
return
}
console.log("Starting legacy API configuration migration to mode-specific keys...")
// Get the planActSeparateModelsSetting to determine migration strategy
const planActSeparateModelsSetting = (await context.globalState.get("planActSeparateModelsSetting")) as
| boolean
| undefined
// Read legacy values directly
const apiProvider = await context.globalState.get("apiProvider")
const apiModelId = await context.globalState.get("apiModelId")
const thinkingBudgetTokens = await context.globalState.get("thinkingBudgetTokens")
const reasoningEffort = await context.globalState.get("reasoningEffort")
const vsCodeLmModelSelector = await context.globalState.get("vsCodeLmModelSelector")
const awsBedrockCustomSelected = await context.globalState.get("awsBedrockCustomSelected")
const awsBedrockCustomModelBaseId = await context.globalState.get("awsBedrockCustomModelBaseId")
const openRouterModelId = await context.globalState.get("openRouterModelId")
const openRouterModelInfo = await context.globalState.get("openRouterModelInfo")
const openAiModelId = await context.globalState.get("openAiModelId")
const openAiModelInfo = await context.globalState.get("openAiModelInfo")
const ollamaModelId = await context.globalState.get("ollamaModelId")
const lmStudioModelId = await context.globalState.get("lmStudioModelId")
const liteLlmModelId = await context.globalState.get("liteLlmModelId")
const liteLlmModelInfo = await context.globalState.get("liteLlmModelInfo")
const requestyModelId = await context.globalState.get("requestyModelId")
const requestyModelInfo = await context.globalState.get("requestyModelInfo")
const togetherModelId = await context.globalState.get("togetherModelId")
const fireworksModelId = await context.globalState.get("fireworksModelId")
const sapAiCoreModelId = await context.globalState.get("sapAiCoreModelId")
const groqModelId = await context.globalState.get("groqModelId")
const groqModelInfo = await context.globalState.get("groqModelInfo")
const huggingFaceModelId = await context.globalState.get("huggingFaceModelId")
const huggingFaceModelInfo = await context.globalState.get("huggingFaceModelInfo")
// Read previous mode values
const previousModeApiProvider = await context.globalState.get("previousModeApiProvider")
const previousModeModelId = await context.globalState.get("previousModeModelId")
const previousModeModelInfo = await context.globalState.get("previousModeModelInfo")
const previousModeVsCodeLmModelSelector = await context.globalState.get("previousModeVsCodeLmModelSelector")
const previousModeThinkingBudgetTokens = await context.globalState.get("previousModeThinkingBudgetTokens")
const previousModeReasoningEffort = await context.globalState.get("previousModeReasoningEffort")
const previousModeAwsBedrockCustomSelected = await context.globalState.get("previousModeAwsBedrockCustomSelected")
const previousModeAwsBedrockCustomModelBaseId = await context.globalState.get("previousModeAwsBedrockCustomModelBaseId")
const previousModeSapAiCoreModelId = await context.globalState.get("previousModeSapAiCoreModelId")
// Migrate based on planActSeparateModelsSetting
if (planActSeparateModelsSetting === false) {
console.log("Migrating with separate models DISABLED - using current values for both modes")
// Use current values for both plan and act modes
if (apiProvider !== undefined) {
await context.globalState.update("planModeApiProvider", apiProvider)
await context.globalState.update("actModeApiProvider", apiProvider)
}
if (apiModelId !== undefined) {
await context.globalState.update("planModeApiModelId", apiModelId)
await context.globalState.update("actModeApiModelId", apiModelId)
}
if (thinkingBudgetTokens !== undefined) {
await context.globalState.update("planModeThinkingBudgetTokens", thinkingBudgetTokens)
await context.globalState.update("actModeThinkingBudgetTokens", thinkingBudgetTokens)
}
if (reasoningEffort !== undefined) {
await context.globalState.update("planModeReasoningEffort", reasoningEffort)
await context.globalState.update("actModeReasoningEffort", reasoningEffort)
}
if (vsCodeLmModelSelector !== undefined) {
await context.globalState.update("planModeVsCodeLmModelSelector", vsCodeLmModelSelector)
await context.globalState.update("actModeVsCodeLmModelSelector", vsCodeLmModelSelector)
}
if (awsBedrockCustomSelected !== undefined) {
await context.globalState.update("planModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
await context.globalState.update("actModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
}
if (awsBedrockCustomModelBaseId !== undefined) {
await context.globalState.update("planModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
}
if (openRouterModelId !== undefined) {
await context.globalState.update("planModeOpenRouterModelId", openRouterModelId)
await context.globalState.update("actModeOpenRouterModelId", openRouterModelId)
}
if (openRouterModelInfo !== undefined) {
await context.globalState.update("planModeOpenRouterModelInfo", openRouterModelInfo)
await context.globalState.update("actModeOpenRouterModelInfo", openRouterModelInfo)
}
if (openAiModelId !== undefined) {
await context.globalState.update("planModeOpenAiModelId", openAiModelId)
await context.globalState.update("actModeOpenAiModelId", openAiModelId)
}
if (openAiModelInfo !== undefined) {
await context.globalState.update("planModeOpenAiModelInfo", openAiModelInfo)
await context.globalState.update("actModeOpenAiModelInfo", openAiModelInfo)
}
if (ollamaModelId !== undefined) {
await context.globalState.update("planModeOllamaModelId", ollamaModelId)
await context.globalState.update("actModeOllamaModelId", ollamaModelId)
}
if (lmStudioModelId !== undefined) {
await context.globalState.update("planModeLmStudioModelId", lmStudioModelId)
await context.globalState.update("actModeLmStudioModelId", lmStudioModelId)
}
if (liteLlmModelId !== undefined) {
await context.globalState.update("planModeLiteLlmModelId", liteLlmModelId)
await context.globalState.update("actModeLiteLlmModelId", liteLlmModelId)
}
if (liteLlmModelInfo !== undefined) {
await context.globalState.update("planModeLiteLlmModelInfo", liteLlmModelInfo)
await context.globalState.update("actModeLiteLlmModelInfo", liteLlmModelInfo)
}
if (requestyModelId !== undefined) {
await context.globalState.update("planModeRequestyModelId", requestyModelId)
await context.globalState.update("actModeRequestyModelId", requestyModelId)
}
if (requestyModelInfo !== undefined) {
await context.globalState.update("planModeRequestyModelInfo", requestyModelInfo)
await context.globalState.update("actModeRequestyModelInfo", requestyModelInfo)
}
if (togetherModelId !== undefined) {
await context.globalState.update("planModeTogetherModelId", togetherModelId)
await context.globalState.update("actModeTogetherModelId", togetherModelId)
}
if (fireworksModelId !== undefined) {
await context.globalState.update("planModeFireworksModelId", fireworksModelId)
await context.globalState.update("actModeFireworksModelId", fireworksModelId)
}
if (sapAiCoreModelId !== undefined) {
await context.globalState.update("planModeSapAiCoreModelId", sapAiCoreModelId)
await context.globalState.update("actModeSapAiCoreModelId", sapAiCoreModelId)
}
if (groqModelId !== undefined) {
await context.globalState.update("planModeGroqModelId", groqModelId)
await context.globalState.update("actModeGroqModelId", groqModelId)
}
if (groqModelInfo !== undefined) {
await context.globalState.update("planModeGroqModelInfo", groqModelInfo)
await context.globalState.update("actModeGroqModelInfo", groqModelInfo)
}
if (huggingFaceModelId !== undefined) {
await context.globalState.update("planModeHuggingFaceModelId", huggingFaceModelId)
await context.globalState.update("actModeHuggingFaceModelId", huggingFaceModelId)
}
if (huggingFaceModelInfo !== undefined) {
await context.globalState.update("planModeHuggingFaceModelInfo", huggingFaceModelInfo)
await context.globalState.update("actModeHuggingFaceModelInfo", huggingFaceModelInfo)
}
} else {
console.log("Migrating with separate models ENABLED - using current->plan, previous->act")
// Use current values for plan mode
if (apiProvider !== undefined) {
await context.globalState.update("planModeApiProvider", apiProvider)
}
if (apiModelId !== undefined) {
await context.globalState.update("planModeApiModelId", apiModelId)
}
if (thinkingBudgetTokens !== undefined) {
await context.globalState.update("planModeThinkingBudgetTokens", thinkingBudgetTokens)
}
if (reasoningEffort !== undefined) {
await context.globalState.update("planModeReasoningEffort", reasoningEffort)
}
if (vsCodeLmModelSelector !== undefined) {
await context.globalState.update("planModeVsCodeLmModelSelector", vsCodeLmModelSelector)
}
if (awsBedrockCustomSelected !== undefined) {
await context.globalState.update("planModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
}
if (awsBedrockCustomModelBaseId !== undefined) {
await context.globalState.update("planModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
}
if (openRouterModelId !== undefined) {
await context.globalState.update("planModeOpenRouterModelId", openRouterModelId)
}
if (openRouterModelInfo !== undefined) {
await context.globalState.update("planModeOpenRouterModelInfo", openRouterModelInfo)
}
if (openAiModelId !== undefined) {
await context.globalState.update("planModeOpenAiModelId", openAiModelId)
}
if (openAiModelInfo !== undefined) {
await context.globalState.update("planModeOpenAiModelInfo", openAiModelInfo)
}
if (ollamaModelId !== undefined) {
await context.globalState.update("planModeOllamaModelId", ollamaModelId)
}
if (lmStudioModelId !== undefined) {
await context.globalState.update("planModeLmStudioModelId", lmStudioModelId)
}
if (liteLlmModelId !== undefined) {
await context.globalState.update("planModeLiteLlmModelId", liteLlmModelId)
}
if (liteLlmModelInfo !== undefined) {
await context.globalState.update("planModeLiteLlmModelInfo", liteLlmModelInfo)
}
if (requestyModelId !== undefined) {
await context.globalState.update("planModeRequestyModelId", requestyModelId)
}
if (requestyModelInfo !== undefined) {
await context.globalState.update("planModeRequestyModelInfo", requestyModelInfo)
}
if (togetherModelId !== undefined) {
await context.globalState.update("planModeTogetherModelId", togetherModelId)
}
if (fireworksModelId !== undefined) {
await context.globalState.update("planModeFireworksModelId", fireworksModelId)
}
if (sapAiCoreModelId !== undefined) {
await context.globalState.update("planModeSapAiCoreModelId", sapAiCoreModelId)
}
if (groqModelId !== undefined) {
await context.globalState.update("planModeGroqModelId", groqModelId)
}
if (groqModelInfo !== undefined) {
await context.globalState.update("planModeGroqModelInfo", groqModelInfo)
}
if (huggingFaceModelId !== undefined) {
await context.globalState.update("planModeHuggingFaceModelId", huggingFaceModelId)
}
if (huggingFaceModelInfo !== undefined) {
await context.globalState.update("planModeHuggingFaceModelInfo", huggingFaceModelInfo)
}
// Use previous values for act mode (with fallback to current values)
if (previousModeApiProvider !== undefined) {
await context.globalState.update("actModeApiProvider", previousModeApiProvider)
} else if (apiProvider !== undefined) {
await context.globalState.update("actModeApiProvider", apiProvider)
}
if (previousModeModelId !== undefined) {
await context.globalState.update("actModeApiModelId", previousModeModelId)
} else if (apiModelId !== undefined) {
await context.globalState.update("actModeApiModelId", apiModelId)
}
if (previousModeThinkingBudgetTokens !== undefined) {
await context.globalState.update("actModeThinkingBudgetTokens", previousModeThinkingBudgetTokens)
} else if (thinkingBudgetTokens !== undefined) {
await context.globalState.update("actModeThinkingBudgetTokens", thinkingBudgetTokens)
}
if (previousModeReasoningEffort !== undefined) {
await context.globalState.update("actModeReasoningEffort", previousModeReasoningEffort)
} else if (reasoningEffort !== undefined) {
await context.globalState.update("actModeReasoningEffort", reasoningEffort)
}
if (previousModeVsCodeLmModelSelector !== undefined) {
await context.globalState.update("actModeVsCodeLmModelSelector", previousModeVsCodeLmModelSelector)
} else if (vsCodeLmModelSelector !== undefined) {
await context.globalState.update("actModeVsCodeLmModelSelector", vsCodeLmModelSelector)
}
if (previousModeAwsBedrockCustomSelected !== undefined) {
await context.globalState.update("actModeAwsBedrockCustomSelected", previousModeAwsBedrockCustomSelected)
} else if (awsBedrockCustomSelected !== undefined) {
await context.globalState.update("actModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
}
if (previousModeAwsBedrockCustomModelBaseId !== undefined) {
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", previousModeAwsBedrockCustomModelBaseId)
} else if (awsBedrockCustomModelBaseId !== undefined) {
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
}
if (previousModeSapAiCoreModelId !== undefined) {
await context.globalState.update("actModeSapAiCoreModelId", previousModeSapAiCoreModelId)
} else if (sapAiCoreModelId !== undefined) {
await context.globalState.update("actModeSapAiCoreModelId", sapAiCoreModelId)
}
// For fields without previous variants, use current values for act mode
if (previousModeModelInfo !== undefined) {
await context.globalState.update("actModeOpenRouterModelInfo", previousModeModelInfo)
} else if (openRouterModelInfo !== undefined) {
await context.globalState.update("actModeOpenRouterModelInfo", openRouterModelInfo)
}
if (openRouterModelId !== undefined) {
await context.globalState.update("actModeOpenRouterModelId", openRouterModelId)
}
if (openAiModelId !== undefined) {
await context.globalState.update("actModeOpenAiModelId", openAiModelId)
}
if (openAiModelInfo !== undefined) {
await context.globalState.update("actModeOpenAiModelInfo", openAiModelInfo)
}
if (ollamaModelId !== undefined) {
await context.globalState.update("actModeOllamaModelId", ollamaModelId)
}
if (lmStudioModelId !== undefined) {
await context.globalState.update("actModeLmStudioModelId", lmStudioModelId)
}
if (liteLlmModelId !== undefined) {
await context.globalState.update("actModeLiteLlmModelId", liteLlmModelId)
}
if (liteLlmModelInfo !== undefined) {
await context.globalState.update("actModeLiteLlmModelInfo", liteLlmModelInfo)
}
if (requestyModelId !== undefined) {
await context.globalState.update("actModeRequestyModelId", requestyModelId)
}
if (requestyModelInfo !== undefined) {
await context.globalState.update("actModeRequestyModelInfo", requestyModelInfo)
}
if (togetherModelId !== undefined) {
await context.globalState.update("actModeTogetherModelId", togetherModelId)
}
if (fireworksModelId !== undefined) {
await context.globalState.update("actModeFireworksModelId", fireworksModelId)
}
if (groqModelId !== undefined) {
await context.globalState.update("actModeGroqModelId", groqModelId)
}
if (groqModelInfo !== undefined) {
await context.globalState.update("actModeGroqModelInfo", groqModelInfo)
}
if (huggingFaceModelId !== undefined) {
await context.globalState.update("actModeHuggingFaceModelId", huggingFaceModelId)
}
if (huggingFaceModelInfo !== undefined) {
await context.globalState.update("actModeHuggingFaceModelInfo", huggingFaceModelInfo)
}
}
// Clean up legacy keys after successful migration
console.log("Cleaning up legacy keys...")
await context.globalState.update("apiProvider", undefined)
await context.globalState.update("apiModelId", undefined)
await context.globalState.update("thinkingBudgetTokens", undefined)
await context.globalState.update("reasoningEffort", undefined)
await context.globalState.update("vsCodeLmModelSelector", undefined)
await context.globalState.update("awsBedrockCustomSelected", undefined)
await context.globalState.update("awsBedrockCustomModelBaseId", undefined)
await context.globalState.update("openRouterModelId", undefined)
await context.globalState.update("openRouterModelInfo", undefined)
await context.globalState.update("openAiModelId", undefined)
await context.globalState.update("openAiModelInfo", undefined)
await context.globalState.update("ollamaModelId", undefined)
await context.globalState.update("lmStudioModelId", undefined)
await context.globalState.update("liteLlmModelId", undefined)
await context.globalState.update("liteLlmModelInfo", undefined)
await context.globalState.update("requestyModelId", undefined)
await context.globalState.update("requestyModelInfo", undefined)
await context.globalState.update("togetherModelId", undefined)
await context.globalState.update("fireworksModelId", undefined)
await context.globalState.update("sapAiCoreModelId", undefined)
await context.globalState.update("groqModelId", undefined)
await context.globalState.update("groqModelInfo", undefined)
await context.globalState.update("huggingFaceModelId", undefined)
await context.globalState.update("huggingFaceModelInfo", undefined)
await context.globalState.update("previousModeApiProvider", undefined)
await context.globalState.update("previousModeModelId", undefined)
await context.globalState.update("previousModeModelInfo", undefined)
await context.globalState.update("previousModeVsCodeLmModelSelector", undefined)
await context.globalState.update("previousModeThinkingBudgetTokens", undefined)
await context.globalState.update("previousModeReasoningEffort", undefined)
await context.globalState.update("previousModeAwsBedrockCustomSelected", undefined)
await context.globalState.update("previousModeAwsBedrockCustomModelBaseId", undefined)
await context.globalState.update("previousModeSapAiCoreModelId", undefined)
console.log("Successfully migrated legacy API configuration to mode-specific keys")
} catch (error) {
console.error("Failed to migrate legacy API configuration to mode-specific keys:", error)
// Continue execution - migration failure shouldn't break extension startup
}
}
export async function migrateWelcomeViewCompleted(context: vscode.ExtensionContext) {
try {
// Check if welcomeViewCompleted is already set
@@ -190,8 +563,10 @@ export async function migrateWelcomeViewCompleted(context: vscode.ExtensionConte
config.awsRegion,
config.vertexProjectId,
config.openAiApiKey,
config.ollamaModelId,
config.lmStudioModelId,
config.planModeOllamaModelId,
config.planModeLmStudioModelId,
config.actModeOllamaModelId,
config.actModeLmStudioModelId,
config.liteLlmApiKey,
config.geminiApiKey,
config.openAiNativeApiKey,
@@ -201,7 +576,8 @@ export async function migrateWelcomeViewCompleted(context: vscode.ExtensionConte
config.qwenApiKey,
config.doubaoApiKey,
config.mistralApiKey,
config.vsCodeLmModelSelector,
config.planModeVsCodeLmModelSelector,
config.actModeVsCodeLmModelSelector,
config.clineAccountId,
config.asksageApiKey,
config.xaiApiKey,
+254 -154
View File
@@ -1,5 +1,5 @@
import * as vscode from "vscode"
import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
import { DEFAULT_CHAT_SETTINGS, Mode } from "@shared/ChatSettings"
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
import { GlobalStateKey, LocalStateKey, SecretKey } from "./state-keys"
@@ -190,8 +190,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
sapAiCoreTokenUrl,
sapAiResourceGroup,
claudeCodePath,
groqModelId,
groqModelInfo,
] = await Promise.all([
getGlobalState(context, "isNewUser") as Promise<boolean | undefined>,
getGlobalState(context, "welcomeViewCompleted") as Promise<boolean | undefined>,
@@ -271,8 +269,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getGlobalState(context, "sapAiCoreTokenUrl") as Promise<string | undefined>,
getGlobalState(context, "sapAiResourceGroup") as Promise<string | undefined>,
getGlobalState(context, "claudeCodePath") as Promise<string | undefined>,
getGlobalState(context, "groqModelId") as Promise<string | undefined>,
getGlobalState(context, "groqModelInfo") as Promise<ModelInfo | undefined>,
])
const localClineRulesToggles = (await getWorkspaceState(context, "localClineRulesToggles")) as ClineRulesToggles
@@ -281,78 +277,115 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
const [
chatSettings,
currentMode,
storedApiProvider,
apiModelId,
thinkingBudgetTokens,
reasoningEffort,
vsCodeLmModelSelector,
awsBedrockCustomSelected,
awsBedrockCustomModelBaseId,
openRouterModelId,
openRouterModelInfo,
openAiModelId,
openAiModelInfo,
ollamaModelId,
lmStudioModelId,
liteLlmModelId,
liteLlmModelInfo,
requestyModelId,
requestyModelInfo,
togetherModelId,
fireworksModelId,
previousModeApiProvider,
previousModeModelId,
previousModeModelInfo,
previousModeVsCodeLmModelSelector,
previousModeThinkingBudgetTokens,
previousModeReasoningEffort,
previousModeAwsBedrockCustomSelected,
previousModeAwsBedrockCustomModelBaseId,
previousModeSapAiCoreModelId,
sapAiCoreModelId,
huggingFaceModelId,
huggingFaceModelInfo,
// Plan mode configurations
planModeApiProvider,
planModeApiModelId,
planModeThinkingBudgetTokens,
planModeReasoningEffort,
planModeVsCodeLmModelSelector,
planModeAwsBedrockCustomSelected,
planModeAwsBedrockCustomModelBaseId,
planModeOpenRouterModelId,
planModeOpenRouterModelInfo,
planModeOpenAiModelId,
planModeOpenAiModelInfo,
planModeOllamaModelId,
planModeLmStudioModelId,
planModeLiteLlmModelId,
planModeLiteLlmModelInfo,
planModeRequestyModelId,
planModeRequestyModelInfo,
planModeTogetherModelId,
planModeFireworksModelId,
planModeSapAiCoreModelId,
planModeGroqModelId,
planModeGroqModelInfo,
planModeHuggingFaceModelId,
planModeHuggingFaceModelInfo,
// Act mode configurations
actModeApiProvider,
actModeApiModelId,
actModeThinkingBudgetTokens,
actModeReasoningEffort,
actModeVsCodeLmModelSelector,
actModeAwsBedrockCustomSelected,
actModeAwsBedrockCustomModelBaseId,
actModeOpenRouterModelId,
actModeOpenRouterModelInfo,
actModeOpenAiModelId,
actModeOpenAiModelInfo,
actModeOllamaModelId,
actModeLmStudioModelId,
actModeLiteLlmModelId,
actModeLiteLlmModelInfo,
actModeRequestyModelId,
actModeRequestyModelInfo,
actModeTogetherModelId,
actModeFireworksModelId,
actModeSapAiCoreModelId,
actModeGroqModelId,
actModeGroqModelInfo,
actModeHuggingFaceModelId,
actModeHuggingFaceModelInfo,
] = await Promise.all([
getGlobalState(context, "chatSettings") as Promise<StoredChatSettings | undefined>,
getGlobalState(context, "mode") as Promise<"plan" | "act" | undefined>,
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
getGlobalState(context, "thinkingBudgetTokens") as Promise<number | undefined>,
getGlobalState(context, "reasoningEffort") as Promise<string | undefined>,
getGlobalState(context, "vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
getGlobalState(context, "awsBedrockCustomSelected") as Promise<boolean | undefined>,
getGlobalState(context, "awsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
getGlobalState(context, "openRouterModelId") as Promise<string | undefined>,
getGlobalState(context, "openRouterModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "openAiModelId") as Promise<string | undefined>,
getGlobalState(context, "openAiModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "ollamaModelId") as Promise<string | undefined>,
getGlobalState(context, "lmStudioModelId") as Promise<string | undefined>,
getGlobalState(context, "liteLlmModelId") as Promise<string | undefined>,
getGlobalState(context, "liteLlmModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "requestyModelId") as Promise<string | undefined>,
getGlobalState(context, "requestyModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "togetherModelId") as Promise<string | undefined>,
getGlobalState(context, "fireworksModelId") as Promise<string | undefined>,
getGlobalState(context, "previousModeApiProvider") as Promise<ApiProvider | undefined>,
getGlobalState(context, "previousModeModelId") as Promise<string | undefined>,
getGlobalState(context, "previousModeModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "previousModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
getGlobalState(context, "previousModeThinkingBudgetTokens") as Promise<number | undefined>,
getGlobalState(context, "previousModeReasoningEffort") as Promise<string | undefined>,
getGlobalState(context, "previousModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
getGlobalState(context, "previousModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
getGlobalState(context, "previousModeSapAiCoreModelId") as Promise<string | undefined>,
getGlobalState(context, "sapAiCoreModelId") as Promise<string | undefined>,
getGlobalState(context, "huggingFaceModelId") as Promise<string | undefined>,
getGlobalState(context, "huggingFaceModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "mode") as Promise<Mode | undefined>,
// Plan mode configurations
getGlobalState(context, "planModeApiProvider") as Promise<ApiProvider | undefined>,
getGlobalState(context, "planModeApiModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeThinkingBudgetTokens") as Promise<number | undefined>,
getGlobalState(context, "planModeReasoningEffort") as Promise<string | undefined>,
getGlobalState(context, "planModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
getGlobalState(context, "planModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
getGlobalState(context, "planModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
getGlobalState(context, "planModeOpenRouterModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeOpenRouterModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "planModeOpenAiModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeOpenAiModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "planModeOllamaModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeLmStudioModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeLiteLlmModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeLiteLlmModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "planModeRequestyModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeRequestyModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "planModeTogetherModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeFireworksModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeSapAiCoreModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeGroqModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeGroqModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "planModeHuggingFaceModelId") as Promise<string | undefined>,
getGlobalState(context, "planModeHuggingFaceModelInfo") as Promise<ModelInfo | undefined>,
// Act mode configurations
getGlobalState(context, "actModeApiProvider") as Promise<ApiProvider | undefined>,
getGlobalState(context, "actModeApiModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeThinkingBudgetTokens") as Promise<number | undefined>,
getGlobalState(context, "actModeReasoningEffort") as Promise<string | undefined>,
getGlobalState(context, "actModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
getGlobalState(context, "actModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
getGlobalState(context, "actModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
getGlobalState(context, "actModeOpenRouterModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeOpenRouterModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "actModeOpenAiModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeOpenAiModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "actModeOllamaModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeLmStudioModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeLiteLlmModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeLiteLlmModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "actModeRequestyModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeRequestyModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "actModeTogetherModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeFireworksModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeSapAiCoreModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeGroqModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeGroqModelInfo") as Promise<ModelInfo | undefined>,
getGlobalState(context, "actModeHuggingFaceModelId") as Promise<string | undefined>,
getGlobalState(context, "actModeHuggingFaceModelInfo") as Promise<ModelInfo | undefined>,
])
const processingStart = performance.now()
let apiProvider: ApiProvider
if (storedApiProvider) {
// Use the explicitly stored provider - this respects user's selection
apiProvider = storedApiProvider
if (planModeApiProvider) {
apiProvider = planModeApiProvider
} else {
// Either new user or legacy user that doesn't have the apiProvider stored in state
// (If they're using OpenRouter or Bedrock, then apiProvider state will exist)
@@ -375,7 +408,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
planActSeparateModelsSetting = planActSeparateModelsSettingRaw
} else {
// default to true for existing users
if (storedApiProvider) {
if (planModeApiProvider) {
planActSeparateModelsSetting = true
} else {
// default to false for new users
@@ -388,8 +421,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
return {
apiConfiguration: {
apiProvider,
apiModelId,
apiKey,
openRouterApiKey,
clineAccountId,
@@ -405,19 +436,13 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
awsBedrockApiKey,
awsUseProfile,
awsAuthentication,
awsBedrockCustomSelected,
awsBedrockCustomModelBaseId,
vertexProjectId,
vertexRegion,
openAiBaseUrl,
openAiApiKey,
openAiModelId,
openAiModelInfo,
openAiHeaders: openAiHeaders || {},
ollamaModelId,
ollamaBaseUrl,
ollamaApiOptionsCtxNum,
lmStudioModelId,
lmStudioBaseUrl,
anthropicBaseUrl,
geminiApiKey,
@@ -425,29 +450,18 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
openAiNativeApiKey,
deepSeekApiKey,
requestyApiKey,
requestyModelId,
requestyModelInfo,
togetherApiKey,
togetherModelId,
qwenApiKey,
qwenApiLine,
moonshotApiLine,
doubaoApiKey,
mistralApiKey,
azureApiVersion,
openRouterModelId,
openRouterModelInfo,
openRouterProviderSorting,
vsCodeLmModelSelector,
thinkingBudgetTokens,
reasoningEffort,
liteLlmBaseUrl,
liteLlmModelId,
liteLlmModelInfo,
liteLlmApiKey,
liteLlmUsePromptCache,
fireworksApiKey,
fireworksModelId,
fireworksModelMaxCompletionTokens,
fireworksModelMaxTokens,
asksageApiKey,
@@ -456,8 +470,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
sambanovaApiKey,
cerebrasApiKey,
groqApiKey,
groqModelId,
groqModelInfo,
moonshotApiKey,
nebiusApiKey,
favoritedModelIds,
@@ -467,10 +479,55 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
sapAiCoreBaseUrl,
sapAiCoreTokenUrl,
sapAiResourceGroup,
sapAiCoreModelId,
huggingFaceApiKey,
huggingFaceModelId,
huggingFaceModelInfo,
// Plan mode configurations
planModeApiProvider: planModeApiProvider || apiProvider,
planModeApiModelId,
planModeThinkingBudgetTokens,
planModeReasoningEffort,
planModeVsCodeLmModelSelector,
planModeAwsBedrockCustomSelected,
planModeAwsBedrockCustomModelBaseId,
planModeOpenRouterModelId,
planModeOpenRouterModelInfo,
planModeOpenAiModelId,
planModeOpenAiModelInfo,
planModeOllamaModelId,
planModeLmStudioModelId,
planModeLiteLlmModelId,
planModeLiteLlmModelInfo,
planModeRequestyModelId,
planModeRequestyModelInfo,
planModeTogetherModelId,
planModeFireworksModelId,
planModeSapAiCoreModelId,
planModeGroqModelId,
planModeGroqModelInfo,
planModeHuggingFaceModelId,
planModeHuggingFaceModelInfo,
// Act mode configurations
actModeApiProvider: actModeApiProvider || apiProvider,
actModeApiModelId,
actModeThinkingBudgetTokens,
actModeReasoningEffort,
actModeVsCodeLmModelSelector,
actModeAwsBedrockCustomSelected,
actModeAwsBedrockCustomModelBaseId,
actModeOpenRouterModelId,
actModeOpenRouterModelInfo,
actModeOpenAiModelId,
actModeOpenAiModelInfo,
actModeOllamaModelId,
actModeLmStudioModelId,
actModeLiteLlmModelId,
actModeLiteLlmModelInfo,
actModeRequestyModelId,
actModeRequestyModelInfo,
actModeTogetherModelId,
actModeFireworksModelId,
actModeSapAiCoreModelId,
actModeGroqModelId,
actModeGroqModelInfo,
},
isNewUser: isNewUser ?? true,
welcomeViewCompleted,
@@ -486,15 +543,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
mode: currentMode || "act", // Merge mode from global state
},
userInfo,
previousModeApiProvider,
previousModeModelId,
previousModeModelInfo,
previousModeVsCodeLmModelSelector,
previousModeThinkingBudgetTokens,
previousModeReasoningEffort,
previousModeAwsBedrockCustomSelected,
previousModeAwsBedrockCustomModelBaseId,
previousModeSapAiCoreModelId,
mcpMarketplaceEnabled: mcpMarketplaceEnabled,
mcpDisplayMode: mcpDisplayMode ?? DEFAULT_MCP_DISPLAY_MODE,
mcpResponsesCollapsed: mcpResponsesCollapsed,
@@ -511,8 +559,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
export async function updateApiConfiguration(context: vscode.ExtensionContext, apiConfiguration: ApiConfiguration) {
const {
apiProvider,
apiModelId,
apiKey,
openRouterApiKey,
awsAccessKey,
@@ -526,19 +572,13 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
awsProfile,
awsUseProfile,
awsAuthentication,
awsBedrockCustomSelected,
awsBedrockCustomModelBaseId,
vertexProjectId,
vertexRegion,
openAiBaseUrl,
openAiApiKey,
openAiModelId,
openAiModelInfo,
openAiHeaders,
ollamaModelId,
ollamaBaseUrl,
ollamaApiOptionsCtxNum,
lmStudioModelId,
lmStudioBaseUrl,
anthropicBaseUrl,
geminiApiKey,
@@ -546,21 +586,13 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
openAiNativeApiKey,
deepSeekApiKey,
requestyApiKey,
requestyModelId,
requestyModelInfo,
togetherApiKey,
togetherModelId,
qwenApiKey,
doubaoApiKey,
mistralApiKey,
azureApiVersion,
openRouterModelId,
openRouterModelInfo,
openRouterProviderSorting,
vsCodeLmModelSelector,
liteLlmBaseUrl,
liteLlmModelId,
liteLlmModelInfo,
liteLlmApiKey,
liteLlmUsePromptCache,
qwenApiLine,
@@ -568,19 +600,14 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
asksageApiKey,
asksageApiUrl,
xaiApiKey,
thinkingBudgetTokens,
reasoningEffort,
clineAccountId,
sambanovaApiKey,
cerebrasApiKey,
groqApiKey,
groqModelId,
groqModelInfo,
moonshotApiKey,
nebiusApiKey,
favoritedModelIds,
fireworksApiKey,
fireworksModelId,
fireworksModelMaxCompletionTokens,
fireworksModelMaxTokens,
sapAiCoreClientId,
@@ -588,40 +615,113 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
sapAiCoreBaseUrl,
sapAiCoreTokenUrl,
sapAiResourceGroup,
sapAiCoreModelId,
claudeCodePath,
huggingFaceApiKey,
huggingFaceModelId,
huggingFaceModelInfo,
// Plan mode configurations
planModeApiProvider,
planModeApiModelId,
planModeThinkingBudgetTokens,
planModeReasoningEffort,
planModeVsCodeLmModelSelector,
planModeAwsBedrockCustomSelected,
planModeAwsBedrockCustomModelBaseId,
planModeOpenRouterModelId,
planModeOpenRouterModelInfo,
planModeOpenAiModelId,
planModeOpenAiModelInfo,
planModeOllamaModelId,
planModeLmStudioModelId,
planModeLiteLlmModelId,
planModeLiteLlmModelInfo,
planModeRequestyModelId,
planModeRequestyModelInfo,
planModeTogetherModelId,
planModeFireworksModelId,
planModeSapAiCoreModelId,
planModeGroqModelId,
planModeGroqModelInfo,
planModeHuggingFaceModelId,
planModeHuggingFaceModelInfo,
// Act mode configurations
actModeApiProvider,
actModeApiModelId,
actModeThinkingBudgetTokens,
actModeReasoningEffort,
actModeVsCodeLmModelSelector,
actModeAwsBedrockCustomSelected,
actModeAwsBedrockCustomModelBaseId,
actModeOpenRouterModelId,
actModeOpenRouterModelInfo,
actModeOpenAiModelId,
actModeOpenAiModelInfo,
actModeOllamaModelId,
actModeLmStudioModelId,
actModeLiteLlmModelId,
actModeLiteLlmModelInfo,
actModeRequestyModelId,
actModeRequestyModelInfo,
actModeTogetherModelId,
actModeFireworksModelId,
actModeSapAiCoreModelId,
actModeGroqModelId,
actModeGroqModelInfo,
actModeHuggingFaceModelId,
actModeHuggingFaceModelInfo,
} = apiConfiguration
// OPTIMIZED: Batch all global state updates into 2 operations instead of 47
const batchedGlobalUpdates = {
// Ephemeral model config updates (20 keys)
apiProvider,
apiModelId,
thinkingBudgetTokens,
reasoningEffort,
vsCodeLmModelSelector,
awsBedrockCustomSelected,
awsBedrockCustomModelBaseId,
openRouterModelId,
openRouterModelInfo,
openAiModelId,
openAiModelInfo,
ollamaModelId,
lmStudioModelId,
liteLlmModelId,
liteLlmModelInfo,
requestyModelId,
requestyModelInfo,
togetherModelId,
fireworksModelId,
groqModelId,
groqModelInfo,
sapAiCoreModelId,
huggingFaceModelId,
huggingFaceModelInfo,
// Plan mode configuration updates
planModeApiProvider,
planModeApiModelId,
planModeThinkingBudgetTokens,
planModeReasoningEffort,
planModeVsCodeLmModelSelector,
planModeAwsBedrockCustomSelected,
planModeAwsBedrockCustomModelBaseId,
planModeOpenRouterModelId,
planModeOpenRouterModelInfo,
planModeOpenAiModelId,
planModeOpenAiModelInfo,
planModeOllamaModelId,
planModeLmStudioModelId,
planModeLiteLlmModelId,
planModeLiteLlmModelInfo,
planModeRequestyModelId,
planModeRequestyModelInfo,
planModeTogetherModelId,
planModeFireworksModelId,
planModeSapAiCoreModelId,
planModeGroqModelId,
planModeGroqModelInfo,
planModeHuggingFaceModelId,
planModeHuggingFaceModelInfo,
// Act mode configuration updates
actModeApiProvider,
actModeApiModelId,
actModeThinkingBudgetTokens,
actModeReasoningEffort,
actModeVsCodeLmModelSelector,
actModeAwsBedrockCustomSelected,
actModeAwsBedrockCustomModelBaseId,
actModeOpenRouterModelId,
actModeOpenRouterModelInfo,
actModeOpenAiModelId,
actModeOpenAiModelInfo,
actModeOllamaModelId,
actModeLmStudioModelId,
actModeLiteLlmModelId,
actModeLiteLlmModelInfo,
actModeRequestyModelId,
actModeRequestyModelInfo,
actModeTogetherModelId,
actModeFireworksModelId,
actModeSapAiCoreModelId,
actModeGroqModelId,
actModeGroqModelInfo,
actModeHuggingFaceModelId,
actModeHuggingFaceModelInfo,
// Global state updates (27 keys)
awsRegion,
+8 -1
View File
@@ -54,6 +54,7 @@ import { TaskState } from "./TaskState"
import { MessageStateHandler } from "./message-state"
import { AutoApprove } from "./tools/autoApprove"
import { showNotificationForApprovalIfAutoApprovalEnabled } from "./utils"
import { ChatSettings } from "@/shared/ChatSettings"
export class ToolExecutor {
private autoApprover: AutoApprove
@@ -90,6 +91,7 @@ export class ToolExecutor {
private browserSettings: BrowserSettings,
private cwd: string,
private taskId: string,
private chatSettings: ChatSettings,
// Callbacks to the Task (Entity)
private say: (
@@ -1917,7 +1919,12 @@ export class ToolExecutor {
const clineVersion =
vscode.extensions.getExtension("saoudrizwan.claude-dev")?.packageJSON.version || "Unknown"
const systemInfo = `VSCode: ${vscode.version}, Node.js: ${process.version}, Architecture: ${os.arch()}`
const providerAndModel = `${await getGlobalState(this.context, "apiProvider")} / ${this.api.getModel().id}`
const currentMode = this.chatSettings.mode
const apiProvider =
currentMode === "plan"
? await getGlobalState(this.context, "planModeApiProvider")
: await getGlobalState(this.context, "actModeApiProvider")
const providerAndModel = `${apiProvider} / ${this.api.getModel().id}`
// Ask user for confirmation
const bugReportData = JSON.stringify({
+17 -6
View File
@@ -261,12 +261,19 @@ export class Task {
},
}
if (apiConfiguration.apiProvider === "openai" || apiConfiguration.apiProvider === "openai-native") {
effectiveApiConfiguration.reasoningEffort = chatSettings.openAIReasoningEffort
const currentProvider =
chatSettings.mode === "plan" ? apiConfiguration.planModeApiProvider : apiConfiguration.actModeApiProvider
if (currentProvider === "openai" || currentProvider === "openai-native") {
if (chatSettings.mode === "plan") {
effectiveApiConfiguration.planModeReasoningEffort = chatSettings.openAIReasoningEffort
} else {
effectiveApiConfiguration.actModeReasoningEffort = chatSettings.openAIReasoningEffort
}
}
// Now that taskId is initialized, we can build the API handler
this.api = buildApiHandler(effectiveApiConfiguration)
this.api = buildApiHandler(effectiveApiConfiguration, chatSettings.mode)
// Set taskId on browserSession for telemetry tracking
this.browserSession.setTaskId(this.taskId)
@@ -281,10 +288,10 @@ export class Task {
// initialize telemetry
if (historyItem) {
// Open task from history
telemetryService.captureTaskRestarted(this.taskId, apiConfiguration.apiProvider)
telemetryService.captureTaskRestarted(this.taskId, currentProvider)
} else {
// New task started
telemetryService.captureTaskCreated(this.taskId, apiConfiguration.apiProvider)
telemetryService.captureTaskCreated(this.taskId, currentProvider)
}
this.toolExecutor = new ToolExecutor(
@@ -304,6 +311,7 @@ export class Task {
this.browserSettings,
cwd,
this.taskId,
this.chatSettings,
this.say.bind(this),
this.ask.bind(this),
this.saveCheckpoint.bind(this),
@@ -1591,7 +1599,10 @@ export class Task {
private async getCurrentProviderInfo(): Promise<{ modelId: string; providerId: string }> {
const modelId = this.api.getModel()?.id
const providerId = (await getGlobalState(this.getContext(), "apiProvider")) as string
const providerId =
this.chatSettings.mode === "plan"
? ((await getGlobalState(this.getContext(), "planModeApiProvider")) as string)
: ((await getGlobalState(this.getContext(), "actModeApiProvider")) as string)
return { modelId, providerId }
}
+4
View File
@@ -27,6 +27,7 @@ import {
migrateCustomInstructionsToGlobalRules,
migrateModeFromWorkspaceStorageToControllerState,
migrateWelcomeViewCompleted,
migrateLegacyApiConfigurationToModeSpecific,
} from "./core/storage/state-migrations"
import { sendFocusChatInputEvent } from "./core/controller/ui/subscribeToFocusChatInput"
@@ -75,6 +76,9 @@ export async function activate(context: vscode.ExtensionContext) {
// Migrate workspace storage values back to global storage (reverting previous migration)
await migrateWorkspaceToGlobalStorage(context)
// Migrate legacy API configuration to mode-specific keys (one-time migration)
await migrateLegacyApiConfigurationToModeSpecific(context)
// Clean up orphaned file context warnings (startup cleanup)
await FileContextTracker.cleanupOrphanedWarnings(context)
@@ -5,6 +5,7 @@ import { version as extensionVersion } from "../../../../package.json"
import type { TaskFeedbackType } from "@shared/WebviewMessage"
import type { BrowserSettings } from "@shared/BrowserSettings"
import { posthogClientProvider } from "../PostHogClientProvider"
import { Mode } from "@/shared/ChatSettings"
/**
* TelemetryService handles telemetry event tracking for the Cline extension
@@ -312,7 +313,7 @@ class TelemetryService {
* @param taskId Unique identifier for the task
* @param mode The mode being switched to (plan or act)
*/
public captureModeSwitch(taskId: string, mode: "plan" | "act") {
public captureModeSwitch(taskId: string, mode: Mode) {
this.capture({
event: TelemetryService.EVENTS.TASK.MODE_SWITCH,
properties: {
@@ -534,7 +535,7 @@ class TelemetryService {
* @param qty The quantity of options that were presented
* @param mode The mode in which the option was selected ("plan" or "act")
*/
public captureOptionSelected(taskId: string, qty: number, mode: "plan" | "act") {
public captureOptionSelected(taskId: string, qty: number, mode: Mode) {
this.capture({
event: TelemetryService.EVENTS.TASK.OPTION_SELECTED,
properties: {
@@ -551,7 +552,7 @@ class TelemetryService {
* @param qty The quantity of options that were presented
* @param mode The mode in which the custom response was provided ("plan" or "act")
*/
public captureOptionsIgnored(taskId: string, qty: number, mode: "plan" | "act") {
public captureOptionsIgnored(taskId: string, qty: number, mode: Mode) {
this.capture({
event: TelemetryService.EVENTS.TASK.OPTIONS_IGNORED,
properties: {
+2 -1
View File
@@ -274,7 +274,8 @@ export function createTestServer(webviewProvider?: WebviewProvider): http.Server
await updateApiConfiguration(visibleWebview.controller.context, updatedConfig)
// Update global state to use cline provider
await updateGlobalState(visibleWebview.controller.context, "apiProvider", "cline" as ApiProvider)
await updateGlobalState(visibleWebview.controller.context, "planModeApiProvider", "cline")
await updateGlobalState(visibleWebview.controller.context, "actModeApiProvider", "cline")
// Post state to webview to reflect changes
await visibleWebview.controller.postStateToWebview()
+3 -1
View File
@@ -1,7 +1,9 @@
export type OpenAIReasoningEffort = "low" | "medium" | "high"
export type Mode = "plan" | "act"
export interface ChatSettings {
mode: "plan" | "act"
mode: Mode
preferredLanguage?: string
openAIReasoningEffort?: OpenAIReasoningEffort
}
+52 -24
View File
@@ -32,20 +32,16 @@ export type ApiProvider =
| "huggingface"
export interface ApiHandlerOptions {
apiModelId?: string
// Global configuration (not mode-specific)
apiKey?: string // anthropic
clineAccountId?: string
taskId?: string // Used to identify the task in API requests
liteLlmBaseUrl?: string
liteLlmModelId?: string
liteLlmApiKey?: string
liteLlmUsePromptCache?: boolean
openAiHeaders?: Record<string, string> // Custom headers for OpenAI requests
liteLlmModelInfo?: LiteLLMModelInfo
anthropicBaseUrl?: string
openRouterApiKey?: string
openRouterModelId?: string
openRouterModelInfo?: ModelInfo
openRouterProviderSorting?: string
awsAccessKey?: string
awsSecretKey?: string
@@ -58,67 +54,99 @@ export interface ApiHandlerOptions {
awsProfile?: string
awsBedrockApiKey?: string
awsBedrockEndpoint?: string
awsBedrockCustomSelected?: boolean
awsBedrockCustomModelBaseId?: BedrockModelId
claudeCodePath?: string
vertexProjectId?: string
vertexRegion?: string
openAiBaseUrl?: string
openAiApiKey?: string
openAiModelId?: string
openAiModelInfo?: OpenAiCompatibleModelInfo
ollamaModelId?: string
ollamaBaseUrl?: string
ollamaApiOptionsCtxNum?: string
lmStudioModelId?: string
lmStudioBaseUrl?: string
geminiApiKey?: string
geminiBaseUrl?: string
openAiNativeApiKey?: string
deepSeekApiKey?: string
requestyApiKey?: string
requestyModelId?: string
requestyModelInfo?: ModelInfo
togetherApiKey?: string
togetherModelId?: string
fireworksApiKey?: string
fireworksModelId?: string
fireworksModelMaxCompletionTokens?: number
fireworksModelMaxTokens?: number
qwenApiKey?: string
doubaoApiKey?: string
mistralApiKey?: string
azureApiVersion?: string
vsCodeLmModelSelector?: LanguageModelChatSelector
qwenApiLine?: string
moonshotApiLine?: string
moonshotApiKey?: string
huggingFaceApiKey?: string
huggingFaceModelId?: string
huggingFaceModelInfo?: ModelInfo
nebiusApiKey?: string
asksageApiUrl?: string
asksageApiKey?: string
xaiApiKey?: string
thinkingBudgetTokens?: number
reasoningEffort?: string
sambanovaApiKey?: string
cerebrasApiKey?: string
groqApiKey?: string
groqModelId?: string
groqModelInfo?: ModelInfo
requestTimeoutMs?: number
sapAiCoreClientId?: string
sapAiCoreClientSecret?: string
sapAiResourceGroup?: string
sapAiCoreTokenUrl?: string
sapAiCoreBaseUrl?: string
sapAiCoreModelId?: string
onRetryAttempt?: (attempt: number, maxRetries: number, delay: number, error: any) => void
// Plan mode configurations
planModeApiModelId?: string
planModeThinkingBudgetTokens?: number
planModeReasoningEffort?: string
planModeVsCodeLmModelSelector?: LanguageModelChatSelector
planModeAwsBedrockCustomSelected?: boolean
planModeAwsBedrockCustomModelBaseId?: BedrockModelId
planModeOpenRouterModelId?: string
planModeOpenRouterModelInfo?: ModelInfo
planModeOpenAiModelId?: string
planModeOpenAiModelInfo?: OpenAiCompatibleModelInfo
planModeOllamaModelId?: string
planModeLmStudioModelId?: string
planModeLiteLlmModelId?: string
planModeLiteLlmModelInfo?: LiteLLMModelInfo
planModeRequestyModelId?: string
planModeRequestyModelInfo?: ModelInfo
planModeTogetherModelId?: string
planModeFireworksModelId?: string
planModeSapAiCoreModelId?: string
planModeGroqModelId?: string
planModeGroqModelInfo?: ModelInfo
planModeHuggingFaceModelId?: string
planModeHuggingFaceModelInfo?: ModelInfo
// Act mode configurations
actModeApiModelId?: string
actModeThinkingBudgetTokens?: number
actModeReasoningEffort?: string
actModeVsCodeLmModelSelector?: LanguageModelChatSelector
actModeAwsBedrockCustomSelected?: boolean
actModeAwsBedrockCustomModelBaseId?: BedrockModelId
actModeOpenRouterModelId?: string
actModeOpenRouterModelInfo?: ModelInfo
actModeOpenAiModelId?: string
actModeOpenAiModelInfo?: OpenAiCompatibleModelInfo
actModeOllamaModelId?: string
actModeLmStudioModelId?: string
actModeLiteLlmModelId?: string
actModeLiteLlmModelInfo?: LiteLLMModelInfo
actModeRequestyModelId?: string
actModeRequestyModelInfo?: ModelInfo
actModeTogetherModelId?: string
actModeFireworksModelId?: string
actModeSapAiCoreModelId?: string
actModeGroqModelId?: string
actModeGroqModelInfo?: ModelInfo
actModeHuggingFaceModelId?: string
actModeHuggingFaceModelInfo?: ModelInfo
}
export type ApiConfiguration = ApiHandlerOptions & {
apiProvider?: ApiProvider
planModeApiProvider?: ApiProvider
actModeApiProvider?: ApiProvider
favoritedModelIds?: string[]
}
@@ -318,20 +318,16 @@ function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvider {
// Converts application ApiConfiguration to proto ApiConfiguration
export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoApiConfiguration {
return {
apiModelId: config.apiModelId,
// Global configuration fields
apiKey: config.apiKey,
clineAccountId: config.clineAccountId,
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,
@@ -344,83 +340,115 @@ export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoA
awsProfile: config.awsProfile,
awsBedrockApiKey: config.awsBedrockApiKey,
awsBedrockEndpoint: config.awsBedrockEndpoint,
awsBedrockCustomSelected: config.awsBedrockCustomSelected,
awsBedrockCustomModelBaseId: config.awsBedrockCustomModelBaseId as string | undefined,
claudeCodePath: config.claudeCodePath,
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,
moonshotApiLine: config.moonshotApiLine,
moonshotApiKey: config.moonshotApiKey,
huggingFaceApiKey: config.huggingFaceApiKey,
huggingFaceModelId: config.huggingFaceModelId,
huggingFaceModelInfo: convertModelInfoToProtoOpenRouter(config.huggingFaceModelInfo),
nebiusApiKey: config.nebiusApiKey,
asksageApiUrl: config.asksageApiUrl,
asksageApiKey: config.asksageApiKey,
xaiApiKey: config.xaiApiKey,
thinkingBudgetTokens: config.thinkingBudgetTokens,
reasoningEffort: config.reasoningEffort,
sambanovaApiKey: config.sambanovaApiKey,
cerebrasApiKey: config.cerebrasApiKey,
groqApiKey: config.groqApiKey,
groqModelId: config.groqModelId,
groqModelInfo: convertModelInfoToProtoOpenRouter(config.groqModelInfo),
requestTimeoutMs: config.requestTimeoutMs,
apiProvider: config.apiProvider ? convertApiProviderToProto(config.apiProvider) : undefined,
favoritedModelIds: config.favoritedModelIds || [],
sapAiCoreClientId: config.sapAiCoreClientId,
sapAiCoreClientSecret: config.sapAiCoreClientSecret,
sapAiResourceGroup: config.sapAiResourceGroup,
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
sapAiCoreBaseUrl: config.sapAiCoreBaseUrl,
claudeCodePath: config.claudeCodePath,
// Plan mode configurations
planModeApiProvider: config.planModeApiProvider ? convertApiProviderToProto(config.planModeApiProvider) : undefined,
planModeApiModelId: config.planModeApiModelId,
planModeThinkingBudgetTokens: config.planModeThinkingBudgetTokens,
planModeReasoningEffort: config.planModeReasoningEffort,
planModeVsCodeLmModelSelector: config.planModeVsCodeLmModelSelector,
planModeAwsBedrockCustomSelected: config.planModeAwsBedrockCustomSelected,
planModeAwsBedrockCustomModelBaseId: config.planModeAwsBedrockCustomModelBaseId as string | undefined,
planModeOpenRouterModelId: config.planModeOpenRouterModelId,
planModeOpenRouterModelInfo: convertModelInfoToProtoOpenRouter(config.planModeOpenRouterModelInfo),
planModeOpenAiModelId: config.planModeOpenAiModelId,
planModeOpenAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.planModeOpenAiModelInfo),
planModeOllamaModelId: config.planModeOllamaModelId,
planModeLmStudioModelId: config.planModeLmStudioModelId,
planModeLiteLlmModelId: config.planModeLiteLlmModelId,
planModeLiteLlmModelInfo: convertLiteLLMModelInfoToProto(config.planModeLiteLlmModelInfo),
planModeRequestyModelId: config.planModeRequestyModelId,
planModeRequestyModelInfo: convertModelInfoToProtoOpenRouter(config.planModeRequestyModelInfo),
planModeTogetherModelId: config.planModeTogetherModelId,
planModeFireworksModelId: config.planModeFireworksModelId,
planModeGroqModelId: config.planModeGroqModelId,
planModeGroqModelInfo: convertModelInfoToProtoOpenRouter(config.planModeGroqModelInfo),
planModeHuggingFaceModelId: config.planModeHuggingFaceModelId,
planModeHuggingFaceModelInfo: convertModelInfoToProtoOpenRouter(config.planModeHuggingFaceModelInfo),
planModeSapAiCoreModelId: config.planModeSapAiCoreModelId,
// Act mode configurations
actModeApiProvider: config.actModeApiProvider ? convertApiProviderToProto(config.actModeApiProvider) : undefined,
actModeApiModelId: config.actModeApiModelId,
actModeThinkingBudgetTokens: config.actModeThinkingBudgetTokens,
actModeReasoningEffort: config.actModeReasoningEffort,
actModeVsCodeLmModelSelector: config.actModeVsCodeLmModelSelector,
actModeAwsBedrockCustomSelected: config.actModeAwsBedrockCustomSelected,
actModeAwsBedrockCustomModelBaseId: config.actModeAwsBedrockCustomModelBaseId as string | undefined,
actModeOpenRouterModelId: config.actModeOpenRouterModelId,
actModeOpenRouterModelInfo: convertModelInfoToProtoOpenRouter(config.actModeOpenRouterModelInfo),
actModeOpenAiModelId: config.actModeOpenAiModelId,
actModeOpenAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.actModeOpenAiModelInfo),
actModeOllamaModelId: config.actModeOllamaModelId,
actModeLmStudioModelId: config.actModeLmStudioModelId,
actModeLiteLlmModelId: config.actModeLiteLlmModelId,
actModeLiteLlmModelInfo: convertLiteLLMModelInfoToProto(config.actModeLiteLlmModelInfo),
actModeRequestyModelId: config.actModeRequestyModelId,
actModeRequestyModelInfo: convertModelInfoToProtoOpenRouter(config.actModeRequestyModelInfo),
actModeTogetherModelId: config.actModeTogetherModelId,
actModeFireworksModelId: config.actModeFireworksModelId,
actModeGroqModelId: config.actModeGroqModelId,
actModeGroqModelInfo: convertModelInfoToProtoOpenRouter(config.actModeGroqModelInfo),
actModeHuggingFaceModelId: config.actModeHuggingFaceModelId,
actModeHuggingFaceModelInfo: convertModelInfoToProtoOpenRouter(config.actModeHuggingFaceModelInfo),
actModeSapAiCoreModelId: config.actModeSapAiCoreModelId,
// Favorited model IDs
favoritedModelIds: config.favoritedModelIds || [],
}
}
// Converts proto ApiConfiguration to application ApiConfiguration
export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguration): ApiConfiguration {
return {
apiModelId: protoConfig.apiModelId,
// Global configuration fields
apiKey: protoConfig.apiKey,
clineAccountId: protoConfig.clineAccountId,
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),
openAiHeaders: Object.keys(protoConfig.openAiHeaders || {}).length > 0 ? protoConfig.openAiHeaders : undefined,
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
openRouterApiKey: protoConfig.openRouterApiKey,
openRouterModelId: protoConfig.openRouterModelId,
openRouterModelInfo: convertProtoToModelInfo(protoConfig.openRouterModelInfo),
openRouterProviderSorting: protoConfig.openRouterProviderSorting,
awsAccessKey: protoConfig.awsAccessKey,
awsSecretKey: protoConfig.awsSecretKey,
@@ -433,62 +461,103 @@ export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguratio
awsProfile: protoConfig.awsProfile,
awsBedrockApiKey: protoConfig.awsBedrockApiKey,
awsBedrockEndpoint: protoConfig.awsBedrockEndpoint,
awsBedrockCustomSelected: protoConfig.awsBedrockCustomSelected,
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as BedrockModelId | undefined,
claudeCodePath: protoConfig.claudeCodePath,
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,
moonshotApiLine: protoConfig.moonshotApiLine,
moonshotApiKey: protoConfig.moonshotApiKey,
huggingFaceApiKey: protoConfig.huggingFaceApiKey,
huggingFaceModelId: protoConfig.huggingFaceModelId,
huggingFaceModelInfo: convertProtoToModelInfo(protoConfig.huggingFaceModelInfo),
nebiusApiKey: protoConfig.nebiusApiKey,
asksageApiUrl: protoConfig.asksageApiUrl,
asksageApiKey: protoConfig.asksageApiKey,
xaiApiKey: protoConfig.xaiApiKey,
thinkingBudgetTokens: protoConfig.thinkingBudgetTokens,
reasoningEffort: protoConfig.reasoningEffort,
sambanovaApiKey: protoConfig.sambanovaApiKey,
cerebrasApiKey: protoConfig.cerebrasApiKey,
groqApiKey: protoConfig.groqApiKey,
groqModelId: protoConfig.groqModelId,
groqModelInfo: convertProtoToModelInfo(protoConfig.groqModelInfo),
requestTimeoutMs: protoConfig.requestTimeoutMs,
apiProvider: protoConfig.apiProvider !== undefined ? convertProtoToApiProvider(protoConfig.apiProvider) : undefined,
favoritedModelIds: protoConfig.favoritedModelIds.length > 0 ? protoConfig.favoritedModelIds : undefined,
sapAiCoreClientId: protoConfig.sapAiCoreClientId,
sapAiCoreClientSecret: protoConfig.sapAiCoreClientSecret,
sapAiResourceGroup: protoConfig.sapAiResourceGroup,
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
sapAiCoreBaseUrl: protoConfig.sapAiCoreBaseUrl,
claudeCodePath: protoConfig.claudeCodePath,
// Plan mode configurations
planModeApiProvider:
protoConfig.planModeApiProvider !== undefined
? convertProtoToApiProvider(protoConfig.planModeApiProvider)
: undefined,
planModeApiModelId: protoConfig.planModeApiModelId,
planModeThinkingBudgetTokens: protoConfig.planModeThinkingBudgetTokens,
planModeReasoningEffort: protoConfig.planModeReasoningEffort,
planModeVsCodeLmModelSelector: protoConfig.planModeVsCodeLmModelSelector,
planModeAwsBedrockCustomSelected: protoConfig.planModeAwsBedrockCustomSelected,
planModeAwsBedrockCustomModelBaseId: protoConfig.planModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
planModeOpenRouterModelId: protoConfig.planModeOpenRouterModelId,
planModeOpenRouterModelInfo: convertProtoToModelInfo(protoConfig.planModeOpenRouterModelInfo),
planModeOpenAiModelId: protoConfig.planModeOpenAiModelId,
planModeOpenAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.planModeOpenAiModelInfo),
planModeOllamaModelId: protoConfig.planModeOllamaModelId,
planModeLmStudioModelId: protoConfig.planModeLmStudioModelId,
planModeLiteLlmModelId: protoConfig.planModeLiteLlmModelId,
planModeLiteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.planModeLiteLlmModelInfo),
planModeRequestyModelId: protoConfig.planModeRequestyModelId,
planModeRequestyModelInfo: convertProtoToModelInfo(protoConfig.planModeRequestyModelInfo),
planModeTogetherModelId: protoConfig.planModeTogetherModelId,
planModeFireworksModelId: protoConfig.planModeFireworksModelId,
planModeGroqModelId: protoConfig.planModeGroqModelId,
planModeGroqModelInfo: convertProtoToModelInfo(protoConfig.planModeGroqModelInfo),
planModeHuggingFaceModelId: protoConfig.planModeHuggingFaceModelId,
planModeHuggingFaceModelInfo: convertProtoToModelInfo(protoConfig.planModeHuggingFaceModelInfo),
planModeSapAiCoreModelId: protoConfig.planModeSapAiCoreModelId,
// Act mode configurations
actModeApiProvider:
protoConfig.actModeApiProvider !== undefined ? convertProtoToApiProvider(protoConfig.actModeApiProvider) : undefined,
actModeApiModelId: protoConfig.actModeApiModelId,
actModeThinkingBudgetTokens: protoConfig.actModeThinkingBudgetTokens,
actModeReasoningEffort: protoConfig.actModeReasoningEffort,
actModeVsCodeLmModelSelector: protoConfig.actModeVsCodeLmModelSelector,
actModeAwsBedrockCustomSelected: protoConfig.actModeAwsBedrockCustomSelected,
actModeAwsBedrockCustomModelBaseId: protoConfig.actModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
actModeOpenRouterModelId: protoConfig.actModeOpenRouterModelId,
actModeOpenRouterModelInfo: convertProtoToModelInfo(protoConfig.actModeOpenRouterModelInfo),
actModeOpenAiModelId: protoConfig.actModeOpenAiModelId,
actModeOpenAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.actModeOpenAiModelInfo),
actModeOllamaModelId: protoConfig.actModeOllamaModelId,
actModeLmStudioModelId: protoConfig.actModeLmStudioModelId,
actModeLiteLlmModelId: protoConfig.actModeLiteLlmModelId,
actModeLiteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.actModeLiteLlmModelInfo),
actModeRequestyModelId: protoConfig.actModeRequestyModelId,
actModeRequestyModelInfo: convertProtoToModelInfo(protoConfig.actModeRequestyModelInfo),
actModeTogetherModelId: protoConfig.actModeTogetherModelId,
actModeFireworksModelId: protoConfig.actModeFireworksModelId,
actModeGroqModelId: protoConfig.actModeGroqModelId,
actModeGroqModelInfo: convertProtoToModelInfo(protoConfig.actModeGroqModelInfo),
actModeHuggingFaceModelId: protoConfig.actModeHuggingFaceModelId,
actModeHuggingFaceModelInfo: convertProtoToModelInfo(protoConfig.actModeHuggingFaceModelInfo),
actModeSapAiCoreModelId: protoConfig.actModeSapAiCoreModelId,
// Favorited model IDs
favoritedModelIds:
protoConfig.favoritedModelIds && protoConfig.favoritedModelIds.length > 0 ? protoConfig.favoritedModelIds : undefined,
}
}
@@ -11,49 +11,17 @@ import {
*/
export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfiguration): ProtoApiConfiguration {
return ProtoApiConfiguration.create({
// Core API fields
apiProvider: config.apiProvider,
apiModelId: config.apiModelId,
// Global configuration fields (not mode-specific)
apiKey: config.apiKey,
// Provider-specific API keys
clineAccountId: config.clineAccountId,
openrouterApiKey: config.openRouterApiKey,
taskId: config.taskId,
liteLlmBaseUrl: config.liteLlmBaseUrl,
liteLlmApiKey: config.liteLlmApiKey,
liteLlmUsePromptCache: config.liteLlmUsePromptCache,
openaiHeaders: config.openAiHeaders ? JSON.stringify(config.openAiHeaders) : undefined,
anthropicBaseUrl: config.anthropicBaseUrl,
openaiApiKey: config.openAiApiKey,
openaiNativeApiKey: config.openAiNativeApiKey,
geminiApiKey: config.geminiApiKey,
deepseekApiKey: config.deepSeekApiKey,
requestyApiKey: config.requestyApiKey,
togetherApiKey: config.togetherApiKey,
fireworksApiKey: config.fireworksApiKey,
qwenApiKey: config.qwenApiKey,
doubaoApiKey: config.doubaoApiKey,
mistralApiKey: config.mistralApiKey,
moonshotApiKey: config.moonshotApiKey,
nebiusApiKey: config.nebiusApiKey,
asksageApiKey: config.asksageApiKey,
xaiApiKey: config.xaiApiKey,
sambanovaApiKey: config.sambanovaApiKey,
cerebrasApiKey: config.cerebrasApiKey,
// Model IDs - each provider has its own field
openrouterModelId: config.openRouterModelId,
openaiModelId: config.openAiModelId,
anthropicModelId: config.apiModelId,
bedrockModelId: config.apiModelId,
vertexModelId: config.apiModelId,
geminiModelId: config.apiModelId,
ollamaModelId: config.ollamaModelId,
lmStudioModelId: config.lmStudioModelId,
litellmModelId: config.liteLlmModelId,
requestyModelId: config.requestyModelId,
togetherModelId: config.togetherModelId,
fireworksModelId: config.fireworksModelId,
// AWS Bedrock fields
awsBedrockCustomSelected: config.awsBedrockCustomSelected,
awsBedrockCustomModelBaseId: config.awsBedrockCustomModelBaseId,
openrouterApiKey: config.openRouterApiKey,
openrouterProviderSorting: config.openRouterProviderSorting,
awsAccessKey: config.awsAccessKey,
awsSecretKey: config.awsSecretKey,
awsSessionToken: config.awsSessionToken,
@@ -65,68 +33,101 @@ export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfig
awsProfile: config.awsProfile,
awsBedrockApiKey: config.awsBedrockApiKey,
awsBedrockEndpoint: config.awsBedrockEndpoint,
// Vertex AI fields
claudeCodePath: config.claudeCodePath,
vertexProjectId: config.vertexProjectId,
vertexRegion: config.vertexRegion,
// Base URLs and endpoints
openaiBaseUrl: config.openAiBaseUrl,
openaiApiKey: config.openAiApiKey,
ollamaBaseUrl: config.ollamaBaseUrl,
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
lmStudioBaseUrl: config.lmStudioBaseUrl,
geminiApiKey: config.geminiApiKey,
geminiBaseUrl: config.geminiBaseUrl,
litellmBaseUrl: config.liteLlmBaseUrl,
asksageApiUrl: config.asksageApiUrl,
// LiteLLM specific fields
litellmApiKey: config.liteLlmApiKey,
litellmUsePromptCache: config.liteLlmUsePromptCache,
// Model configuration
thinkingBudgetTokens: config.thinkingBudgetTokens ? Number(config.thinkingBudgetTokens) : undefined,
reasoningEffort: config.reasoningEffort,
requestTimeoutMs: config.requestTimeoutMs ? Number(config.requestTimeoutMs) : undefined,
// Fireworks specific
openaiNativeApiKey: config.openAiNativeApiKey,
deepSeekApiKey: config.deepSeekApiKey,
requestyApiKey: config.requestyApiKey,
togetherApiKey: config.togetherApiKey,
fireworksApiKey: config.fireworksApiKey,
fireworksModelMaxCompletionTokens: config.fireworksModelMaxCompletionTokens
? Number(config.fireworksModelMaxCompletionTokens)
: undefined,
fireworksModelMaxTokens: config.fireworksModelMaxTokens ? Number(config.fireworksModelMaxTokens) : undefined,
// Azure specific
qwenApiKey: config.qwenApiKey,
doubaoApiKey: config.doubaoApiKey,
mistralApiKey: config.mistralApiKey,
moonshotApiKey: config.moonshotApiKey,
azureApiVersion: config.azureApiVersion,
// Ollama specific
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
// Qwen specific
qwenApiLine: config.qwenApiLine,
// Moonshot specific
moonshotApiLine: config.moonshotApiLine,
// OpenRouter specific
openrouterProviderSorting: config.openRouterProviderSorting,
// SAP AI Core specific
nebiusApiKey: config.nebiusApiKey,
asksageApiUrl: config.asksageApiUrl,
asksageApiKey: config.asksageApiKey,
xaiApiKey: config.xaiApiKey,
sambanovaApiKey: config.sambanovaApiKey,
cerebrasApiKey: config.cerebrasApiKey,
requestTimeoutMs: config.requestTimeoutMs ? Number(config.requestTimeoutMs) : undefined,
sapAiCoreClientId: config.sapAiCoreClientId,
sapAiCoreClientSecret: config.sapAiCoreClientSecret,
sapAiCoreBaseUrl: config.sapAiCoreBaseUrl,
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
sapAiResourceGroup: config.sapAiResourceGroup,
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
sapAiCoreBaseUrl: config.sapAiCoreBaseUrl,
// Complex objects stored as JSON strings
vscodeLmModelSelector: config.vsCodeLmModelSelector ? JSON.stringify(config.vsCodeLmModelSelector) : undefined,
openrouterModelInfo: config.openRouterModelInfo ? JSON.stringify(config.openRouterModelInfo) : undefined,
openaiModelInfo: config.openAiModelInfo ? JSON.stringify(config.openAiModelInfo) : undefined,
requestyModelInfo: config.requestyModelInfo ? JSON.stringify(config.requestyModelInfo) : undefined,
litellmModelInfo: config.liteLlmModelInfo ? JSON.stringify(config.liteLlmModelInfo) : undefined,
openaiHeaders: config.openAiHeaders ? JSON.stringify(config.openAiHeaders) : undefined,
// Plan mode configurations
planModeApiProvider: config.planModeApiProvider,
planModeApiModelId: config.planModeApiModelId,
planModeThinkingBudgetTokens: config.planModeThinkingBudgetTokens
? Number(config.planModeThinkingBudgetTokens)
: undefined,
planModeReasoningEffort: config.planModeReasoningEffort,
planModeVscodeLmModelSelector: config.planModeVsCodeLmModelSelector
? JSON.stringify(config.planModeVsCodeLmModelSelector)
: undefined,
planModeAwsBedrockCustomSelected: config.planModeAwsBedrockCustomSelected,
planModeAwsBedrockCustomModelBaseId: config.planModeAwsBedrockCustomModelBaseId,
planModeOpenrouterModelId: config.planModeOpenRouterModelId,
planModeOpenrouterModelInfo: config.planModeOpenRouterModelInfo
? JSON.stringify(config.planModeOpenRouterModelInfo)
: undefined,
planModeOpenaiModelId: config.planModeOpenAiModelId,
planModeOpenaiModelInfo: config.planModeOpenAiModelInfo ? JSON.stringify(config.planModeOpenAiModelInfo) : undefined,
planModeOllamaModelId: config.planModeOllamaModelId,
planModeLmStudioModelId: config.planModeLmStudioModelId,
planModeLiteLlmModelId: config.planModeLiteLlmModelId,
planModeLiteLlmModelInfo: config.planModeLiteLlmModelInfo ? JSON.stringify(config.planModeLiteLlmModelInfo) : undefined,
planModeRequestyModelId: config.planModeRequestyModelId,
planModeRequestyModelInfo: config.planModeRequestyModelInfo
? JSON.stringify(config.planModeRequestyModelInfo)
: undefined,
planModeTogetherModelId: config.planModeTogetherModelId,
planModeFireworksModelId: config.planModeFireworksModelId,
planModeSapAiCoreModelId: config.planModeSapAiCoreModelId,
// Claude Code specific
claudeCodePath: config.claudeCodePath,
// Act mode configurations
actModeApiProvider: config.actModeApiProvider,
actModeApiModelId: config.actModeApiModelId,
actModeThinkingBudgetTokens: config.actModeThinkingBudgetTokens ? Number(config.actModeThinkingBudgetTokens) : undefined,
actModeReasoningEffort: config.actModeReasoningEffort,
actModeVscodeLmModelSelector: config.actModeVsCodeLmModelSelector
? JSON.stringify(config.actModeVsCodeLmModelSelector)
: undefined,
actModeAwsBedrockCustomSelected: config.actModeAwsBedrockCustomSelected,
actModeAwsBedrockCustomModelBaseId: config.actModeAwsBedrockCustomModelBaseId,
actModeOpenrouterModelId: config.actModeOpenRouterModelId,
actModeOpenrouterModelInfo: config.actModeOpenRouterModelInfo
? JSON.stringify(config.actModeOpenRouterModelInfo)
: undefined,
actModeOpenaiModelId: config.actModeOpenAiModelId,
actModeOpenaiModelInfo: config.actModeOpenAiModelInfo ? JSON.stringify(config.actModeOpenAiModelInfo) : undefined,
actModeOllamaModelId: config.actModeOllamaModelId,
actModeLmStudioModelId: config.actModeLmStudioModelId,
actModeLiteLlmModelId: config.actModeLiteLlmModelId,
actModeLiteLlmModelInfo: config.actModeLiteLlmModelInfo ? JSON.stringify(config.actModeLiteLlmModelInfo) : undefined,
actModeRequestyModelId: config.actModeRequestyModelId,
actModeRequestyModelInfo: config.actModeRequestyModelInfo ? JSON.stringify(config.actModeRequestyModelInfo) : undefined,
actModeTogetherModelId: config.actModeTogetherModelId,
actModeFireworksModelId: config.actModeFireworksModelId,
actModeSapAiCoreModelId: config.actModeSapAiCoreModelId,
// Arrays
// Favorited model IDs
favoritedModelIds: config.favoritedModelIds || [],
})
}
@@ -137,45 +138,16 @@ export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfig
export function convertProtoApiConfigurationToApiConfiguration(protoConfig: ProtoApiConfiguration): ApiConfiguration {
// eslint-disable-next-line eslint-rules/no-protobuf-object-literals
const config: ApiConfiguration = {
// Core API fields
apiProvider: protoConfig.apiProvider as ApiProvider,
apiModelId: protoConfig.apiModelId,
// Global configuration fields (not mode-specific)
apiKey: protoConfig.apiKey,
// Provider-specific API keys
clineAccountId: protoConfig.clineAccountId,
openRouterApiKey: protoConfig.openrouterApiKey,
taskId: protoConfig.taskId,
liteLlmBaseUrl: protoConfig.liteLlmBaseUrl,
liteLlmApiKey: protoConfig.liteLlmApiKey,
liteLlmUsePromptCache: protoConfig.liteLlmUsePromptCache,
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
openAiApiKey: protoConfig.openaiApiKey,
openAiNativeApiKey: protoConfig.openaiNativeApiKey,
geminiApiKey: protoConfig.geminiApiKey,
deepSeekApiKey: protoConfig.deepseekApiKey,
requestyApiKey: protoConfig.requestyApiKey,
togetherApiKey: protoConfig.togetherApiKey,
fireworksApiKey: protoConfig.fireworksApiKey,
qwenApiKey: protoConfig.qwenApiKey,
doubaoApiKey: protoConfig.doubaoApiKey,
mistralApiKey: protoConfig.mistralApiKey,
moonshotApiKey: protoConfig.moonshotApiKey,
nebiusApiKey: protoConfig.nebiusApiKey,
asksageApiKey: protoConfig.asksageApiKey,
xaiApiKey: protoConfig.xaiApiKey,
sambanovaApiKey: protoConfig.sambanovaApiKey,
cerebrasApiKey: protoConfig.cerebrasApiKey,
// Model IDs
openRouterModelId: protoConfig.openrouterModelId,
openAiModelId: protoConfig.openaiModelId,
ollamaModelId: protoConfig.ollamaModelId,
lmStudioModelId: protoConfig.lmStudioModelId,
liteLlmModelId: protoConfig.litellmModelId,
requestyModelId: protoConfig.requestyModelId,
togetherModelId: protoConfig.togetherModelId,
fireworksModelId: protoConfig.fireworksModelId,
// AWS Bedrock fields
awsBedrockCustomSelected: protoConfig.awsBedrockCustomSelected,
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as BedrockModelId | undefined,
openRouterApiKey: protoConfig.openrouterApiKey,
openRouterProviderSorting: protoConfig.openrouterProviderSorting,
awsAccessKey: protoConfig.awsAccessKey,
awsSecretKey: protoConfig.awsSecretKey,
awsSessionToken: protoConfig.awsSessionToken,
@@ -187,83 +159,121 @@ export function convertProtoApiConfigurationToApiConfiguration(protoConfig: Prot
awsAuthentication: protoConfig.awsAuthentication,
awsBedrockApiKey: protoConfig.awsBedrockApiKey,
awsBedrockEndpoint: protoConfig.awsBedrockEndpoint,
// Vertex AI fields
claudeCodePath: protoConfig.claudeCodePath,
vertexProjectId: protoConfig.vertexProjectId,
vertexRegion: protoConfig.vertexRegion,
// Base URLs and endpoints
openAiBaseUrl: protoConfig.openaiBaseUrl,
openAiApiKey: protoConfig.openaiApiKey,
ollamaBaseUrl: protoConfig.ollamaBaseUrl,
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
lmStudioBaseUrl: protoConfig.lmStudioBaseUrl,
geminiApiKey: protoConfig.geminiApiKey,
geminiBaseUrl: protoConfig.geminiBaseUrl,
liteLlmBaseUrl: protoConfig.litellmBaseUrl,
asksageApiUrl: protoConfig.asksageApiUrl,
// LiteLLM specific fields
liteLlmApiKey: protoConfig.litellmApiKey,
liteLlmUsePromptCache: protoConfig.litellmUsePromptCache,
// Model configuration
thinkingBudgetTokens: protoConfig.thinkingBudgetTokens ? Number(protoConfig.thinkingBudgetTokens) : undefined,
reasoningEffort: protoConfig.reasoningEffort,
requestTimeoutMs: protoConfig.requestTimeoutMs ? Number(protoConfig.requestTimeoutMs) : undefined,
// Fireworks specific
openAiNativeApiKey: protoConfig.openaiNativeApiKey,
deepSeekApiKey: protoConfig.deepSeekApiKey,
requestyApiKey: protoConfig.requestyApiKey,
togetherApiKey: protoConfig.togetherApiKey,
fireworksApiKey: protoConfig.fireworksApiKey,
fireworksModelMaxCompletionTokens: protoConfig.fireworksModelMaxCompletionTokens
? Number(protoConfig.fireworksModelMaxCompletionTokens)
: undefined,
fireworksModelMaxTokens: protoConfig.fireworksModelMaxTokens ? Number(protoConfig.fireworksModelMaxTokens) : undefined,
// Azure specific
qwenApiKey: protoConfig.qwenApiKey,
doubaoApiKey: protoConfig.doubaoApiKey,
mistralApiKey: protoConfig.mistralApiKey,
moonshotApiKey: protoConfig.moonshotApiKey,
azureApiVersion: protoConfig.azureApiVersion,
// Ollama specific
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
// Qwen specific
qwenApiLine: protoConfig.qwenApiLine,
// Moonshot specific
moonshotApiLine: protoConfig.moonshotApiLine,
// OpenRouter specific
openRouterProviderSorting: protoConfig.openrouterProviderSorting,
// SAP AI Core specific
nebiusApiKey: protoConfig.nebiusApiKey,
asksageApiUrl: protoConfig.asksageApiUrl,
asksageApiKey: protoConfig.asksageApiKey,
xaiApiKey: protoConfig.xaiApiKey,
sambanovaApiKey: protoConfig.sambanovaApiKey,
cerebrasApiKey: protoConfig.cerebrasApiKey,
requestTimeoutMs: protoConfig.requestTimeoutMs ? Number(protoConfig.requestTimeoutMs) : undefined,
sapAiCoreClientId: protoConfig.sapAiCoreClientId,
sapAiCoreClientSecret: protoConfig.sapAiCoreClientSecret,
sapAiCoreBaseUrl: protoConfig.sapAiCoreBaseUrl,
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
sapAiResourceGroup: protoConfig.sapAiResourceGroup,
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
sapAiCoreBaseUrl: protoConfig.sapAiCoreBaseUrl,
// Claude Code specific
claudeCodePath: protoConfig.claudeCodePath,
// Plan mode configurations
planModeApiProvider: protoConfig.planModeApiProvider as ApiProvider,
planModeApiModelId: protoConfig.planModeApiModelId,
planModeThinkingBudgetTokens: protoConfig.planModeThinkingBudgetTokens
? Number(protoConfig.planModeThinkingBudgetTokens)
: undefined,
planModeReasoningEffort: protoConfig.planModeReasoningEffort,
planModeAwsBedrockCustomSelected: protoConfig.planModeAwsBedrockCustomSelected,
planModeAwsBedrockCustomModelBaseId: protoConfig.planModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
planModeOpenRouterModelId: protoConfig.planModeOpenrouterModelId,
planModeOpenAiModelId: protoConfig.planModeOpenaiModelId,
planModeOllamaModelId: protoConfig.planModeOllamaModelId,
planModeLmStudioModelId: protoConfig.planModeLmStudioModelId,
planModeLiteLlmModelId: protoConfig.planModeLiteLlmModelId,
planModeRequestyModelId: protoConfig.planModeRequestyModelId,
planModeTogetherModelId: protoConfig.planModeTogetherModelId,
planModeFireworksModelId: protoConfig.planModeFireworksModelId,
planModeSapAiCoreModelId: protoConfig.planModeSapAiCoreModelId,
// Arrays
// Act mode configurations
actModeApiProvider: protoConfig.actModeApiProvider as ApiProvider,
actModeApiModelId: protoConfig.actModeApiModelId,
actModeThinkingBudgetTokens: protoConfig.actModeThinkingBudgetTokens
? Number(protoConfig.actModeThinkingBudgetTokens)
: undefined,
actModeReasoningEffort: protoConfig.actModeReasoningEffort,
actModeAwsBedrockCustomSelected: protoConfig.actModeAwsBedrockCustomSelected,
actModeAwsBedrockCustomModelBaseId: protoConfig.actModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
actModeOpenRouterModelId: protoConfig.actModeOpenrouterModelId,
actModeOpenAiModelId: protoConfig.actModeOpenaiModelId,
actModeOllamaModelId: protoConfig.actModeOllamaModelId,
actModeLmStudioModelId: protoConfig.actModeLmStudioModelId,
actModeLiteLlmModelId: protoConfig.actModeLiteLlmModelId,
actModeRequestyModelId: protoConfig.actModeRequestyModelId,
actModeTogetherModelId: protoConfig.actModeTogetherModelId,
actModeFireworksModelId: protoConfig.actModeFireworksModelId,
actModeSapAiCoreModelId: protoConfig.actModeSapAiCoreModelId,
// Favorited model IDs
favoritedModelIds: protoConfig.favoritedModelIds || [],
}
// Handle complex JSON objects
try {
if (protoConfig.vscodeLmModelSelector) {
config.vsCodeLmModelSelector = JSON.parse(protoConfig.vscodeLmModelSelector)
}
if (protoConfig.openrouterModelInfo) {
config.openRouterModelInfo = JSON.parse(protoConfig.openrouterModelInfo)
}
if (protoConfig.openaiModelInfo) {
config.openAiModelInfo = JSON.parse(protoConfig.openaiModelInfo)
}
if (protoConfig.requestyModelInfo) {
config.requestyModelInfo = JSON.parse(protoConfig.requestyModelInfo)
}
if (protoConfig.litellmModelInfo) {
config.liteLlmModelInfo = JSON.parse(protoConfig.litellmModelInfo)
}
if (protoConfig.openaiHeaders) {
config.openAiHeaders = JSON.parse(protoConfig.openaiHeaders)
}
if (protoConfig.planModeVscodeLmModelSelector) {
config.planModeVsCodeLmModelSelector = JSON.parse(protoConfig.planModeVscodeLmModelSelector)
}
if (protoConfig.planModeOpenrouterModelInfo) {
config.planModeOpenRouterModelInfo = JSON.parse(protoConfig.planModeOpenrouterModelInfo)
}
if (protoConfig.planModeOpenaiModelInfo) {
config.planModeOpenAiModelInfo = JSON.parse(protoConfig.planModeOpenaiModelInfo)
}
if (protoConfig.planModeLiteLlmModelInfo) {
config.planModeLiteLlmModelInfo = JSON.parse(protoConfig.planModeLiteLlmModelInfo)
}
if (protoConfig.planModeRequestyModelInfo) {
config.planModeRequestyModelInfo = JSON.parse(protoConfig.planModeRequestyModelInfo)
}
if (protoConfig.actModeVscodeLmModelSelector) {
config.actModeVsCodeLmModelSelector = JSON.parse(protoConfig.actModeVscodeLmModelSelector)
}
if (protoConfig.actModeOpenrouterModelInfo) {
config.actModeOpenRouterModelInfo = JSON.parse(protoConfig.actModeOpenrouterModelInfo)
}
if (protoConfig.actModeOpenaiModelInfo) {
config.actModeOpenAiModelInfo = JSON.parse(protoConfig.actModeOpenaiModelInfo)
}
if (protoConfig.actModeLiteLlmModelInfo) {
config.actModeLiteLlmModelInfo = JSON.parse(protoConfig.actModeLiteLlmModelInfo)
}
if (protoConfig.actModeRequestyModelInfo) {
config.actModeRequestyModelInfo = JSON.parse(protoConfig.actModeRequestyModelInfo)
}
} catch (error) {
console.error("Failed to parse complex JSON objects in API configuration:", error)
}
+28 -13
View File
@@ -7,7 +7,7 @@ import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import Thumbnails from "@/components/common/Thumbnails"
import Tooltip from "@/components/common/Tooltip"
import ApiOptions from "@/components/settings/ApiOptions"
import { normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
import { normalizeApiConfiguration, getModeSpecificFields } from "@/components/settings/utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { FileServiceClient, StateServiceClient, ModelsServiceClient } from "@/services/grpc-client"
import {
@@ -966,8 +966,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
// Separate the API config submission logic
const submitApiConfig = useCallback(async () => {
const apiValidationResult = validateApiConfiguration(apiConfiguration)
const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels)
const apiValidationResult = validateApiConfiguration(chatSettings.mode, apiConfiguration)
const modelIdValidationResult = validateModelId(chatSettings.mode, apiConfiguration, openRouterModels)
if (!apiValidationResult && !modelIdValidationResult && apiConfiguration) {
try {
@@ -1089,7 +1089,16 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
// Get model display name
const modelDisplayName = useMemo(() => {
const { selectedProvider, selectedModelId } = normalizeApiConfiguration(apiConfiguration)
const { selectedProvider, selectedModelId } = normalizeApiConfiguration(apiConfiguration, chatSettings.mode)
const {
vsCodeLmModelSelector,
togetherModelId,
fireworksModelId,
lmStudioModelId,
ollamaModelId,
liteLlmModelId,
requestyModelId,
} = getModeSpecificFields(apiConfiguration, chatSettings.mode)
const unknownModel = "unknown"
if (!apiConfiguration) return unknownModel
switch (selectedProvider) {
@@ -1098,25 +1107,25 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
case "openai":
return `openai-compat:${selectedModelId}`
case "vscode-lm":
return `vscode-lm:${apiConfiguration.vsCodeLmModelSelector ? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}` : unknownModel}`
return `vscode-lm:${vsCodeLmModelSelector ? `${vsCodeLmModelSelector.vendor ?? ""}/${vsCodeLmModelSelector.family ?? ""}` : unknownModel}`
case "together":
return `${selectedProvider}:${apiConfiguration.togetherModelId}`
return `${selectedProvider}:${togetherModelId}`
case "fireworks":
return `fireworks:${apiConfiguration.fireworksModelId}`
return `fireworks:${fireworksModelId}`
case "lmstudio":
return `${selectedProvider}:${apiConfiguration.lmStudioModelId}`
return `${selectedProvider}:${lmStudioModelId}`
case "ollama":
return `${selectedProvider}:${apiConfiguration.ollamaModelId}`
return `${selectedProvider}:${ollamaModelId}`
case "litellm":
return `${selectedProvider}:${apiConfiguration.liteLlmModelId}`
return `${selectedProvider}:${liteLlmModelId}`
case "requesty":
return `${selectedProvider}:${apiConfiguration.requestyModelId}`
return `${selectedProvider}:${requestyModelId}`
case "anthropic":
case "openrouter":
default:
return `${selectedProvider}:${selectedModelId}`
}
}, [apiConfiguration])
}, [apiConfiguration, chatSettings.mode])
// Calculate arrow position and menu position based on button location
useEffect(() => {
@@ -1720,7 +1729,13 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
style={{
bottom: `calc(100vh - ${menuPosition}px + 6px)`,
}}>
<ApiOptions showModelOptions={true} modelIdErrorMessage={undefined} isPopup={true} />
<ApiOptions
showModelOptions={true}
apiErrorMessage={undefined}
modelIdErrorMessage={undefined}
isPopup={true}
currentMode={chatSettings.mode}
/>
</ModelSelectorTooltip>
)}
</ModelContainer>
+3 -2
View File
@@ -47,6 +47,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
apiConfiguration,
telemetrySetting,
navigateToChat,
chatSettings,
} = useExtensionState()
const shouldShowQuickWins = false // !taskHistory || taskHistory.length < QUICK_WINS_HISTORY_THRESHOLD
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
@@ -199,8 +200,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
messageHandlers
const { selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
return normalizeApiConfiguration(apiConfiguration, chatSettings.mode)
}, [apiConfiguration, chatSettings.mode])
const selectFilesAndImages = useCallback(async () => {
try {
@@ -1,6 +1,6 @@
import HeroTooltip from "@/components/common/HeroTooltip"
import Thumbnails from "@/components/common/Thumbnails"
import { normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
import { normalizeApiConfiguration, getModeSpecificFields } from "@/components/settings/utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { FileServiceClient, TaskServiceClient, UiServiceClient } from "@/services/grpc-client"
import { formatLargeNumber, formatSize } from "@/utils/format"
@@ -43,7 +43,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
onClose,
onScrollToMessage,
}) => {
const { apiConfiguration, currentTaskItem, checkpointTrackerErrorMessage, clineMessages, navigateToSettings } =
const { apiConfiguration, currentTaskItem, checkpointTrackerErrorMessage, clineMessages, navigateToSettings, chatSettings } =
useExtensionState()
const [isTaskExpanded, setIsTaskExpanded] = useState(true)
const [isTextExpanded, setIsTextExpanded] = useState(false)
@@ -51,7 +51,10 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
const textContainerRef = useRef<HTMLDivElement>(null)
const textRef = useRef<HTMLDivElement>(null)
const { selectedModelInfo } = useMemo(() => normalizeApiConfiguration(apiConfiguration), [apiConfiguration])
const { selectedModelInfo } = useMemo(
() => normalizeApiConfiguration(apiConfiguration, chatSettings.mode),
[apiConfiguration, chatSettings.mode],
)
const contextWindow = selectedModelInfo?.contextWindow
// Open task header when checkpoint tracker error message is set
@@ -130,19 +133,18 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
}, [task.text, windowWidth, isTaskExpanded])
const isCostAvailable = useMemo(() => {
const modeFields = getModeSpecificFields(apiConfiguration, chatSettings.mode)
const openAiCompatHasPricing =
apiConfiguration?.apiProvider === "openai" &&
apiConfiguration?.openAiModelInfo?.inputPrice &&
apiConfiguration?.openAiModelInfo?.outputPrice
modeFields.apiProvider === "openai" &&
modeFields.openAiModelInfo?.inputPrice &&
modeFields.openAiModelInfo?.outputPrice
if (openAiCompatHasPricing) {
return true
}
return (
apiConfiguration?.apiProvider !== "vscode-lm" &&
apiConfiguration?.apiProvider !== "ollama" &&
apiConfiguration?.apiProvider !== "lmstudio"
modeFields.apiProvider !== "vscode-lm" && modeFields.apiProvider !== "ollama" && modeFields.apiProvider !== "lmstudio"
)
}, [apiConfiguration?.apiProvider, apiConfiguration?.openAiModelInfo])
}, [apiConfiguration, chatSettings.mode])
const shouldShowPromptCacheInfo = () => {
// Hybrid logic: Show cache info if we have actual cache data,
@@ -1,12 +1,12 @@
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient, StateServiceClient } from "@/services/grpc-client"
import { BooleanRequest, StringRequest } from "@shared/proto/common"
import { VSCodeButton, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { ModelsServiceClient } from "@/services/grpc-client"
import { StringRequest } from "@shared/proto/common"
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { useCallback, useEffect, useState } from "react"
import { useInterval } from "react-use"
import styled from "styled-components"
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
import { normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
import { ClineProvider } from "./providers/ClineProvider"
import { OpenRouterProvider } from "./providers/OpenRouterProvider"
import { MistralProvider } from "./providers/MistralProvider"
@@ -37,13 +37,14 @@ import { VSCodeLmProvider } from "./providers/VSCodeLmProvider"
import { LMStudioProvider } from "./providers/LMStudioProvider"
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
import { GroqProvider } from "./providers/GroqProvider"
import { Mode } from "@shared/ChatSettings"
interface ApiOptionsProps {
showSubmitButton?: boolean
showModelOptions: boolean
apiErrorMessage?: string
modelIdErrorMessage?: string
isPopup?: boolean
currentMode: Mode
}
// This is necessary to ensure dropdown opens downward, important for when this is used in popup
@@ -70,24 +71,16 @@ declare module "vscode" {
}
}
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, isPopup, showSubmitButton }: ApiOptionsProps) => {
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, isPopup, currentMode }: ApiOptionsProps) => {
// Use full context state for immediate save payload
const { apiConfiguration, uriScheme } = useExtensionState()
const { apiConfiguration } = useExtensionState()
const selectedProvider = apiConfiguration?.apiProvider
const { selectedProvider } = normalizeApiConfiguration(apiConfiguration, currentMode)
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleModeFieldChange } = useApiConfigurationHandlers()
const [ollamaModels, setOllamaModels] = useState<string[]>([])
const handleSubmit = async () => {
try {
await StateServiceClient.setWelcomeViewCompleted(BooleanRequest.create({ value: true }))
} catch (error) {
console.error("Failed to update API configuration or complete welcome view:", error)
}
}
// Poll ollama/vscode-lm models
const requestLocalModels = useCallback(async () => {
if (selectedProvider === "ollama") {
@@ -132,7 +125,11 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
id="api-provider"
value={selectedProvider}
onChange={(e: any) => {
handleFieldChange("apiProvider", e.target.value)
handleModeFieldChange(
{ plan: "planModeApiProvider", act: "actModeApiProvider" },
e.target.value,
currentMode,
)
}}
style={{
minWidth: 130,
@@ -143,13 +140,13 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
<VSCodeOption value="claude-code">Claude Code</VSCodeOption>
<VSCodeOption value="bedrock">Amazon Bedrock</VSCodeOption>
<VSCodeOption value="openai-native">OpenAI</VSCodeOption>
<VSCodeOption value="openai">OpenAI Compatible</VSCodeOption>
<VSCodeOption value="vertex">GCP Vertex AI</VSCodeOption>
<VSCodeOption value="gemini">Google Gemini</VSCodeOption>
<VSCodeOption value="groq">Groq</VSCodeOption>
<VSCodeOption value="deepseek">DeepSeek</VSCodeOption>
<VSCodeOption value="openai">OpenAI Compatible</VSCodeOption>
<VSCodeOption value="mistral">Mistral</VSCodeOption>
<VSCodeOption value="openai-native">OpenAI</VSCodeOption>
<VSCodeOption value="vscode-lm">VS Code LM API</VSCodeOption>
<VSCodeOption value="requesty">Requesty</VSCodeOption>
<VSCodeOption value="fireworks">Fireworks</VSCodeOption>
@@ -171,116 +168,116 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
</DropdownContainer>
{apiConfiguration && selectedProvider === "cline" && (
<ClineProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<ClineProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "asksage" && (
<AskSageProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<AskSageProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "anthropic" && (
<AnthropicProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<AnthropicProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "claude-code" && (
<ClaudeCodeProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<ClaudeCodeProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "openai-native" && (
<OpenAINativeProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<OpenAINativeProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "qwen" && (
<QwenProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<QwenProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "doubao" && (
<DoubaoProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<DoubaoProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "mistral" && (
<MistralProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<MistralProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "openrouter" && (
<OpenRouterProvider showModelOptions={showModelOptions} isPopup={isPopup} uriScheme={uriScheme} />
<OpenRouterProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "deepseek" && (
<DeepSeekProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<DeepSeekProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "together" && (
<TogetherProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<TogetherProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "openai" && (
<OpenAICompatibleProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<OpenAICompatibleProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "sambanova" && (
<SambanovaProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<SambanovaProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "bedrock" && (
<BedrockProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<BedrockProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "vertex" && (
<VertexProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<VertexProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "gemini" && (
<GeminiProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<GeminiProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "requesty" && (
<RequestyProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<RequestyProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "fireworks" && (
<FireworksProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<FireworksProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "vscode-lm" && <VSCodeLmProvider />}
{apiConfiguration && selectedProvider === "vscode-lm" && <VSCodeLmProvider currentMode={currentMode} />}
{apiConfiguration && selectedProvider === "groq" && (
<GroqProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<GroqProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "litellm" && (
<LiteLlmProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<LiteLlmProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "lmstudio" && (
<LMStudioProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<LMStudioProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "ollama" && (
<OllamaProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<OllamaProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "moonshot" && (
<MoonshotProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<MoonshotProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "huggingface" && (
<HuggingFaceProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<HuggingFaceProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "nebius" && (
<NebiusProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<NebiusProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "xai" && (
<XaiProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<XaiProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "cerebras" && (
<CerebrasProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<CerebrasProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiConfiguration && selectedProvider === "sapaicore" && (
<SapAiCoreProvider showModelOptions={showModelOptions} isPopup={isPopup} />
<SapAiCoreProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
)}
{apiErrorMessage && (
@@ -303,12 +300,6 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
{modelIdErrorMessage}
</p>
)}
{showSubmitButton && (
<VSCodeButton onClick={handleSubmit} disabled={apiErrorMessage != null} className="mt-0.75" title="Submit">
Let's go!
</VSCodeButton>
)}
</div>
)
}
@@ -12,15 +12,19 @@ import { highlight } from "../history/HistoryView"
import { ModelInfoView } from "./common/ModelInfoView"
import { normalizeApiConfiguration } from "./utils/providerUtils"
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
import { getModeSpecificFields } from "./utils/providerUtils"
import { Mode } from "@shared/ChatSettings"
export interface GroqModelPickerProps {
isPopup?: boolean
currentMode: Mode
}
const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup }) => {
const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup, currentMode }) => {
const { apiConfiguration, groqModels: dynamicGroqModels, setGroqModels } = useExtensionState()
const { handleFieldsChange } = useApiConfigurationHandlers()
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.groqModelId || groqDefaultModelId)
const { handleModeFieldsChange } = useApiConfigurationHandlers()
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
const [searchTerm, setSearchTerm] = useState(modeFields.groqModelId || groqDefaultModelId)
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm)
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(-1)
@@ -32,16 +36,23 @@ const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup }) => {
// Use dynamic models if available, otherwise fall back to static models
const modelInfo = dynamicGroqModels?.[newModelId] || groqModels[newModelId as keyof typeof groqModels]
handleFieldsChange({
groqModelId: newModelId,
groqModelInfo: modelInfo,
})
handleModeFieldsChange(
{
groqModelId: { plan: "planModeGroqModelId", act: "actModeGroqModelId" },
groqModelInfo: { plan: "planModeGroqModelInfo", act: "actModeGroqModelInfo" },
},
{
groqModelId: newModelId,
groqModelInfo: modelInfo,
},
currentMode,
)
setSearchTerm(newModelId)
}
const { selectedModelId, selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
return normalizeApiConfiguration(apiConfiguration, currentMode)
}, [apiConfiguration, currentMode])
useMount(() => {
ModelsServiceClient.refreshGroqModels(EmptyRequest.create({}))
@@ -4,21 +4,24 @@ import Fuse from "fuse.js"
import React, { KeyboardEvent, useEffect, useMemo, useRef, useState } from "react"
import { useMount } from "react-use"
import { huggingFaceDefaultModelId, huggingFaceModels } from "@shared/api"
import { Mode } from "@shared/ChatSettings"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { ModelsServiceClient } from "../../services/grpc-client"
import { highlight } from "../history/HistoryView"
import { ModelInfoView } from "./common/ModelInfoView"
import { normalizeApiConfiguration } from "./utils/providerUtils"
import { normalizeApiConfiguration, getModeSpecificFields } from "./utils/providerUtils"
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
export interface HuggingFaceModelPickerProps {
isPopup?: boolean
currentMode: Mode
}
const HuggingFaceModelPicker: React.FC<HuggingFaceModelPickerProps> = ({ isPopup }) => {
const HuggingFaceModelPicker: React.FC<HuggingFaceModelPickerProps> = ({ isPopup, currentMode }) => {
const { apiConfiguration, huggingFaceModels: dynamicModels, setHuggingFaceModels } = useExtensionState()
const { handleFieldsChange } = useApiConfigurationHandlers()
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.huggingFaceModelId || huggingFaceDefaultModelId)
const { handleModeFieldsChange } = useApiConfigurationHandlers()
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
const [searchTerm, setSearchTerm] = useState(modeFields.huggingFaceModelId || huggingFaceDefaultModelId)
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(-1)
const dropdownRef = useRef<HTMLDivElement>(null)
@@ -27,16 +30,25 @@ const HuggingFaceModelPicker: React.FC<HuggingFaceModelPickerProps> = ({ isPopup
const handleModelChange = (newModelId: string) => {
const allModels = { ...huggingFaceModels, ...dynamicModels }
handleFieldsChange({
huggingFaceModelId: newModelId,
huggingFaceModelInfo: allModels[newModelId as keyof typeof allModels],
})
const modelInfo = allModels[newModelId as keyof typeof allModels]
handleModeFieldsChange(
{
huggingFaceModelId: { plan: "planModeHuggingFaceModelId", act: "actModeHuggingFaceModelId" },
huggingFaceModelInfo: { plan: "planModeHuggingFaceModelInfo", act: "actModeHuggingFaceModelInfo" },
},
{
huggingFaceModelId: newModelId,
huggingFaceModelInfo: modelInfo,
},
currentMode,
)
setSearchTerm(newModelId)
}
const { selectedModelId, selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
return normalizeApiConfiguration(apiConfiguration, currentMode)
}, [apiConfiguration, currentMode])
useMount(() => {
ModelsServiceClient.refreshHuggingFaceModels(EmptyRequest.create({}))
@@ -11,10 +11,11 @@ import { useMount } from "react-use"
import styled from "styled-components"
import { highlight } from "../history/HistoryView"
import { ModelInfoView } from "./common/ModelInfoView"
import { normalizeApiConfiguration } from "./utils/providerUtils"
import { getModeSpecificFields, normalizeApiConfiguration } from "./utils/providerUtils"
import FeaturedModelCard from "./FeaturedModelCard"
import ThinkingBudgetSlider from "./ThinkingBudgetSlider"
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
// Star icon for favorites
const StarIcon = ({ isFavorite, onClick }: { isFavorite: boolean; onClick: (e: React.MouseEvent) => void }) => {
@@ -39,6 +40,7 @@ const StarIcon = ({ isFavorite, onClick }: { isFavorite: boolean; onClick: (e: R
export interface OpenRouterModelPickerProps {
isPopup?: boolean
currentMode: Mode
}
// Featured models for Cline provider
@@ -60,10 +62,11 @@ const featuredModels = [
},
]
const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }) => {
const { handleFieldsChange } = useApiConfigurationHandlers()
const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup, currentMode }) => {
const { handleModeFieldsChange } = useApiConfigurationHandlers()
const { apiConfiguration, openRouterModels, refreshOpenRouterModels } = useExtensionState()
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.openRouterModelId || openRouterDefaultModelId)
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
const [searchTerm, setSearchTerm] = useState(modeFields.openRouterModelId || openRouterDefaultModelId)
const [isSearchInputDirty, setIsSearchInputDirty] = useState(false)
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(-1)
@@ -76,25 +79,32 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
setSearchTerm(newModelId)
handleFieldsChange({
openRouterModelId: newModelId,
openRouterModelInfo: openRouterModels[newModelId],
})
handleModeFieldsChange(
{
openRouterModelId: { plan: "planModeOpenRouterModelId", act: "actModeOpenRouterModelId" },
openRouterModelInfo: { plan: "planModeOpenRouterModelInfo", act: "actModeOpenRouterModelInfo" },
},
{
openRouterModelId: newModelId,
openRouterModelInfo: openRouterModels[newModelId],
},
currentMode,
)
}
const { selectedModelId, selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
return normalizeApiConfiguration(apiConfiguration, currentMode)
}, [apiConfiguration, currentMode])
useMount(refreshOpenRouterModels)
// Sync external changes only when user isn't actively typing
useEffect(() => {
if (!isSearchInputDirty) {
const currentModelId = apiConfiguration?.openRouterModelId || openRouterDefaultModelId
const currentModelId = modeFields.openRouterModelId || openRouterDefaultModelId
setSearchTerm(currentModelId)
}
}, [apiConfiguration?.openRouterModelId, isSearchInputDirty])
}, [modeFields.openRouterModelId, isSearchInputDirty])
// Reset dirty flag after user stops typing (1 second timeout)
useEffect(() => {
@@ -121,10 +131,8 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
const modelIds = useMemo(() => {
const unfilteredModelIds = Object.keys(openRouterModels).sort((a, b) => a.localeCompare(b))
return apiConfiguration?.apiProvider === "cline"
? unfilteredModelIds.filter((id) => !id.includes(":free"))
: unfilteredModelIds
}, [openRouterModels, apiConfiguration?.apiProvider])
return modeFields.apiProvider === "cline" ? unfilteredModelIds.filter((id) => !id.includes(":free")) : unfilteredModelIds
}, [openRouterModels, modeFields.apiProvider])
const searchableItems = useMemo(() => {
return modelIds.map((id) => ({
@@ -237,7 +245,7 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
<span style={{ fontWeight: 500 }}>Model</span>
</label>
{apiConfiguration?.apiProvider === "cline" && (
{modeFields.apiProvider === "cline" && (
<div style={{ marginBottom: "6px", marginTop: 4 }}>
{featuredModels.map((model) => (
<FeaturedModelCard
@@ -326,7 +334,7 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
{hasInfo ? (
<>
{showBudgetSlider && <ThinkingBudgetSlider />}
{showBudgetSlider && <ThinkingBudgetSlider currentMode={currentMode} />}
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
</>
@@ -11,18 +11,21 @@ import { ModelsServiceClient } from "../../services/grpc-client"
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
import { highlight } from "../history/HistoryView"
import { ModelInfoView } from "./common/ModelInfoView"
import { normalizeApiConfiguration } from "./utils/providerUtils"
import { getModeSpecificFields, normalizeApiConfiguration } from "./utils/providerUtils"
import ThinkingBudgetSlider from "./ThinkingBudgetSlider"
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
export interface RequestyModelPickerProps {
isPopup?: boolean
currentMode: Mode
}
const RequestyModelPicker: React.FC<RequestyModelPickerProps> = ({ isPopup }) => {
const RequestyModelPicker: React.FC<RequestyModelPickerProps> = ({ isPopup, currentMode }) => {
const { apiConfiguration, requestyModels, setRequestyModels } = useExtensionState()
const { handleFieldsChange } = useApiConfigurationHandlers()
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.requestyModelId || requestyDefaultModelId)
const { handleModeFieldsChange } = useApiConfigurationHandlers()
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
const [searchTerm, setSearchTerm] = useState(modeFields.requestyModelId || requestyDefaultModelId)
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(-1)
const dropdownRef = useRef<HTMLDivElement>(null)
@@ -32,16 +35,23 @@ const RequestyModelPicker: React.FC<RequestyModelPickerProps> = ({ isPopup }) =>
const handleModelChange = (newModelId: string) => {
// could be setting invalid model id/undefined info but validation will catch it
handleFieldsChange({
requestyModelId: newModelId,
requestyModelInfo: requestyModels[newModelId],
})
handleModeFieldsChange(
{
requestyModelId: { plan: "planModeRequestyModelId", act: "actModeRequestyModelId" },
requestyModelInfo: { plan: "planModeRequestyModelInfo", act: "actModeRequestyModelInfo" },
},
{
requestyModelId: newModelId,
requestyModelInfo: requestyModels[newModelId],
},
currentMode,
)
setSearchTerm(newModelId)
}
const { selectedModelId, selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
return normalizeApiConfiguration(apiConfiguration, currentMode)
}, [apiConfiguration, currentMode])
useMount(() => {
ModelsServiceClient.refreshRequestyModels(EmptyRequest.create({}))
@@ -226,7 +236,7 @@ const RequestyModelPicker: React.FC<RequestyModelPickerProps> = ({ isPopup }) =>
{hasInfo ? (
<>
{showBudgetSlider && <ThinkingBudgetSlider />}
{showBudgetSlider && <ThinkingBudgetSlider currentMode={currentMode} />}
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
</>
) : (
@@ -103,7 +103,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
// Track active tab
const [activeTab, setActiveTab] = useState<string>(targetSection || SETTINGS_TABS[0].id)
// Track if we're currently switching modes
const [isSwitchingMode, setIsSwitchingMode] = useState(false)
const { version, chatSettings } = useExtensionState()
@@ -158,34 +157,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
}
}
const handlePlanActModeChange = async (tab: "plan" | "act") => {
// Prevent switching if already in that mode or if currently switching
if (tab === chatSettings.mode || isSwitchingMode) {
return
}
// All settings save immediately, so we can switch modes directly
setIsSwitchingMode(true)
try {
// Perform the mode switch
await StateServiceClient.togglePlanActMode(
TogglePlanActModeRequest.create({
chatSettings: {
mode: tab === "plan" ? PlanActMode.PLAN : PlanActMode.ACT,
preferredLanguage: chatSettings.preferredLanguage,
openAiReasoningEffort: chatSettings.openAIReasoningEffort,
},
}),
)
} catch (error) {
console.error("Failed to toggle Plan/Act mode:", error)
} finally {
// Always re-enable mode switching, even on error
setIsSwitchingMode(false)
}
}
// Update active tab when targetSection changes
useEffect(() => {
if (targetSection) {
@@ -313,13 +284,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
return (
<TabContent className="flex-1 overflow-auto">
{/* API Configuration Tab */}
{activeTab === "api-config" && (
<ApiConfigurationSection
isSwitchingMode={isSwitchingMode}
handlePlanActModeChange={handlePlanActModeChange}
renderSectionHeader={renderSectionHeader}
/>
)}
{activeTab === "api-config" && <ApiConfigurationSection renderSectionHeader={renderSectionHeader} />}
{/* General Settings Tab */}
{activeTab === "general" && <GeneralSettingsSection renderSectionHeader={renderSectionHeader} />}
@@ -4,6 +4,8 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import styled from "styled-components"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
import { getModeSpecificFields } from "./utils/providerUtils"
import { Mode } from "@shared/ChatSettings"
// Constants
const DEFAULT_MIN_VALID_TOKENS = 1024
@@ -83,20 +85,23 @@ const RangeInput = styled.input<{ $value: number; $min: number; $max: number }>`
interface ThinkingBudgetSliderProps {
maxBudget?: number
currentMode: Mode
}
const ThinkingBudgetSlider = ({ maxBudget }: ThinkingBudgetSliderProps) => {
const ThinkingBudgetSlider = ({ maxBudget, currentMode }: ThinkingBudgetSliderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleModeFieldChange } = useApiConfigurationHandlers()
const [isEnabled, setIsEnabled] = useState<boolean>((apiConfiguration?.thinkingBudgetTokens || 0) > 0)
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
const [isEnabled, setIsEnabled] = useState<boolean>((modeFields.thinkingBudgetTokens || 0) > 0)
const maxTokens = useMemo(
() =>
apiConfiguration?.apiProvider === "gemini"
modeFields.apiProvider === "gemini"
? geminiModels[geminiDefaultModelId].maxTokens
: anthropicModels["claude-3-7-sonnet-20250219"].maxTokens,
[apiConfiguration?.apiProvider],
[modeFields.apiProvider],
)
// use maxBudget prop if provided, otherwise apply the percentage cap to maxTokens
@@ -108,7 +113,7 @@ const ThinkingBudgetSlider = ({ maxBudget }: ThinkingBudgetSliderProps) => {
}, [maxBudget, maxTokens])
// Add local state for the slider value
const [localValue, setLocalValue] = useState(apiConfiguration?.thinkingBudgetTokens || 0)
const [localValue, setLocalValue] = useState(modeFields.thinkingBudgetTokens || 0)
const handleSliderChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(event.target.value, 10)
@@ -116,7 +121,11 @@ const ThinkingBudgetSlider = ({ maxBudget }: ThinkingBudgetSliderProps) => {
}, [])
const handleSliderComplete = () => {
handleFieldChange("thinkingBudgetTokens", localValue)
handleModeFieldChange(
{ plan: "planModeThinkingBudgetTokens", act: "actModeThinkingBudgetTokens" },
localValue,
currentMode,
)
}
const handleToggleChange = (event: any) => {
@@ -125,7 +134,7 @@ const ThinkingBudgetSlider = ({ maxBudget }: ThinkingBudgetSliderProps) => {
setIsEnabled(isChecked)
setLocalValue(newValue)
handleFieldChange("thinkingBudgetTokens", newValue)
handleModeFieldChange({ plan: "planModeThinkingBudgetTokens", act: "actModeThinkingBudgetTokens" }, newValue, currentMode)
}
return (
@@ -11,13 +11,16 @@ vi.mock("../../../context/ExtensionStateContext", async (importOriginal) => {
// your mocked methods
useExtensionState: vi.fn(() => ({
apiConfiguration: {
apiProvider: "requesty",
planModeApiProvider: "requesty",
actModeApiProvider: "requesty",
requestyApiKey: "",
requestyModelId: "",
planModeRequestyModelId: "",
actModeRequestyModelId: "",
},
setApiConfiguration: vi.fn(),
uriScheme: "vscode",
requestyModels: {},
planActSeparateModelsSetting: false,
})),
}
})
@@ -28,6 +31,7 @@ const mockExtensionState = (apiConfiguration: Partial<ApiConfiguration>) => {
setApiConfiguration: vi.fn(),
uriScheme: "vscode",
requestyModels: {},
planActSeparateModelsSetting: false,
} as any)
}
@@ -39,14 +43,15 @@ describe("ApiOptions Component", () => {
//@ts-expect-error - vscode is not defined in the global namespace in test environment
global.vscode = { postMessage: mockPostMessage }
mockExtensionState({
apiProvider: "requesty",
planModeApiProvider: "requesty",
actModeApiProvider: "requesty",
})
})
it("renders Requesty API Key input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const apiKeyInput = screen.getByPlaceholderText("Enter API Key...")
@@ -56,7 +61,7 @@ describe("ApiOptions Component", () => {
it("renders Requesty Model ID input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const modelIdInput = screen.getByPlaceholderText("Search and select a model...")
@@ -72,14 +77,15 @@ describe("ApiOptions Component", () => {
//@ts-expect-error - vscode is not defined in the global namespace in test environment
global.vscode = { postMessage: mockPostMessage }
mockExtensionState({
apiProvider: "together",
planModeApiProvider: "together",
actModeApiProvider: "together",
})
})
it("renders Together API Key input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const apiKeyInput = screen.getByPlaceholderText("Enter API Key...")
@@ -89,7 +95,7 @@ describe("ApiOptions Component", () => {
it("renders Together Model ID input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const modelIdInput = screen.getByPlaceholderText("Enter Model ID...")
@@ -106,9 +112,11 @@ describe("ApiOptions Component", () => {
global.vscode = { postMessage: mockPostMessage }
mockExtensionState({
apiProvider: "fireworks",
planModeApiProvider: "fireworks",
actModeApiProvider: "fireworks",
fireworksApiKey: "",
fireworksModelId: "",
planModeFireworksModelId: "",
actModeFireworksModelId: "",
fireworksModelMaxCompletionTokens: 2000,
fireworksModelMaxTokens: 4000,
})
@@ -117,7 +125,7 @@ describe("ApiOptions Component", () => {
it("renders Fireworks API Key input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const apiKeyInput = screen.getByPlaceholderText("Enter API Key...")
@@ -127,7 +135,7 @@ describe("ApiOptions Component", () => {
it("renders Fireworks Model ID input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const modelIdInput = screen.getByPlaceholderText("Enter Model ID...")
@@ -137,7 +145,7 @@ describe("ApiOptions Component", () => {
it("renders Fireworks Max Completion Tokens input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const maxCompletionTokensInput = screen.getByPlaceholderText("2000")
@@ -147,7 +155,7 @@ describe("ApiOptions Component", () => {
it("renders Fireworks Max Tokens input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const maxTokensInput = screen.getByPlaceholderText("4000")
@@ -163,14 +171,15 @@ describe("OpenApiInfoOptions", () => {
//@ts-expect-error - vscode is not defined in the global namespace in test environment
global.vscode = { postMessage: mockPostMessage }
mockExtensionState({
apiProvider: "openai",
planModeApiProvider: "openai",
actModeApiProvider: "openai",
})
})
it("renders OpenAI Supports Images input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
fireEvent.click(screen.getByText("Model Configuration"))
@@ -181,7 +190,7 @@ describe("OpenApiInfoOptions", () => {
it("renders OpenAI Context Window Size input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
fireEvent.click(screen.getByText("Model Configuration"))
@@ -192,7 +201,7 @@ describe("OpenApiInfoOptions", () => {
it("renders OpenAI Max Output Tokens input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
fireEvent.click(screen.getByText("Model Configuration"))
@@ -210,7 +219,8 @@ describe("ApiOptions Component", () => {
global.vscode = { postMessage: mockPostMessage }
mockExtensionState({
apiProvider: "nebius",
planModeApiProvider: "nebius",
actModeApiProvider: "nebius",
nebiusApiKey: "",
})
})
@@ -218,7 +228,7 @@ describe("ApiOptions Component", () => {
it("renders Nebius API Key input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const apiKeyInput = screen.getByPlaceholderText("Enter API Key...")
@@ -228,7 +238,7 @@ describe("ApiOptions Component", () => {
it("renders Nebius Model ID select with a default model", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode="plan" />
</ExtensionStateContextProvider>,
)
const modelIdSelect = screen.getByLabelText("Model")
@@ -3,10 +3,11 @@ import { ApiKeyField } from "../common/ApiKeyField"
import { BaseUrlField } from "../common/BaseUrlField"
import { ModelSelector } from "../common/ModelSelector"
import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { getModeSpecificFields, normalizeApiConfiguration } from "../utils/providerUtils"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
// Anthropic models that support thinking/reasoning mode
export const SUPPORTED_ANTHROPIC_THINKING_MODELS = [
@@ -21,17 +22,18 @@ export const SUPPORTED_ANTHROPIC_THINKING_MODELS = [
interface AnthropicProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Anthropic provider configuration component
*/
export const AnthropicProvider = ({ showModelOptions, isPopup }: AnthropicProviderProps) => {
export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: AnthropicProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -54,12 +56,18 @@ export const AnthropicProvider = ({ showModelOptions, isPopup }: AnthropicProvid
<ModelSelector
models={anthropicModels}
selectedModelId={selectedModelId}
onChange={(e) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
{SUPPORTED_ANTHROPIC_THINKING_MODELS.includes(selectedModelId) && (
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} />
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} currentMode={currentMode} />
)}
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
@@ -6,6 +6,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the AskSageProvider component
@@ -13,17 +14,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface AskSageProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The AskSage provider configuration component
*/
export const AskSageProvider = ({ showModelOptions, isPopup }: AskSageProviderProps) => {
export const AskSageProvider = ({ showModelOptions, isPopup, currentMode }: AskSageProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -48,7 +50,13 @@ export const AskSageProvider = ({ showModelOptions, isPopup }: AskSageProviderPr
<ModelSelector
models={askSageModels}
selectedModelId={selectedModelId}
onChange={(e) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -5,9 +5,10 @@ import { DebouncedTextField } from "../common/DebouncedTextField"
import { ModelInfoView } from "../common/ModelInfoView"
import { DropdownContainer } from "../common/ModelSelector"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { normalizeApiConfiguration, getModeSpecificFields } from "../utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
// Z-index constants for proper dropdown layering
const DROPDOWN_Z_INDEX = 1000
@@ -15,13 +16,15 @@ const DROPDOWN_Z_INDEX = 1000
interface BedrockProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
export const BedrockProvider = ({ showModelOptions, isPopup }: BedrockProviderProps) => {
export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: BedrockProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleFieldsChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleFieldsChange, handleModeFieldChange, handleModeFieldsChange } = useApiConfigurationHandlers()
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpoint)
return (
@@ -207,15 +210,29 @@ export const BedrockProvider = ({ showModelOptions, isPopup }: BedrockProviderPr
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 2} className="dropdown-container">
<VSCodeDropdown
id="bedrock-model-dropdown"
value={apiConfiguration?.awsBedrockCustomSelected ? "custom" : selectedModelId}
value={modeFields.awsBedrockCustomSelected ? "custom" : selectedModelId}
onChange={(e: any) => {
const isCustom = e.target.value === "custom"
handleFieldsChange({
apiModelId: isCustom ? "" : e.target.value,
awsBedrockCustomSelected: isCustom,
awsBedrockCustomModelBaseId: bedrockDefaultModelId,
})
handleModeFieldsChange(
{
apiModelId: { plan: "planModeApiModelId", act: "actModeApiModelId" },
awsBedrockCustomSelected: {
plan: "planModeAwsBedrockCustomSelected",
act: "actModeAwsBedrockCustomSelected",
},
awsBedrockCustomModelBaseId: {
plan: "planModeAwsBedrockCustomModelBaseId",
act: "actModeAwsBedrockCustomModelBaseId",
},
},
{
apiModelId: isCustom ? "" : e.target.value,
awsBedrockCustomSelected: isCustom,
awsBedrockCustomModelBaseId: bedrockDefaultModelId,
},
currentMode,
)
}}
style={{ width: "100%" }}>
<VSCodeOption value="">Select a model...</VSCodeOption>
@@ -235,7 +252,7 @@ export const BedrockProvider = ({ showModelOptions, isPopup }: BedrockProviderPr
</VSCodeDropdown>
</DropdownContainer>
{apiConfiguration?.awsBedrockCustomSelected && (
{modeFields.awsBedrockCustomSelected && (
<div>
<p
style={{
@@ -248,8 +265,14 @@ export const BedrockProvider = ({ showModelOptions, isPopup }: BedrockProviderPr
</p>
<DebouncedTextField
id="bedrock-model-input"
initialValue={apiConfiguration?.apiModelId || ""}
onChange={(value) => handleFieldChange("apiModelId", value)}
initialValue={modeFields.apiModelId || ""}
onChange={(value) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
value,
currentMode,
)
}
style={{ width: "100%", marginTop: 3 }}
placeholder="Enter custom model ID...">
<span style={{ fontWeight: 500 }}>Model ID</span>
@@ -260,8 +283,17 @@ export const BedrockProvider = ({ showModelOptions, isPopup }: BedrockProviderPr
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 3} className="dropdown-container">
<VSCodeDropdown
id="bedrock-base-model-dropdown"
value={apiConfiguration?.awsBedrockCustomModelBaseId || bedrockDefaultModelId}
onChange={(e: any) => handleFieldChange("awsBedrockCustomModelBaseId", e.target.value)}
value={modeFields.awsBedrockCustomModelBaseId || bedrockDefaultModelId}
onChange={(e: any) =>
handleModeFieldChange(
{
plan: "planModeAwsBedrockCustomModelBaseId",
act: "actModeAwsBedrockCustomModelBaseId",
},
e.target.value,
currentMode,
)
}
style={{ width: "100%" }}>
<VSCodeOption value="">Select a model...</VSCodeOption>
{Object.keys(bedrockModels).map((modelId) => (
@@ -284,13 +316,13 @@ export const BedrockProvider = ({ showModelOptions, isPopup }: BedrockProviderPr
{(selectedModelId === "anthropic.claude-3-7-sonnet-20250219-v1:0" ||
selectedModelId === "anthropic.claude-sonnet-4-20250514-v1:0" ||
selectedModelId === "anthropic.claude-opus-4-20250514-v1:0" ||
(apiConfiguration?.awsBedrockCustomSelected &&
apiConfiguration?.awsBedrockCustomModelBaseId === "anthropic.claude-3-7-sonnet-20250219-v1:0") ||
(apiConfiguration?.awsBedrockCustomSelected &&
apiConfiguration?.awsBedrockCustomModelBaseId === "anthropic.claude-sonnet-4-20250514-v1:0") ||
(apiConfiguration?.awsBedrockCustomSelected &&
apiConfiguration?.awsBedrockCustomModelBaseId === "anthropic.claude-opus-4-20250514-v1:0")) && (
<ThinkingBudgetSlider />
(modeFields.awsBedrockCustomSelected &&
modeFields.awsBedrockCustomModelBaseId === "anthropic.claude-3-7-sonnet-20250219-v1:0") ||
(modeFields.awsBedrockCustomSelected &&
modeFields.awsBedrockCustomModelBaseId === "anthropic.claude-sonnet-4-20250514-v1:0") ||
(modeFields.awsBedrockCustomSelected &&
modeFields.awsBedrockCustomModelBaseId === "anthropic.claude-opus-4-20250514-v1:0")) && (
<ThinkingBudgetSlider currentMode={currentMode} />
)}
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
@@ -5,6 +5,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the CerebrasProvider component
@@ -12,17 +13,18 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface CerebrasProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Cerebras provider configuration component
*/
export const CerebrasProvider = ({ showModelOptions, isPopup }: CerebrasProviderProps) => {
export const CerebrasProvider = ({ showModelOptions, isPopup, currentMode }: CerebrasProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -38,7 +40,13 @@ export const CerebrasProvider = ({ showModelOptions, isPopup }: CerebrasProvider
<ModelSelector
models={cerebrasModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -7,6 +7,7 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
import { useExtensionState } from "@/context/ExtensionStateContext"
import { SUPPORTED_ANTHROPIC_THINKING_MODELS } from "./AnthropicProvider"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the ClaudeCodeProvider component
@@ -14,17 +15,18 @@ import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
interface ClaudeCodeProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Claude Code provider configuration component
*/
export const ClaudeCodeProvider = ({ showModelOptions, isPopup }: ClaudeCodeProviderProps) => {
export const ClaudeCodeProvider = ({ showModelOptions, isPopup, currentMode }: ClaudeCodeProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -51,12 +53,18 @@ export const ClaudeCodeProvider = ({ showModelOptions, isPopup }: ClaudeCodeProv
<ModelSelector
models={claudeCodeModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
{SUPPORTED_ANTHROPIC_THINKING_MODELS.includes(selectedModelId) && (
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} />
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} currentMode={currentMode} />
)}
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
@@ -6,6 +6,7 @@ import OpenRouterModelPicker, { OPENROUTER_MODEL_PICKER_Z_INDEX } from "../OpenR
import { DropdownContainer } from "../common/ModelSelector"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the ClineProvider component
@@ -13,12 +14,13 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface ClineProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Cline provider configuration component
*/
export const ClineProvider = ({ showModelOptions, isPopup }: ClineProviderProps) => {
export const ClineProvider = ({ showModelOptions, isPopup, currentMode }: ClineProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
@@ -76,7 +78,7 @@ export const ClineProvider = ({ showModelOptions, isPopup }: ClineProviderProps)
)}
{/* OpenRouter Model Picker */}
<OpenRouterModelPicker isPopup={isPopup} />
<OpenRouterModelPicker isPopup={isPopup} currentMode={currentMode} />
</>
)}
</div>
@@ -5,6 +5,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the DeepSeekProvider component
@@ -12,17 +13,18 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface DeepSeekProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The DeepSeek provider configuration component
*/
export const DeepSeekProvider = ({ showModelOptions, isPopup }: DeepSeekProviderProps) => {
export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: DeepSeekProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -38,7 +40,13 @@ export const DeepSeekProvider = ({ showModelOptions, isPopup }: DeepSeekProvider
<ModelSelector
models={deepSeekModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -5,6 +5,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the DoubaoProvider component
@@ -12,17 +13,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface DoubaoProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The ByteDance Doubao provider configuration component
*/
export const DoubaoProvider = ({ showModelOptions, isPopup }: DoubaoProviderProps) => {
export const DoubaoProvider = ({ showModelOptions, isPopup, currentMode }: DoubaoProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -38,7 +40,13 @@ export const DoubaoProvider = ({ showModelOptions, isPopup }: DoubaoProviderProp
<ModelSelector
models={doubaoModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -2,7 +2,9 @@ import { ApiKeyField } from "../common/ApiKeyField"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { getModeSpecificFields } from "../utils/providerUtils"
import { ApiConfiguration } from "@shared/api"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the FireworksProvider component
@@ -10,14 +12,17 @@ import { ApiConfiguration } from "@shared/api"
interface FireworksProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Fireworks provider configuration component
*/
export const FireworksProvider = ({ showModelOptions, isPopup }: FireworksProviderProps) => {
export const FireworksProvider = ({ showModelOptions, isPopup, currentMode }: FireworksProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleModeFieldChange, handleFieldChange } = useApiConfigurationHandlers()
const { fireworksModelId } = getModeSpecificFields(apiConfiguration, currentMode)
// Handler for number input fields with validation
const handleNumberInputChange = (field: keyof ApiConfiguration, value: string) => {
@@ -43,8 +48,14 @@ export const FireworksProvider = ({ showModelOptions, isPopup }: FireworksProvid
{showModelOptions && (
<>
<DebouncedTextField
initialValue={apiConfiguration?.fireworksModelId || ""}
onChange={(value) => handleFieldChange("fireworksModelId", value)}
initialValue={fireworksModelId || ""}
onChange={(value) =>
handleModeFieldChange(
{ plan: "planModeFireworksModelId", act: "actModeFireworksModelId" },
value,
currentMode,
)
}
style={{ width: "100%" }}
placeholder={"Enter Model ID..."}>
<span style={{ fontWeight: 500 }}>Model ID</span>
@@ -7,6 +7,7 @@ import { normalizeApiConfiguration } from "../utils/providerUtils"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Mode } from "@shared/ChatSettings"
// Gemini models that support thinking/reasoning mode
const SUPPORTED_THINKING_MODELS = ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite-preview-06-17"]
@@ -17,17 +18,18 @@ const SUPPORTED_THINKING_MODELS = ["gemini-2.5-pro", "gemini-2.5-flash", "gemini
interface GeminiProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Gemini provider configuration component
*/
export const GeminiProvider = ({ showModelOptions, isPopup }: GeminiProviderProps) => {
export const GeminiProvider = ({ showModelOptions, isPopup, currentMode }: GeminiProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -50,12 +52,18 @@ export const GeminiProvider = ({ showModelOptions, isPopup }: GeminiProviderProp
<ModelSelector
models={geminiModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
{SUPPORTED_THINKING_MODELS.includes(selectedModelId) && (
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} />
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} currentMode={currentMode} />
)}
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
@@ -1,3 +1,4 @@
import { Mode } from "@shared/ChatSettings"
import { ApiKeyField } from "../common/ApiKeyField"
import GroqModelPicker from "../GroqModelPicker"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
@@ -9,12 +10,13 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface GroqProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Groq provider configuration component
*/
export const GroqProvider = ({ showModelOptions, isPopup }: GroqProviderProps) => {
export const GroqProvider = ({ showModelOptions, isPopup, currentMode }: GroqProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
@@ -27,7 +29,7 @@ export const GroqProvider = ({ showModelOptions, isPopup }: GroqProviderProps) =
signupUrl="https://console.groq.com/keys"
/>
{showModelOptions && <GroqModelPicker isPopup={isPopup} />}
{showModelOptions && <GroqModelPicker isPopup={isPopup} currentMode={currentMode} />}
</div>
)
}
@@ -1,4 +1,5 @@
import { huggingFaceModels } from "@shared/api"
import { Mode } from "@shared/ChatSettings"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { ModelSelector } from "../common/ModelSelector"
import { ModelInfoView } from "../common/ModelInfoView"
@@ -13,17 +14,18 @@ import { HuggingFaceModelPicker } from "../HuggingFaceModelPicker"
interface HuggingFaceProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Hugging Face provider configuration component
*/
export const HuggingFaceProvider = ({ showModelOptions, isPopup }: HuggingFaceProviderProps) => {
export const HuggingFaceProvider = ({ showModelOptions, isPopup, currentMode }: HuggingFaceProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -50,7 +52,7 @@ export const HuggingFaceProvider = ({ showModelOptions, isPopup }: HuggingFacePr
{showModelOptions && (
<>
<HuggingFaceModelPicker isPopup={isPopup} />
<HuggingFaceModelPicker isPopup={isPopup} currentMode={currentMode} />
</>
)}
</div>
@@ -7,6 +7,8 @@ import { StringRequest } from "@shared/proto/common"
import { BaseUrlField } from "../common/BaseUrlField"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { getModeSpecificFields } from "../utils/providerUtils"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the LMStudioProvider component
@@ -14,14 +16,17 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface LMStudioProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The LM Studio provider configuration component
*/
export const LMStudioProvider = ({ showModelOptions, isPopup }: LMStudioProviderProps) => {
export const LMStudioProvider = ({ showModelOptions, isPopup, currentMode }: LMStudioProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
const { lmStudioModelId } = getModeSpecificFields(apiConfiguration, currentMode)
const [lmStudioModels, setLmStudioModels] = useState<string[]>([])
@@ -58,8 +63,10 @@ export const LMStudioProvider = ({ showModelOptions, isPopup }: LMStudioProvider
/>
<DebouncedTextField
initialValue={apiConfiguration?.lmStudioModelId || ""}
onChange={(value) => handleFieldChange("lmStudioModelId", value)}
initialValue={lmStudioModelId || ""}
onChange={(value) =>
handleModeFieldChange({ plan: "planModeLmStudioModelId", act: "actModeLmStudioModelId" }, value, currentMode)
}
style={{ width: "100%" }}
placeholder={"e.g. meta-llama-3.1-8b-instruct"}>
<span style={{ fontWeight: 500 }}>Model ID</span>
@@ -67,18 +74,20 @@ export const LMStudioProvider = ({ showModelOptions, isPopup }: LMStudioProvider
{lmStudioModels.length > 0 && (
<VSCodeRadioGroup
value={
lmStudioModels.includes(apiConfiguration?.lmStudioModelId || "") ? apiConfiguration?.lmStudioModelId : ""
}
value={lmStudioModels.includes(lmStudioModelId || "") ? lmStudioModelId : ""}
onChange={(e) => {
const value = (e.target as HTMLInputElement)?.value
// need to check value first since radio group returns empty string sometimes
if (value) {
handleFieldChange("lmStudioModelId", value)
handleModeFieldChange(
{ plan: "planModeLmStudioModelId", act: "actModeLmStudioModelId" },
value,
currentMode,
)
}
}}>
{lmStudioModels.map((model) => (
<VSCodeRadio key={model} value={model} checked={apiConfiguration?.lmStudioModelId === model}>
<VSCodeRadio key={model} value={model} checked={lmStudioModelId === model}>
{model}
</VSCodeRadio>
))}
@@ -3,11 +3,12 @@ import { liteLlmModelInfoSaneDefaults } from "@shared/api"
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { getAsVar, VSC_DESCRIPTION_FOREGROUND } from "@/utils/vscStyles"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { normalizeApiConfiguration, getModeSpecificFields } from "../utils/providerUtils"
import { ModelInfoView } from "../common/ModelInfoView"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the LiteLlmProvider component
@@ -15,17 +16,21 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface LiteLlmProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The LiteLLM provider configuration component
*/
export const LiteLlmProvider = ({ showModelOptions, isPopup }: LiteLlmProviderProps) => {
export const LiteLlmProvider = ({ showModelOptions, isPopup, currentMode }: LiteLlmProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
// Get mode-specific fields
const { liteLlmModelId, liteLlmModelInfo } = getModeSpecificFields(apiConfiguration, currentMode)
// Local state for collapsible model configuration section
const [modelConfigurationSelected, setModelConfigurationSelected] = useState(false)
@@ -49,8 +54,10 @@ export const LiteLlmProvider = ({ showModelOptions, isPopup }: LiteLlmProviderPr
<span style={{ fontWeight: 500 }}>API Key</span>
</DebouncedTextField>
<DebouncedTextField
initialValue={apiConfiguration?.liteLlmModelId || ""}
onChange={(value) => handleFieldChange("liteLlmModelId", value)}
initialValue={liteLlmModelId || ""}
onChange={(value) =>
handleModeFieldChange({ plan: "planModeLiteLlmModelId", act: "actModeLiteLlmModelId" }, value, currentMode)
}
style={{ width: "100%" }}
placeholder={"e.g. anthropic/claude-sonnet-4-20250514"}>
<span style={{ fontWeight: 500 }}>Model ID</span>
@@ -77,7 +84,7 @@ export const LiteLlmProvider = ({ showModelOptions, isPopup }: LiteLlmProviderPr
</div>
<>
<ThinkingBudgetSlider />
<ThinkingBudgetSlider currentMode={currentMode} />
<p
style={{
fontSize: "12px",
@@ -118,50 +125,56 @@ export const LiteLlmProvider = ({ showModelOptions, isPopup }: LiteLlmProviderPr
{modelConfigurationSelected && (
<>
<VSCodeCheckbox
checked={!!apiConfiguration?.liteLlmModelInfo?.supportsImages}
checked={!!liteLlmModelInfo?.supportsImages}
onChange={(e: any) => {
const isChecked = e.target.checked === true
const modelInfo = apiConfiguration?.liteLlmModelInfo
? apiConfiguration.liteLlmModelInfo
: { ...liteLlmModelInfoSaneDefaults }
const modelInfo = liteLlmModelInfo ? liteLlmModelInfo : { ...liteLlmModelInfoSaneDefaults }
modelInfo.supportsImages = isChecked
handleFieldChange("liteLlmModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeLiteLlmModelInfo", act: "actModeLiteLlmModelInfo" },
modelInfo,
currentMode,
)
}}>
Supports Images
</VSCodeCheckbox>
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<DebouncedTextField
initialValue={
apiConfiguration?.liteLlmModelInfo?.contextWindow
? apiConfiguration.liteLlmModelInfo.contextWindow.toString()
liteLlmModelInfo?.contextWindow
? liteLlmModelInfo.contextWindow.toString()
: (liteLlmModelInfoSaneDefaults.contextWindow?.toString() ?? "")
}
style={{ flex: 1 }}
onChange={(value) => {
const modelInfo = apiConfiguration?.liteLlmModelInfo
? apiConfiguration.liteLlmModelInfo
: { ...liteLlmModelInfoSaneDefaults }
const modelInfo = liteLlmModelInfo ? liteLlmModelInfo : { ...liteLlmModelInfoSaneDefaults }
modelInfo.contextWindow = Number(value)
handleFieldChange("liteLlmModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeLiteLlmModelInfo", act: "actModeLiteLlmModelInfo" },
modelInfo,
currentMode,
)
}}>
<span style={{ fontWeight: 500 }}>Context Window Size</span>
</DebouncedTextField>
<DebouncedTextField
initialValue={
apiConfiguration?.liteLlmModelInfo?.maxTokens
? apiConfiguration.liteLlmModelInfo.maxTokens.toString()
liteLlmModelInfo?.maxTokens
? liteLlmModelInfo.maxTokens.toString()
: (liteLlmModelInfoSaneDefaults.maxTokens?.toString() ?? "")
}
style={{ flex: 1 }}
onChange={(value) => {
const modelInfo = apiConfiguration?.liteLlmModelInfo
? apiConfiguration.liteLlmModelInfo
: { ...liteLlmModelInfoSaneDefaults }
const modelInfo = liteLlmModelInfo ? liteLlmModelInfo : { ...liteLlmModelInfoSaneDefaults }
modelInfo.maxTokens = Number(value)
handleFieldChange("liteLlmModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeLiteLlmModelInfo", act: "actModeLiteLlmModelInfo" },
modelInfo,
currentMode,
)
}}>
<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
</DebouncedTextField>
@@ -169,14 +182,12 @@ export const LiteLlmProvider = ({ showModelOptions, isPopup }: LiteLlmProviderPr
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<DebouncedTextField
initialValue={
apiConfiguration?.liteLlmModelInfo?.temperature !== undefined
? apiConfiguration.liteLlmModelInfo.temperature.toString()
liteLlmModelInfo?.temperature !== undefined
? liteLlmModelInfo.temperature.toString()
: (liteLlmModelInfoSaneDefaults.temperature?.toString() ?? "")
}
onChange={(value) => {
const modelInfo = apiConfiguration?.liteLlmModelInfo
? apiConfiguration.liteLlmModelInfo
: { ...liteLlmModelInfoSaneDefaults }
const modelInfo = liteLlmModelInfo ? liteLlmModelInfo : { ...liteLlmModelInfoSaneDefaults }
// Check if the input ends with a decimal point or has trailing zeros after decimal
const shouldPreserveFormat = value.endsWith(".") || (value.includes(".") && value.endsWith("0"))
@@ -184,7 +195,11 @@ export const LiteLlmProvider = ({ showModelOptions, isPopup }: LiteLlmProviderPr
modelInfo.temperature =
value === "" ? liteLlmModelInfoSaneDefaults.temperature : parseFloat(value)
handleFieldChange("liteLlmModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeLiteLlmModelInfo", act: "actModeLiteLlmModelInfo" },
modelInfo,
currentMode,
)
}}>
<span style={{ fontWeight: 500 }}>Temperature</span>
</DebouncedTextField>
@@ -5,6 +5,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the MistralProvider component
@@ -12,17 +13,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface MistralProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Mistral provider configuration component
*/
export const MistralProvider = ({ showModelOptions, isPopup }: MistralProviderProps) => {
export const MistralProvider = ({ showModelOptions, isPopup, currentMode }: MistralProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -38,7 +40,13 @@ export const MistralProvider = ({ showModelOptions, isPopup }: MistralProviderPr
<ModelSelector
models={mistralModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -7,6 +7,7 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useState } from "react"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the MoonshotProvider component
@@ -14,20 +15,21 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
interface MoonshotProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Moonshot AI Studio provider configuration component
*/
export const MoonshotProvider = ({ showModelOptions, isPopup }: MoonshotProviderProps) => {
export const MoonshotProvider = ({ showModelOptions, isPopup, currentMode }: MoonshotProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Local state for Chinese API endpoint checkbox
const [isChineseEndpoint, setIsChineseEndpoint] = useState(!!apiConfiguration?.moonshotApiLine)
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
const handleChineseEndpointToggle = (e: any) => {
const checked = e.target.checked === true
@@ -57,7 +59,13 @@ export const MoonshotProvider = ({ showModelOptions, isPopup }: MoonshotProvider
<ModelSelector
models={moonshotModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -5,6 +5,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the NebiusProvider component
@@ -12,17 +13,17 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface NebiusProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Nebius AI Studio provider configuration component
*/
export const NebiusProvider = ({ showModelOptions, isPopup }: NebiusProviderProps) => {
export const NebiusProvider = ({ showModelOptions, isPopup, currentMode }: NebiusProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -39,7 +40,13 @@ export const NebiusProvider = ({ showModelOptions, isPopup }: NebiusProviderProp
<ModelSelector
models={nebiusModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -8,6 +8,8 @@ import OllamaModelPicker from "../OllamaModelPicker"
import { BaseUrlField } from "../common/BaseUrlField"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { getModeSpecificFields } from "../utils/providerUtils"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the OllamaProvider component
@@ -15,14 +17,17 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface OllamaProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Ollama provider configuration component
*/
export const OllamaProvider = ({ showModelOptions, isPopup }: OllamaProviderProps) => {
export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: OllamaProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
const { ollamaModelId } = getModeSpecificFields(apiConfiguration, currentMode)
const [ollamaModels, setOllamaModels] = useState<string[]>([])
@@ -64,9 +69,9 @@ export const OllamaProvider = ({ showModelOptions, isPopup }: OllamaProviderProp
</label>
<OllamaModelPicker
ollamaModels={ollamaModels}
selectedModelId={apiConfiguration?.ollamaModelId || ""}
selectedModelId={ollamaModelId || ""}
onModelChange={(modelId) => {
handleFieldChange("ollamaModelId", modelId)
handleModeFieldChange({ plan: "planModeOllamaModelId", act: "actModeOllamaModelId" }, modelId, currentMode)
}}
placeholder={ollamaModels.length > 0 ? "Search and select a model..." : "e.g. llama3.1"}
/>
@@ -8,9 +8,10 @@ import { DebouncedTextField } from "../common/DebouncedTextField"
import { ModelInfoView } from "../common/ModelInfoView"
import { ApiKeyField } from "../common/ApiKeyField"
import { BaseUrlField } from "../common/BaseUrlField"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { normalizeApiConfiguration, getModeSpecificFields } from "../utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the OpenAICompatibleProvider component
@@ -18,19 +19,23 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface OpenAICompatibleProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The OpenAI Compatible provider configuration component
*/
export const OpenAICompatibleProvider = ({ showModelOptions, isPopup }: OpenAICompatibleProviderProps) => {
export const OpenAICompatibleProvider = ({ showModelOptions, isPopup, currentMode }: OpenAICompatibleProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
const [modelConfigurationSelected, setModelConfigurationSelected] = useState(false)
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
// Get mode-specific fields
const { openAiModelInfo } = getModeSpecificFields(apiConfiguration, currentMode)
// Debounced function to refresh OpenAI models (prevents excessive API calls while typing)
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null)
@@ -86,8 +91,10 @@ export const OpenAICompatibleProvider = ({ showModelOptions, isPopup }: OpenAICo
/>
<DebouncedTextField
initialValue={apiConfiguration?.openAiModelId || ""}
onChange={(value) => handleFieldChange("openAiModelId", value)}
initialValue={selectedModelId || ""}
onChange={(value) =>
handleModeFieldChange({ plan: "planModeOpenAiModelId", act: "actModeOpenAiModelId" }, value, currentMode)
}
style={{ width: "100%", marginBottom: 10 }}
placeholder={"Enter Model ID..."}>
<span style={{ fontWeight: 500 }}>Model ID</span>
@@ -188,41 +195,47 @@ export const OpenAICompatibleProvider = ({ showModelOptions, isPopup }: OpenAICo
{modelConfigurationSelected && (
<>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.supportsImages}
checked={!!openAiModelInfo?.supportsImages}
onChange={(e: any) => {
const isChecked = e.target.checked === true
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
const modelInfo = openAiModelInfo ? openAiModelInfo : { ...openAiModelInfoSaneDefaults }
modelInfo.supportsImages = isChecked
handleFieldChange("openAiModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeOpenAiModelInfo", act: "actModeOpenAiModelInfo" },
modelInfo,
currentMode,
)
}}>
Supports Images
</VSCodeCheckbox>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.supportsImages}
checked={!!openAiModelInfo?.supportsImages}
onChange={(e: any) => {
const isChecked = e.target.checked === true
let modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
let modelInfo = openAiModelInfo ? openAiModelInfo : { ...openAiModelInfoSaneDefaults }
modelInfo.supportsImages = isChecked
handleFieldChange("openAiModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeOpenAiModelInfo", act: "actModeOpenAiModelInfo" },
modelInfo,
currentMode,
)
}}>
Supports browser use
</VSCodeCheckbox>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.isR1FormatRequired}
checked={!!openAiModelInfo?.isR1FormatRequired}
onChange={(e: any) => {
const isChecked = e.target.checked === true
let modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
let modelInfo = openAiModelInfo ? openAiModelInfo : { ...openAiModelInfoSaneDefaults }
modelInfo = { ...modelInfo, isR1FormatRequired: isChecked }
handleFieldChange("openAiModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeOpenAiModelInfo", act: "actModeOpenAiModelInfo" },
modelInfo,
currentMode,
)
}}>
Enable R1 messages format
</VSCodeCheckbox>
@@ -230,34 +243,38 @@ export const OpenAICompatibleProvider = ({ showModelOptions, isPopup }: OpenAICo
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<DebouncedTextField
initialValue={
apiConfiguration?.openAiModelInfo?.contextWindow
? apiConfiguration.openAiModelInfo.contextWindow.toString()
openAiModelInfo?.contextWindow
? openAiModelInfo.contextWindow.toString()
: (openAiModelInfoSaneDefaults.contextWindow?.toString() ?? "")
}
style={{ flex: 1 }}
onChange={(value) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
const modelInfo = openAiModelInfo ? openAiModelInfo : { ...openAiModelInfoSaneDefaults }
modelInfo.contextWindow = Number(value)
handleFieldChange("openAiModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeOpenAiModelInfo", act: "actModeOpenAiModelInfo" },
modelInfo,
currentMode,
)
}}>
<span style={{ fontWeight: 500 }}>Context Window Size</span>
</DebouncedTextField>
<DebouncedTextField
initialValue={
apiConfiguration?.openAiModelInfo?.maxTokens
? apiConfiguration.openAiModelInfo.maxTokens.toString()
openAiModelInfo?.maxTokens
? openAiModelInfo.maxTokens.toString()
: (openAiModelInfoSaneDefaults.maxTokens?.toString() ?? "")
}
style={{ flex: 1 }}
onChange={(value) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
const modelInfo = openAiModelInfo ? openAiModelInfo : { ...openAiModelInfoSaneDefaults }
modelInfo.maxTokens = Number(value)
handleFieldChange("openAiModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeOpenAiModelInfo", act: "actModeOpenAiModelInfo" },
modelInfo,
currentMode,
)
}}>
<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
</DebouncedTextField>
@@ -266,34 +283,38 @@ export const OpenAICompatibleProvider = ({ showModelOptions, isPopup }: OpenAICo
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<DebouncedTextField
initialValue={
apiConfiguration?.openAiModelInfo?.inputPrice
? apiConfiguration.openAiModelInfo.inputPrice.toString()
openAiModelInfo?.inputPrice
? openAiModelInfo.inputPrice.toString()
: (openAiModelInfoSaneDefaults.inputPrice?.toString() ?? "")
}
style={{ flex: 1 }}
onChange={(value) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
const modelInfo = openAiModelInfo ? openAiModelInfo : { ...openAiModelInfoSaneDefaults }
modelInfo.inputPrice = Number(value)
handleFieldChange("openAiModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeOpenAiModelInfo", act: "actModeOpenAiModelInfo" },
modelInfo,
currentMode,
)
}}>
<span style={{ fontWeight: 500 }}>Input Price / 1M tokens</span>
</DebouncedTextField>
<DebouncedTextField
initialValue={
apiConfiguration?.openAiModelInfo?.outputPrice
? apiConfiguration.openAiModelInfo.outputPrice.toString()
openAiModelInfo?.outputPrice
? openAiModelInfo.outputPrice.toString()
: (openAiModelInfoSaneDefaults.outputPrice?.toString() ?? "")
}
style={{ flex: 1 }}
onChange={(value) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
const modelInfo = openAiModelInfo ? openAiModelInfo : { ...openAiModelInfoSaneDefaults }
modelInfo.outputPrice = Number(value)
handleFieldChange("openAiModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeOpenAiModelInfo", act: "actModeOpenAiModelInfo" },
modelInfo,
currentMode,
)
}}>
<span style={{ fontWeight: 500 }}>Output Price / 1M tokens</span>
</DebouncedTextField>
@@ -302,14 +323,12 @@ export const OpenAICompatibleProvider = ({ showModelOptions, isPopup }: OpenAICo
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<DebouncedTextField
initialValue={
apiConfiguration?.openAiModelInfo?.temperature
? apiConfiguration.openAiModelInfo.temperature.toString()
openAiModelInfo?.temperature
? openAiModelInfo.temperature.toString()
: (openAiModelInfoSaneDefaults.temperature?.toString() ?? "")
}
onChange={(value) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
const modelInfo = openAiModelInfo ? openAiModelInfo : { ...openAiModelInfoSaneDefaults }
const shouldPreserveFormat = value.endsWith(".") || (value.includes(".") && value.endsWith("0"))
@@ -320,7 +339,11 @@ export const OpenAICompatibleProvider = ({ showModelOptions, isPopup }: OpenAICo
? (value as any)
: parseFloat(value)
handleFieldChange("openAiModelInfo", modelInfo)
handleModeFieldChange(
{ plan: "planModeOpenAiModelInfo", act: "actModeOpenAiModelInfo" },
modelInfo,
currentMode,
)
}}>
<span style={{ fontWeight: 500 }}>Temperature</span>
</DebouncedTextField>
@@ -5,6 +5,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the OpenAINativeProvider component
@@ -12,17 +13,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface OpenAINativeProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The OpenAI (native) provider configuration component
*/
export const OpenAINativeProvider = ({ showModelOptions, isPopup }: OpenAINativeProviderProps) => {
export const OpenAINativeProvider = ({ showModelOptions, isPopup, currentMode }: OpenAINativeProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -38,7 +40,13 @@ export const OpenAINativeProvider = ({ showModelOptions, isPopup }: OpenAINative
<ModelSelector
models={openAiNativeModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -10,6 +10,7 @@ import OpenRouterModelPicker, { OPENROUTER_MODEL_PICKER_Z_INDEX } from "../OpenR
import { formatPrice } from "../utils/pricingUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
/**
* Component to display OpenRouter balance information
@@ -53,14 +54,14 @@ const OpenRouterBalanceDisplay = ({ apiKey }: { apiKey: string }) => {
interface OpenRouterProviderProps {
showModelOptions: boolean
isPopup?: boolean
uriScheme?: string
currentMode: Mode
}
/**
* The OpenRouter provider configuration component
*/
export const OpenRouterProvider = ({ showModelOptions, isPopup, uriScheme }: OpenRouterProviderProps) => {
const { apiConfiguration } = useExtensionState()
export const OpenRouterProvider = ({ showModelOptions, isPopup, currentMode }: OpenRouterProviderProps) => {
const { apiConfiguration, uriScheme } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const [providerSortingSelected, setProviderSortingSelected] = useState(!!apiConfiguration?.openRouterProviderSorting)
@@ -142,7 +143,7 @@ export const OpenRouterProvider = ({ showModelOptions, isPopup, uriScheme }: Ope
</div>
)}
<OpenRouterModelPicker isPopup={isPopup} />
<OpenRouterModelPicker isPopup={isPopup} currentMode={currentMode} />
</>
)}
</div>
@@ -8,6 +8,7 @@ import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { DROPDOWN_Z_INDEX } from "../ApiOptions"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
const SUPPORTED_THINKING_MODELS = [
"qwen3-235b-a22b",
@@ -28,17 +29,18 @@ const SUPPORTED_THINKING_MODELS = [
interface QwenProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Alibaba Qwen provider configuration component
*/
export const QwenProvider = ({ showModelOptions, isPopup }: QwenProviderProps) => {
export const QwenProvider = ({ showModelOptions, isPopup, currentMode }: QwenProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
// Determine which models to use based on API line selection
const qwenModels = apiConfiguration?.qwenApiLine === "china" ? mainlandQwenModels : internationalQwenModels
@@ -83,13 +85,19 @@ export const QwenProvider = ({ showModelOptions, isPopup }: QwenProviderProps) =
<ModelSelector
models={qwenModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
zIndex={DROPDOWN_Z_INDEX - 2}
/>
{SUPPORTED_THINKING_MODELS.includes(selectedModelId) && (
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} />
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} currentMode={currentMode} />
)}
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
@@ -1,3 +1,4 @@
import { Mode } from "@shared/ChatSettings"
import { ApiKeyField } from "../common/ApiKeyField"
import RequestyModelPicker from "../RequestyModelPicker"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
@@ -9,12 +10,13 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface RequestyProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Requesty provider configuration component
*/
export const RequestyProvider = ({ showModelOptions, isPopup }: RequestyProviderProps) => {
export const RequestyProvider = ({ showModelOptions, isPopup, currentMode }: RequestyProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
@@ -27,7 +29,7 @@ export const RequestyProvider = ({ showModelOptions, isPopup }: RequestyProvider
signupUrl="https://app.requesty.ai/manage-api"
/>
{showModelOptions && <RequestyModelPicker isPopup={isPopup} />}
{showModelOptions && <RequestyModelPicker isPopup={isPopup} currentMode={currentMode} />}
</div>
)
}
@@ -5,6 +5,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the SambanovaProvider component
@@ -12,17 +13,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface SambanovaProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The SambaNova provider configuration component
*/
export const SambanovaProvider = ({ showModelOptions, isPopup }: SambanovaProviderProps) => {
export const SambanovaProvider = ({ showModelOptions, isPopup, currentMode }: SambanovaProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div>
@@ -38,7 +40,13 @@ export const SambanovaProvider = ({ showModelOptions, isPopup }: SambanovaProvid
<ModelSelector
models={sambanovaModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -6,6 +6,7 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the SapAiCoreProvider component
@@ -13,16 +14,17 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface SapAiCoreProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The SAP AI Core provider configuration component
*/
export const SapAiCoreProvider = ({ showModelOptions, isPopup }: SapAiCoreProviderProps) => {
export const SapAiCoreProvider = ({ showModelOptions, isPopup, currentMode }: SapAiCoreProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
return (
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
@@ -97,7 +99,13 @@ export const SapAiCoreProvider = ({ showModelOptions, isPopup }: SapAiCoreProvid
<ModelSelector
models={sapAiCoreModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -3,6 +3,8 @@ import { DebouncedTextField } from "../common/DebouncedTextField"
import { ApiKeyField } from "../common/ApiKeyField"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { getModeSpecificFields } from "../utils/providerUtils"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the TogetherProvider component
@@ -10,14 +12,17 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface TogetherProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
/**
* The Together provider configuration component
*/
export const TogetherProvider = ({ showModelOptions, isPopup }: TogetherProviderProps) => {
export const TogetherProvider = ({ showModelOptions, isPopup, currentMode }: TogetherProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
const { togetherModelId } = getModeSpecificFields(apiConfiguration, currentMode)
return (
<div>
@@ -27,8 +32,10 @@ export const TogetherProvider = ({ showModelOptions, isPopup }: TogetherProvider
providerName="Together"
/>
<DebouncedTextField
initialValue={apiConfiguration?.togetherModelId || ""}
onChange={(value) => handleFieldChange("togetherModelId", value)}
initialValue={togetherModelId || ""}
onChange={(value) =>
handleModeFieldChange({ plan: "planModeTogetherModelId", act: "actModeTogetherModelId" }, value, currentMode)
}
style={{ width: "100%" }}
placeholder={"Enter Model ID..."}>
<span style={{ fontWeight: 500 }}>Model ID</span>
@@ -7,11 +7,19 @@ import * as vscodemodels from "vscode"
import { DropdownContainer, DROPDOWN_Z_INDEX } from "../ApiOptions"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { getModeSpecificFields } from "../utils/providerUtils"
import { Mode } from "@shared/ChatSettings"
export const VSCodeLmProvider = () => {
interface VSCodeLmProviderProps {
currentMode: Mode
}
export const VSCodeLmProvider = ({ currentMode }: VSCodeLmProviderProps) => {
const [vsCodeLmModels, setVsCodeLmModels] = useState<vscodemodels.LanguageModelChatSelector[]>([])
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
const { vsCodeLmModelSelector } = getModeSpecificFields(apiConfiguration, currentMode)
// Poll VS Code LM models
const requestVsCodeLmModels = useCallback(async () => {
@@ -42,8 +50,8 @@ export const VSCodeLmProvider = () => {
<VSCodeDropdown
id="vscode-lm-model"
value={
apiConfiguration?.vsCodeLmModelSelector
? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}`
vsCodeLmModelSelector
? `${vsCodeLmModelSelector.vendor ?? ""}/${vsCodeLmModelSelector.family ?? ""}`
: ""
}
onChange={(e) => {
@@ -53,7 +61,11 @@ export const VSCodeLmProvider = () => {
}
const [vendor, family] = value.split("/")
handleFieldChange("vsCodeLmModelSelector", { vendor, family })
handleModeFieldChange(
{ plan: "planModeVsCodeLmModelSelector", act: "actModeVsCodeLmModelSelector" },
{ vendor, family },
currentMode,
)
}}
style={{ width: "100%" }}>
<VSCodeOption value="">Select a model...</VSCodeOption>
@@ -8,6 +8,7 @@ import { DropdownContainer, DROPDOWN_Z_INDEX } from "../ApiOptions"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the VertexProvider component
@@ -15,6 +16,7 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
interface VertexProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
// Vertex models that support thinking
@@ -30,12 +32,12 @@ const SUPPORTED_THINKING_MODELS = [
/**
* The GCP Vertex AI provider configuration component
*/
export const VertexProvider = ({ showModelOptions, isPopup }: VertexProviderProps) => {
export const VertexProvider = ({ showModelOptions, isPopup, currentMode }: VertexProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
// Determine which models to use based on region
const modelsToUse = apiConfiguration?.vertexRegion === "global" ? vertexGlobalModels : vertexModels
@@ -98,13 +100,19 @@ export const VertexProvider = ({ showModelOptions, isPopup }: VertexProviderProp
<ModelSelector
models={modelsToUse}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
zIndex={DROPDOWN_Z_INDEX - 2}
/>
{SUPPORTED_THINKING_MODELS.includes(selectedModelId) && (
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} />
<ThinkingBudgetSlider maxBudget={selectedModelInfo.thinkingConfig?.maxBudget} currentMode={currentMode} />
)}
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
@@ -4,10 +4,11 @@ import { useState, useEffect } from "react"
import { ApiKeyField } from "../common/ApiKeyField"
import { ModelSelector, DropdownContainer } from "../common/ModelSelector"
import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { getModeSpecificFields, normalizeApiConfiguration } from "../utils/providerUtils"
import { DROPDOWN_Z_INDEX } from "../ApiOptions"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { Mode } from "@shared/ChatSettings"
/**
* Props for the XaiProvider component
@@ -15,17 +16,20 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
interface XaiProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
}
export const XaiProvider = ({ showModelOptions, isPopup }: XaiProviderProps) => {
export const XaiProvider = ({ showModelOptions, isPopup, currentMode }: XaiProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
// Local state for reasoning effort toggle
const [reasoningEffortSelected, setReasoningEffortSelected] = useState(!!apiConfiguration?.reasoningEffort)
const [reasoningEffortSelected, setReasoningEffortSelected] = useState(!!modeFields.reasoningEffort)
return (
<div>
@@ -54,7 +58,13 @@ export const XaiProvider = ({ showModelOptions, isPopup }: XaiProviderProps) =>
<ModelSelector
models={xaiModels}
selectedModelId={selectedModelId}
onChange={(e: any) => handleFieldChange("apiModelId", e.target.value)}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
)
}
label="Model"
/>
@@ -67,7 +77,11 @@ export const XaiProvider = ({ showModelOptions, isPopup }: XaiProviderProps) =>
const isChecked = e.target.checked === true
setReasoningEffortSelected(isChecked)
if (!isChecked) {
handleFieldChange("reasoningEffort", "")
handleModeFieldChange(
{ plan: "planModeReasoningEffort", act: "actModeReasoningEffort" },
"",
currentMode,
)
}
}}>
Modify reasoning effort
@@ -82,9 +96,13 @@ export const XaiProvider = ({ showModelOptions, isPopup }: XaiProviderProps) =>
<VSCodeDropdown
id="reasoning-effort-dropdown"
style={{ width: "100%", marginTop: 3 }}
value={apiConfiguration?.reasoningEffort || "high"}
value={modeFields.reasoningEffort || "high"}
onChange={(e: any) => {
handleFieldChange("reasoningEffort", e.target.value)
handleModeFieldChange(
{ plan: "planModeReasoningEffort", act: "actModeReasoningEffort" },
e.target.value,
currentMode,
)
}}>
<VSCodeOption value="low">low</VSCodeOption>
<VSCodeOption value="high">high</VSCodeOption>
@@ -5,19 +5,19 @@ import Section from "../Section"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { StateServiceClient } from "@/services/grpc-client"
import { UpdateSettingsRequest } from "@shared/proto/state"
import { useState } from "react"
import { syncModeConfigurations } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { Mode } from "@shared/ChatSettings"
interface ApiConfigurationSectionProps {
isSwitchingMode: boolean
handlePlanActModeChange: (mode: "plan" | "act") => Promise<void>
renderSectionHeader: (tabId: string) => JSX.Element | null
}
const ApiConfigurationSection = ({
isSwitchingMode,
handlePlanActModeChange,
renderSectionHeader,
}: ApiConfigurationSectionProps) => {
const { planActSeparateModelsSetting, chatSettings } = useExtensionState()
const ApiConfigurationSection = ({ renderSectionHeader }: ApiConfigurationSectionProps) => {
const { planActSeparateModelsSetting, chatSettings, apiConfiguration } = useExtensionState()
const [currentTab, setCurrentTab] = useState<Mode>(chatSettings.mode)
const { handleFieldsChange } = useApiConfigurationHandlers()
return (
<div>
{renderSectionHeader("api-config")}
@@ -27,34 +27,34 @@ const ApiConfigurationSection = ({
<div className="rounded-md mb-5 bg-[var(--vscode-panel-background)]">
<div className="flex gap-[1px] mb-[10px] -mt-2 border-0 border-b border-solid border-[var(--vscode-panel-border)]">
<TabButton
isActive={chatSettings.mode === "plan"}
onClick={() => handlePlanActModeChange("plan")}
disabled={isSwitchingMode}
isActive={currentTab === "plan"}
onClick={() => setCurrentTab("plan")}
disabled={currentTab === "plan"}
style={{
opacity: isSwitchingMode ? 0.6 : 1,
cursor: isSwitchingMode ? "not-allowed" : "pointer",
opacity: 1,
cursor: "pointer",
}}>
{isSwitchingMode && chatSettings.mode === "act" ? "Switching..." : "Plan Mode"}
Plan Mode
</TabButton>
<TabButton
isActive={chatSettings.mode === "act"}
onClick={() => handlePlanActModeChange("act")}
disabled={isSwitchingMode}
isActive={currentTab === "act"}
onClick={() => setCurrentTab("act")}
disabled={currentTab === "act"}
style={{
opacity: isSwitchingMode ? 0.6 : 1,
cursor: isSwitchingMode ? "not-allowed" : "pointer",
opacity: 1,
cursor: "pointer",
}}>
{isSwitchingMode && chatSettings.mode === "plan" ? "Switching..." : "Act Mode"}
Act Mode
</TabButton>
</div>
{/* Content container */}
<div className="-mb-3">
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode={currentTab} />
</div>
</div>
) : (
<ApiOptions showModelOptions={true} />
<ApiOptions showModelOptions={true} currentMode={chatSettings.mode} />
)}
<div className="mb-[5px]">
@@ -64,6 +64,10 @@ const ApiConfigurationSection = ({
onChange={async (e: any) => {
const checked = e.target.checked === true
try {
// If unchecking the toggle, wait a bit for state to update, then sync configurations
if (!checked) {
await syncModeConfigurations(apiConfiguration, currentTab, handleFieldsChange)
}
await StateServiceClient.updateSettings(
UpdateSettingsRequest.create({
planActSeparateModelsSetting: checked,
@@ -49,6 +49,7 @@ import {
groqModels,
groqDefaultModelId,
} from "@shared/api"
import { Mode } from "@shared/ChatSettings"
/**
* Interface for normalized API configuration
@@ -62,9 +63,13 @@ export interface NormalizedApiConfig {
/**
* Normalizes API configuration to ensure consistent values
*/
export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration): NormalizedApiConfig {
const provider = apiConfiguration?.apiProvider || "anthropic"
const modelId = apiConfiguration?.apiModelId
export function normalizeApiConfiguration(
apiConfiguration: ApiConfiguration | undefined,
currentMode: Mode,
): NormalizedApiConfig {
const provider =
(currentMode === "plan" ? apiConfiguration?.planModeApiProvider : apiConfiguration?.actModeApiProvider) || "anthropic"
const modelId = currentMode === "plan" ? apiConfiguration?.planModeApiModelId : apiConfiguration?.actModeApiModelId
const getProviderData = (models: Record<string, ModelInfo>, defaultId: string) => {
let selectedModelId: string
@@ -89,8 +94,15 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
case "claude-code":
return getProviderData(claudeCodeModels, claudeCodeDefaultModelId)
case "bedrock":
if (apiConfiguration?.awsBedrockCustomSelected) {
const baseModelId = apiConfiguration.awsBedrockCustomModelBaseId
const awsBedrockCustomSelected =
currentMode === "plan"
? apiConfiguration?.planModeAwsBedrockCustomSelected
: apiConfiguration?.actModeAwsBedrockCustomSelected
if (awsBedrockCustomSelected) {
const baseModelId =
currentMode === "plan"
? apiConfiguration?.planModeAwsBedrockCustomModelBaseId
: apiConfiguration?.actModeAwsBedrockCustomModelBaseId
return {
selectedProvider: provider,
selectedModelId: modelId || bedrockDefaultModelId,
@@ -118,69 +130,107 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
case "asksage":
return getProviderData(askSageModels, askSageDefaultModelId)
case "openrouter":
const openRouterModelId =
currentMode === "plan" ? apiConfiguration?.planModeOpenRouterModelId : apiConfiguration?.actModeOpenRouterModelId
const openRouterModelInfo =
currentMode === "plan"
? apiConfiguration?.planModeOpenRouterModelInfo
: apiConfiguration?.actModeOpenRouterModelInfo
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
selectedModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo,
selectedModelId: openRouterModelId || openRouterDefaultModelId,
selectedModelInfo: openRouterModelInfo || openRouterDefaultModelInfo,
}
case "requesty":
const requestyModelId =
currentMode === "plan" ? apiConfiguration?.planModeRequestyModelId : apiConfiguration?.actModeRequestyModelId
const requestyModelInfo =
currentMode === "plan" ? apiConfiguration?.planModeRequestyModelInfo : apiConfiguration?.actModeRequestyModelInfo
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.requestyModelId || requestyDefaultModelId,
selectedModelInfo: apiConfiguration?.requestyModelInfo || requestyDefaultModelInfo,
selectedModelId: requestyModelId || requestyDefaultModelId,
selectedModelInfo: requestyModelInfo || requestyDefaultModelInfo,
}
case "cline":
const openRouterModelId = apiConfiguration?.openRouterModelId || openRouterDefaultModelId
const openRouterModelInfo = apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo
const clineOpenRouterModelId =
(currentMode === "plan"
? apiConfiguration?.planModeOpenRouterModelId
: apiConfiguration?.actModeOpenRouterModelId) || openRouterDefaultModelId
const clineOpenRouterModelInfo =
(currentMode === "plan"
? apiConfiguration?.planModeOpenRouterModelInfo
: apiConfiguration?.actModeOpenRouterModelInfo) || openRouterDefaultModelInfo
return {
selectedProvider: provider,
selectedModelId: openRouterModelId,
selectedModelInfo: openRouterModelInfo,
selectedModelId: clineOpenRouterModelId,
selectedModelInfo: clineOpenRouterModelInfo,
}
case "openai":
const openAiModelId =
currentMode === "plan" ? apiConfiguration?.planModeOpenAiModelId : apiConfiguration?.actModeOpenAiModelId
const openAiModelInfo =
currentMode === "plan" ? apiConfiguration?.planModeOpenAiModelInfo : apiConfiguration?.actModeOpenAiModelInfo
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.openAiModelId || "",
selectedModelInfo: apiConfiguration?.openAiModelInfo || openAiModelInfoSaneDefaults,
selectedModelId: openAiModelId || "",
selectedModelInfo: openAiModelInfo || openAiModelInfoSaneDefaults,
}
case "ollama":
const ollamaModelId =
currentMode === "plan" ? apiConfiguration?.planModeOllamaModelId : apiConfiguration?.actModeOllamaModelId
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.ollamaModelId || "",
selectedModelId: ollamaModelId || "",
selectedModelInfo: openAiModelInfoSaneDefaults,
}
case "lmstudio":
const lmStudioModelId =
currentMode === "plan" ? apiConfiguration?.planModeLmStudioModelId : apiConfiguration?.actModeLmStudioModelId
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.lmStudioModelId || "",
selectedModelId: lmStudioModelId || "",
selectedModelInfo: openAiModelInfoSaneDefaults,
}
case "vscode-lm":
const vsCodeLmModelSelector =
currentMode === "plan"
? apiConfiguration?.planModeVsCodeLmModelSelector
: apiConfiguration?.actModeVsCodeLmModelSelector
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.vsCodeLmModelSelector
? `${apiConfiguration.vsCodeLmModelSelector.vendor}/${apiConfiguration.vsCodeLmModelSelector.family}`
: "",
selectedModelId: vsCodeLmModelSelector ? `${vsCodeLmModelSelector.vendor}/${vsCodeLmModelSelector.family}` : "",
selectedModelInfo: {
...openAiModelInfoSaneDefaults,
supportsImages: false, // VSCode LM API currently doesn't support images
},
}
case "litellm":
const liteLlmModelId =
currentMode === "plan" ? apiConfiguration?.planModeLiteLlmModelId : apiConfiguration?.actModeLiteLlmModelId
const liteLlmModelInfo =
currentMode === "plan" ? apiConfiguration?.planModeLiteLlmModelInfo : apiConfiguration?.actModeLiteLlmModelInfo
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.liteLlmModelId || "",
selectedModelInfo: apiConfiguration?.liteLlmModelInfo || liteLlmModelInfoSaneDefaults,
selectedModelId: liteLlmModelId || "",
selectedModelInfo: liteLlmModelInfo || liteLlmModelInfoSaneDefaults,
}
case "xai":
return getProviderData(xaiModels, xaiDefaultModelId)
case "moonshot":
return getProviderData(moonshotModels, moonshotDefaultModelId)
case "huggingface":
const huggingFaceModelId =
currentMode === "plan"
? apiConfiguration?.planModeHuggingFaceModelId
: apiConfiguration?.actModeHuggingFaceModelId
const huggingFaceModelInfo =
currentMode === "plan"
? apiConfiguration?.planModeHuggingFaceModelInfo
: apiConfiguration?.actModeHuggingFaceModelInfo
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.huggingFaceModelId || huggingFaceDefaultModelId,
selectedModelInfo: apiConfiguration?.huggingFaceModelInfo || huggingFaceModels[huggingFaceDefaultModelId],
selectedModelId: huggingFaceModelId || huggingFaceDefaultModelId,
selectedModelInfo: huggingFaceModelInfo || huggingFaceModels[huggingFaceDefaultModelId],
}
case "nebius":
return getProviderData(nebiusModels, nebiusDefaultModelId)
@@ -189,13 +239,15 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
case "cerebras":
return getProviderData(cerebrasModels, cerebrasDefaultModelId)
case "groq":
const result = {
const groqModelId =
currentMode === "plan" ? apiConfiguration?.planModeGroqModelId : apiConfiguration?.actModeGroqModelId
const groqModelInfo =
currentMode === "plan" ? apiConfiguration?.planModeGroqModelInfo : apiConfiguration?.actModeGroqModelInfo
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.groqModelId || groqDefaultModelId,
selectedModelInfo: apiConfiguration?.groqModelInfo || groqModels[groqDefaultModelId],
selectedModelId: groqModelId || groqDefaultModelId,
selectedModelInfo: groqModelInfo || groqModels[groqDefaultModelId],
}
return result
case "sapaicore":
return getProviderData(sapAiCoreModels, sapAiCoreDefaultModelId)
default:
@@ -203,6 +255,231 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
}
}
/**
* Gets mode-specific field values from API configuration
* @param apiConfiguration The API configuration object
* @param mode The current mode ("plan" or "act")
* @returns Object containing mode-specific field values for clean destructuring
*/
export function getModeSpecificFields(apiConfiguration: ApiConfiguration | undefined, mode: Mode) {
if (!apiConfiguration) {
return {
// Core fields
apiProvider: undefined,
apiModelId: undefined,
// Provider-specific model IDs
togetherModelId: undefined,
fireworksModelId: undefined,
lmStudioModelId: undefined,
ollamaModelId: undefined,
liteLlmModelId: undefined,
requestyModelId: undefined,
openAiModelId: undefined,
openRouterModelId: undefined,
groqModelId: undefined,
huggingFaceModelId: undefined,
// Model info objects
openAiModelInfo: undefined,
liteLlmModelInfo: undefined,
openRouterModelInfo: undefined,
requestyModelInfo: undefined,
groqModelInfo: undefined,
huggingFaceModelInfo: undefined,
vsCodeLmModelSelector: undefined,
// AWS Bedrock fields
awsBedrockCustomSelected: undefined,
awsBedrockCustomModelBaseId: undefined,
// Other mode-specific fields
thinkingBudgetTokens: undefined,
reasoningEffort: undefined,
}
}
return {
// Core fields
apiProvider: mode === "plan" ? apiConfiguration.planModeApiProvider : apiConfiguration.actModeApiProvider,
apiModelId: mode === "plan" ? apiConfiguration.planModeApiModelId : apiConfiguration.actModeApiModelId,
// Provider-specific model IDs
togetherModelId: mode === "plan" ? apiConfiguration.planModeTogetherModelId : apiConfiguration.actModeTogetherModelId,
fireworksModelId: mode === "plan" ? apiConfiguration.planModeFireworksModelId : apiConfiguration.actModeFireworksModelId,
lmStudioModelId: mode === "plan" ? apiConfiguration.planModeLmStudioModelId : apiConfiguration.actModeLmStudioModelId,
ollamaModelId: mode === "plan" ? apiConfiguration.planModeOllamaModelId : apiConfiguration.actModeOllamaModelId,
liteLlmModelId: mode === "plan" ? apiConfiguration.planModeLiteLlmModelId : apiConfiguration.actModeLiteLlmModelId,
requestyModelId: mode === "plan" ? apiConfiguration.planModeRequestyModelId : apiConfiguration.actModeRequestyModelId,
openAiModelId: mode === "plan" ? apiConfiguration.planModeOpenAiModelId : apiConfiguration.actModeOpenAiModelId,
openRouterModelId:
mode === "plan" ? apiConfiguration.planModeOpenRouterModelId : apiConfiguration.actModeOpenRouterModelId,
groqModelId: mode === "plan" ? apiConfiguration.planModeGroqModelId : apiConfiguration.actModeGroqModelId,
huggingFaceModelId:
mode === "plan" ? apiConfiguration.planModeHuggingFaceModelId : apiConfiguration.actModeHuggingFaceModelId,
// Model info objects
openAiModelInfo: mode === "plan" ? apiConfiguration.planModeOpenAiModelInfo : apiConfiguration.actModeOpenAiModelInfo,
liteLlmModelInfo: mode === "plan" ? apiConfiguration.planModeLiteLlmModelInfo : apiConfiguration.actModeLiteLlmModelInfo,
openRouterModelInfo:
mode === "plan" ? apiConfiguration.planModeOpenRouterModelInfo : apiConfiguration.actModeOpenRouterModelInfo,
requestyModelInfo:
mode === "plan" ? apiConfiguration.planModeRequestyModelInfo : apiConfiguration.actModeRequestyModelInfo,
groqModelInfo: mode === "plan" ? apiConfiguration.planModeGroqModelInfo : apiConfiguration.actModeGroqModelInfo,
huggingFaceModelInfo:
mode === "plan" ? apiConfiguration.planModeHuggingFaceModelInfo : apiConfiguration.actModeHuggingFaceModelInfo,
vsCodeLmModelSelector:
mode === "plan" ? apiConfiguration.planModeVsCodeLmModelSelector : apiConfiguration.actModeVsCodeLmModelSelector,
// AWS Bedrock fields
awsBedrockCustomSelected:
mode === "plan"
? apiConfiguration.planModeAwsBedrockCustomSelected
: apiConfiguration.actModeAwsBedrockCustomSelected,
awsBedrockCustomModelBaseId:
mode === "plan"
? apiConfiguration.planModeAwsBedrockCustomModelBaseId
: apiConfiguration.actModeAwsBedrockCustomModelBaseId,
// Other mode-specific fields
thinkingBudgetTokens:
mode === "plan" ? apiConfiguration.planModeThinkingBudgetTokens : apiConfiguration.actModeThinkingBudgetTokens,
reasoningEffort: mode === "plan" ? apiConfiguration.planModeReasoningEffort : apiConfiguration.actModeReasoningEffort,
}
}
/**
* Synchronizes mode configurations by copying the source mode's settings to both modes
* This is used when the "Use different models for Plan and Act modes" toggle is unchecked
*/
export async function syncModeConfigurations(
apiConfiguration: ApiConfiguration | undefined,
sourceMode: Mode,
handleFieldsChange: (updates: Partial<ApiConfiguration>) => Promise<void>,
): Promise<void> {
if (!apiConfiguration) return
const sourceFields = getModeSpecificFields(apiConfiguration, sourceMode)
const { apiProvider } = sourceFields
if (!apiProvider) return
// Build the complete update object with both plan and act mode fields
const updates: Partial<ApiConfiguration> = {
// Always sync common fields
planModeApiProvider: sourceFields.apiProvider,
actModeApiProvider: sourceFields.apiProvider,
planModeThinkingBudgetTokens: sourceFields.thinkingBudgetTokens,
actModeThinkingBudgetTokens: sourceFields.thinkingBudgetTokens,
planModeReasoningEffort: sourceFields.reasoningEffort,
actModeReasoningEffort: sourceFields.reasoningEffort,
}
// Handle provider-specific fields
switch (apiProvider) {
case "openrouter":
case "cline":
updates.planModeOpenRouterModelId = sourceFields.openRouterModelId
updates.actModeOpenRouterModelId = sourceFields.openRouterModelId
updates.planModeOpenRouterModelInfo = sourceFields.openRouterModelInfo
updates.actModeOpenRouterModelInfo = sourceFields.openRouterModelInfo
break
case "requesty":
updates.planModeRequestyModelId = sourceFields.requestyModelId
updates.actModeRequestyModelId = sourceFields.requestyModelId
updates.planModeRequestyModelInfo = sourceFields.requestyModelInfo
updates.actModeRequestyModelInfo = sourceFields.requestyModelInfo
break
case "openai":
updates.planModeOpenAiModelId = sourceFields.openAiModelId
updates.actModeOpenAiModelId = sourceFields.openAiModelId
updates.planModeOpenAiModelInfo = sourceFields.openAiModelInfo
updates.actModeOpenAiModelInfo = sourceFields.openAiModelInfo
break
case "ollama":
updates.planModeOllamaModelId = sourceFields.ollamaModelId
updates.actModeOllamaModelId = sourceFields.ollamaModelId
break
case "lmstudio":
updates.planModeLmStudioModelId = sourceFields.lmStudioModelId
updates.actModeLmStudioModelId = sourceFields.lmStudioModelId
break
case "vscode-lm":
updates.planModeVsCodeLmModelSelector = sourceFields.vsCodeLmModelSelector
updates.actModeVsCodeLmModelSelector = sourceFields.vsCodeLmModelSelector
break
case "litellm":
updates.planModeLiteLlmModelId = sourceFields.liteLlmModelId
updates.actModeLiteLlmModelId = sourceFields.liteLlmModelId
updates.planModeLiteLlmModelInfo = sourceFields.liteLlmModelInfo
updates.actModeLiteLlmModelInfo = sourceFields.liteLlmModelInfo
break
case "groq":
updates.planModeGroqModelId = sourceFields.groqModelId
updates.actModeGroqModelId = sourceFields.groqModelId
updates.planModeGroqModelInfo = sourceFields.groqModelInfo
updates.actModeGroqModelInfo = sourceFields.groqModelInfo
break
case "huggingface":
updates.planModeHuggingFaceModelId = sourceFields.huggingFaceModelId
updates.actModeHuggingFaceModelId = sourceFields.huggingFaceModelId
updates.planModeHuggingFaceModelInfo = sourceFields.huggingFaceModelInfo
updates.actModeHuggingFaceModelInfo = sourceFields.huggingFaceModelInfo
break
case "together":
updates.planModeTogetherModelId = sourceFields.togetherModelId
updates.actModeTogetherModelId = sourceFields.togetherModelId
break
case "fireworks":
updates.planModeFireworksModelId = sourceFields.fireworksModelId
updates.actModeFireworksModelId = sourceFields.fireworksModelId
break
case "bedrock":
updates.planModeApiModelId = sourceFields.apiModelId
updates.actModeApiModelId = sourceFields.apiModelId
updates.planModeAwsBedrockCustomSelected = sourceFields.awsBedrockCustomSelected
updates.actModeAwsBedrockCustomSelected = sourceFields.awsBedrockCustomSelected
updates.planModeAwsBedrockCustomModelBaseId = sourceFields.awsBedrockCustomModelBaseId
updates.actModeAwsBedrockCustomModelBaseId = sourceFields.awsBedrockCustomModelBaseId
break
// Providers that use apiProvider + apiModelId fields
case "anthropic":
case "claude-code":
case "vertex":
case "gemini":
case "openai-native":
case "deepseek":
case "qwen":
case "doubao":
case "mistral":
case "asksage":
case "xai":
case "nebius":
case "sambanova":
case "cerebras":
case "sapaicore":
default:
updates.planModeApiModelId = sourceFields.apiModelId
updates.actModeApiModelId = sourceFields.apiModelId
break
}
// Make the atomic update
await handleFieldsChange(updates)
}
/**
* Gets the OpenRouter authentication URL
*/
@@ -1,12 +1,14 @@
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ApiConfiguration } from "@shared/api"
import { Mode } from "@shared/ChatSettings"
import { convertApiConfigurationToProto } from "@shared/proto-conversions/models/api-configuration-conversion"
import { UpdateApiConfigurationRequest } from "@shared/proto/models"
import { useCallback } from "react"
export const useApiConfigurationHandlers = () => {
const { apiConfiguration, uriScheme } = useExtensionState()
const { apiConfiguration, planActSeparateModelsSetting } = useExtensionState()
/**
* Updates a single field in the API configuration.
*
@@ -17,24 +19,19 @@ export const useApiConfigurationHandlers = () => {
* @param field - The field key to update
* @param value - The new value for the field
*/
const handleFieldChange = useCallback(
<K extends keyof ApiConfiguration>(field: K, value: ApiConfiguration[K]) => {
const updatedConfig = {
...apiConfiguration,
[field]: value,
}
const handleFieldChange = async <K extends keyof ApiConfiguration>(field: K, value: ApiConfiguration[K]) => {
const updatedConfig = {
...apiConfiguration,
[field]: value,
}
const protoConfig = convertApiConfigurationToProto(updatedConfig)
ModelsServiceClient.updateApiConfigurationProto(
UpdateApiConfigurationRequest.create({
apiConfiguration: protoConfig,
}),
).catch((error) => {
console.error(`Failed to update API configuration field ${field}:`, error)
})
},
[apiConfiguration],
)
const protoConfig = convertApiConfigurationToProto(updatedConfig)
await ModelsServiceClient.updateApiConfigurationProto(
UpdateApiConfigurationRequest.create({
apiConfiguration: protoConfig,
}),
)
}
/**
* Updates multiple fields in the API configuration at once.
@@ -45,24 +42,69 @@ export const useApiConfigurationHandlers = () => {
*
* @param updates - An object containing the fields to update and their new values
*/
const handleFieldsChange = useCallback(
(updates: Partial<ApiConfiguration>) => {
const updatedConfig = {
...apiConfiguration,
...updates,
}
const handleFieldsChange = async (updates: Partial<ApiConfiguration>) => {
const updatedConfig = {
...apiConfiguration,
...updates,
}
const protoConfig = convertApiConfigurationToProto(updatedConfig)
ModelsServiceClient.updateApiConfigurationProto(
UpdateApiConfigurationRequest.create({
apiConfiguration: protoConfig,
}),
).catch((error) => {
console.error("Failed to update API configuration fields:", error)
const protoConfig = convertApiConfigurationToProto(updatedConfig)
await ModelsServiceClient.updateApiConfigurationProto(
UpdateApiConfigurationRequest.create({
apiConfiguration: protoConfig,
}),
)
}
const handleModeFieldChange = async <PlanK extends keyof ApiConfiguration, ActK extends keyof ApiConfiguration>(
fieldPair: { plan: PlanK; act: ActK },
value: ApiConfiguration[PlanK] & ApiConfiguration[ActK], // Intersection ensures value is compatible with both field types
currentMode: Mode,
) => {
if (planActSeparateModelsSetting) {
const targetField = fieldPair[currentMode]
await handleFieldChange(targetField, value)
} else {
await handleFieldsChange({
[fieldPair.plan]: value,
[fieldPair.act]: value,
})
},
[apiConfiguration],
)
}
}
return { handleFieldChange, handleFieldsChange, uriScheme, apiConfiguration }
/**
* Updates multiple mode-specific fields in a single atomic operation.
*
* This prevents race conditions that can occur when making multiple separate
* handleModeFieldChange calls in rapid succession.
*
* @param fieldPairs - Object mapping keys to plan/act field pairs
* @param values - Object with values for each key
* @param currentMode - The current mode being targeted
*/
const handleModeFieldsChange = async <T extends Record<string, any>>(
fieldPairs: { [K in keyof T]: { plan: keyof ApiConfiguration; act: keyof ApiConfiguration } },
values: T,
currentMode: Mode,
) => {
if (planActSeparateModelsSetting) {
// Update only the current mode's fields
const updates: Partial<ApiConfiguration> = {}
Object.entries(fieldPairs).forEach(([key, { plan, act }]) => {
const targetField = currentMode === "plan" ? plan : act
updates[targetField] = values[key]
})
await handleFieldsChange(updates)
} else {
// Update both modes' fields
const updates: Partial<ApiConfiguration> = {}
Object.entries(fieldPairs).forEach(([key, { plan, act }]) => {
updates[plan] = values[key]
updates[act] = values[key]
})
await handleFieldsChange(updates)
}
}
return { handleFieldChange, handleFieldsChange, handleModeFieldChange, handleModeFieldsChange }
}
@@ -1,19 +1,37 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { useState, memo } from "react"
import { useEffect, useState, memo } from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { validateApiConfiguration } from "@/utils/validate"
import ApiOptions from "@/components/settings/ApiOptions"
import ClineLogoWhite from "@/assets/ClineLogoWhite"
import { AccountServiceClient } from "@/services/grpc-client"
import { EmptyRequest } from "@shared/proto/common"
import { AccountServiceClient, StateServiceClient } from "@/services/grpc-client"
import { EmptyRequest, BooleanRequest } from "@shared/proto/common"
const WelcomeView = memo(() => {
const { apiConfiguration, chatSettings } = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
const [showApiOptions, setShowApiOptions] = useState(false)
const disableLetsGoButton = apiErrorMessage != null
const handleLogin = () => {
AccountServiceClient.accountLoginClicked(EmptyRequest.create()).catch((err) =>
console.error("Failed to get login URL:", err),
)
}
const handleSubmit = async () => {
try {
await StateServiceClient.setWelcomeViewCompleted(BooleanRequest.create({ value: true }))
} catch (error) {
console.error("Failed to update API configuration or complete welcome view:", error)
}
}
useEffect(() => {
setApiErrorMessage(validateApiConfiguration(chatSettings.mode, apiConfiguration))
}, [apiConfiguration, chatSettings.mode])
return (
<div className="fixed inset-0 p-0 flex flex-col">
<div className="h-full px-5 overflow-auto">
@@ -49,7 +67,16 @@ const WelcomeView = memo(() => {
</VSCodeButton>
)}
<div className="mt-4.5">{showApiOptions && <ApiOptions showModelOptions={false} showSubmitButton={true} />}</div>
<div className="mt-4.5">
{showApiOptions && (
<div>
<ApiOptions showModelOptions={false} currentMode={chatSettings.mode} />
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} className="mt-0.75">
Let's go!
</VSCodeButton>
</div>
)}
</div>
</div>
</div>
)
+26 -11
View File
@@ -1,8 +1,21 @@
import { ApiConfiguration, openRouterDefaultModelId, ModelInfo } from "@shared/api"
import { getModeSpecificFields } from "@/components/settings/utils/providerUtils"
import { Mode } from "@shared/ChatSettings"
export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): string | undefined {
export function validateApiConfiguration(currentMode: Mode, apiConfiguration?: ApiConfiguration): string | undefined {
if (apiConfiguration) {
switch (apiConfiguration.apiProvider) {
const {
apiProvider,
openAiModelId,
requestyModelId,
fireworksModelId,
togetherModelId,
ollamaModelId,
lmStudioModelId,
vsCodeLmModelSelector,
} = getModeSpecificFields(apiConfiguration, currentMode)
switch (apiProvider) {
case "anthropic":
if (!apiConfiguration.apiKey) {
return "You must provide a valid API key or choose a different provider."
@@ -64,37 +77,37 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
}
break
case "openai":
if (!apiConfiguration.openAiBaseUrl || !apiConfiguration.openAiApiKey || !apiConfiguration.openAiModelId) {
if (!apiConfiguration.openAiBaseUrl || !apiConfiguration.openAiApiKey || !openAiModelId) {
return "You must provide a valid base URL, API key, and model ID."
}
break
case "requesty":
if (!apiConfiguration.requestyApiKey || !apiConfiguration.requestyModelId) {
if (!apiConfiguration.requestyApiKey || !requestyModelId) {
return "You must provide a valid API key or choose a different provider."
}
break
case "fireworks":
if (!apiConfiguration.fireworksApiKey || !apiConfiguration.fireworksModelId) {
if (!apiConfiguration.fireworksApiKey || !fireworksModelId) {
return "You must provide a valid API key or choose a different provider."
}
break
case "together":
if (!apiConfiguration.togetherApiKey || !apiConfiguration.togetherModelId) {
if (!apiConfiguration.togetherApiKey || !togetherModelId) {
return "You must provide a valid API key or choose a different provider."
}
break
case "ollama":
if (!apiConfiguration.ollamaModelId) {
if (!ollamaModelId) {
return "You must provide a valid model ID."
}
break
case "lmstudio":
if (!apiConfiguration.lmStudioModelId) {
if (!lmStudioModelId) {
return "You must provide a valid model ID."
}
break
case "vscode-lm":
if (!apiConfiguration.vsCodeLmModelSelector) {
if (!vsCodeLmModelSelector) {
return "You must provide a valid model selector."
}
break
@@ -138,14 +151,16 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
}
export function validateModelId(
currentMode: Mode,
apiConfiguration?: ApiConfiguration,
openRouterModels?: Record<string, ModelInfo>,
): string | undefined {
if (apiConfiguration) {
switch (apiConfiguration.apiProvider) {
const { apiProvider, openRouterModelId } = getModeSpecificFields(apiConfiguration, currentMode)
switch (apiProvider) {
case "openrouter":
case "cline":
const modelId = apiConfiguration.openRouterModelId || openRouterDefaultModelId // in case the user hasn't changed the model id, it will be undefined by default
const modelId = openRouterModelId || openRouterDefaultModelId // in case the user hasn't changed the model id, it will be undefined by default
if (!modelId) {
return "You must provide a model ID."
}