mirror of
https://github.com/cline/cline.git
synced 2026-09-17 17:45:33 +08:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3572772df2 | ||
|
|
3e0e541332 | ||
|
|
a8823ab048 | ||
|
|
1cf48eca36 | ||
|
|
28022f56fd | ||
|
|
28a71ecd85 | ||
|
|
85245ca18f | ||
|
|
8b49b5ef9f | ||
|
|
d29e4e07f8 | ||
|
|
7a8ab90a49 | ||
|
|
09032188c1 | ||
|
|
d18492ca37 | ||
|
|
5f78128cd0 | ||
|
|
b8bd3af2f1 | ||
|
|
d56a3ee1bc | ||
|
|
34e2f0dc41 | ||
|
|
4b1429c667 | ||
|
|
a6eb8765c5 | ||
|
|
0bb13b1a36 | ||
|
|
574f82a171 | ||
|
|
0f7904e52b | ||
|
|
21eaa754ac | ||
|
|
5d47aa5f8f | ||
|
|
f2c9b4f42b |
+3
-1
@@ -36,6 +36,8 @@ webview-ui/src/services/grpc-client.ts
|
||||
# Host bridge
|
||||
src/hosts/vscode/client/host-grpc-client.ts
|
||||
src/standalone/server-setup.ts
|
||||
|
||||
# Storybook
|
||||
*storybook.log
|
||||
storybook-static
|
||||
# E2E Tests
|
||||
test-results
|
||||
|
||||
Generated
+1337
-668
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -359,7 +359,8 @@
|
||||
"docs": "cd docs && mintlify dev",
|
||||
"docs:check-links": "cd docs && mintlify broken-links",
|
||||
"docs:rename-file": "cd docs && mintlify rename",
|
||||
"report-issue": "node scripts/report-issue.js"
|
||||
"report-issue": "node scripts/report-issue.js",
|
||||
"storybook": "cd webview-ui && npm run storybook"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
|
||||
+115
-84
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
+89
-63
@@ -41,27 +41,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: "plan" | "act",
|
||||
): 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 +79,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 +103,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 +125,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,7 +211,7 @@ 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({
|
||||
@@ -204,36 +221,36 @@ function createHandlerForProvider(apiProvider: string | undefined, options: Omit
|
||||
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 +259,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: "plan" | "act"): 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 +307,5 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
||||
console.error("buildApiHandler error:", error)
|
||||
}
|
||||
|
||||
return createHandlerForProvider(apiProvider, options)
|
||||
return createHandlerForProvider(apiProvider, options, mode)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("OllamaHandler", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
options = {
|
||||
ollamaModelId: "llama2",
|
||||
actModeOllamaModelId: "llama2",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
}
|
||||
handler = new OllamaHandler(options)
|
||||
|
||||
@@ -13,7 +13,7 @@ interface AnthropicHandlerOptions {
|
||||
}
|
||||
|
||||
export class AnthropicHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private options: AnthropicHandlerOptions
|
||||
private client: Anthropic | undefined
|
||||
|
||||
constructor(options: AnthropicHandlerOptions) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+46
-158
@@ -50,6 +50,7 @@ export class Controller {
|
||||
|
||||
private disposables: vscode.Disposable[] = []
|
||||
task?: Task
|
||||
|
||||
workspaceTracker: WorkspaceTracker
|
||||
mcpHub: McpHub
|
||||
accountService: ClineAccountService
|
||||
@@ -83,7 +84,7 @@ export class Controller {
|
||||
})
|
||||
}
|
||||
|
||||
private async getCurrentMode(): Promise<"plan" | "act"> {
|
||||
async getCurrentMode(): Promise<"plan" | "act"> {
|
||||
return ((await getGlobalState(this.context, "mode")) as "plan" | "act" | 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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -53,8 +53,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 +169,327 @@ 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")
|
||||
|
||||
// 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)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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("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 +511,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 +524,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
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
+16
-6
@@ -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,9 @@ 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 context = this.getContext()
|
||||
const providerKey = this.chatSettings.mode === "plan" ? "planModeApiProvider" : "actModeApiProvider"
|
||||
const providerId: string = (await getGlobalState(context, providerKey)) ?? "cline"
|
||||
return { modelId, providerId }
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
+48
-22
@@ -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,38 +54,27 @@ 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
|
||||
@@ -100,25 +85,66 @@ export interface ApiHandlerOptions {
|
||||
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
|
||||
// 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
|
||||
}
|
||||
|
||||
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,37 +340,27 @@ 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,
|
||||
@@ -385,42 +371,82 @@ export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoA
|
||||
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),
|
||||
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),
|
||||
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,37 +459,27 @@ 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,
|
||||
@@ -474,21 +490,70 @@ export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguratio
|
||||
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),
|
||||
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),
|
||||
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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022", "esnext.disposable", "DOM"],
|
||||
"lib": ["es2022", "DOM"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "Bundler",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:storybook/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { StorybookConfig } from "@storybook/react-vite"
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
addons: [],
|
||||
framework: "@storybook/react-vite",
|
||||
viteFinal: async (config) => {
|
||||
// Define environment variables for Storybook
|
||||
config.define = {
|
||||
...config.define,
|
||||
"process.env": {
|
||||
...process.env,
|
||||
IS_DEV: JSON.stringify(true),
|
||||
IS_TEST: JSON.stringify(false),
|
||||
TEMP_PROFILE: JSON.stringify(true),
|
||||
},
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
typescript: {
|
||||
check: false,
|
||||
reactDocgen: "react-docgen-typescript",
|
||||
reactDocgenTypescriptOptions: {
|
||||
shouldExtractLiteralValuesFromEnum: true,
|
||||
propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
|
||||
},
|
||||
},
|
||||
}
|
||||
export default config
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { Preview } from "@storybook/react-vite"
|
||||
import "../src/index.css" // Import Tailwind CSS and other global styles
|
||||
import { VSCodeWebview } from "../src/components/common/StorybookDecorator"
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
viewport: {
|
||||
viewports: {
|
||||
"Editor Sidebar": {
|
||||
name: "Editor Sidebar",
|
||||
styles: { width: "700px", height: "800px" },
|
||||
type: "desktop",
|
||||
},
|
||||
},
|
||||
defaultViewport: "Editor Sidebar",
|
||||
},
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
docs: {
|
||||
theme: {
|
||||
base: "dark",
|
||||
colorPrimary: "#3794ff",
|
||||
colorSecondary: "#0e639c",
|
||||
appBg: "#1e1e1e",
|
||||
appContentBg: "#252526",
|
||||
textColor: "#d4d4d4",
|
||||
},
|
||||
},
|
||||
layout: "padded",
|
||||
},
|
||||
decorators: [VSCodeWebview],
|
||||
globalTypes: {
|
||||
theme: {
|
||||
description: "Color Themes",
|
||||
defaultValue: "vs_dark",
|
||||
toolbar: {
|
||||
dynamicTitle: true,
|
||||
icon: "sun",
|
||||
title: "Themes",
|
||||
items: [
|
||||
{ value: "vs_dark", title: "VS Code Dark" },
|
||||
{ value: "vs_light", title: "VS Code Light" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
@@ -0,0 +1,63 @@
|
||||
const mockVSCodeDarkTheme = {
|
||||
"--vscode-editor-background": "#1E1E1E",
|
||||
"--vscode-editor-foreground": "#D4D4D4",
|
||||
"--vscode-sideBar-background": "#252526",
|
||||
"--vscode-editorGroup-border": "#454545",
|
||||
"--vscode-descriptionForeground": "#CCCCCC99",
|
||||
"--vscode-button-background": "#007ACC",
|
||||
"--vscode-button-foreground": "#FFFFFF",
|
||||
"--vscode-button-secondaryBackground": "#383B3D",
|
||||
"--vscode-textLink-foreground": "#007ACC",
|
||||
"--vscode-errorForeground": "#f48771",
|
||||
"--vscode-editorWidget-border": "#303031",
|
||||
"--vscode-editorError-foreground": "#f14c4c",
|
||||
"--vscode-diffEditor-removedTextBackground": "#9c353520",
|
||||
"--vscode-diffEditor-insertedTextBackground": "#9ccc6520",
|
||||
"--vscode-editor-font-family": 'Menlo, Monaco, "Courier New", monospace',
|
||||
"--vscode-editor-font-size": "12px",
|
||||
"--vscode-font-family": '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||
"--vscode-font-size": "13px",
|
||||
"--vscode-focusBorder": "#007ACC",
|
||||
"--vscode-input-border": "#6B6B6B",
|
||||
"--vscode-banner-background": "#007ACC",
|
||||
"--vscode-titleBar-inactiveForeground": "#BBBBBB",
|
||||
"--vscode-input-foreground": "#D4D4D4",
|
||||
"--vscode-input-background": "#383B3D",
|
||||
"--vscode-editor-line-height": "20px",
|
||||
"--vscode-input-placeholderForeground": "#A6A6A6",
|
||||
}
|
||||
|
||||
const mockVSCodeLightTheme = {
|
||||
"--vscode-editor-background": "#FFFFFF",
|
||||
"--vscode-editor-foreground": "#000000",
|
||||
"--vscode-sideBar-background": "#F3F3F3",
|
||||
"--vscode-editorGroup-border": "#D4D4D4",
|
||||
"--vscode-descriptionForeground": "#6F6F6F",
|
||||
"--vscode-button-background": "#007ACC",
|
||||
"--vscode-button-foreground": "#FFFFFF",
|
||||
"--vscode-button-secondaryBackground": "#E8E8E8",
|
||||
"--vscode-textLink-foreground": "#007ACC",
|
||||
"--vscode-errorForeground": "#c72e0f",
|
||||
"--vscode-editorWidget-border": "#D4D4D4",
|
||||
"--vscode-editorError-foreground": "#c72e0f",
|
||||
"--vscode-diffEditor-removedTextBackground": "#c72e0f20",
|
||||
"--vscode-diffEditor-insertedTextBackground": "#36943220",
|
||||
"--vscode-editor-font-family": 'Menlo, Monaco, "Courier New", monospace',
|
||||
"--vscode-editor-font-size": "12px",
|
||||
"--vscode-font-family": '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||
"--vscode-font-size": "13px",
|
||||
"--vscode-focusBorder": "#007ACC",
|
||||
"--vscode-input-border": "#CECECE",
|
||||
"--vscode-banner-background": "#007ACC",
|
||||
"--vscode-titleBar-inactiveForeground": "#919191",
|
||||
"--vscode-input-foreground": "#000000",
|
||||
"--vscode-input-background": "#FFFFFF",
|
||||
"--vscode-editor-line-height": "20px",
|
||||
"--vscode-input-placeholderForeground": "#767676",
|
||||
}
|
||||
|
||||
// Mock VSCode theme variables for Storybook
|
||||
export const StorybookThemes = {
|
||||
dark: mockVSCodeDarkTheme,
|
||||
light: mockVSCodeLightTheme,
|
||||
}
|
||||
Generated
+1012
-259
File diff suppressed because it is too large
Load Diff
@@ -12,15 +12,18 @@
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest dev",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"devtools": "react-devtools"
|
||||
"devtools": "react-devtools",
|
||||
"storybook": "storybook build && storybook dev -p 6006"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.4",
|
||||
"@fontsource/azeret-mono": "^5.2.9",
|
||||
"@heroui/react": "^2.8.0-beta.2",
|
||||
"@storybook/react-vite": "^9.0.16",
|
||||
"@vscode/webview-ui-toolkit": "^1.4.0",
|
||||
"debounce": "^2.1.1",
|
||||
"dompurify": "^3.2.4",
|
||||
"eslint-plugin-storybook": "^9.0.16",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"firebase": "^11.3.0",
|
||||
"framer-motion": "^12.7.4",
|
||||
@@ -40,6 +43,7 @@
|
||||
"rehype-parse": "^9.0.1",
|
||||
"rehype-remark": "^10.0.1",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"storybook": "^9.0.16",
|
||||
"styled-components": "^6.1.15",
|
||||
"unified": "^11.0.5",
|
||||
"uuid": "^9.0.1"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,673 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
|
||||
import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import ChatView from "./ChatView"
|
||||
import { StorybookProvider, VSCodeWebview } from "../common/StorybookDecorator"
|
||||
|
||||
const sidebarViewClassNames = "w-[80px]"
|
||||
|
||||
const meta: Meta<typeof ChatView> = {
|
||||
title: "Views/ChatView",
|
||||
component: ChatView,
|
||||
decorators: [VSCodeWebview],
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof ChatView>
|
||||
|
||||
export const Default: StoryObj<typeof meta> = {}
|
||||
|
||||
// Mock API configuration
|
||||
const mockApiConfiguration: ApiConfiguration = {
|
||||
actModeApiProvider: "anthropic",
|
||||
actModeApiModelId: "claude-3-5-sonnet-20241022",
|
||||
apiKey: "mock-key",
|
||||
}
|
||||
|
||||
// Mock task history
|
||||
const mockTaskHistory: HistoryItem[] = [
|
||||
{
|
||||
id: "task-1",
|
||||
ts: Date.now() - 3600000, // 1 hour ago
|
||||
task: "Create a React component for displaying user profiles",
|
||||
tokensIn: 1500,
|
||||
tokensOut: 800,
|
||||
cacheWrites: 200,
|
||||
cacheReads: 100,
|
||||
totalCost: 0.05,
|
||||
},
|
||||
{
|
||||
id: "task-2",
|
||||
ts: Date.now() - 7200000, // 2 hours ago
|
||||
task: "Debug the authentication flow in the login system",
|
||||
tokensIn: 2200,
|
||||
tokensOut: 1200,
|
||||
cacheWrites: 300,
|
||||
cacheReads: 150,
|
||||
totalCost: 0.08,
|
||||
},
|
||||
{
|
||||
id: "task-3",
|
||||
ts: Date.now() - 86400000, // 1 day ago
|
||||
task: "Optimize database queries for better performance",
|
||||
tokensIn: 3000,
|
||||
tokensOut: 1800,
|
||||
cacheWrites: 500,
|
||||
cacheReads: 250,
|
||||
totalCost: 0.12,
|
||||
},
|
||||
]
|
||||
|
||||
// Mock messages for active conversation
|
||||
const mockActiveMessages: ClineMessage[] = [
|
||||
{
|
||||
ts: Date.now() - 300000, // 5 minutes ago
|
||||
type: "say",
|
||||
say: "task",
|
||||
text: "Help me create a responsive navigation component for a React application",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 280000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "I'll help you create a responsive navigation component for your React application. Let me start by examining your current project structure and then create a modern, accessible navigation component.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 260000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "listFilesTopLevel",
|
||||
path: "src/components",
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 240000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "Based on your project structure, I'll create a responsive navigation component with the following features:\n\n- Mobile-first responsive design\n- Accessible keyboard navigation\n- Smooth animations\n- Support for nested menu items\n- Dark/light theme support",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 220000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "newFileCreated",
|
||||
path: "src/components/Navigation/Navigation.tsx",
|
||||
content: `import React, { useState } from 'react'
|
||||
import './Navigation.css'
|
||||
|
||||
interface NavigationProps {
|
||||
items: NavigationItem[]
|
||||
theme?: 'light' | 'dark'
|
||||
}
|
||||
|
||||
interface NavigationItem {
|
||||
label: string
|
||||
href: string
|
||||
children?: NavigationItem[]
|
||||
}
|
||||
|
||||
export const Navigation: React.FC<NavigationProps> = ({ items, theme = 'light' }) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<nav className={\`navigation navigation--\${theme}\`}>
|
||||
<div className="navigation__container">
|
||||
<button
|
||||
className="navigation__toggle"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
Menu
|
||||
</button>
|
||||
<ul className={\`navigation__list \${isOpen ? 'navigation__list--open' : ''}\`}>
|
||||
{items.map((item, index) => (
|
||||
<li key={index} className="navigation__item">
|
||||
<a href={item.href} className="navigation__link">
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}`,
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 200000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "I've created a responsive navigation component with TypeScript support. The component includes:\n\n✅ Mobile-first responsive design\n✅ Accessible ARIA attributes\n✅ Toggle functionality for mobile\n✅ TypeScript interfaces for type safety\n✅ Theme support\n\nWould you like me to also create the CSS styles for this component?",
|
||||
},
|
||||
]
|
||||
|
||||
// Mock streaming message
|
||||
const mockStreamingMessages: ClineMessage[] = [
|
||||
...mockActiveMessages,
|
||||
{
|
||||
ts: Date.now() - 10000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "Now I'll create the CSS styles for the navigation component. This will include responsive breakpoints, smooth animations, and accessibility features...",
|
||||
partial: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const WelcomeScreen: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
welcomeViewCompleted: false,
|
||||
showWelcome: true,
|
||||
clineMessages: [],
|
||||
taskHistory: mockTaskHistory,
|
||||
apiConfiguration: mockApiConfiguration,
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const ActiveConversation: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<StorybookProvider
|
||||
mockState={{
|
||||
welcomeViewCompleted: true,
|
||||
clineMessages: mockActiveMessages,
|
||||
taskHistory: mockTaskHistory,
|
||||
apiConfiguration: mockApiConfiguration,
|
||||
}}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
// Streaming response
|
||||
export const StreamingResponse: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
welcomeViewCompleted: true,
|
||||
showWelcome: false,
|
||||
clineMessages: mockStreamingMessages,
|
||||
taskHistory: mockTaskHistory,
|
||||
apiConfiguration: mockApiConfiguration,
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Shows a streaming response in progress, demonstrating real-time AI response rendering.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Long conversation
|
||||
export const LongConversation: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const longMessages: ClineMessage[] = [
|
||||
{
|
||||
ts: Date.now() - 1800000, // 30 minutes ago
|
||||
type: "say",
|
||||
say: "task",
|
||||
text: "Help me build a complete e-commerce application with React, Node.js, and MongoDB",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1780000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "I'll help you build a complete e-commerce application. Let's start by setting up the project structure and implementing the core features step by step.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1760000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "newFileCreated",
|
||||
path: "package.json",
|
||||
content: `{
|
||||
"name": "ecommerce-app",
|
||||
"version": "1.0.0",
|
||||
"description": "Full-stack e-commerce application",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"dev": "concurrently \\"npm run server\\" \\"npm run client\\"",
|
||||
"server": "nodemon server.js",
|
||||
"client": "cd client && npm start"
|
||||
}
|
||||
}`,
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1740000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "Great! I've set up the initial package.json. Now let's create the backend server with Express and MongoDB integration.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1720000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "newFileCreated",
|
||||
path: "server.js",
|
||||
content: `const express = require('express')
|
||||
const mongoose = require('mongoose')
|
||||
const cors = require('cors')
|
||||
const productRoutes = require('./routes/products')
|
||||
const userRoutes = require('./routes/users')
|
||||
const orderRoutes = require('./routes/orders')
|
||||
|
||||
const app = express()
|
||||
const PORT = process.env.PORT || 5000
|
||||
|
||||
// Middleware
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
// Routes
|
||||
app.use('/api/products', productRoutes)
|
||||
app.use('/api/users', userRoutes)
|
||||
app.use('/api/orders', orderRoutes)
|
||||
|
||||
// MongoDB connection
|
||||
mongoose.connect('mongodb://localhost:27017/ecommerce', {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true
|
||||
})
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(\`Server running on port \${PORT}\`)
|
||||
})`,
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1700000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "Perfect! The backend server is set up. Now let's create the product model and routes for handling product operations.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1680000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "newFileCreated",
|
||||
path: "models/Product.js",
|
||||
content: `const mongoose = require('mongoose')
|
||||
|
||||
const productSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
required: true,
|
||||
min: 0
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
stock: {
|
||||
type: Number,
|
||||
required: true,
|
||||
min: 0
|
||||
},
|
||||
images: [{
|
||||
type: String
|
||||
}],
|
||||
ratings: {
|
||||
average: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('Product', productSchema)`,
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1660000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "Excellent! The Product model is ready with all necessary fields. Now let's create the React frontend with a modern component structure.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1640000,
|
||||
type: "say",
|
||||
say: "command",
|
||||
text: "cd client && npx create-react-app . --template typescript",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1620000,
|
||||
type: "say",
|
||||
say: "command_output",
|
||||
text: "Creating a new React app in /path/to/project/client...\n\nInstalling packages. This might take a couple of minutes.\nInstalling react, react-dom, and react-scripts with cra-template-typescript...\n\nSuccess! Created client at /path/to/project/client",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1600000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "Great! The React frontend is set up with TypeScript. Now let's create the main components for our e-commerce application.",
|
||||
},
|
||||
]
|
||||
|
||||
const mockState = {
|
||||
welcomeViewCompleted: true,
|
||||
showWelcome: false,
|
||||
clineMessages: longMessages,
|
||||
taskHistory: mockTaskHistory,
|
||||
apiConfiguration: mockApiConfiguration,
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<div className={sidebarViewClassNames}>
|
||||
<Story />
|
||||
</div>
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "A longer conversation showing multiple tool uses, file creation, and command execution in a complex development task.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Error state
|
||||
export const ErrorState: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const errorMessages: ClineMessage[] = [
|
||||
{
|
||||
ts: Date.now() - 300000,
|
||||
type: "say",
|
||||
say: "task",
|
||||
text: "Help me fix the build errors in my React application",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 280000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "I'll help you fix the build errors. Let me first examine the current state of your application.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 260000,
|
||||
type: "say",
|
||||
say: "command",
|
||||
text: "npm run build",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 240000,
|
||||
type: "say",
|
||||
say: "error",
|
||||
text: "Build failed with the following errors:\n\nTypeScript error in src/components/UserProfile.tsx(15,23):\nProperty 'username' does not exist on type 'User'.\n\nTypeScript error in src/utils/api.ts(42,15):\nArgument of type 'string | undefined' is not assignable to parameter of type 'string'.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 220000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "I can see there are TypeScript errors in your code. Let me examine the files and fix these issues.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 200000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "readFile",
|
||||
path: "src/components/UserProfile.tsx",
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 180000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "I found the issue. The User type doesn't have a 'username' property. Let me fix this by updating the component to use the correct property name.",
|
||||
},
|
||||
]
|
||||
|
||||
const mockState = {
|
||||
welcomeViewCompleted: true,
|
||||
showWelcome: false,
|
||||
clineMessages: errorMessages,
|
||||
taskHistory: mockTaskHistory,
|
||||
apiConfiguration: mockApiConfiguration,
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<div className={sidebarViewClassNames}>
|
||||
<Story />
|
||||
</div>
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Shows how Cline handles and displays error messages, helping users understand and resolve issues.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Auto-approval enabled
|
||||
export const AutoApprovalEnabled: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
welcomeViewCompleted: true,
|
||||
showWelcome: false,
|
||||
clineMessages: mockActiveMessages,
|
||||
taskHistory: mockTaskHistory,
|
||||
apiConfiguration: mockApiConfiguration,
|
||||
autoApprovalSettings: {
|
||||
...DEFAULT_AUTO_APPROVAL_SETTINGS,
|
||||
enabled: true,
|
||||
maxRequestsPerTask: 10,
|
||||
maxRequestsPerHour: 50,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<div className={sidebarViewClassNames}>
|
||||
<Story />
|
||||
</div>
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Shows the interface with auto-approval enabled, allowing Cline to execute certain actions automatically without user confirmation.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Plan mode
|
||||
export const PlanMode: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const planModeMessages: ClineMessage[] = [
|
||||
{
|
||||
ts: Date.now() - 300000,
|
||||
type: "say",
|
||||
say: "task",
|
||||
text: "Help me refactor my React application to use TypeScript and improve performance",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 280000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "I'll help you refactor your React application to use TypeScript and improve performance. Let me create a detailed plan for this migration.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 260000,
|
||||
type: "ask",
|
||||
ask: "plan_mode_respond",
|
||||
text: JSON.stringify({
|
||||
response:
|
||||
"Here's my comprehensive plan for refactoring your React application:\n\n## Phase 1: TypeScript Migration\n1. **Setup TypeScript configuration**\n - Install TypeScript and related dependencies\n - Configure tsconfig.json with strict settings\n - Set up ESLint with TypeScript rules\n\n2. **Convert JavaScript files to TypeScript**\n - Start with utility functions and constants\n - Convert React components one by one\n - Add proper type definitions for props and state\n\n## Phase 2: Performance Optimization\n1. **Code splitting and lazy loading**\n - Implement React.lazy() for route-based splitting\n - Add Suspense boundaries\n\n2. **Memoization and optimization**\n - Use React.memo for expensive components\n - Implement useMemo and useCallback where appropriate\n - Optimize re-renders with proper dependency arrays\n\n## Phase 3: Bundle Optimization\n1. **Webpack optimization**\n - Configure tree shaking\n - Optimize chunk splitting\n - Implement proper caching strategies\n\nWould you like me to proceed with this plan, or would you like to modify any part of it?",
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
const mockState = {
|
||||
welcomeViewCompleted: true,
|
||||
showWelcome: false,
|
||||
clineMessages: planModeMessages,
|
||||
taskHistory: mockTaskHistory,
|
||||
apiConfiguration: mockApiConfiguration,
|
||||
chatSettings: {
|
||||
...DEFAULT_CHAT_SETTINGS,
|
||||
mode: "plan" as const,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Shows Cline in Plan mode, where it focuses on creating detailed plans and discussing approaches before implementation.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Browser automation
|
||||
export const BrowserAutomation: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const browserMessages: ClineMessage[] = [
|
||||
{
|
||||
ts: Date.now() - 300000,
|
||||
type: "say",
|
||||
say: "task",
|
||||
text: "Help me test the login functionality on my web application",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 280000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "I'll help you test the login functionality. Let me launch a browser and navigate to your application.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 260000,
|
||||
type: "say",
|
||||
say: "browser_action_launch",
|
||||
text: JSON.stringify({
|
||||
action: "launch",
|
||||
url: "http://localhost:3000/login",
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 240000,
|
||||
type: "say",
|
||||
say: "browser_action_result",
|
||||
text: JSON.stringify({
|
||||
screenshot:
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
|
||||
currentUrl: "http://localhost:3000/login",
|
||||
logs: "Page loaded successfully",
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 220000,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text: "Great! The browser has launched and navigated to your login page. I can see the login form is displayed. Now let me test the login functionality by filling in the form.",
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 200000,
|
||||
type: "say",
|
||||
say: "browser_action",
|
||||
text: JSON.stringify({
|
||||
action: "click",
|
||||
coordinate: "400,200",
|
||||
}),
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 180000,
|
||||
type: "say",
|
||||
say: "browser_action",
|
||||
text: JSON.stringify({
|
||||
action: "type",
|
||||
text: "test@example.com",
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
const mockState = {
|
||||
welcomeViewCompleted: true,
|
||||
showWelcome: false,
|
||||
clineMessages: browserMessages,
|
||||
taskHistory: mockTaskHistory,
|
||||
apiConfiguration: mockApiConfiguration,
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Shows Cline performing browser automation tasks, including launching browsers, clicking elements, and testing web applications.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import React from "react"
|
||||
import CreditLimitError from "./CreditLimitError"
|
||||
import { StorybookProvider } from "../common/StorybookDecorator"
|
||||
|
||||
const meta: Meta<typeof CreditLimitError> = {
|
||||
title: "Component/CreditLimitError",
|
||||
component: CreditLimitError,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<StorybookProvider>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
argTypes: {
|
||||
currentBalance: {
|
||||
control: { type: "number", min: 0, max: 10000000, step: 100000 },
|
||||
description: "Current balance in microcredits (1 credit = 1,000,000 microcredits)",
|
||||
},
|
||||
totalSpent: {
|
||||
control: { type: "number", min: 0, max: 100000000, step: 1000000 },
|
||||
description: "Total amount spent in microcredits (optional, for future use)",
|
||||
},
|
||||
totalPromotions: {
|
||||
control: { type: "number", min: 0, max: 10000000, step: 100000 },
|
||||
description: "Total promotional credits in microcredits (optional, for future use)",
|
||||
},
|
||||
message: {
|
||||
control: "text",
|
||||
description: "Custom error message to display to the user",
|
||||
},
|
||||
buyCreditsUrl: {
|
||||
control: "text",
|
||||
description: "URL for the credit purchase page",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof CreditLimitError>
|
||||
|
||||
// Default credit limit error
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
currentBalance: 0,
|
||||
message: "You have run out of credit.",
|
||||
buyCreditsUrl: "https://app.cline.bot/dashboard",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Default credit limit error with zero balance and standard message.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// With usage statistics
|
||||
export const WithUsageStats: Story = {
|
||||
args: {
|
||||
currentBalance: 500000, // $0.5000
|
||||
totalSpent: 15000000, // $15.0000
|
||||
totalPromotions: 2000000, // $2.0000
|
||||
message: "Your current request would exceed your remaining credit balance.",
|
||||
buyCreditsUrl: "https://app.cline.bot/dashboard",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Component showing usage statistics including total spent and promotional credits (for future implementation).",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Interactive demo
|
||||
export const InteractiveDemo: Story = {
|
||||
args: {
|
||||
currentBalance: 0,
|
||||
totalSpent: 15000000, // $15.0000
|
||||
totalPromotions: 0,
|
||||
message: "You have run out of credit.",
|
||||
buyCreditsUrl: "https://app.cline.bot/dashboard/account?tab=credits&redirect=true",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const [retryCount, setRetryCount] = React.useState(0)
|
||||
const [lastAction, setLastAction] = React.useState<string>("")
|
||||
|
||||
// Mock the TaskServiceClient for demo purposes
|
||||
React.useEffect(() => {
|
||||
const originalConsoleLog = console.log
|
||||
console.log = (...args) => {
|
||||
if (args[0]?.includes?.("TaskServiceClient")) {
|
||||
setLastAction("Retry request sent to extension")
|
||||
setRetryCount((prev) => prev + 1)
|
||||
}
|
||||
originalConsoleLog(...args)
|
||||
}
|
||||
|
||||
return () => {
|
||||
console.log = originalConsoleLog
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<StorybookProvider>
|
||||
<div className="max-w-md space-y-4">
|
||||
<Story />
|
||||
{(retryCount > 0 || lastAction) && (
|
||||
<div className="p-3 bg-[var(--vscode-textBlockQuote-background)] rounded border text-sm">
|
||||
<div className="font-semibold mb-1">Demo Actions:</div>
|
||||
{retryCount > 0 && <div>Retry attempts: {retryCount}</div>}
|
||||
{lastAction && <div>Last action: {lastAction}</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Interactive demo showing retry functionality with action tracking for demonstration purposes.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Long message handling
|
||||
export const LongMessage: Story = {
|
||||
args: {
|
||||
currentBalance: 0,
|
||||
message:
|
||||
"You have exceeded your credit limit for this billing period. This can happen when making many API requests or using advanced AI models that consume more credits per request. To continue using Cline's AI-powered features, please purchase additional credits from your dashboard. Your current usage will be preserved and you can resume immediately after adding credits to your account.",
|
||||
buyCreditsUrl: "https://app.cline.bot/dashboard",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Component handling long error messages, demonstrating text wrapping and layout behavior.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Compact layout
|
||||
export const CompactLayout: Story = {
|
||||
args: {
|
||||
currentBalance: 1000000, // $1.0000
|
||||
message: "Low credits",
|
||||
buyCreditsUrl: "https://app.cline.bot/dashboard",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<StorybookProvider>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Component in a compact layout with shorter message, suitable for narrow interfaces.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import React from "react"
|
||||
import UserMessage from "./UserMessage"
|
||||
import { StorybookProvider } from "../common/StorybookDecorator"
|
||||
|
||||
const meta: Meta<typeof UserMessage> = {
|
||||
title: "Component/UserMessage",
|
||||
component: UserMessage,
|
||||
parameters: {
|
||||
layout: "padded",
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
The UserMessage component displays user messages in the chat interface with editing and checkpoint restore capabilities.
|
||||
|
||||
**Features:**
|
||||
- **Editable Messages**: Click to edit message text with auto-resizing textarea
|
||||
- **Checkpoint Restore**: Restore chat or workspace to previous states when editing
|
||||
- **File Attachments**: Display thumbnails for attached images and files
|
||||
- **Keyboard Shortcuts**:
|
||||
- Escape: Cancel editing
|
||||
- Enter: Restore chat and send edited message
|
||||
- Cmd/Ctrl+Enter: Restore all (chat + workspace) and send edited message
|
||||
- **Focus Management**: Proper handling of focus states and blur events
|
||||
- **Text Highlighting**: Supports text highlighting and formatting
|
||||
- **Error Handling**: Graceful handling of checkpoint tracker errors
|
||||
|
||||
**Restore Types:**
|
||||
- **Restore Chat**: Restores just the conversation history to the checkpoint
|
||||
- **Restore All**: Restores both conversation and workspace files to the checkpoint
|
||||
|
||||
**Use Cases:**
|
||||
- Displaying user messages in chat conversations
|
||||
- Editing and resending messages with checkpoint restoration
|
||||
- Showing attached files and images with messages
|
||||
- Managing conversation history and workspace state
|
||||
|
||||
**Interaction Flow:**
|
||||
1. Click message to enter edit mode
|
||||
2. Modify text as needed
|
||||
3. Use restore buttons or keyboard shortcuts to apply changes
|
||||
4. System restores to checkpoint and sends edited message
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<StorybookProvider>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
argTypes: {
|
||||
text: {
|
||||
control: "text",
|
||||
description: "The message text content",
|
||||
},
|
||||
images: {
|
||||
control: "object",
|
||||
description: "Array of image file paths or URLs",
|
||||
},
|
||||
files: {
|
||||
control: "object",
|
||||
description: "Array of file paths",
|
||||
},
|
||||
messageTs: {
|
||||
control: "number",
|
||||
description: "Timestamp for the message (used for checkpoint restore)",
|
||||
},
|
||||
sendMessageFromChatRow: {
|
||||
action: "sendMessageFromChatRow",
|
||||
description: "Callback function when message is resent after editing",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof UserMessage>
|
||||
|
||||
// Basic text message
|
||||
export const BasicText: Story = {
|
||||
args: {
|
||||
text: "Create a simple todo app with HTML, CSS, and JavaScript",
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "A basic user message with just text content. Click to edit and see the restore options.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Message with images
|
||||
export const WithImages: Story = {
|
||||
args: {
|
||||
text: "Please analyze these screenshots and help me fix the layout issues",
|
||||
images: ["/path/to/screenshot1.png", "/path/to/screenshot2.png", "/path/to/design-mockup.jpg"],
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "User message with attached images. Shows thumbnail display below the message text.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Message with files
|
||||
export const WithFiles: Story = {
|
||||
args: {
|
||||
text: "Review these configuration files and suggest improvements",
|
||||
files: ["/path/to/package.json", "/path/to/tsconfig.json", "/path/to/webpack.config.js", "/path/to/README.md"],
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "User message with attached files. File thumbnails are displayed with appropriate icons.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Message with both images and files
|
||||
export const WithImagesAndFiles: Story = {
|
||||
args: {
|
||||
text: "Here's the current state of the project with some design mockups and the source files",
|
||||
images: ["/path/to/current-state.png", "/path/to/desired-design.jpg"],
|
||||
files: ["/path/to/src/App.tsx", "/path/to/src/components/Header.tsx", "/path/to/styles/main.css"],
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "User message with both images and files attached, demonstrating the full attachment display.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Long text message
|
||||
export const LongText: Story = {
|
||||
args: {
|
||||
text: `I need help creating a comprehensive web application with the following requirements:
|
||||
|
||||
1. User authentication system with login/logout functionality
|
||||
2. Dashboard with data visualization charts
|
||||
3. CRUD operations for managing user profiles
|
||||
4. Real-time notifications using WebSockets
|
||||
5. Responsive design that works on mobile and desktop
|
||||
6. Integration with external APIs for data fetching
|
||||
7. Proper error handling and loading states
|
||||
8. Unit tests for all components
|
||||
9. Documentation for the API endpoints
|
||||
10. Deployment configuration for production
|
||||
|
||||
The application should be built using React with TypeScript, and I'd like to use modern best practices for state management, routing, and styling. Please provide a detailed implementation plan and help me get started with the project structure.`,
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "A longer user message demonstrating text wrapping and the editing experience with more content.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Empty message
|
||||
export const EmptyMessage: Story = {
|
||||
args: {
|
||||
text: "",
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Handles empty message text gracefully. Still allows editing to add content.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Message with special characters and formatting
|
||||
export const WithSpecialCharacters: Story = {
|
||||
args: {
|
||||
text: `Here's some code with special characters:
|
||||
|
||||
const greeting = "Hello, World! 👋";
|
||||
const math = 2 + 2 = 4;
|
||||
const symbols = !@#$%^&*()_+-=[]{}|;':",./<>?
|
||||
|
||||
And some Unicode: 🚀 ✨ 💻 🎉 ⚡ 🔥`,
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Message containing special characters, code snippets, and Unicode emojis.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Message with checkpoint tracker error
|
||||
export const WithCheckpointError: Story = {
|
||||
args: {
|
||||
text: "This message demonstrates the state when checkpoint tracking has an error",
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
checkpointTrackerErrorMessage: "Git repository not found. Checkpoint functionality is disabled.",
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Message state when checkpoint tracker has an error. Only 'Restore Chat' button is available, and Cmd+Enter shortcut is disabled.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Simulated editing state
|
||||
export const EditingState: Story = {
|
||||
args: {
|
||||
text: "Click this message to see the editing interface",
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
// Custom wrapper to demonstrate editing state
|
||||
const EditingDemo = () => {
|
||||
const [isEditing, setIsEditing] = React.useState(false)
|
||||
const [editedText, setEditedText] = React.useState("Click this message to see the editing interface")
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: "600px", margin: "0 auto", padding: "16px" }}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: isEditing ? "unset" : "var(--vscode-badge-background)",
|
||||
color: "var(--vscode-badge-foreground)",
|
||||
borderRadius: "3px",
|
||||
padding: "9px",
|
||||
whiteSpace: "pre-line",
|
||||
wordWrap: "break-word",
|
||||
cursor: isEditing ? "default" : "pointer",
|
||||
}}
|
||||
onClick={() => !isEditing && setIsEditing(true)}>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<textarea
|
||||
value={editedText}
|
||||
onChange={(e) => setEditedText(e.target.value)}
|
||||
autoFocus
|
||||
placeholder="Edit your message..."
|
||||
aria-label="Edit message text"
|
||||
title="Edit message text"
|
||||
style={{
|
||||
width: "100%",
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
borderColor: "var(--vscode-input-border)",
|
||||
border: "1px solid",
|
||||
borderRadius: "2px",
|
||||
padding: "6px",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "inherit",
|
||||
lineHeight: "inherit",
|
||||
boxSizing: "border-box",
|
||||
resize: "none",
|
||||
minHeight: "60px",
|
||||
}}
|
||||
/>
|
||||
<div style={{ display: "flex", gap: "8px", marginTop: "8px", justifyContent: "flex-end" }}>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
console.log("Restore All clicked")
|
||||
setIsEditing(false)
|
||||
}}
|
||||
style={{
|
||||
backgroundColor:
|
||||
"var(--vscode-button-secondaryBackground, var(--vscode-descriptionForeground))",
|
||||
color: "var(--vscode-button-secondaryForeground, var(--vscode-foreground))",
|
||||
border: "none",
|
||||
padding: "4px 8px",
|
||||
borderRadius: "2px",
|
||||
fontSize: "9px",
|
||||
cursor: "pointer",
|
||||
}}>
|
||||
Restore All
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
console.log("Restore Chat clicked")
|
||||
setIsEditing(false)
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: "var(--vscode-button-background)",
|
||||
color: "var(--vscode-button-foreground)",
|
||||
border: "none",
|
||||
padding: "4px 8px",
|
||||
borderRadius: "2px",
|
||||
fontSize: "9px",
|
||||
cursor: "pointer",
|
||||
}}>
|
||||
Restore Chat
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className="ph-no-capture" style={{ display: "block" }}>
|
||||
{editedText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <EditingDemo />
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Interactive demonstration of the editing state with restore buttons. Click the message to enter edit mode.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Message without timestamp (no checkpoint functionality)
|
||||
export const WithoutTimestamp: Story = {
|
||||
args: {
|
||||
text: "This message has no timestamp, so checkpoint restore functionality is not available",
|
||||
// messageTs is undefined
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Message without a timestamp. Editing is still possible but checkpoint restore buttons won't function.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Message with line breaks and formatting
|
||||
export const WithLineBreaks: Story = {
|
||||
args: {
|
||||
text: `This message contains multiple lines:
|
||||
|
||||
Line 1: Introduction
|
||||
Line 2: Details about the request
|
||||
Line 3: Additional context
|
||||
|
||||
And some formatting:
|
||||
- Bullet point 1
|
||||
- Bullet point 2
|
||||
- Bullet point 3
|
||||
|
||||
Final paragraph with conclusion.`,
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Message with line breaks and basic formatting to demonstrate text rendering with whitespace preservation.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Comprehensive example with all features
|
||||
export const ComprehensiveExample: Story = {
|
||||
args: {
|
||||
text: `Please help me implement a new feature for the dashboard:
|
||||
|
||||
Requirements:
|
||||
1. Add a new chart component for sales data
|
||||
2. Include filtering options by date range
|
||||
3. Make it responsive for mobile devices
|
||||
|
||||
I've attached the current dashboard screenshot and the design mockup for reference.`,
|
||||
images: ["/path/to/current-dashboard.png", "/path/to/new-design-mockup.jpg"],
|
||||
files: ["/path/to/src/Dashboard.tsx", "/path/to/src/components/Chart.tsx", "/path/to/styles/dashboard.css"],
|
||||
messageTs: Date.now(),
|
||||
sendMessageFromChatRow: (text: string, images: string[], files: string[]) => {
|
||||
console.log("Resending message:", { text, images, files })
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Comprehensive example showing a realistic user message with text, images, and files - demonstrating all component features together.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from "react"
|
||||
import { ClineAuthProvider } from "@/context/ClineAuthContext"
|
||||
import { ExtensionStateContextProvider, useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ExtensionState } from "@shared/ExtensionMessage"
|
||||
import { Decorator } from "@storybook/react-vite"
|
||||
import { StorybookThemes } from "../../../.storybook/themes"
|
||||
|
||||
// Inner component that uses the context
|
||||
const StorybookStateUpdater: React.FC<{
|
||||
children: React.ReactNode
|
||||
mockState?: Partial<ExtensionState>
|
||||
}> = ({ mockState, children }) => {
|
||||
const { setExtensionStateForTest } = useExtensionState()
|
||||
|
||||
// Set state when mockState changes
|
||||
React.useEffect(() => {
|
||||
if (mockState) {
|
||||
setExtensionStateForTest(mockState)
|
||||
}
|
||||
}, [mockState, setExtensionStateForTest])
|
||||
|
||||
return <div className="container">{children}</div>
|
||||
}
|
||||
|
||||
export const StorybookProvider: React.FC<{
|
||||
children: React.ReactNode
|
||||
mockState?: Partial<ExtensionState>
|
||||
}> = ({ mockState, children }) => {
|
||||
return <StorybookStateUpdater mockState={mockState}>{children}</StorybookStateUpdater>
|
||||
}
|
||||
|
||||
// Component that handles theme switching
|
||||
const ThemeHandler: React.FC<{ children: React.ReactNode; theme?: string }> = ({ children, theme }) => {
|
||||
React.useEffect(() => {
|
||||
const styles = theme?.includes("light") ? StorybookThemes.light : StorybookThemes.dark
|
||||
|
||||
// Apply CSS variables to the document root
|
||||
const root = document.documentElement
|
||||
Object.entries(styles).forEach(([property, value]) => {
|
||||
root.style.setProperty(property, value)
|
||||
})
|
||||
|
||||
document.body.style.backgroundColor = styles["--vscode-editor-background"]
|
||||
document.body.style.color = styles["--vscode-editor-foreground"]
|
||||
document.body.style.fontFamily = styles["--vscode-font-family"]
|
||||
document.body.style.fontSize = styles["--vscode-font-size"]
|
||||
|
||||
return () => {
|
||||
// Cleanup on unmount
|
||||
Object.keys(styles).forEach((property) => {
|
||||
root.style.removeProperty(property)
|
||||
})
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
function VSCodeDecorator(className: string | undefined): Decorator {
|
||||
return (story, parameters) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<ExtensionStateContextProvider>
|
||||
<ClineAuthProvider>
|
||||
<ThemeHandler theme={parameters?.globals?.theme}>{React.createElement(story)}</ThemeHandler>
|
||||
</ClineAuthProvider>
|
||||
</ExtensionStateContextProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const VSCodeWebview = VSCodeDecorator("relative")
|
||||
@@ -0,0 +1,198 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import McpResponseDisplay from "./McpResponseDisplay"
|
||||
import { StorybookProvider, VSCodeWebview } from "@/components/common/StorybookDecorator"
|
||||
import { ExtensionState } from "@shared/ExtensionMessage"
|
||||
|
||||
const meta: Meta<typeof McpResponseDisplay> = {
|
||||
title: "Component/mcpDisplayMode",
|
||||
component: McpResponseDisplay,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
The McpResponseDisplay component renders MCP (Model Context Protocol) server responses with rich content support.
|
||||
|
||||
**Features:**
|
||||
- **Rich Display Mode**: Automatically detects and renders images and link previews
|
||||
- **Plain Text Mode**: Fallback mode for simple text display
|
||||
- **Collapsible**: Can be expanded/collapsed with header controls
|
||||
- **Toggle Switch**: Users can switch between rich and plain display modes
|
||||
- **URL Processing**: Extracts and processes URLs from response text
|
||||
- **Error Handling**: Graceful error handling with fallbacks
|
||||
- **Loading States**: Shows progress indicators during content processing
|
||||
|
||||
**Use Cases:**
|
||||
- Displaying MCP server responses in chat interfaces
|
||||
- Showing rich content like images and link previews
|
||||
- Providing user control over content display modes
|
||||
|
||||
**Note**: In Storybook, rich content features may be limited due to mocking constraints.
|
||||
The component will demonstrate the basic text display and UI structure.
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [VSCodeWebview],
|
||||
argTypes: {
|
||||
responseText: {
|
||||
control: "text",
|
||||
description: "The response text to display",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const defaultSettings = {
|
||||
mcpDisplayMode: "plain",
|
||||
mcpResponsesCollapsed: false,
|
||||
} satisfies Partial<ExtensionState>
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof McpResponseDisplay>
|
||||
|
||||
// Basic text response
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
responseText: `Here's a simple text response from an MCP server.
|
||||
|
||||
This response contains multiple paragraphs and demonstrates the basic text rendering capabilities of the component.
|
||||
|
||||
The text is displayed in a monospace font with proper line breaks and formatting.`,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "A basic text response without any URLs or rich content in plain mode.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Response with URLs (will show as plain text in Storybook)
|
||||
export const LinksPreview: Story = {
|
||||
args: {
|
||||
responseText: `Here's a response that contains URLs:
|
||||
|
||||
Documentation: https://example.com/docs
|
||||
API reference: https://api.example.com/reference
|
||||
Sample image: https://via.placeholder.com/600x400/0066cc/ffffff?text=Sample+Image
|
||||
|
||||
In the actual application, these URLs would be processed for rich display when rich mode is enabled.`,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Response containing URLs. In the real application, these would be processed for rich display.",
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
...defaultSettings,
|
||||
mcpDisplayMode: "rich",
|
||||
} satisfies Partial<ExtensionState>
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Mixed content response
|
||||
export const MixedContent: Story = {
|
||||
args: {
|
||||
responseText: `Here's a comprehensive response with mixed content:
|
||||
|
||||
## Documentation Links
|
||||
- Main docs: https://example.com/docs
|
||||
- API guide: https://api.example.com/guide
|
||||
|
||||
## Sample Images
|
||||
Here's a screenshot: https://via.placeholder.com/500x300/28a745/ffffff?text=Screenshot
|
||||
And a diagram: https://via.placeholder.com/400x400/6f42c1/ffffff?text=Diagram
|
||||
|
||||
## Additional Resources
|
||||
- Tutorial: https://tutorial.example.com
|
||||
- Examples: https://examples.example.com
|
||||
|
||||
This demonstrates how the component handles a mix of text, links, and images.
|
||||
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
|
||||
More content here to demonstrate scrolling and layout behavior with longer text content that might wrap across multiple lines and require proper text handling.
|
||||
|
||||
Sample images:
|
||||
- https://via.placeholder.com/300x200/007bff/ffffff?text=Logo
|
||||
- https://via.placeholder.com/400x300/28a745/ffffff?text=Feature+1
|
||||
- https://via.placeholder.com/400x300/dc3545/ffffff?text=Feature+2
|
||||
|
||||
This tests the component's ability to handle longer content efficiently while maintaining good performance and user experience.`,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "A comprehensive response mixing text, links, and images to showcase the component structure.",
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
...defaultSettings,
|
||||
mcpDisplayMode: "rich",
|
||||
} satisfies Partial<ExtensionState>
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Code-like response
|
||||
export const CodeResponse: Story = {
|
||||
args: {
|
||||
responseText: `Here's some code with embedded URLs:
|
||||
|
||||
\`\`\`javascript
|
||||
// API endpoint
|
||||
const apiUrl = "https://api.example.com/v1/data";
|
||||
|
||||
// Image URL
|
||||
const imageUrl = "https://via.placeholder.com/200x200/6c757d/ffffff?text=API+Response";
|
||||
|
||||
fetch(apiUrl)
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data));
|
||||
\`\`\`
|
||||
|
||||
Documentation: https://docs.example.com/api`,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Response containing code blocks with embedded URLs.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Empty response
|
||||
export const EmptyResponse: Story = {
|
||||
args: {
|
||||
responseText: "",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Handles empty response text gracefully.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import React from "react"
|
||||
import McpConfigurationView from "./McpConfigurationView"
|
||||
import { StorybookProvider, VSCodeWebview } from "../../common/StorybookDecorator"
|
||||
import { ExtensionState } from "@shared/ExtensionMessage"
|
||||
import { McpMarketplaceCatalog, McpViewTab } from "@shared/mcp"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
|
||||
const mcpMarketplaceCatalog: McpMarketplaceCatalog = {
|
||||
items: [
|
||||
{
|
||||
mcpId: "example-server",
|
||||
githubUrl: "https://github.com/example/mcp-server",
|
||||
name: "Example Server",
|
||||
author: "Example Author",
|
||||
description: "An example MCP server for demonstration",
|
||||
codiconIcon: "database",
|
||||
logoUrl: "https://example.com/logo.png",
|
||||
category: "Data",
|
||||
tags: ["example", "demo"],
|
||||
requiresApiKey: false,
|
||||
isRecommended: true,
|
||||
githubStars: 100,
|
||||
downloadCount: 500,
|
||||
createdAt: "2024-01-01T00:00:00Z",
|
||||
updatedAt: "2024-01-15T00:00:00Z",
|
||||
lastGithubSync: "2024-01-15T00:00:00Z",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const mcpservers = [
|
||||
{
|
||||
name: "example-server",
|
||||
config: "npx -y example-server",
|
||||
status: "connected" as const,
|
||||
tools: [{ name: "example_tool", description: "Example tool" }],
|
||||
resources: [],
|
||||
resourceTemplates: [],
|
||||
},
|
||||
{
|
||||
name: "filesystem",
|
||||
config: "npx -y @modelcontextprotocol/server-filesystem /path/to/files",
|
||||
status: "connected" as const,
|
||||
tools: [
|
||||
{
|
||||
name: "read_file",
|
||||
description: "Read contents of a file",
|
||||
inputSchema: {},
|
||||
},
|
||||
{
|
||||
name: "write_file",
|
||||
description: "Write contents to a file",
|
||||
inputSchema: {},
|
||||
},
|
||||
],
|
||||
resources: [],
|
||||
resourceTemplates: [],
|
||||
},
|
||||
{
|
||||
name: "database",
|
||||
config: "npx -y @modelcontextprotocol/server-sqlite /path/to/database.db",
|
||||
status: "disconnected" as const,
|
||||
error: "Connection failed",
|
||||
tools: [],
|
||||
resources: [],
|
||||
resourceTemplates: [],
|
||||
},
|
||||
]
|
||||
|
||||
const createStoryDecorator =
|
||||
(
|
||||
state: Partial<ExtensionState> = { mcpMarketplaceEnabled: true },
|
||||
servers: McpMarketplaceCatalog = mcpMarketplaceCatalog,
|
||||
mcpServers = mcpservers,
|
||||
) =>
|
||||
(Story: React.ComponentType) => {
|
||||
const StoryWithCatalog = () => {
|
||||
const { setMcpMarketplaceCatalog, setMcpServers } = useExtensionState()
|
||||
|
||||
React.useEffect(() => {
|
||||
setMcpMarketplaceCatalog(servers)
|
||||
setMcpServers(mcpServers)
|
||||
// Don't call setMcpTab or navigateToMcp here as they interfere with the component's initialTab prop
|
||||
}, [])
|
||||
|
||||
return <Story />
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider
|
||||
mockState={{
|
||||
mcpMarketplaceEnabled: true,
|
||||
...state,
|
||||
}}>
|
||||
<StoryWithCatalog />
|
||||
</StorybookProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const meta: Meta<typeof McpConfigurationView> = {
|
||||
title: "Component/McpConfigurationView",
|
||||
component: McpConfigurationView,
|
||||
decorators: [VSCodeWebview],
|
||||
argTypes: {
|
||||
initialTab: {
|
||||
control: "select",
|
||||
options: ["marketplace", "addRemote", "installed"],
|
||||
defaultValue: "marketplace",
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
}
|
||||
|
||||
const mcpDecorator = (Story: React.ComponentType) => {
|
||||
return (
|
||||
<StorybookProvider>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof McpConfigurationView>
|
||||
|
||||
const defaultArgs = {
|
||||
initialTab: "marketplace" as McpViewTab,
|
||||
}
|
||||
|
||||
export const Default: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [mcpDecorator],
|
||||
}
|
||||
|
||||
export const MarketplaceEnabled: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [createStoryDecorator({ mcpMarketplaceEnabled: true }, mcpMarketplaceCatalog)],
|
||||
}
|
||||
|
||||
export const MarketplaceDisabled: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [createStoryDecorator({ mcpMarketplaceEnabled: false })],
|
||||
}
|
||||
|
||||
export const DisconnectedServers: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
initialTab: "installed" as McpViewTab,
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
mcpMarketplaceEnabled: true,
|
||||
} satisfies Partial<ExtensionState>
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { McpServiceClient } from "@/services/grpc-client"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { McpViewTab } from "@shared/mcp"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
@@ -19,18 +18,20 @@ type McpViewProps = {
|
||||
|
||||
const McpConfigurationView = ({ onDone, initialTab }: McpViewProps) => {
|
||||
const { mcpMarketplaceEnabled, setMcpServers } = useExtensionState()
|
||||
const [activeTab, setActiveTab] = useState<McpViewTab>(initialTab || (mcpMarketplaceEnabled ? "marketplace" : "installed"))
|
||||
const [activeTab, setActiveTab] = useState<McpViewTab>(
|
||||
mcpMarketplaceEnabled && initialTab === "marketplace" ? "marketplace" : "installed",
|
||||
)
|
||||
|
||||
const handleTabChange = (tab: McpViewTab) => {
|
||||
setActiveTab(tab)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!mcpMarketplaceEnabled && activeTab === "marketplace") {
|
||||
// If marketplace is disabled and we're on marketplace tab, switch to installed
|
||||
setActiveTab("installed")
|
||||
}
|
||||
}, [mcpMarketplaceEnabled, activeTab])
|
||||
// Only override the tab if no initialTab was explicitly provided and marketplace is disabled
|
||||
const isMarketplaceSelected = activeTab === "marketplace" || initialTab === "marketplace"
|
||||
// If marketplace is disabled and we're on marketplace tab, switch to installed
|
||||
setActiveTab(mcpMarketplaceEnabled && isMarketplaceSelected ? "marketplace" : "installed")
|
||||
}, [mcpMarketplaceEnabled, activeTab, initialTab])
|
||||
|
||||
// Get setter for MCP marketplace catalog from context
|
||||
const { setMcpMarketplaceCatalog } = useExtensionState()
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import React from "react"
|
||||
import ApiOptions, { ApiOptionsProps } from "./ApiOptions"
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import { StorybookProvider, VSCodeWebview } from "../common/StorybookDecorator"
|
||||
import { ExtensionState } from "@shared/ExtensionMessage"
|
||||
|
||||
const mockApiConfiguration: ApiConfiguration = {
|
||||
planModeApiProvider: undefined,
|
||||
actModeApiProvider: "cline",
|
||||
favoritedModelIds: [],
|
||||
}
|
||||
|
||||
const createMockState = (config: Partial<ApiConfiguration>): Partial<ExtensionState> => ({
|
||||
apiConfiguration: { ...mockApiConfiguration, ...config } as ApiConfiguration,
|
||||
})
|
||||
|
||||
const createStoryDecorator = (config: Partial<ApiConfiguration>) => (Story: React.ComponentType) => (
|
||||
<StorybookProvider mockState={createMockState(config)}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
|
||||
const meta: Meta<typeof ApiOptions> = {
|
||||
title: "Component/ApiOptions",
|
||||
component: ApiOptions,
|
||||
decorators: [VSCodeWebview],
|
||||
argTypes: {
|
||||
showModelOptions: { control: "boolean", value: true },
|
||||
apiErrorMessage: { control: "text" },
|
||||
modelIdErrorMessage: { control: "text" },
|
||||
isPopup: { control: "boolean", defaultValue: false },
|
||||
currentMode: {
|
||||
control: "select",
|
||||
options: ["plan", "act"],
|
||||
defaultValue: "act",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof ApiOptions>
|
||||
|
||||
const defaultArgs = {
|
||||
showModelOptions: true,
|
||||
isPopup: false,
|
||||
}
|
||||
|
||||
export const Default: Story = {}
|
||||
|
||||
export const ClineProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [createStoryDecorator({ actModeApiModelId: "cline" })],
|
||||
}
|
||||
|
||||
export const OpenRouterProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [createStoryDecorator({ actModeApiProvider: "openrouter" })],
|
||||
}
|
||||
|
||||
export const OpenAIProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [
|
||||
createStoryDecorator({
|
||||
planModeApiProvider: "openai-native",
|
||||
openAiApiKey: "sk-abc123...",
|
||||
planModeOpenAiModelId: "gpt-4o",
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export const OllamaProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [
|
||||
createStoryDecorator({
|
||||
actModeApiProvider: "ollama",
|
||||
actModeOllamaModelId: "llama3.2:latest",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export const BedrockProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [
|
||||
createStoryDecorator({
|
||||
actModeApiProvider: "bedrock",
|
||||
awsAccessKey: "AKIA...",
|
||||
awsSecretKey: "secret123...",
|
||||
awsRegion: "us-east-1",
|
||||
actModeAwsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export const GeminiProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [
|
||||
createStoryDecorator({
|
||||
actModeApiProvider: "gemini",
|
||||
geminiApiKey: "AIza...",
|
||||
actModeApiModelId: "gemini-1.5-pro-002",
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export const DeepSeekProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [
|
||||
createStoryDecorator({
|
||||
actModeApiProvider: "deepseek",
|
||||
deepSeekApiKey: "sk-abc123...",
|
||||
actModeApiModelId: "deepseek-chat",
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export const VSCodeLMProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [createStoryDecorator({ actModeApiProvider: "vscode-lm" })],
|
||||
}
|
||||
|
||||
export const OpenAICompatibleProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [
|
||||
createStoryDecorator({
|
||||
actModeApiProvider: "openai",
|
||||
openAiApiKey: "custom-key-123",
|
||||
openAiBaseUrl: "https://api.custom-provider.com/v1",
|
||||
actModeApiModelId: "custom-model-v1",
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export const LMStudioProvider: Story = {
|
||||
args: defaultArgs,
|
||||
decorators: [
|
||||
createStoryDecorator({
|
||||
actModeApiProvider: "lmstudio",
|
||||
lmStudioBaseUrl: "http://localhost:1234/v1",
|
||||
actModeApiModelId: "local-model",
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const ErrorStatesComponent: React.FC<ApiOptionsProps> = (args) => {
|
||||
const [errorType, setErrorType] = React.useState<"api" | "model" | "both" | "none">("api")
|
||||
|
||||
const errorProps = {
|
||||
api: { apiErrorMessage: "Invalid API key. Please check your credentials and try again." },
|
||||
model: { modelIdErrorMessage: "Selected model is not available. Please choose a different model." },
|
||||
both: {
|
||||
apiErrorMessage: "Connection failed: Unable to reach API endpoint.",
|
||||
modelIdErrorMessage: "Model validation failed: Insufficient permissions for selected model.",
|
||||
},
|
||||
none: {},
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={createMockState({})}>
|
||||
<div className="mb-5">
|
||||
<label className="block mb-2 font-medium">Error Type:</label>
|
||||
<select
|
||||
title="Error Type"
|
||||
value={errorType}
|
||||
onChange={(e) => setErrorType(e.target.value as typeof errorType)}
|
||||
className="px-2 py-1 rounded border border-[var(--vscode-widget-border)] bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)]">
|
||||
<option value="api">API Error</option>
|
||||
<option value="model">Model Error</option>
|
||||
<option value="both">Both Errors</option>
|
||||
<option value="none">No Errors</option>
|
||||
</select>
|
||||
</div>
|
||||
<ApiOptions {...args} {...errorProps[errorType]} />
|
||||
</StorybookProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export const ErrorStates: Story = {
|
||||
args: {
|
||||
showModelOptions: true,
|
||||
isPopup: false,
|
||||
},
|
||||
render: (args) => <ErrorStatesComponent {...args} />,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Interactive error states showing API, model, or both error messages.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const ProviderComparisonComponent: React.FC<ApiOptionsProps> = (args) => {
|
||||
const [selectedProvider, setSelectedProvider] = React.useState("anthropic")
|
||||
|
||||
const providerConfigs = {
|
||||
anthropic: { actModeApiProvider: "anthropic" },
|
||||
"openai-native": { actModeApiProvider: "openai-native", openAiApiKey: "sk-test..." },
|
||||
openrouter: { actModeApiProvider: "openrouter", openRouterApiKey: "or-v1-test..." },
|
||||
ollama: { actModeApiProvider: "ollama", ollamaBaseUrl: "http://localhost:11434" },
|
||||
gemini: { actModeApiProvider: "gemini", geminiApiKey: "AIza..." },
|
||||
} as const
|
||||
|
||||
const providers = Object.keys(providerConfigs).map((id) => ({
|
||||
id,
|
||||
name: id.charAt(0).toUpperCase() + id.slice(1).replace("-", " "),
|
||||
}))
|
||||
|
||||
const mockState = React.useMemo(
|
||||
() => createMockState(providerConfigs[selectedProvider as keyof typeof providerConfigs]),
|
||||
[selectedProvider],
|
||||
)
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<div className="mb-5">
|
||||
<label className="block mb-2 font-medium">Switch Provider:</label>
|
||||
<select
|
||||
title="API Provider"
|
||||
value={selectedProvider}
|
||||
onChange={(e) => setSelectedProvider(e.target.value)}
|
||||
className="px-2 py-1 rounded border border-[var(--vscode-widget-border)] bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)]">
|
||||
{providers.map((provider) => (
|
||||
<option key={provider.id} value={provider.id}>
|
||||
{provider.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<ApiOptions {...args} />
|
||||
</StorybookProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export const ProviderComparison: Story = {
|
||||
args: defaultArgs,
|
||||
render: (args) => <ProviderComparisonComponent {...args} />,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Interactive comparison of different API providers with a provider switcher.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
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"
|
||||
@@ -38,12 +38,12 @@ import { LMStudioProvider } from "./providers/LMStudioProvider"
|
||||
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
|
||||
import { GroqProvider } from "./providers/GroqProvider"
|
||||
|
||||
interface ApiOptionsProps {
|
||||
showSubmitButton?: boolean
|
||||
export interface ApiOptionsProps {
|
||||
showModelOptions: boolean
|
||||
apiErrorMessage?: string
|
||||
modelIdErrorMessage?: string
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
// This is necessary to ensure dropdown opens downward, important for when this is used in popup
|
||||
@@ -70,24 +70,17 @@ 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 =
|
||||
(currentMode === "plan" ? apiConfiguration?.planModeApiProvider : apiConfiguration?.actModeApiProvider) || "cline"
|
||||
|
||||
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,96 +168,96 @@ 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" && (
|
||||
@@ -268,19 +265,19 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
)}
|
||||
|
||||
{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,18 @@ 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"
|
||||
|
||||
export interface GroqModelPickerProps {
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
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 +35,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({}))
|
||||
|
||||
@@ -11,7 +11,7 @@ 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"
|
||||
@@ -39,6 +39,7 @@ const StarIcon = ({ isFavorite, onClick }: { isFavorite: boolean; onClick: (e: R
|
||||
|
||||
export interface OpenRouterModelPickerProps {
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
// Featured models for Cline provider
|
||||
@@ -60,10 +61,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 +78,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 +130,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 +244,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 +333,7 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
|
||||
|
||||
{hasInfo ? (
|
||||
<>
|
||||
{showBudgetSlider && <ThinkingBudgetSlider />}
|
||||
{showBudgetSlider && <ThinkingBudgetSlider currentMode={currentMode} />}
|
||||
|
||||
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
|
||||
</>
|
||||
|
||||
@@ -11,18 +11,20 @@ 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"
|
||||
|
||||
export interface RequestyModelPickerProps {
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
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 +34,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 +235,7 @@ const RequestyModelPicker: React.FC<RequestyModelPickerProps> = ({ isPopup }) =>
|
||||
|
||||
{hasInfo ? (
|
||||
<>
|
||||
{showBudgetSlider && <ThinkingBudgetSlider />}
|
||||
{showBudgetSlider && <ThinkingBudgetSlider currentMode={currentMode} />}
|
||||
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import React from "react"
|
||||
import SettingsView from "./SettingsView"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
|
||||
import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
|
||||
import { StorybookProvider, VSCodeWebview } from "../common/StorybookDecorator"
|
||||
import { ExtensionState } from "@shared/ExtensionMessage"
|
||||
|
||||
const meta: Meta<typeof SettingsView> = {
|
||||
title: "Views/SettingsView",
|
||||
component: SettingsView,
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
The SettingsView component provides a comprehensive settings interface for Cline with tabbed navigation.
|
||||
|
||||
**Features:**
|
||||
- **Tabbed Interface**: Organized settings into logical sections (API Configuration, General, Features, Browser, Terminal, Debug, About)
|
||||
- **Responsive Design**: Adapts to different screen sizes with compact mode for narrow layouts
|
||||
- **Plan/Act Mode Support**: Separate model configurations for planning and execution modes
|
||||
- **Real-time Updates**: All settings save immediately without requiring a save button
|
||||
- **Contextual Navigation**: Can navigate directly to specific settings sections
|
||||
- **Debug Tools**: Development-only debug section for state management
|
||||
- **Accessibility**: Proper ARIA labels and keyboard navigation support
|
||||
|
||||
**Tabs:**
|
||||
- **API Configuration**: Model selection, API keys, and provider settings
|
||||
- **General**: Language preferences, telemetry, and basic settings
|
||||
- **Features**: Auto-approval, checkpoints, MCP marketplace, and feature toggles
|
||||
- **Browser**: Browser automation settings and preferences
|
||||
- **Terminal**: Terminal integration, profiles, and output settings
|
||||
- **Debug**: Development tools for state reset and debugging (dev mode only)
|
||||
- **About**: Version information and extension details
|
||||
|
||||
**Use Cases:**
|
||||
- Configuring AI models and API providers
|
||||
- Customizing extension behavior and features
|
||||
- Managing terminal and browser integration
|
||||
- Debugging and troubleshooting (development)
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [VSCodeWebview],
|
||||
argTypes: {
|
||||
onDone: {
|
||||
action: "done clicked",
|
||||
description: "Callback when the Done button is clicked",
|
||||
},
|
||||
targetSection: {
|
||||
control: "select",
|
||||
options: ["api-config", "general", "features", "browser", "terminal", "debug", "about"],
|
||||
description: "The settings section to navigate to initially",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof SettingsView>
|
||||
|
||||
// Default settings view
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Default settings view starting with the API Configuration tab.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// API Configuration tab focused
|
||||
export const ApiConfiguration: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "api-config",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Settings view focused on the API Configuration tab for model and provider setup.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// General settings tab
|
||||
export const GeneralSettings: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "general",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "General settings tab with language preferences and basic configuration options.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Features tab with enhanced state
|
||||
export const FeaturesTab: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "features",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const defaultStates = {
|
||||
autoApprovalSettings: {
|
||||
...DEFAULT_AUTO_APPROVAL_SETTINGS,
|
||||
},
|
||||
apiConfiguration: {
|
||||
actModeApiProvider: "cline",
|
||||
planModeApiProvider: "cline",
|
||||
},
|
||||
enableCheckpointsSetting: true,
|
||||
mcpMarketplaceEnabled: true,
|
||||
mcpDisplayMode: "rich",
|
||||
} satisfies Partial<ExtensionState>
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={defaultStates}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Features tab showing auto-approval settings, checkpoints, and MCP marketplace options.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Browser settings tab
|
||||
export const BrowserSettings: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "browser",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
browserSettings: {
|
||||
...DEFAULT_BROWSER_SETTINGS,
|
||||
viewport: { width: 1200, height: 800 },
|
||||
userAgent: "Custom User Agent String",
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Browser settings tab with viewport configuration and user agent settings.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Terminal settings tab
|
||||
export const TerminalSettings: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "terminal",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
shellIntegrationTimeout: 6000,
|
||||
terminalReuseEnabled: false,
|
||||
terminalOutputLineLimit: 1000,
|
||||
defaultTerminalProfile: "PowerShell",
|
||||
availableTerminalProfiles: [
|
||||
{ id: "powershell", name: "PowerShell", path: "powershell.exe" },
|
||||
{ id: "cmd", name: "Command Prompt", path: "cmd.exe" },
|
||||
{ id: "bash", name: "Git Bash", path: "bash.exe" },
|
||||
],
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Terminal settings tab with shell integration, profiles, and output configuration.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Debug tab (development mode)
|
||||
export const DebugTab: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "debug",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Debug tab with development tools for state management and troubleshooting. Only visible in development mode.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// About tab
|
||||
export const AboutTab: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "about",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<StorybookProvider>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "About tab showing version information and extension details.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Plan mode configuration
|
||||
export const PlanModeConfiguration: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "api-config",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
chatSettings: {
|
||||
...DEFAULT_CHAT_SETTINGS,
|
||||
mode: "plan" as const,
|
||||
},
|
||||
planActSeparateModelsSetting: true,
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Settings view in Plan mode showing separate model configuration options.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Act mode configuration
|
||||
export const ActModeConfiguration: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "api-config",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
chatSettings: {
|
||||
...DEFAULT_CHAT_SETTINGS,
|
||||
mode: "act" as const,
|
||||
},
|
||||
planActSeparateModelsSetting: true,
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Settings view in Act mode showing execution-focused model configuration.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Compact mode (narrow layout)
|
||||
export const CompactMode: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<StorybookProvider>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Settings view in compact mode for narrow layouts, showing icon-only navigation with tooltips.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// With comprehensive feature toggles
|
||||
export const WithFeatureToggles: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "features",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const mockState = {
|
||||
globalClineRulesToggles: {
|
||||
"use-typescript": true,
|
||||
"prefer-react": false,
|
||||
"follow-conventions": true,
|
||||
},
|
||||
localClineRulesToggles: {
|
||||
"project-specific": true,
|
||||
"local-override": false,
|
||||
},
|
||||
globalWorkflowToggles: {
|
||||
"auto-format": true,
|
||||
"run-tests": false,
|
||||
},
|
||||
localWorkflowToggles: {
|
||||
"build-on-save": true,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Features tab with various rule and workflow toggles enabled to demonstrate the full feature set.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Mode switching in progress
|
||||
export const ModeSwitching: Story = {
|
||||
args: {
|
||||
onDone: () => console.log("Settings done clicked"),
|
||||
targetSection: "api-config",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
// Simulate mode switching state
|
||||
const [isSwitching, setIsSwitching] = React.useState(true)
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => setIsSwitching(false), 2000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
const mockState = {
|
||||
chatSettings: {
|
||||
...DEFAULT_CHAT_SETTINGS,
|
||||
mode: "plan" as const,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<StorybookProvider mockState={mockState}>
|
||||
<Story />
|
||||
</StorybookProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Settings view showing the mode switching state with disabled controls during transition.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,7 @@ 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"
|
||||
|
||||
// Constants
|
||||
const DEFAULT_MIN_VALID_TOKENS = 1024
|
||||
@@ -83,20 +84,23 @@ const RangeInput = styled.input<{ $value: number; $min: number; $max: number }>`
|
||||
|
||||
interface ThinkingBudgetSliderProps {
|
||||
maxBudget?: number
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
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 +112,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 +120,11 @@ const ThinkingBudgetSlider = ({ maxBudget }: ThinkingBudgetSliderProps) => {
|
||||
}, [])
|
||||
|
||||
const handleSliderComplete = () => {
|
||||
handleFieldChange("thinkingBudgetTokens", localValue)
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeThinkingBudgetTokens", act: "actModeThinkingBudgetTokens" },
|
||||
localValue,
|
||||
currentMode,
|
||||
)
|
||||
}
|
||||
|
||||
const handleToggleChange = (event: any) => {
|
||||
@@ -125,7 +133,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,7 +3,7 @@ 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"
|
||||
@@ -21,17 +21,18 @@ export const SUPPORTED_ANTHROPIC_THINKING_MODELS = [
|
||||
interface AnthropicProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +55,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} />
|
||||
|
||||
@@ -13,17 +13,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface AskSageProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +49,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,7 +5,7 @@ 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"
|
||||
|
||||
@@ -15,13 +15,15 @@ const DROPDOWN_Z_INDEX = 1000
|
||||
interface BedrockProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
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 +209,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 +251,7 @@ export const BedrockProvider = ({ showModelOptions, isPopup }: BedrockProviderPr
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
|
||||
{apiConfiguration?.awsBedrockCustomSelected && (
|
||||
{modeFields.awsBedrockCustomSelected && (
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
@@ -248,8 +264,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 +282,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 +315,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} />
|
||||
|
||||
@@ -12,17 +12,18 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface CerebrasProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +39,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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -14,17 +14,18 @@ import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
|
||||
interface ClaudeCodeProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +52,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} />
|
||||
|
||||
@@ -13,12 +13,13 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface ClineProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +77,7 @@ export const ClineProvider = ({ showModelOptions, isPopup }: ClineProviderProps)
|
||||
)}
|
||||
|
||||
{/* OpenRouter Model Picker */}
|
||||
<OpenRouterModelPicker isPopup={isPopup} />
|
||||
<OpenRouterModelPicker isPopup={isPopup} currentMode={currentMode} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -12,17 +12,18 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface DeepSeekProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +39,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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -12,17 +12,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface DoubaoProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +39,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,6 +2,7 @@ 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"
|
||||
|
||||
/**
|
||||
@@ -10,14 +11,17 @@ import { ApiConfiguration } from "@shared/api"
|
||||
interface FireworksProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +47,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>
|
||||
|
||||
@@ -17,17 +17,18 @@ const SUPPORTED_THINKING_MODELS = ["gemini-2.5-pro", "gemini-2.5-flash", "gemini
|
||||
interface GeminiProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +51,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} />
|
||||
|
||||
@@ -9,12 +9,13 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface GroqProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +28,7 @@ export const GroqProvider = ({ showModelOptions, isPopup }: GroqProviderProps) =
|
||||
signupUrl="https://console.groq.com/keys"
|
||||
/>
|
||||
|
||||
{showModelOptions && <GroqModelPicker isPopup={isPopup} />}
|
||||
{showModelOptions && <GroqModelPicker isPopup={isPopup} currentMode={currentMode} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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"
|
||||
|
||||
/**
|
||||
* Props for the LMStudioProvider component
|
||||
@@ -14,14 +15,17 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface LMStudioProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +62,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 +73,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,7 +3,7 @@ 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"
|
||||
@@ -15,17 +15,21 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface LiteLlmProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +53,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 +83,7 @@ export const LiteLlmProvider = ({ showModelOptions, isPopup }: LiteLlmProviderPr
|
||||
</div>
|
||||
|
||||
<>
|
||||
<ThinkingBudgetSlider />
|
||||
<ThinkingBudgetSlider currentMode={currentMode} />
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
@@ -118,50 +124,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 +181,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 +194,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>
|
||||
|
||||
@@ -12,17 +12,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface MistralProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +39,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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -14,20 +14,21 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
interface MoonshotProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +58,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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -12,17 +12,17 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface NebiusProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +39,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,7 @@ import OllamaModelPicker from "../OllamaModelPicker"
|
||||
import { BaseUrlField } from "../common/BaseUrlField"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
|
||||
import { getModeSpecificFields } from "../utils/providerUtils"
|
||||
|
||||
/**
|
||||
* Props for the OllamaProvider component
|
||||
@@ -15,14 +16,17 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface OllamaProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +68,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,7 +8,7 @@ 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"
|
||||
|
||||
@@ -18,19 +18,23 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface OpenAICompatibleProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +90,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 +194,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 +242,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 +282,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 +322,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 +338,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>
|
||||
|
||||
@@ -12,17 +12,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface OpenAINativeProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +39,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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -53,14 +53,14 @@ const OpenRouterBalanceDisplay = ({ apiKey }: { apiKey: string }) => {
|
||||
interface OpenRouterProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
uriScheme?: string
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +142,7 @@ export const OpenRouterProvider = ({ showModelOptions, isPopup, uriScheme }: Ope
|
||||
</div>
|
||||
)}
|
||||
|
||||
<OpenRouterModelPicker isPopup={isPopup} />
|
||||
<OpenRouterModelPicker isPopup={isPopup} currentMode={currentMode} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -28,17 +28,18 @@ const SUPPORTED_THINKING_MODELS = [
|
||||
interface QwenProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +84,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} />
|
||||
|
||||
@@ -9,12 +9,13 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface RequestyProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +28,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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,17 +12,18 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface SambanovaProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +39,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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -13,16 +13,17 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface SapAiCoreProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +98,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,7 @@ 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"
|
||||
|
||||
/**
|
||||
* Props for the TogetherProvider component
|
||||
@@ -10,14 +11,17 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface TogetherProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +31,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,18 @@ 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"
|
||||
|
||||
export const VSCodeLmProvider = () => {
|
||||
interface VSCodeLmProviderProps {
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
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 +49,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 +60,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>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
|
||||
interface VertexProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
// Vertex models that support thinking
|
||||
@@ -30,12 +31,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 +99,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,7 +4,7 @@ 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"
|
||||
@@ -15,17 +15,20 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
interface XaiProviderProps {
|
||||
showModelOptions: boolean
|
||||
isPopup?: boolean
|
||||
currentMode: "plan" | "act"
|
||||
}
|
||||
|
||||
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 +57,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 +76,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 +95,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,18 @@ 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"
|
||||
|
||||
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<"plan" | "act">(chatSettings.mode)
|
||||
const { handleFieldsChange } = useApiConfigurationHandlers()
|
||||
return (
|
||||
<div>
|
||||
{renderSectionHeader("api-config")}
|
||||
@@ -27,34 +26,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 +63,12 @@ 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) {
|
||||
// Wait for React state to update after the protocol call
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
await syncModeConfigurations(apiConfiguration, currentTab, handleFieldsChange)
|
||||
}
|
||||
await StateServiceClient.updateSettings(
|
||||
UpdateSettingsRequest.create({
|
||||
planActSeparateModelsSetting: checked,
|
||||
|
||||
@@ -62,9 +62,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: "plan" | "act",
|
||||
): 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 +93,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,59 +129,89 @@ 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)
|
||||
@@ -189,13 +230,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 +246,218 @@ 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: "plan" | "act") {
|
||||
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,
|
||||
|
||||
// Model info objects
|
||||
openAiModelInfo: undefined,
|
||||
liteLlmModelInfo: undefined,
|
||||
openRouterModelInfo: undefined,
|
||||
requestyModelInfo: undefined,
|
||||
groqModelInfo: 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,
|
||||
|
||||
// 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,
|
||||
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: "plan" | "act",
|
||||
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 "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
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,8 @@ 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 +18,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 +41,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: "plan" | "act",
|
||||
) => {
|
||||
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: "plan" | "act",
|
||||
) => {
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import WelcomeView from "./WelcomeView"
|
||||
import { VSCodeWebview } from "../common/StorybookDecorator"
|
||||
|
||||
const meta: Meta<typeof WelcomeView> = {
|
||||
title: "Views/WelcomeView",
|
||||
component: WelcomeView,
|
||||
decorators: [VSCodeWebview],
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
export const Default: StoryObj<typeof meta> = {}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -99,6 +99,9 @@ interface ExtensionStateContextType extends ExtensionState {
|
||||
|
||||
// Event callbacks
|
||||
onRelinquishControl: (callback: () => void) => () => void
|
||||
|
||||
// Set initial state for testing purposes only
|
||||
setExtensionStateForTest: (partialState: Partial<ExtensionState>) => void
|
||||
}
|
||||
|
||||
const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
@@ -237,6 +240,16 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
const workspaceUpdatesUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
const relinquishControlUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
|
||||
// Set extension state for testing purposes
|
||||
const setExtensionStateForTest = useCallback((partialState: Partial<ExtensionState>) => {
|
||||
if (process.env.IS_DEV === "true") {
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
...partialState,
|
||||
}))
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Add ref for callbacks
|
||||
const relinquishControlCallbacks = useRef<Set<() => void>>(new Set())
|
||||
|
||||
@@ -755,6 +768,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
refreshOpenRouterModels,
|
||||
onRelinquishControl,
|
||||
setUserInfo: (userInfo?: UserInfo) => setState((prevState) => ({ ...prevState, userInfo })),
|
||||
setExtensionStateForTest,
|
||||
}
|
||||
|
||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { ApiConfiguration, openRouterDefaultModelId, ModelInfo } from "@shared/api"
|
||||
import { getModeSpecificFields } from "@/components/settings/utils/providerUtils"
|
||||
|
||||
export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): string | undefined {
|
||||
export function validateApiConfiguration(currentMode: "plan" | "act", 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 +76,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 +150,16 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
|
||||
}
|
||||
|
||||
export function validateModelId(
|
||||
currentMode: "plan" | "act",
|
||||
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."
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
darkMode: "class",
|
||||
// Toggle dark-mode based on .dark class or data-mode="dark"
|
||||
darkMode: ["class", '[data-mode="dark"]', '[class="vs-dark"]'],
|
||||
plugins: [
|
||||
heroui({
|
||||
defaultTheme: "vscode",
|
||||
|
||||
Reference in New Issue
Block a user