mirror of
https://github.com/cline/cline.git
synced 2026-09-04 11:44:01 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09724b41fb |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: mcp servers are not started when disabled
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Improve the Claude Code error messages
|
||||
@@ -56,29 +56,6 @@ jobs:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
# Cache VS Code installation
|
||||
- name: Cache VS Code
|
||||
uses: actions/cache@v4
|
||||
id: vscode-cache
|
||||
with:
|
||||
path: .vscode-test
|
||||
key: vscode-${{ runner.os }}-stable-${{ hashFiles('.vscode-test.mjs', 'package.json') }}
|
||||
restore-keys: |
|
||||
vscode-${{ runner.os }}-stable-
|
||||
|
||||
# Cache Playwright browsers
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
~/Library/Caches/ms-playwright
|
||||
~/AppData/Local/ms-playwright
|
||||
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
playwright-browsers-${{ runner.os }}-
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
@@ -91,14 +68,17 @@ jobs:
|
||||
if: matrix.runner == 'ubuntu'
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb
|
||||
|
||||
# Run optimized E2E tests (eliminates redundant builds)
|
||||
# Build the extension before running tests
|
||||
- name: Build Tests and Extension
|
||||
run: npm run pretest
|
||||
|
||||
- name: Run E2E tests - Linux
|
||||
if: matrix.runner == 'ubuntu'
|
||||
run: xvfb-run -a npm run test:e2e:optimal
|
||||
run: xvfb-run -a npm run test:e2e
|
||||
|
||||
- name: Run E2E tests - Non-Linux
|
||||
if: matrix.runner != 'ubuntu'
|
||||
run: npm run test:e2e:optimal
|
||||
run: npm run test:e2e
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() }}
|
||||
|
||||
@@ -94,7 +94,6 @@ jobs:
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
CLINE_ENVIRONMENT: production
|
||||
run: |
|
||||
# Required to generate the .vsix
|
||||
vsce package --out "cline-${{ steps.get_version.outputs.version }}.vsix"
|
||||
|
||||
+7
-1
@@ -22,14 +22,20 @@ coverage
|
||||
|
||||
*evals.env
|
||||
|
||||
## Generated files ##
|
||||
# Generated files
|
||||
src/generated/
|
||||
# Core
|
||||
src/core/controller/*/methods.ts
|
||||
src/core/controller/*/index.ts
|
||||
src/core/controller/grpc-service-config.ts
|
||||
# Shared
|
||||
src/shared/proto/*.ts
|
||||
src/shared/proto/host/*.ts
|
||||
# Webview
|
||||
webview-ui/src/services/grpc-client.ts
|
||||
# Host bridge
|
||||
src/hosts/vscode/client/host-grpc-client.ts
|
||||
src/standalone/server-setup.ts
|
||||
|
||||
# E2E Tests
|
||||
test-results
|
||||
|
||||
+1
-3
@@ -5,6 +5,4 @@ webview-ui/build/
|
||||
package-lock.json
|
||||
src/core/prompts/system.ts
|
||||
src/core/prompts/model_prompts/claude4.ts
|
||||
evals/
|
||||
docs/
|
||||
out/
|
||||
evals/
|
||||
Vendored
+2
-4
@@ -14,8 +14,7 @@
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
|
||||
"CLINE_ENVIRONMENT": "production"
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -38,8 +37,7 @@
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"TEMP_PROFILE": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
|
||||
"CLINE_ENVIRONMENT": "production"
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [3.19.7]
|
||||
|
||||
- Add Hugging Face as a new API provider with support for their inference API models
|
||||
- Improve Claude Code error messages with better guidance for common setup issues (Thanks @BarreiroT!)
|
||||
- Fix authentication sync issues when using multiple VSCode windows
|
||||
|
||||
## [3.19.6]
|
||||
|
||||
- Improve Kimi K2 model provider routing with additional provider options for better availability and performance
|
||||
|
||||
+1
-2
@@ -159,8 +159,7 @@
|
||||
"provider-config/openai",
|
||||
"provider-config/openai-compatible",
|
||||
"provider-config/openrouter",
|
||||
"provider-config/requesty",
|
||||
"provider-config/sap-aicore"
|
||||
"provider-config/requesty"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Generated
+10118
File diff suppressed because it is too large
Load Diff
+2
-4
@@ -4,15 +4,13 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "mintlify dev",
|
||||
"check": "mintlify broken-links",
|
||||
"rename": "mintlify rename"
|
||||
"dev": "mintlify dev"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"mintlify": "^4.2.23"
|
||||
"mintlify": "^4.0.538"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
title: "SAP AI Core"
|
||||
description: "Learn how to configure and use LLM models from Generative AI Hub in SAP AI Core with Cline."
|
||||
---
|
||||
|
||||
SAP AI Core and the generative AI hub help you to integrate LLMs and AI into new business processes in a cost-efficient manner.
|
||||
|
||||
**Website:** [SAP Help Portal](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/what-is-sap-ai-core)
|
||||
|
||||
### Getting a Service Binding
|
||||
|
||||
> 💡 **Information**
|
||||
>
|
||||
> SAP AI Core, and Generative AI Hub, are offerings from SAP BTP.
|
||||
> You need an active SAP BTP contract and a existing subaccount with a SAP AI Core instance to perform these steps.
|
||||
|
||||
1. **Access:** Go to your subaccount via [BTP Cloud Cockpit](cockpit.btp.cloud.sap/cockpit)
|
||||
2. **Create a Service Binding:** Go to "Instances and Subscriptions", select your SAP AI Core service instance and click on Service Bindings > Create.
|
||||
3. **Copy the Service Binding:** Copy the service binding values.
|
||||
|
||||
### Supported Models
|
||||
|
||||
SAP AI Core supports a large and growing number of models.
|
||||
Refer to the [Generative AI Hub Supported Models page](https://me.sap.com/notes/3437766) for the complete and up-to-date list.
|
||||
|
||||
### Configuration in Cline
|
||||
|
||||
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
|
||||
2. **Select Provider:** Choose "SAP AI Core" from the "API Provider" dropdown.
|
||||
3. **Enter Client Id:** Add the `.clientid` field from the service binding into the "AI Core Client Id" field.
|
||||
4. **Enter Client Secret:** Add the `.clientsecret` field from the service binding into the "AI Core Client Secret" field.
|
||||
5. **Enter Base URL:** Add the `.serviceurls.AI_API_URL` field from the service binding into the "AI Core Base URL" field.
|
||||
6. **Enter Auth URL:** Add the `.url` field from the service binding into the "AI Core Auth URL" field.
|
||||
7. **Enter Resource Group:** Add the resource group where you have your model deployments. See [Create a Deployment for a Generative AI Model](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-deployment-for-generative-ai-model-in-sap-ai-core).
|
||||
8. **Select Model:** Choose your desired model from the "Model" dropdown.
|
||||
|
||||
### Tips and Notes
|
||||
|
||||
- **Model Selection:** SAP AI Core offers a wide range of models. You won't be able to use the model, even if selected, if a deployment doesn't exist in the provided resource group.
|
||||
@@ -11,11 +11,8 @@ const disallowedApis = {
|
||||
"vscode.workspace.fs.stat": {
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
"vscode.workspace.fs.writeFile": {
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
"vscode.workspace.workspaceFolders": {
|
||||
messageId: "useHostBridgeWorkspace",
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
"vscode.workspace.asRelativePath": {
|
||||
messageId: "usePathUtils",
|
||||
@@ -23,29 +20,6 @@ const disallowedApis = {
|
||||
"vscode.workspace.getWorkspaceFolder": {
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
"vscode.window.showTextDocument": {
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
"vscode.workspace.applyEdit": {
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
// "vscode.env.openExternal": {
|
||||
// messageId: "useUtils",
|
||||
// },
|
||||
// "vscode.window.showWarningMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
"vscode.window.showOpenDialog": {
|
||||
messageId: "useHostBridgeShowMessage",
|
||||
},
|
||||
// There are too many warnings for these calls, uncomment the following
|
||||
// when the migration is finished.
|
||||
// "vscode.window.showErrorMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
// "vscode.window.showInformationMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
}
|
||||
|
||||
module.exports = createRule({
|
||||
@@ -63,28 +37,16 @@ module.exports = createRule({
|
||||
"Example: AccountServiceClient.methodName(RequestType.create({...})) instead of vscode.postMessage({type: '...'}).\n" +
|
||||
"Found: {{code}}",
|
||||
useFsUtils:
|
||||
"Use utilities in @/utils/fs instead of vscode.workspace.fs\n" +
|
||||
"Use utilities in @/utils/fs instead of vscode.workspace.fs.stat.\n" +
|
||||
"Example: import { isDirectory } from '@/utils/fs' or use the file system methods from the host bridge provider.\n" +
|
||||
"Found: {{code}}",
|
||||
usePathUtils:
|
||||
"Use path utilities from @/utils/path instead of VSCode workspace path methods.\n" +
|
||||
"This provides consistent path handling across different environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridgeWorkspace:
|
||||
useHostBridge:
|
||||
"Use getHostBridgeProvider().workspaceClient.getWorkspacePaths({}) instead of vscode.workspace.workspaceFolders.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridgeShowMessage:
|
||||
"Use getHostBridgeProvider().windowClient.showMessage instead of the vscode.window.showMessage.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridge:
|
||||
"Use the host bridge instead of calling vscode APIs directly.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useUtils:
|
||||
"Use utilities in @/utils instead of calling vscode APIs directly.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
usePathUtils:
|
||||
"Use path utilities from @/utils/path instead of direct VSCode workspace path methods.\n" +
|
||||
"This provides consistent path handling across different environments.\n" +
|
||||
"Found: {{code}}",
|
||||
},
|
||||
schema: [],
|
||||
@@ -92,10 +54,17 @@ module.exports = createRule({
|
||||
defaultOptions: [],
|
||||
|
||||
create(context) {
|
||||
// Check if current file is in an exception directory or is grpc-client-base.ts
|
||||
const filename = context.filename
|
||||
const isGrpcClientBase = path.basename(filename) === "grpc-client-base.ts"
|
||||
|
||||
// Skip checking files in src/hosts/vscode or standalone/runtime-files
|
||||
const isExceptionDirectory = filename.includes("/src/hosts/vscode/") || filename.includes("/standalone/runtime-files/")
|
||||
|
||||
// Pattern for checking memberExpressions like vscode.workspace.fs.stat
|
||||
function checkMemberExpression(node) {
|
||||
if (isExcluded(context.filename)) {
|
||||
// Skip if this file is being excluded.
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isGrpcClientBase || isExceptionDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -174,20 +143,6 @@ module.exports = createRule({
|
||||
})
|
||||
}
|
||||
|
||||
function isExcluded(filename) {
|
||||
// Check if current file is in an exception directory or is grpc-client-base.ts
|
||||
if (path.basename(filename) === "grpc-client-base.ts") {
|
||||
return true
|
||||
}
|
||||
// Skip checking files in src/hosts/vscode or standalone/runtime-files
|
||||
if (filename.includes("/src/hosts/vscode/")) {
|
||||
return true
|
||||
}
|
||||
if (filename.includes("/standalone/runtime-files/")) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// Detect basic member expressions (e.g., vscode.postMessage)
|
||||
MemberExpression(node) {
|
||||
@@ -197,7 +152,7 @@ module.exports = createRule({
|
||||
// Detect property access through destructuring
|
||||
VariableDeclarator(node) {
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isExcluded(context.filename)) {
|
||||
if (isGrpcClientBase || isExceptionDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Generated
+12557
-1031
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.19.7",
|
||||
"version": "3.19.6",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -347,7 +347,6 @@
|
||||
"test:coverage": "vscode-test --coverage",
|
||||
"e2e": "playwright test -c playwright.config.ts",
|
||||
"test:e2e": "playwright install && vsce package --no-dependencies --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
"test:e2e:optimal": "vsce package --no-dependencies --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
"install:all": "npm install && cd webview-ui && npm install",
|
||||
"dev:webview": "cd webview-ui && npm run dev",
|
||||
"build:webview": "cd webview-ui && npm run build",
|
||||
@@ -357,9 +356,9 @@
|
||||
"prepare": "husky",
|
||||
"changeset": "changeset",
|
||||
"version-packages": "changeset version",
|
||||
"docs": "cd docs && npm run dev",
|
||||
"docs:check-links": "cd docs && npm run check",
|
||||
"docs:rename-file": "cd docs && npm run rename",
|
||||
"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"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -397,6 +396,7 @@
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.0",
|
||||
"minimatch": "^3.0.3",
|
||||
"mintlify": "^4.0.515",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
"protoc-gen-ts": "^0.8.7",
|
||||
|
||||
+2
-40
@@ -10,16 +10,7 @@ import "common.proto";
|
||||
service DiffService {
|
||||
// Open the diff view/editor.
|
||||
rpc openDiff(OpenDiffRequest) returns (OpenDiffResponse);
|
||||
// Get the contents of the diff view.
|
||||
rpc getDocumentText(GetDocumentTextRequest) returns (GetDocumentTextResponse);
|
||||
// Replace a text selection in the diff.
|
||||
rpc replaceText(ReplaceTextRequest) returns (ReplaceTextResponse);
|
||||
// Truncate the diff document.
|
||||
rpc truncateDocument(TruncateDocumentRequest) returns (TruncateDocumentResponse);
|
||||
// Save the diff document.
|
||||
rpc saveDocument(SaveDocumentRequest) returns (SaveDocumentResponse);
|
||||
// Close the diff editor UI.
|
||||
rpc closeDiff(CloseDiffRequest) returns (CloseDiffResponse);
|
||||
}
|
||||
|
||||
message OpenDiffRequest {
|
||||
@@ -35,15 +26,6 @@ message OpenDiffResponse {
|
||||
optional string diff_id = 1;
|
||||
}
|
||||
|
||||
message GetDocumentTextRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
}
|
||||
|
||||
message GetDocumentTextResponse {
|
||||
optional string content = 1;
|
||||
}
|
||||
|
||||
message ReplaceTextRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
@@ -52,26 +34,6 @@ message ReplaceTextRequest {
|
||||
optional int32 end_line = 5;
|
||||
}
|
||||
|
||||
message ReplaceTextResponse {}
|
||||
|
||||
message TruncateDocumentRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
optional int32 end_line = 5;
|
||||
message ReplaceTextResponse {
|
||||
// TBD
|
||||
}
|
||||
|
||||
message TruncateDocumentResponse {}
|
||||
|
||||
message CloseDiffRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
}
|
||||
|
||||
message CloseDiffResponse {}
|
||||
|
||||
message SaveDocumentRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
}
|
||||
|
||||
message SaveDocumentResponse {}
|
||||
|
||||
@@ -6,10 +6,6 @@ option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
/**
|
||||
* The watch service is only here as example of a streaming rpc in the host bridge.
|
||||
* This being replaced with a native JS file watcher.
|
||||
*/
|
||||
// WatchService provides methods for watching files in the IDE
|
||||
service WatchService {
|
||||
// Subscribe to file changes
|
||||
|
||||
+81
-118
@@ -15,8 +15,6 @@ service ModelsService {
|
||||
rpc getVsCodeLmModels(EmptyRequest) returns (VsCodeLmModelsArray);
|
||||
// Refreshes and returns OpenRouter models
|
||||
rpc refreshOpenRouterModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns Hugging Face models
|
||||
rpc refreshHuggingFaceModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns OpenAI models
|
||||
rpc refreshOpenAiModels(OpenAiModelsRequest) returns (StringArray);
|
||||
// Refreshes and returns Requesty models
|
||||
@@ -128,7 +126,6 @@ enum ApiProvider {
|
||||
SAPAICORE = 25;
|
||||
CLAUDE_CODE = 26;
|
||||
MOONSHOT = 27;
|
||||
HUGGINGFACE = 28;
|
||||
}
|
||||
|
||||
// Model info for OpenAI-compatible models
|
||||
@@ -168,119 +165,85 @@ message LiteLLMModelInfo {
|
||||
|
||||
// Main ApiConfiguration message
|
||||
message ModelsApiConfiguration {
|
||||
// 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;
|
||||
// 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;
|
||||
}
|
||||
|
||||
+115
-102
@@ -118,113 +118,126 @@ message UpdateSettingsRequest {
|
||||
|
||||
// Complete API Configuration message
|
||||
message ApiConfiguration {
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
// Favorited model IDs
|
||||
repeated string favorited_model_ids = 300;
|
||||
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;
|
||||
|
||||
// Extension fields for Bedrock Api Keys
|
||||
optional string aws_authentication = 301;
|
||||
optional string aws_bedrock_api_key = 302;
|
||||
optional string aws_authentication = 78;
|
||||
optional string aws_bedrock_api_key = 79;
|
||||
|
||||
optional string cline_account_id = 303;
|
||||
// Moonshot
|
||||
optional string moonshot_api_key = 80;
|
||||
optional string moonshot_api_line = 81;
|
||||
}
|
||||
|
||||
+15
-12
@@ -42,8 +42,6 @@ const serviceDirs = Object.keys(serviceNameMap).map((serviceKey) => path.join("s
|
||||
async function main() {
|
||||
console.log(chalk.bold.blue("Starting Protocol Buffer code generation..."))
|
||||
|
||||
await cleanup()
|
||||
|
||||
// Check for Apple Silicon compatibility before proceeding
|
||||
checkAppleSiliconCompatibility()
|
||||
|
||||
@@ -52,6 +50,8 @@ async function main() {
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
}
|
||||
|
||||
await cleanup()
|
||||
|
||||
// Check for missing proto files for services in serviceNameMap
|
||||
await ensureProtoFilesExist()
|
||||
|
||||
@@ -420,13 +420,20 @@ service ${serviceClassName} {
|
||||
async function cleanup() {
|
||||
// Clean up existing generated files
|
||||
log_verbose(chalk.cyan("Cleaning up existing generated TypeScript files..."))
|
||||
await rmrf(TS_OUT_DIR)
|
||||
await rmrf("src/generated")
|
||||
const existingFiles = await globby("**/*.ts", { cwd: TS_OUT_DIR })
|
||||
for (const file of existingFiles) {
|
||||
await fs.unlink(path.join(TS_OUT_DIR, file))
|
||||
}
|
||||
await rmdir("src/generated")
|
||||
|
||||
// Clean up generated files that were moved.
|
||||
await rmrf("src/standalone/services/host-grpc-client.ts")
|
||||
await rmrf("src/standalone/server-setup.ts")
|
||||
await rmrf("src/hosts/vscode/host-grpc-service-config.ts")
|
||||
await fs.rm("src/standalone/services/host-grpc-client.ts", { force: true })
|
||||
await rmdir("src/standalone/services")
|
||||
await fs.rm("hosts/vscode", { force: true, recursive: true })
|
||||
await rmdir("hosts")
|
||||
|
||||
await fs.rm("src/standalone/server-setup.ts", { force: true })
|
||||
await fs.rm("src/hosts/vscode/host-grpc-service-config.ts", { force: true })
|
||||
const oldhostbridgefiles = [
|
||||
"src/hosts/vscode/workspace/methods.ts",
|
||||
"src/hosts/vscode/workspace/index.ts",
|
||||
@@ -442,7 +449,7 @@ async function cleanup() {
|
||||
"src/hosts/vscode/uri/index.ts",
|
||||
]
|
||||
for (const file of oldhostbridgefiles) {
|
||||
await rmrf(file)
|
||||
await fs.rm(file, { force: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,10 +475,6 @@ async function rmdir(path) {
|
||||
}
|
||||
}
|
||||
|
||||
async function rmrf(path) {
|
||||
await fs.rm(path, { force: true, recursive: true })
|
||||
}
|
||||
|
||||
// Check for Apple Silicon compatibility
|
||||
function checkAppleSiliconCompatibility() {
|
||||
// Only run check on macOS
|
||||
|
||||
@@ -62,7 +62,7 @@ let output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
import * as grpc from "@grpc/grpc-js"
|
||||
import { cline } from "@generated/grpc-js"
|
||||
import { Controller } from "@core/controller"
|
||||
import { GrpcHandlerWrapper, GrpcStreamingResponseHandlerWrapper } from "@hosts/external/grpc-types"
|
||||
import { GrpcHandlerWrapper, GrpcStreamingResponseHandlerWrapper } from "@/standalone/grpc-types"
|
||||
|
||||
${imports}
|
||||
export function addProtobusServices(
|
||||
|
||||
+63
-98
@@ -29,8 +29,6 @@ import { SapAiCoreHandler } from "./providers/sapaicore"
|
||||
import { ClaudeCodeHandler } from "./providers/claude-code"
|
||||
import { MoonshotHandler } from "./providers/moonshot"
|
||||
import { GroqHandler } from "./providers/groq"
|
||||
import { Mode } from "../shared/ChatSettings"
|
||||
import { HuggingFaceHandler } from "./providers/huggingface"
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
|
||||
@@ -42,33 +40,27 @@ export interface SingleCompletionHandler {
|
||||
completePrompt(prompt: string): Promise<string>
|
||||
}
|
||||
|
||||
function createHandlerForProvider(
|
||||
apiProvider: string | undefined,
|
||||
options: Omit<ApiConfiguration, "apiProvider">,
|
||||
mode: Mode,
|
||||
): ApiHandler {
|
||||
function createHandlerForProvider(apiProvider: string | undefined, options: Omit<ApiConfiguration, "apiProvider">): ApiHandler {
|
||||
switch (apiProvider) {
|
||||
case "anthropic":
|
||||
return new AnthropicHandler({
|
||||
apiKey: options.apiKey,
|
||||
anthropicBaseUrl: options.anthropicBaseUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
case "openrouter":
|
||||
return new OpenRouterHandler({
|
||||
openRouterApiKey: options.openRouterApiKey,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
openRouterModelId: options.openRouterModelId,
|
||||
openRouterModelInfo: options.openRouterModelInfo,
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
case "bedrock":
|
||||
return new AwsBedrockHandler({
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
awsAccessKey: options.awsAccessKey,
|
||||
awsSecretKey: options.awsSecretKey,
|
||||
awsSessionToken: options.awsSessionToken,
|
||||
@@ -80,20 +72,16 @@ function createHandlerForProvider(
|
||||
awsUseProfile: options.awsUseProfile,
|
||||
awsProfile: options.awsProfile,
|
||||
awsBedrockEndpoint: options.awsBedrockEndpoint,
|
||||
awsBedrockCustomSelected:
|
||||
mode === "plan" ? options.planModeAwsBedrockCustomSelected : options.actModeAwsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId:
|
||||
mode === "plan" ? options.planModeAwsBedrockCustomModelBaseId : options.actModeAwsBedrockCustomModelBaseId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
awsBedrockCustomSelected: options.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: options.awsBedrockCustomModelBaseId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
case "vertex":
|
||||
return new VertexHandler({
|
||||
vertexProjectId: options.vertexProjectId,
|
||||
vertexRegion: options.vertexRegion,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
geminiApiKey: options.geminiApiKey,
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
taskId: options.taskId,
|
||||
@@ -104,21 +92,21 @@ function createHandlerForProvider(
|
||||
openAiBaseUrl: options.openAiBaseUrl,
|
||||
azureApiVersion: options.azureApiVersion,
|
||||
openAiHeaders: options.openAiHeaders,
|
||||
openAiModelId: mode === "plan" ? options.planModeOpenAiModelId : options.actModeOpenAiModelId,
|
||||
openAiModelInfo: mode === "plan" ? options.planModeOpenAiModelInfo : options.actModeOpenAiModelInfo,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
openAiModelId: options.openAiModelId,
|
||||
openAiModelInfo: options.openAiModelInfo,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
})
|
||||
case "ollama":
|
||||
return new OllamaHandler({
|
||||
ollamaBaseUrl: options.ollamaBaseUrl,
|
||||
ollamaModelId: mode === "plan" ? options.planModeOllamaModelId : options.actModeOllamaModelId,
|
||||
ollamaModelId: options.ollamaModelId,
|
||||
ollamaApiOptionsCtxNum: options.ollamaApiOptionsCtxNum,
|
||||
requestTimeoutMs: options.requestTimeoutMs,
|
||||
})
|
||||
case "lmstudio":
|
||||
return new LmStudioHandler({
|
||||
lmStudioBaseUrl: options.lmStudioBaseUrl,
|
||||
lmStudioModelId: mode === "plan" ? options.planModeLmStudioModelId : options.actModeLmStudioModelId,
|
||||
lmStudioModelId: options.lmStudioModelId,
|
||||
})
|
||||
case "gemini":
|
||||
return new GeminiHandler({
|
||||
@@ -126,85 +114,78 @@ function createHandlerForProvider(
|
||||
vertexRegion: options.vertexRegion,
|
||||
geminiApiKey: options.geminiApiKey,
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
taskId: options.taskId,
|
||||
})
|
||||
case "openai-native":
|
||||
return new OpenAiNativeHandler({
|
||||
openAiNativeApiKey: options.openAiNativeApiKey,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "deepseek":
|
||||
return new DeepSeekHandler({
|
||||
deepSeekApiKey: options.deepSeekApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "requesty":
|
||||
return new RequestyHandler({
|
||||
requestyApiKey: options.requestyApiKey,
|
||||
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,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
requestyModelId: options.requestyModelId,
|
||||
requestyModelInfo: options.requestyModelInfo,
|
||||
})
|
||||
case "fireworks":
|
||||
return new FireworksHandler({
|
||||
fireworksApiKey: options.fireworksApiKey,
|
||||
fireworksModelId: mode === "plan" ? options.planModeFireworksModelId : options.actModeFireworksModelId,
|
||||
fireworksModelId: options.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: options.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: options.fireworksModelMaxTokens,
|
||||
})
|
||||
case "together":
|
||||
return new TogetherHandler({
|
||||
togetherApiKey: options.togetherApiKey,
|
||||
togetherModelId: mode === "plan" ? options.planModeTogetherModelId : options.actModeTogetherModelId,
|
||||
togetherModelId: options.togetherModelId,
|
||||
})
|
||||
case "qwen":
|
||||
return new QwenHandler({
|
||||
qwenApiKey: options.qwenApiKey,
|
||||
qwenApiLine: options.qwenApiLine,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
case "doubao":
|
||||
return new DoubaoHandler({
|
||||
doubaoApiKey: options.doubaoApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "mistral":
|
||||
return new MistralHandler({
|
||||
mistralApiKey: options.mistralApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "vscode-lm":
|
||||
return new VsCodeLmHandler({
|
||||
vsCodeLmModelSelector:
|
||||
mode === "plan" ? options.planModeVsCodeLmModelSelector : options.actModeVsCodeLmModelSelector,
|
||||
vsCodeLmModelSelector: options.vsCodeLmModelSelector,
|
||||
})
|
||||
case "cline":
|
||||
return new ClineHandler({
|
||||
clineAccountId: options.clineAccountId,
|
||||
taskId: options.taskId,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
openRouterModelId: options.openRouterModelId,
|
||||
openRouterModelInfo: options.openRouterModelInfo,
|
||||
})
|
||||
case "litellm":
|
||||
return new LiteLlmHandler({
|
||||
liteLlmApiKey: options.liteLlmApiKey,
|
||||
liteLlmBaseUrl: options.liteLlmBaseUrl,
|
||||
liteLlmModelId: mode === "plan" ? options.planModeLiteLlmModelId : options.actModeLiteLlmModelId,
|
||||
liteLlmModelInfo: mode === "plan" ? options.planModeLiteLlmModelInfo : options.actModeLiteLlmModelInfo,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
liteLlmModelId: options.liteLlmModelId,
|
||||
liteLlmModelInfo: options.liteLlmModelInfo,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
liteLlmUsePromptCache: options.liteLlmUsePromptCache,
|
||||
taskId: options.taskId,
|
||||
})
|
||||
@@ -212,48 +193,41 @@ function createHandlerForProvider(
|
||||
return new MoonshotHandler({
|
||||
moonshotApiKey: options.moonshotApiKey,
|
||||
moonshotApiLine: options.moonshotApiLine,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
})
|
||||
case "huggingface":
|
||||
return new HuggingFaceHandler({
|
||||
huggingFaceApiKey: options.huggingFaceApiKey,
|
||||
huggingFaceModelId: mode === "plan" ? options.planModeHuggingFaceModelId : options.actModeHuggingFaceModelId,
|
||||
huggingFaceModelInfo:
|
||||
mode === "plan" ? options.planModeHuggingFaceModelInfo : options.actModeHuggingFaceModelInfo,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "nebius":
|
||||
return new NebiusHandler({
|
||||
nebiusApiKey: options.nebiusApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "asksage":
|
||||
return new AskSageHandler({
|
||||
asksageApiKey: options.asksageApiKey,
|
||||
asksageApiUrl: options.asksageApiUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "xai":
|
||||
return new XAIHandler({
|
||||
xaiApiKey: options.xaiApiKey,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "sambanova":
|
||||
return new SambanovaHandler({
|
||||
sambanovaApiKey: options.sambanovaApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "cerebras":
|
||||
return new CerebrasHandler({
|
||||
cerebrasApiKey: options.cerebrasApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "groq":
|
||||
return new GroqHandler({
|
||||
groqApiKey: options.groqApiKey,
|
||||
groqModelId: mode === "plan" ? options.planModeGroqModelId : options.actModeGroqModelId,
|
||||
groqModelInfo: mode === "plan" ? options.planModeGroqModelInfo : options.actModeGroqModelInfo,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
groqModelId: options.groqModelId,
|
||||
groqModelInfo: options.groqModelInfo,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "sapaicore":
|
||||
return new SapAiCoreHandler({
|
||||
@@ -262,46 +236,37 @@ function createHandlerForProvider(
|
||||
sapAiCoreTokenUrl: options.sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup: options.sapAiResourceGroup,
|
||||
sapAiCoreBaseUrl: options.sapAiCoreBaseUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "claude-code":
|
||||
return new ClaudeCodeHandler({
|
||||
claudeCodePath: options.claudeCodePath,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
default:
|
||||
return new AnthropicHandler({
|
||||
apiKey: options.apiKey,
|
||||
anthropicBaseUrl: options.anthropicBaseUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function buildApiHandler(configuration: ApiConfiguration, mode: Mode): ApiHandler {
|
||||
const { planModeApiProvider, actModeApiProvider, ...options } = configuration
|
||||
|
||||
const apiProvider = mode === "plan" ? planModeApiProvider : actModeApiProvider
|
||||
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
||||
const { apiProvider, ...options } = configuration
|
||||
|
||||
// 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 {
|
||||
const thinkingBudgetTokens = mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens
|
||||
if (thinkingBudgetTokens && thinkingBudgetTokens > 0) {
|
||||
const handler = createHandlerForProvider(apiProvider, options, mode)
|
||||
if (options.thinkingBudgetTokens && options.thinkingBudgetTokens > 0) {
|
||||
const handler = createHandlerForProvider(apiProvider, options)
|
||||
|
||||
const modelInfo = handler.getModel().info
|
||||
if (modelInfo.maxTokens && thinkingBudgetTokens > modelInfo.maxTokens) {
|
||||
if (modelInfo.maxTokens && options.thinkingBudgetTokens > modelInfo.maxTokens) {
|
||||
const clippedValue = modelInfo.maxTokens - 1
|
||||
if (mode === "plan") {
|
||||
options.planModeThinkingBudgetTokens = clippedValue
|
||||
} else {
|
||||
options.actModeThinkingBudgetTokens = clippedValue
|
||||
}
|
||||
options.thinkingBudgetTokens = clippedValue
|
||||
} else {
|
||||
return handler // don't rebuild unless its necessary
|
||||
}
|
||||
@@ -310,5 +275,5 @@ export function buildApiHandler(configuration: ApiConfiguration, mode: Mode): Ap
|
||||
console.error("buildApiHandler error:", error)
|
||||
}
|
||||
|
||||
return createHandlerForProvider(apiProvider, options, mode)
|
||||
return createHandlerForProvider(apiProvider, options)
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ describe("AwsBedrockHandler", () => {
|
||||
})
|
||||
|
||||
const mockOptions: ApiHandlerOptions = {
|
||||
actModeApiModelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
apiModelId: "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: "",
|
||||
actModeAwsBedrockCustomSelected: false,
|
||||
actModeAwsBedrockCustomModelBaseId: undefined,
|
||||
actModeThinkingBudgetTokens: 1600,
|
||||
awsBedrockCustomSelected: false,
|
||||
awsBedrockCustomModelBaseId: undefined,
|
||||
thinkingBudgetTokens: 1600,
|
||||
}
|
||||
|
||||
const mockModelInfo = {
|
||||
@@ -616,8 +616,8 @@ describe("AwsBedrockHandler", () => {
|
||||
it("should return raw model ID for custom models", async () => {
|
||||
const customOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId:
|
||||
awsBedrockCustomSelected: true,
|
||||
apiModelId:
|
||||
"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,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId: "my-namespace/my-custom-model",
|
||||
awsBedrockCustomSelected: true,
|
||||
apiModelId: "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,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
|
||||
awsBedrockCustomSelected: true,
|
||||
apiModelId: "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,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId:
|
||||
awsBedrockCustomSelected: true,
|
||||
apiModelId:
|
||||
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
actModeAwsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
awsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
}
|
||||
const ultraThinkHandler = new AwsBedrockHandler(ultraThinkOptions)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("OllamaHandler", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
options = {
|
||||
actModeOllamaModelId: "llama2",
|
||||
ollamaModelId: "llama2",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
}
|
||||
handler = new OllamaHandler(options)
|
||||
|
||||
@@ -13,7 +13,7 @@ interface AnthropicHandlerOptions {
|
||||
}
|
||||
|
||||
export class AnthropicHandler implements ApiHandler {
|
||||
private options: AnthropicHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: Anthropic | undefined
|
||||
|
||||
constructor(options: AnthropicHandlerOptions) {
|
||||
|
||||
@@ -177,7 +177,17 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Cline API Error:", error)
|
||||
throw error
|
||||
const requestId = error?.request_id ? `\n | Request ID: ${error.request_id}` : ""
|
||||
if (error.code === "ERR_BAD_REQUEST" || error.status === 401) {
|
||||
throw new Error(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE + requestId)
|
||||
} else if (error.code === "insufficient_credits" || error.status === 402) {
|
||||
if (error.error) {
|
||||
throw new Error(JSON.stringify(error.error))
|
||||
}
|
||||
}
|
||||
const _error = error instanceof Error ? error : new Error(String(error))
|
||||
_error.message = _error.message + requestId
|
||||
throw _error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ interface GeminiHandlerOptions {
|
||||
* 4. Separating immediate costs from ongoing costs to avoid double-counting
|
||||
*/
|
||||
export class GeminiHandler implements ApiHandler {
|
||||
private options: GeminiHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: GoogleGenAI | undefined
|
||||
|
||||
constructor(options: GeminiHandlerOptions) {
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, HuggingFaceModelId, ModelInfo, huggingFaceDefaultModelId, huggingFaceModels } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface HuggingFaceHandlerOptions {
|
||||
huggingFaceApiKey?: string
|
||||
huggingFaceModelId?: string
|
||||
huggingFaceModelInfo?: ModelInfo
|
||||
}
|
||||
|
||||
export class HuggingFaceHandler implements ApiHandler {
|
||||
private options: HuggingFaceHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
private cachedModel: { id: HuggingFaceModelId; info: ModelInfo } | undefined
|
||||
|
||||
constructor(options: HuggingFaceHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
private ensureClient(): OpenAI {
|
||||
if (!this.client) {
|
||||
if (!this.options.huggingFaceApiKey) {
|
||||
throw new Error("Hugging Face API key is required")
|
||||
}
|
||||
|
||||
try {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://router.huggingface.co/v1",
|
||||
apiKey: this.options.huggingFaceApiKey,
|
||||
defaultHeaders: {
|
||||
"User-Agent": "Cline/1.0",
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Hugging Face client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
|
||||
private async *yieldUsage(info: ModelInfo, usage: OpenAI.Completions.CompletionUsage | undefined): ApiStream {
|
||||
if (!usage) {
|
||||
return
|
||||
}
|
||||
|
||||
const inputTokens = usage.prompt_tokens || 0
|
||||
const outputTokens = usage.completion_tokens || 0
|
||||
const totalCost = calculateApiCostOpenAI(info, inputTokens, outputTokens)
|
||||
|
||||
const usageData = {
|
||||
type: "usage" as const,
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: 0,
|
||||
totalCost: totalCost,
|
||||
}
|
||||
|
||||
yield usageData
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
try {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const requestParams = {
|
||||
model: model.id,
|
||||
max_tokens: model.info.maxTokens,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
}
|
||||
|
||||
const stream = (await client.chat.completions.create(requestParams)) as any
|
||||
|
||||
let chunkCount = 0
|
||||
let totalContent = ""
|
||||
|
||||
for await (const chunk of stream) {
|
||||
chunkCount++
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.content) {
|
||||
totalContent += delta.content
|
||||
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: HuggingFaceModelId; info: ModelInfo } {
|
||||
// Return cached model if available
|
||||
if (this.cachedModel) {
|
||||
return this.cachedModel
|
||||
}
|
||||
|
||||
const modelId = this.options.huggingFaceModelId
|
||||
|
||||
// List all available models for debugging
|
||||
const availableModels = Object.keys(huggingFaceModels)
|
||||
let result: { id: HuggingFaceModelId; info: ModelInfo }
|
||||
|
||||
if (modelId && modelId in huggingFaceModels) {
|
||||
const id = modelId as HuggingFaceModelId
|
||||
const modelInfo = huggingFaceModels[id]
|
||||
result = { id, info: modelInfo }
|
||||
} else {
|
||||
const defaultInfo = huggingFaceModels[huggingFaceDefaultModelId]
|
||||
result = {
|
||||
id: huggingFaceDefaultModelId,
|
||||
info: defaultInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result for future calls
|
||||
this.cachedModel = result
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,6 @@ 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,12 +14,6 @@ interface XAIHandlerOptions {
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
interface XAIHandlerOptions {
|
||||
xaiApiKey?: string
|
||||
reasoningEffort?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class XAIHandler implements ApiHandler {
|
||||
private options: XAIHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
export type Environment = "production" | "staging" | "local"
|
||||
|
||||
const CLINE_ENVIRONMENT: Environment = (process.env.CLINE_ENVIRONMENT as Environment) || "production"
|
||||
const CURRENT_ENVIRONMENT: Environment = "production"
|
||||
|
||||
interface EnvironmentConfig {
|
||||
appBaseUrl: string
|
||||
@@ -55,4 +55,4 @@ const configs: Record<Environment, EnvironmentConfig> = {
|
||||
},
|
||||
}
|
||||
|
||||
export const clineEnvConfig = configs[CLINE_ENVIRONMENT]
|
||||
export const clineEnvConfig = configs[CURRENT_ENVIRONMENT]
|
||||
|
||||
@@ -44,10 +44,12 @@ export const createRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
|
||||
if (fileExists) {
|
||||
const message = `${fileTypeName} file "${request.filename}" already exists.`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
// Still open it for editing
|
||||
await handleFileServiceRequest(controller, "openFile", { value: filePath })
|
||||
} else {
|
||||
@@ -61,10 +63,12 @@ export const createRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
await handleFileServiceRequest(controller, "openFile", { value: filePath })
|
||||
|
||||
const message = `Created new ${request.isGlobal ? "global" : "workspace"} ${fileTypeName} file: ${request.filename}`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return RuleFile.create({
|
||||
|
||||
@@ -46,10 +46,12 @@ export const deleteRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
const fileTypeName = request.type === "workflow" ? "workflow" : "rule"
|
||||
|
||||
const message = `${fileTypeName} file "${fileName}" deleted successfully`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
|
||||
return RuleFile.create({
|
||||
filePath: request.rulePath,
|
||||
|
||||
+238
-100
@@ -10,7 +10,7 @@ import { ClineAccountService } from "@services/account/ClineAccountService"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { ChatContent } from "@shared/ChatContent"
|
||||
import { ChatSettings, Mode, StoredChatSettings } from "@shared/ChatSettings"
|
||||
import { ChatSettings, StoredChatSettings } from "@shared/ChatSettings"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { ExtensionMessage, ExtensionState, Platform } from "@shared/ExtensionMessage"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
@@ -50,7 +50,6 @@ export class Controller {
|
||||
|
||||
private disposables: vscode.Disposable[] = []
|
||||
task?: Task
|
||||
|
||||
workspaceTracker: WorkspaceTracker
|
||||
mcpHub: McpHub
|
||||
accountService: ClineAccountService
|
||||
@@ -84,8 +83,8 @@ export class Controller {
|
||||
})
|
||||
}
|
||||
|
||||
async getCurrentMode(): Promise<Mode> {
|
||||
return ((await getGlobalState(this.context, "mode")) as Mode | undefined) || "act"
|
||||
private async getCurrentMode(): Promise<"plan" | "act"> {
|
||||
return ((await getGlobalState(this.context, "mode")) as "plan" | "act" | undefined) || "act"
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -113,20 +112,21 @@ 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 Promise.all([
|
||||
updateGlobalState(this.context, "planModeApiProvider", "openrouter"),
|
||||
updateGlobalState(this.context, "actModeApiProvider", "openrouter"),
|
||||
])
|
||||
await updateGlobalState(this.context, "apiProvider", "openrouter")
|
||||
await this.postStateToWebview()
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Successfully logged out of Cline",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Successfully logged out of Cline",
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Logout failed",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Logout failed",
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,10 +257,153 @@ export class Controller {
|
||||
// Capture mode switch telemetry | Capture regardless of if we know the taskId
|
||||
telemetryService.captureModeSwitch(this.task?.taskId ?? "0", chatSettings.mode)
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save only non-mode properties to global storage
|
||||
@@ -324,47 +467,30 @@ export class Controller {
|
||||
await this.authService.handleAuthCallback(customToken, provider ? provider : "google")
|
||||
|
||||
const clineProvider: ApiProvider = "cline"
|
||||
await updateGlobalState(this.context, "apiProvider", clineProvider)
|
||||
|
||||
// Get current settings to determine how to update providers
|
||||
const { planActSeparateModelsSetting } = await getAllExtensionState(this.context)
|
||||
const currentMode = await this.getCurrentMode()
|
||||
// Mark welcome view as completed since user has successfully logged in
|
||||
await updateGlobalState(this.context, "welcomeViewCompleted", true)
|
||||
|
||||
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, currentMode)
|
||||
this.task.api = buildApiHandler(updatedConfig)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
} catch (error) {
|
||||
console.error("Failed to handle auth callback:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to log in to Cline",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to log in to Cline",
|
||||
}),
|
||||
)
|
||||
// Even on login failure, we preserve any existing tokens
|
||||
// Only clear tokens on explicit logout
|
||||
}
|
||||
@@ -399,10 +525,12 @@ export class Controller {
|
||||
console.error("Failed to fetch MCP marketplace:", error)
|
||||
if (!silent) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
}),
|
||||
)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
@@ -486,10 +614,12 @@ export class Controller {
|
||||
} catch (error) {
|
||||
console.error("Failed to handle cached MCP marketplace:", error)
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,21 +640,14 @@ export class Controller {
|
||||
}
|
||||
|
||||
const openrouter: ApiProvider = "openrouter"
|
||||
const currentMode = await this.getCurrentMode()
|
||||
await Promise.all([
|
||||
updateGlobalState(this.context, "planModeApiProvider", openrouter),
|
||||
updateGlobalState(this.context, "actModeApiProvider", openrouter),
|
||||
])
|
||||
await updateGlobalState(this.context, "apiProvider", openrouter)
|
||||
await storeSecret(this.context, "openRouterApiKey", apiKey)
|
||||
await this.postStateToWebview()
|
||||
if (this.task) {
|
||||
// Get the updated API configuration (now includes the updated providers)
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
const updatedConfig = {
|
||||
...apiConfiguration,
|
||||
this.task.api = buildApiHandler({
|
||||
apiProvider: openrouter,
|
||||
openRouterApiKey: apiKey,
|
||||
}
|
||||
this.task.api = buildApiHandler(updatedConfig, currentMode)
|
||||
})
|
||||
}
|
||||
// await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome
|
||||
}
|
||||
@@ -859,20 +982,24 @@ export class Controller {
|
||||
// Check if there's a workspace folder open
|
||||
const cwd = await getCwd()
|
||||
if (!cwd) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No workspace folder open",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No workspace folder open",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the git diff
|
||||
const gitDiff = await getWorkingState(cwd)
|
||||
if (gitDiff === "No changes in working directory") {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "No changes in workspace for commit message",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "No changes in workspace for commit message",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -900,10 +1027,9 @@ 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, currentMode)
|
||||
const apiHandler = buildApiHandler(apiConfiguration)
|
||||
|
||||
// Create a system prompt
|
||||
const systemPrompt =
|
||||
@@ -941,46 +1067,58 @@ Commit message:`
|
||||
const repo = api.repositories[0]
|
||||
repo.inputBox.value = commitMessage
|
||||
const message = "Commit message generated and applied"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
const message = "No Git repositories found"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const message = "Git extension not found"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const message = "Failed to generate commit message"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (innerError) {
|
||||
const innerErrorMessage = innerError instanceof Error ? innerError.message : String(innerError)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${innerErrorMessage}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${innerErrorMessage}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${errorMessage}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${errorMessage}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { Controller } from ".."
|
||||
import { EmptyRequest } from "../../../shared/proto/common"
|
||||
import { OpenRouterCompatibleModelInfo, OpenRouterModelInfo } from "../../../shared/proto/models"
|
||||
import axios from "axios"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { huggingFaceModels } from "@shared/api"
|
||||
|
||||
/**
|
||||
* Ensures the cache directory exists and returns its path
|
||||
*/
|
||||
async function ensureCacheDirectoryExists(controller: Controller): Promise<string> {
|
||||
const cacheDir = path.join(controller.context.globalStorageUri.fsPath, "cache")
|
||||
try {
|
||||
await fs.mkdir(cacheDir, { recursive: true })
|
||||
} catch (error) {
|
||||
// Directory might already exist
|
||||
}
|
||||
return cacheDir
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the Hugging Face models and returns the updated model list
|
||||
* @param controller The controller instance
|
||||
* @param request Empty request object
|
||||
* @returns Response containing the Hugging Face models
|
||||
*/
|
||||
export async function refreshHuggingFaceModels(
|
||||
controller: Controller,
|
||||
_request: EmptyRequest,
|
||||
): Promise<OpenRouterCompatibleModelInfo> {
|
||||
const huggingFaceModelsFilePath = path.join(await ensureCacheDirectoryExists(controller), "huggingface_models.json")
|
||||
|
||||
let models: Record<string, OpenRouterModelInfo> = {}
|
||||
|
||||
try {
|
||||
// Fetch models from Hugging Face API
|
||||
const response = await axios.get("https://router.huggingface.co/v1/models", {
|
||||
timeout: 10000,
|
||||
})
|
||||
|
||||
if (response.data?.data) {
|
||||
const rawModels = response.data.data
|
||||
|
||||
// Transform HF models to OpenRouter-compatible format
|
||||
for (const rawModel of rawModels) {
|
||||
const modelInfo = OpenRouterModelInfo.create({
|
||||
maxTokens: 8192, // HF doesn't provide max_tokens, use default
|
||||
contextWindow: 128_000, // FIXME: HF doesn't provide context window, use default
|
||||
supportsImages: false, // Most models don't support images
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0, // Will be set based on providers
|
||||
outputPrice: 0, // Will be set based on providers
|
||||
cacheWritesPrice: 0,
|
||||
cacheReadsPrice: 0,
|
||||
description: `Available on providers: ${rawModel.providers?.join(", ") || "unknown"}`,
|
||||
})
|
||||
|
||||
// Add model-specific configurations if we have them in our static models
|
||||
if (rawModel.id in huggingFaceModels) {
|
||||
const staticModel = huggingFaceModels[rawModel.id as keyof typeof huggingFaceModels]
|
||||
modelInfo.maxTokens = staticModel.maxTokens
|
||||
modelInfo.contextWindow = staticModel.contextWindow
|
||||
modelInfo.supportsImages = staticModel.supportsImages
|
||||
modelInfo.supportsPromptCache = staticModel.supportsPromptCache
|
||||
modelInfo.inputPrice = staticModel.inputPrice
|
||||
modelInfo.outputPrice = staticModel.outputPrice
|
||||
modelInfo.description = staticModel.description || modelInfo.description
|
||||
}
|
||||
|
||||
models[rawModel.id] = modelInfo
|
||||
}
|
||||
|
||||
// Save to cache
|
||||
await fs.writeFile(huggingFaceModelsFilePath, JSON.stringify(models, null, 2))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching Hugging Face models:", error)
|
||||
|
||||
// Try to load from cache
|
||||
try {
|
||||
if (await fileExistsAtPath(huggingFaceModelsFilePath)) {
|
||||
const cachedModels = await fs.readFile(huggingFaceModelsFilePath, "utf-8")
|
||||
const parsedModels = JSON.parse(cachedModels)
|
||||
models = parsedModels
|
||||
}
|
||||
} catch (cacheError) {
|
||||
console.error("Error loading cached Hugging Face models:", cacheError)
|
||||
}
|
||||
|
||||
// If no cache available, use static models as fallback
|
||||
if (Object.keys(models).length === 0) {
|
||||
for (const [modelId, modelInfo] of Object.entries(huggingFaceModels)) {
|
||||
models[modelId] = OpenRouterModelInfo.create({
|
||||
maxTokens: modelInfo.maxTokens,
|
||||
contextWindow: modelInfo.contextWindow,
|
||||
supportsImages: modelInfo.supportsImages,
|
||||
supportsPromptCache: modelInfo.supportsPromptCache,
|
||||
inputPrice: modelInfo.inputPrice,
|
||||
outputPrice: modelInfo.outputPrice,
|
||||
cacheWritesPrice: (modelInfo as any).cacheWritesPrice || 0,
|
||||
cacheReadsPrice: (modelInfo as any).cacheReadsPrice || 0,
|
||||
description: modelInfo.description || "",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OpenRouterCompatibleModelInfo.create({ models })
|
||||
}
|
||||
@@ -29,8 +29,7 @@ export async function updateApiConfigurationProto(
|
||||
|
||||
// Update the task's API handler if there's an active task
|
||||
if (controller.task) {
|
||||
const currentMode = await controller.getCurrentMode()
|
||||
controller.task.api = buildApiHandler(appApiConfiguration, currentMode)
|
||||
controller.task.api = buildApiHandler(appApiConfiguration)
|
||||
}
|
||||
|
||||
// Post updated state to webview
|
||||
|
||||
@@ -15,16 +15,20 @@ import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
export async function resetState(controller: Controller, request: ResetStateRequest): Promise<Empty> {
|
||||
try {
|
||||
if (request.global) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting global state...",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting global state...",
|
||||
}),
|
||||
)
|
||||
await resetGlobalState(controller.context)
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting workspace state...",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting workspace state...",
|
||||
}),
|
||||
)
|
||||
await resetWorkspaceState(controller.context)
|
||||
}
|
||||
|
||||
@@ -33,10 +37,12 @@ export async function resetState(controller: Controller, request: ResetStateRequ
|
||||
controller.task = undefined
|
||||
}
|
||||
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "State reset",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "State reset",
|
||||
}),
|
||||
)
|
||||
await controller.postStateToWebview()
|
||||
|
||||
await sendChatButtonClickedEvent(controller.id)
|
||||
@@ -44,10 +50,12 @@ export async function resetState(controller: Controller, request: ResetStateRequ
|
||||
return Empty.create()
|
||||
} catch (error) {
|
||||
console.error("Error resetting state:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to reset state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to reset state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,12 @@ export async function updateDefaultTerminalProfile(
|
||||
// Show information message if terminals were closed
|
||||
if (closedCount > 0) {
|
||||
const message = `Closed ${closedCount} ${closedCount === 1 ? "terminal" : "terminals"} with different profile.`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Show warning if there are busy terminals that couldn't be closed
|
||||
@@ -38,10 +40,12 @@ export async function updateDefaultTerminalProfile(
|
||||
const message =
|
||||
`${busyTerminals.length} busy ${busyTerminals.length === 1 ? "terminal has" : "terminals have"} a different profile. ` +
|
||||
`Close ${busyTerminals.length === 1 ? "it" : "them"} to use the new profile for all commands.`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
await updateApiConfiguration(controller.context, apiConfiguration)
|
||||
|
||||
if (controller.task) {
|
||||
const currentMode = await controller.getCurrentMode()
|
||||
controller.task.api = buildApiHandler(apiConfiguration, currentMode)
|
||||
controller.task.api = buildApiHandler(apiConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
},
|
||||
}),
|
||||
)
|
||||
).selectedOption
|
||||
)?.selectedOption
|
||||
|
||||
// Default VS Code Cancel button returns `undefined` - don't delete anything
|
||||
if (userChoice === undefined) {
|
||||
@@ -67,15 +67,17 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
} else {
|
||||
// No favorited tasks found - show warning and ask user what to do
|
||||
const answer = (
|
||||
await getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.WARNING,
|
||||
message: "No favorited tasks found. Would you like to delete all tasks anyway?",
|
||||
options: {
|
||||
modal: true,
|
||||
items: ["Delete All Tasks"],
|
||||
},
|
||||
})
|
||||
).selectedOption
|
||||
await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message: "No favorited tasks found. Would you like to delete all tasks anyway?",
|
||||
options: {
|
||||
modal: true,
|
||||
items: ["Delete All Tasks"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
)?.selectedOption
|
||||
|
||||
// User cancelled - don't delete anything
|
||||
if (answer === undefined) {
|
||||
@@ -103,10 +105,12 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
await fs.rm(checkpointsDirPath, { recursive: true, force: true })
|
||||
}
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Encountered error while deleting task history, there may be some files left behind. Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Encountered error while deleting task history, there may be some files left behind. Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Update webview
|
||||
|
||||
@@ -28,11 +28,13 @@ export const deleteTasksWithIds: TaskMethodHandler = async (
|
||||
? "Are you sure you want to delete this task? This action cannot be undone."
|
||||
: `Are you sure you want to delete these ${taskCount} tasks? This action cannot be undone.`
|
||||
|
||||
const userChoice = await getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
options: { modal: true, items: ["Delete"] },
|
||||
})
|
||||
const userChoice = await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
options: { modal: true, items: ["Delete"] },
|
||||
}),
|
||||
)
|
||||
|
||||
if (userChoice === undefined) {
|
||||
return Empty.create()
|
||||
|
||||
@@ -30,77 +30,25 @@ 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, 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()
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
handleModelsServiceRequest(controller, "refreshGroqModels", EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
// 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()
|
||||
}
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as vscode from "vscode"
|
||||
import type { Controller } from "../index"
|
||||
import type { EmptyRequest } from "../../../shared/proto/common"
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import { telemetryService } from "../../../services/posthog/telemetry/TelemetryService"
|
||||
|
||||
/**
|
||||
* Opens the Cline walkthrough in VSCode
|
||||
* @param controller The controller instance
|
||||
* @param request Empty request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function openWalkthrough(controller: Controller, request: EmptyRequest): Promise<Empty> {
|
||||
try {
|
||||
await vscode.commands.executeCommand("workbench.action.openWalkthrough", "saoudrizwan.claude-dev#ClineWalkthrough")
|
||||
telemetryService.captureButtonClick("webview_openWalkthrough")
|
||||
return Empty.create({})
|
||||
} catch (error) {
|
||||
console.error(`Failed to open walkthrough: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -78,10 +78,12 @@ export async function parseMentions(
|
||||
await urlContentFetcher.launchBrowser()
|
||||
} catch (error) {
|
||||
launchBrowserError = error
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${urlMention}: ${error.message}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${urlMention}: ${error.message}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +100,12 @@ export async function parseMentions(
|
||||
const markdown = await urlContentFetcher.urlToMarkdown(mention)
|
||||
result = markdown
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${mention}: ${error.message}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${mention}: ${error.message}`,
|
||||
}),
|
||||
)
|
||||
result = `Error fetching content: ${error.message}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as diff from "diff"
|
||||
import * as path from "path"
|
||||
import { ClineIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/ClineIgnoreController"
|
||||
import { Mode } from "@/shared/ChatSettings"
|
||||
|
||||
export const formatResponse = {
|
||||
duplicateFileReadNotice: () =>
|
||||
@@ -148,7 +147,7 @@ Otherwise, if you have not completed the task and do not need additional informa
|
||||
},
|
||||
|
||||
taskResumption: (
|
||||
mode: Mode,
|
||||
mode: "plan" | "act",
|
||||
agoText: string,
|
||||
cwd: string,
|
||||
wasRecent: boolean | 0 | undefined,
|
||||
|
||||
@@ -21,7 +21,6 @@ export type SecretKey =
|
||||
| "asksageApiKey"
|
||||
| "xaiApiKey"
|
||||
| "moonshotApiKey"
|
||||
| "huggingFaceApiKey"
|
||||
| "nebiusApiKey"
|
||||
| "sambanovaApiKey"
|
||||
| "cerebrasApiKey"
|
||||
@@ -43,9 +42,12 @@ export type GlobalStateKey =
|
||||
| "lastShownAnnouncementId"
|
||||
| "taskHistory"
|
||||
| "openAiBaseUrl"
|
||||
| "openAiModelId"
|
||||
| "openAiModelInfo"
|
||||
| "openAiHeaders"
|
||||
| "ollamaBaseUrl"
|
||||
| "ollamaApiOptionsCtxNum"
|
||||
| "lmStudioModelId"
|
||||
| "lmStudioBaseUrl"
|
||||
| "anthropicBaseUrl"
|
||||
| "geminiBaseUrl"
|
||||
@@ -85,55 +87,38 @@ export type GlobalStateKey =
|
||||
// Settings around plan/act and ephemeral model configuration
|
||||
| "chatSettings"
|
||||
| "mode"
|
||||
// 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"
|
||||
// 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"
|
||||
| "sapAiCoreModelId"
|
||||
// Previous mode saved configurations (per workspace)
|
||||
| "previousModeApiProvider"
|
||||
| "previousModeModelId"
|
||||
| "previousModeModelInfo"
|
||||
| "previousModeVsCodeLmModelSelector"
|
||||
| "previousModeThinkingBudgetTokens"
|
||||
| "previousModeReasoningEffort"
|
||||
| "previousModeAwsBedrockCustomSelected"
|
||||
| "previousModeAwsBedrockCustomModelBaseId"
|
||||
| "previousModeSapAiCoreModelId"
|
||||
| "groqModelId"
|
||||
| "groqModelInfo"
|
||||
|
||||
export type LocalStateKey = "localClineRulesToggles" | "localCursorRulesToggles" | "localWindsurfRulesToggles" | "workflowToggles"
|
||||
|
||||
@@ -32,10 +32,6 @@ export async function migrateWorkspaceToGlobalStorage(context: vscode.ExtensionC
|
||||
"togetherModelId",
|
||||
"fireworksModelId",
|
||||
"sapAiCoreModelId",
|
||||
"groqModelId",
|
||||
"groqModelInfo",
|
||||
"huggingFaceModelId",
|
||||
"huggingFaceModelInfo",
|
||||
|
||||
// Previous mode settings
|
||||
"previousModeApiProvider",
|
||||
@@ -57,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 using raw VSCode method to avoid type errors
|
||||
await context.globalState.update(key, workspaceValue)
|
||||
// Move to global storage
|
||||
await updateGlobalState(context, key as GlobalStateKey, workspaceValue)
|
||||
// Remove from workspace storage
|
||||
await context.workspaceState.update(key, undefined)
|
||||
const newWorkspaceValue = await context.workspaceState.get(key)
|
||||
@@ -173,375 +169,6 @@ export async function migrateModeFromWorkspaceStorageToControllerState(context:
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateLegacyApiConfigurationToModeSpecific(context: vscode.ExtensionContext) {
|
||||
try {
|
||||
// Check if migration is needed - if planModeApiProvider already exists, skip migration
|
||||
const planModeApiProvider = await context.globalState.get("planModeApiProvider")
|
||||
if (planModeApiProvider !== undefined) {
|
||||
console.log("Legacy API configuration migration already completed, skipping...")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Starting legacy API configuration migration to mode-specific keys...")
|
||||
|
||||
// Get the planActSeparateModelsSetting to determine migration strategy
|
||||
const planActSeparateModelsSetting = (await context.globalState.get("planActSeparateModelsSetting")) as
|
||||
| boolean
|
||||
| undefined
|
||||
|
||||
// Read legacy values directly
|
||||
const apiProvider = await context.globalState.get("apiProvider")
|
||||
const apiModelId = await context.globalState.get("apiModelId")
|
||||
const thinkingBudgetTokens = await context.globalState.get("thinkingBudgetTokens")
|
||||
const reasoningEffort = await context.globalState.get("reasoningEffort")
|
||||
const vsCodeLmModelSelector = await context.globalState.get("vsCodeLmModelSelector")
|
||||
const awsBedrockCustomSelected = await context.globalState.get("awsBedrockCustomSelected")
|
||||
const awsBedrockCustomModelBaseId = await context.globalState.get("awsBedrockCustomModelBaseId")
|
||||
const openRouterModelId = await context.globalState.get("openRouterModelId")
|
||||
const openRouterModelInfo = await context.globalState.get("openRouterModelInfo")
|
||||
const openAiModelId = await context.globalState.get("openAiModelId")
|
||||
const openAiModelInfo = await context.globalState.get("openAiModelInfo")
|
||||
const ollamaModelId = await context.globalState.get("ollamaModelId")
|
||||
const lmStudioModelId = await context.globalState.get("lmStudioModelId")
|
||||
const liteLlmModelId = await context.globalState.get("liteLlmModelId")
|
||||
const liteLlmModelInfo = await context.globalState.get("liteLlmModelInfo")
|
||||
const requestyModelId = await context.globalState.get("requestyModelId")
|
||||
const requestyModelInfo = await context.globalState.get("requestyModelInfo")
|
||||
const togetherModelId = await context.globalState.get("togetherModelId")
|
||||
const fireworksModelId = await context.globalState.get("fireworksModelId")
|
||||
const sapAiCoreModelId = await context.globalState.get("sapAiCoreModelId")
|
||||
const groqModelId = await context.globalState.get("groqModelId")
|
||||
const groqModelInfo = await context.globalState.get("groqModelInfo")
|
||||
const huggingFaceModelId = await context.globalState.get("huggingFaceModelId")
|
||||
const huggingFaceModelInfo = await context.globalState.get("huggingFaceModelInfo")
|
||||
|
||||
// Read previous mode values
|
||||
const previousModeApiProvider = await context.globalState.get("previousModeApiProvider")
|
||||
const previousModeModelId = await context.globalState.get("previousModeModelId")
|
||||
const previousModeModelInfo = await context.globalState.get("previousModeModelInfo")
|
||||
const previousModeVsCodeLmModelSelector = await context.globalState.get("previousModeVsCodeLmModelSelector")
|
||||
const previousModeThinkingBudgetTokens = await context.globalState.get("previousModeThinkingBudgetTokens")
|
||||
const previousModeReasoningEffort = await context.globalState.get("previousModeReasoningEffort")
|
||||
const previousModeAwsBedrockCustomSelected = await context.globalState.get("previousModeAwsBedrockCustomSelected")
|
||||
const previousModeAwsBedrockCustomModelBaseId = await context.globalState.get("previousModeAwsBedrockCustomModelBaseId")
|
||||
const previousModeSapAiCoreModelId = await context.globalState.get("previousModeSapAiCoreModelId")
|
||||
|
||||
// Migrate based on planActSeparateModelsSetting
|
||||
if (planActSeparateModelsSetting === false) {
|
||||
console.log("Migrating with separate models DISABLED - using current values for both modes")
|
||||
|
||||
// Use current values for both plan and act modes
|
||||
if (apiProvider !== undefined) {
|
||||
await context.globalState.update("planModeApiProvider", apiProvider)
|
||||
await context.globalState.update("actModeApiProvider", apiProvider)
|
||||
}
|
||||
if (apiModelId !== undefined) {
|
||||
await context.globalState.update("planModeApiModelId", apiModelId)
|
||||
await context.globalState.update("actModeApiModelId", apiModelId)
|
||||
}
|
||||
if (thinkingBudgetTokens !== undefined) {
|
||||
await context.globalState.update("planModeThinkingBudgetTokens", thinkingBudgetTokens)
|
||||
await context.globalState.update("actModeThinkingBudgetTokens", thinkingBudgetTokens)
|
||||
}
|
||||
if (reasoningEffort !== undefined) {
|
||||
await context.globalState.update("planModeReasoningEffort", reasoningEffort)
|
||||
await context.globalState.update("actModeReasoningEffort", reasoningEffort)
|
||||
}
|
||||
if (vsCodeLmModelSelector !== undefined) {
|
||||
await context.globalState.update("planModeVsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
await context.globalState.update("actModeVsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
}
|
||||
if (awsBedrockCustomSelected !== undefined) {
|
||||
await context.globalState.update("planModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
await context.globalState.update("actModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
}
|
||||
if (awsBedrockCustomModelBaseId !== undefined) {
|
||||
await context.globalState.update("planModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
}
|
||||
if (openRouterModelId !== undefined) {
|
||||
await context.globalState.update("planModeOpenRouterModelId", openRouterModelId)
|
||||
await context.globalState.update("actModeOpenRouterModelId", openRouterModelId)
|
||||
}
|
||||
if (openRouterModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeOpenRouterModelInfo", openRouterModelInfo)
|
||||
await context.globalState.update("actModeOpenRouterModelInfo", openRouterModelInfo)
|
||||
}
|
||||
if (openAiModelId !== undefined) {
|
||||
await context.globalState.update("planModeOpenAiModelId", openAiModelId)
|
||||
await context.globalState.update("actModeOpenAiModelId", openAiModelId)
|
||||
}
|
||||
if (openAiModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeOpenAiModelInfo", openAiModelInfo)
|
||||
await context.globalState.update("actModeOpenAiModelInfo", openAiModelInfo)
|
||||
}
|
||||
if (ollamaModelId !== undefined) {
|
||||
await context.globalState.update("planModeOllamaModelId", ollamaModelId)
|
||||
await context.globalState.update("actModeOllamaModelId", ollamaModelId)
|
||||
}
|
||||
if (lmStudioModelId !== undefined) {
|
||||
await context.globalState.update("planModeLmStudioModelId", lmStudioModelId)
|
||||
await context.globalState.update("actModeLmStudioModelId", lmStudioModelId)
|
||||
}
|
||||
if (liteLlmModelId !== undefined) {
|
||||
await context.globalState.update("planModeLiteLlmModelId", liteLlmModelId)
|
||||
await context.globalState.update("actModeLiteLlmModelId", liteLlmModelId)
|
||||
}
|
||||
if (liteLlmModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeLiteLlmModelInfo", liteLlmModelInfo)
|
||||
await context.globalState.update("actModeLiteLlmModelInfo", liteLlmModelInfo)
|
||||
}
|
||||
if (requestyModelId !== undefined) {
|
||||
await context.globalState.update("planModeRequestyModelId", requestyModelId)
|
||||
await context.globalState.update("actModeRequestyModelId", requestyModelId)
|
||||
}
|
||||
if (requestyModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeRequestyModelInfo", requestyModelInfo)
|
||||
await context.globalState.update("actModeRequestyModelInfo", requestyModelInfo)
|
||||
}
|
||||
if (togetherModelId !== undefined) {
|
||||
await context.globalState.update("planModeTogetherModelId", togetherModelId)
|
||||
await context.globalState.update("actModeTogetherModelId", togetherModelId)
|
||||
}
|
||||
if (fireworksModelId !== undefined) {
|
||||
await context.globalState.update("planModeFireworksModelId", fireworksModelId)
|
||||
await context.globalState.update("actModeFireworksModelId", fireworksModelId)
|
||||
}
|
||||
if (sapAiCoreModelId !== undefined) {
|
||||
await context.globalState.update("planModeSapAiCoreModelId", sapAiCoreModelId)
|
||||
await context.globalState.update("actModeSapAiCoreModelId", sapAiCoreModelId)
|
||||
}
|
||||
if (groqModelId !== undefined) {
|
||||
await context.globalState.update("planModeGroqModelId", groqModelId)
|
||||
await context.globalState.update("actModeGroqModelId", groqModelId)
|
||||
}
|
||||
if (groqModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeGroqModelInfo", groqModelInfo)
|
||||
await context.globalState.update("actModeGroqModelInfo", groqModelInfo)
|
||||
}
|
||||
if (huggingFaceModelId !== undefined) {
|
||||
await context.globalState.update("planModeHuggingFaceModelId", huggingFaceModelId)
|
||||
await context.globalState.update("actModeHuggingFaceModelId", huggingFaceModelId)
|
||||
}
|
||||
if (huggingFaceModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeHuggingFaceModelInfo", huggingFaceModelInfo)
|
||||
await context.globalState.update("actModeHuggingFaceModelInfo", huggingFaceModelInfo)
|
||||
}
|
||||
} else {
|
||||
console.log("Migrating with separate models ENABLED - using current->plan, previous->act")
|
||||
|
||||
// Use current values for plan mode
|
||||
if (apiProvider !== undefined) {
|
||||
await context.globalState.update("planModeApiProvider", apiProvider)
|
||||
}
|
||||
if (apiModelId !== undefined) {
|
||||
await context.globalState.update("planModeApiModelId", apiModelId)
|
||||
}
|
||||
if (thinkingBudgetTokens !== undefined) {
|
||||
await context.globalState.update("planModeThinkingBudgetTokens", thinkingBudgetTokens)
|
||||
}
|
||||
if (reasoningEffort !== undefined) {
|
||||
await context.globalState.update("planModeReasoningEffort", reasoningEffort)
|
||||
}
|
||||
if (vsCodeLmModelSelector !== undefined) {
|
||||
await context.globalState.update("planModeVsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
}
|
||||
if (awsBedrockCustomSelected !== undefined) {
|
||||
await context.globalState.update("planModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
}
|
||||
if (awsBedrockCustomModelBaseId !== undefined) {
|
||||
await context.globalState.update("planModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
}
|
||||
if (openRouterModelId !== undefined) {
|
||||
await context.globalState.update("planModeOpenRouterModelId", openRouterModelId)
|
||||
}
|
||||
if (openRouterModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeOpenRouterModelInfo", openRouterModelInfo)
|
||||
}
|
||||
if (openAiModelId !== undefined) {
|
||||
await context.globalState.update("planModeOpenAiModelId", openAiModelId)
|
||||
}
|
||||
if (openAiModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeOpenAiModelInfo", openAiModelInfo)
|
||||
}
|
||||
if (ollamaModelId !== undefined) {
|
||||
await context.globalState.update("planModeOllamaModelId", ollamaModelId)
|
||||
}
|
||||
if (lmStudioModelId !== undefined) {
|
||||
await context.globalState.update("planModeLmStudioModelId", lmStudioModelId)
|
||||
}
|
||||
if (liteLlmModelId !== undefined) {
|
||||
await context.globalState.update("planModeLiteLlmModelId", liteLlmModelId)
|
||||
}
|
||||
if (liteLlmModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeLiteLlmModelInfo", liteLlmModelInfo)
|
||||
}
|
||||
if (requestyModelId !== undefined) {
|
||||
await context.globalState.update("planModeRequestyModelId", requestyModelId)
|
||||
}
|
||||
if (requestyModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeRequestyModelInfo", requestyModelInfo)
|
||||
}
|
||||
if (togetherModelId !== undefined) {
|
||||
await context.globalState.update("planModeTogetherModelId", togetherModelId)
|
||||
}
|
||||
if (fireworksModelId !== undefined) {
|
||||
await context.globalState.update("planModeFireworksModelId", fireworksModelId)
|
||||
}
|
||||
if (sapAiCoreModelId !== undefined) {
|
||||
await context.globalState.update("planModeSapAiCoreModelId", sapAiCoreModelId)
|
||||
}
|
||||
if (groqModelId !== undefined) {
|
||||
await context.globalState.update("planModeGroqModelId", groqModelId)
|
||||
}
|
||||
if (groqModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeGroqModelInfo", groqModelInfo)
|
||||
}
|
||||
if (huggingFaceModelId !== undefined) {
|
||||
await context.globalState.update("planModeHuggingFaceModelId", huggingFaceModelId)
|
||||
}
|
||||
if (huggingFaceModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeHuggingFaceModelInfo", huggingFaceModelInfo)
|
||||
}
|
||||
|
||||
// Use previous values for act mode (with fallback to current values)
|
||||
if (previousModeApiProvider !== undefined) {
|
||||
await context.globalState.update("actModeApiProvider", previousModeApiProvider)
|
||||
} else if (apiProvider !== undefined) {
|
||||
await context.globalState.update("actModeApiProvider", apiProvider)
|
||||
}
|
||||
if (previousModeModelId !== undefined) {
|
||||
await context.globalState.update("actModeApiModelId", previousModeModelId)
|
||||
} else if (apiModelId !== undefined) {
|
||||
await context.globalState.update("actModeApiModelId", apiModelId)
|
||||
}
|
||||
if (previousModeThinkingBudgetTokens !== undefined) {
|
||||
await context.globalState.update("actModeThinkingBudgetTokens", previousModeThinkingBudgetTokens)
|
||||
} else if (thinkingBudgetTokens !== undefined) {
|
||||
await context.globalState.update("actModeThinkingBudgetTokens", thinkingBudgetTokens)
|
||||
}
|
||||
if (previousModeReasoningEffort !== undefined) {
|
||||
await context.globalState.update("actModeReasoningEffort", previousModeReasoningEffort)
|
||||
} else if (reasoningEffort !== undefined) {
|
||||
await context.globalState.update("actModeReasoningEffort", reasoningEffort)
|
||||
}
|
||||
if (previousModeVsCodeLmModelSelector !== undefined) {
|
||||
await context.globalState.update("actModeVsCodeLmModelSelector", previousModeVsCodeLmModelSelector)
|
||||
} else if (vsCodeLmModelSelector !== undefined) {
|
||||
await context.globalState.update("actModeVsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
}
|
||||
if (previousModeAwsBedrockCustomSelected !== undefined) {
|
||||
await context.globalState.update("actModeAwsBedrockCustomSelected", previousModeAwsBedrockCustomSelected)
|
||||
} else if (awsBedrockCustomSelected !== undefined) {
|
||||
await context.globalState.update("actModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
}
|
||||
if (previousModeAwsBedrockCustomModelBaseId !== undefined) {
|
||||
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", previousModeAwsBedrockCustomModelBaseId)
|
||||
} else if (awsBedrockCustomModelBaseId !== undefined) {
|
||||
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
}
|
||||
if (previousModeSapAiCoreModelId !== undefined) {
|
||||
await context.globalState.update("actModeSapAiCoreModelId", previousModeSapAiCoreModelId)
|
||||
} else if (sapAiCoreModelId !== undefined) {
|
||||
await context.globalState.update("actModeSapAiCoreModelId", sapAiCoreModelId)
|
||||
}
|
||||
|
||||
// For fields without previous variants, use current values for act mode
|
||||
if (previousModeModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeOpenRouterModelInfo", previousModeModelInfo)
|
||||
} else if (openRouterModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeOpenRouterModelInfo", openRouterModelInfo)
|
||||
}
|
||||
if (openRouterModelId !== undefined) {
|
||||
await context.globalState.update("actModeOpenRouterModelId", openRouterModelId)
|
||||
}
|
||||
if (openAiModelId !== undefined) {
|
||||
await context.globalState.update("actModeOpenAiModelId", openAiModelId)
|
||||
}
|
||||
if (openAiModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeOpenAiModelInfo", openAiModelInfo)
|
||||
}
|
||||
if (ollamaModelId !== undefined) {
|
||||
await context.globalState.update("actModeOllamaModelId", ollamaModelId)
|
||||
}
|
||||
if (lmStudioModelId !== undefined) {
|
||||
await context.globalState.update("actModeLmStudioModelId", lmStudioModelId)
|
||||
}
|
||||
if (liteLlmModelId !== undefined) {
|
||||
await context.globalState.update("actModeLiteLlmModelId", liteLlmModelId)
|
||||
}
|
||||
if (liteLlmModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeLiteLlmModelInfo", liteLlmModelInfo)
|
||||
}
|
||||
if (requestyModelId !== undefined) {
|
||||
await context.globalState.update("actModeRequestyModelId", requestyModelId)
|
||||
}
|
||||
if (requestyModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeRequestyModelInfo", requestyModelInfo)
|
||||
}
|
||||
if (togetherModelId !== undefined) {
|
||||
await context.globalState.update("actModeTogetherModelId", togetherModelId)
|
||||
}
|
||||
if (fireworksModelId !== undefined) {
|
||||
await context.globalState.update("actModeFireworksModelId", fireworksModelId)
|
||||
}
|
||||
if (groqModelId !== undefined) {
|
||||
await context.globalState.update("actModeGroqModelId", groqModelId)
|
||||
}
|
||||
if (groqModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeGroqModelInfo", groqModelInfo)
|
||||
}
|
||||
if (huggingFaceModelId !== undefined) {
|
||||
await context.globalState.update("actModeHuggingFaceModelId", huggingFaceModelId)
|
||||
}
|
||||
if (huggingFaceModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeHuggingFaceModelInfo", huggingFaceModelInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up legacy keys after successful migration
|
||||
console.log("Cleaning up legacy keys...")
|
||||
await context.globalState.update("apiProvider", undefined)
|
||||
await context.globalState.update("apiModelId", undefined)
|
||||
await context.globalState.update("thinkingBudgetTokens", undefined)
|
||||
await context.globalState.update("reasoningEffort", undefined)
|
||||
await context.globalState.update("vsCodeLmModelSelector", undefined)
|
||||
await context.globalState.update("awsBedrockCustomSelected", undefined)
|
||||
await context.globalState.update("awsBedrockCustomModelBaseId", undefined)
|
||||
await context.globalState.update("openRouterModelId", undefined)
|
||||
await context.globalState.update("openRouterModelInfo", undefined)
|
||||
await context.globalState.update("openAiModelId", undefined)
|
||||
await context.globalState.update("openAiModelInfo", undefined)
|
||||
await context.globalState.update("ollamaModelId", undefined)
|
||||
await context.globalState.update("lmStudioModelId", undefined)
|
||||
await context.globalState.update("liteLlmModelId", undefined)
|
||||
await context.globalState.update("liteLlmModelInfo", undefined)
|
||||
await context.globalState.update("requestyModelId", undefined)
|
||||
await context.globalState.update("requestyModelInfo", undefined)
|
||||
await context.globalState.update("togetherModelId", undefined)
|
||||
await context.globalState.update("fireworksModelId", undefined)
|
||||
await context.globalState.update("sapAiCoreModelId", undefined)
|
||||
await context.globalState.update("groqModelId", undefined)
|
||||
await context.globalState.update("groqModelInfo", undefined)
|
||||
await context.globalState.update("huggingFaceModelId", undefined)
|
||||
await context.globalState.update("huggingFaceModelInfo", undefined)
|
||||
await context.globalState.update("previousModeApiProvider", undefined)
|
||||
await context.globalState.update("previousModeModelId", undefined)
|
||||
await context.globalState.update("previousModeModelInfo", undefined)
|
||||
await context.globalState.update("previousModeVsCodeLmModelSelector", undefined)
|
||||
await context.globalState.update("previousModeThinkingBudgetTokens", undefined)
|
||||
await context.globalState.update("previousModeReasoningEffort", undefined)
|
||||
await context.globalState.update("previousModeAwsBedrockCustomSelected", undefined)
|
||||
await context.globalState.update("previousModeAwsBedrockCustomModelBaseId", undefined)
|
||||
await context.globalState.update("previousModeSapAiCoreModelId", undefined)
|
||||
|
||||
console.log("Successfully migrated legacy API configuration to mode-specific keys")
|
||||
} catch (error) {
|
||||
console.error("Failed to migrate legacy API configuration to mode-specific keys:", error)
|
||||
// Continue execution - migration failure shouldn't break extension startup
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateWelcomeViewCompleted(context: vscode.ExtensionContext) {
|
||||
try {
|
||||
// Check if welcomeViewCompleted is already set
|
||||
@@ -563,10 +190,8 @@ export async function migrateWelcomeViewCompleted(context: vscode.ExtensionConte
|
||||
config.awsRegion,
|
||||
config.vertexProjectId,
|
||||
config.openAiApiKey,
|
||||
config.planModeOllamaModelId,
|
||||
config.planModeLmStudioModelId,
|
||||
config.actModeOllamaModelId,
|
||||
config.actModeLmStudioModelId,
|
||||
config.ollamaModelId,
|
||||
config.lmStudioModelId,
|
||||
config.liteLlmApiKey,
|
||||
config.geminiApiKey,
|
||||
config.openAiNativeApiKey,
|
||||
@@ -576,8 +201,7 @@ export async function migrateWelcomeViewCompleted(context: vscode.ExtensionConte
|
||||
config.qwenApiKey,
|
||||
config.doubaoApiKey,
|
||||
config.mistralApiKey,
|
||||
config.planModeVsCodeLmModelSelector,
|
||||
config.actModeVsCodeLmModelSelector,
|
||||
config.vsCodeLmModelSelector,
|
||||
config.clineAccountId,
|
||||
config.asksageApiKey,
|
||||
config.xaiApiKey,
|
||||
|
||||
+144
-260
@@ -1,5 +1,5 @@
|
||||
import * as vscode from "vscode"
|
||||
import { DEFAULT_CHAT_SETTINGS, Mode } from "@shared/ChatSettings"
|
||||
import { DEFAULT_CHAT_SETTINGS } 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"
|
||||
@@ -170,7 +170,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
groqApiKey,
|
||||
moonshotApiKey,
|
||||
nebiusApiKey,
|
||||
huggingFaceApiKey,
|
||||
planActSeparateModelsSettingRaw,
|
||||
favoritedModelIds,
|
||||
globalClineRulesToggles,
|
||||
@@ -190,6 +189,8 @@ 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>,
|
||||
@@ -249,7 +250,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getSecret(context, "groqApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "moonshotApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "nebiusApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "huggingFaceApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planActSeparateModelsSetting") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
|
||||
getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
|
||||
@@ -269,6 +269,8 @@ 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
|
||||
@@ -277,115 +279,74 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
const [
|
||||
chatSettings,
|
||||
currentMode,
|
||||
// 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,
|
||||
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,
|
||||
] = await Promise.all([
|
||||
getGlobalState(context, "chatSettings") as Promise<StoredChatSettings | 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>,
|
||||
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>,
|
||||
])
|
||||
|
||||
const processingStart = performance.now()
|
||||
let apiProvider: ApiProvider
|
||||
if (planModeApiProvider) {
|
||||
apiProvider = planModeApiProvider
|
||||
if (storedApiProvider) {
|
||||
// Use the explicitly stored provider - this respects user's selection
|
||||
apiProvider = storedApiProvider
|
||||
} 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)
|
||||
@@ -408,7 +369,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
planActSeparateModelsSetting = planActSeparateModelsSettingRaw
|
||||
} else {
|
||||
// default to true for existing users
|
||||
if (planModeApiProvider) {
|
||||
if (storedApiProvider) {
|
||||
planActSeparateModelsSetting = true
|
||||
} else {
|
||||
// default to false for new users
|
||||
@@ -421,6 +382,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
|
||||
return {
|
||||
apiConfiguration: {
|
||||
apiProvider,
|
||||
apiModelId,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
clineAccountId,
|
||||
@@ -436,13 +399,19 @@ 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,
|
||||
@@ -450,18 +419,29 @@ 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,
|
||||
@@ -470,6 +450,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
sambanovaApiKey,
|
||||
cerebrasApiKey,
|
||||
groqApiKey,
|
||||
groqModelId,
|
||||
groqModelInfo,
|
||||
moonshotApiKey,
|
||||
nebiusApiKey,
|
||||
favoritedModelIds,
|
||||
@@ -479,55 +461,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup,
|
||||
huggingFaceApiKey,
|
||||
// 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,
|
||||
sapAiCoreModelId,
|
||||
},
|
||||
isNewUser: isNewUser ?? true,
|
||||
welcomeViewCompleted,
|
||||
@@ -543,6 +477,15 @@ 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,
|
||||
@@ -559,6 +502,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
|
||||
export async function updateApiConfiguration(context: vscode.ExtensionContext, apiConfiguration: ApiConfiguration) {
|
||||
const {
|
||||
apiProvider,
|
||||
apiModelId,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
awsAccessKey,
|
||||
@@ -572,13 +517,19 @@ 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,
|
||||
@@ -586,13 +537,21 @@ 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,
|
||||
@@ -600,14 +559,19 @@ 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,
|
||||
@@ -615,113 +579,35 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup,
|
||||
sapAiCoreModelId,
|
||||
claudeCodePath,
|
||||
huggingFaceApiKey,
|
||||
// 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 = {
|
||||
// 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,
|
||||
// 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,
|
||||
|
||||
// Global state updates (27 keys)
|
||||
awsRegion,
|
||||
@@ -786,7 +672,6 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
nebiusApiKey,
|
||||
sapAiCoreClientId,
|
||||
sapAiCoreClientSecret,
|
||||
huggingFaceApiKey,
|
||||
}
|
||||
|
||||
// Execute batched operations in parallel for maximum performance
|
||||
@@ -830,7 +715,6 @@ export async function resetGlobalState(context: vscode.ExtensionContext) {
|
||||
"groqApiKey",
|
||||
"moonshotApiKey",
|
||||
"nebiusApiKey",
|
||||
"huggingFaceApiKey",
|
||||
]
|
||||
for (const key of secretKeys) {
|
||||
await storeSecret(context, key, undefined)
|
||||
|
||||
@@ -54,7 +54,6 @@ 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
|
||||
@@ -91,7 +90,6 @@ export class ToolExecutor {
|
||||
private browserSettings: BrowserSettings,
|
||||
private cwd: string,
|
||||
private taskId: string,
|
||||
private chatSettings: ChatSettings,
|
||||
|
||||
// Callbacks to the Task (Entity)
|
||||
private say: (
|
||||
@@ -636,7 +634,7 @@ export class ToolExecutor {
|
||||
}
|
||||
await this.diffViewProvider.update(newContent, true)
|
||||
await setTimeoutPromise(300) // wait for diff view to update
|
||||
await this.diffViewProvider.scrollToFirstDiff()
|
||||
this.diffViewProvider.scrollToFirstDiff()
|
||||
// showOmissionWarning(this.diffViewProvider.originalContent || "", newContent)
|
||||
|
||||
const completeMessage = JSON.stringify({
|
||||
@@ -1919,12 +1917,7 @@ 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 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}`
|
||||
const providerAndModel = `${await getGlobalState(this.context, "apiProvider")} / ${this.api.getModel().id}`
|
||||
|
||||
// Ask user for confirmation
|
||||
const bugReportData = JSON.stringify({
|
||||
|
||||
+51
-77
@@ -81,10 +81,9 @@ import { refreshWorkflowToggles } from "../context/instructions/user-instruction
|
||||
import { MessageStateHandler } from "./message-state"
|
||||
import { TaskState } from "./TaskState"
|
||||
import { ToolExecutor } from "./ToolExecutor"
|
||||
import { updateApiReqMsg } from "./utils"
|
||||
import { createDiffViewProvider } from "@/hosts/host-providers"
|
||||
import { ErrorService } from "@/services/error/ErrorService"
|
||||
import { ClineErrorType } from "@/services/error/ClineError"
|
||||
import { extractErrorDetails, formatErrorWithStatusCode, updateApiReqMsg } from "./utils"
|
||||
import { createDiffViewProvider, getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
|
||||
export const USE_EXPERIMENTAL_CLAUDE4_FEATURES = false
|
||||
|
||||
export type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
|
||||
@@ -166,19 +165,8 @@ export class Task {
|
||||
this.reinitExistingTaskFromId = reinitExistingTaskFromId
|
||||
this.cancelTask = cancelTask
|
||||
this.clineIgnoreController = new ClineIgnoreController(cwd)
|
||||
|
||||
// TODO(ae) this is a hack to replace the terminal manager for standalone,
|
||||
// until we have proper host bridge support for terminal execution. The
|
||||
// standaloneTerminalManager is defined in the vscode-impls and injected
|
||||
// during compilation of the standalone manager only, so this variable only
|
||||
// exists in that case
|
||||
if ((global as any).standaloneTerminalManager) {
|
||||
console.log("[DEBUG] Using vscode-impls.js terminal manager")
|
||||
this.terminalManager = (global as any).standaloneTerminalManager
|
||||
} else {
|
||||
console.log("[DEBUG] Using built in terminal manager")
|
||||
this.terminalManager = new TerminalManager()
|
||||
}
|
||||
// Initialization moved to startTask/resumeTaskFromHistory
|
||||
this.terminalManager = new TerminalManager()
|
||||
this.terminalManager.setShellIntegrationTimeout(shellIntegrationTimeout)
|
||||
this.terminalManager.setTerminalReuseEnabled(terminalReuseEnabled ?? true)
|
||||
this.terminalManager.setTerminalOutputLineLimit(terminalOutputLineLimit)
|
||||
@@ -261,19 +249,12 @@ export class Task {
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if (apiConfiguration.apiProvider === "openai" || apiConfiguration.apiProvider === "openai-native") {
|
||||
effectiveApiConfiguration.reasoningEffort = chatSettings.openAIReasoningEffort
|
||||
}
|
||||
|
||||
// Now that taskId is initialized, we can build the API handler
|
||||
this.api = buildApiHandler(effectiveApiConfiguration, chatSettings.mode)
|
||||
this.api = buildApiHandler(effectiveApiConfiguration)
|
||||
|
||||
// Set taskId on browserSession for telemetry tracking
|
||||
this.browserSession.setTaskId(this.taskId)
|
||||
@@ -288,10 +269,10 @@ export class Task {
|
||||
// initialize telemetry
|
||||
if (historyItem) {
|
||||
// Open task from history
|
||||
telemetryService.captureTaskRestarted(this.taskId, currentProvider)
|
||||
telemetryService.captureTaskRestarted(this.taskId, apiConfiguration.apiProvider)
|
||||
} else {
|
||||
// New task started
|
||||
telemetryService.captureTaskCreated(this.taskId, currentProvider)
|
||||
telemetryService.captureTaskCreated(this.taskId, apiConfiguration.apiProvider)
|
||||
}
|
||||
|
||||
this.toolExecutor = new ToolExecutor(
|
||||
@@ -311,7 +292,6 @@ export class Task {
|
||||
this.browserSettings,
|
||||
cwd,
|
||||
this.taskId,
|
||||
this.chatSettings,
|
||||
this.say.bind(this),
|
||||
this.ask.bind(this),
|
||||
this.saveCheckpoint.bind(this),
|
||||
@@ -1597,15 +1577,6 @@ export class Task {
|
||||
}
|
||||
}
|
||||
|
||||
private async getCurrentProviderInfo(): Promise<{ modelId: string; providerId: string }> {
|
||||
const modelId = this.api.getModel()?.id
|
||||
const providerId =
|
||||
this.chatSettings.mode === "plan"
|
||||
? ((await getGlobalState(this.getContext(), "planModeApiProvider")) as string)
|
||||
: ((await getGlobalState(this.getContext(), "actModeApiProvider")) as string)
|
||||
return { modelId, providerId }
|
||||
}
|
||||
|
||||
async *attemptApiRequest(previousApiReqIndex: number): ApiStream {
|
||||
// Wait for MCP servers to be connected before generating system prompt
|
||||
await pWaitFor(() => this.mcpHub.isConnecting !== true, { timeout: 10_000 }).catch(() => {
|
||||
@@ -1700,17 +1671,16 @@ export class Task {
|
||||
const isAnthropic = this.api instanceof AnthropicHandler
|
||||
const isOpenRouterContextWindowError = checkIsOpenRouterContextWindowError(error) && isOpenRouter
|
||||
const isAnthropicContextWindowError = checkIsAnthropicContextWindowError(error) && isAnthropic
|
||||
const { modelId, providerId } = await this.getCurrentProviderInfo()
|
||||
const clineError = ErrorService.toClineError(error, modelId, providerId)
|
||||
|
||||
// Capture provider failure telemetry using clineError
|
||||
// TODO: Move into ErrorService
|
||||
const { statusCode, message, requestId } = extractErrorDetails(error)
|
||||
|
||||
// Capture provider failure telemetry
|
||||
telemetryService.captureProviderApiError({
|
||||
taskId: this.taskId,
|
||||
model: modelInfo.id,
|
||||
errorMessage: clineError.message,
|
||||
errorStatus: clineError._error?.status,
|
||||
requestId: clineError._error?.request_id,
|
||||
errorMessage: message,
|
||||
errorStatus: statusCode,
|
||||
requestId,
|
||||
})
|
||||
|
||||
if (isAnthropic && isAnthropicContextWindowError && !this.taskState.didAutomaticallyRetryFailedApiRequest) {
|
||||
@@ -1756,12 +1726,12 @@ export class Task {
|
||||
// If the conversation has more than 3 messages, we can truncate again. If not, then the conversation is bricked.
|
||||
// ToDo: Allow the user to change their input if this is the case.
|
||||
if (truncatedConversationHistory.length > 3) {
|
||||
clineError.message = "Context window exceeded. Click retry to truncate the conversation and try again."
|
||||
error = new Error("Context window exceeded. Click retry to truncate the conversation and try again.")
|
||||
this.taskState.didAutomaticallyRetryFailedApiRequest = false
|
||||
}
|
||||
}
|
||||
|
||||
const streamingFailedMessage = clineError.serialize()
|
||||
const errorMessage = formatErrorWithStatusCode(error)
|
||||
|
||||
// Update the 'api_req_started' message to reflect final failure before asking user to manually retry
|
||||
const lastApiReqStartedIndex = findLastIndex(
|
||||
@@ -1777,24 +1747,19 @@ export class Task {
|
||||
text: JSON.stringify({
|
||||
...currentApiReqInfo, // Spread the modified info (with retryStatus removed)
|
||||
// cancelReason: "retries_exhausted", // Indicate that automatic retries failed
|
||||
streamingFailedMessage,
|
||||
streamingFailedMessage: errorMessage,
|
||||
} satisfies ClineApiReqInfo),
|
||||
})
|
||||
// this.ask will trigger postStateToWebview, so this change should be picked up.
|
||||
}
|
||||
|
||||
const { response } = await this.ask("api_req_failed", streamingFailedMessage)
|
||||
const { response } = await this.ask("api_req_failed", errorMessage)
|
||||
|
||||
if (response !== "yesButtonClicked") {
|
||||
// this will never happen since if noButtonClicked, we will clear current task, aborting this instance
|
||||
throw new Error("API request failed")
|
||||
}
|
||||
|
||||
// Do not retry automatically again if currently unauthenticated
|
||||
if (clineError.isErrorType(ClineErrorType.Auth)) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.say("api_req_retried")
|
||||
}
|
||||
// delegate generator output from the recursive call
|
||||
@@ -1930,10 +1895,10 @@ export class Task {
|
||||
}
|
||||
|
||||
// Used to know what models were used in the task if user wants to export metadata for error reporting purposes
|
||||
const { modelId, providerId } = await this.getCurrentProviderInfo()
|
||||
if (providerId && modelId) {
|
||||
const currentProviderId = (await getGlobalState(this.getContext(), "apiProvider")) as string
|
||||
if (currentProviderId && this.api.getModel().id) {
|
||||
try {
|
||||
await this.modelContextTracker.recordModelUsage(providerId, modelId, this.chatSettings.mode)
|
||||
await this.modelContextTracker.recordModelUsage(currentProviderId, this.api.getModel().id, this.chatSettings.mode)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -2111,7 +2076,7 @@ export class Task {
|
||||
content: userContent,
|
||||
})
|
||||
|
||||
telemetryService.captureConversationTurnEvent(this.taskId, providerId, modelId, "user")
|
||||
telemetryService.captureConversationTurnEvent(this.taskId, currentProviderId, this.api.getModel().id, "user")
|
||||
|
||||
// since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message
|
||||
const lastApiReqIndex = findLastIndex(this.messageStateHandler.getClineMessages(), (m) => m.say === "api_req_started")
|
||||
@@ -2176,13 +2141,19 @@ export class Task {
|
||||
})
|
||||
await this.messageStateHandler.saveClineMessagesAndUpdateHistory()
|
||||
|
||||
telemetryService.captureConversationTurnEvent(this.taskId, providerId, this.api.getModel().id, "assistant", {
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
})
|
||||
telemetryService.captureConversationTurnEvent(
|
||||
this.taskId,
|
||||
currentProviderId,
|
||||
this.api.getModel().id,
|
||||
"assistant",
|
||||
{
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
},
|
||||
)
|
||||
|
||||
// signals to provider that it can retrieve the saved messages from disk, as abortTask can not be awaited on in nature
|
||||
this.taskState.didFinishAbortingStream = true
|
||||
@@ -2279,8 +2250,7 @@ export class Task {
|
||||
// abandoned happens when extension is no longer waiting for the cline instance to finish aborting (error is thrown here when any function in the for loop throws due to this.abort)
|
||||
if (!this.taskState.abandoned) {
|
||||
this.abortTask() // if the stream failed, there's various states the task could be in (i.e. could have streamed some tools the user may have executed), so we just resort to replicating a cancel task
|
||||
const clineError = ErrorService.toClineError(error, this.api.getModel().id)
|
||||
const errorMessage = clineError.serialize()
|
||||
const errorMessage = formatErrorWithStatusCode(error)
|
||||
|
||||
await abortStream("streaming_failed", errorMessage)
|
||||
await this.reinitExistingTaskFromId(this.taskId)
|
||||
@@ -2350,13 +2320,19 @@ export class Task {
|
||||
// need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response
|
||||
let didEndLoop = false
|
||||
if (assistantMessage.length > 0) {
|
||||
telemetryService.captureConversationTurnEvent(this.taskId, providerId, modelId, "assistant", {
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
})
|
||||
telemetryService.captureConversationTurnEvent(
|
||||
this.taskId,
|
||||
currentProviderId,
|
||||
this.api.getModel().id,
|
||||
"assistant",
|
||||
{
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
},
|
||||
)
|
||||
|
||||
await this.messageStateHandler.addToApiConversationHistory({
|
||||
role: "assistant",
|
||||
@@ -2402,8 +2378,6 @@ export class Task {
|
||||
},
|
||||
],
|
||||
})
|
||||
// Returns early to avoid retry since no assistant message was received
|
||||
return true
|
||||
}
|
||||
|
||||
return didEndLoop // will always be false for now
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
import { showSystemNotification } from "@/integrations/notifications"
|
||||
import { ClineApiReqCancelReason, ClineApiReqInfo } from "@/shared/ExtensionMessage"
|
||||
import { serializeError } from "serialize-error"
|
||||
import { MessageStateHandler } from "./message-state"
|
||||
import { calculateApiCostAnthropic } from "@/utils/cost"
|
||||
import { ApiHandler } from "@/api"
|
||||
|
||||
export function formatErrorWithStatusCode(error: any): string {
|
||||
const { statusCode, message } = extractErrorDetails(error)
|
||||
|
||||
// Only prepend the statusCode if it's not already part of the message
|
||||
return statusCode && !message.includes(statusCode.toString()) ? `${statusCode} - ${message}` : message
|
||||
}
|
||||
|
||||
export function extractErrorDetails(error: any): { message: string; statusCode?: number; requestId?: string } {
|
||||
const statusCode = error.status || error.statusCode || (error.response && error.response?.status)
|
||||
const message = error.message ?? JSON.stringify(serializeError(error), null, 2)
|
||||
const requestId = error.request_id || error.response?.request_id || undefined
|
||||
|
||||
return { message, statusCode, requestId }
|
||||
}
|
||||
|
||||
export const showNotificationForApprovalIfAutoApprovalEnabled = (
|
||||
message: string,
|
||||
autoApprovalSettingsEnabled: boolean,
|
||||
|
||||
@@ -264,11 +264,13 @@ export abstract class WebviewProvider {
|
||||
} catch (error) {
|
||||
// Only show the error message if not in development mode.
|
||||
if (!process.env.IS_DEV) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message:
|
||||
"Cline: Local webview dev server is not running, HMR will not work. Please run 'npm run dev:webview' before launching the extension to enable HMR. Using bundled assets.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message:
|
||||
"Cline: Local webview dev server is not running, HMR will not work. Please run 'npm run dev:webview' before launching the extension to enable HMR. Using bundled assets.",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return this.getHtmlContent()
|
||||
|
||||
@@ -99,10 +99,12 @@ export function registerTaskCommands(context: vscode.ExtensionContext, controlle
|
||||
await controller.postStateToWebview()
|
||||
|
||||
const message = `Created ${tasksCount} test tasks`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
},
|
||||
)
|
||||
}),
|
||||
|
||||
+32
-35
@@ -27,7 +27,6 @@ import {
|
||||
migrateCustomInstructionsToGlobalRules,
|
||||
migrateModeFromWorkspaceStorageToControllerState,
|
||||
migrateWelcomeViewCompleted,
|
||||
migrateLegacyApiConfigurationToModeSpecific,
|
||||
} from "./core/storage/state-migrations"
|
||||
|
||||
import { sendFocusChatInputEvent } from "./core/controller/ui/subscribeToFocusChatInput"
|
||||
@@ -76,9 +75,6 @@ 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)
|
||||
|
||||
@@ -110,10 +106,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const message = `Cline has been updated to v${currentVersion}`
|
||||
await vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
// Record that we've shown the popup for this version.
|
||||
await context.globalState.update("clineLastPopupNotificationVersion", currentVersion)
|
||||
}
|
||||
@@ -415,10 +413,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Ensure clipboard is restored even if an error occurs
|
||||
await writeTextToClipboard(tempCopyBuffer)
|
||||
console.error("Error getting terminal contents:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to get terminal contents",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to get terminal contents",
|
||||
}),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -554,10 +554,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
const selectedText = editor.document.getText(range)
|
||||
if (!selectedText.trim()) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to explain.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to explain.",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
const filePath = editor.document.uri.fsPath
|
||||
@@ -579,10 +581,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
const selectedText = editor.document.getText(range)
|
||||
if (!selectedText.trim()) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to improve.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to improve.",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
const filePath = editor.document.uri.fsPath
|
||||
@@ -647,10 +651,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
sendFocusChatInputEvent(clientId)
|
||||
} else {
|
||||
console.error("FocusChatInput: Could not find or activate a Cline webview to focus.")
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Could not activate Cline view. Please try opening it manually from the Activity Bar.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Could not activate Cline view. Please try opening it manually from the Activity Bar.",
|
||||
}),
|
||||
)
|
||||
}
|
||||
telemetryService.captureButtonClick("command_focusChatInput", activeWebviewProvider?.controller.task?.taskId)
|
||||
}),
|
||||
@@ -685,18 +691,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
)
|
||||
|
||||
context.subscriptions.push(
|
||||
context.secrets.onDidChange(async (event) => {
|
||||
context.secrets.onDidChange((event) => {
|
||||
if (event.key === "clineAccountId") {
|
||||
// Check if the secret was removed (logout) or added/updated (login)
|
||||
const secretValue = await context.secrets.get("clineAccountId")
|
||||
const authService = AuthService.getInstance(context)
|
||||
if (secretValue) {
|
||||
// Secret was added or updated - restore auth info (login from another window)
|
||||
authService?.restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
} else {
|
||||
// Secret was removed - handle logout for all windows
|
||||
authService?.handleDeauth()
|
||||
}
|
||||
AuthService.getInstance(context)?.restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { DiffViewProvider } from "@/integrations/editor/DiffViewProvider"
|
||||
|
||||
export class ExternalDiffViewProvider extends DiffViewProvider {
|
||||
private activeDiffEditorId: string | undefined
|
||||
|
||||
override async openDiffEditor(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
return
|
||||
}
|
||||
const response = await getHostBridgeProvider().diffClient.openDiff({
|
||||
path: this.absolutePath,
|
||||
content: this.originalContent ?? "",
|
||||
})
|
||||
this.activeDiffEditorId = response.diffId
|
||||
}
|
||||
|
||||
override async replaceText(
|
||||
content: string,
|
||||
rangeToReplace: { startLine: number; endLine: number },
|
||||
_currentLine: number | undefined,
|
||||
): Promise<void> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return
|
||||
}
|
||||
await getHostBridgeProvider().diffClient.replaceText({
|
||||
diffId: this.activeDiffEditorId,
|
||||
content: content,
|
||||
startLine: rangeToReplace.startLine,
|
||||
endLine: rangeToReplace.endLine,
|
||||
})
|
||||
}
|
||||
|
||||
protected override async truncateDocument(lineNumber: number): Promise<void> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return
|
||||
}
|
||||
await getHostBridgeProvider().diffClient.truncateDocument({
|
||||
diffId: this.activeDiffEditorId,
|
||||
endLine: lineNumber,
|
||||
})
|
||||
}
|
||||
|
||||
protected async saveDocument(): Promise<void> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return
|
||||
}
|
||||
await getHostBridgeProvider().diffClient.saveDocument({ diffId: this.activeDiffEditorId })
|
||||
}
|
||||
|
||||
protected override async scrollEditorToLine(line: number): Promise<void> {
|
||||
console.log(`Called ExternalDiffViewProvider.scrollEditorToLine(${line}) stub`)
|
||||
}
|
||||
|
||||
override async scrollAnimation(startLine: number, endLine: number): Promise<void> {
|
||||
console.log(`Called ExternalDiffViewProvider.scrollAnimation(${startLine}, ${endLine}) stub`)
|
||||
}
|
||||
|
||||
protected override async getDocumentText(): Promise<string | undefined> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return undefined
|
||||
}
|
||||
return (await getHostBridgeProvider().diffClient.getDocumentText({ diffId: this.activeDiffEditorId })).content
|
||||
}
|
||||
|
||||
protected override async getNewDiagnosticProblems(): Promise<string> {
|
||||
console.log(`Called ExternalDiffViewProvider.getNewDiagnosticProblems() stub`)
|
||||
return ""
|
||||
}
|
||||
|
||||
protected override async closeDiffView(): Promise<void> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return
|
||||
}
|
||||
await getHostBridgeProvider().diffClient.closeDiff({ diffId: this.activeDiffEditorId })
|
||||
this.activeDiffEditorId = undefined
|
||||
}
|
||||
|
||||
protected override async resetDiffView(): Promise<void> {
|
||||
this.activeDiffEditorId = undefined
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
import { arePathsEqual } from "@/utils/path"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { DecorationController } from "@/hosts/vscode/DecorationController"
|
||||
import { DecorationController } from "@integrations/editor/DecorationController"
|
||||
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "@integrations/editor/DiffViewProvider"
|
||||
import { diagnosticsToProblemsString, getNewDiagnostics } from "@/integrations/diagnostics"
|
||||
|
||||
export class VscodeDiffViewProvider extends DiffViewProvider {
|
||||
private activeDiffEditor?: vscode.TextEditor
|
||||
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
|
||||
|
||||
private fadedOverlayController?: DecorationController
|
||||
private activeLineController?: DecorationController
|
||||
|
||||
override async openDiffEditor(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
throw new Error("No file path set")
|
||||
@@ -88,110 +81,20 @@ export class VscodeDiffViewProvider extends DiffViewProvider {
|
||||
override async replaceText(
|
||||
content: string,
|
||||
rangeToReplace: { startLine: number; endLine: number },
|
||||
currentLine: number | undefined,
|
||||
currentLine: number,
|
||||
): Promise<void> {
|
||||
if (!this.activeDiffEditor || !this.activeDiffEditor.document) {
|
||||
const document = this.activeDiffEditor?.document
|
||||
if (!document) {
|
||||
throw new Error("User closed text editor, unable to edit file...")
|
||||
}
|
||||
// Place cursor at the beginning of the diff editor to keep it out of the way of the stream animation
|
||||
const beginningOfDocument = new vscode.Position(0, 0)
|
||||
this.activeDiffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
|
||||
|
||||
// Replace the text in the diff editor document.
|
||||
const document = this.activeDiffEditor?.document
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
const range = new vscode.Range(rangeToReplace.startLine, 0, rangeToReplace.endLine, 0)
|
||||
edit.replace(document.uri, range, content)
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
|
||||
if (currentLine !== undefined) {
|
||||
// Update decorations for the entire changed section
|
||||
this.activeLineController?.setActiveLine(currentLine)
|
||||
this.fadedOverlayController?.updateOverlayAfterLine(currentLine, document.lineCount)
|
||||
}
|
||||
}
|
||||
|
||||
override async scrollEditorToLine(line: number): Promise<void> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const scrollLine = line + 4
|
||||
this.activeDiffEditor.revealRange(new vscode.Range(scrollLine, 0, scrollLine, 0), vscode.TextEditorRevealType.InCenter)
|
||||
}
|
||||
|
||||
override async scrollAnimation(startLine: number, endLine: number): Promise<void> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const totalLines = endLine - startLine
|
||||
const numSteps = 10 // Adjust this number to control animation speed
|
||||
const stepSize = Math.max(1, Math.floor(totalLines / numSteps))
|
||||
|
||||
// Create and await the smooth scrolling animation
|
||||
for (let line = startLine; line <= endLine; line += stepSize) {
|
||||
this.activeDiffEditor.revealRange(new vscode.Range(line, 0, line, 0), vscode.TextEditorRevealType.InCenter)
|
||||
await new Promise((resolve) => setTimeout(resolve, 16)) // ~60fps
|
||||
}
|
||||
}
|
||||
|
||||
override async truncateDocument(lineNumber: number): Promise<void> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const document = this.activeDiffEditor.document
|
||||
if (lineNumber < document.lineCount) {
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
edit.delete(document.uri, new vscode.Range(lineNumber, 0, document.lineCount, 0))
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
}
|
||||
// Clear all decorations at the end (before applying final edit)
|
||||
this.fadedOverlayController?.clear()
|
||||
this.activeLineController?.clear()
|
||||
}
|
||||
|
||||
protected override async getDocumentText(): Promise<string | undefined> {
|
||||
if (!this.activeDiffEditor || !this.activeDiffEditor.document) {
|
||||
return undefined
|
||||
}
|
||||
return this.activeDiffEditor.document.getText()
|
||||
}
|
||||
|
||||
protected override async getNewDiagnosticProblems(): Promise<string> {
|
||||
// Get the diagnostics after changing the document.
|
||||
const postDiagnostics = vscode.languages.getDiagnostics()
|
||||
const newProblems = getNewDiagnostics(this.preDiagnostics, postDiagnostics)
|
||||
// Only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
|
||||
// will be empty string if no errors
|
||||
const problems = await diagnosticsToProblemsString(newProblems, [vscode.DiagnosticSeverity.Error])
|
||||
return problems
|
||||
}
|
||||
|
||||
protected override async saveDocument(): Promise<void> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
if (this.activeDiffEditor.document.isDirty) {
|
||||
await this.activeDiffEditor.document.save()
|
||||
}
|
||||
}
|
||||
|
||||
protected async closeDiffView(): Promise<void> {
|
||||
// Close all the cline diff views.
|
||||
const tabs = vscode.window.tabGroups.all
|
||||
.flatMap((tg) => tg.tabs)
|
||||
.filter((tab) => tab.input instanceof vscode.TabInputTextDiff && tab.input?.original?.scheme === DIFF_VIEW_URI_SCHEME)
|
||||
for (const tab of tabs) {
|
||||
// trying to close dirty views results in save popup
|
||||
if (!tab.isDirty) {
|
||||
await vscode.window.tabGroups.close(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async resetDiffView(): Promise<void> {
|
||||
this.activeDiffEditor = undefined
|
||||
this.fadedOverlayController = undefined
|
||||
this.activeLineController = undefined
|
||||
this.preDiagnostics = []
|
||||
// Update decorations for the entire changed section
|
||||
this.activeLineController?.setActiveLine(currentLine)
|
||||
this.fadedOverlayController?.updateOverlayAfterLine(currentLine, document.lineCount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { CloseDiffRequest, CloseDiffResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function closeDiff(_request: CloseDiffRequest): Promise<CloseDiffResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { GetDocumentTextRequest, GetDocumentTextResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function getDocumentText(_request: GetDocumentTextRequest): Promise<GetDocumentTextResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenDiffRequest, OpenDiffResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function openDiff(_request: OpenDiffRequest): Promise<OpenDiffResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
throw new Error("diffService.openDiff is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReplaceTextRequest, ReplaceTextResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function replaceText(_request: ReplaceTextRequest): Promise<ReplaceTextResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
throw new Error("diffService.replaceText is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { SaveDocumentRequest, SaveDocumentResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function saveDocument(_request: SaveDocumentRequest): Promise<SaveDocumentResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { TruncateDocumentRequest, TruncateDocumentResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function truncateDocument(_request: TruncateDocumentRequest): Promise<TruncateDocumentResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { SelectedResponse, ShowMessageRequest, ShowMessageType } from "@/shared/
|
||||
|
||||
const DEFAULT_OPTIONS = { modal: false, items: [] } as const
|
||||
|
||||
export async function showMessage(request: ShowMessageRequest): Promise<SelectedResponse> {
|
||||
export async function showMessage(request: ShowMessageRequest): Promise<SelectedResponse | undefined> {
|
||||
const { message, type, options } = request
|
||||
const { modal, detail, items } = { ...DEFAULT_OPTIONS, ...options }
|
||||
const option = { modal, detail }
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import * as vscode from "vscode"
|
||||
|
||||
interface DebugSession {
|
||||
id: string
|
||||
name: string
|
||||
output: string[]
|
||||
lastRetrievedIndex: number
|
||||
}
|
||||
|
||||
export class DebugConsoleManager {
|
||||
private sessions: Map<string, DebugSession> = new Map()
|
||||
private disposables: vscode.Disposable[] = []
|
||||
|
||||
constructor() {
|
||||
// Listen for debug session start events
|
||||
this.disposables.push(
|
||||
vscode.debug.onDidStartDebugSession((session) => {
|
||||
this.sessions.set(session.id, {
|
||||
id: session.id,
|
||||
name: session.name,
|
||||
output: [],
|
||||
lastRetrievedIndex: -1,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
// Listen for debug session end events
|
||||
this.disposables.push(
|
||||
vscode.debug.onDidTerminateDebugSession((session) => {
|
||||
this.sessions.delete(session.id)
|
||||
}),
|
||||
)
|
||||
|
||||
// Listen for debug console output
|
||||
this.disposables.push(
|
||||
vscode.debug.onDidReceiveDebugSessionCustomEvent((e: vscode.DebugSessionCustomEvent) => {
|
||||
if (e.event === "output" && e.body?.output) {
|
||||
const session = this.sessions.get(e.session.id)
|
||||
if (session) {
|
||||
session.output.push(e.body.output)
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active debug sessions
|
||||
*/
|
||||
getActiveSessions(): { id: string; name: string }[] {
|
||||
return Array.from(this.sessions.values()).map(({ id, name }) => ({ id, name }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any new output since the last retrieval for a specific debug session
|
||||
*/
|
||||
getUnretrievedOutput(sessionId: string): string | undefined {
|
||||
const session = this.sessions.get(sessionId)
|
||||
if (!session) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const newOutput = session.output.slice(session.lastRetrievedIndex + 1).join("")
|
||||
session.lastRetrievedIndex = session.output.length - 1
|
||||
return newOutput || undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
dispose() {
|
||||
this.disposables.forEach((d) => d.dispose())
|
||||
this.sessions.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
import * as vscode from "vscode"
|
||||
import deepEqual from "fast-deep-equal"
|
||||
|
||||
type FileDiagnostics = [vscode.Uri, vscode.Diagnostic[]][]
|
||||
|
||||
|
||||
About Diagnostics:
|
||||
The Problems tab shows diagnostics that have been reported for your project. These diagnostics are categorized into:
|
||||
Errors: Critical issues that usually prevent your code from compiling or running correctly.
|
||||
Warnings: Potential problems in the code that may not prevent it from running but could cause issues (e.g., bad practices, unused variables).
|
||||
Information: Non-critical suggestions or tips (e.g., formatting issues or notes from linters).
|
||||
The Problems tab displays diagnostics from various sources:
|
||||
1. Language Servers:
|
||||
- TypeScript: Type errors, missing imports, syntax issues
|
||||
- Python: Syntax errors, invalid type hints, undefined variables
|
||||
- JavaScript/Node.js: Parsing and execution errors
|
||||
2. Linters:
|
||||
- ESLint: Code style, best practices, potential bugs
|
||||
- Pylint: Unused imports, naming conventions
|
||||
- TSLint: Style and correctness issues in TypeScript
|
||||
3. Build Tools:
|
||||
- Webpack: Module resolution failures, build errors
|
||||
- Gulp: Build errors during task execution
|
||||
4. Custom Validators:
|
||||
- Extensions can generate custom diagnostics for specific languages or tools
|
||||
Each problem typically indicates its source (e.g., language server, linter, build tool).
|
||||
Diagnostics update in real-time as you edit code, helping identify issues quickly. For example, if you introduce a syntax error in a TypeScript file, the Problems tab will immediately display the new error.
|
||||
|
||||
Notes on diagnostics:
|
||||
- linter diagnostics are only captured for open editors
|
||||
- this works great for us since when cline edits/creates files its through vscode's textedit api's and we get those diagnostics for free
|
||||
- some tools might require you to save the file or manually refresh to clear the problem from the list.
|
||||
|
||||
System Prompt
|
||||
- You will automatically receive workspace error diagnostics in environment_details. Be mindful that this may include issues beyond the scope of your task or the user's request. Only address errors relevant to your work, and avoid fixing pre-existing or unrelated issues unless the user specifically instructs you to do so.
|
||||
- If you are unable to resolve errors provided in environment_details after two attempts, consider using ask_followup_question to ask the user for additional information, such as the latest documentation related to a problematic framework, to help you make progress on the task. If the error remains unresolved after this step, proceed with your task while disregarding the error.
|
||||
|
||||
class DiagnosticsMonitor {
|
||||
private diagnosticsChangeEmitter: vscode.EventEmitter<void> = new vscode.EventEmitter<void>()
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private lastDiagnostics: FileDiagnostics = []
|
||||
|
||||
constructor() {
|
||||
this.disposables.push(
|
||||
vscode.languages.onDidChangeDiagnostics(() => {
|
||||
this.diagnosticsChangeEmitter.fire()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
public async getCurrentDiagnostics(shouldWaitForChanges: boolean): Promise<FileDiagnostics> {
|
||||
const currentDiagnostics = this.getDiagnostics()
|
||||
if (!shouldWaitForChanges) {
|
||||
this.lastDiagnostics = currentDiagnostics
|
||||
return currentDiagnostics
|
||||
}
|
||||
|
||||
if (!deepEqual(this.lastDiagnostics, currentDiagnostics)) {
|
||||
this.lastDiagnostics = currentDiagnostics
|
||||
return currentDiagnostics
|
||||
}
|
||||
|
||||
let timeout = 300 // only way this happens is if there's no errors
|
||||
|
||||
// if diagnostics contain existing errors (since the check above didn't trigger) then it's likely cline just did something that should have fixed the error, so we'll give a longer grace period for diagnostics to catch up
|
||||
const hasErrors = currentDiagnostics.some(([_, diagnostics]) =>
|
||||
diagnostics.some((d) => d.severity === vscode.DiagnosticSeverity.Error)
|
||||
)
|
||||
if (hasErrors) {
|
||||
console.log("Existing errors detected, extending timeout", currentDiagnostics)
|
||||
timeout = 10_000
|
||||
}
|
||||
|
||||
return this.waitForUpdatedDiagnostics(timeout)
|
||||
}
|
||||
|
||||
private async waitForUpdatedDiagnostics(timeout: number): Promise<FileDiagnostics> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
cleanup()
|
||||
const finalDiagnostics = this.getDiagnostics()
|
||||
this.lastDiagnostics = finalDiagnostics
|
||||
resolve(finalDiagnostics)
|
||||
}, timeout)
|
||||
|
||||
const disposable = this.diagnosticsChangeEmitter.event(() => {
|
||||
const updatedDiagnostics = this.getDiagnostics() // I thought this would only trigger when diagnostics changed, but that's not the case.
|
||||
if (deepEqual(this.lastDiagnostics, updatedDiagnostics)) {
|
||||
// diagnostics have not changed, ignoring...
|
||||
return
|
||||
}
|
||||
cleanup()
|
||||
this.lastDiagnostics = updatedDiagnostics
|
||||
resolve(updatedDiagnostics)
|
||||
})
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(timer)
|
||||
disposable.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private getDiagnostics(): FileDiagnostics {
|
||||
const allDiagnostics = vscode.languages.getDiagnostics()
|
||||
return allDiagnostics
|
||||
.filter(([_, diagnostics]) => diagnostics.some((d) => d.severity === vscode.DiagnosticSeverity.Error))
|
||||
.map(([uri, diagnostics]) => [
|
||||
uri,
|
||||
diagnostics.filter((d) => d.severity === vscode.DiagnosticSeverity.Error),
|
||||
])
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposables.forEach((d) => d.dispose())
|
||||
this.disposables = []
|
||||
this.diagnosticsChangeEmitter.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
export default DiagnosticsMonitor
|
||||
*/
|
||||
@@ -4,10 +4,13 @@ import * as fs from "fs/promises"
|
||||
import { createDirectoriesForFile } from "@utils/fs"
|
||||
import { arePathsEqual, getCwd } from "@utils/path"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { DecorationController } from "./DecorationController"
|
||||
import * as diff from "diff"
|
||||
import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
|
||||
import { detectEncoding } from "../misc/extract-text"
|
||||
import * as iconv from "iconv-lite"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowTextDocumentRequest, ShowTextDocumentOptions } from "@/shared/proto/host/window"
|
||||
|
||||
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
|
||||
|
||||
@@ -23,6 +26,11 @@ export abstract class DiffViewProvider {
|
||||
private streamedLines: string[] = []
|
||||
private newContent?: string
|
||||
|
||||
protected activeDiffEditor?: vscode.TextEditor
|
||||
protected fadedOverlayController?: DecorationController
|
||||
protected activeLineController?: DecorationController
|
||||
protected preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
|
||||
|
||||
constructor() {}
|
||||
|
||||
public async open(relPath: string): Promise<void> {
|
||||
@@ -54,13 +62,17 @@ export abstract class DiffViewProvider {
|
||||
await fs.writeFile(this.absolutePath, "")
|
||||
}
|
||||
await this.openDiffEditor()
|
||||
await this.scrollEditorToLine(0)
|
||||
this.scrollEditorToLine(0) // will this crash for new files?
|
||||
this.streamedLines = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a diff editor or viewer for the current file.
|
||||
*
|
||||
* This abstract method must be implemented by subclasses to create and display
|
||||
* a diff editor or viewer that shows the difference between the original and
|
||||
* modified content.
|
||||
*
|
||||
* Called automatically by the `open` method after ensuring the file exists and
|
||||
* creating any necessary directories.
|
||||
*
|
||||
@@ -68,82 +80,13 @@ export abstract class DiffViewProvider {
|
||||
*/
|
||||
protected abstract openDiffEditor(): Promise<void>
|
||||
|
||||
/**
|
||||
* Scrolls the diff editor to reveal a specific line.
|
||||
*
|
||||
* It's used during streaming updates to keep the user's view focused on the changing content.
|
||||
*
|
||||
* @param line The 0-based line number to scroll to
|
||||
*/
|
||||
protected abstract scrollEditorToLine(line: number): Promise<void>
|
||||
|
||||
/**
|
||||
* Creates a smooth scrolling animation between two lines in the diff editor.
|
||||
*
|
||||
* It's typically used when updates contain many lines, to help the user visually track the flow
|
||||
* of significant changes in the document.
|
||||
*
|
||||
* @param startLine The 0-based line number to begin the animation from
|
||||
* @param endLine The 0-based line number to animate to
|
||||
*/
|
||||
protected abstract scrollAnimation(startLine: number, endLine: number): Promise<void>
|
||||
|
||||
/**
|
||||
* Removes content from the specified line to the end of the document.
|
||||
* Called after the final update is received.
|
||||
*/
|
||||
protected abstract truncateDocument(lineNumber: number): Promise<void>
|
||||
|
||||
/**
|
||||
* Get the contents of the diff editor document.
|
||||
*
|
||||
* Returns undefined if the diff editor was closed.
|
||||
*/
|
||||
protected abstract getDocumentText(): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Get any new diagnostic problems that appeared after applying the diff.
|
||||
*
|
||||
* Getting diagnostics before and after the file edit is a better approach than
|
||||
* automatically tracking problems in real-time. This method ensures we only
|
||||
* report new problems that are a direct result of this specific edit.
|
||||
* Since these are new problems resulting from Cline's edit, we know they're
|
||||
* directly related to the work he's doing. This eliminates the risk of Cline
|
||||
* going off-task or getting distracted by unrelated issues, which was a problem
|
||||
* with the previous auto-debug approach. Some users' machines may be slow to
|
||||
* update diagnostics, so this approach provides a good balance between automation
|
||||
* and avoiding potential issues where Cline might get stuck in loops due to
|
||||
* outdated problem information. If no new problems show up by the time the user
|
||||
* accepts the changes, they can always debug later using the '@problems' mention.
|
||||
* This way, Cline only becomes aware of new problems resulting from his edits
|
||||
* and can address them accordingly. If problems don't change immediately after
|
||||
* applying a fix, Cline won't be notified, which is generally fine since the
|
||||
* initial fix is usually correct and it may just take time for linters to catch up.
|
||||
*/
|
||||
protected abstract getNewDiagnosticProblems(): Promise<string>
|
||||
|
||||
/**
|
||||
* Save the contents of the diff editor UI to the file.
|
||||
*/
|
||||
protected abstract saveDocument(): Promise<void>
|
||||
|
||||
/**
|
||||
* Closes the diff editor tab or window.
|
||||
*/
|
||||
protected abstract closeDiffView(): Promise<void>
|
||||
|
||||
/**
|
||||
* Cleans up the diff view resources and resets internal state.
|
||||
*/
|
||||
protected abstract resetDiffView(): Promise<void>
|
||||
|
||||
async update(
|
||||
accumulatedContent: string,
|
||||
isFinal: boolean,
|
||||
changeLocation?: { startLine: number; endLine: number; startChar: number; endChar: number },
|
||||
) {
|
||||
if (!this.isEditing) {
|
||||
throw new Error("Not editing any file")
|
||||
if (!this.relPath || !this.activeLineController || !this.fadedOverlayController) {
|
||||
throw new Error("Required values not set")
|
||||
}
|
||||
|
||||
// --- Fix to prevent duplicate BOM ---
|
||||
@@ -161,6 +104,16 @@ export abstract class DiffViewProvider {
|
||||
}
|
||||
const diffLines = accumulatedLines.slice(this.streamedLines.length)
|
||||
|
||||
const diffEditor = this.activeDiffEditor
|
||||
const document = diffEditor?.document
|
||||
if (!diffEditor || !document) {
|
||||
throw new Error("User closed text editor, unable to edit file...")
|
||||
}
|
||||
|
||||
// Place cursor at the beginning of the diff editor to keep it out of the way of the stream animation
|
||||
const beginningOfDocument = new vscode.Position(0, 0)
|
||||
diffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
|
||||
|
||||
// Instead of animating each line, we'll update in larger chunks
|
||||
const currentLine = this.streamedLines.length + diffLines.length - 1
|
||||
if (currentLine >= 0) {
|
||||
@@ -176,19 +129,30 @@ export abstract class DiffViewProvider {
|
||||
if (changeLocation) {
|
||||
// We have the actual location of the change, scroll to it
|
||||
const targetLine = changeLocation.startLine
|
||||
await this.scrollEditorToLine(targetLine)
|
||||
this.scrollEditorToLine(targetLine)
|
||||
} else {
|
||||
// Fallback to the old logic for non-replacement updates
|
||||
if (diffLines.length <= 5) {
|
||||
// For small changes, just jump directly to the line
|
||||
await this.scrollEditorToLine(currentLine)
|
||||
this.scrollEditorToLine(currentLine)
|
||||
} else {
|
||||
// For larger changes, create a quick scrolling animation
|
||||
const startLine = this.streamedLines.length
|
||||
const endLine = currentLine
|
||||
await this.scrollAnimation(startLine, endLine)
|
||||
const totalLines = endLine - startLine
|
||||
const numSteps = 10 // Adjust this number to control animation speed
|
||||
const stepSize = Math.max(1, Math.floor(totalLines / numSteps))
|
||||
|
||||
// Create and await the smooth scrolling animation
|
||||
for (let line = startLine; line <= endLine; line += stepSize) {
|
||||
this.activeDiffEditor?.revealRange(
|
||||
new vscode.Range(line, 0, line, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
await new Promise((resolve) => setTimeout(resolve, 16)) // ~60fps
|
||||
}
|
||||
// Ensure we end at the final line
|
||||
await this.scrollEditorToLine(currentLine)
|
||||
this.scrollEditorToLine(currentLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,8 +161,11 @@ export abstract class DiffViewProvider {
|
||||
this.streamedLines = accumulatedLines
|
||||
if (isFinal) {
|
||||
// Handle any remaining lines if the new content is shorter than the original
|
||||
await this.truncateDocument(this.streamedLines.length)
|
||||
|
||||
if (this.streamedLines.length < document.lineCount) {
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
edit.delete(document.uri, new vscode.Range(this.streamedLines.length, 0, document.lineCount, 0))
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
}
|
||||
// Add empty last line if original content had one
|
||||
const hasEmptyLastLine = this.originalContent?.endsWith("\n")
|
||||
if (hasEmptyLastLine) {
|
||||
@@ -207,6 +174,9 @@ export abstract class DiffViewProvider {
|
||||
accumulatedContent += "\n"
|
||||
}
|
||||
}
|
||||
// Clear all decorations at the end (before applying final edit)
|
||||
this.fadedOverlayController.clear()
|
||||
this.activeLineController.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +195,7 @@ export abstract class DiffViewProvider {
|
||||
abstract replaceText(
|
||||
content: string,
|
||||
rangeToReplace: { startLine: number; endLine: number },
|
||||
currentLine: number | undefined,
|
||||
currentLine: number,
|
||||
): Promise<void>
|
||||
|
||||
async saveChanges(): Promise<{
|
||||
@@ -234,10 +204,7 @@ export abstract class DiffViewProvider {
|
||||
autoFormattingEdits: string | undefined
|
||||
finalContent: string | undefined
|
||||
}> {
|
||||
// get the contents before save operation which may do auto-formatting
|
||||
const preSaveContent = await this.getDocumentText()
|
||||
|
||||
if (!this.relPath || !this.absolutePath || !this.newContent || preSaveContent === undefined) {
|
||||
if (!this.relPath || !this.newContent || !this.activeDiffEditor) {
|
||||
return {
|
||||
newProblemsMessage: undefined,
|
||||
userEdits: undefined,
|
||||
@@ -245,21 +212,50 @@ export abstract class DiffViewProvider {
|
||||
finalContent: undefined,
|
||||
}
|
||||
}
|
||||
const updatedDocument = this.activeDiffEditor.document
|
||||
|
||||
// get the contents before save operation which may do auto-formatting
|
||||
const preSaveContent = updatedDocument.getText()
|
||||
|
||||
if (updatedDocument.isDirty) {
|
||||
await updatedDocument.save()
|
||||
}
|
||||
|
||||
await this.saveDocument()
|
||||
// get text after save in case there is any auto-formatting done by the editor
|
||||
const postSaveContent = (await this.getDocumentText()) || ""
|
||||
const postSaveContent = updatedDocument.getText()
|
||||
|
||||
await getHostBridgeProvider().windowClient.showTextDocument({
|
||||
path: this.absolutePath,
|
||||
options: {
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
},
|
||||
})
|
||||
await this.closeDiffView()
|
||||
await getHostBridgeProvider().windowClient.showTextDocument(
|
||||
ShowTextDocumentRequest.create({
|
||||
path: this.absolutePath,
|
||||
options: ShowTextDocumentOptions.create({
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await this.closeAllDiffViews()
|
||||
|
||||
const newProblems = await this.getNewDiagnosticProblems()
|
||||
/*
|
||||
Getting diagnostics before and after the file edit is a better approach than
|
||||
automatically tracking problems in real-time. This method ensures we only
|
||||
report new problems that are a direct result of this specific edit.
|
||||
Since these are new problems resulting from Cline's edit, we know they're
|
||||
directly related to the work he's doing. This eliminates the risk of Cline
|
||||
going off-task or getting distracted by unrelated issues, which was a problem
|
||||
with the previous auto-debug approach. Some users' machines may be slow to
|
||||
update diagnostics, so this approach provides a good balance between automation
|
||||
and avoiding potential issues where Cline might get stuck in loops due to
|
||||
outdated problem information. If no new problems show up by the time the user
|
||||
accepts the changes, they can always debug later using the '@problems' mention.
|
||||
This way, Cline only becomes aware of new problems resulting from his edits
|
||||
and can address them accordingly. If problems don't change immediately after
|
||||
applying a fix, Cline won't be notified, which is generally fine since the
|
||||
initial fix is usually correct and it may just take time for linters to catch up.
|
||||
*/
|
||||
const postDiagnostics = vscode.languages.getDiagnostics()
|
||||
const newProblems = await diagnosticsToProblemsString(getNewDiagnostics(this.preDiagnostics, postDiagnostics), [
|
||||
vscode.DiagnosticSeverity.Error, // only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
|
||||
]) // will be empty string if no errors
|
||||
const newProblemsMessage =
|
||||
newProblems.length > 0 ? `\n\nNew problems detected after saving the file:\n${newProblems}` : ""
|
||||
|
||||
@@ -299,14 +295,16 @@ export abstract class DiffViewProvider {
|
||||
}
|
||||
|
||||
async revertChanges(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
if (!this.absolutePath || !this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const fileExists = this.editType === "modify"
|
||||
|
||||
const updatedDocument = this.activeDiffEditor.document
|
||||
if (!fileExists) {
|
||||
await this.saveDocument()
|
||||
await this.closeDiffView()
|
||||
if (updatedDocument.isDirty) {
|
||||
await updatedDocument.save()
|
||||
}
|
||||
await this.closeAllDiffViews()
|
||||
await fs.unlink(this.absolutePath)
|
||||
// Remove only the directories we created, in reverse order
|
||||
for (let i = this.createdDirs.length - 1; i >= 0; i--) {
|
||||
@@ -316,41 +314,70 @@ export abstract class DiffViewProvider {
|
||||
console.log(`File ${this.absolutePath} has been deleted.`)
|
||||
} else {
|
||||
// revert document
|
||||
// Apply the edit and save, since contents shouldn't have changed this won't show in local history unless of
|
||||
// course the user made changes and saved during the edit.
|
||||
const contents = (await this.getDocumentText()) || ""
|
||||
const lineCount = (contents.match(/\n/g) || []).length + 1
|
||||
await this.replaceText(this.originalContent ?? "", { startLine: 0, endLine: lineCount }, undefined)
|
||||
|
||||
await this.saveDocument()
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
const fullRange = new vscode.Range(
|
||||
updatedDocument.positionAt(0),
|
||||
updatedDocument.positionAt(updatedDocument.getText().length),
|
||||
)
|
||||
edit.replace(updatedDocument.uri, fullRange, this.originalContent ?? "")
|
||||
// Apply the edit and save, since contents shouldn't have changed this won't show in local history unless of course the user made changes and saved during the edit
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
await updatedDocument.save()
|
||||
console.log(`File ${this.absolutePath} has been reverted to its original content.`)
|
||||
if (this.documentWasOpen) {
|
||||
await getHostBridgeProvider().windowClient.showTextDocument({
|
||||
path: this.absolutePath,
|
||||
options: {
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
},
|
||||
})
|
||||
await getHostBridgeProvider().windowClient.showTextDocument(
|
||||
ShowTextDocumentRequest.create({
|
||||
path: this.absolutePath,
|
||||
options: ShowTextDocumentOptions.create({
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
await this.closeDiffView()
|
||||
await this.closeAllDiffViews()
|
||||
}
|
||||
|
||||
// edit is done
|
||||
await this.reset()
|
||||
}
|
||||
|
||||
async scrollToFirstDiff() {
|
||||
if (!this.isEditing) {
|
||||
private async closeAllDiffViews() {
|
||||
const tabs = vscode.window.tabGroups.all
|
||||
.flatMap((tg) => tg.tabs)
|
||||
.filter((tab) => tab.input instanceof vscode.TabInputTextDiff && tab.input?.original?.scheme === DIFF_VIEW_URI_SCHEME)
|
||||
for (const tab of tabs) {
|
||||
// trying to close dirty views results in save popup
|
||||
if (!tab.isDirty) {
|
||||
await vscode.window.tabGroups.close(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private scrollEditorToLine(line: number) {
|
||||
if (this.activeDiffEditor) {
|
||||
const scrollLine = line + 4
|
||||
this.activeDiffEditor.revealRange(
|
||||
new vscode.Range(scrollLine, 0, scrollLine, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
scrollToFirstDiff() {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const currentContent = (await this.getDocumentText()) || ""
|
||||
const currentContent = this.activeDiffEditor.document.getText()
|
||||
const diffs = diff.diffLines(this.originalContent || "", currentContent)
|
||||
let lineCount = 0
|
||||
for (const part of diffs) {
|
||||
if (part.added || part.removed) {
|
||||
// Found the first diff, scroll to it
|
||||
this.scrollEditorToLine(lineCount)
|
||||
this.activeDiffEditor.revealRange(
|
||||
new vscode.Range(lineCount, 0, lineCount, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!part.removed) {
|
||||
@@ -366,8 +393,10 @@ export abstract class DiffViewProvider {
|
||||
this.originalContent = undefined
|
||||
this.createdDirs = []
|
||||
this.documentWasOpen = false
|
||||
this.activeDiffEditor = undefined
|
||||
this.fadedOverlayController = undefined
|
||||
this.activeLineController = undefined
|
||||
this.streamedLines = []
|
||||
|
||||
await this.resetDiffView()
|
||||
this.preDiagnostics = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +59,12 @@ export function extractCommitMessage(aiResponse: string): string {
|
||||
*/
|
||||
export async function copyCommitMessageToClipboard(message: string): Promise<void> {
|
||||
await writeTextToClipboard(message)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message copied to clipboard",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message copied to clipboard",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,16 +77,18 @@ export async function showCommitMessageOptions(message: string): Promise<void> {
|
||||
const editAction = "Edit Message"
|
||||
|
||||
const selectedAction = (
|
||||
await getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message generated",
|
||||
options: {
|
||||
modal: false,
|
||||
detail: message,
|
||||
items: [copyAction, applyAction, editAction],
|
||||
},
|
||||
})
|
||||
).selectedOption
|
||||
await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message generated",
|
||||
options: {
|
||||
modal: false,
|
||||
detail: message,
|
||||
items: [copyAction, applyAction, editAction],
|
||||
},
|
||||
}),
|
||||
)
|
||||
)?.selectedOption
|
||||
|
||||
// Handle user dismissing the dialog (selectedAction is undefined)
|
||||
if (!selectedAction) {
|
||||
@@ -116,22 +120,28 @@ async function applyCommitMessageToGitInput(message: string): Promise<void> {
|
||||
if (api && api.repositories.length > 0) {
|
||||
const repo = api.repositories[0]
|
||||
repo.inputBox.value = message
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message applied to Git input",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message applied to Git input",
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No Git repositories found",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No Git repositories found",
|
||||
}),
|
||||
)
|
||||
await copyCommitMessageToClipboard(message)
|
||||
}
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Git extension not found",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Git extension not found",
|
||||
}),
|
||||
)
|
||||
await copyCommitMessageToClipboard(message)
|
||||
}
|
||||
}
|
||||
@@ -151,8 +161,10 @@ async function editCommitMessage(message: string): Promise<void> {
|
||||
path: document.uri.fsPath,
|
||||
}),
|
||||
)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Edit the commit message and copy when ready",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Edit the commit message and copy when ready",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,10 +48,12 @@ export async function downloadTask(dateTs: number, conversationHistory: Anthropi
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to save markdown file: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to save markdown file: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@ import * as os from "os"
|
||||
import * as vscode from "vscode"
|
||||
import { arePathsEqual } from "@utils/path"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { ShowTextDocumentRequest, ShowTextDocumentOptions, ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { writeFile } from "@utils/fs"
|
||||
|
||||
export async function openImage(dataUri: string) {
|
||||
const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
|
||||
if (!matches) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid data URI format",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid data URI format",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
const [, format, base64Data] = matches
|
||||
@@ -22,10 +24,12 @@ export async function openImage(dataUri: string) {
|
||||
await writeFile(tempFilePath, new Uint8Array(imageBuffer))
|
||||
await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error opening image: ${error}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error opening image: ${error}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,18 +46,22 @@ export async function selectFiles(imagesAllowed: boolean): Promise<{ images: str
|
||||
const dimensions = sizeOf(uint8Array) // Get dimensions from Uint8Array
|
||||
if (dimensions.width! > 7500 || dimensions.height! > 7500) {
|
||||
console.warn(`Image dimensions exceed 7500px, skipping: ${filePath}`)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Image too large: ${path.basename(filePath)} was skipped (dimensions exceed 7500px).`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Image too large: ${path.basename(filePath)} was skipped (dimensions exceed 7500px).`,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading file or getting dimensions for ${filePath}:`, error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Could not read dimensions for ${path.basename(filePath)}, skipping.`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Could not read dimensions for ${path.basename(filePath)}, skipping.`,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -72,18 +76,22 @@ export async function selectFiles(imagesAllowed: boolean): Promise<{ images: str
|
||||
const stats = await fs.stat(filePath)
|
||||
if (stats.size > 20 * 1000 * 1024) {
|
||||
console.warn(`File too large, skipping: ${filePath}`)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `File too large: ${path.basename(filePath)} was skipped (size exceeds 20MB).`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `File too large: ${path.basename(filePath)} was skipped (size exceeds 20MB).`,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error checking file size for ${filePath}:`, error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Could not check file size for ${path.basename(filePath)}, skipping.`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Could not check file size for ${path.basename(filePath)}, skipping.`,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
return { type: "file", data: filePath }
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "../../shared/ClineAccount"
|
||||
import { serializeError } from "serialize-error"
|
||||
|
||||
export enum ClineErrorType {
|
||||
Auth = "auth",
|
||||
Network = "network",
|
||||
RateLimit = "rateLimit",
|
||||
Balance = "balance",
|
||||
}
|
||||
|
||||
interface ErrorDetails {
|
||||
/**
|
||||
* The HTTP status code of the error, if applicable.
|
||||
*/
|
||||
status?: number
|
||||
/**
|
||||
* The request ID associated with the error, if available.
|
||||
* This can be useful for debugging and support.
|
||||
*/
|
||||
request_id?: string
|
||||
/**
|
||||
* Specific error code provided by the API or service.
|
||||
*/
|
||||
code?: string
|
||||
/**
|
||||
* The model ID associated with the error, if applicable.
|
||||
* This is useful for identifying which model the error relates to.
|
||||
*/
|
||||
modelId?: string
|
||||
/**
|
||||
* The provider ID associated with the error, if applicable.
|
||||
* This is useful for identifying which provider the error relates to.
|
||||
*/
|
||||
providerId?: string
|
||||
/**
|
||||
* The error message associated with the error, if applicable.
|
||||
*/
|
||||
message?: string
|
||||
// Additional details that might be present in the error
|
||||
// This can include things like current balance, error messages, etc.
|
||||
details?: any
|
||||
}
|
||||
|
||||
const RATE_LIMIT_PATTERNS = [/status code 429/i, /rate limit/i, /too many requests/i, /quota exceeded/i, /resource exhausted/i]
|
||||
|
||||
export class ClineError extends Error {
|
||||
readonly title = "ClineError"
|
||||
readonly _error: ErrorDetails
|
||||
|
||||
// Error details per providers:
|
||||
// Cline: error?.error
|
||||
// Ollama: error?.cause
|
||||
// tbc
|
||||
constructor(
|
||||
raw: any,
|
||||
public readonly modelId?: string,
|
||||
public readonly providerId?: string,
|
||||
) {
|
||||
const error = serializeError(raw)
|
||||
|
||||
const message = error.message || String(error) || error?.cause?.means
|
||||
super(message)
|
||||
|
||||
// Extract status from multiple possible locations
|
||||
const status = error.status || error.statusCode || error.response?.status
|
||||
|
||||
// Construct the error details object to includes relevant information
|
||||
// And ensure it has a consistent structure
|
||||
this._error = {
|
||||
message: raw.message || message,
|
||||
status,
|
||||
request_id: error.request_id || error.response?.request_id,
|
||||
code: error.code || error?.cause?.code,
|
||||
modelId,
|
||||
providerId,
|
||||
details: error.details || error.error, // Additional details provided by the server
|
||||
...error,
|
||||
stack: undefined, // Avoid serializing stack trace to keep the error object clean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the error to a JSON string that allows for easy transmission and storage.
|
||||
* This is useful for logging or sending error details to a webviews.
|
||||
*/
|
||||
public serialize(): string {
|
||||
return JSON.stringify({
|
||||
message: this.message,
|
||||
status: this._error.status,
|
||||
request_id: this._error.request_id,
|
||||
code: this._error.code,
|
||||
modelId: this.modelId,
|
||||
providerId: this.providerId,
|
||||
details: this._error.details,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a stringified error into a ClineError instance.
|
||||
*/
|
||||
static parse(errorStr?: string, modelId?: string): ClineError | undefined {
|
||||
if (!errorStr || typeof errorStr !== "string") {
|
||||
return undefined
|
||||
}
|
||||
return ClineError.transform(errorStr, modelId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms any object into a ClineError instance.
|
||||
* Always returns a ClineError, even if the input is not a valid error object.
|
||||
*/
|
||||
static transform(error: any, modelId?: string, providerId?: string): ClineError {
|
||||
try {
|
||||
return new ClineError(JSON.parse(error), modelId, providerId)
|
||||
} catch {
|
||||
return new ClineError(error, modelId, providerId)
|
||||
}
|
||||
}
|
||||
|
||||
public isErrorType(type: ClineErrorType): boolean {
|
||||
return ClineError.getErrorType(this) === type
|
||||
}
|
||||
|
||||
/**
|
||||
* Is known error type based on the error code, status, and details.
|
||||
* This is useful for determining how to handle the error in the UI or logic.
|
||||
*/
|
||||
static getErrorType(err: ClineError): ClineErrorType | undefined {
|
||||
const { code, status, details } = err._error
|
||||
|
||||
// Check balance error first (most specific)
|
||||
if (code === "insufficient_credits" && typeof details?.current_balance === "number") {
|
||||
return ClineErrorType.Balance
|
||||
}
|
||||
|
||||
// Check auth errors
|
||||
if (code === "ERR_BAD_REQUEST" || status === 401) {
|
||||
return ClineErrorType.Auth
|
||||
}
|
||||
|
||||
// Check for auth message (only if message exists)
|
||||
const message = err.message
|
||||
if (message?.includes(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE)) {
|
||||
return ClineErrorType.Auth
|
||||
}
|
||||
|
||||
// Check rate limit patterns
|
||||
if (message) {
|
||||
const lowerMessage = message.toLowerCase()
|
||||
if (RATE_LIMIT_PATTERNS.some((pattern) => pattern.test(lowerMessage))) {
|
||||
return ClineErrorType.RateLimit
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as Sentry from "@sentry/browser"
|
||||
import * as vscode from "vscode"
|
||||
import { telemetryService } from "../posthog/telemetry/TelemetryService"
|
||||
import { telemetryService } from "@/services/posthog/telemetry/TelemetryService"
|
||||
import * as pkg from "../../../package.json"
|
||||
import { ClineError } from "./ClineError"
|
||||
|
||||
let telemetryLevel = vscode.workspace.getConfiguration("telemetry").get<string>("telemetryLevel", "all")
|
||||
let isTelemetryEnabled = ["all", "error"].includes(telemetryLevel)
|
||||
@@ -16,8 +15,6 @@ vscode.workspace.onDidChangeConfiguration(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const isDev = process.env.IS_DEV === "true"
|
||||
|
||||
export class ErrorService {
|
||||
private static serviceEnabled: boolean
|
||||
private static serviceLevel: string
|
||||
@@ -32,7 +29,7 @@ export class ErrorService {
|
||||
beforeSend(event) {
|
||||
// TelemetryService keeps track of whether the user has opted in to telemetry/error reporting
|
||||
const isUserManuallyOptedIn = telemetryService.isTelemetryEnabled()
|
||||
if (isUserManuallyOptedIn && ErrorService.isEnabled() && !isDev) {
|
||||
if (isUserManuallyOptedIn && ErrorService.isEnabled()) {
|
||||
return event
|
||||
}
|
||||
return null
|
||||
@@ -68,7 +65,7 @@ export class ErrorService {
|
||||
}
|
||||
}
|
||||
|
||||
static logException(error: Error | ClineError): void {
|
||||
static logException(error: Error): void {
|
||||
// Don't log if telemetry is off
|
||||
const isUserManuallyOptedIn = telemetryService.isTelemetryEnabled()
|
||||
if (!isUserManuallyOptedIn || !ErrorService.isEnabled()) {
|
||||
@@ -96,8 +93,4 @@ export class ErrorService {
|
||||
static isEnabled(): boolean {
|
||||
return ErrorService.serviceEnabled
|
||||
}
|
||||
|
||||
static toClineError(rawError: any, modelId?: string, providerId?: string): ClineError {
|
||||
return ClineError.transform(rawError, modelId, providerId)
|
||||
}
|
||||
}
|
||||
|
||||
+71
-90
@@ -35,10 +35,10 @@ import { secondsToMs } from "@utils/time"
|
||||
import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import { DEFAULT_REQUEST_TIMEOUT_MS } from "./constants"
|
||||
import { McpConnection, McpServerConfig, Transport } from "./types"
|
||||
import { McpConnection, McpServerConfig } from "./types"
|
||||
import { BaseConfigSchema, ServerConfigSchema, McpSettingsSchema } from "./schemas"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
export class McpHub {
|
||||
getMcpServersPath: () => Promise<string>
|
||||
private getSettingsDirectoryPath: () => Promise<string>
|
||||
@@ -109,20 +109,24 @@ export class McpHub {
|
||||
try {
|
||||
config = JSON.parse(content)
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid MCP settings format. Please ensure your settings follow the correct JSON format.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid MCP settings format. Please ensure your settings follow the correct JSON format.",
|
||||
}),
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Validate against schema
|
||||
const result = McpSettingsSchema.safeParse(config)
|
||||
if (!result.success) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid MCP settings schema.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid MCP settings schema.",
|
||||
}),
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -154,6 +158,12 @@ export class McpHub {
|
||||
if (settings) {
|
||||
try {
|
||||
await this.updateServerConnections(settings.mcpServers)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "MCP servers updated",
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Failed to process MCP settings change:", error)
|
||||
}
|
||||
@@ -192,23 +202,6 @@ export class McpHub {
|
||||
// Remove existing connection if it exists (should never happen, the connection should be deleted beforehand)
|
||||
this.connections = this.connections.filter((conn) => conn.server.name !== name)
|
||||
|
||||
if (config.disabled) {
|
||||
console.log(`[MCP Debug] Creating disabled connection object for server "${name}"`)
|
||||
// Create a connection object for disabled server so it appears in UI
|
||||
const disabledConnection: McpConnection = {
|
||||
server: {
|
||||
name,
|
||||
config: JSON.stringify(config),
|
||||
status: "disconnected",
|
||||
disabled: true,
|
||||
},
|
||||
client: null as unknown as Client,
|
||||
transport: null as unknown as Transport,
|
||||
}
|
||||
this.connections.push(disabledConnection)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Each MCP server requires its own transport connection and has unique capabilities, configurations, and error handling. Having separate clients also allows proper scoping of resources/tools and independent server management like reconnection.
|
||||
const client = new Client(
|
||||
@@ -420,10 +413,12 @@ export class McpHub {
|
||||
console.log(`[MCP Fallback Notification] ${name}:`, JSON.stringify(notification, null, 2))
|
||||
|
||||
// Show in VS Code for visibility
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `MCP ${name}: ${notification.method || "unknown"} - ${JSON.stringify(notification.params || {})}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `MCP ${name}: ${notification.method || "unknown"} - ${JSON.stringify(notification.params || {})}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
console.log(`[MCP Debug] Successfully set fallback notification handler for ${name}`)
|
||||
} catch (error) {
|
||||
@@ -458,11 +453,6 @@ export class McpHub {
|
||||
throw new Error(`No connection found for server: ${serverName}`)
|
||||
}
|
||||
|
||||
// Disabled servers don't have clients, so return empty tools list
|
||||
if (connection.server.disabled || !connection.client) {
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await connection.client.request({ method: "tools/list" }, ListToolsResultSchema, {
|
||||
timeout: DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
})
|
||||
@@ -488,16 +478,9 @@ export class McpHub {
|
||||
|
||||
private async fetchResourcesList(serverName: string): Promise<McpResource[]> {
|
||||
try {
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
|
||||
// Disabled servers don't have clients, so return empty resources list
|
||||
if (!connection || connection.server.disabled || !connection.client) {
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await connection.client.request({ method: "resources/list" }, ListResourcesResultSchema, {
|
||||
timeout: DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
})
|
||||
const response = await this.connections
|
||||
.find((conn) => conn.server.name === serverName)
|
||||
?.client.request({ method: "resources/list" }, ListResourcesResultSchema, { timeout: DEFAULT_REQUEST_TIMEOUT_MS })
|
||||
return response?.resources || []
|
||||
} catch (error) {
|
||||
// console.error(`Failed to fetch resources for ${serverName}:`, error)
|
||||
@@ -507,20 +490,11 @@ export class McpHub {
|
||||
|
||||
private async fetchResourceTemplatesList(serverName: string): Promise<McpResourceTemplate[]> {
|
||||
try {
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
|
||||
// Disabled servers don't have clients, so return empty resource templates list
|
||||
if (!connection || connection.server.disabled || !connection.client) {
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await connection.client.request(
|
||||
{ method: "resources/templates/list" },
|
||||
ListResourceTemplatesResultSchema,
|
||||
{
|
||||
const response = await this.connections
|
||||
.find((conn) => conn.server.name === serverName)
|
||||
?.client.request({ method: "resources/templates/list" }, ListResourceTemplatesResultSchema, {
|
||||
timeout: DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
return response?.resourceTemplates || []
|
||||
} catch (error) {
|
||||
@@ -533,13 +507,8 @@ export class McpHub {
|
||||
const connection = this.connections.find((conn) => conn.server.name === name)
|
||||
if (connection) {
|
||||
try {
|
||||
// Only close transport and client if they exist (disabled servers don't have them)
|
||||
if (connection.transport) {
|
||||
await connection.transport.close()
|
||||
}
|
||||
if (connection.client) {
|
||||
await connection.client.close()
|
||||
}
|
||||
await connection.transport.close()
|
||||
await connection.client.close()
|
||||
} catch (error) {
|
||||
console.error(`Failed to close transport for ${name}:`, error)
|
||||
}
|
||||
@@ -702,10 +671,12 @@ export class McpHub {
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
const config = connection?.server.config
|
||||
if (config) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `Restarting ${serverName} MCP server...`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `Restarting ${serverName} MCP server...`,
|
||||
}),
|
||||
)
|
||||
connection.server.status = "connecting"
|
||||
connection.server.error = ""
|
||||
await this.notifyWebviewOfServerChanges()
|
||||
@@ -714,16 +685,20 @@ export class McpHub {
|
||||
await this.deleteConnection(serverName)
|
||||
// Try to connect again using existing config
|
||||
await this.connectToServer(serverName, JSON.parse(config), "internal")
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `${serverName} MCP server connected`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `${serverName} MCP server connected`,
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`Failed to restart connection for ${serverName}:`, error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to connect to ${serverName} MCP server`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to connect to ${serverName} MCP server`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,10 +784,12 @@ export class McpHub {
|
||||
if (error instanceof Error) {
|
||||
console.error("Error details:", error.message, error.stack)
|
||||
}
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to update server state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to update server state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -969,10 +946,12 @@ export class McpHub {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update autoApprove settings:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to update autoApprove settings",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to update autoApprove settings",
|
||||
}),
|
||||
)
|
||||
throw error // Re-throw to ensure the error is properly handled
|
||||
}
|
||||
}
|
||||
@@ -1090,10 +1069,12 @@ export class McpHub {
|
||||
if (error instanceof Error) {
|
||||
console.error("Error details:", error.message, error.stack)
|
||||
}
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to update server timeout: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to update server timeout: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PostHog } from "posthog-node"
|
||||
import { posthogConfig } from "../../shared/services/config/posthog-config"
|
||||
import { posthogConfig } from "@/shared/services/config/posthog-config"
|
||||
|
||||
class PostHogClientProvider {
|
||||
private static instance: PostHogClientProvider
|
||||
|
||||
@@ -5,7 +5,6 @@ import { version as extensionVersion } from "../../../../package.json"
|
||||
import type { TaskFeedbackType } from "@shared/WebviewMessage"
|
||||
import type { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { posthogClientProvider } from "../PostHogClientProvider"
|
||||
import { Mode } from "@/shared/ChatSettings"
|
||||
|
||||
/**
|
||||
* TelemetryService handles telemetry event tracking for the Cline extension
|
||||
@@ -313,7 +312,7 @@ class TelemetryService {
|
||||
* @param taskId Unique identifier for the task
|
||||
* @param mode The mode being switched to (plan or act)
|
||||
*/
|
||||
public captureModeSwitch(taskId: string, mode: Mode) {
|
||||
public captureModeSwitch(taskId: string, mode: "plan" | "act") {
|
||||
this.capture({
|
||||
event: TelemetryService.EVENTS.TASK.MODE_SWITCH,
|
||||
properties: {
|
||||
@@ -535,7 +534,7 @@ class TelemetryService {
|
||||
* @param qty The quantity of options that were presented
|
||||
* @param mode The mode in which the option was selected ("plan" or "act")
|
||||
*/
|
||||
public captureOptionSelected(taskId: string, qty: number, mode: Mode) {
|
||||
public captureOptionSelected(taskId: string, qty: number, mode: "plan" | "act") {
|
||||
this.capture({
|
||||
event: TelemetryService.EVENTS.TASK.OPTION_SELECTED,
|
||||
properties: {
|
||||
@@ -552,7 +551,7 @@ class TelemetryService {
|
||||
* @param qty The quantity of options that were presented
|
||||
* @param mode The mode in which the custom response was provided ("plan" or "act")
|
||||
*/
|
||||
public captureOptionsIgnored(taskId: string, qty: number, mode: Mode) {
|
||||
public captureOptionsIgnored(taskId: string, qty: number, mode: "plan" | "act") {
|
||||
this.capture({
|
||||
event: TelemetryService.EVENTS.TASK.OPTIONS_IGNORED,
|
||||
properties: {
|
||||
|
||||
@@ -274,8 +274,7 @@ 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, "planModeApiProvider", "cline")
|
||||
await updateGlobalState(visibleWebview.controller.context, "actModeApiProvider", "cline")
|
||||
await updateGlobalState(visibleWebview.controller.context, "apiProvider", "cline" as ApiProvider)
|
||||
|
||||
// Post state to webview to reflect changes
|
||||
await visibleWebview.controller.postStateToWebview()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
export type OpenAIReasoningEffort = "low" | "medium" | "high"
|
||||
|
||||
export type Mode = "plan" | "act"
|
||||
|
||||
export interface ChatSettings {
|
||||
mode: Mode
|
||||
mode: "plan" | "act"
|
||||
preferredLanguage?: string
|
||||
openAIReasoningEffort?: OpenAIReasoningEffort
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { HistoryItem } from "./HistoryItem"
|
||||
import { TelemetrySetting } from "./TelemetrySetting"
|
||||
import { ClineRulesToggles } from "./cline-rules"
|
||||
import { UserInfo } from "./UserInfo"
|
||||
import { McpDisplayMode } from "./McpDisplayMode"
|
||||
import { McpDisplayMode, DEFAULT_MCP_DISPLAY_MODE } from "./McpDisplayMode"
|
||||
|
||||
// webview will hold state
|
||||
export interface ExtensionMessage {
|
||||
|
||||
+22
-97
@@ -29,19 +29,22 @@ export type ApiProvider =
|
||||
| "cerebras"
|
||||
| "sapaicore"
|
||||
| "groq"
|
||||
| "huggingface"
|
||||
|
||||
export interface ApiHandlerOptions {
|
||||
// Global configuration (not mode-specific)
|
||||
apiModelId?: string
|
||||
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
|
||||
@@ -54,99 +57,64 @@ export interface ApiHandlerOptions {
|
||||
awsProfile?: string
|
||||
awsBedrockApiKey?: string
|
||||
awsBedrockEndpoint?: string
|
||||
awsBedrockCustomSelected?: boolean
|
||||
awsBedrockCustomModelBaseId?: BedrockModelId
|
||||
claudeCodePath?: string
|
||||
vertexProjectId?: string
|
||||
vertexRegion?: string
|
||||
openAiBaseUrl?: string
|
||||
openAiApiKey?: string
|
||||
openAiModelId?: string
|
||||
openAiModelInfo?: OpenAiCompatibleModelInfo
|
||||
ollamaModelId?: string
|
||||
ollamaBaseUrl?: string
|
||||
ollamaApiOptionsCtxNum?: string
|
||||
lmStudioModelId?: string
|
||||
lmStudioBaseUrl?: string
|
||||
geminiApiKey?: string
|
||||
geminiBaseUrl?: string
|
||||
openAiNativeApiKey?: string
|
||||
deepSeekApiKey?: string
|
||||
requestyApiKey?: string
|
||||
requestyModelId?: string
|
||||
requestyModelInfo?: ModelInfo
|
||||
togetherApiKey?: string
|
||||
togetherModelId?: string
|
||||
fireworksApiKey?: string
|
||||
fireworksModelId?: string
|
||||
fireworksModelMaxCompletionTokens?: number
|
||||
fireworksModelMaxTokens?: number
|
||||
qwenApiKey?: string
|
||||
doubaoApiKey?: string
|
||||
mistralApiKey?: string
|
||||
azureApiVersion?: string
|
||||
vsCodeLmModelSelector?: LanguageModelChatSelector
|
||||
qwenApiLine?: string
|
||||
moonshotApiLine?: string
|
||||
moonshotApiKey?: string
|
||||
huggingFaceApiKey?: string
|
||||
nebiusApiKey?: string
|
||||
asksageApiUrl?: string
|
||||
asksageApiKey?: string
|
||||
xaiApiKey?: string
|
||||
thinkingBudgetTokens?: number
|
||||
reasoningEffort?: string
|
||||
sambanovaApiKey?: string
|
||||
cerebrasApiKey?: string
|
||||
groqApiKey?: string
|
||||
groqModelId?: string
|
||||
groqModelInfo?: ModelInfo
|
||||
requestTimeoutMs?: number
|
||||
sapAiCoreClientId?: string
|
||||
sapAiCoreClientSecret?: string
|
||||
sapAiResourceGroup?: string
|
||||
sapAiCoreTokenUrl?: string
|
||||
sapAiCoreBaseUrl?: string
|
||||
sapAiCoreModelId?: string
|
||||
onRetryAttempt?: (attempt: number, maxRetries: number, delay: number, error: any) => void
|
||||
// Plan mode configurations
|
||||
planModeApiModelId?: string
|
||||
planModeThinkingBudgetTokens?: number
|
||||
planModeReasoningEffort?: string
|
||||
planModeVsCodeLmModelSelector?: LanguageModelChatSelector
|
||||
planModeAwsBedrockCustomSelected?: boolean
|
||||
planModeAwsBedrockCustomModelBaseId?: BedrockModelId
|
||||
planModeOpenRouterModelId?: string
|
||||
planModeOpenRouterModelInfo?: ModelInfo
|
||||
planModeOpenAiModelId?: string
|
||||
planModeOpenAiModelInfo?: OpenAiCompatibleModelInfo
|
||||
planModeOllamaModelId?: string
|
||||
planModeLmStudioModelId?: string
|
||||
planModeLiteLlmModelId?: string
|
||||
planModeLiteLlmModelInfo?: LiteLLMModelInfo
|
||||
planModeRequestyModelId?: string
|
||||
planModeRequestyModelInfo?: ModelInfo
|
||||
planModeTogetherModelId?: string
|
||||
planModeFireworksModelId?: string
|
||||
planModeSapAiCoreModelId?: string
|
||||
planModeGroqModelId?: string
|
||||
planModeGroqModelInfo?: ModelInfo
|
||||
planModeHuggingFaceModelId?: string
|
||||
planModeHuggingFaceModelInfo?: ModelInfo
|
||||
// Act mode configurations
|
||||
|
||||
actModeApiModelId?: string
|
||||
actModeThinkingBudgetTokens?: number
|
||||
actModeReasoningEffort?: string
|
||||
actModeVsCodeLmModelSelector?: LanguageModelChatSelector
|
||||
actModeAwsBedrockCustomSelected?: boolean
|
||||
actModeAwsBedrockCustomModelBaseId?: BedrockModelId
|
||||
actModeOpenRouterModelId?: string
|
||||
actModeOpenRouterModelInfo?: ModelInfo
|
||||
actModeOpenAiModelId?: string
|
||||
actModeOpenAiModelInfo?: OpenAiCompatibleModelInfo
|
||||
actModeOllamaModelId?: string
|
||||
actModeLmStudioModelId?: string
|
||||
actModeLiteLlmModelId?: string
|
||||
actModeLiteLlmModelInfo?: LiteLLMModelInfo
|
||||
actModeRequestyModelId?: string
|
||||
actModeRequestyModelInfo?: ModelInfo
|
||||
actModeTogetherModelId?: string
|
||||
actModeFireworksModelId?: string
|
||||
actModeSapAiCoreModelId?: string
|
||||
actModeGroqModelId?: string
|
||||
actModeGroqModelInfo?: ModelInfo
|
||||
actModeHuggingFaceModelId?: string
|
||||
actModeHuggingFaceModelInfo?: ModelInfo
|
||||
}
|
||||
|
||||
export type ApiConfiguration = ApiHandlerOptions & {
|
||||
planModeApiProvider?: ApiProvider
|
||||
actModeApiProvider?: ApiProvider
|
||||
apiProvider?: ApiProvider
|
||||
favoritedModelIds?: string[]
|
||||
}
|
||||
|
||||
@@ -1106,49 +1074,6 @@ export const deepSeekModels = {
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
// Hugging Face Inference Providers
|
||||
// https://huggingface.co/docs/inference-providers/en/index
|
||||
export type HuggingFaceModelId = keyof typeof huggingFaceModels
|
||||
export const huggingFaceDefaultModelId: HuggingFaceModelId = "moonshotai/Kimi-K2-Instruct"
|
||||
export const huggingFaceModels = {
|
||||
"moonshotai/Kimi-K2-Instruct": {
|
||||
maxTokens: 131_072,
|
||||
contextWindow: 131_072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Advanced reasoning model with superior performance across coding, math, and general capabilities.",
|
||||
},
|
||||
"deepseek-ai/DeepSeek-V3-0324": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 64_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Advanced reasoning model with superior performance across coding, math, and general capabilities.",
|
||||
},
|
||||
"deepseek-ai/DeepSeek-R1": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 64_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "DeepSeek's reasoning model with step-by-step thinking capabilities.",
|
||||
},
|
||||
"meta-llama/Llama-3.1-8B-Instruct": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Efficient 8B parameter Llama model for general-purpose tasks.",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
// Qwen
|
||||
// https://bailian.console.aliyun.com/
|
||||
export type MainlandQwenModelId = keyof typeof mainlandQwenModels
|
||||
|
||||
@@ -224,8 +224,6 @@ function convertApiProviderToProto(provider: string | undefined): ProtoApiProvid
|
||||
return ProtoApiProvider.LITELLM
|
||||
case "moonshot":
|
||||
return ProtoApiProvider.MOONSHOT
|
||||
case "huggingface":
|
||||
return ProtoApiProvider.HUGGINGFACE
|
||||
case "nebius":
|
||||
return ProtoApiProvider.NEBIUS
|
||||
case "fireworks":
|
||||
@@ -290,8 +288,6 @@ function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvider {
|
||||
return "litellm"
|
||||
case ProtoApiProvider.MOONSHOT:
|
||||
return "moonshot"
|
||||
case ProtoApiProvider.HUGGINGFACE:
|
||||
return "huggingface"
|
||||
case ProtoApiProvider.NEBIUS:
|
||||
return "nebius"
|
||||
case ProtoApiProvider.FIREWORKS:
|
||||
@@ -318,16 +314,20 @@ function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvider {
|
||||
// Converts application ApiConfiguration to proto ApiConfiguration
|
||||
export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoApiConfiguration {
|
||||
return {
|
||||
// Global configuration fields
|
||||
apiModelId: config.apiModelId,
|
||||
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,
|
||||
@@ -340,115 +340,80 @@ export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoA
|
||||
awsProfile: config.awsProfile,
|
||||
awsBedrockApiKey: config.awsBedrockApiKey,
|
||||
awsBedrockEndpoint: config.awsBedrockEndpoint,
|
||||
claudeCodePath: config.claudeCodePath,
|
||||
awsBedrockCustomSelected: config.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: config.awsBedrockCustomModelBaseId as string | undefined,
|
||||
vertexProjectId: config.vertexProjectId,
|
||||
vertexRegion: config.vertexRegion,
|
||||
openAiBaseUrl: config.openAiBaseUrl,
|
||||
openAiApiKey: config.openAiApiKey,
|
||||
openAiModelId: config.openAiModelId,
|
||||
openAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.openAiModelInfo),
|
||||
ollamaModelId: config.ollamaModelId,
|
||||
ollamaBaseUrl: config.ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId: config.lmStudioModelId,
|
||||
lmStudioBaseUrl: config.lmStudioBaseUrl,
|
||||
geminiApiKey: config.geminiApiKey,
|
||||
geminiBaseUrl: config.geminiBaseUrl,
|
||||
openAiNativeApiKey: config.openAiNativeApiKey,
|
||||
deepSeekApiKey: config.deepSeekApiKey,
|
||||
requestyApiKey: config.requestyApiKey,
|
||||
requestyModelId: config.requestyModelId,
|
||||
requestyModelInfo: convertModelInfoToProtoOpenRouter(config.requestyModelInfo),
|
||||
togetherApiKey: config.togetherApiKey,
|
||||
togetherModelId: config.togetherModelId,
|
||||
fireworksApiKey: config.fireworksApiKey,
|
||||
fireworksModelId: config.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: config.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: config.fireworksModelMaxTokens,
|
||||
qwenApiKey: config.qwenApiKey,
|
||||
doubaoApiKey: config.doubaoApiKey,
|
||||
mistralApiKey: config.mistralApiKey,
|
||||
azureApiVersion: config.azureApiVersion,
|
||||
vsCodeLmModelSelector: config.vsCodeLmModelSelector,
|
||||
qwenApiLine: config.qwenApiLine,
|
||||
moonshotApiLine: config.moonshotApiLine,
|
||||
moonshotApiKey: config.moonshotApiKey,
|
||||
huggingFaceApiKey: config.huggingFaceApiKey,
|
||||
nebiusApiKey: config.nebiusApiKey,
|
||||
asksageApiUrl: config.asksageApiUrl,
|
||||
asksageApiKey: config.asksageApiKey,
|
||||
xaiApiKey: config.xaiApiKey,
|
||||
thinkingBudgetTokens: config.thinkingBudgetTokens,
|
||||
reasoningEffort: config.reasoningEffort,
|
||||
sambanovaApiKey: config.sambanovaApiKey,
|
||||
cerebrasApiKey: config.cerebrasApiKey,
|
||||
groqApiKey: config.groqApiKey,
|
||||
groqModelId: config.groqModelId,
|
||||
groqModelInfo: convertModelInfoToProtoOpenRouter(config.groqModelInfo),
|
||||
requestTimeoutMs: config.requestTimeoutMs,
|
||||
apiProvider: config.apiProvider ? convertApiProviderToProto(config.apiProvider) : undefined,
|
||||
favoritedModelIds: config.favoritedModelIds || [],
|
||||
sapAiCoreClientId: config.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: config.sapAiCoreClientSecret,
|
||||
sapAiResourceGroup: config.sapAiResourceGroup,
|
||||
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
|
||||
sapAiCoreBaseUrl: config.sapAiCoreBaseUrl,
|
||||
|
||||
// Plan mode configurations
|
||||
planModeApiProvider: config.planModeApiProvider ? convertApiProviderToProto(config.planModeApiProvider) : undefined,
|
||||
planModeApiModelId: config.planModeApiModelId,
|
||||
planModeThinkingBudgetTokens: config.planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort: config.planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector: config.planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected: config.planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId: config.planModeAwsBedrockCustomModelBaseId as string | undefined,
|
||||
planModeOpenRouterModelId: config.planModeOpenRouterModelId,
|
||||
planModeOpenRouterModelInfo: convertModelInfoToProtoOpenRouter(config.planModeOpenRouterModelInfo),
|
||||
planModeOpenAiModelId: config.planModeOpenAiModelId,
|
||||
planModeOpenAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.planModeOpenAiModelInfo),
|
||||
planModeOllamaModelId: config.planModeOllamaModelId,
|
||||
planModeLmStudioModelId: config.planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId: config.planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo: convertLiteLLMModelInfoToProto(config.planModeLiteLlmModelInfo),
|
||||
planModeRequestyModelId: config.planModeRequestyModelId,
|
||||
planModeRequestyModelInfo: convertModelInfoToProtoOpenRouter(config.planModeRequestyModelInfo),
|
||||
planModeTogetherModelId: config.planModeTogetherModelId,
|
||||
planModeFireworksModelId: config.planModeFireworksModelId,
|
||||
planModeGroqModelId: config.planModeGroqModelId,
|
||||
planModeGroqModelInfo: convertModelInfoToProtoOpenRouter(config.planModeGroqModelInfo),
|
||||
planModeHuggingFaceModelId: config.planModeHuggingFaceModelId,
|
||||
planModeHuggingFaceModelInfo: convertModelInfoToProtoOpenRouter(config.planModeHuggingFaceModelInfo),
|
||||
planModeSapAiCoreModelId: config.planModeSapAiCoreModelId,
|
||||
|
||||
// Act mode configurations
|
||||
actModeApiProvider: config.actModeApiProvider ? convertApiProviderToProto(config.actModeApiProvider) : undefined,
|
||||
actModeApiModelId: config.actModeApiModelId,
|
||||
actModeThinkingBudgetTokens: config.actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort: config.actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector: config.actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected: config.actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId: config.actModeAwsBedrockCustomModelBaseId as string | undefined,
|
||||
actModeOpenRouterModelId: config.actModeOpenRouterModelId,
|
||||
actModeOpenRouterModelInfo: convertModelInfoToProtoOpenRouter(config.actModeOpenRouterModelInfo),
|
||||
actModeOpenAiModelId: config.actModeOpenAiModelId,
|
||||
actModeOpenAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.actModeOpenAiModelInfo),
|
||||
actModeOllamaModelId: config.actModeOllamaModelId,
|
||||
actModeLmStudioModelId: config.actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId: config.actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo: convertLiteLLMModelInfoToProto(config.actModeLiteLlmModelInfo),
|
||||
actModeRequestyModelId: config.actModeRequestyModelId,
|
||||
actModeRequestyModelInfo: convertModelInfoToProtoOpenRouter(config.actModeRequestyModelInfo),
|
||||
actModeTogetherModelId: config.actModeTogetherModelId,
|
||||
actModeFireworksModelId: config.actModeFireworksModelId,
|
||||
actModeGroqModelId: config.actModeGroqModelId,
|
||||
actModeGroqModelInfo: convertModelInfoToProtoOpenRouter(config.actModeGroqModelInfo),
|
||||
actModeHuggingFaceModelId: config.actModeHuggingFaceModelId,
|
||||
actModeHuggingFaceModelInfo: convertModelInfoToProtoOpenRouter(config.actModeHuggingFaceModelInfo),
|
||||
actModeSapAiCoreModelId: config.actModeSapAiCoreModelId,
|
||||
|
||||
// Favorited model IDs
|
||||
favoritedModelIds: config.favoritedModelIds || [],
|
||||
claudeCodePath: config.claudeCodePath,
|
||||
}
|
||||
}
|
||||
|
||||
// Converts proto ApiConfiguration to application ApiConfiguration
|
||||
export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguration): ApiConfiguration {
|
||||
return {
|
||||
// Global configuration fields
|
||||
apiModelId: protoConfig.apiModelId,
|
||||
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,
|
||||
openAiHeaders: Object.keys(protoConfig.openAiHeaders).length > 0 ? protoConfig.openAiHeaders : undefined,
|
||||
liteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.liteLlmModelInfo),
|
||||
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
|
||||
openRouterApiKey: protoConfig.openRouterApiKey,
|
||||
openRouterModelId: protoConfig.openRouterModelId,
|
||||
openRouterModelInfo: convertProtoToModelInfo(protoConfig.openRouterModelInfo),
|
||||
openRouterProviderSorting: protoConfig.openRouterProviderSorting,
|
||||
awsAccessKey: protoConfig.awsAccessKey,
|
||||
awsSecretKey: protoConfig.awsSecretKey,
|
||||
@@ -461,103 +426,59 @@ export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguratio
|
||||
awsProfile: protoConfig.awsProfile,
|
||||
awsBedrockApiKey: protoConfig.awsBedrockApiKey,
|
||||
awsBedrockEndpoint: protoConfig.awsBedrockEndpoint,
|
||||
claudeCodePath: protoConfig.claudeCodePath,
|
||||
awsBedrockCustomSelected: protoConfig.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
vertexProjectId: protoConfig.vertexProjectId,
|
||||
vertexRegion: protoConfig.vertexRegion,
|
||||
openAiBaseUrl: protoConfig.openAiBaseUrl,
|
||||
openAiApiKey: protoConfig.openAiApiKey,
|
||||
openAiModelId: protoConfig.openAiModelId,
|
||||
openAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.openAiModelInfo),
|
||||
ollamaModelId: protoConfig.ollamaModelId,
|
||||
ollamaBaseUrl: protoConfig.ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId: protoConfig.lmStudioModelId,
|
||||
lmStudioBaseUrl: protoConfig.lmStudioBaseUrl,
|
||||
geminiApiKey: protoConfig.geminiApiKey,
|
||||
geminiBaseUrl: protoConfig.geminiBaseUrl,
|
||||
openAiNativeApiKey: protoConfig.openAiNativeApiKey,
|
||||
deepSeekApiKey: protoConfig.deepSeekApiKey,
|
||||
requestyApiKey: protoConfig.requestyApiKey,
|
||||
requestyModelId: protoConfig.requestyModelId,
|
||||
requestyModelInfo: convertProtoToModelInfo(protoConfig.requestyModelInfo),
|
||||
togetherApiKey: protoConfig.togetherApiKey,
|
||||
togetherModelId: protoConfig.togetherModelId,
|
||||
fireworksApiKey: protoConfig.fireworksApiKey,
|
||||
fireworksModelId: protoConfig.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: protoConfig.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: protoConfig.fireworksModelMaxTokens,
|
||||
qwenApiKey: protoConfig.qwenApiKey,
|
||||
doubaoApiKey: protoConfig.doubaoApiKey,
|
||||
mistralApiKey: protoConfig.mistralApiKey,
|
||||
azureApiVersion: protoConfig.azureApiVersion,
|
||||
vsCodeLmModelSelector: protoConfig.vsCodeLmModelSelector,
|
||||
qwenApiLine: protoConfig.qwenApiLine,
|
||||
moonshotApiLine: protoConfig.moonshotApiLine,
|
||||
moonshotApiKey: protoConfig.moonshotApiKey,
|
||||
huggingFaceApiKey: protoConfig.huggingFaceApiKey,
|
||||
nebiusApiKey: protoConfig.nebiusApiKey,
|
||||
asksageApiUrl: protoConfig.asksageApiUrl,
|
||||
asksageApiKey: protoConfig.asksageApiKey,
|
||||
xaiApiKey: protoConfig.xaiApiKey,
|
||||
thinkingBudgetTokens: protoConfig.thinkingBudgetTokens,
|
||||
reasoningEffort: protoConfig.reasoningEffort,
|
||||
sambanovaApiKey: protoConfig.sambanovaApiKey,
|
||||
cerebrasApiKey: protoConfig.cerebrasApiKey,
|
||||
groqApiKey: protoConfig.groqApiKey,
|
||||
groqModelId: protoConfig.groqModelId,
|
||||
groqModelInfo: convertProtoToModelInfo(protoConfig.groqModelInfo),
|
||||
requestTimeoutMs: protoConfig.requestTimeoutMs,
|
||||
apiProvider: protoConfig.apiProvider !== undefined ? convertProtoToApiProvider(protoConfig.apiProvider) : undefined,
|
||||
favoritedModelIds: protoConfig.favoritedModelIds.length > 0 ? protoConfig.favoritedModelIds : undefined,
|
||||
sapAiCoreClientId: protoConfig.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: protoConfig.sapAiCoreClientSecret,
|
||||
sapAiResourceGroup: protoConfig.sapAiResourceGroup,
|
||||
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
|
||||
sapAiCoreBaseUrl: protoConfig.sapAiCoreBaseUrl,
|
||||
|
||||
// Plan mode configurations
|
||||
planModeApiProvider:
|
||||
protoConfig.planModeApiProvider !== undefined
|
||||
? convertProtoToApiProvider(protoConfig.planModeApiProvider)
|
||||
: undefined,
|
||||
planModeApiModelId: protoConfig.planModeApiModelId,
|
||||
planModeThinkingBudgetTokens: protoConfig.planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort: protoConfig.planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector: protoConfig.planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected: protoConfig.planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId: protoConfig.planModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
planModeOpenRouterModelId: protoConfig.planModeOpenRouterModelId,
|
||||
planModeOpenRouterModelInfo: convertProtoToModelInfo(protoConfig.planModeOpenRouterModelInfo),
|
||||
planModeOpenAiModelId: protoConfig.planModeOpenAiModelId,
|
||||
planModeOpenAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.planModeOpenAiModelInfo),
|
||||
planModeOllamaModelId: protoConfig.planModeOllamaModelId,
|
||||
planModeLmStudioModelId: protoConfig.planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId: protoConfig.planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.planModeLiteLlmModelInfo),
|
||||
planModeRequestyModelId: protoConfig.planModeRequestyModelId,
|
||||
planModeRequestyModelInfo: convertProtoToModelInfo(protoConfig.planModeRequestyModelInfo),
|
||||
planModeTogetherModelId: protoConfig.planModeTogetherModelId,
|
||||
planModeFireworksModelId: protoConfig.planModeFireworksModelId,
|
||||
planModeGroqModelId: protoConfig.planModeGroqModelId,
|
||||
planModeGroqModelInfo: convertProtoToModelInfo(protoConfig.planModeGroqModelInfo),
|
||||
planModeHuggingFaceModelId: protoConfig.planModeHuggingFaceModelId,
|
||||
planModeHuggingFaceModelInfo: convertProtoToModelInfo(protoConfig.planModeHuggingFaceModelInfo),
|
||||
planModeSapAiCoreModelId: protoConfig.planModeSapAiCoreModelId,
|
||||
|
||||
// Act mode configurations
|
||||
actModeApiProvider:
|
||||
protoConfig.actModeApiProvider !== undefined ? convertProtoToApiProvider(protoConfig.actModeApiProvider) : undefined,
|
||||
actModeApiModelId: protoConfig.actModeApiModelId,
|
||||
actModeThinkingBudgetTokens: protoConfig.actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort: protoConfig.actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector: protoConfig.actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected: protoConfig.actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId: protoConfig.actModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
actModeOpenRouterModelId: protoConfig.actModeOpenRouterModelId,
|
||||
actModeOpenRouterModelInfo: convertProtoToModelInfo(protoConfig.actModeOpenRouterModelInfo),
|
||||
actModeOpenAiModelId: protoConfig.actModeOpenAiModelId,
|
||||
actModeOpenAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.actModeOpenAiModelInfo),
|
||||
actModeOllamaModelId: protoConfig.actModeOllamaModelId,
|
||||
actModeLmStudioModelId: protoConfig.actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId: protoConfig.actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.actModeLiteLlmModelInfo),
|
||||
actModeRequestyModelId: protoConfig.actModeRequestyModelId,
|
||||
actModeRequestyModelInfo: convertProtoToModelInfo(protoConfig.actModeRequestyModelInfo),
|
||||
actModeTogetherModelId: protoConfig.actModeTogetherModelId,
|
||||
actModeFireworksModelId: protoConfig.actModeFireworksModelId,
|
||||
actModeGroqModelId: protoConfig.actModeGroqModelId,
|
||||
actModeGroqModelInfo: convertProtoToModelInfo(protoConfig.actModeGroqModelInfo),
|
||||
actModeHuggingFaceModelId: protoConfig.actModeHuggingFaceModelId,
|
||||
actModeHuggingFaceModelInfo: convertProtoToModelInfo(protoConfig.actModeHuggingFaceModelInfo),
|
||||
actModeSapAiCoreModelId: protoConfig.actModeSapAiCoreModelId,
|
||||
|
||||
// Favorited model IDs
|
||||
favoritedModelIds:
|
||||
protoConfig.favoritedModelIds && protoConfig.favoritedModelIds.length > 0 ? protoConfig.favoritedModelIds : undefined,
|
||||
claudeCodePath: protoConfig.claudeCodePath,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,17 +11,49 @@ import {
|
||||
*/
|
||||
export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfiguration): ProtoApiConfiguration {
|
||||
return ProtoApiConfiguration.create({
|
||||
// Global configuration fields (not mode-specific)
|
||||
// Core API fields
|
||||
apiProvider: config.apiProvider,
|
||||
apiModelId: config.apiModelId,
|
||||
apiKey: config.apiKey,
|
||||
|
||||
// Provider-specific API keys
|
||||
clineAccountId: config.clineAccountId,
|
||||
taskId: config.taskId,
|
||||
liteLlmBaseUrl: config.liteLlmBaseUrl,
|
||||
liteLlmApiKey: config.liteLlmApiKey,
|
||||
liteLlmUsePromptCache: config.liteLlmUsePromptCache,
|
||||
openaiHeaders: config.openAiHeaders ? JSON.stringify(config.openAiHeaders) : undefined,
|
||||
anthropicBaseUrl: config.anthropicBaseUrl,
|
||||
openrouterApiKey: config.openRouterApiKey,
|
||||
openrouterProviderSorting: config.openRouterProviderSorting,
|
||||
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,
|
||||
awsAccessKey: config.awsAccessKey,
|
||||
awsSecretKey: config.awsSecretKey,
|
||||
awsSessionToken: config.awsSessionToken,
|
||||
@@ -33,101 +65,68 @@ export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfig
|
||||
awsProfile: config.awsProfile,
|
||||
awsBedrockApiKey: config.awsBedrockApiKey,
|
||||
awsBedrockEndpoint: config.awsBedrockEndpoint,
|
||||
claudeCodePath: config.claudeCodePath,
|
||||
|
||||
// Vertex AI fields
|
||||
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,
|
||||
openaiNativeApiKey: config.openAiNativeApiKey,
|
||||
deepSeekApiKey: config.deepSeekApiKey,
|
||||
requestyApiKey: config.requestyApiKey,
|
||||
togetherApiKey: config.togetherApiKey,
|
||||
fireworksApiKey: config.fireworksApiKey,
|
||||
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
|
||||
fireworksModelMaxCompletionTokens: config.fireworksModelMaxCompletionTokens
|
||||
? Number(config.fireworksModelMaxCompletionTokens)
|
||||
: undefined,
|
||||
fireworksModelMaxTokens: config.fireworksModelMaxTokens ? Number(config.fireworksModelMaxTokens) : undefined,
|
||||
qwenApiKey: config.qwenApiKey,
|
||||
doubaoApiKey: config.doubaoApiKey,
|
||||
mistralApiKey: config.mistralApiKey,
|
||||
moonshotApiKey: config.moonshotApiKey,
|
||||
|
||||
// Azure specific
|
||||
azureApiVersion: config.azureApiVersion,
|
||||
|
||||
// Ollama specific
|
||||
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
|
||||
|
||||
// Qwen specific
|
||||
qwenApiLine: config.qwenApiLine,
|
||||
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,
|
||||
|
||||
// Moonshot specific
|
||||
moonshotApiLine: config.moonshotApiLine,
|
||||
|
||||
// OpenRouter specific
|
||||
openrouterProviderSorting: config.openRouterProviderSorting,
|
||||
|
||||
// SAP AI Core specific
|
||||
sapAiCoreClientId: config.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: config.sapAiCoreClientSecret,
|
||||
sapAiResourceGroup: config.sapAiResourceGroup,
|
||||
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
|
||||
sapAiCoreBaseUrl: config.sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup: config.sapAiResourceGroup,
|
||||
|
||||
// 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,
|
||||
// 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,
|
||||
|
||||
// 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,
|
||||
// Claude Code specific
|
||||
claudeCodePath: config.claudeCodePath,
|
||||
|
||||
// Favorited model IDs
|
||||
// Arrays
|
||||
favoritedModelIds: config.favoritedModelIds || [],
|
||||
})
|
||||
}
|
||||
@@ -138,16 +137,45 @@ export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfig
|
||||
export function convertProtoApiConfigurationToApiConfiguration(protoConfig: ProtoApiConfiguration): ApiConfiguration {
|
||||
// eslint-disable-next-line eslint-rules/no-protobuf-object-literals
|
||||
const config: ApiConfiguration = {
|
||||
// Global configuration fields (not mode-specific)
|
||||
// Core API fields
|
||||
apiProvider: protoConfig.apiProvider as ApiProvider,
|
||||
apiModelId: protoConfig.apiModelId,
|
||||
apiKey: protoConfig.apiKey,
|
||||
|
||||
// Provider-specific API keys
|
||||
clineAccountId: protoConfig.clineAccountId,
|
||||
taskId: protoConfig.taskId,
|
||||
liteLlmBaseUrl: protoConfig.liteLlmBaseUrl,
|
||||
liteLlmApiKey: protoConfig.liteLlmApiKey,
|
||||
liteLlmUsePromptCache: protoConfig.liteLlmUsePromptCache,
|
||||
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
|
||||
openRouterApiKey: protoConfig.openrouterApiKey,
|
||||
openRouterProviderSorting: protoConfig.openrouterProviderSorting,
|
||||
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,
|
||||
awsAccessKey: protoConfig.awsAccessKey,
|
||||
awsSecretKey: protoConfig.awsSecretKey,
|
||||
awsSessionToken: protoConfig.awsSessionToken,
|
||||
@@ -159,121 +187,83 @@ export function convertProtoApiConfigurationToApiConfiguration(protoConfig: Prot
|
||||
awsAuthentication: protoConfig.awsAuthentication,
|
||||
awsBedrockApiKey: protoConfig.awsBedrockApiKey,
|
||||
awsBedrockEndpoint: protoConfig.awsBedrockEndpoint,
|
||||
claudeCodePath: protoConfig.claudeCodePath,
|
||||
|
||||
// Vertex AI fields
|
||||
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,
|
||||
openAiNativeApiKey: protoConfig.openaiNativeApiKey,
|
||||
deepSeekApiKey: protoConfig.deepSeekApiKey,
|
||||
requestyApiKey: protoConfig.requestyApiKey,
|
||||
togetherApiKey: protoConfig.togetherApiKey,
|
||||
fireworksApiKey: protoConfig.fireworksApiKey,
|
||||
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
|
||||
fireworksModelMaxCompletionTokens: protoConfig.fireworksModelMaxCompletionTokens
|
||||
? Number(protoConfig.fireworksModelMaxCompletionTokens)
|
||||
: undefined,
|
||||
fireworksModelMaxTokens: protoConfig.fireworksModelMaxTokens ? Number(protoConfig.fireworksModelMaxTokens) : undefined,
|
||||
qwenApiKey: protoConfig.qwenApiKey,
|
||||
doubaoApiKey: protoConfig.doubaoApiKey,
|
||||
mistralApiKey: protoConfig.mistralApiKey,
|
||||
moonshotApiKey: protoConfig.moonshotApiKey,
|
||||
|
||||
// Azure specific
|
||||
azureApiVersion: protoConfig.azureApiVersion,
|
||||
|
||||
// Ollama specific
|
||||
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
|
||||
|
||||
// Qwen specific
|
||||
qwenApiLine: protoConfig.qwenApiLine,
|
||||
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,
|
||||
|
||||
// Moonshot specific
|
||||
moonshotApiLine: protoConfig.moonshotApiLine,
|
||||
|
||||
// OpenRouter specific
|
||||
openRouterProviderSorting: protoConfig.openrouterProviderSorting,
|
||||
|
||||
// SAP AI Core specific
|
||||
sapAiCoreClientId: protoConfig.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: protoConfig.sapAiCoreClientSecret,
|
||||
sapAiResourceGroup: protoConfig.sapAiResourceGroup,
|
||||
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
|
||||
sapAiCoreBaseUrl: protoConfig.sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup: protoConfig.sapAiResourceGroup,
|
||||
|
||||
// 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,
|
||||
// Claude Code specific
|
||||
claudeCodePath: protoConfig.claudeCodePath,
|
||||
|
||||
// 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
|
||||
// Arrays
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { DiffViewProvider } from "@/integrations/editor/DiffViewProvider"
|
||||
|
||||
export class ExternalDiffViewProvider extends DiffViewProvider {
|
||||
private activeDiffEditorId: string | undefined
|
||||
override async openDiffEditor(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
return
|
||||
}
|
||||
const response = await getHostBridgeProvider().diffClient.openDiff({
|
||||
path: this.absolutePath,
|
||||
content: this.originalContent ?? "",
|
||||
})
|
||||
this.activeDiffEditorId = response.diffId
|
||||
}
|
||||
override async replaceText(
|
||||
content: string,
|
||||
rangeToReplace: { startLine: number; endLine: number },
|
||||
_currentLine: number,
|
||||
): Promise<void> {
|
||||
await getHostBridgeProvider().diffClient.replaceText({
|
||||
diffId: this.activeDiffEditorId,
|
||||
content: content,
|
||||
startLine: rangeToReplace.startLine,
|
||||
endLine: rangeToReplace.endLine,
|
||||
})
|
||||
}
|
||||
}
|
||||
+5
@@ -4,6 +4,11 @@ import * as vscode from "vscode"
|
||||
import { URI } from "vscode-uri"
|
||||
import { WebviewProvider } from "@core/webview"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts
|
||||
*/
|
||||
|
||||
export class ExternalWebviewProvider extends WebviewProvider {
|
||||
// This hostname cannot be changed without updating the external webview handler.
|
||||
private RESOURCE_HOSTNAME: string = "internal.resources"
|
||||
@@ -2,18 +2,18 @@ import * as grpc from "@grpc/grpc-js"
|
||||
import { ReflectionService } from "@grpc/reflection"
|
||||
import * as health from "grpc-health-check"
|
||||
import * as hostProviders from "@hosts/host-providers"
|
||||
import { activate } from "@/extension"
|
||||
import { Controller } from "@core/controller"
|
||||
import { activate } from "../extension"
|
||||
import { Controller } from "../core/controller"
|
||||
import { extensionContext, outputChannel, postMessage } from "./vscode-context"
|
||||
import { getPackageDefinition, log } from "./utils"
|
||||
import { GrpcHandler, GrpcStreamingResponseHandler } from "@hosts/external/grpc-types"
|
||||
import { GrpcHandler, GrpcStreamingResponseHandler } from "./grpc-types"
|
||||
import { addProtobusServices } from "@generated/standalone/server-setup"
|
||||
import { StreamingResponseHandler } from "@core/controller/grpc-handler"
|
||||
import { ExternalHostBridgeClientManager } from "@hosts/external/host-bridge-client-manager"
|
||||
import { ExternalWebviewProvider } from "@hosts/external/ExternalWebviewProvider"
|
||||
import { WebviewProviderType } from "@shared/webview/types"
|
||||
import { StreamingResponseHandler } from "@/core/controller/grpc-handler"
|
||||
import { ExternalHostBridgeClientManager } from "./host-bridge-client-manager"
|
||||
import { ExternalWebviewProvider } from "./ExternalWebviewProvider"
|
||||
import { WebviewProviderType } from "@/shared/webview/types"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import { ExternalDiffViewProvider } from "@hosts/external/ExternalDiffviewProvider"
|
||||
import { ExternalDiffViewProvider } from "./ExternalDiffviewProvider"
|
||||
|
||||
export const PROTOBUS_PORT = 26040
|
||||
export const HOSTBRIDGE_PORT = 26041
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as grpc from "@grpc/grpc-js"
|
||||
import { Controller } from "@core/controller"
|
||||
import { Controller } from "../core/controller"
|
||||
|
||||
/**
|
||||
* Type definition for a gRPC handler function.
|
||||
+1
-1
@@ -14,7 +14,7 @@ import {
|
||||
DiffServiceClientInterface,
|
||||
} from "@generated/hosts/host-bridge-client-types"
|
||||
import { HostBridgeClientProvider } from "@/hosts/host-provider-types"
|
||||
import { HOSTBRIDGE_PORT } from "../../standalone/cline-core"
|
||||
import { HOSTBRIDGE_PORT } from "./cline-core"
|
||||
|
||||
/**
|
||||
* Manager to hold the gRPC clients for the host bridge. The clients should be re-used to avoid
|
||||
@@ -39,9 +39,210 @@ vscode.window = {
|
||||
console.log("Stubbed showSaveDialog:", options)
|
||||
return undefined
|
||||
},
|
||||
showTextDocument: async (...args) => {
|
||||
console.log("Stubbed showTextDocument:", ...args)
|
||||
return {}
|
||||
showTextDocument: async (uri, options) => {
|
||||
console.log("Stubbed showTextDocument:", uri, options)
|
||||
|
||||
// Extract file path from URI
|
||||
let filePath = uri.path || uri.fsPath || uri
|
||||
if (typeof filePath !== "string") {
|
||||
filePath = uri.toString()
|
||||
}
|
||||
|
||||
// Remove file:// prefix if present
|
||||
if (filePath.startsWith("file://")) {
|
||||
filePath = filePath.substring(7)
|
||||
}
|
||||
|
||||
// Create a function that always reads the current file content
|
||||
const getCurrentFileContent = async () => {
|
||||
try {
|
||||
const content = await fs.promises.readFile(filePath, "utf8")
|
||||
console.log(`getCurrentFileContent: Read file ${filePath} (${content.length} chars)`)
|
||||
return content
|
||||
} catch (error) {
|
||||
console.log(`getCurrentFileContent: Could not read file ${filePath}:`, error.message)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Try to read the initial file content
|
||||
let fileContent = await getCurrentFileContent()
|
||||
let lineCount = fileContent.split("\n").length
|
||||
|
||||
// Check if we already have an active editor for this file path
|
||||
const existingEditor = vscode.window._documentEditors && vscode.window._documentEditors[filePath]
|
||||
if (existingEditor) {
|
||||
console.log(`showTextDocument: Updating existing editor for ${filePath}`)
|
||||
// Update the existing editor's content
|
||||
fileContent = await getCurrentFileContent()
|
||||
lineCount = fileContent.split("\n").length
|
||||
|
||||
// Update the document's getText method to return current content
|
||||
existingEditor.document.getText = (range) => {
|
||||
// Always read fresh content for getText calls
|
||||
const currentContent = require("fs").readFileSync(filePath, "utf8")
|
||||
if (!range) {
|
||||
return currentContent
|
||||
}
|
||||
// Handle range-based getText with current content
|
||||
const lines = currentContent.split("\n")
|
||||
const startLine = Math.max(0, range.start.line)
|
||||
const endLine = Math.min(lines.length - 1, range.end.line)
|
||||
|
||||
if (startLine === endLine) {
|
||||
// Single line
|
||||
const line = lines[startLine] || ""
|
||||
const startChar = Math.max(0, range.start.character)
|
||||
const endChar = Math.min(line.length, range.end.character)
|
||||
return line.substring(startChar, endChar)
|
||||
} else {
|
||||
// Multiple lines
|
||||
const result = []
|
||||
for (let i = startLine; i <= endLine; i++) {
|
||||
const line = lines[i] || ""
|
||||
if (i === startLine) {
|
||||
result.push(line.substring(range.start.character))
|
||||
} else if (i === endLine) {
|
||||
result.push(line.substring(0, range.end.character))
|
||||
} else {
|
||||
result.push(line)
|
||||
}
|
||||
}
|
||||
return result.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Update other properties
|
||||
existingEditor.document.lineCount = lineCount
|
||||
existingEditor.document.fileName = filePath
|
||||
|
||||
// Update the active text editor reference
|
||||
vscode.window.activeTextEditor = existingEditor
|
||||
|
||||
return existingEditor
|
||||
}
|
||||
|
||||
// Create a new mock text editor that always reads current file content
|
||||
const mockEditor = {
|
||||
document: {
|
||||
uri: uri,
|
||||
fileName: filePath,
|
||||
isDirty: false,
|
||||
lineCount: lineCount,
|
||||
getText: (range) => {
|
||||
// Always read fresh content for getText calls
|
||||
try {
|
||||
const currentContent = require("fs").readFileSync(filePath, "utf8")
|
||||
console.log(`document.getText: Read fresh content (${currentContent.length} chars)`)
|
||||
if (!range) {
|
||||
return currentContent
|
||||
}
|
||||
// Handle range-based getText with current content
|
||||
const lines = currentContent.split("\n")
|
||||
const startLine = Math.max(0, range.start.line)
|
||||
const endLine = Math.min(lines.length - 1, range.end.line)
|
||||
|
||||
if (startLine === endLine) {
|
||||
// Single line
|
||||
const line = lines[startLine] || ""
|
||||
const startChar = Math.max(0, range.start.character)
|
||||
const endChar = Math.min(line.length, range.end.character)
|
||||
return line.substring(startChar, endChar)
|
||||
} else {
|
||||
// Multiple lines
|
||||
const result = []
|
||||
for (let i = startLine; i <= endLine; i++) {
|
||||
const line = lines[i] || ""
|
||||
if (i === startLine) {
|
||||
result.push(line.substring(range.start.character))
|
||||
} else if (i === endLine) {
|
||||
result.push(line.substring(0, range.end.character))
|
||||
} else {
|
||||
result.push(line)
|
||||
}
|
||||
}
|
||||
return result.join("\n")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading file in getText: ${error.message}`)
|
||||
return ""
|
||||
}
|
||||
},
|
||||
save: async () => {
|
||||
console.log("Called mock textDocument.save")
|
||||
return true
|
||||
},
|
||||
positionAt: (offset) => {
|
||||
try {
|
||||
const currentContent = require("fs").readFileSync(filePath, "utf8")
|
||||
const lines = currentContent.split("\n")
|
||||
let currentOffset = 0
|
||||
for (let line = 0; line < lines.length; line++) {
|
||||
const lineLength = lines[line].length + 1 // +1 for newline
|
||||
if (currentOffset + lineLength > offset) {
|
||||
return { line: line, character: offset - currentOffset }
|
||||
}
|
||||
currentOffset += lineLength
|
||||
}
|
||||
return { line: lines.length - 1, character: lines[lines.length - 1]?.length || 0 }
|
||||
} catch (error) {
|
||||
return { line: 0, character: 0 }
|
||||
}
|
||||
},
|
||||
offsetAt: (position) => {
|
||||
try {
|
||||
const currentContent = require("fs").readFileSync(filePath, "utf8")
|
||||
const lines = currentContent.split("\n")
|
||||
let offset = 0
|
||||
for (let i = 0; i < position.line && i < lines.length; i++) {
|
||||
offset += lines[i].length + 1 // +1 for newline
|
||||
}
|
||||
offset += Math.min(position.character, lines[position.line]?.length || 0)
|
||||
return offset
|
||||
} catch (error) {
|
||||
return 0
|
||||
}
|
||||
},
|
||||
},
|
||||
selection: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
|
||||
selections: [],
|
||||
visibleRanges: [{ start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }],
|
||||
options: {},
|
||||
viewColumn: 1,
|
||||
edit: async (callback) => {
|
||||
console.log("Called mock textEditor.edit")
|
||||
return true
|
||||
},
|
||||
insertSnippet: async () => true,
|
||||
setDecorations: () => {},
|
||||
revealRange: () => {},
|
||||
show: () => {},
|
||||
hide: () => {},
|
||||
}
|
||||
|
||||
// Store the editor by file path for future reference
|
||||
if (!vscode.window._documentEditors) {
|
||||
vscode.window._documentEditors = {}
|
||||
}
|
||||
vscode.window._documentEditors[filePath] = mockEditor
|
||||
|
||||
// Update the active text editor
|
||||
vscode.window.activeTextEditor = mockEditor
|
||||
|
||||
// Trigger onDidChangeActiveTextEditor listeners
|
||||
if (vscode.window._activeTextEditorListeners) {
|
||||
setTimeout(() => {
|
||||
vscode.window._activeTextEditorListeners.forEach((listener) => {
|
||||
try {
|
||||
listener(mockEditor)
|
||||
} catch (error) {
|
||||
console.error("Error calling onDidChangeActiveTextEditor listener:", error)
|
||||
}
|
||||
})
|
||||
}, 10) // Small delay to simulate async behavior
|
||||
}
|
||||
|
||||
return mockEditor
|
||||
},
|
||||
createOutputChannel: (name) => {
|
||||
console.log("Stubbed createOutputChannel:", name)
|
||||
@@ -89,10 +290,23 @@ vscode.window = {
|
||||
activeTextEditor: undefined,
|
||||
visibleTextEditors: [],
|
||||
tabGroups: {
|
||||
all: [],
|
||||
close: async () => {},
|
||||
onDidChangeTabs: createStub("vscode.env.tabGroups.onDidChangeTabs"),
|
||||
activeTabGroup: { tabs: [] },
|
||||
all: [
|
||||
{
|
||||
tabs: [],
|
||||
isActive: true,
|
||||
viewColumn: 1,
|
||||
},
|
||||
],
|
||||
activeTabGroup: {
|
||||
tabs: [],
|
||||
isActive: true,
|
||||
viewColumn: 1,
|
||||
},
|
||||
close: async (tab) => {
|
||||
console.log("Stubbed tabGroups.close:", tab)
|
||||
return true
|
||||
},
|
||||
onDidChangeTabs: createStub("vscode.window.tabGroups.onDidChangeTabs"),
|
||||
},
|
||||
withProgress: async (_options, task) => {
|
||||
console.log("Stubbed withProgress")
|
||||
@@ -100,14 +314,56 @@ vscode.window = {
|
||||
},
|
||||
registerUriHandler: () => ({ dispose: () => {} }),
|
||||
registerWebviewViewProvider: () => ({ dispose: () => {} }),
|
||||
onDidChangeActiveTextEditor: () => ({ dispose: () => {} }),
|
||||
createTextEditorDecorationType: () => ({ dispose: () => {} }),
|
||||
createWebviewPanel: (..._args) => {
|
||||
throw new Error("WebviewPanel is not supported in standalone app.")
|
||||
onDidChangeActiveTextEditor: (listener) => {
|
||||
console.log("Called vscode.window.onDidChangeActiveTextEditor")
|
||||
// Store the listener so we can call it when showTextDocument is called
|
||||
vscode.window._activeTextEditorListeners = vscode.window._activeTextEditorListeners || []
|
||||
vscode.window._activeTextEditorListeners.push(listener)
|
||||
return {
|
||||
dispose: () => {
|
||||
console.log("Disposed onDidChangeActiveTextEditor listener")
|
||||
const index = vscode.window._activeTextEditorListeners.indexOf(listener)
|
||||
if (index > -1) {
|
||||
vscode.window._activeTextEditorListeners.splice(index, 1)
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
createTextEditorDecorationType: () => ({ dispose: () => {} }),
|
||||
createWebviewPanel: (...args) => {
|
||||
console.log("Stubbed createWebviewPanel:", ...args)
|
||||
return {
|
||||
webview: {},
|
||||
reveal: () => {},
|
||||
dispose: () => {},
|
||||
}
|
||||
},
|
||||
onDidChangeTerminalState: (listener) => {
|
||||
console.log("Called vscode.window.onDidChangeTerminalState")
|
||||
return {
|
||||
dispose: () => {
|
||||
console.log("Disposed onDidChangeTerminalState listener")
|
||||
},
|
||||
}
|
||||
},
|
||||
onDidChangeTextEditorVisibleRanges: (listener) => {
|
||||
console.log("Called vscode.window.onDidChangeTextEditorVisibleRanges")
|
||||
return {
|
||||
dispose: () => {
|
||||
console.log("Disposed onDidChangeTextEditorVisibleRanges listener")
|
||||
},
|
||||
}
|
||||
},
|
||||
terminals: [],
|
||||
activeTerminal: null,
|
||||
}
|
||||
|
||||
vscode.env = {
|
||||
// Initialize env object if it doesn't exist, then extend it
|
||||
if (!vscode.env) {
|
||||
vscode.env = {}
|
||||
}
|
||||
|
||||
Object.assign(vscode.env, {
|
||||
uriScheme: "vscode",
|
||||
appName: "Visual Studio Code",
|
||||
appRoot: "/tmp/vscode/appRoot",
|
||||
@@ -117,17 +373,26 @@ vscode.env = {
|
||||
sessionId: "stub-session-id",
|
||||
shell: "/bin/bash",
|
||||
|
||||
// Add the stub functions that were missing
|
||||
clipboard: createStub("vscode.env.clipboard"),
|
||||
openExternal: createStub("vscode.env.openExternal"),
|
||||
getQueryParameter: createStub("vscode.env.getQueryParameter"),
|
||||
onDidChangeTelemetryEnabled: createStub("vscode.env.onDidChangeTelemetryEnabled"),
|
||||
isTelemetryEnabled: createStub("vscode.env.isTelemetryEnabled"),
|
||||
telemetryConfiguration: createStub("vscode.env.telemetryConfiguration"),
|
||||
onDidChangeTelemetryConfiguration: createStub("vscode.env.onDidChangeTelemetryConfiguration"),
|
||||
createTelemetryLogger: createStub("vscode.env.createTelemetryLogger"),
|
||||
})
|
||||
|
||||
// Override the openExternal function with actual implementation
|
||||
vscode.env.openExternal = async (uri) => {
|
||||
const url = typeof uri === "string" ? uri : (uri.toString?.() ?? "")
|
||||
console.log("Opening browser:", url)
|
||||
await open(url)
|
||||
return true
|
||||
}
|
||||
|
||||
vscode.Uri = {
|
||||
// Extend Uri object with improved implementations
|
||||
Object.assign(vscode.Uri, {
|
||||
parse: (uriString) => {
|
||||
const url = new URL(uriString)
|
||||
return {
|
||||
@@ -172,16 +437,387 @@ vscode.Uri = {
|
||||
const joined = segments.map((s) => (typeof s === "string" ? s : s.path)).join("/")
|
||||
return vscode.Uri.file("/" + joined.replace(/\/+/g, "/"))
|
||||
},
|
||||
})
|
||||
|
||||
// Extend workspace object with file system operations
|
||||
Object.assign(vscode.workspace, {
|
||||
fs: {
|
||||
readFile: async function (uri) {
|
||||
console.log(`Called vscode.workspace.fs.readFile with uri:`, uri)
|
||||
try {
|
||||
// Extract file path from URI
|
||||
let filePath = uri.path || uri.fsPath || uri
|
||||
if (typeof filePath !== "string") {
|
||||
filePath = uri.toString()
|
||||
}
|
||||
|
||||
// Remove file:// prefix if present
|
||||
if (filePath.startsWith("file://")) {
|
||||
filePath = filePath.substring(7)
|
||||
}
|
||||
|
||||
console.log(`Reading file: ${filePath}`)
|
||||
const content = await fs.promises.readFile(filePath, "utf8")
|
||||
console.log(
|
||||
`File content read (${content.length} chars):`,
|
||||
content.substring(0, 100) + (content.length > 100 ? "..." : ""),
|
||||
)
|
||||
return new Uint8Array(Buffer.from(content, "utf8"))
|
||||
} catch (error) {
|
||||
console.error(`Error reading file:`, error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
writeFile: async function (uri, content) {
|
||||
console.log(`Called vscode.workspace.fs.writeFile with uri:`, uri)
|
||||
try {
|
||||
// Extract file path from URI
|
||||
let filePath = uri.path || uri.fsPath || uri
|
||||
if (typeof filePath !== "string") {
|
||||
filePath = uri.toString()
|
||||
}
|
||||
|
||||
// Remove file:// prefix if present
|
||||
if (filePath.startsWith("file://")) {
|
||||
filePath = filePath.substring(7)
|
||||
}
|
||||
|
||||
console.log(`Writing file: ${filePath}`)
|
||||
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(filePath)
|
||||
await fs.promises.mkdir(dir, { recursive: true })
|
||||
|
||||
// Write the file
|
||||
await fs.promises.writeFile(filePath, content)
|
||||
} catch (error) {
|
||||
console.error(`Error writing file:`, error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
rootPath: process.cwd(),
|
||||
name: path.basename(process.cwd()),
|
||||
|
||||
// Add other workspace methods as stubs
|
||||
getConfiguration: () => ({
|
||||
get: () => undefined,
|
||||
update: () => Promise.resolve(),
|
||||
has: () => false,
|
||||
}),
|
||||
createFileSystemWatcher: () => ({
|
||||
onDidChange: () => ({ dispose: () => {} }),
|
||||
onDidCreate: () => ({ dispose: () => {} }),
|
||||
onDidDelete: () => ({ dispose: () => {} }),
|
||||
dispose: () => {},
|
||||
}),
|
||||
onDidChangeConfiguration: () => ({ dispose: () => {} }),
|
||||
onDidCreateFiles: createStub("vscode.workspace.onDidCreateFiles"),
|
||||
onDidDeleteFiles: createStub("vscode.workspace.onDidDeleteFiles"),
|
||||
onDidRenameFiles: createStub("vscode.workspace.onDidRenameFiles"),
|
||||
onWillCreateFiles: createStub("vscode.workspace.onWillCreateFiles"),
|
||||
onWillDeleteFiles: createStub("vscode.workspace.onWillDeleteFiles"),
|
||||
onWillRenameFiles: createStub("vscode.workspace.onWillRenameFiles"),
|
||||
textDocuments: {
|
||||
find: (predicate) => {
|
||||
console.log("Called vscode.workspace.textDocuments.find")
|
||||
// Return a mock text document that behaves like VSCode expects
|
||||
return {
|
||||
uri: { fsPath: "/tmp/mock-document" },
|
||||
fileName: "/tmp/mock-document",
|
||||
isDirty: false,
|
||||
save: async () => {
|
||||
console.log("Called mock textDocument.save")
|
||||
return true
|
||||
},
|
||||
getText: () => "",
|
||||
lineCount: 0,
|
||||
}
|
||||
},
|
||||
forEach: (callback) => {
|
||||
console.log("Called vscode.workspace.textDocuments.forEach")
|
||||
// No documents to iterate over in standalone mode
|
||||
},
|
||||
length: 0,
|
||||
[Symbol.iterator]: function* () {
|
||||
// Empty iterator for standalone mode
|
||||
},
|
||||
},
|
||||
|
||||
// Add the crucial applyEdit method
|
||||
applyEdit: async (workspaceEdit) => {
|
||||
console.log("Called vscode.workspace.applyEdit", workspaceEdit)
|
||||
|
||||
// For standalone mode, we'll simulate applying the edit by actually writing to files
|
||||
try {
|
||||
// WorkspaceEdit can contain multiple types of edits
|
||||
if (workspaceEdit._edits) {
|
||||
for (const edit of workspaceEdit._edits) {
|
||||
if (edit._type === 1) {
|
||||
// TextEdit
|
||||
const uri = edit._uri
|
||||
const edits = edit._edits
|
||||
|
||||
let filePath = uri.path || uri.fsPath
|
||||
if (filePath.startsWith("file://")) {
|
||||
filePath = filePath.substring(7)
|
||||
}
|
||||
|
||||
console.log(`Applying text edits to: ${filePath}`)
|
||||
|
||||
// Read current content if file exists
|
||||
let currentContent = ""
|
||||
try {
|
||||
currentContent = await fs.promises.readFile(filePath, "utf8")
|
||||
} catch (e) {
|
||||
// File doesn't exist, start with empty content
|
||||
console.log(`File ${filePath} doesn't exist, starting with empty content`)
|
||||
}
|
||||
|
||||
// Apply edits in reverse order (from end to beginning) to maintain positions
|
||||
const sortedEdits = edits.sort((a, b) => {
|
||||
const aStart = a.range.start.line * 1000000 + a.range.start.character
|
||||
const bStart = b.range.start.line * 1000000 + b.range.start.character
|
||||
return bStart - aStart
|
||||
})
|
||||
|
||||
let lines = currentContent.split("\n")
|
||||
|
||||
for (const edit of sortedEdits) {
|
||||
const startLine = edit.range.start.line
|
||||
const startChar = edit.range.start.character
|
||||
const endLine = edit.range.end.line
|
||||
const endChar = edit.range.end.character
|
||||
const newText = edit.newText
|
||||
|
||||
console.log(`Applying edit: ${startLine}:${startChar} - ${endLine}:${endChar} -> "${newText}"`)
|
||||
|
||||
// Handle the edit
|
||||
if (startLine === endLine) {
|
||||
// Single line edit
|
||||
const line = lines[startLine] || ""
|
||||
lines[startLine] = line.substring(0, startChar) + newText + line.substring(endChar)
|
||||
} else {
|
||||
// Multi-line edit
|
||||
const firstLine = lines[startLine] || ""
|
||||
const lastLine = lines[endLine] || ""
|
||||
const newFirstLine = firstLine.substring(0, startChar) + newText + lastLine.substring(endChar)
|
||||
|
||||
// Replace the range with the new content
|
||||
lines.splice(startLine, endLine - startLine + 1, newFirstLine)
|
||||
}
|
||||
}
|
||||
|
||||
const newContent = lines.join("\n")
|
||||
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(filePath)
|
||||
await fs.promises.mkdir(dir, { recursive: true })
|
||||
|
||||
// Write the updated content
|
||||
await fs.promises.writeFile(filePath, newContent, "utf8")
|
||||
console.log(`Successfully applied edits to: ${filePath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error("Error applying workspace edit:", error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Fix CodeActionKind to have static properties instead of being a class
|
||||
vscode.CodeActionKind = {
|
||||
Empty: "",
|
||||
QuickFix: "quickfix",
|
||||
Refactor: "refactor",
|
||||
RefactorExtract: "refactor.extract",
|
||||
RefactorInline: "refactor.inline",
|
||||
RefactorRewrite: "refactor.rewrite",
|
||||
Source: "source",
|
||||
SourceOrganizeImports: "source.organizeImports",
|
||||
SourceFixAll: "source.fixAll",
|
||||
}
|
||||
|
||||
vscode.env.openExternal = async (uri) => {
|
||||
const url = typeof uri === "string" ? uri : (uri.toString?.() ?? "")
|
||||
console.log("Opening browser:", url)
|
||||
await open(url)
|
||||
return true
|
||||
// Add missing commands implementation
|
||||
if (!vscode.commands) {
|
||||
vscode.commands = {}
|
||||
}
|
||||
|
||||
Object.assign(vscode.commands, {
|
||||
executeCommand: async (command, ...args) => {
|
||||
console.log(`Called vscode.commands.executeCommand: ${command}`, args)
|
||||
|
||||
// Handle the vscode.diff command specifically
|
||||
if (command === "vscode.diff") {
|
||||
const [originalUri, modifiedUri, title, options] = args
|
||||
console.log("Opening diff view:", { originalUri, modifiedUri, title })
|
||||
|
||||
// For standalone mode, just open the modified file directly
|
||||
// since we can't show a proper diff view
|
||||
const editor = await vscode.window.showTextDocument(modifiedUri, {
|
||||
preserveFocus: options?.preserveFocus || false,
|
||||
preview: false,
|
||||
})
|
||||
|
||||
// Ensure the onDidChangeActiveTextEditor event fires with a slight delay
|
||||
// This is crucial for DiffViewProvider.openDiffEditor() to work properly
|
||||
setTimeout(() => {
|
||||
if (vscode.window._activeTextEditorListeners) {
|
||||
vscode.window._activeTextEditorListeners.forEach((listener) => {
|
||||
try {
|
||||
listener(editor)
|
||||
} catch (error) {
|
||||
console.error("Error calling onDidChangeActiveTextEditor listener in vscode.diff:", error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 50) // Slightly longer delay to ensure proper event ordering
|
||||
|
||||
return editor
|
||||
}
|
||||
|
||||
// For other commands, just return a resolved promise
|
||||
return Promise.resolve()
|
||||
},
|
||||
registerCommand: (command, callback) => {
|
||||
console.log(`Registered command: ${command}`)
|
||||
return { dispose: () => {} }
|
||||
},
|
||||
getCommands: async () => {
|
||||
return []
|
||||
},
|
||||
})
|
||||
|
||||
// Add missing TabInput classes
|
||||
vscode.TabInputText = class TabInputText {
|
||||
constructor(uri) {
|
||||
this.uri = uri
|
||||
}
|
||||
}
|
||||
|
||||
vscode.TabInputTextDiff = class TabInputTextDiff {
|
||||
constructor(original, modified) {
|
||||
this.original = original
|
||||
this.modified = modified
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing WorkspaceEdit and related classes
|
||||
vscode.WorkspaceEdit = class WorkspaceEdit {
|
||||
constructor() {
|
||||
this._edits = []
|
||||
}
|
||||
|
||||
replace(uri, range, newText) {
|
||||
console.log("WorkspaceEdit.replace:", uri, range, newText)
|
||||
this._edits.push({
|
||||
_type: 1, // TextEdit
|
||||
_uri: uri,
|
||||
_edits: [
|
||||
{
|
||||
range: range,
|
||||
newText: newText,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
insert(uri, position, newText) {
|
||||
console.log("WorkspaceEdit.insert:", uri, position, newText)
|
||||
this.replace(uri, new vscode.Range(position, position), newText)
|
||||
}
|
||||
|
||||
delete(uri, range) {
|
||||
console.log("WorkspaceEdit.delete:", uri, range)
|
||||
this.replace(uri, range, "")
|
||||
}
|
||||
}
|
||||
|
||||
vscode.Range = class Range {
|
||||
constructor(startLine, startCharacter, endLine, endCharacter) {
|
||||
if (typeof startLine === "object") {
|
||||
// Called with Position objects
|
||||
this.start = startLine
|
||||
this.end = startCharacter
|
||||
} else {
|
||||
// Called with line/character numbers
|
||||
this.start = new vscode.Position(startLine, startCharacter)
|
||||
this.end = new vscode.Position(endLine, endCharacter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vscode.Position = class Position {
|
||||
constructor(line, character) {
|
||||
this.line = line
|
||||
this.character = character
|
||||
}
|
||||
}
|
||||
|
||||
vscode.Selection = class Selection extends vscode.Range {
|
||||
constructor(anchorLine, anchorCharacter, activeLine, activeCharacter) {
|
||||
if (typeof anchorLine === "object") {
|
||||
// Called with Position objects
|
||||
super(anchorLine, anchorCharacter)
|
||||
this.anchor = anchorLine
|
||||
this.active = anchorCharacter
|
||||
} else {
|
||||
// Called with line/character numbers
|
||||
super(anchorLine, anchorCharacter, activeLine, activeCharacter)
|
||||
this.anchor = new vscode.Position(anchorLine, anchorCharacter)
|
||||
this.active = new vscode.Position(activeLine, activeCharacter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add TextEditorRevealType enum
|
||||
vscode.TextEditorRevealType = {
|
||||
Default: 0,
|
||||
InCenter: 1,
|
||||
InCenterIfOutsideViewport: 2,
|
||||
AtTop: 3,
|
||||
}
|
||||
|
||||
// Add missing languages API
|
||||
if (!vscode.languages) {
|
||||
vscode.languages = {}
|
||||
}
|
||||
|
||||
Object.assign(vscode.languages, {
|
||||
getDiagnostics: (uri) => {
|
||||
console.log("Called vscode.languages.getDiagnostics")
|
||||
// Return empty diagnostics for standalone mode
|
||||
if (uri) {
|
||||
return []
|
||||
} else {
|
||||
// Return all diagnostics as empty array
|
||||
return []
|
||||
}
|
||||
},
|
||||
registerCodeActionsProvider: () => ({ dispose: () => {} }),
|
||||
createDiagnosticCollection: () => ({
|
||||
set: () => {},
|
||||
delete: () => {},
|
||||
clear: () => {},
|
||||
dispose: () => {},
|
||||
}),
|
||||
})
|
||||
|
||||
// Export the terminal manager globally for Cline core to use
|
||||
global.standaloneTerminalManager = globalTerminalManager
|
||||
|
||||
// Override the TerminalManager to use our standalone implementation
|
||||
if (typeof global !== "undefined") {
|
||||
// Replace the TerminalManager class with our standalone implementation
|
||||
global.StandaloneTerminalManagerClass = require("./enhanced-terminal").StandaloneTerminalManager
|
||||
}
|
||||
|
||||
module.exports = vscode
|
||||
|
||||
console.log("Finished loading stub impls...")
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022", "DOM"],
|
||||
"lib": ["es2022", "esnext.disposable", "DOM"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "Bundler",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
import deepEqual from "fast-deep-equal"
|
||||
import React, { memo, MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import { useSize } from "react-use"
|
||||
|
||||
import CreditLimitError from "@/components/chat/CreditLimitError"
|
||||
import { OptionsButtons } from "@/components/chat/OptionsButtons"
|
||||
import TaskFeedbackButtons from "@/components/chat/TaskFeedbackButtons"
|
||||
import { CheckmarkControl } from "@/components/common/CheckmarkControl"
|
||||
@@ -35,8 +36,8 @@ import NewTaskPreview from "./NewTaskPreview"
|
||||
import ReportBugPreview from "./ReportBugPreview"
|
||||
import UserMessage from "./UserMessage"
|
||||
import QuoteButton from "./QuoteButton"
|
||||
import ErrorRow from "./ErrorRow"
|
||||
import { ErrorBlockTitle } from "./ErrorBlockTitle"
|
||||
import { useClineAuth } from "@/context/ClineAuthContext"
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@shared/ClineAccount"
|
||||
|
||||
const normalColor = "var(--vscode-foreground)"
|
||||
const errorColor = "var(--vscode-errorForeground)"
|
||||
@@ -103,6 +104,42 @@ const Markdown = memo(({ markdown }: { markdown?: string }) => {
|
||||
)
|
||||
})
|
||||
|
||||
const RetryMessage = memo(
|
||||
({ seconds, attempt, retryOperations }: { retryOperations: number; attempt: number; seconds?: number }) => {
|
||||
const [remainingSeconds, setRemainingSeconds] = useState(seconds || 0)
|
||||
|
||||
useEffect(() => {
|
||||
if (seconds && seconds > 0) {
|
||||
setRemainingSeconds(seconds)
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setRemainingSeconds((prev) => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(interval)
|
||||
return 0
|
||||
}
|
||||
return prev - 1
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [seconds])
|
||||
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
color: normalColor,
|
||||
fontWeight: "bold",
|
||||
}}>
|
||||
{`API Request (Retrying failed attempt ${attempt}/${retryOperations}`}
|
||||
{remainingSeconds > 0 && ` in ${remainingSeconds} seconds`}
|
||||
)...
|
||||
</span>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const ChatRow = memo(
|
||||
(props: ChatRowProps) => {
|
||||
const { isLast, onHeightChange, message, lastModifiedMessage, inputValue } = props
|
||||
@@ -149,6 +186,7 @@ export const ChatRowContent = memo(
|
||||
sendMessageFromChatRow,
|
||||
onSetQuote,
|
||||
}: ChatRowContentProps) => {
|
||||
const { handleSignIn, clineUser } = useClineAuth()
|
||||
const { mcpServers, mcpMarketplaceCatalog, onRelinquishControl, apiConfiguration } = useExtensionState()
|
||||
const [seeNewChangesDisabled, setSeeNewChangesDisabled] = useState(false)
|
||||
const [quoteButtonState, setQuoteButtonState] = useState<QuoteButtonState>({
|
||||
@@ -163,7 +201,7 @@ export const ChatRowContent = memo(
|
||||
const info: ClineApiReqInfo = JSON.parse(message.text)
|
||||
return [info.cost, info.cancelReason, info.streamingFailedMessage, info.retryStatus]
|
||||
}
|
||||
return [undefined, undefined, undefined, undefined, undefined]
|
||||
return [undefined, undefined, undefined, undefined]
|
||||
}, [message.text, message.say])
|
||||
|
||||
// when resuming task last won't be api_req_failed but a resume_task message so api_req_started will show loading spinner. that's why we just remove the last api_req_started that failed without streaming anything
|
||||
@@ -343,12 +381,73 @@ export const ChatRowContent = memo(
|
||||
<span style={{ color: successColor, fontWeight: "bold" }}>Task Completed</span>,
|
||||
]
|
||||
case "api_req_started":
|
||||
return ErrorBlockTitle({
|
||||
cost,
|
||||
apiReqCancelReason,
|
||||
apiRequestFailedMessage,
|
||||
retryStatus,
|
||||
})
|
||||
const getIconSpan = (iconName: string, color: string) => (
|
||||
<div
|
||||
style={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<span
|
||||
className={`codicon codicon-${iconName}`}
|
||||
style={{
|
||||
color,
|
||||
fontSize: 16,
|
||||
marginBottom: "-1.5px",
|
||||
}}></span>
|
||||
</div>
|
||||
)
|
||||
return [
|
||||
apiReqCancelReason != null ? (
|
||||
apiReqCancelReason === "user_cancelled" ? (
|
||||
getIconSpan("error", cancelledColor)
|
||||
) : (
|
||||
getIconSpan("error", errorColor)
|
||||
)
|
||||
) : cost != null ? (
|
||||
getIconSpan("check", successColor)
|
||||
) : apiRequestFailedMessage ? (
|
||||
getIconSpan("error", errorColor)
|
||||
) : (
|
||||
<ProgressIndicator />
|
||||
),
|
||||
(() => {
|
||||
if (apiReqCancelReason != null) {
|
||||
return apiReqCancelReason === "user_cancelled" ? (
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request Cancelled</span>
|
||||
) : (
|
||||
<span style={{ color: errorColor, fontWeight: "bold" }}>API Streaming Failed</span>
|
||||
)
|
||||
}
|
||||
|
||||
if (cost != null) {
|
||||
return <span style={{ color: normalColor, fontWeight: "bold" }}>API Request</span>
|
||||
}
|
||||
|
||||
if (apiRequestFailedMessage) {
|
||||
const errorData = parseErrorText(apiRequestFailedMessage)
|
||||
if (errorData?.code === "insufficient_credits") {
|
||||
return <span style={{ color: errorColor, fontWeight: "bold" }}>Credit Limit Reached</span>
|
||||
}
|
||||
return <span style={{ color: errorColor, fontWeight: "bold" }}>API Request Failed</span>
|
||||
}
|
||||
// New: Check for retryStatus to modify the title
|
||||
if (retryStatus && cost == null && !apiReqCancelReason) {
|
||||
const retryOperations = retryStatus.maxAttempts > 0 ? retryStatus.maxAttempts - 1 : 0
|
||||
return (
|
||||
<RetryMessage
|
||||
seconds={retryStatus.delaySec}
|
||||
attempt={retryStatus.attempt}
|
||||
retryOperations={retryOperations}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return <span style={{ color: normalColor, fontWeight: "bold" }}>API Request...</span>
|
||||
})(),
|
||||
]
|
||||
case "followup":
|
||||
return [
|
||||
<span
|
||||
@@ -847,12 +946,92 @@ export const ChatRowContent = memo(
|
||||
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
||||
</div>
|
||||
{((cost == null && apiRequestFailedMessage) || apiReqStreamingFailedMessage) && (
|
||||
<ErrorRow
|
||||
message={message}
|
||||
errorType="error"
|
||||
apiRequestFailedMessage={apiRequestFailedMessage}
|
||||
apiReqStreamingFailedMessage={apiReqStreamingFailedMessage}
|
||||
/>
|
||||
<>
|
||||
{(() => {
|
||||
// Try to parse the error message as JSON for credit limit error
|
||||
const errorData = parseErrorText(
|
||||
apiRequestFailedMessage || apiReqStreamingFailedMessage,
|
||||
)
|
||||
if (errorData) {
|
||||
if (
|
||||
errorData.code === "insufficient_credits" &&
|
||||
typeof errorData.current_balance === "number"
|
||||
) {
|
||||
return (
|
||||
<CreditLimitError
|
||||
currentBalance={errorData.current_balance}
|
||||
totalSpent={errorData.total_spent}
|
||||
totalPromotions={errorData.total_promotions}
|
||||
message={errorData.message}
|
||||
buyCreditsUrl={errorData.buy_credits_url}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for rate limit errors (status code 429)
|
||||
const isRateLimitError =
|
||||
apiRequestFailedMessage?.includes("status code 429") ||
|
||||
apiRequestFailedMessage?.toLowerCase().includes("rate limit") ||
|
||||
apiRequestFailedMessage?.toLowerCase().includes("too many requests") ||
|
||||
apiRequestFailedMessage?.toLowerCase().includes("quota exceeded") ||
|
||||
apiRequestFailedMessage?.toLowerCase().includes("resource exhausted")
|
||||
|
||||
if (isRateLimitError) {
|
||||
return (
|
||||
<p
|
||||
style={{
|
||||
...pStyle,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{apiRequestFailedMessage || apiReqStreamingFailedMessage}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
// Default error display
|
||||
return (
|
||||
<p
|
||||
style={{
|
||||
...pStyle,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{apiRequestFailedMessage || apiReqStreamingFailedMessage}
|
||||
{apiRequestFailedMessage?.toLowerCase().includes("powershell") && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
It seems like you're having Windows PowerShell issues, please see this{" "}
|
||||
<a
|
||||
href="https://github.com/cline/cline/wiki/TroubleShooting-%E2%80%90-%22PowerShell-is-not-recognized-as-an-internal-or-external-command%22"
|
||||
style={{
|
||||
color: "inherit",
|
||||
textDecoration: "underline",
|
||||
}}>
|
||||
troubleshooting guide
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
{apiRequestFailedMessage?.includes(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE) && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
{clineUser ? (
|
||||
<span style={{ color: "var(--vscode-descriptionForeground)" }}>
|
||||
(Click "Retry" below)
|
||||
</span>
|
||||
) : (
|
||||
<VSCodeButton onClick={handleSignIn} className="w-full mb-4">
|
||||
Sign in to Cline
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
})()}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isExpanded && (
|
||||
@@ -1004,11 +1183,97 @@ export const ChatRowContent = memo(
|
||||
</div>
|
||||
)
|
||||
case "error":
|
||||
return <ErrorRow message={message} errorType="error" />
|
||||
return (
|
||||
<>
|
||||
{title && (
|
||||
<div style={headerStyle}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<p
|
||||
style={{
|
||||
...pStyle,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{message.text}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
case "diff_error":
|
||||
return <ErrorRow message={message} errorType="diff_error" />
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "var(--vscode-textBlockQuote-background)",
|
||||
padding: 8,
|
||||
borderRadius: 3,
|
||||
fontSize: 12,
|
||||
color: "var(--vscode-foreground)",
|
||||
opacity: 0.8,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: 4,
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-warning"
|
||||
style={{
|
||||
marginRight: 8,
|
||||
fontSize: 14,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}></i>
|
||||
<span style={{ fontWeight: 500 }}>Diff Edit Mismatch</span>
|
||||
</div>
|
||||
<div>The model used search patterns that don't match anything in the file. Retrying...</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
case "clineignore_error":
|
||||
return <ErrorRow message={message} errorType="clineignore_error" />
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "rgba(255, 191, 0, 0.1)",
|
||||
padding: 8,
|
||||
borderRadius: 3,
|
||||
fontSize: 12,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: 4,
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-error"
|
||||
style={{
|
||||
marginRight: 8,
|
||||
fontSize: 18,
|
||||
color: "#FFA500",
|
||||
}}></i>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: "#FFA500",
|
||||
}}>
|
||||
Access Denied
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Cline tried to access <code>{message.text}</code> which is blocked by the{" "}
|
||||
<code>.clineignore</code>
|
||||
file.
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
case "checkpoint_created":
|
||||
return (
|
||||
<>
|
||||
@@ -1168,9 +1433,37 @@ export const ChatRowContent = memo(
|
||||
case "ask":
|
||||
switch (message.ask) {
|
||||
case "mistake_limit_reached":
|
||||
return <ErrorRow message={message} errorType="mistake_limit_reached" />
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
...pStyle,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{message.text}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
case "auto_approval_max_req_reached":
|
||||
return <ErrorRow message={message} errorType="auto_approval_max_req_reached" />
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
...pStyle,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{message.text}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
case "completion_result":
|
||||
if (message.text) {
|
||||
const hasChanges = message.text.endsWith(COMPLETION_RESULT_CHANGES_FLAG) ?? false
|
||||
@@ -1393,3 +1686,20 @@ export const ChatRowContent = memo(
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function parseErrorText(text: string | undefined) {
|
||||
if (!text) {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
const startIndex = text.indexOf("{")
|
||||
const endIndex = text.lastIndexOf("}")
|
||||
if (startIndex !== -1 && endIndex !== -1) {
|
||||
const jsonStr = text.substring(startIndex, endIndex + 1)
|
||||
const errorObject = JSON.parse(jsonStr)
|
||||
return errorObject
|
||||
}
|
||||
} catch (e) {
|
||||
// Not JSON or missing required fields
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, getModeSpecificFields } from "@/components/settings/utils/providerUtils"
|
||||
import { normalizeApiConfiguration } 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(chatSettings.mode, apiConfiguration)
|
||||
const modelIdValidationResult = validateModelId(chatSettings.mode, apiConfiguration, openRouterModels)
|
||||
const apiValidationResult = validateApiConfiguration(apiConfiguration)
|
||||
const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels)
|
||||
|
||||
if (!apiValidationResult && !modelIdValidationResult && apiConfiguration) {
|
||||
try {
|
||||
@@ -1089,16 +1089,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
|
||||
// Get model display name
|
||||
const modelDisplayName = useMemo(() => {
|
||||
const { selectedProvider, selectedModelId } = normalizeApiConfiguration(apiConfiguration, chatSettings.mode)
|
||||
const {
|
||||
vsCodeLmModelSelector,
|
||||
togetherModelId,
|
||||
fireworksModelId,
|
||||
lmStudioModelId,
|
||||
ollamaModelId,
|
||||
liteLlmModelId,
|
||||
requestyModelId,
|
||||
} = getModeSpecificFields(apiConfiguration, chatSettings.mode)
|
||||
const { selectedProvider, selectedModelId } = normalizeApiConfiguration(apiConfiguration)
|
||||
const unknownModel = "unknown"
|
||||
if (!apiConfiguration) return unknownModel
|
||||
switch (selectedProvider) {
|
||||
@@ -1107,25 +1098,25 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
case "openai":
|
||||
return `openai-compat:${selectedModelId}`
|
||||
case "vscode-lm":
|
||||
return `vscode-lm:${vsCodeLmModelSelector ? `${vsCodeLmModelSelector.vendor ?? ""}/${vsCodeLmModelSelector.family ?? ""}` : unknownModel}`
|
||||
return `vscode-lm:${apiConfiguration.vsCodeLmModelSelector ? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}` : unknownModel}`
|
||||
case "together":
|
||||
return `${selectedProvider}:${togetherModelId}`
|
||||
return `${selectedProvider}:${apiConfiguration.togetherModelId}`
|
||||
case "fireworks":
|
||||
return `fireworks:${fireworksModelId}`
|
||||
return `fireworks:${apiConfiguration.fireworksModelId}`
|
||||
case "lmstudio":
|
||||
return `${selectedProvider}:${lmStudioModelId}`
|
||||
return `${selectedProvider}:${apiConfiguration.lmStudioModelId}`
|
||||
case "ollama":
|
||||
return `${selectedProvider}:${ollamaModelId}`
|
||||
return `${selectedProvider}:${apiConfiguration.ollamaModelId}`
|
||||
case "litellm":
|
||||
return `${selectedProvider}:${liteLlmModelId}`
|
||||
return `${selectedProvider}:${apiConfiguration.liteLlmModelId}`
|
||||
case "requesty":
|
||||
return `${selectedProvider}:${requestyModelId}`
|
||||
return `${selectedProvider}:${apiConfiguration.requestyModelId}`
|
||||
case "anthropic":
|
||||
case "openrouter":
|
||||
default:
|
||||
return `${selectedProvider}:${selectedModelId}`
|
||||
}
|
||||
}, [apiConfiguration, chatSettings.mode])
|
||||
}, [apiConfiguration])
|
||||
|
||||
// Calculate arrow position and menu position based on button location
|
||||
useEffect(() => {
|
||||
@@ -1729,13 +1720,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
style={{
|
||||
bottom: `calc(100vh - ${menuPosition}px + 6px)`,
|
||||
}}>
|
||||
<ApiOptions
|
||||
showModelOptions={true}
|
||||
apiErrorMessage={undefined}
|
||||
modelIdErrorMessage={undefined}
|
||||
isPopup={true}
|
||||
currentMode={chatSettings.mode}
|
||||
/>
|
||||
<ApiOptions showModelOptions={true} modelIdErrorMessage={undefined} isPopup={true} />
|
||||
</ModelSelectorTooltip>
|
||||
)}
|
||||
</ModelContainer>
|
||||
|
||||
@@ -47,7 +47,6 @@ 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
|
||||
@@ -200,8 +199,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
messageHandlers
|
||||
|
||||
const { selectedModelInfo } = useMemo(() => {
|
||||
return normalizeApiConfiguration(apiConfiguration, chatSettings.mode)
|
||||
}, [apiConfiguration, chatSettings.mode])
|
||||
return normalizeApiConfiguration(apiConfiguration)
|
||||
}, [apiConfiguration])
|
||||
|
||||
const selectFilesAndImages = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import React from "react"
|
||||
import { ClineError, ClineErrorType } from "../../../../src/services/error/ClineError"
|
||||
import { ProgressIndicator } from "./ChatRow"
|
||||
|
||||
const RetryMessage = React.memo(
|
||||
({ seconds, attempt, retryOperations }: { retryOperations: number; attempt: number; seconds?: number }) => {
|
||||
const [remainingSeconds, setRemainingSeconds] = React.useState(seconds || 0)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (seconds && seconds > 0) {
|
||||
setRemainingSeconds(seconds)
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setRemainingSeconds((prev) => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(interval)
|
||||
return 0
|
||||
}
|
||||
return prev - 1
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [seconds])
|
||||
|
||||
return (
|
||||
<span className="font-bold text-[var(--vscode-foreground)]">
|
||||
{`API Request (Retrying failed attempt ${attempt}/${retryOperations}`}
|
||||
{remainingSeconds > 0 && ` in ${remainingSeconds} seconds`}
|
||||
)...
|
||||
</span>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
interface ErrorBlockTitleProps {
|
||||
cost?: number
|
||||
apiReqCancelReason?: string
|
||||
apiRequestFailedMessage?: string
|
||||
retryStatus?: {
|
||||
attempt: number
|
||||
maxAttempts: number
|
||||
delaySec?: number
|
||||
errorSnippet?: string
|
||||
}
|
||||
}
|
||||
|
||||
export const ErrorBlockTitle = ({
|
||||
cost,
|
||||
apiReqCancelReason,
|
||||
apiRequestFailedMessage,
|
||||
retryStatus,
|
||||
}: ErrorBlockTitleProps): [React.ReactElement, React.ReactElement] => {
|
||||
const getIconSpan = (iconName: string, colorClass: string) => (
|
||||
<div className="w-4 h-4 flex items-center justify-center">
|
||||
<span className={`codicon codicon-${iconName} text-base -mb-0.5 ${colorClass}`}></span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const icon =
|
||||
apiReqCancelReason != null ? (
|
||||
apiReqCancelReason === "user_cancelled" ? (
|
||||
getIconSpan("error", "text-[var(--vscode-descriptionForeground)]")
|
||||
) : (
|
||||
getIconSpan("error", "text-[var(--vscode-errorForeground)]")
|
||||
)
|
||||
) : cost != null ? (
|
||||
getIconSpan("check", "text-[var(--vscode-charts-green)]")
|
||||
) : apiRequestFailedMessage ? (
|
||||
getIconSpan("error", "text-[var(--vscode-errorForeground)]")
|
||||
) : (
|
||||
<ProgressIndicator />
|
||||
)
|
||||
|
||||
const title = (() => {
|
||||
// Default loading state
|
||||
const details = { title: "API Request...", classNames: ["font-bold"] }
|
||||
// Handle cancellation states first
|
||||
if (apiReqCancelReason === "user_cancelled") {
|
||||
details.title = "API Request Cancelled"
|
||||
details.classNames.push("text-[var(--vscode-foreground)]")
|
||||
} else if (apiReqCancelReason != null) {
|
||||
details.title = "API Streaming Failed"
|
||||
details.classNames.push("text-[var(--vscode-errorForeground)]")
|
||||
} else if (cost != null) {
|
||||
// Handle completed request
|
||||
details.title = "API Request"
|
||||
details.classNames.push("text-[var(--vscode-foreground)]")
|
||||
} else if (apiRequestFailedMessage) {
|
||||
// Handle failed request
|
||||
const clineError = ClineError.parse(apiRequestFailedMessage)
|
||||
const titleText = clineError?.isErrorType(ClineErrorType.Balance) ? "Credit Limit Reached" : "API Request Failed"
|
||||
details.title = titleText
|
||||
details.classNames.push("font-bold text-[var(--vscode-errorForeground)]")
|
||||
} else if (retryStatus) {
|
||||
// Handle retry state
|
||||
const retryOperations = Math.max(0, retryStatus.maxAttempts - 1)
|
||||
return <RetryMessage seconds={retryStatus.delaySec} attempt={retryStatus.attempt} retryOperations={retryOperations} />
|
||||
}
|
||||
|
||||
return <span className={details.classNames.join(" ")}>{details.title}</span>
|
||||
})()
|
||||
|
||||
return [icon, title]
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
import { render, screen } from "@testing-library/react"
|
||||
import { describe, it, expect, vi } from "vitest"
|
||||
import ErrorRow from "./ErrorRow"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
|
||||
// Mock the auth context
|
||||
const mockHandleSignIn = vi.fn()
|
||||
vi.mock("@/context/ClineAuthContext", () => ({
|
||||
useClineAuth: () => ({
|
||||
handleSignIn: mockHandleSignIn,
|
||||
clineUser: null,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock CreditLimitError component
|
||||
vi.mock("@/components/chat/CreditLimitError", () => ({
|
||||
default: ({ message }: { message: string }) => <div data-testid="credit-limit-error">{message}</div>,
|
||||
}))
|
||||
|
||||
// Mock ClineError
|
||||
vi.mock("../../../../src/services/error/ClineError", () => ({
|
||||
ClineError: {
|
||||
parse: vi.fn(),
|
||||
},
|
||||
ClineErrorType: {
|
||||
Balance: "balance",
|
||||
RateLimit: "rateLimit",
|
||||
Auth: "auth",
|
||||
},
|
||||
}))
|
||||
|
||||
describe("ErrorRow", () => {
|
||||
const mockMessage: ClineMessage = {
|
||||
ts: 123456789,
|
||||
type: "say",
|
||||
say: "error",
|
||||
text: "Test error message",
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it("renders basic error message", () => {
|
||||
render(<ErrorRow message={mockMessage} errorType="error" />)
|
||||
|
||||
expect(screen.getByText("Test error message")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders mistake limit reached error", () => {
|
||||
const mistakeMessage = { ...mockMessage, text: "Mistake limit reached" }
|
||||
render(<ErrorRow message={mistakeMessage} errorType="mistake_limit_reached" />)
|
||||
|
||||
expect(screen.getByText("Mistake limit reached")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders auto approval max requests error", () => {
|
||||
const maxReqMessage = { ...mockMessage, text: "Max requests reached" }
|
||||
render(<ErrorRow message={maxReqMessage} errorType="auto_approval_max_req_reached" />)
|
||||
|
||||
expect(screen.getByText("Max requests reached")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders diff error", () => {
|
||||
render(<ErrorRow message={mockMessage} errorType="diff_error" />)
|
||||
|
||||
expect(
|
||||
screen.getByText("The model used search patterns that don't match anything in the file. Retrying..."),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders clineignore error", () => {
|
||||
const clineignoreMessage = { ...mockMessage, text: "/path/to/file.txt" }
|
||||
render(<ErrorRow message={clineignoreMessage} errorType="clineignore_error" />)
|
||||
|
||||
expect(screen.getByText(/Cline tried to access/)).toBeInTheDocument()
|
||||
expect(screen.getByText("/path/to/file.txt")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
describe("API error handling", () => {
|
||||
it("renders credit limit error when balance error is detected", async () => {
|
||||
const mockClineError = {
|
||||
message: "Insufficient credits",
|
||||
isErrorType: vi.fn((type) => type === "balance"),
|
||||
_error: {
|
||||
details: {
|
||||
current_balance: 0,
|
||||
total_spent: 10.5,
|
||||
total_promotions: 5.0,
|
||||
message: "You have run out of credit.",
|
||||
buy_credits_url: "https://app.cline.bot/dashboard",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const { ClineError } = await import("../../../../src/services/error/ClineError")
|
||||
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
|
||||
|
||||
render(<ErrorRow message={mockMessage} errorType="error" apiRequestFailedMessage="Insufficient credits error" />)
|
||||
|
||||
expect(screen.getByTestId("credit-limit-error")).toBeInTheDocument()
|
||||
expect(screen.getByText("You have run out of credit.")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders rate limit error with request ID", async () => {
|
||||
const mockClineError = {
|
||||
message: "Rate limit exceeded",
|
||||
isErrorType: vi.fn((type) => type === "rateLimit"),
|
||||
_error: {
|
||||
request_id: "req_123456",
|
||||
},
|
||||
}
|
||||
|
||||
const { ClineError } = await import("../../../../src/services/error/ClineError")
|
||||
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
|
||||
|
||||
render(<ErrorRow message={mockMessage} errorType="error" apiRequestFailedMessage="Rate limit exceeded" />)
|
||||
|
||||
expect(screen.getByText("Rate limit exceeded")).toBeInTheDocument()
|
||||
expect(screen.getByText("Request ID: req_123456")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders auth error with sign in button when user is not signed in", async () => {
|
||||
const mockClineError = {
|
||||
message: "Authentication failed",
|
||||
isErrorType: vi.fn((type) => type === "auth"),
|
||||
providerId: "cline",
|
||||
_error: {},
|
||||
}
|
||||
|
||||
const { ClineError } = await import("../../../../src/services/error/ClineError")
|
||||
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
|
||||
|
||||
render(<ErrorRow message={mockMessage} errorType="error" apiRequestFailedMessage="Authentication failed" />)
|
||||
|
||||
expect(screen.getByText("Authentication failed")).toBeInTheDocument()
|
||||
expect(screen.getByText("Sign in to Cline")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders PowerShell troubleshooting link when error mentions PowerShell", async () => {
|
||||
const mockClineError = {
|
||||
message: "PowerShell is not recognized as an internal or external command",
|
||||
isErrorType: vi.fn(() => false),
|
||||
_error: {},
|
||||
}
|
||||
|
||||
const { ClineError } = await import("../../../../src/services/error/ClineError")
|
||||
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
|
||||
|
||||
render(
|
||||
<ErrorRow
|
||||
message={mockMessage}
|
||||
errorType="error"
|
||||
apiRequestFailedMessage="PowerShell is not recognized as an internal or external command"
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/PowerShell is not recognized/)).toBeInTheDocument()
|
||||
expect(screen.getByText("troubleshooting guide")).toBeInTheDocument()
|
||||
expect(screen.getByRole("link", { name: "troubleshooting guide" })).toHaveAttribute(
|
||||
"href",
|
||||
"https://github.com/cline/cline/wiki/TroubleShooting-%E2%80%90-%22PowerShell-is-not-recognized-as-an-internal-or-external-command%22",
|
||||
)
|
||||
})
|
||||
|
||||
it("handles apiReqStreamingFailedMessage instead of apiRequestFailedMessage", async () => {
|
||||
const mockClineError = {
|
||||
message: "Streaming failed",
|
||||
isErrorType: vi.fn(() => false),
|
||||
_error: {},
|
||||
}
|
||||
|
||||
const { ClineError } = await import("../../../../src/services/error/ClineError")
|
||||
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
|
||||
|
||||
render(<ErrorRow message={mockMessage} errorType="error" apiReqStreamingFailedMessage="Streaming failed" />)
|
||||
|
||||
expect(screen.getByText("Streaming failed")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("falls back to regular error message when ClineError.parse returns null", async () => {
|
||||
const { ClineError } = await import("../../../../src/services/error/ClineError")
|
||||
vi.mocked(ClineError.parse).mockReturnValue(undefined)
|
||||
|
||||
render(<ErrorRow message={mockMessage} errorType="error" apiRequestFailedMessage="Some API error" />)
|
||||
|
||||
// When ClineError.parse returns null, clineErrorMessage is undefined, so it renders an empty paragraph
|
||||
// The fallback to message.text only happens when there's no apiRequestFailedMessage at all
|
||||
const paragraph = screen.getByRole("paragraph")
|
||||
expect(paragraph).toBeInTheDocument()
|
||||
expect(paragraph).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
it("renders regular error message when no API error messages are provided", () => {
|
||||
render(<ErrorRow message={mockMessage} errorType="error" />)
|
||||
|
||||
expect(screen.getByText("Test error message")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,130 +0,0 @@
|
||||
import { memo } from "react"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { ClineError, ClineErrorType } from "../../../../src/services/error/ClineError"
|
||||
import CreditLimitError from "@/components/chat/CreditLimitError"
|
||||
import { useClineAuth } from "@/context/ClineAuthContext"
|
||||
|
||||
const errorColor = "var(--vscode-errorForeground)"
|
||||
|
||||
interface ErrorRowProps {
|
||||
message: ClineMessage
|
||||
errorType: "error" | "mistake_limit_reached" | "auto_approval_max_req_reached" | "diff_error" | "clineignore_error"
|
||||
apiRequestFailedMessage?: string
|
||||
apiReqStreamingFailedMessage?: string
|
||||
}
|
||||
|
||||
const ErrorRow = memo(({ message, errorType, apiRequestFailedMessage, apiReqStreamingFailedMessage }: ErrorRowProps) => {
|
||||
const { handleSignIn, clineUser } = useClineAuth()
|
||||
|
||||
const renderErrorContent = () => {
|
||||
switch (errorType) {
|
||||
case "error":
|
||||
case "mistake_limit_reached":
|
||||
case "auto_approval_max_req_reached":
|
||||
// Handle API request errors with special error parsing
|
||||
if (apiRequestFailedMessage || apiReqStreamingFailedMessage) {
|
||||
const clineError = ClineError.parse(apiRequestFailedMessage || apiReqStreamingFailedMessage)
|
||||
const clineErrorMessage = clineError?.message
|
||||
const requestId = clineError?._error?.request_id
|
||||
const isClineProvider = clineError?.providerId === "cline"
|
||||
|
||||
if (clineError) {
|
||||
if (clineError.isErrorType(ClineErrorType.Balance)) {
|
||||
const errorDetails = clineError._error?.details
|
||||
return (
|
||||
<CreditLimitError
|
||||
currentBalance={errorDetails?.current_balance}
|
||||
totalSpent={errorDetails?.total_spent}
|
||||
totalPromotions={errorDetails?.total_promotions}
|
||||
message={errorDetails?.message}
|
||||
buyCreditsUrl={errorDetails?.buy_credits_url}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (clineError?.isErrorType(ClineErrorType.RateLimit)) {
|
||||
return (
|
||||
<p className="m-0 whitespace-pre-wrap text-[var(--vscode-errorForeground)] wrap-anywhere">
|
||||
{clineErrorMessage}
|
||||
{requestId && <div>Request ID: {requestId}</div>}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
// Default error display
|
||||
return (
|
||||
<p className="m-0 whitespace-pre-wrap text-[var(--vscode-errorForeground)] wrap-anywhere">
|
||||
{clineErrorMessage}
|
||||
{requestId && <div>Request ID: {requestId}</div>}
|
||||
{clineErrorMessage?.toLowerCase()?.includes("powershell") && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
It seems like you're having Windows PowerShell issues, please see this{" "}
|
||||
<a
|
||||
href="https://github.com/cline/cline/wiki/TroubleShooting-%E2%80%90-%22PowerShell-is-not-recognized-as-an-internal-or-external-command%22"
|
||||
className="underline text-inherit">
|
||||
troubleshooting guide
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
{clineError?.isErrorType(ClineErrorType.Auth) && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
{/* The user is signed in or not using cline provider */}
|
||||
{clineUser && !isClineProvider ? (
|
||||
<span className="mb-4 text-[var(--vscode-descriptionForeground)]">
|
||||
(Click "Retry" below)
|
||||
</span>
|
||||
) : (
|
||||
<VSCodeButton onClick={handleSignIn} className="w-full mb-4">
|
||||
Sign in to Cline
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
// Regular error message
|
||||
return (
|
||||
<p className="m-0 whitespace-pre-wrap text-[var(--vscode-errorForeground)] wrap-anywhere">{message.text}</p>
|
||||
)
|
||||
|
||||
case "diff_error":
|
||||
return (
|
||||
<div className="flex flex-col p-2 rounded text-xs opacity-80 bg-[var(--vscode-textBlockQuote-background)] text-[var(--vscode-foreground)]">
|
||||
<div>The model used search patterns that don't match anything in the file. Retrying...</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
case "clineignore_error":
|
||||
return (
|
||||
<div className="flex flex-col p-2 rounded text-xs bg-[var(--vscode-textBlockQuote-background)] text-[var(--vscode-foreground)] opacity-80">
|
||||
<div>
|
||||
Cline tried to access <code>{message.text}</code> which is blocked by the <code>.clineignore</code>
|
||||
file.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// For diff_error and clineignore_error, we don't show the header separately
|
||||
if (errorType === "diff_error" || errorType === "clineignore_error") {
|
||||
return <>{renderErrorContent()}</>
|
||||
}
|
||||
|
||||
// For other error types, show header + content
|
||||
return <>{renderErrorContent()}</>
|
||||
})
|
||||
|
||||
export default ErrorRow
|
||||
@@ -1,51 +0,0 @@
|
||||
import { describe, it, expect } from "vitest"
|
||||
import { ErrorBlockTitle } from "../ErrorBlockTitle"
|
||||
|
||||
describe("ErrorBlockTitle", () => {
|
||||
it("should return icon and title for API request cancelled", () => {
|
||||
const [icon, title] = ErrorBlockTitle({
|
||||
apiReqCancelReason: "user_cancelled",
|
||||
})
|
||||
|
||||
expect(icon).toBeDefined()
|
||||
expect(title).toBeDefined()
|
||||
})
|
||||
|
||||
it("should return icon and title for completed API request", () => {
|
||||
const [icon, title] = ErrorBlockTitle({
|
||||
cost: 0.001,
|
||||
})
|
||||
|
||||
expect(icon).toBeDefined()
|
||||
expect(title).toBeDefined()
|
||||
})
|
||||
|
||||
it("should return icon and title for failed API request", () => {
|
||||
const [icon, title] = ErrorBlockTitle({
|
||||
apiRequestFailedMessage: "Request failed",
|
||||
})
|
||||
|
||||
expect(icon).toBeDefined()
|
||||
expect(title).toBeDefined()
|
||||
})
|
||||
|
||||
it("should return icon and title for retry status", () => {
|
||||
const [icon, title] = ErrorBlockTitle({
|
||||
retryStatus: {
|
||||
attempt: 2,
|
||||
maxAttempts: 3,
|
||||
delaySec: 5,
|
||||
},
|
||||
})
|
||||
|
||||
expect(icon).toBeDefined()
|
||||
expect(title).toBeDefined()
|
||||
})
|
||||
|
||||
it("should return icon and title for default API request", () => {
|
||||
const [icon, title] = ErrorBlockTitle({})
|
||||
|
||||
expect(icon).toBeDefined()
|
||||
expect(title).toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import HeroTooltip from "@/components/common/HeroTooltip"
|
||||
import Thumbnails from "@/components/common/Thumbnails"
|
||||
import { normalizeApiConfiguration, getModeSpecificFields } from "@/components/settings/utils/providerUtils"
|
||||
import { normalizeApiConfiguration } 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, chatSettings } =
|
||||
const { apiConfiguration, currentTaskItem, checkpointTrackerErrorMessage, clineMessages, navigateToSettings } =
|
||||
useExtensionState()
|
||||
const [isTaskExpanded, setIsTaskExpanded] = useState(true)
|
||||
const [isTextExpanded, setIsTextExpanded] = useState(false)
|
||||
@@ -51,10 +51,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
const textContainerRef = useRef<HTMLDivElement>(null)
|
||||
const textRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { selectedModelInfo } = useMemo(
|
||||
() => normalizeApiConfiguration(apiConfiguration, chatSettings.mode),
|
||||
[apiConfiguration, chatSettings.mode],
|
||||
)
|
||||
const { selectedModelInfo } = useMemo(() => normalizeApiConfiguration(apiConfiguration), [apiConfiguration])
|
||||
const contextWindow = selectedModelInfo?.contextWindow
|
||||
|
||||
// Open task header when checkpoint tracker error message is set
|
||||
@@ -133,18 +130,19 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
}, [task.text, windowWidth, isTaskExpanded])
|
||||
|
||||
const isCostAvailable = useMemo(() => {
|
||||
const modeFields = getModeSpecificFields(apiConfiguration, chatSettings.mode)
|
||||
const openAiCompatHasPricing =
|
||||
modeFields.apiProvider === "openai" &&
|
||||
modeFields.openAiModelInfo?.inputPrice &&
|
||||
modeFields.openAiModelInfo?.outputPrice
|
||||
apiConfiguration?.apiProvider === "openai" &&
|
||||
apiConfiguration?.openAiModelInfo?.inputPrice &&
|
||||
apiConfiguration?.openAiModelInfo?.outputPrice
|
||||
if (openAiCompatHasPricing) {
|
||||
return true
|
||||
}
|
||||
return (
|
||||
modeFields.apiProvider !== "vscode-lm" && modeFields.apiProvider !== "ollama" && modeFields.apiProvider !== "lmstudio"
|
||||
apiConfiguration?.apiProvider !== "vscode-lm" &&
|
||||
apiConfiguration?.apiProvider !== "ollama" &&
|
||||
apiConfiguration?.apiProvider !== "lmstudio"
|
||||
)
|
||||
}, [apiConfiguration, chatSettings.mode])
|
||||
}, [apiConfiguration?.apiProvider, apiConfiguration?.openAiModelInfo])
|
||||
|
||||
const shouldShowPromptCacheInfo = () => {
|
||||
// Hybrid logic: Show cache info if we have actual cache data,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { ModelsServiceClient } from "@/services/grpc-client"
|
||||
import { StringRequest } from "@shared/proto/common"
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
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 { useCallback, useEffect, useState } from "react"
|
||||
import { useInterval } from "react-use"
|
||||
import styled from "styled-components"
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
|
||||
import { normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
|
||||
|
||||
import { ClineProvider } from "./providers/ClineProvider"
|
||||
import { OpenRouterProvider } from "./providers/OpenRouterProvider"
|
||||
import { MistralProvider } from "./providers/MistralProvider"
|
||||
@@ -30,21 +30,19 @@ import { ClaudeCodeProvider } from "./providers/ClaudeCodeProvider"
|
||||
import { SapAiCoreProvider } from "./providers/SapAiCoreProvider"
|
||||
import { BedrockProvider } from "./providers/BedrockProvider"
|
||||
import { MoonshotProvider } from "./providers/MoonshotProvider"
|
||||
import { HuggingFaceProvider } from "./providers/HuggingFaceProvider"
|
||||
import { NebiusProvider } from "./providers/NebiusProvider"
|
||||
import { LiteLlmProvider } from "./providers/LiteLlmProvider"
|
||||
import { VSCodeLmProvider } from "./providers/VSCodeLmProvider"
|
||||
import { LMStudioProvider } from "./providers/LMStudioProvider"
|
||||
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
|
||||
import { GroqProvider } from "./providers/GroqProvider"
|
||||
import { Mode } from "@shared/ChatSettings"
|
||||
|
||||
interface ApiOptionsProps {
|
||||
showSubmitButton?: boolean
|
||||
showModelOptions: boolean
|
||||
apiErrorMessage?: string
|
||||
modelIdErrorMessage?: string
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
}
|
||||
|
||||
// This is necessary to ensure dropdown opens downward, important for when this is used in popup
|
||||
@@ -71,16 +69,24 @@ declare module "vscode" {
|
||||
}
|
||||
}
|
||||
|
||||
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, isPopup, currentMode }: ApiOptionsProps) => {
|
||||
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, isPopup, showSubmitButton }: ApiOptionsProps) => {
|
||||
// Use full context state for immediate save payload
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
const { apiConfiguration, uriScheme } = useExtensionState()
|
||||
|
||||
const { selectedProvider } = normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
const selectedProvider = apiConfiguration?.apiProvider
|
||||
|
||||
const { handleModeFieldChange } = useApiConfigurationHandlers()
|
||||
const { handleFieldChange } = 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") {
|
||||
@@ -125,11 +131,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
id="api-provider"
|
||||
value={selectedProvider}
|
||||
onChange={(e: any) => {
|
||||
handleModeFieldChange(
|
||||
{ plan: "planModeApiProvider", act: "actModeApiProvider" },
|
||||
e.target.value,
|
||||
currentMode,
|
||||
)
|
||||
handleFieldChange("apiProvider", e.target.value)
|
||||
}}
|
||||
style={{
|
||||
minWidth: 130,
|
||||
@@ -140,13 +142,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">OpenAI Compatible</VSCodeOption>
|
||||
<VSCodeOption value="openai-native">OpenAI</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>
|
||||
@@ -157,7 +159,6 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
<VSCodeOption value="ollama">Ollama</VSCodeOption>
|
||||
<VSCodeOption value="litellm">LiteLLM</VSCodeOption>
|
||||
<VSCodeOption value="moonshot">Moonshot AI</VSCodeOption>
|
||||
<VSCodeOption value="huggingface">Hugging Face</VSCodeOption>
|
||||
<VSCodeOption value="nebius">Nebius AI Studio</VSCodeOption>
|
||||
<VSCodeOption value="asksage">AskSage</VSCodeOption>
|
||||
<VSCodeOption value="xai">xAI</VSCodeOption>
|
||||
@@ -168,116 +169,112 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
</DropdownContainer>
|
||||
|
||||
{apiConfiguration && selectedProvider === "cline" && (
|
||||
<ClineProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<ClineProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "asksage" && (
|
||||
<AskSageProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<AskSageProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "anthropic" && (
|
||||
<AnthropicProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<AnthropicProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "claude-code" && (
|
||||
<ClaudeCodeProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<ClaudeCodeProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "openai-native" && (
|
||||
<OpenAINativeProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<OpenAINativeProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "qwen" && (
|
||||
<QwenProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<QwenProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "doubao" && (
|
||||
<DoubaoProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<DoubaoProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "mistral" && (
|
||||
<MistralProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<MistralProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "openrouter" && (
|
||||
<OpenRouterProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<OpenRouterProvider showModelOptions={showModelOptions} isPopup={isPopup} uriScheme={uriScheme} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "deepseek" && (
|
||||
<DeepSeekProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<DeepSeekProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "together" && (
|
||||
<TogetherProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<TogetherProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "openai" && (
|
||||
<OpenAICompatibleProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<OpenAICompatibleProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "sambanova" && (
|
||||
<SambanovaProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<SambanovaProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "bedrock" && (
|
||||
<BedrockProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<BedrockProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "vertex" && (
|
||||
<VertexProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<VertexProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "gemini" && (
|
||||
<GeminiProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<GeminiProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "requesty" && (
|
||||
<RequestyProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<RequestyProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "fireworks" && (
|
||||
<FireworksProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<FireworksProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "vscode-lm" && <VSCodeLmProvider currentMode={currentMode} />}
|
||||
{apiConfiguration && selectedProvider === "vscode-lm" && <VSCodeLmProvider />}
|
||||
|
||||
{apiConfiguration && selectedProvider === "groq" && (
|
||||
<GroqProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<GroqProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
{apiConfiguration && selectedProvider === "litellm" && (
|
||||
<LiteLlmProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<LiteLlmProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "lmstudio" && (
|
||||
<LMStudioProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<LMStudioProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "ollama" && (
|
||||
<OllamaProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<OllamaProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "moonshot" && (
|
||||
<MoonshotProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "huggingface" && (
|
||||
<HuggingFaceProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<MoonshotProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "nebius" && (
|
||||
<NebiusProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<NebiusProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "xai" && (
|
||||
<XaiProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<XaiProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "cerebras" && (
|
||||
<CerebrasProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<CerebrasProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiConfiguration && selectedProvider === "sapaicore" && (
|
||||
<SapAiCoreProvider showModelOptions={showModelOptions} isPopup={isPopup} currentMode={currentMode} />
|
||||
<SapAiCoreProvider showModelOptions={showModelOptions} isPopup={isPopup} />
|
||||
)}
|
||||
|
||||
{apiErrorMessage && (
|
||||
@@ -300,6 +297,12 @@ 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,19 +12,15 @@ import { highlight } from "../history/HistoryView"
|
||||
import { ModelInfoView } from "./common/ModelInfoView"
|
||||
import { normalizeApiConfiguration } from "./utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
|
||||
import { getModeSpecificFields } from "./utils/providerUtils"
|
||||
import { Mode } from "@shared/ChatSettings"
|
||||
|
||||
export interface GroqModelPickerProps {
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
}
|
||||
|
||||
const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup, currentMode }) => {
|
||||
const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup }) => {
|
||||
const { apiConfiguration, groqModels: dynamicGroqModels, setGroqModels } = useExtensionState()
|
||||
const { handleModeFieldsChange } = useApiConfigurationHandlers()
|
||||
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
|
||||
const [searchTerm, setSearchTerm] = useState(modeFields.groqModelId || groqDefaultModelId)
|
||||
const { handleFieldsChange } = useApiConfigurationHandlers()
|
||||
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.groqModelId || groqDefaultModelId)
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm)
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1)
|
||||
@@ -36,23 +32,16 @@ const GroqModelPicker: React.FC<GroqModelPickerProps> = ({ isPopup, currentMode
|
||||
// Use dynamic models if available, otherwise fall back to static models
|
||||
const modelInfo = dynamicGroqModels?.[newModelId] || groqModels[newModelId as keyof typeof groqModels]
|
||||
|
||||
handleModeFieldsChange(
|
||||
{
|
||||
groqModelId: { plan: "planModeGroqModelId", act: "actModeGroqModelId" },
|
||||
groqModelInfo: { plan: "planModeGroqModelInfo", act: "actModeGroqModelInfo" },
|
||||
},
|
||||
{
|
||||
groqModelId: newModelId,
|
||||
groqModelInfo: modelInfo,
|
||||
},
|
||||
currentMode,
|
||||
)
|
||||
handleFieldsChange({
|
||||
groqModelId: newModelId,
|
||||
groqModelInfo: modelInfo,
|
||||
})
|
||||
setSearchTerm(newModelId)
|
||||
}
|
||||
|
||||
const { selectedModelId, selectedModelInfo } = useMemo(() => {
|
||||
return normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
}, [apiConfiguration, currentMode])
|
||||
return normalizeApiConfiguration(apiConfiguration)
|
||||
}, [apiConfiguration])
|
||||
|
||||
useMount(() => {
|
||||
ModelsServiceClient.refreshGroqModels(EmptyRequest.create({}))
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import Fuse from "fuse.js"
|
||||
import React, { KeyboardEvent, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useMount } from "react-use"
|
||||
import { huggingFaceDefaultModelId, huggingFaceModels } from "@shared/api"
|
||||
import { Mode } from "@shared/ChatSettings"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { ModelsServiceClient } from "../../services/grpc-client"
|
||||
import { highlight } from "../history/HistoryView"
|
||||
import { ModelInfoView } from "./common/ModelInfoView"
|
||||
import { normalizeApiConfiguration, getModeSpecificFields } from "./utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
|
||||
|
||||
export interface HuggingFaceModelPickerProps {
|
||||
isPopup?: boolean
|
||||
currentMode: Mode
|
||||
}
|
||||
|
||||
const HuggingFaceModelPicker: React.FC<HuggingFaceModelPickerProps> = ({ isPopup, currentMode }) => {
|
||||
const { apiConfiguration, huggingFaceModels: dynamicModels, setHuggingFaceModels } = useExtensionState()
|
||||
const { handleModeFieldsChange } = useApiConfigurationHandlers()
|
||||
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
|
||||
const [searchTerm, setSearchTerm] = useState(modeFields.huggingFaceModelId || huggingFaceDefaultModelId)
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const itemRefs = useRef<(HTMLDivElement | null)[]>([])
|
||||
const dropdownListRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleModelChange = (newModelId: string) => {
|
||||
const allModels = { ...huggingFaceModels, ...dynamicModels }
|
||||
const modelInfo = allModels[newModelId as keyof typeof allModels]
|
||||
|
||||
handleModeFieldsChange(
|
||||
{
|
||||
huggingFaceModelId: { plan: "planModeHuggingFaceModelId", act: "actModeHuggingFaceModelId" },
|
||||
huggingFaceModelInfo: { plan: "planModeHuggingFaceModelInfo", act: "actModeHuggingFaceModelInfo" },
|
||||
},
|
||||
{
|
||||
huggingFaceModelId: newModelId,
|
||||
huggingFaceModelInfo: modelInfo,
|
||||
},
|
||||
currentMode,
|
||||
)
|
||||
setSearchTerm(newModelId)
|
||||
}
|
||||
|
||||
const { selectedModelId, selectedModelInfo } = useMemo(() => {
|
||||
return normalizeApiConfiguration(apiConfiguration, currentMode)
|
||||
}, [apiConfiguration, currentMode])
|
||||
|
||||
useMount(() => {
|
||||
ModelsServiceClient.refreshHuggingFaceModels(EmptyRequest.create({}))
|
||||
.then((response) => {
|
||||
setHuggingFaceModels({
|
||||
[huggingFaceDefaultModelId]: huggingFaceModels[huggingFaceDefaultModelId],
|
||||
...response.models,
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to refresh Hugging Face models:", err)
|
||||
})
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsDropdownVisible(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const allModels = useMemo(() => {
|
||||
return { ...huggingFaceModels, ...dynamicModels }
|
||||
}, [dynamicModels])
|
||||
|
||||
const modelIds = useMemo(() => {
|
||||
return Object.keys(allModels).sort((a, b) => a.localeCompare(b))
|
||||
}, [allModels])
|
||||
|
||||
const searchableItems = useMemo(() => {
|
||||
return modelIds.map((id) => ({
|
||||
id,
|
||||
html: id,
|
||||
}))
|
||||
}, [modelIds])
|
||||
|
||||
const fuse = useMemo(() => {
|
||||
return new Fuse(searchableItems, {
|
||||
keys: ["html"],
|
||||
threshold: 0.6,
|
||||
shouldSort: true,
|
||||
isCaseSensitive: false,
|
||||
ignoreLocation: false,
|
||||
includeMatches: true,
|
||||
minMatchCharLength: 1,
|
||||
})
|
||||
}, [searchableItems])
|
||||
|
||||
const modelSearchResults = useMemo(() => {
|
||||
let results: { id: string; html: string }[] = searchTerm
|
||||
? highlight(fuse.search(searchTerm), "model-item-highlight")
|
||||
: searchableItems
|
||||
return results
|
||||
}, [searchTerm, fuse, searchableItems])
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLElement>) => {
|
||||
if (!isDropdownVisible) return
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowDown":
|
||||
e.preventDefault()
|
||||
setSelectedIndex((prev) => (prev < modelSearchResults.length - 1 ? prev + 1 : 0))
|
||||
break
|
||||
case "ArrowUp":
|
||||
e.preventDefault()
|
||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : modelSearchResults.length - 1))
|
||||
break
|
||||
case "Enter":
|
||||
e.preventDefault()
|
||||
if (selectedIndex >= 0 && selectedIndex < modelSearchResults.length) {
|
||||
const selectedModelId = modelSearchResults[selectedIndex].id
|
||||
handleModelChange(selectedModelId)
|
||||
setIsDropdownVisible(false)
|
||||
}
|
||||
break
|
||||
case "Escape":
|
||||
e.preventDefault()
|
||||
setIsDropdownVisible(false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedIndex >= 0 && itemRefs.current[selectedIndex] && dropdownListRef.current) {
|
||||
const selectedItem = itemRefs.current[selectedIndex]
|
||||
const dropdown = dropdownListRef.current
|
||||
const itemOffsetTop = selectedItem.offsetTop
|
||||
const itemHeight = selectedItem.offsetHeight
|
||||
const dropdownScrollTop = dropdown.scrollTop
|
||||
const dropdownHeight = dropdown.offsetHeight
|
||||
|
||||
if (itemOffsetTop < dropdownScrollTop) {
|
||||
dropdown.scrollTop = itemOffsetTop
|
||||
} else if (itemOffsetTop + itemHeight > dropdownScrollTop + dropdownHeight) {
|
||||
dropdown.scrollTop = itemOffsetTop + itemHeight - dropdownHeight
|
||||
}
|
||||
}
|
||||
}, [selectedIndex])
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="hf-model-search">
|
||||
<span className="font-medium">Model</span>
|
||||
</label>
|
||||
|
||||
<div ref={dropdownRef} className="relative w-full">
|
||||
<VSCodeTextField
|
||||
id="hf-model-search"
|
||||
placeholder="Search models..."
|
||||
value={searchTerm}
|
||||
onInput={(e: any) => {
|
||||
setSearchTerm(e.target.value)
|
||||
setIsDropdownVisible(true)
|
||||
setSelectedIndex(-1)
|
||||
}}
|
||||
onFocus={() => setIsDropdownVisible(true)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full relative z-[1000]"
|
||||
/>
|
||||
{isDropdownVisible && (
|
||||
<div
|
||||
ref={dropdownListRef}
|
||||
className={`absolute top-[calc(100%-3px)] left-0 w-[calc(100%-2px)] ${
|
||||
isPopup ? "max-h-[90px]" : "max-h-[200px]"
|
||||
} overflow-y-auto bg-[var(--vscode-dropdown-background)] border border-[var(--vscode-list-activeSelectionBackground)] z-[999] rounded-b-[3px]`}>
|
||||
{modelSearchResults.map((result, index) => (
|
||||
<div
|
||||
key={result.id}
|
||||
ref={(el: HTMLDivElement | null) => (itemRefs.current[index] = el)}
|
||||
className={`p-[5px_10px] cursor-pointer break-all whitespace-normal ${
|
||||
index === selectedIndex ? "bg-[var(--vscode-list-activeSelectionBackground)]" : ""
|
||||
} hover:bg-[var(--vscode-list-activeSelectionBackground)]`}
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
onClick={() => {
|
||||
handleModelChange(result.id)
|
||||
setIsDropdownVisible(false)
|
||||
}}>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: result.html }}
|
||||
className="[&_.model-item-highlight]:bg-[var(--vscode-editor-findMatchHighlightBackground)] [&_.model-item-highlight]:text-inherit"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { HuggingFaceModelPicker }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user