Compare commits

..
Author SHA1 Message Date
Cline Evaluation f263073ebb better cleanup 2025-04-13 20:22:49 -07:00
Cline Evaluation c7a0df622f better initialization 2025-04-13 20:01:59 -07:00
Cline Evaluation d6a3ac681d more aggressive cline opening 2025-04-13 19:38:53 -07:00
Cline Evaluation 102a49fc72 Initial commit for evaluation 2025-04-13 18:52:11 -07:00
61 changed files with 2092 additions and 2724 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
gRPC over vscode message bus to make messaging better
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
fix vertexai token count
-10
View File
@@ -1,15 +1,5 @@
# Changelog
## [3.12.3]
- Add copy button to MermaidBlock component (Thanks @cacosub7!)
- Add the ability to fetch from global cline rules files
- Add icon to indicate when a file outside of the users workspace is edited
## [3.12.2]
- Add gpt-4.1
## [3.12.1]
- Use visual checkpoint indicator to make it clear when checkpoints are created
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -41,7 +41,7 @@ cline-repo/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── repositories/ # Cloned benchmark repositories
│ │ ├── exercism/ # Modified Exercism (from pashpashpash/evals)
│ │ ├── exercism/ # Modified Exercism (from cte/evals)
│ │ ├── swe-bench/ # SWE-Bench repository
│ │ ├── swelancer/ # SWELancer repository
│ │ └── multi-swe/ # Multi-SWE-Bench repository
@@ -110,7 +110,7 @@ Options:
### Exercism
Modified Exercism exercises from the [pashpashpash/evals](https://github.com/pashpashpash/evals) repository. These are small, focused programming exercises in various languages.
Modified Exercism exercises from the [cte/evals](https://github.com/cte/evals) repository. These are small, focused programming exercises in various languages.
### SWE-Bench (Coming Soon)
+1 -1
View File
@@ -20,7 +20,7 @@ export class ExercismAdapter implements BenchmarkAdapter {
if (!fs.existsSync(exercismDir)) {
console.log(`Cloning Exercism repository to ${exercismDir}...`)
await execa("git", ["clone", "https://github.com/pashpashpash/evals.git", exercismDir])
await execa("git", ["clone", "https://github.com/cte/evals.git", exercismDir])
console.log("Exercism repository cloned successfully")
} else {
console.log(`Exercism repository already exists at ${exercismDir}`)
+2 -2
View File
@@ -93,7 +93,7 @@ export async function runHandler(options: RunOptions): Promise<void> {
storeSpinner.succeed("Result stored")
console.log(chalk.green(`Task completed. Success: ${verification.success}`))
// Clean up VS Code and temporary files
const cleanupSpinner = ora("Cleaning up...").start()
try {
@@ -106,7 +106,7 @@ export async function runHandler(options: RunOptions): Promise<void> {
} catch (error: any) {
sendSpinner.fail(`Task failed: ${error.message}`)
console.error(chalk.red(error.stack))
// Clean up VS Code and temporary files even if the task failed
const cleanupSpinner = ora("Cleaning up...").start()
try {
-131
View File
@@ -1,131 +0,0 @@
import execa from "execa"
import * as fs from "fs"
import * as path from "path"
import * as os from "os"
/**
* List of VSCode extensions to install for evaluation environments
* These extensions provide language support and other useful features
*/
export const REQUIRED_EXTENSIONS = [
"golang.go", // Go language support
"dbaeumer.vscode-eslint", // ESLint support
"redhat.java", // Java support
"ms-python.python", // Python support
"rust-lang.rust-analyzer", // Rust support
"ms-vscode.cpptools", // C/C++ support
]
/**
* Install required VSCode extensions in the specified extensions directory
* @param extensionsDir The directory where extensions should be installed
* @returns Promise that resolves when all extensions are installed
*/
export async function installRequiredExtensions(extensionsDir: string): Promise<void> {
console.log("Installing required VSCode extensions...")
// Create the extensions directory if it doesn't exist
if (!fs.existsSync(extensionsDir)) {
fs.mkdirSync(extensionsDir, { recursive: true })
}
// Install each extension
for (const extension of REQUIRED_EXTENSIONS) {
try {
console.log(`Installing extension: ${extension}...`)
await execa("code", ["--extensions-dir", extensionsDir, "--install-extension", extension, "--force"])
console.log(`✅ Extension ${extension} installed successfully`)
} catch (error: any) {
console.warn(`⚠️ Failed to install extension ${extension}: ${error.message}`)
// Continue with other extensions even if one fails
}
}
console.log("✅ All required extensions installed")
}
/**
* Check if a VSCode extension is installed in the specified directory
* @param extensionsDir The directory to check for installed extensions
* @param extensionId The ID of the extension to check
* @returns True if the extension is installed, false otherwise
*/
export function isExtensionInstalled(extensionsDir: string, extensionId: string): boolean {
// Extensions are installed in directories named publisher.name-version
// We need to check if any directory starts with the extensionId
const extensionPrefix = extensionId.toLowerCase() + "-"
try {
const files = fs.readdirSync(extensionsDir)
return files.some((file) => {
const lowerCaseFile = file.toLowerCase()
return lowerCaseFile === extensionId.toLowerCase() || lowerCaseFile.startsWith(extensionPrefix)
})
} catch (error) {
return false
}
}
/**
* Get the path to the VSCode settings file in the specified user data directory
* @param userDataDir The VSCode user data directory
* @returns The path to the settings.json file
*/
export function getSettingsPath(userDataDir: string): string {
const settingsDir = path.join(userDataDir, "User")
fs.mkdirSync(settingsDir, { recursive: true })
return path.join(settingsDir, "settings.json")
}
/**
* Configure extension settings in the VSCode user data directory
* @param userDataDir The VSCode user data directory
*/
export function configureExtensionSettings(userDataDir: string): void {
const settingsPath = getSettingsPath(userDataDir)
// Read existing settings if they exist
let settings = {}
if (fs.existsSync(settingsPath)) {
try {
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"))
} catch (error) {
console.warn(`Error reading settings file: ${error}`)
}
}
// Add or update extension-specific settings
const updatedSettings = {
...settings,
// Go extension settings
"go.toolsManagement.autoUpdate": false,
"go.survey.prompt": false,
// ESLint settings
"eslint.enable": true,
"eslint.run": "onSave",
// Java settings
"java.configuration.checkProjectSettingsExclusions": false,
"java.configure.checkForOutdatedExtensions": false,
"java.help.firstView": false,
// Python settings
"python.experiments.enabled": false,
"python.showStartPage": false,
// Rust settings
"rust-analyzer.checkOnSave.command": "check",
// C/C++ settings
"C_Cpp.intelliSenseEngine": "default",
// General extension settings
"extensions.autoUpdate": false,
"extensions.ignoreRecommendations": true,
}
// Write updated settings
fs.writeFileSync(settingsPath, JSON.stringify(updatedSettings, null, 2))
console.log("✅ Extension settings configured")
}
+2 -2
View File
@@ -18,9 +18,9 @@ export async function sendTaskToServer(task: string, apiKey?: string): Promise<a
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
body: JSON.stringify({
task,
apiKey,
apiKey
}),
})
+175 -187
View File
@@ -4,17 +4,16 @@ import * as fs from "fs"
import fetch from "node-fetch"
import * as os from "os"
import * as child_process from "child_process"
import { installRequiredExtensions, configureExtensionSettings } from "./extensions"
// Store temporary directories for cleanup
interface VSCodeResources {
tempUserDataDir: string
tempExtensionsDir: string
vscodePid?: number
tempUserDataDir: string;
tempExtensionsDir: string;
vscodePid?: number;
}
// Global map to track resources for each workspace
const workspaceResources = new Map<string, VSCodeResources>()
const workspaceResources = new Map<string, VSCodeResources>();
/**
* Spawn a VSCode instance with the Cline extension
@@ -60,37 +59,37 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
const tempUserDataDir = path.join(os.tmpdir(), `vscode-cline-eval-${Date.now()}`)
fs.mkdirSync(tempUserDataDir, { recursive: true })
console.log(`Created temporary user data directory: ${tempUserDataDir}`)
// Create a temporary extensions directory to ensure no other extensions are loaded
const tempExtensionsDir = path.join(os.tmpdir(), `vscode-cline-eval-ext-${Date.now()}`)
fs.mkdirSync(tempExtensionsDir, { recursive: true })
console.log(`Created temporary extensions directory: ${tempExtensionsDir}`)
// Create settings.json in the temporary user data directory to disable workspace trust
// and configure Cline to auto-open on startup
const settingsDir = path.join(tempUserDataDir, "User")
const settingsDir = path.join(tempUserDataDir, 'User')
fs.mkdirSync(settingsDir, { recursive: true })
const settingsPath = path.join(settingsDir, "settings.json")
const settingsPath = path.join(settingsDir, 'settings.json')
const settings = {
// Disable workspace trust
"security.workspace.trust.enabled": false,
"security.workspace.trust.startupPrompt": "never",
"security.workspace.trust.banner": "never",
"security.workspace.trust.emptyWindow": true,
// Configure startup behavior
"workbench.startupEditor": "none",
// Auto-open Cline on startup
"cline.autoOpenOnStartup": true,
// Show the activity bar and sidebar
"workbench.activityBar.visible": true,
"workbench.sideBar.visible": true,
"workbench.view.extension.saoudrizwan.claude-dev-ActivityBar.visible": true,
"workbench.view.alwaysShowHeaderActions": true,
"workbench.editor.openSideBySideDirection": "right",
// Disable GitLens from opening automatically
"gitlens.views.repositories.autoReveal": false,
"gitlens.views.fileHistory.autoReveal": false,
@@ -99,26 +98,26 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
"gitlens.views.search.autoReveal": false,
"gitlens.showWelcomeOnInstall": false,
"gitlens.showWhatsNewAfterUpgrades": false,
// Disable other extensions that might compete for startup focus
"extensions.autoUpdate": false,
"extensions.autoUpdate": false
}
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
console.log(`Created settings.json to disable workspace trust and auto-open Cline`)
// Create keybindings.json to automatically open Cline on startup
const keybindingsPath = path.join(settingsDir, "keybindings.json")
const keybindingsPath = path.join(settingsDir, 'keybindings.json')
const keybindings = [
{
key: "alt+c",
command: "workbench.view.extension.saoudrizwan.claude-dev-ActivityBar",
when: "viewContainer.workbench.view.extension.saoudrizwan.claude-dev-ActivityBar.enabled",
"key": "alt+c",
"command": "workbench.view.extension.saoudrizwan.claude-dev-ActivityBar",
"when": "viewContainer.workbench.view.extension.saoudrizwan.claude-dev-ActivityBar.enabled"
},
{
key: "alt+shift+c",
command: "cline.openInNewTab",
when: "viewContainer.workbench.view.extension.saoudrizwan.claude-dev-ActivityBar.enabled",
},
"key": "alt+shift+c",
"command": "cline.openInNewTab",
"when": "viewContainer.workbench.view.extension.saoudrizwan.claude-dev-ActivityBar.enabled"
}
]
fs.writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2))
console.log(`Created keybindings.json to help with Cline activation`)
@@ -126,28 +125,24 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
// Build the command arguments with custom user data directory
const args = [
// Use a custom user data directory to isolate this instance
"--user-data-dir",
tempUserDataDir,
"--user-data-dir", tempUserDataDir,
// Use a custom extensions directory to ensure only our extension is loaded
"--extensions-dir",
tempExtensionsDir,
"--extensions-dir", tempExtensionsDir,
// Disable workspace trust
"--disable-workspace-trust",
"-n",
workspacePath,
// Force the extension to be activated on startup
"--start-up-extension",
"saoudrizwan.claude-dev",
"--start-up-extension", "saoudrizwan.claude-dev",
// Run a command on startup to open Cline
"--command",
"workbench.view.extension.saoudrizwan.claude-dev-ActivityBar",
"--command", "workbench.view.extension.saoudrizwan.claude-dev-ActivityBar",
// Additional flags to help with extension activation
"--disable-gpu=false",
"--max-memory=4096",
"--max-memory=4096"
]
// Create a startup script to run commands after VS Code launches
const startupScriptPath = path.join(settingsDir, "startup.js")
const startupScriptPath = path.join(settingsDir, 'startup.js')
const startupScript = `
// This script will be executed when VS Code starts
setTimeout(() => {
@@ -159,9 +154,9 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
require('vscode').commands.executeCommand('cline.openInNewTab');
}, 5000);
}, 5000);
`
fs.writeFileSync(startupScriptPath, startupScript)
console.log(`Created startup script to activate Cline`)
`;
fs.writeFileSync(startupScriptPath, startupScript);
console.log(`Created startup script to activate Cline`);
// If a VSIX is provided, install it
if (vsixPath) {
@@ -171,14 +166,6 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
args.unshift("--install-extension", vsixPath)
}
// Install required extensions
console.log("Installing required VSCode extensions...")
await installRequiredExtensions(tempExtensionsDir)
// Configure extension settings
console.log("Configuring extension settings...")
configureExtensionSettings(tempUserDataDir)
// Execute the command
try {
// We don't need to install extensions globally anymore since we're using a custom user data directory
@@ -195,34 +182,36 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
await new Promise((resolve) => setTimeout(resolve, 30000))
// Create a JavaScript file that will be loaded as a VS Code extension
const extensionDir = path.join(tempExtensionsDir, "cline-activator")
const extensionDir = path.join(tempExtensionsDir, 'cline-activator')
fs.mkdirSync(extensionDir, { recursive: true })
// Create package.json for the extension
const packageJsonPath = path.join(extensionDir, "package.json")
const packageJsonPath = path.join(extensionDir, 'package.json')
const packageJson = {
name: "cline-activator",
displayName: "Cline Activator",
description: "Activates Cline and starts the test server",
version: "0.0.1",
engines: {
vscode: "^1.60.0",
"name": "cline-activator",
"displayName": "Cline Activator",
"description": "Activates Cline and starts the test server",
"version": "0.0.1",
"engines": {
"vscode": "^1.60.0"
},
main: "./extension.js",
activationEvents: ["*"],
contributes: {
commands: [
"main": "./extension.js",
"activationEvents": [
"*"
],
"contributes": {
"commands": [
{
command: "cline-activator.activate",
title: "Activate Cline",
},
],
},
"command": "cline-activator.activate",
"title": "Activate Cline"
}
]
}
}
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
// Create extension.js
const extensionJsPath = path.join(extensionDir, "extension.js")
const extensionJsPath = path.join(extensionDir, 'extension.js')
const extensionJs = `
const vscode = require('vscode');
@@ -292,47 +281,43 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
activate,
deactivate
}
`
fs.writeFileSync(extensionJsPath, extensionJs)
console.log(`Created Cline Activator extension`)
`;
fs.writeFileSync(extensionJsPath, extensionJs);
console.log(`Created Cline Activator extension`);
// Try multiple approaches to activate the extension
let serverStarted = false
// Create an activation script to run in VS Code
const activationScriptPath = path.join(settingsDir, "activate-cline.js")
const activationScriptPath = path.join(settingsDir, 'activate-cline.js');
const activationScript = `
// This script will be executed to activate Cline and start the test server
const vscode = require('vscode');
// Execute the cline-activator.activate command
vscode.commands.executeCommand('cline-activator.activate');
`
fs.writeFileSync(activationScriptPath, activationScript)
console.log(`Created activation script to run in VS Code`)
`;
fs.writeFileSync(activationScriptPath, activationScript);
console.log(`Created activation script to run in VS Code`);
// Execute the activation script
try {
console.log("Executing activation script to start Cline and test server...")
console.log("Executing activation script to start Cline and test server...");
await execa(
"code",
[
"--user-data-dir",
tempUserDataDir,
"--extensions-dir",
tempExtensionsDir,
"--folder-uri",
`file://${workspacePath}`,
"--execute",
activationScriptPath,
"--user-data-dir", tempUserDataDir,
"--extensions-dir", tempExtensionsDir,
"--folder-uri", `file://${workspacePath}`,
"--execute", activationScriptPath
],
{
stdio: "inherit",
},
)
}
);
// Wait for the test server to start
console.log("Waiting for test server to start...")
console.log("Waiting for test server to start...");
for (let i = 0; i < 30; i++) {
try {
// Try to connect to the test server
@@ -341,38 +326,38 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
headers: {
"Content-Type": "application/json",
},
})
});
if (response.status === 204) {
console.log("Test server is running!")
serverStarted = true
break
console.log("Test server is running!");
serverStarted = true;
break;
}
} catch (error) {
// Server not started yet, wait and try again
await new Promise((resolve) => setTimeout(resolve, 1000))
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
} catch (error) {
console.warn("Failed to execute activation script:", error)
console.warn("Failed to execute activation script:", error);
}
if (!serverStarted) {
console.warn("Test server did not start after multiple attempts")
console.log("You may need to manually open the Cline extension in VS Code")
}
// Store the resources for this workspace
const resources: VSCodeResources = {
tempUserDataDir,
tempExtensionsDir,
}
tempExtensionsDir
};
// Store in the global map
workspaceResources.set(workspacePath, resources)
workspaceResources.set(workspacePath, resources);
// Return the resources
return resources
return resources;
} catch (error: any) {
throw new Error(`Failed to spawn VSCode: ${error.message}`)
}
@@ -383,18 +368,18 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
* @param workspacePath The workspace path to clean up resources for
*/
export async function cleanupVSCode(workspacePath: string): Promise<void> {
console.log(`Cleaning up VS Code resources for workspace: ${workspacePath}`)
console.log(`Cleaning up VS Code resources for workspace: ${workspacePath}`);
// Get the resources for this workspace
const resources = workspaceResources.get(workspacePath)
const resources = workspaceResources.get(workspacePath);
if (!resources) {
console.log(`No resources found for workspace: ${workspacePath}`)
return
console.log(`No resources found for workspace: ${workspacePath}`);
return;
}
// Try to shut down the test server
try {
console.log("Shutting down test server...")
console.log("Shutting down test server...");
await fetch("http://localhost:9876/shutdown", {
method: "POST",
headers: {
@@ -402,169 +387,172 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
},
}).catch(() => {
// Ignore errors, the server might already be down
})
});
} catch (error) {
console.warn(`Error shutting down test server: ${error}`)
console.warn(`Error shutting down test server: ${error}`);
}
// Try to gracefully close VS Code instead of killing it
try {
console.log("Attempting to gracefully close VS Code...")
console.log("Attempting to gracefully close VS Code...");
// Create a settings file that will disable the crash reporter and the exit confirmation dialog
const settingsDir = path.join(resources.tempUserDataDir, "User")
const settingsPath = path.join(settingsDir, "settings.json")
const settingsDir = path.join(resources.tempUserDataDir, 'User');
const settingsPath = path.join(settingsDir, 'settings.json');
// Read existing settings if they exist
let settings = {}
let settings = {};
if (fs.existsSync(settingsPath)) {
try {
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"))
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
} catch (error) {
console.warn(`Error reading settings file: ${error}`)
console.warn(`Error reading settings file: ${error}`);
}
}
// Update settings to disable crash reporter and exit confirmation
settings = {
...settings,
"window.confirmBeforeClose": "never",
"telemetry.enableCrashReporter": false,
"window.restoreWindows": "none",
"window.newWindowDimensions": "default",
}
"window.newWindowDimensions": "default"
};
// Write updated settings
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
// On macOS, use AppleScript to quit VS Code gracefully
if (process.platform === "darwin") {
if (process.platform === 'darwin') {
try {
// First try AppleScript to quit VS Code gracefully
await execa("osascript", ["-e", 'tell application "Visual Studio Code" to quit'])
await execa('osascript', [
'-e',
'tell application "Visual Studio Code" to quit'
]);
// Wait a moment for VS Code to close
await new Promise((resolve) => setTimeout(resolve, 2000))
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (appleScriptError) {
console.warn(`Error using AppleScript to quit VS Code: ${appleScriptError}`)
console.warn(`Error using AppleScript to quit VS Code: ${appleScriptError}`);
}
} else if (process.platform === "win32") {
} else if (process.platform === 'win32') {
// On Windows, try to use taskkill without /F first
try {
await execa("taskkill", ["/IM", "code.exe"])
await execa('taskkill', ['/IM', 'code.exe']);
// Wait a moment for VS Code to close
await new Promise((resolve) => setTimeout(resolve, 2000))
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (taskkillError) {
console.warn(`Error using taskkill to quit VS Code: ${taskkillError}`)
console.warn(`Error using taskkill to quit VS Code: ${taskkillError}`);
}
} else {
// On Linux, try to use SIGTERM first
try {
// Find VS Code processes
const { stdout } = await execa("ps", ["aux"])
const lines = stdout.split("\n")
const { stdout } = await execa('ps', ['aux']);
const lines = stdout.split('\n');
for (const line of lines) {
if (line.includes(resources.tempUserDataDir)) {
const parts = line.trim().split(/\s+/)
const pid = parseInt(parts[1])
const parts = line.trim().split(/\s+/);
const pid = parseInt(parts[1]);
if (pid && !isNaN(pid)) {
console.log(`Sending SIGTERM to VS Code process with PID: ${pid}`)
console.log(`Sending SIGTERM to VS Code process with PID: ${pid}`);
try {
// Use SIGTERM instead of SIGKILL for a graceful shutdown
process.kill(pid, "SIGTERM")
process.kill(pid, 'SIGTERM');
} catch (killError) {
console.warn(`Failed to terminate process ${pid}: ${killError}`)
console.warn(`Failed to terminate process ${pid}: ${killError}`);
}
}
}
}
// Wait a moment for VS Code to close
await new Promise((resolve) => setTimeout(resolve, 2000))
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (psError) {
console.warn(`Error listing processes: ${psError}`)
console.warn(`Error listing processes: ${psError}`);
}
}
// If graceful methods failed, fall back to forceful termination as a last resort
// Check if VS Code is still running with the temp user data dir
let vsCodeStillRunning = false
if (process.platform !== "win32") {
let vsCodeStillRunning = false;
if (process.platform !== 'win32') {
try {
const { stdout } = await execa("ps", ["aux"])
vsCodeStillRunning = stdout.split("\n").some((line) => line.includes(resources.tempUserDataDir))
const { stdout } = await execa('ps', ['aux']);
vsCodeStillRunning = stdout.split('\n').some(line => line.includes(resources.tempUserDataDir));
} catch (error) {
console.warn(`Error checking if VS Code is still running: ${error}`)
console.warn(`Error checking if VS Code is still running: ${error}`);
}
} else {
try {
const { stdout } = await execa("tasklist", ["/FI", `IMAGENAME eq code.exe`])
vsCodeStillRunning = stdout.includes("code.exe")
const { stdout } = await execa('tasklist', ['/FI', `IMAGENAME eq code.exe`]);
vsCodeStillRunning = stdout.includes('code.exe');
} catch (error) {
console.warn(`Error checking if VS Code is still running: ${error}`)
console.warn(`Error checking if VS Code is still running: ${error}`);
}
}
// If VS Code is still running, use forceful termination as a last resort
if (vsCodeStillRunning) {
console.log("Graceful shutdown failed, falling back to forceful termination...")
if (process.platform === "win32") {
console.log("Graceful shutdown failed, falling back to forceful termination...");
if (process.platform === 'win32') {
try {
await execa("taskkill", ["/IM", "code.exe", "/F"])
await execa('taskkill', ['/IM', 'code.exe', '/F']);
} catch (error) {
console.warn(`Error forcefully terminating VS Code: ${error}`)
console.warn(`Error forcefully terminating VS Code: ${error}`);
}
} else {
try {
const { stdout } = await execa("ps", ["aux"])
const lines = stdout.split("\n")
const { stdout } = await execa('ps', ['aux']);
const lines = stdout.split('\n');
for (const line of lines) {
if (line.includes(resources.tempUserDataDir)) {
const parts = line.trim().split(/\s+/)
const pid = parseInt(parts[1])
const parts = line.trim().split(/\s+/);
const pid = parseInt(parts[1]);
if (pid && !isNaN(pid)) {
console.log(`Forcefully killing VS Code process with PID: ${pid}`)
console.log(`Forcefully killing VS Code process with PID: ${pid}`);
try {
process.kill(pid, "SIGKILL")
process.kill(pid, 'SIGKILL');
} catch (killError) {
console.warn(`Failed to kill process ${pid}: ${killError}`)
console.warn(`Failed to kill process ${pid}: ${killError}`);
}
}
}
}
} catch (error) {
console.warn(`Error forcefully terminating VS Code: ${error}`)
console.warn(`Error forcefully terminating VS Code: ${error}`);
}
}
}
} catch (error) {
console.warn(`Error closing VS Code: ${error}`)
console.warn(`Error closing VS Code: ${error}`);
}
// Clean up temporary directories
try {
console.log(`Removing temporary user data directory: ${resources.tempUserDataDir}`)
fs.rmSync(resources.tempUserDataDir, { recursive: true, force: true })
console.log(`Removing temporary user data directory: ${resources.tempUserDataDir}`);
fs.rmSync(resources.tempUserDataDir, { recursive: true, force: true });
} catch (error) {
console.warn(`Error removing temporary user data directory: ${error}`)
console.warn(`Error removing temporary user data directory: ${error}`);
}
try {
console.log(`Removing temporary extensions directory: ${resources.tempExtensionsDir}`)
fs.rmSync(resources.tempExtensionsDir, { recursive: true, force: true })
console.log(`Removing temporary extensions directory: ${resources.tempExtensionsDir}`);
fs.rmSync(resources.tempExtensionsDir, { recursive: true, force: true });
} catch (error) {
console.warn(`Error removing temporary extensions directory: ${error}`)
console.warn(`Error removing temporary extensions directory: ${error}`);
}
// Remove from the global map
workspaceResources.delete(workspacePath)
console.log("Cleanup completed")
workspaceResources.delete(workspacePath);
console.log("Cleanup completed");
}
Binary file not shown.
+36 -164
View File
@@ -1,22 +1,20 @@
{
"name": "claude-dev",
"version": "3.12.3",
"version": "3.12.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.12.3",
"version": "3.12.1",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.12.4",
"@anthropic-ai/sdk": "^0.37.0",
"@anthropic-ai/vertex-sdk": "^0.6.4",
"@aws-sdk/client-bedrock-runtime": "^3.758.0",
"@bufbuild/protobuf": "^2.2.5",
"@google-cloud/vertexai": "^1.9.3",
"@google/generative-ai": "^0.18.0",
"@grpc/grpc-js": "^1.9.15",
"@mistralai/mistralai": "^1.5.0",
"@modelcontextprotocol/sdk": "^1.7.0",
"@opentelemetry/api": "^1.4.1",
@@ -81,18 +79,15 @@
"@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0",
"chai": "^4.3.10",
"chalk": "^5.3.0",
"esbuild": "^0.25.0",
"eslint": "^8.57.0",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"protoc-gen-ts": "^0.8.7",
"proxyquire": "^2.1.3",
"should": "^13.2.3",
"sinon": "^19.0.2",
"ts-node": "^10.9.2",
"ts-proto": "^2.6.1",
"typescript": "^5.4.5"
},
"engines": {
@@ -3962,12 +3957,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@bufbuild/protobuf": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.5.tgz",
"integrity": "sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@changesets/apply-release-plan": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.8.tgz",
@@ -5577,7 +5566,6 @@
"version": "1.9.15",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
"integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==",
"license": "Apache-2.0",
"dependencies": {
"@grpc/proto-loader": "^0.7.8",
"@types/node": ">=12.12.47"
@@ -9931,19 +9919,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/case-anything": {
"version": "2.1.13",
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz",
"integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.13"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/chai": {
"version": "4.3.10",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz",
@@ -9963,18 +9938,35 @@
}
},
"node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chalk/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
@@ -10620,19 +10612,6 @@
"node": ">=8"
}
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/devtools-protocol": {
"version": "0.0.1342118",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1342118.tgz",
@@ -10743,16 +10722,6 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dprint-node": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz",
"integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^1.0.3"
}
},
"node_modules/duck": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
@@ -11223,23 +11192,6 @@
"concat-map": "0.0.1"
}
},
"node_modules/eslint/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/eslint/node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -11288,19 +11240,6 @@
"node": ">=8"
}
},
"node_modules/eslint/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@@ -13720,36 +13659,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-symbols/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/log-symbols/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/long": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz",
@@ -14859,6 +14768,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora/node_modules/chalk": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/ora/node_modules/emoji-regex": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
@@ -15444,20 +15366,6 @@
"node": ">=12.0.0"
}
},
"node_modules/protoc-gen-ts": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/protoc-gen-ts/-/protoc-gen-ts-0.8.7.tgz",
"integrity": "sha512-jr4VJey2J9LVYCV7EVyVe53g1VMw28cCmYJhBe5e3YX5wiyiDwgxWxeDf9oTqAe4P1bN/YGAkW2jhlH8LohwiQ==",
"dev": true,
"license": "MIT",
"bin": {
"protoc-gen-ts": "protoc-gen-ts.js"
},
"funding": {
"type": "individual",
"url": "https://www.buymeacoffee.com/thesayyn"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -17062,42 +16970,6 @@
"node": ">=0.3.1"
}
},
"node_modules/ts-poet": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.11.0.tgz",
"integrity": "sha512-r5AGF8vvb+GjBsnqiTqbLhN1/U2FJt6BI+k0dfCrkKzWvUhNlwMmq9nDHuucHs45LomgHjZPvYj96dD3JawjJA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"dprint-node": "^1.0.8"
}
},
"node_modules/ts-proto": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.7.0.tgz",
"integrity": "sha512-BGHjse2wTOeswOqnnPKinpxmbaRd882so/e1En6ww59YMG7AO9Kg4vPpJcbVfrpBixPRDqHafXD/RDyd2T99GA==",
"dev": true,
"license": "ISC",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"case-anything": "^2.1.13",
"ts-poet": "^6.7.0",
"ts-proto-descriptors": "2.0.0"
},
"bin": {
"protoc-gen-ts_proto": "protoc-gen-ts_proto"
}
},
"node_modules/ts-proto-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.0.0.tgz",
"integrity": "sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0"
}
},
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+6 -9
View File
@@ -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.12.3",
"version": "3.12.1",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
@@ -39,7 +39,10 @@
"ai",
"llama"
],
"activationEvents": [],
"activationEvents": [
"onLanguage",
"onStartupFinished"
],
"main": "./dist/extension.js",
"contributes": {
"viewsContainers": {
@@ -283,14 +286,13 @@
}
},
"scripts": {
"vscode:prepublish": "npm run package",
"vscode:prepublish": "if [ \"$IS_TEST\" = \"true\" ]; then npm run package:test; else npm run package; fi",
"compile": "npm run check-types && npm run lint && node esbuild.js",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.js --watch",
"watch:esbuild:test": "IS_TEST=true node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
"protos": "node proto/build-proto.js && prettier src/shared/proto --write && prettier src/core/controller --write",
"package:test": "IS_TEST=true npm run build:webview:test && npm run check-types && npm run lint && IS_TEST=true node esbuild.js --production",
"build:webview:test": "cd webview-ui && IS_TEST=true npm run build",
"watch:test": "IS_TEST=true npm-run-all -p watch:tsc watch:esbuild:test",
@@ -335,18 +337,15 @@
"@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0",
"chai": "^4.3.10",
"chalk": "^5.3.0",
"esbuild": "^0.25.0",
"eslint": "^8.57.0",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"protoc-gen-ts": "^0.8.7",
"proxyquire": "^2.1.3",
"should": "^13.2.3",
"sinon": "^19.0.2",
"ts-node": "^10.9.2",
"ts-proto": "^2.6.1",
"typescript": "^5.4.5"
},
"dependencies": {
@@ -354,10 +353,8 @@
"@anthropic-ai/sdk": "^0.37.0",
"@anthropic-ai/vertex-sdk": "^0.6.4",
"@aws-sdk/client-bedrock-runtime": "^3.758.0",
"@bufbuild/protobuf": "^2.2.5",
"@google-cloud/vertexai": "^1.9.3",
"@google/generative-ai": "^0.18.0",
"@grpc/grpc-js": "^1.9.15",
"@mistralai/mistralai": "^1.5.0",
"@modelcontextprotocol/sdk": "^1.7.0",
"@opentelemetry/api": "^1.4.1",
-22
View File
@@ -1,22 +0,0 @@
syntax = "proto3";
package cline;
import "common.proto";
service BrowserService {
rpc getBrowserConnectionInfo(EmptyRequest) returns (BrowserConnectionInfo);
rpc testBrowserConnection(StringRequest) returns (BrowserConnection);
}
message BrowserConnectionInfo {
bool is_connected = 1;
bool is_remote = 2;
optional string host = 3;
}
message BrowserConnection {
bool success = 1;
string message = 2;
optional string endpoint = 3;
}
-157
View File
@@ -1,157 +0,0 @@
#!/usr/bin/env node
import * as fs from "fs/promises"
import * as path from "path"
import { execSync } from "child_process"
import { globby } from "globby"
import chalk from "chalk"
// Get script directory and root directory
const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname)
const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
async function main() {
console.log(chalk.bold.blue("Starting Protocol Buffer code generation..."))
// Check if protoc is installed
try {
const options = { stdio: "ignore" }
execSync("protoc --version", options)
} catch (error) {
console.warn(chalk.yellow("Warning: protoc is not installed. Skipping proto generation."))
console.warn(chalk.yellow("To install Protocol Buffers compiler, visit: https://grpc.io/docs/protoc-installation/"))
process.exit(0) // Exit with success as requested
}
// Check if ts-proto plugin is available
const TS_PROTO_PLUGIN = path.join(ROOT_DIR, "node_modules", ".bin", "protoc-gen-ts_proto")
try {
await fs.access(TS_PROTO_PLUGIN)
} catch (error) {
console.error(chalk.red("Error: ts-proto plugin not found at"), TS_PROTO_PLUGIN)
console.error(chalk.red('Please run "npm install" to install the required dependencies.'))
process.exit(1)
}
// Define output directories
const TS_OUT_DIR = path.join(ROOT_DIR, "src", "shared", "proto")
// Create output directory if it doesn't exist
await fs.mkdir(TS_OUT_DIR, { recursive: true })
// Clean up existing generated files
console.log(chalk.cyan("Cleaning up existing generated TypeScript files..."))
const existingFiles = await globby("**/*.ts", { cwd: TS_OUT_DIR })
for (const file of existingFiles) {
await fs.unlink(path.join(TS_OUT_DIR, file))
}
// Process all proto files
console.log(chalk.cyan("Processing proto files from"), SCRIPT_DIR)
const protoFiles = await globby("**/*.proto", { cwd: SCRIPT_DIR })
for (const protoFile of protoFiles) {
console.log(chalk.cyan(`Generating TypeScript code for ${protoFile}...`))
// Build the protoc command with proper path handling for cross-platform
const protocCommand = [
"protoc",
`--plugin=protoc-gen-ts_proto="${TS_PROTO_PLUGIN}"`,
`--ts_proto_out="${TS_OUT_DIR}"`,
"--ts_proto_opt=outputServices=generic-definitions,env=node,esModuleInterop=true,useDate=false,useOptionals=messages",
`--proto_path="${SCRIPT_DIR}"`,
`"${path.join(SCRIPT_DIR, protoFile)}"`,
].join(" ")
try {
const execOptions = {
stdio: "inherit",
}
execSync(protocCommand, execOptions)
} catch (error) {
console.error(chalk.red(`Error generating TypeScript for ${protoFile}:`), error)
process.exit(1)
}
}
console.log(chalk.green("Protocol Buffer code generation completed successfully."))
console.log(chalk.green(`TypeScript files generated in: ${TS_OUT_DIR}`))
// Generate method registration files
await generateMethodRegistrations()
// Make the script executable
try {
await fs.chmod(path.join(SCRIPT_DIR, "build-proto.js"), 0o755)
} catch (error) {
console.warn(chalk.yellow("Warning: Could not make script executable:"), error)
}
}
async function generateMethodRegistrations() {
console.log(chalk.cyan("Generating method registration files..."))
const serviceDirs = [
path.join(ROOT_DIR, "src", "core", "controller", "browser"),
// Add more service directories here as needed
]
for (const serviceDir of serviceDirs) {
try {
await fs.access(serviceDir)
} catch (error) {
console.log(chalk.gray(`Skipping ${serviceDir} - directory does not exist`))
continue
}
const serviceName = path.basename(serviceDir)
const registryFile = path.join(serviceDir, "methods.ts")
console.log(chalk.cyan(`Generating method registrations for ${serviceName}...`))
// Get all TypeScript files in the service directory
const files = await globby("*.ts", { cwd: serviceDir })
// Filter out index.ts and methods.ts
const implementationFiles = files.filter((file) => file !== "index.ts" && file !== "methods.ts")
// Create the output file with header
let content = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
// Import all method implementations
import { registerMethod } from "./index"\n`
// Add imports for all implementation files
for (const file of implementationFiles) {
const baseName = path.basename(file, ".ts")
content += `import { ${baseName} } from "./${baseName}"\n`
}
// Add registration function
content += `\n// Register all ${serviceName} service methods
export function registerAllMethods(): void {
\t// Register each method with the registry\n`
// Add registration statements
for (const file of implementationFiles) {
const baseName = path.basename(file, ".ts")
content += `\tregisterMethod("${baseName}", ${baseName})\n`
}
// Close the function
content += `}`
// Write the file
await fs.writeFile(registryFile, content)
console.log(chalk.green(`Generated ${registryFile}`))
}
console.log(chalk.green("Method registration files generated successfully."))
}
// Run the main function
main().catch((error) => {
console.error(chalk.red("Error:"), error)
process.exit(1)
})
-40
View File
@@ -1,40 +0,0 @@
syntax = "proto3";
package cline;
message Metadata {
}
message EmptyRequest {
Metadata metadata = 1;
}
message Empty {
}
message StringRequest {
Metadata metadata = 1;
string value = 2;
}
message String {
string value = 1;
}
message Int64Request {
Metadata metadata = 1;
int64 value = 2;
}
message Int64 {
int64 value = 1;
}
message BytesRequest {
Metadata metadata = 1;
bytes value = 2;
}
message Bytes {
bytes value = 1;
}
-3
View File
@@ -1,3 +0,0 @@
{
"type": "module"
}
+1 -1
View File
@@ -12,7 +12,7 @@ import {
import { convertToOpenAiMessages } from "../transform/openai-format"
import { calculateApiCostOpenAI } from "../../utils/cost"
import { ApiStream } from "../transform/stream"
import type { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions.mjs"
export class OpenAiNativeHandler implements ApiHandler {
private options: ApiHandlerOptions
+1 -1
View File
@@ -6,7 +6,7 @@ import { ApiHandler } from "../index"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import { convertToR1Format } from "../transform/r1-format"
import type { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions.mjs"
export class OpenAiHandler implements ApiHandler {
private options: ApiHandlerOptions
-7
View File
@@ -1,13 +1,6 @@
// For the following openrouter error type sources, see the docs here:
// https://openrouter.ai/docs/api-reference/errors
export interface LanguageModelChatSelector {
vendor?: string
family?: string
version?: string
id?: string
}
export type OpenRouterErrorResponse = {
error: {
message: string
-12
View File
@@ -5,7 +5,6 @@ import { ApiHandler } from "../"
import { ApiHandlerOptions, ModelInfo, vertexDefaultModelId, VertexModelId, vertexModels } from "../../shared/api"
import { ApiStream } from "../transform/stream"
import { VertexAI } from "@google-cloud/vertexai"
import { calculateApiCostOpenAI } from "../../utils/cost"
// https://docs.anthropic.com/en/api/claude-on-vertex-ai
export class VertexHandler implements ApiHandler {
@@ -267,17 +266,6 @@ export class VertexHandler implements ApiHandler {
}
}
}
// Handle token usage metadata
const { usageMetadata } = await streamingResult.response
if (usageMetadata) {
const { promptTokenCount = 0, candidatesTokenCount = 0 } = usageMetadata
yield {
type: "usage",
inputTokens: promptTokenCount,
outputTokens: candidatesTokenCount,
totalCost: calculateApiCostOpenAI(model.info, promptTokenCount, candidatesTokenCount, 0, 0),
}
}
}
}
+6 -2
View File
@@ -6,7 +6,6 @@ import { ApiStream } from "../transform/stream"
import { convertToVsCodeLmMessages } from "../transform/vscode-lm-format"
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "../../shared/vsCodeSelectorUtils"
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
import type { LanguageModelChatSelector as LanguageModelChatSelectorFromTypes } from "./types"
// Cline does not update VSCode type definitions or engine requirements to maintain compatibility.
// This declaration (as seen in src/integrations/TerminalManager.ts) provides types for the Language Model API in newer versions of VSCode.
@@ -20,7 +19,12 @@ declare module "vscode" {
Auto = 1,
Required = 2,
}
interface LanguageModelChatSelector extends LanguageModelChatSelectorFromTypes {}
interface LanguageModelChatSelector {
vendor?: string
family?: string
version?: string
id?: string
}
interface LanguageModelChatTool {
name: string
description: string
-1
View File
@@ -34,7 +34,6 @@ export class XAIHandler implements ApiHandler {
temperature: 0,
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
stream: true,
stream_options: { include_usage: true },
reasoning_effort: reasoningEffort,
})
-211
View File
@@ -1,211 +0,0 @@
// This file contains `declare module "vscode"` so we must import it.
import "../providers/vscode-lm"
import { describe, it } from "mocha"
import "should"
import * as vscode from "vscode"
import { Anthropic } from "@anthropic-ai/sdk"
import { asObjectSafe, convertToAnthropicRole, convertToVsCodeLmMessages, convertToAnthropicMessage } from "./vscode-lm-format"
describe("asObjectSafe", () => {
it("should handle falsy values", () => {
asObjectSafe(0).should.deepEqual({})
asObjectSafe("").should.deepEqual({})
asObjectSafe(null).should.deepEqual({})
asObjectSafe(undefined).should.deepEqual({})
})
it("should parse valid JSON strings", () => {
asObjectSafe('{"key": "value"}').should.deepEqual({ key: "value" })
})
it("should return an empty object for invalid JSON strings", () => {
asObjectSafe("invalid json").should.deepEqual({})
})
it("should convert objects to plain objects", () => {
const input = { prop: "value" }
asObjectSafe(input).should.deepEqual(input)
asObjectSafe(input).should.not.equal(input) // Should be a new object
})
it("should convert arrays to plain objects", () => {
const input = ["hello world"]
asObjectSafe(input).should.deepEqual({ 0: "hello world" })
})
})
describe("convertToAnthropicRole", () => {
it("should convert VSCode roles to Anthropic roles", () => {
// @ts-expect-errorTesting with an invalid role
const unknownRole = "unknown" as vscode.LanguageModelChatMessageRole
;(convertToAnthropicRole(vscode.LanguageModelChatMessageRole.Assistant) === "assistant").should.be.true()
;(convertToAnthropicRole(vscode.LanguageModelChatMessageRole.User) === "user").should.be.true()
;(convertToAnthropicRole(unknownRole) === null).should.be.true()
})
})
describe("convertToVsCodeLmMessages", () => {
it("should convert simple string messages", () => {
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there" },
]
const result = convertToVsCodeLmMessages(anthropicMessages)
result.should.have.length(2)
result[0].role.should.equal(vscode.LanguageModelChatMessageRole.User)
result[0].content[0].should.be.instanceof(vscode.LanguageModelTextPart)
const textPart0 = result[0].content[0] as vscode.LanguageModelTextPart
textPart0.should.have.property("value", "Hello")
result[1].role.should.equal(vscode.LanguageModelChatMessageRole.Assistant)
result[1].content[0].should.be.instanceof(vscode.LanguageModelTextPart)
const textPart1 = result[1].content[0] as vscode.LanguageModelTextPart
textPart1.should.have.property("value", "Hi there")
})
it("should convert complex user messages with tool results", () => {
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [
{ type: "text", text: "User text" },
{
type: "tool_result",
tool_use_id: "tool-123",
content: [{ type: "text", text: "Tool result" }],
},
],
},
]
const result = convertToVsCodeLmMessages(anthropicMessages)
result.should.have.length(1)
result[0].role.should.equal(vscode.LanguageModelChatMessageRole.User)
result[0].content.should.have.length(2)
// Check that the first content part is a ToolResultPart
result[0].content[0].should.be.instanceof(vscode.LanguageModelToolResultPart)
const toolResultPart = result[0].content[0] as vscode.LanguageModelToolResultPart
toolResultPart.should.have.property("callId", "tool-123")
// Skip detailed testing of internal structure as it may vary
// Just verify it's the right type with the right ID
// Check the second content part is a TextPart
result[0].content[1].should.be.instanceof(vscode.LanguageModelTextPart)
const textPart = result[0].content[1] as vscode.LanguageModelTextPart
textPart.should.have.property("value", "User text")
})
it("should convert complex assistant messages with tool calls", () => {
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
{
role: "assistant",
content: [
{ type: "text", text: "Assistant text" },
{
type: "tool_use",
id: "tool-123",
name: "testTool",
input: { param: "value" },
},
],
},
]
const result = convertToVsCodeLmMessages(anthropicMessages)
result.should.have.length(1)
result[0].role.should.equal(vscode.LanguageModelChatMessageRole.Assistant)
result[0].content.should.have.length(2)
result[0].content[0].should.be.instanceof(vscode.LanguageModelToolCallPart)
const toolCallPart = result[0].content[0] as vscode.LanguageModelToolCallPart
toolCallPart.should.have.property("callId", "tool-123")
toolCallPart.should.have.property("name", "testTool")
toolCallPart.should.have.property("input")
toolCallPart.input.should.deepEqual({ param: "value" })
result[0].content[1].should.be.instanceof(vscode.LanguageModelTextPart)
const textPart = result[0].content[1] as vscode.LanguageModelTextPart
textPart.should.have.property("value", "Assistant text")
})
it("should handle image blocks with appropriate placeholders", () => {
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: "base64data",
},
},
],
},
]
const result = convertToVsCodeLmMessages(anthropicMessages)
result.should.have.length(1)
result[0].content[0].should.be.instanceof(vscode.LanguageModelTextPart)
const textPart = result[0].content[0] as vscode.LanguageModelTextPart
textPart.should.have.property("value")
textPart.value.should.match(/Image \(base64\): image\/jpeg not supported by VSCode LM API/)
})
})
describe("convertToAnthropicMessage", () => {
it("should convert VSCode assistant messages to Anthropic format", () => {
const vsCodeMsg = vscode.LanguageModelChatMessage.Assistant([
new vscode.LanguageModelTextPart("Test message"),
new vscode.LanguageModelToolCallPart("tool-id", "testTool", { param: "value" }),
])
const result = convertToAnthropicMessage(vsCodeMsg)
result.should.have.property("role", "assistant")
result.should.have.property("content").which.is.an.Array()
result.content.should.have.length(2)
// Check properties carefully to avoid null reference errors
if (result.content && result.content.length >= 1) {
const textContent = result.content[0]
if (textContent) {
textContent.should.have.property("type", "text")
if (textContent.type === "text") {
textContent.should.have.property("text", "Test message")
}
}
}
if (result.content && result.content.length >= 2) {
const toolContent = result.content[1]
if (toolContent) {
toolContent.should.have.property("type", "tool_use")
if (toolContent.type === "tool_use") {
toolContent.should.have.property("id", "tool-id")
toolContent.should.have.property("name", "testTool")
toolContent.should.have.property("input").which.deepEqual({ param: "value" })
}
}
}
})
it("should throw an error for non-assistant messages", () => {
const vsCodeMsg = vscode.LanguageModelChatMessage.User("User message")
try {
convertToAnthropicMessage(vsCodeMsg)
throw new Error("Should have thrown an error")
} catch (error: any) {
error.message.should.match(/Only assistant messages are supported/)
}
})
})
+6 -6
View File
@@ -4,7 +4,7 @@ import * as vscode from "vscode"
/**
* Safely converts a value into a plain object.
*/
export function asObjectSafe(value: any): object {
function asObjectSafe(value: any): object {
// Handle null/undefined
if (!value) {
return {}
@@ -145,9 +145,7 @@ export function convertToVsCodeLmMessages(
return vsCodeLmMessages
}
export function convertToAnthropicRole(
vsCodeLmMessageRole: vscode.LanguageModelChatMessageRole,
): Anthropic.Messages.MessageParam["role"] | null {
export function convertToAnthropicRole(vsCodeLmMessageRole: vscode.LanguageModelChatMessageRole): string | null {
switch (vsCodeLmMessageRole) {
case vscode.LanguageModelChatMessageRole.Assistant:
return "assistant"
@@ -158,8 +156,10 @@ export function convertToAnthropicRole(
}
}
export function convertToAnthropicMessage(vsCodeLmMessage: vscode.LanguageModelChatMessage): Anthropic.Messages.Message {
const anthropicRole = convertToAnthropicRole(vsCodeLmMessage.role)
export async function convertToAnthropicMessage(
vsCodeLmMessage: vscode.LanguageModelChatMessage,
): Promise<Anthropic.Messages.Message> {
const anthropicRole: string | null = convertToAnthropicRole(vsCodeLmMessage.role)
if (anthropicRole !== "assistant") {
throw new Error("Cline <Language Model API>: Only assistant messages are supported.")
}
@@ -4,27 +4,7 @@ import { fileExistsAtPath, isDirectory, readDirectory } from "../../../../utils/
import { formatResponse } from "../../../prompts/responses"
import fs from "fs/promises"
export const getGlobalClineRules = async (globalClineRulesFilePath: string) => {
if (await fileExistsAtPath(globalClineRulesFilePath)) {
if (await isDirectory(globalClineRulesFilePath)) {
try {
const rulesFilePaths = await readDirectory(globalClineRulesFilePath)
const rulesFilesTotalContent = await getClineRulesFilesTotalContent(rulesFilePaths, globalClineRulesFilePath)
const clineRulesFileInstructions = formatResponse.clineRulesGlobalDirectoryInstructions(rulesFilesTotalContent)
return clineRulesFileInstructions
} catch {
console.error(`Failed to read .clinerules directory at ${globalClineRulesFilePath}`)
}
} else {
console.error(`${globalClineRulesFilePath} is not a directory`)
return undefined
}
}
return undefined
}
export const getLocalClineRules = async (cwd: string) => {
export const getClineRules = async (cwd: string) => {
const clineRulesFilePath = path.resolve(cwd, GlobalFileNames.clineRules)
let clineRulesFileInstructions: string | undefined
@@ -34,7 +14,7 @@ export const getLocalClineRules = async (cwd: string) => {
try {
const rulesFilePaths = await readDirectory(path.join(cwd, GlobalFileNames.clineRules))
const rulesFilesTotalContent = await getClineRulesFilesTotalContent(rulesFilePaths, cwd)
clineRulesFileInstructions = formatResponse.clineRulesLocalDirectoryInstructions(cwd, rulesFilesTotalContent)
clineRulesFileInstructions = formatResponse.clineRulesDirectoryInstructions(cwd, rulesFilesTotalContent)
} catch {
console.error(`Failed to read .clinerules directory at ${clineRulesFilePath}`)
}
@@ -42,7 +22,7 @@ export const getLocalClineRules = async (cwd: string) => {
try {
const ruleFileContent = (await fs.readFile(clineRulesFilePath, "utf8")).trim()
if (ruleFileContent) {
clineRulesFileInstructions = formatResponse.clineRulesLocalFileInstructions(cwd, ruleFileContent)
clineRulesFileInstructions = formatResponse.clineRulesFileInstructions(cwd, ruleFileContent)
}
} catch {
console.error(`Failed to read .clinerules file at ${clineRulesFilePath}`)
@@ -53,11 +33,11 @@ export const getLocalClineRules = async (cwd: string) => {
return clineRulesFileInstructions
}
const getClineRulesFilesTotalContent = async (rulesFilePaths: string[], basePath: string) => {
const getClineRulesFilesTotalContent = async (rulesFilePaths: string[], cwd: string) => {
const ruleFilesTotalContent = await Promise.all(
rulesFilePaths.map(async (filePath) => {
const ruleFilePath = path.resolve(basePath, filePath)
const ruleFilePathRelative = path.relative(basePath, ruleFilePath)
const ruleFilePath = path.resolve(cwd, filePath)
const ruleFilePathRelative = path.relative(cwd, ruleFilePath)
return `${ruleFilePathRelative}\n` + (await fs.readFile(ruleFilePath, "utf8")).trim()
}),
).then((contents) => contents.join("\n\n"))
@@ -1,47 +0,0 @@
import { BrowserConnectionInfo } from "../../../shared/proto/browser"
import { EmptyRequest } from "../../../shared/proto/common"
import { Controller } from "../index"
import { getAllExtensionState } from "../../storage/state"
/**
* Get information about the current browser connection
* @param controller The controller instance
* @param request The request message
* @returns The browser connection info
*/
export async function getBrowserConnectionInfo(controller: Controller, request: EmptyRequest): Promise<BrowserConnectionInfo> {
try {
// Get browser settings from extension state
const { browserSettings } = await getAllExtensionState(controller.context)
// Check if there's an active browser session by using the controller's handleWebviewMessage approach
// This is similar to what's done in controller/index.ts for the "getBrowserConnectionInfo" message
if (controller.task?.browserSession) {
// Access the browser session through the controller's task property
// Using indexer notation to access private property
const browserSession = controller.task.browserSession
const connectionInfo = browserSession.getConnectionInfo()
// Convert from BrowserSession.BrowserConnectionInfo to proto.BrowserConnectionInfo
return {
isConnected: connectionInfo.isConnected,
isRemote: connectionInfo.isRemote,
host: connectionInfo.host || "", // Ensure host is never undefined
}
}
// Fallback to browser settings if no active browser session
return {
isConnected: false,
isRemote: !!browserSettings.remoteBrowserEnabled,
host: browserSettings.remoteBrowserHost || "",
}
} catch (error: unknown) {
console.error("Error getting browser connection info:", error)
return {
isConnected: false,
isRemote: false,
host: "",
}
}
}
-15
View File
@@ -1,15 +0,0 @@
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
import { registerAllMethods } from "./methods"
// Create browser service registry
const browserService = createServiceRegistry("browser")
// Export the method handler type and registration function
export type BrowserMethodHandler = ServiceMethodHandler
export const registerMethod = browserService.registerMethod
// Export the request handler
export const handleBrowserServiceRequest = browserService.handleRequest
// Register all browser methods
registerAllMethods()
-14
View File
@@ -1,14 +0,0 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
// Import all method implementations
import { registerMethod } from "./index"
import { getBrowserConnectionInfo } from "./getBrowserConnectionInfo"
import { testBrowserConnection } from "./testBrowserConnection"
// Register all browser service methods
export function registerAllMethods(): void {
// Register each method with the registry
registerMethod("getBrowserConnectionInfo", getBrowserConnectionInfo)
registerMethod("testBrowserConnection", testBrowserConnection)
}
@@ -1,63 +0,0 @@
import { BrowserConnection } from "../../../shared/proto/browser"
import { StringRequest } from "../../../shared/proto/common"
import { Controller } from "../index"
import { getAllExtensionState } from "../../storage/state"
import { BrowserSession } from "../../../services/browser/BrowserSession"
import { discoverChromeInstances } from "../../../services/browser/BrowserDiscovery"
/**
* Test connection to a browser instance
* @param controller The controller instance
* @param request The request message
* @returns The browser connection result
*/
export async function testBrowserConnection(controller: Controller, request: StringRequest): Promise<BrowserConnection> {
try {
const { browserSettings } = await getAllExtensionState(controller.context)
const browserSession = new BrowserSession(controller.context, browserSettings)
const text = request.value || ""
// If no text is provided, try auto-discovery
if (!text) {
try {
const discoveredHost = await discoverChromeInstances()
if (discoveredHost) {
// Test the connection to the discovered host
const result = await browserSession.testConnection(discoveredHost)
return {
success: result.success,
message: `Auto-discovered and tested connection to Chrome at ${discoveredHost}: ${result.message}`,
endpoint: result.endpoint || "",
}
} else {
return {
success: false,
message:
"No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
endpoint: "",
}
}
} catch (error) {
return {
success: false,
message: `Error during auto-discovery: ${error instanceof Error ? error.message : String(error)}`,
endpoint: "",
}
}
} else {
// Test the provided URL
const result = await browserSession.testConnection(text)
return {
success: result.success,
message: result.message,
endpoint: result.endpoint || "",
}
}
} catch (error) {
return {
success: false,
message: `Error testing connection: ${error instanceof Error ? error.message : String(error)}`,
endpoint: "",
}
}
}
-81
View File
@@ -1,81 +0,0 @@
import { Controller } from "./index"
import { handleBrowserServiceRequest } from "./browser/index"
import { ExtensionMessage } from "../../shared/ExtensionMessage"
/**
* Handles gRPC requests from the webview
*/
export class GrpcHandler {
constructor(private controller: Controller) {}
/**
* Handle a gRPC request from the webview
* @param service The service name
* @param method The method name
* @param message The request message
* @param requestId The request ID for response correlation
* @returns The response message or error
*/
async handleRequest(
service: string,
method: string,
message: any,
requestId: string,
): Promise<{
message?: any
error?: string
request_id: string
}> {
try {
// Handle BrowserService requests
if (service === "cline.BrowserService") {
return {
message: await handleBrowserServiceRequest(this.controller, method, message),
request_id: requestId,
}
}
throw new Error(`Unknown service: ${service}`)
} catch (error) {
return {
error: error instanceof Error ? error.message : String(error),
request_id: requestId,
}
}
}
}
/**
* Handle a gRPC request from the webview
* @param controller The controller instance
* @param request The gRPC request
*/
export async function handleGrpcRequest(
controller: Controller,
request: {
service: string
method: string
message: any
request_id: string
},
) {
try {
const grpcHandler = new GrpcHandler(controller)
const response = await grpcHandler.handleRequest(request.service, request.method, request.message, request.request_id)
// Send the response back to the webview
await controller.postMessageToWebview({
type: "grpc_response",
grpc_response: response,
})
} catch (error) {
// Send error response
await controller.postMessageToWebview({
type: "grpc_response",
grpc_response: {
error: error instanceof Error ? error.message : String(error),
request_id: request.request_id,
},
})
}
}
-65
View File
@@ -1,65 +0,0 @@
import { Controller } from "./index"
/**
* Generic type for service method handlers
*/
export type ServiceMethodHandler = (controller: Controller, message: any) => Promise<any>
/**
* Generic service registry for gRPC services
*/
export class ServiceRegistry {
private serviceName: string
private methodRegistry: Record<string, ServiceMethodHandler> = {}
/**
* Create a new service registry
* @param serviceName The name of the service (used for logging)
*/
constructor(serviceName: string) {
this.serviceName = serviceName
}
/**
* Register a method handler
* @param methodName The name of the method to register
* @param handler The handler function for the method
*/
registerMethod(methodName: string, handler: ServiceMethodHandler): void {
this.methodRegistry[methodName] = handler
console.log(`Registered ${this.serviceName} method: ${methodName}`)
}
/**
* Handle a service request
* @param controller The controller instance
* @param method The method name
* @param message The request message
* @returns The response message
*/
async handleRequest(controller: Controller, method: string, message: any): Promise<any> {
const handler = this.methodRegistry[method]
if (!handler) {
throw new Error(`Unknown ${this.serviceName} method: ${method}`)
}
return handler(controller, message)
}
}
/**
* Create a service registry factory function
* @param serviceName The name of the service
* @returns An object with register and handle functions
*/
export function createServiceRegistry(serviceName: string) {
const registry = new ServiceRegistry(serviceName)
return {
registerMethod: (methodName: string, handler: ServiceMethodHandler) => registry.registerMethod(methodName, handler),
handleRequest: (controller: Controller, method: string, message: any) =>
registry.handleRequest(controller, method, message),
}
}
+143 -11
View File
@@ -2,12 +2,13 @@ import { Anthropic } from "@anthropic-ai/sdk"
import axios from "axios"
import type { AxiosRequestConfig } from "axios"
import crypto from "crypto"
import { execa } from "execa"
import fs from "fs/promises"
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
import os from "os"
import pWaitFor from "p-wait-for"
import * as path from "path"
import * as vscode from "vscode"
import { handleGrpcRequest } from "./grpc-handler"
import { buildApiHandler } from "../../api"
import { cleanupLegacyCheckpoints } from "../../integrations/checkpoints/CheckpointMigration"
import { downloadTask } from "../../integrations/misc/export-markdown"
@@ -35,7 +36,7 @@ import { searchCommits } from "../../utils/git"
import { getWorkspacePath } from "../../utils/path"
import { getTotalTasksSize } from "../../utils/storage"
import { openMention } from "../mentions"
import { ensureMcpServersDirectoryExists, ensureSettingsDirectoryExists, GlobalFileNames } from "../storage/disk"
import { GlobalFileNames } from "../storage/disk"
import {
getAllExtensionState,
getGlobalState,
@@ -57,7 +58,7 @@ export class Controller {
private postMessage: (message: ExtensionMessage) => Thenable<boolean> | undefined
private disposables: vscode.Disposable[] = []
task?: Task
private task?: Task
workspaceTracker: WorkspaceTracker
mcpHub: McpHub
accountService: ClineAccountService
@@ -73,8 +74,8 @@ export class Controller {
this.workspaceTracker = new WorkspaceTracker((msg) => this.postMessageToWebview(msg))
this.mcpHub = new McpHub(
() => ensureMcpServersDirectoryExists(),
() => ensureSettingsDirectoryExists(this.context),
() => this.ensureMcpServersDirectoryExists(),
() => this.ensureSettingsDirectoryExists(),
(msg) => this.postMessageToWebview(msg),
this.context.extension?.packageJSON?.version ?? "1.0.0",
)
@@ -300,6 +301,88 @@ export class Controller {
await this.postStateToWebview()
}
break
case "getBrowserConnectionInfo":
try {
// Get the current browser session from Cline if it exists
if (this.task?.browserSession) {
const connectionInfo = this.task.browserSession.getConnectionInfo()
await this.postMessageToWebview({
type: "browserConnectionInfo",
isConnected: connectionInfo.isConnected,
isRemote: connectionInfo.isRemote,
host: connectionInfo.host,
})
} else {
// If no active browser session, just return the settings
const { browserSettings } = await getAllExtensionState(this.context)
await this.postMessageToWebview({
type: "browserConnectionInfo",
isConnected: false,
isRemote: !!browserSettings.remoteBrowserEnabled,
host: browserSettings.remoteBrowserHost,
})
}
} catch (error) {
console.error("Error getting browser connection info:", error)
await this.postMessageToWebview({
type: "browserConnectionInfo",
isConnected: false,
isRemote: false,
})
}
break
case "testBrowserConnection":
try {
const { browserSettings } = await getAllExtensionState(this.context)
const browserSession = new BrowserSession(this.context, browserSettings)
// If no text is provided, try auto-discovery
if (!message.text) {
try {
const discoveredHost = await discoverChromeInstances()
if (discoveredHost) {
// Test the connection to the discovered host
const result = await browserSession.testConnection(discoveredHost)
// Send the result back to the webview
await this.postMessageToWebview({
type: "browserConnectionResult",
success: result.success,
text: `Auto-discovered and tested connection to Chrome at ${discoveredHost}: ${result.message}`,
endpoint: result.endpoint,
})
} else {
await this.postMessageToWebview({
type: "browserConnectionResult",
success: false,
text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
})
}
} catch (error) {
await this.postMessageToWebview({
type: "browserConnectionResult",
success: false,
text: `Error during auto-discovery: ${error instanceof Error ? error.message : String(error)}`,
})
}
} else {
// Test the provided URL
const result = await browserSession.testConnection(message.text)
// Send the result back to the webview
await this.postMessageToWebview({
type: "browserConnectionResult",
success: result.success,
text: result.message,
endpoint: result.endpoint,
})
}
} catch (error) {
await this.postMessageToWebview({
type: "browserConnectionResult",
success: false,
text: `Error testing connection: ${error instanceof Error ? error.message : String(error)}`,
})
}
break
case "discoverBrowser":
try {
const discoveredHost = await discoverChromeInstances()
@@ -829,12 +912,6 @@ export class Controller {
}
break
}
case "grpc_request": {
if (message.grpc_request) {
await handleGrpcRequest(this, message.grpc_request)
}
break
}
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
@@ -1029,6 +1106,61 @@ export class Controller {
}
}
// MCP
async getDocumentsPath(): Promise<string> {
if (process.platform === "win32") {
try {
const { stdout: docsPath } = await execa("powershell", [
"-NoProfile", // Ignore user's PowerShell profile(s)
"-Command",
"[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::MyDocuments)",
])
const trimmedPath = docsPath.trim()
if (trimmedPath) {
return trimmedPath
}
} catch (err) {
console.error("Failed to retrieve Windows Documents path. Falling back to homedir/Documents.")
}
} else if (process.platform === "linux") {
try {
// First check if xdg-user-dir exists
await execa("which", ["xdg-user-dir"])
// If it exists, try to get XDG documents path
const { stdout } = await execa("xdg-user-dir", ["DOCUMENTS"])
const trimmedPath = stdout.trim()
if (trimmedPath) {
return trimmedPath
}
} catch {
// Log error but continue to fallback
console.error("Failed to retrieve XDG Documents path. Falling back to homedir/Documents.")
}
}
// Default fallback for all platforms
return path.join(os.homedir(), "Documents")
}
async ensureMcpServersDirectoryExists(): Promise<string> {
const userDocumentsPath = await this.getDocumentsPath()
const mcpServersDir = path.join(userDocumentsPath, "Cline", "MCP")
try {
await fs.mkdir(mcpServersDir, { recursive: true })
} catch (error) {
return "~/Documents/Cline/MCP" // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine since this path is only ever used in the system prompt
}
return mcpServersDir
}
async ensureSettingsDirectoryExists(): Promise<string> {
const settingsDir = path.join(this.context.globalStorageUri.fsPath, "settings")
await fs.mkdir(settingsDir, { recursive: true })
return settingsDir
}
// VSCode LM API
private async getVsCodeLmModels() {
+2 -5
View File
@@ -204,13 +204,10 @@ Otherwise, if you have not completed the task and do not need additional informa
clineIgnoreInstructions: (content: string) =>
`# .clineignore\n\n(The following is provided by a root-level .clineignore file where the user has specified files and directories that should not be accessed. When using list_files, you'll notice a ${LOCK_TEXT_SYMBOL} next to files that are blocked. Attempting to access the file's contents e.g. through read_file will result in an error.)\n\n${content}\n.clineignore`,
clineRulesGlobalDirectoryInstructions: (content: string) =>
`# .clinerules/\n\nThe following is provided by a global .clinerules/ directory where the user has specified instructions:\n\n${content}`,
clineRulesLocalDirectoryInstructions: (cwd: string, content: string) =>
clineRulesDirectoryInstructions: (cwd: string, content: string) =>
`# .clinerules/\n\nThe following is provided by a root-level .clinerules/ directory where the user has specified instructions for this working directory (${cwd.toPosix()})\n\n${content}`,
clineRulesLocalFileInstructions: (cwd: string, content: string) =>
clineRulesFileInstructions: (cwd: string, content: string) =>
`# .clinerules\n\nThe following is provided by a root-level .clinerules file where the user has specified instructions for this working directory (${cwd.toPosix()})\n\n${content}`,
}
+3 -7
View File
@@ -619,8 +619,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
export function addUserInstructions(
settingsCustomInstructions?: string,
globalClineRulesFileInstructions?: string,
localClineRulesFileInstructions?: string,
clineRulesFileInstructions?: string,
clineIgnoreInstructions?: string,
preferredLanguageInstructions?: string,
) {
@@ -631,11 +630,8 @@ export function addUserInstructions(
if (settingsCustomInstructions) {
customInstructions += settingsCustomInstructions + "\n\n"
}
if (globalClineRulesFileInstructions) {
customInstructions += globalClineRulesFileInstructions + "\n\n"
}
if (localClineRulesFileInstructions) {
customInstructions += localClineRulesFileInstructions + "\n\n"
if (clineRulesFileInstructions) {
customInstructions += clineRulesFileInstructions + "\n\n"
}
if (clineIgnoreInstructions) {
customInstructions += clineIgnoreInstructions
-67
View File
@@ -5,9 +5,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
import { fileExistsAtPath } from "../../utils/fs"
import { ClineMessage } from "../../shared/ExtensionMessage"
import { TaskMetadata } from "../context/context-tracking/ContextTrackerTypes"
import os from "os"
import { execa } from "execa"
export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json",
contextHistory: "context_history.json",
@@ -18,42 +15,6 @@ export const GlobalFileNames = {
taskMetadata: "task_metadata.json",
}
export async function getDocumentsPath(): Promise<string> {
if (process.platform === "win32") {
try {
const { stdout: docsPath } = await execa("powershell", [
"-NoProfile", // Ignore user's PowerShell profile(s)
"-Command",
"[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::MyDocuments)",
])
const trimmedPath = docsPath.trim()
if (trimmedPath) {
return trimmedPath
}
} catch (err) {
console.error("Failed to retrieve Windows Documents path. Falling back to homedir/Documents.")
}
} else if (process.platform === "linux") {
try {
// First check if xdg-user-dir exists
await execa("which", ["xdg-user-dir"])
// If it exists, try to get XDG documents path
const { stdout } = await execa("xdg-user-dir", ["DOCUMENTS"])
const trimmedPath = stdout.trim()
if (trimmedPath) {
return trimmedPath
}
} catch {
// Log error but continue to fallback
console.error("Failed to retrieve XDG Documents path. Falling back to homedir/Documents.")
}
}
// Default fallback for all platforms
return path.join(os.homedir(), "Documents")
}
export async function ensureTaskDirectoryExists(context: vscode.ExtensionContext, taskId: string): Promise<string> {
const globalStoragePath = context.globalStorageUri.fsPath
const taskDir = path.join(globalStoragePath, "tasks", taskId)
@@ -61,34 +22,6 @@ export async function ensureTaskDirectoryExists(context: vscode.ExtensionContext
return taskDir
}
export async function ensureRulesDirectoryExists(): Promise<string> {
const userDocumentsPath = await getDocumentsPath()
const clineRulesDir = path.join(userDocumentsPath, "Cline", "Rules")
try {
await fs.mkdir(clineRulesDir, { recursive: true })
} catch (error) {
return path.join(os.homedir(), "Documents", "Cline", "Rules") // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine because we will fail gracefully with a path that does not exist
}
return clineRulesDir
}
export async function ensureMcpServersDirectoryExists(): Promise<string> {
const userDocumentsPath = await getDocumentsPath()
const mcpServersDir = path.join(userDocumentsPath, "Cline", "MCP")
try {
await fs.mkdir(mcpServersDir, { recursive: true })
} catch (error) {
return "~/Documents/Cline/MCP" // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine since this path is only ever used in the system prompt
}
return mcpServersDir
}
export async function ensureSettingsDirectoryExists(context: vscode.ExtensionContext): Promise<string> {
const settingsDir = path.join(context.globalStorageUri.fsPath, "settings")
await fs.mkdir(settingsDir, { recursive: true })
return settingsDir
}
export async function getSavedApiConversationHistory(
context: vscode.ExtensionContext,
taskId: string,
+5 -21
View File
@@ -55,7 +55,7 @@ import { DEFAULT_LANGUAGE_SETTINGS, getLanguageKey, LanguageDisplay } from "../.
import { ClineAskResponse, ClineCheckpointRestore } from "../../shared/WebviewMessage"
import { calculateApiCostAnthropic } from "../../utils/cost"
import { fileExistsAtPath } from "../../utils/fs"
import { arePathsEqual, getReadablePath, isLocatedInWorkspace } from "../../utils/path"
import { arePathsEqual, getReadablePath } from "../../utils/path"
import { fixModelHtmlEscaping, removeInvalidChars } from "../../utils/string"
import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from ".././assistant-message"
import { constructNewFileContent } from ".././assistant-message/diff"
@@ -73,16 +73,15 @@ import {
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
import { McpHub } from "../../services/mcp/McpHub"
import { ContextManager } from "../context/context-management/ContextManager"
import { getClineRules } from "../context/instructions/user-instructions/cline-rules"
import { loadMcpDocumentation } from "../prompts/loadMcpDocumentation"
import {
ensureRulesDirectoryExists,
ensureTaskDirectoryExists,
getSavedApiConversationHistory,
getSavedClineMessages,
saveApiConversationHistory,
saveClineMessages,
} from "../storage/disk"
import { getGlobalClineRules, getLocalClineRules } from "../context/instructions/user-instructions/cline-rules"
import { getGlobalState } from "../storage/state"
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
@@ -1280,10 +1279,7 @@ export class Task {
? `# Preferred Language\n\nSpeak in ${preferredLanguage}.`
: ""
const localClineRulesFileInstructions = await getLocalClineRules(cwd)
const globalClineRulesFilePath = await ensureRulesDirectoryExists()
const globalClineRulesFileInstructions = await getGlobalClineRules(globalClineRulesFilePath)
const clineRulesFileInstructions = await getClineRules(cwd)
const clineIgnoreContent = this.clineIgnoreController.clineIgnoreContent
let clineIgnoreInstructions: string | undefined
@@ -1293,16 +1289,14 @@ export class Task {
if (
settingsCustomInstructions ||
globalClineRulesFileInstructions ||
localClineRulesFileInstructions ||
clineRulesFileInstructions ||
clineIgnoreInstructions ||
preferredLanguageInstructions
) {
// altering the system prompt mid-task will break the prompt cache, but in the grand scheme this will not change often so it's better to not pollute user messages with it the way we have to with <potentially relevant details>
systemPrompt += addUserInstructions(
settingsCustomInstructions,
globalClineRulesFileInstructions,
localClineRulesFileInstructions,
clineRulesFileInstructions,
clineIgnoreInstructions,
preferredLanguageInstructions,
)
@@ -1755,7 +1749,6 @@ export class Task {
tool: fileExists ? "editedExistingFile" : "newFileCreated",
path: getReadablePath(cwd, removeClosingTag("path", relPath)),
content: diff || content,
operationIsLocatedInWorkspace: isLocatedInWorkspace(relPath),
}
if (block.partial) {
@@ -1819,7 +1812,6 @@ export class Task {
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: diff || content,
operationIsLocatedInWorkspace: isLocatedInWorkspace(relPath),
// ? formatResponse.createPrettyPatch(
// relPath,
// this.diffViewProvider.originalContent,
@@ -1945,7 +1937,6 @@ export class Task {
const partialMessage = JSON.stringify({
...sharedMessageProps,
content: undefined,
operationIsLocatedInWorkspace: isLocatedInWorkspace(relPath),
} satisfies ClineSayTool)
if (this.shouldAutoApproveToolWithPath(block.name, block.params.path)) {
this.removeLastPartialMessageIfExistsWithType("ask", "tool")
@@ -1976,7 +1967,6 @@ export class Task {
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: absolutePath,
operationIsLocatedInWorkspace: isLocatedInWorkspace(relPath),
} satisfies ClineSayTool)
if (this.shouldAutoApproveToolWithPath(block.name, block.params.path)) {
this.removeLastPartialMessageIfExistsWithType("ask", "tool")
@@ -2024,7 +2014,6 @@ export class Task {
const partialMessage = JSON.stringify({
...sharedMessageProps,
content: "",
operationIsLocatedInWorkspace: isLocatedInWorkspace(block.params.path),
} satisfies ClineSayTool)
if (this.shouldAutoApproveToolWithPath(block.name, block.params.path)) {
this.removeLastPartialMessageIfExistsWithType("ask", "tool")
@@ -2056,7 +2045,6 @@ export class Task {
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: result,
operationIsLocatedInWorkspace: isLocatedInWorkspace(block.params.path),
} satisfies ClineSayTool)
if (this.shouldAutoApproveToolWithPath(block.name, block.params.path)) {
this.removeLastPartialMessageIfExistsWithType("ask", "tool")
@@ -2096,7 +2084,6 @@ export class Task {
const partialMessage = JSON.stringify({
...sharedMessageProps,
content: "",
operationIsLocatedInWorkspace: isLocatedInWorkspace(block.params.path),
} satisfies ClineSayTool)
if (this.shouldAutoApproveToolWithPath(block.name, block.params.path)) {
this.removeLastPartialMessageIfExistsWithType("ask", "tool")
@@ -2125,7 +2112,6 @@ export class Task {
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: result,
operationIsLocatedInWorkspace: isLocatedInWorkspace(block.params.path),
} satisfies ClineSayTool)
if (this.shouldAutoApproveToolWithPath(block.name, block.params.path)) {
this.removeLastPartialMessageIfExistsWithType("ask", "tool")
@@ -2169,7 +2155,6 @@ export class Task {
const partialMessage = JSON.stringify({
...sharedMessageProps,
content: "",
operationIsLocatedInWorkspace: isLocatedInWorkspace(block.params.path),
} satisfies ClineSayTool)
if (this.shouldAutoApproveToolWithPath(block.name, block.params.path)) {
this.removeLastPartialMessageIfExistsWithType("ask", "tool")
@@ -2206,7 +2191,6 @@ export class Task {
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: results,
operationIsLocatedInWorkspace: isLocatedInWorkspace(block.params.path),
} satisfies ClineSayTool)
if (this.shouldAutoApproveToolWithPath(block.name, block.params.path)) {
this.removeLastPartialMessageIfExistsWithType("ask", "tool")
+1 -1
View File
@@ -381,7 +381,7 @@ export function activate(context: vscode.ExtensionContext) {
// Register the command handler
context.subscriptions.push(
vscode.commands.registerCommand("cline.fixWithCline", async (range: vscode.Range, diagnostics: vscode.Diagnostic[]) => {
vscode.commands.registerCommand("cline.fixWithCline", async (range: vscode.Range, diagnostics: any[]) => {
const editor = vscode.window.activeTextEditor
if (!editor) {
return
+1 -1
View File
@@ -13,7 +13,7 @@ export async function openImage(dataUri: string) {
const imageBuffer = Buffer.from(base64Data, "base64")
const tempFilePath = path.join(os.tmpdir(), `temp_image_${Date.now()}.${format}`)
try {
await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), new Uint8Array(imageBuffer))
await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), imageBuffer)
await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
} catch (error) {
vscode.window.showErrorMessage(`Error opening image: ${error}`)
+1 -2
View File
@@ -3,7 +3,6 @@ import * as vscode from "vscode"
import { version as extensionVersion } from "../../../package.json"
import type { TaskFeedbackType } from "../../shared/WebviewMessage"
import type { BrowserSettings } from "../../shared/BrowserSettings"
/**
* PostHogClient handles telemetry event tracking for the Cline extension
@@ -475,7 +474,7 @@ class PostHogClient {
* @param taskId Unique identifier for the task
* @param browserSettings The browser settings being used
*/
public captureBrowserToolStart(taskId: string, browserSettings: BrowserSettings) {
public captureBrowserToolStart(taskId: string, browserSettings: any) {
this.capture({
event: PostHogClient.EVENTS.TASK.BROWSER_TOOL_START,
properties: {
+12 -12
View File
@@ -131,11 +131,11 @@ async function updateAutoApprovalSettings(context: vscode.ExtensionContext, prov
export function createTestServer(webviewProvider?: WebviewProvider): http.Server {
// Try to show the Cline sidebar
Logger.log("[createTestServer] Opening Cline in sidebar...")
vscode.commands.executeCommand("workbench.view.claude-dev-ActivityBar")
vscode.commands.executeCommand('workbench.view.claude-dev-ActivityBar');
// Then ensure the webview is focused/loaded
vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
vscode.commands.executeCommand('claude-dev.SidebarProvider.focus');
// Update auto approval settings if webviewProvider is available
if (webviewProvider?.controller?.context) {
updateAutoApprovalSettings(webviewProvider.controller.context, webviewProvider)
@@ -159,12 +159,12 @@ export function createTestServer(webviewProvider?: WebviewProvider): http.Server
if (req.method === "POST" && req.url === "/shutdown") {
res.writeHead(200)
res.end(JSON.stringify({ success: true, message: "Server shutting down" }))
// Shut down the server after sending the response
setTimeout(() => {
shutdownTestServer()
}, 100)
return
}
@@ -250,26 +250,26 @@ export function createTestServer(webviewProvider?: WebviewProvider): http.Server
// If API key is provided, update the API configuration
if (apiKey) {
Logger.log("API key provided, updating API configuration")
// Get current API configuration
const { apiConfiguration } = await getAllExtensionState(visibleWebview.controller.context)
// Update API configuration with API key
const updatedConfig = {
...apiConfiguration,
apiProvider: "cline" as ApiProvider,
clineApiKey: apiKey,
clineApiKey: apiKey
}
// Store the API key securely
await storeSecret(visibleWebview.controller.context, "clineApiKey", apiKey)
// Update the API configuration
await updateApiConfiguration(visibleWebview.controller.context, updatedConfig)
// Update global state to use cline provider
await updateGlobalState(visibleWebview.controller.context, "apiProvider", "cline" as ApiProvider)
// Post state to webview to reflect changes
await visibleWebview.controller.postStateToWebview()
}
+7 -8
View File
@@ -43,12 +43,12 @@ export interface ExtensionMessage {
| "totalTasksSize"
| "addToInput"
| "browserConnectionResult"
| "browserConnectionInfo"
| "detectedChromePath"
| "scrollToSettings"
| "browserRelaunchResult"
| "relativePathsResponse" // Handles single and multiple path responses
| "fileSearchResults"
| "grpc_response" // New type for gRPC responses
text?: string
paths?: (string | null)[] // Used for relativePathsResponse
action?:
@@ -109,11 +109,6 @@ export interface ExtensionMessage {
error?: string
}
tab?: McpViewTab
grpc_response?: {
message?: any // JSON serialized protobuf message
request_id: string // Same ID as the request
error?: string // Optional error message
}
}
export type Invoke = "sendMessage" | "primaryButtonClick" | "secondaryButtonClick"
@@ -159,7 +154,6 @@ export interface ClineMessage {
partial?: boolean
lastCheckpointHash?: string
isCheckpointCheckedOut?: boolean
isOperationOutsideWorkspace?: boolean
conversationHistoryIndex?: number
conversationHistoryDeletedRange?: [number, number] // for when conversation history is truncated for API requests
}
@@ -221,7 +215,6 @@ export interface ClineSayTool {
content?: string
regex?: string
filePattern?: string
operationIsLocatedInWorkspace?: boolean
}
// must keep in sync with system prompt
@@ -241,6 +234,12 @@ export type BrowserActionResult = {
currentMousePosition?: string
}
export interface BrowserConnectionInfo {
isConnected: boolean
isRemote: boolean
host?: string
}
export interface ClineAskUseMcpServer {
serverName: string
type: "use_mcp_tool" | "access_mcp_resource"
+3 -7
View File
@@ -38,6 +38,8 @@ export interface WebviewMessage {
| "autoApprovalSettings"
| "browserSettings"
| "discoverBrowser"
| "testBrowserConnection"
| "browserConnectionResult"
| "browserRelaunchResult"
| "togglePlanActMode"
| "checkpointDiff"
@@ -72,13 +74,13 @@ export interface WebviewMessage {
| "requestTotalTasksSize"
| "relaunchChromeDebugMode"
| "taskFeedback"
| "getBrowserConnectionInfo"
| "getDetectedChromePath"
| "detectedChromePath"
| "scrollToSettings"
| "getRelativePaths" // Handles single and multiple URI resolution
| "searchFiles"
| "toggleFavoriteModel"
| "grpc_request"
// | "relaunchChromeDebugMode"
text?: string
uris?: string[] // Used for getRelativePaths
@@ -115,12 +117,6 @@ export interface WebviewMessage {
query?: string
// For toggleFavoriteModel
modelId?: string
grpc_request?: {
service: string
method: string
message: any // JSON serialized protobuf message
request_id: string // For correlating requests and responses
}
}
export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
+2 -31
View File
@@ -1,5 +1,3 @@
import type { LanguageModelChatSelector } from "../api/providers/types"
export type ApiProvider =
| "anthropic"
| "openrouter"
@@ -69,7 +67,7 @@ export interface ApiHandlerOptions {
doubaoApiKey?: string
mistralApiKey?: string
azureApiVersion?: string
vsCodeLmModelSelector?: LanguageModelChatSelector
vsCodeLmModelSelector?: any
o3MiniReasoningEffort?: string
qwenApiLine?: string
asksageApiUrl?: string
@@ -604,35 +602,8 @@ export const geminiModels = {
// OpenAI Native
// https://openai.com/api/pricing/
export type OpenAiNativeModelId = keyof typeof openAiNativeModels
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-4.1"
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-4o"
export const openAiNativeModels = {
"gpt-4.1": {
maxTokens: 32_768,
contextWindow: 1_047_576,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 2,
outputPrice: 8,
cacheReadsPrice: 0.5,
},
"gpt-4.1-mini": {
maxTokens: 32_768,
contextWindow: 1_047_576,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.4,
outputPrice: 1.6,
cacheReadsPrice: 0.1,
},
"gpt-4.1-nano": {
maxTokens: 32_768,
contextWindow: 1_047_576,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.1,
outputPrice: 0.4,
cacheReadsPrice: 0.025,
},
"o3-mini": {
maxTokens: 100_000,
contextWindow: 200_000,
-261
View File
@@ -1,261 +0,0 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.7.0
// protoc v6.30.1
// source: browser.proto
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"
import { EmptyRequest, StringRequest } from "./common"
export const protobufPackage = "cline"
export interface BrowserConnectionInfo {
isConnected: boolean
isRemote: boolean
host?: string | undefined
}
export interface BrowserConnection {
success: boolean
message: string
endpoint?: string | undefined
}
function createBaseBrowserConnectionInfo(): BrowserConnectionInfo {
return { isConnected: false, isRemote: false, host: undefined }
}
export const BrowserConnectionInfo: MessageFns<BrowserConnectionInfo> = {
encode(message: BrowserConnectionInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.isConnected !== false) {
writer.uint32(8).bool(message.isConnected)
}
if (message.isRemote !== false) {
writer.uint32(16).bool(message.isRemote)
}
if (message.host !== undefined) {
writer.uint32(26).string(message.host)
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): BrowserConnectionInfo {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseBrowserConnectionInfo()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break
}
message.isConnected = reader.bool()
continue
}
case 2: {
if (tag !== 16) {
break
}
message.isRemote = reader.bool()
continue
}
case 3: {
if (tag !== 26) {
break
}
message.host = reader.string()
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): BrowserConnectionInfo {
return {
isConnected: isSet(object.isConnected) ? globalThis.Boolean(object.isConnected) : false,
isRemote: isSet(object.isRemote) ? globalThis.Boolean(object.isRemote) : false,
host: isSet(object.host) ? globalThis.String(object.host) : undefined,
}
},
toJSON(message: BrowserConnectionInfo): unknown {
const obj: any = {}
if (message.isConnected !== false) {
obj.isConnected = message.isConnected
}
if (message.isRemote !== false) {
obj.isRemote = message.isRemote
}
if (message.host !== undefined) {
obj.host = message.host
}
return obj
},
create<I extends Exact<DeepPartial<BrowserConnectionInfo>, I>>(base?: I): BrowserConnectionInfo {
return BrowserConnectionInfo.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<BrowserConnectionInfo>, I>>(object: I): BrowserConnectionInfo {
const message = createBaseBrowserConnectionInfo()
message.isConnected = object.isConnected ?? false
message.isRemote = object.isRemote ?? false
message.host = object.host ?? undefined
return message
},
}
function createBaseBrowserConnection(): BrowserConnection {
return { success: false, message: "", endpoint: undefined }
}
export const BrowserConnection: MessageFns<BrowserConnection> = {
encode(message: BrowserConnection, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.success !== false) {
writer.uint32(8).bool(message.success)
}
if (message.message !== "") {
writer.uint32(18).string(message.message)
}
if (message.endpoint !== undefined) {
writer.uint32(26).string(message.endpoint)
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): BrowserConnection {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseBrowserConnection()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break
}
message.success = reader.bool()
continue
}
case 2: {
if (tag !== 18) {
break
}
message.message = reader.string()
continue
}
case 3: {
if (tag !== 26) {
break
}
message.endpoint = reader.string()
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): BrowserConnection {
return {
success: isSet(object.success) ? globalThis.Boolean(object.success) : false,
message: isSet(object.message) ? globalThis.String(object.message) : "",
endpoint: isSet(object.endpoint) ? globalThis.String(object.endpoint) : undefined,
}
},
toJSON(message: BrowserConnection): unknown {
const obj: any = {}
if (message.success !== false) {
obj.success = message.success
}
if (message.message !== "") {
obj.message = message.message
}
if (message.endpoint !== undefined) {
obj.endpoint = message.endpoint
}
return obj
},
create<I extends Exact<DeepPartial<BrowserConnection>, I>>(base?: I): BrowserConnection {
return BrowserConnection.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<BrowserConnection>, I>>(object: I): BrowserConnection {
const message = createBaseBrowserConnection()
message.success = object.success ?? false
message.message = object.message ?? ""
message.endpoint = object.endpoint ?? undefined
return message
},
}
export type BrowserServiceDefinition = typeof BrowserServiceDefinition
export const BrowserServiceDefinition = {
name: "BrowserService",
fullName: "cline.BrowserService",
methods: {
getBrowserConnectionInfo: {
name: "getBrowserConnectionInfo",
requestType: EmptyRequest,
requestStream: false,
responseType: BrowserConnectionInfo,
responseStream: false,
options: {},
},
testBrowserConnection: {
name: "testBrowserConnection",
requestType: StringRequest,
requestStream: false,
responseType: BrowserConnection,
responseStream: false,
options: {},
},
},
} as const
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined
export type DeepPartial<T> = T extends Builtin
? T
: T extends globalThis.Array<infer U>
? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T extends {}
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>
type KeysOfUnion<T> = T extends T ? keyof T : never
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never }
function isSet(value: any): boolean {
return value !== null && value !== undefined
}
export interface MessageFns<T> {
encode(message: T, writer?: BinaryWriter): BinaryWriter
decode(input: BinaryReader | Uint8Array, length?: number): T
fromJSON(object: any): T
toJSON(message: T): unknown
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T
}
-644
View File
@@ -1,644 +0,0 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.7.0
// protoc v6.30.1
// source: common.proto
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"
export const protobufPackage = "cline"
export interface Metadata {}
export interface EmptyRequest {
metadata?: Metadata | undefined
}
export interface Empty {}
export interface StringRequest {
metadata?: Metadata | undefined
value: string
}
export interface String {
value: string
}
export interface Int64Request {
metadata?: Metadata | undefined
value: number
}
export interface Int64 {
value: number
}
export interface BytesRequest {
metadata?: Metadata | undefined
value: Buffer
}
export interface Bytes {
value: Buffer
}
function createBaseMetadata(): Metadata {
return {}
}
export const Metadata: MessageFns<Metadata> = {
encode(_: Metadata, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): Metadata {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseMetadata()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(_: any): Metadata {
return {}
},
toJSON(_: Metadata): unknown {
const obj: any = {}
return obj
},
create<I extends Exact<DeepPartial<Metadata>, I>>(base?: I): Metadata {
return Metadata.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<Metadata>, I>>(_: I): Metadata {
const message = createBaseMetadata()
return message
},
}
function createBaseEmptyRequest(): EmptyRequest {
return { metadata: undefined }
}
export const EmptyRequest: MessageFns<EmptyRequest> = {
encode(message: EmptyRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.metadata !== undefined) {
Metadata.encode(message.metadata, writer.uint32(10).fork()).join()
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): EmptyRequest {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseEmptyRequest()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break
}
message.metadata = Metadata.decode(reader, reader.uint32())
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): EmptyRequest {
return { metadata: isSet(object.metadata) ? Metadata.fromJSON(object.metadata) : undefined }
},
toJSON(message: EmptyRequest): unknown {
const obj: any = {}
if (message.metadata !== undefined) {
obj.metadata = Metadata.toJSON(message.metadata)
}
return obj
},
create<I extends Exact<DeepPartial<EmptyRequest>, I>>(base?: I): EmptyRequest {
return EmptyRequest.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<EmptyRequest>, I>>(object: I): EmptyRequest {
const message = createBaseEmptyRequest()
message.metadata =
object.metadata !== undefined && object.metadata !== null ? Metadata.fromPartial(object.metadata) : undefined
return message
},
}
function createBaseEmpty(): Empty {
return {}
}
export const Empty: MessageFns<Empty> = {
encode(_: Empty, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): Empty {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseEmpty()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(_: any): Empty {
return {}
},
toJSON(_: Empty): unknown {
const obj: any = {}
return obj
},
create<I extends Exact<DeepPartial<Empty>, I>>(base?: I): Empty {
return Empty.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<Empty>, I>>(_: I): Empty {
const message = createBaseEmpty()
return message
},
}
function createBaseStringRequest(): StringRequest {
return { metadata: undefined, value: "" }
}
export const StringRequest: MessageFns<StringRequest> = {
encode(message: StringRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.metadata !== undefined) {
Metadata.encode(message.metadata, writer.uint32(10).fork()).join()
}
if (message.value !== "") {
writer.uint32(18).string(message.value)
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): StringRequest {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseStringRequest()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break
}
message.metadata = Metadata.decode(reader, reader.uint32())
continue
}
case 2: {
if (tag !== 18) {
break
}
message.value = reader.string()
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): StringRequest {
return {
metadata: isSet(object.metadata) ? Metadata.fromJSON(object.metadata) : undefined,
value: isSet(object.value) ? globalThis.String(object.value) : "",
}
},
toJSON(message: StringRequest): unknown {
const obj: any = {}
if (message.metadata !== undefined) {
obj.metadata = Metadata.toJSON(message.metadata)
}
if (message.value !== "") {
obj.value = message.value
}
return obj
},
create<I extends Exact<DeepPartial<StringRequest>, I>>(base?: I): StringRequest {
return StringRequest.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<StringRequest>, I>>(object: I): StringRequest {
const message = createBaseStringRequest()
message.metadata =
object.metadata !== undefined && object.metadata !== null ? Metadata.fromPartial(object.metadata) : undefined
message.value = object.value ?? ""
return message
},
}
function createBaseString(): String {
return { value: "" }
}
export const String: MessageFns<String> = {
encode(message: String, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.value !== "") {
writer.uint32(10).string(message.value)
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): String {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseString()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break
}
message.value = reader.string()
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): String {
return { value: isSet(object.value) ? globalThis.String(object.value) : "" }
},
toJSON(message: String): unknown {
const obj: any = {}
if (message.value !== "") {
obj.value = message.value
}
return obj
},
create<I extends Exact<DeepPartial<String>, I>>(base?: I): String {
return String.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<String>, I>>(object: I): String {
const message = createBaseString()
message.value = object.value ?? ""
return message
},
}
function createBaseInt64Request(): Int64Request {
return { metadata: undefined, value: 0 }
}
export const Int64Request: MessageFns<Int64Request> = {
encode(message: Int64Request, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.metadata !== undefined) {
Metadata.encode(message.metadata, writer.uint32(10).fork()).join()
}
if (message.value !== 0) {
writer.uint32(16).int64(message.value)
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): Int64Request {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseInt64Request()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break
}
message.metadata = Metadata.decode(reader, reader.uint32())
continue
}
case 2: {
if (tag !== 16) {
break
}
message.value = longToNumber(reader.int64())
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): Int64Request {
return {
metadata: isSet(object.metadata) ? Metadata.fromJSON(object.metadata) : undefined,
value: isSet(object.value) ? globalThis.Number(object.value) : 0,
}
},
toJSON(message: Int64Request): unknown {
const obj: any = {}
if (message.metadata !== undefined) {
obj.metadata = Metadata.toJSON(message.metadata)
}
if (message.value !== 0) {
obj.value = Math.round(message.value)
}
return obj
},
create<I extends Exact<DeepPartial<Int64Request>, I>>(base?: I): Int64Request {
return Int64Request.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<Int64Request>, I>>(object: I): Int64Request {
const message = createBaseInt64Request()
message.metadata =
object.metadata !== undefined && object.metadata !== null ? Metadata.fromPartial(object.metadata) : undefined
message.value = object.value ?? 0
return message
},
}
function createBaseInt64(): Int64 {
return { value: 0 }
}
export const Int64: MessageFns<Int64> = {
encode(message: Int64, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.value !== 0) {
writer.uint32(8).int64(message.value)
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): Int64 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseInt64()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break
}
message.value = longToNumber(reader.int64())
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): Int64 {
return { value: isSet(object.value) ? globalThis.Number(object.value) : 0 }
},
toJSON(message: Int64): unknown {
const obj: any = {}
if (message.value !== 0) {
obj.value = Math.round(message.value)
}
return obj
},
create<I extends Exact<DeepPartial<Int64>, I>>(base?: I): Int64 {
return Int64.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<Int64>, I>>(object: I): Int64 {
const message = createBaseInt64()
message.value = object.value ?? 0
return message
},
}
function createBaseBytesRequest(): BytesRequest {
return { metadata: undefined, value: Buffer.alloc(0) }
}
export const BytesRequest: MessageFns<BytesRequest> = {
encode(message: BytesRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.metadata !== undefined) {
Metadata.encode(message.metadata, writer.uint32(10).fork()).join()
}
if (message.value.length !== 0) {
writer.uint32(18).bytes(message.value)
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): BytesRequest {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseBytesRequest()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break
}
message.metadata = Metadata.decode(reader, reader.uint32())
continue
}
case 2: {
if (tag !== 18) {
break
}
message.value = Buffer.from(reader.bytes())
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): BytesRequest {
return {
metadata: isSet(object.metadata) ? Metadata.fromJSON(object.metadata) : undefined,
value: isSet(object.value) ? Buffer.from(bytesFromBase64(object.value)) : Buffer.alloc(0),
}
},
toJSON(message: BytesRequest): unknown {
const obj: any = {}
if (message.metadata !== undefined) {
obj.metadata = Metadata.toJSON(message.metadata)
}
if (message.value.length !== 0) {
obj.value = base64FromBytes(message.value)
}
return obj
},
create<I extends Exact<DeepPartial<BytesRequest>, I>>(base?: I): BytesRequest {
return BytesRequest.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<BytesRequest>, I>>(object: I): BytesRequest {
const message = createBaseBytesRequest()
message.metadata =
object.metadata !== undefined && object.metadata !== null ? Metadata.fromPartial(object.metadata) : undefined
message.value = object.value ?? Buffer.alloc(0)
return message
},
}
function createBaseBytes(): Bytes {
return { value: Buffer.alloc(0) }
}
export const Bytes: MessageFns<Bytes> = {
encode(message: Bytes, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.value.length !== 0) {
writer.uint32(10).bytes(message.value)
}
return writer
},
decode(input: BinaryReader | Uint8Array, length?: number): Bytes {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
let end = length === undefined ? reader.len : reader.pos + length
const message = createBaseBytes()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break
}
message.value = Buffer.from(reader.bytes())
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): Bytes {
return { value: isSet(object.value) ? Buffer.from(bytesFromBase64(object.value)) : Buffer.alloc(0) }
},
toJSON(message: Bytes): unknown {
const obj: any = {}
if (message.value.length !== 0) {
obj.value = base64FromBytes(message.value)
}
return obj
},
create<I extends Exact<DeepPartial<Bytes>, I>>(base?: I): Bytes {
return Bytes.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<Bytes>, I>>(object: I): Bytes {
const message = createBaseBytes()
message.value = object.value ?? Buffer.alloc(0)
return message
},
}
function bytesFromBase64(b64: string): Uint8Array {
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"))
}
function base64FromBytes(arr: Uint8Array): string {
return globalThis.Buffer.from(arr).toString("base64")
}
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined
export type DeepPartial<T> = T extends Builtin
? T
: T extends globalThis.Array<infer U>
? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T extends {}
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>
type KeysOfUnion<T> = T extends T ? keyof T : never
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never }
function longToNumber(int64: { toString(): string }): number {
const num = globalThis.Number(int64.toString())
if (num > globalThis.Number.MAX_SAFE_INTEGER) {
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER")
}
if (num < globalThis.Number.MIN_SAFE_INTEGER) {
throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER")
}
return num
}
function isSet(value: any): boolean {
return value !== null && value !== undefined
}
export interface MessageFns<T> {
encode(message: T, writer?: BinaryWriter): BinaryWriter
decode(input: BinaryReader | Uint8Array, length?: number): T
fromJSON(object: any): T
toJSON(message: T): unknown
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T
}
-22
View File
@@ -1,7 +1,6 @@
import * as path from "path"
import os from "os"
import * as vscode from "vscode"
import { realpathSync } from "fs"
/*
The Node.js 'path' module resolves and normalizes paths differently depending on the platform:
@@ -111,24 +110,3 @@ export const getWorkspacePath = (defaultCwdPath = "") => {
}
return cwdPath
}
export const isLocatedInWorkspace = (pathToCheck: string = ""): boolean => {
const workspacePath = getWorkspacePath()
// Handle long paths in Windows
if (pathToCheck.startsWith("\\\\?\\") || workspacePath.startsWith("\\\\?\\")) {
return pathToCheck.startsWith(workspacePath)
}
const resolvedPath = path.resolve(workspacePath, pathToCheck)
// Using realpathSync to resolve any symbolic links
try {
const realWorkspacePath = realpathSync(workspacePath)
const realPath = realpathSync(resolvedPath)
return realPath.startsWith(realWorkspacePath)
} catch (error) {
console.error("Error resolving paths:", error)
return false
}
}
+13 -23
View File
@@ -27,8 +27,7 @@
"react-use": "^17.6.0",
"react-virtuoso": "^4.12.3",
"rehype-highlight": "^7.0.1",
"styled-components": "^6.1.15",
"uuid": "^9.0.1"
"styled-components": "^6.1.15"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
@@ -41,7 +40,6 @@
"@types/node": "^22.13.4",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/uuid": "^9.0.8",
"@types/vscode-webview": "^1.57.5",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-v8": "^3.0.9",
@@ -3541,13 +3539,6 @@
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"license": "MIT"
},
"node_modules/@types/uuid": {
"version": "9.0.8",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
"integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/vscode-webview": {
"version": "1.57.5",
"resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.5.tgz",
@@ -6945,6 +6936,18 @@
"uuid": "^9.0.1"
}
},
"node_modules/mermaid/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/micromark": {
"version": "2.11.4",
"resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
@@ -8833,19 +8836,6 @@
}
}
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+1 -3
View File
@@ -33,8 +33,7 @@
"react-use": "^17.6.0",
"react-virtuoso": "^4.12.3",
"rehype-highlight": "^7.0.1",
"styled-components": "^6.1.15",
"uuid": "^9.0.1"
"styled-components": "^6.1.15"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
@@ -47,7 +46,6 @@
"@types/node": "^22.13.4",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/uuid": "^9.0.8",
"@types/vscode-webview": "^1.57.5",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-v8": "^3.0.9",
+1 -2
View File
@@ -15,7 +15,6 @@ import { McpViewTab } from "@shared/mcp"
const AppContent = () => {
const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, vscMachineId } = useExtensionState()
const [showSettings, setShowSettings] = useState(false)
const hideSettings = useCallback(() => setShowSettings(false), [])
const [showHistory, setShowHistory] = useState(false)
const [showMcp, setShowMcp] = useState(false)
const [showAccount, setShowAccount] = useState(false)
@@ -93,7 +92,7 @@ const AppContent = () => {
<WelcomeView />
) : (
<>
{showSettings && <SettingsView onDone={hideSettings} />}
{showSettings && <SettingsView onDone={() => setShowSettings(false)} />}
{showHistory && <HistoryView onDone={() => setShowHistory(false)} />}
{showMcp && <McpView initialTab={mcpTab} onDone={() => setShowMcp(false)} />}
{showAccount && <AccountView onDone={() => setShowAccount(false)} />}
@@ -3,8 +3,6 @@ import { useEffect, useRef, useState } from "react"
import styled from "styled-components"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { vscode } from "@/utils/vscode"
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
import { BrowserServiceClient } from "../../services/grpc-client"
interface ConnectionInfo {
isConnected: boolean
@@ -23,25 +21,29 @@ export const BrowserSettingsMenu = () => {
})
const popoverRef = useRef<HTMLDivElement>(null)
// Get actual connection info from the browser session using gRPC
// Get actual connection info from the browser session
useEffect(() => {
// Function to fetch connection info
;(async () => {
try {
console.log("[DEBUG] SENDING BROWSER CONNECTION INFO REQUEST")
const info = await BrowserServiceClient.getBrowserConnectionInfo({})
console.log("[DEBUG] GOT BROWSER REPLY:", info, typeof info)
setConnectionInfo({
isConnected: info.isConnected,
isRemote: info.isRemote,
host: info.host,
})
} catch (error) {
console.error("Error fetching browser connection info:", error)
}
})()
// Request connection info when component mounts
vscode.postMessage({
type: "getBrowserConnectionInfo",
})
// No need for message event listeners anymore!
// Listen for connection info updates
const handleMessage = (event: MessageEvent) => {
const message = event.data
if (message.type === "browserConnectionInfo") {
setConnectionInfo({
isConnected: message.isConnected,
isRemote: message.isRemote,
host: message.host,
})
}
}
window.addEventListener("message", handleMessage)
return () => {
window.removeEventListener("message", handleMessage)
}
}, [browserSettings.remoteBrowserHost, browserSettings.remoteBrowserEnabled])
// Close popover when clicking outside
@@ -82,22 +84,11 @@ export const BrowserSettingsMenu = () => {
const toggleInfoPopover = () => {
setShowInfoPopover(!showInfoPopover)
// Request updated connection info when opening the popover using gRPC
// Request updated connection info when opening the popover
if (!showInfoPopover) {
const fetchConnectionInfo = async () => {
try {
const info = await BrowserServiceClient.getBrowserConnectionInfo({})
setConnectionInfo({
isConnected: info.isConnected,
isRemote: info.isRemote,
host: info.host,
})
} catch (error) {
console.error("Error fetching browser connection info:", error)
}
}
fetchConnectionInfo()
vscode.postMessage({
type: "getBrowserConnectionInfo",
})
}
}
@@ -121,27 +112,19 @@ export const BrowserSettingsMenu = () => {
}
}
// Check connection status every second to keep icon in sync using gRPC
// Check connection status every second to keep icon in sync
useEffect(() => {
// Function to fetch connection info
const fetchConnectionInfo = async () => {
try {
const info = await BrowserServiceClient.getBrowserConnectionInfo({})
setConnectionInfo({
isConnected: info.isConnected,
isRemote: info.isRemote,
host: info.host,
})
} catch (error) {
console.error("Error fetching browser connection info:", error)
}
}
// Request connection info immediately
fetchConnectionInfo()
vscode.postMessage({
type: "getBrowserConnectionInfo",
})
// Set up interval to refresh every second
const intervalId = setInterval(fetchConnectionInfo, 1000)
const intervalId = setInterval(() => {
vscode.postMessage({
type: "getBrowserConnectionInfo",
})
}, 1000)
return () => clearInterval(intervalId)
}, [])
+3 -24
View File
@@ -328,20 +328,13 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
}, [message.ask, message.say, message.text])
if (tool) {
const colorMap = {
red: "var(--vscode-errorForeground)",
yellow: "var(--vscode-editorWarning-foreground)",
green: "var(--vscode-charts-green)",
}
const toolIcon = (name: string, color?: string, rotation?: number, title?: string) => (
const toolIcon = (name: string) => (
<span
className={`codicon codicon-${name}`}
style={{
color: color ? colorMap[color as keyof typeof colorMap] || color : "var(--vscode-foreground)",
color: "var(--vscode-foreground)",
marginBottom: "-1.5px",
transform: rotation ? `rotate(${rotation}deg)` : undefined,
}}
title={title}></span>
}}></span>
)
switch (tool.tool) {
@@ -350,8 +343,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
<>
<div style={headerStyle}>
{toolIcon("edit")}
{!tool.operationIsLocatedInWorkspace &&
toolIcon("sign-out", "yellow", -90, "This file is outside of your workspace")}
<span style={{ fontWeight: "bold" }}>Cline wants to edit this file:</span>
</div>
<CodeAccordian
@@ -368,8 +359,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
<>
<div style={headerStyle}>
{toolIcon("new-file")}
{!tool.operationIsLocatedInWorkspace &&
toolIcon("sign-out", "yellow", -90, "This file is outside of your workspace")}
<span style={{ fontWeight: "bold" }}>Cline wants to create a new file:</span>
</div>
<CodeAccordian
@@ -386,8 +375,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
<>
<div style={headerStyle}>
{toolIcon("file-code")}
{!tool.operationIsLocatedInWorkspace &&
toolIcon("sign-out", "yellow", -90, "This file is outside of your workspace")}
<span style={{ fontWeight: "bold" }}>
{/* {message.type === "ask" ? "" : "Cline read this file:"} */}
Cline wants to read this file:
@@ -446,8 +433,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
<>
<div style={headerStyle}>
{toolIcon("folder-opened")}
{!tool.operationIsLocatedInWorkspace &&
toolIcon("sign-out", "yellow", -90, "This is outside of your workspace")}
<span style={{ fontWeight: "bold" }}>
{message.type === "ask"
? "Cline wants to view the top level files in this directory:"
@@ -468,8 +453,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
<>
<div style={headerStyle}>
{toolIcon("folder-opened")}
{!tool.operationIsLocatedInWorkspace &&
toolIcon("sign-out", "yellow", -90, "This is outside of your workspace")}
<span style={{ fontWeight: "bold" }}>
{message.type === "ask"
? "Cline wants to recursively view all files in this directory:"
@@ -490,8 +473,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
<>
<div style={headerStyle}>
{toolIcon("file-code")}
{!tool.operationIsLocatedInWorkspace &&
toolIcon("sign-out", "yellow", -90, "This is outside of your workspace")}
<span style={{ fontWeight: "bold" }}>
{message.type === "ask"
? "Cline wants to view source code definition names used in this directory:"
@@ -511,8 +492,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
<>
<div style={headerStyle}>
{toolIcon("search")}
{!tool.operationIsLocatedInWorkspace &&
toolIcon("sign-out", "yellow", -90, "This is outside of your workspace")}
<span style={{ fontWeight: "bold" }}>
Cline wants to search this directory for <code>{tool.regex}</code>:
</span>
@@ -4,7 +4,6 @@ import { useRemark } from "react-remark"
import rehypeHighlight, { Options } from "rehype-highlight"
import styled from "styled-components"
import { visit } from "unist-util-visit"
import type { Node } from "unist"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import MermaidBlock from "@/components/common/MermaidBlock"
@@ -22,7 +21,7 @@ interface MarkdownBlockProps {
* This caused the entire content to disappear because the structure became invalid.
*/
const remarkUrlToLink = () => {
return (tree: Node) => {
return (tree: any) => {
// Visit all "text" nodes in the markdown AST (Abstract Syntax Tree)
visit(tree, "text", (node: any, index, parent) => {
const urlRegex = /https?:\/\/[^\s<>)"]+/g
@@ -3,7 +3,6 @@ import mermaid from "mermaid"
import { useDebounceEffect } from "@/utils/useDebounceEffect"
import styled from "styled-components"
import { vscode } from "@/utils/vscode"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
const MERMAID_THEME = {
background: "#1e1e1e", // VS Code dark theme background
@@ -140,22 +139,11 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
}
}
const handleCopyCode = async () => {
try {
await navigator.clipboard.writeText(code)
} catch (err) {
console.error("Copy failed", err)
}
}
return (
<MermaidBlockContainer>
{isLoading && <LoadingMessage>Generating mermaid diagram...</LoadingMessage>}
<ButtonContainer>
<StyledVSCodeButton onClick={handleCopyCode} title="Copy Code" aria-label="Copy Code">
<span className="codicon codicon-copy"></span>
</StyledVSCodeButton>
</ButtonContainer>
{/* The container for the final <svg> or raw code. */}
<SvgContainer onClick={handleClick} ref={containerRef} $isLoading={isLoading} />
</MermaidBlockContainer>
)
@@ -221,19 +209,6 @@ const MermaidBlockContainer = styled.div`
margin: 8px 0;
`
const ButtonContainer = styled.div`
position: absolute;
top: 8px;
right: 8px;
z-index: 1;
opacity: 0.6;
transition: opacity 0.2s ease;
&:hover {
opacity: 1;
}
`
const LoadingMessage = styled.div`
padding: 8px 0;
color: var(--vscode-descriptionForeground);
@@ -253,34 +228,3 @@ const SvgContainer = styled.div<SvgContainerProps>`
display: flex;
justify-content: center;
`
const StyledVSCodeButton = styled(VSCodeButton)`
padding: 4px;
height: 24px;
width: 24px;
min-width: unset;
background-color: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: 1px solid var(--vscode-button-border);
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
.codicon {
font-size: 14px;
}
&:hover {
background-color: var(--vscode-button-secondaryHoverBackground);
border-color: var(--vscode-button-border);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
box-shadow: none;
}
`
@@ -5,7 +5,6 @@ import { BROWSER_VIEWPORT_PRESETS } from "../../../../src/shared/BrowserSettings
import { useExtensionState } from "../../context/ExtensionStateContext"
import { vscode } from "../../utils/vscode"
import styled from "styled-components"
import { BrowserServiceClient } from "../../services/grpc-client"
const ConnectionStatusIndicator = ({
isChecking,
@@ -94,24 +93,10 @@ export const BrowserSettingsSection: React.FC = () => {
if (browserSettings.remoteBrowserEnabled) {
setIsCheckingConnection(true)
setConnectionStatus(null)
if (browserSettings.remoteBrowserHost) {
// Use gRPC for testBrowserConnection
BrowserServiceClient.testBrowserConnection({ value: browserSettings.remoteBrowserHost })
.then((result) => {
setConnectionStatus(result.success)
setIsCheckingConnection(false)
})
.catch((error) => {
console.error("Error testing browser connection:", error)
setConnectionStatus(false)
setIsCheckingConnection(false)
})
} else {
// Use old message passing for discoverBrowser (not yet migrated)
vscode.postMessage({
type: "discoverBrowser",
})
}
vscode.postMessage({
type: browserSettings.remoteBrowserHost ? "testBrowserConnection" : "discoverBrowser",
text: browserSettings.remoteBrowserHost,
})
}
}, 1000),
[browserSettings.remoteBrowserEnabled, browserSettings.remoteBrowserHost],
@@ -168,22 +153,10 @@ export const BrowserSettingsSection: React.FC = () => {
const checkConnectionOnce = useCallback(() => {
// Don't show the spinner for every check to avoid UI flicker
// We'll rely on the response to update the connectionStatus
if (browserSettings.remoteBrowserHost) {
// Use gRPC for testBrowserConnection
BrowserServiceClient.testBrowserConnection({ value: browserSettings.remoteBrowserHost })
.then((result) => {
setConnectionStatus(result.success)
})
.catch((error) => {
console.error("Error testing browser connection:", error)
setConnectionStatus(false)
})
} else {
// Use old message passing for discoverBrowser (not yet migrated)
vscode.postMessage({
type: "discoverBrowser",
})
}
vscode.postMessage({
type: browserSettings.remoteBrowserHost ? "testBrowserConnection" : "discoverBrowser",
text: browserSettings.remoteBrowserHost,
})
}, [browserSettings.remoteBrowserHost])
// Setup continuous polling for connection status when remote browser is enabled
@@ -51,8 +51,8 @@ const featuredModels = [
label: "Trending",
},
{
id: "openai/gpt-4.1",
description: "1M context window, blazing fast",
id: "meta-llama/llama-4-maverick",
description: "Efficient performance at lower cost",
label: "New",
},
]
@@ -145,16 +145,55 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
}
return (
<div className="fixed top-0 left-0 right-0 bottom-0 pt-[10px] pr-0 pb-0 pl-5 flex flex-col overflow-hidden">
<div className="flex justify-between items-center mb-[13px] pr-[17px]">
<h3 className="text-[var(--vscode-foreground)] m-0">Settings</h3>
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
padding: "10px 0px 0px 20px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
}}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "13px",
paddingRight: 17,
}}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Settings</h3>
<VSCodeButton onClick={() => handleSubmit(false)}>Done</VSCodeButton>
</div>
<div className="grow overflow-y-scroll pr-2 flex flex-col">
<div
style={{
flexGrow: 1,
overflowY: "scroll",
paddingRight: 8,
display: "flex",
flexDirection: "column",
}}>
{/* Tabs container */}
{planActSeparateModelsSetting ? (
<div className="border border-solid border-[var(--vscode-panel-border)] rounded-md p-[10px] mb-5 bg-[var(--vscode-panel-background)]">
<div className="flex gap-[1px] mb-[10px] -mt-2 border-0 border-b border-solid border-[var(--vscode-panel-border)]">
<div
style={{
border: "1px solid var(--vscode-panel-border)",
borderRadius: "4px",
padding: "10px",
marginBottom: "20px",
background: "var(--vscode-panel-background)",
}}>
<div
style={{
display: "flex",
gap: "1px",
marginBottom: "10px",
marginTop: -8,
borderBottom: "1px solid var(--vscode-panel-border)",
}}>
<TabButton isActive={chatSettings.mode === "plan"} onClick={() => handleTabChange("plan")}>
Plan Mode
</TabButton>
@@ -164,7 +203,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
</div>
{/* Content container */}
<div className="-mb-3">
<div style={{ marginBottom: -12 }}>
<ApiOptions
key={chatSettings.mode}
showModelOptions={true}
@@ -182,24 +221,29 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
/>
)}
<div className="mb-[5px]">
<div style={{ marginBottom: 5 }}>
<VSCodeTextArea
value={customInstructions ?? ""}
className="w-full"
style={{ width: "100%" }}
resize="vertical"
rows={4}
placeholder={'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'}
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
<span className="font-medium">Custom Instructions</span>
<span style={{ fontWeight: "500" }}>Custom Instructions</span>
</VSCodeTextArea>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
These instructions are added to the end of the system prompt sent with every request.
</p>
</div>
<div className="mb-[5px]">
<div style={{ marginBottom: 5 }}>
<VSCodeCheckbox
className="mb-[5px]"
style={{ marginBottom: "5px" }}
checked={planActSeparateModelsSetting}
onChange={(e: any) => {
const checked = e.target.checked === true
@@ -207,15 +251,20 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
}}>
Use different models for Plan and Act modes
</VSCodeCheckbox>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Switching between Plan and Act mode will persist the API and model used in the previous mode. This may be
helpful e.g. when using a strong reasoning model to architect a plan for a cheaper coding model to act on.
</p>
</div>
<div className="mb-[5px]">
<div style={{ marginBottom: 5 }}>
<VSCodeCheckbox
className="mb-[5px]"
style={{ marginBottom: "5px" }}
checked={telemetrySetting === "enabled"}
onChange={(e: any) => {
const checked = e.target.checked === true
@@ -223,14 +272,19 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
}}>
Allow anonymous error and usage reporting
</VSCodeCheckbox>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Help improve Cline by sending anonymous usage data and error reports. No code, prompts, or personal
information are ever sent. See our{" "}
<VSCodeLink href="https://docs.cline.bot/more-info/telemetry" className="text-inherit">
<VSCodeLink href="https://docs.cline.bot/more-info/telemetry" style={{ fontSize: "inherit" }}>
telemetry overview
</VSCodeLink>{" "}
and{" "}
<VSCodeLink href="https://cline.bot/privacy" className="text-inherit">
<VSCodeLink href="https://cline.bot/privacy" style={{ fontSize: "inherit" }}>
privacy policy
</VSCodeLink>{" "}
for more details.
@@ -240,10 +294,18 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
{/* Browser Settings Section */}
<BrowserSettingsSection />
<div className="mt-auto pr-2 flex justify-center">
<div
style={{
marginTop: "auto",
paddingRight: 8,
display: "flex",
justifyContent: "center",
}}>
<SettingsButton
onClick={() => vscode.postMessage({ type: "openExtensionSettings" })}
className="mt-0 mr-0 mb-4 ml-0">
style={{
margin: "0 0 16px 0",
}}>
<i className="codicon codicon-settings-gear" />
Advanced Settings
</SettingsButton>
@@ -251,24 +313,49 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
{IS_DEV && (
<>
<div className="mt-[10px] mb-1">Debug</div>
<VSCodeButton onClick={handleResetState} className="mt-[5px] w-auto">
<div style={{ marginTop: "10px", marginBottom: "4px" }}>Debug</div>
<VSCodeButton onClick={handleResetState} style={{ marginTop: "5px", width: "auto" }}>
Reset State
</VSCodeButton>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
This will reset all global state and secret storage in the extension.
</p>
</>
)}
<div className="text-center text-[var(--vscode-descriptionForeground)] text-xs leading-[1.2] px-0 py-0 pr-2 pb-[15px] mt-auto">
<p className="break-words m-0 p-0">
<div
style={{
textAlign: "center",
color: "var(--vscode-descriptionForeground)",
fontSize: "12px",
lineHeight: "1.2",
padding: "0 8px 15px 0",
marginTop: "auto",
}}>
<p
style={{
wordWrap: "break-word",
margin: 0,
padding: 0,
}}>
If you have any questions or feedback, feel free to open an issue at{" "}
<VSCodeLink href="https://github.com/cline/cline" className="inline">
<VSCodeLink href="https://github.com/cline/cline" style={{ display: "inline" }}>
https://github.com/cline/cline
</VSCodeLink>
</p>
<p className="italic mt-[10px] mb-0 p-0">v{version}</p>
<p
style={{
fontStyle: "italic",
margin: "10px 0 0 0",
padding: 0,
}}>
v{version}
</p>
</div>
</div>
</div>
@@ -21,7 +21,7 @@ import { TelemetrySetting } from "@shared/TelemetrySetting"
interface ExtensionStateContextType extends ExtensionState {
didHydrateState: boolean
showWelcome: boolean
theme: Record<string, string> | undefined
theme: any
openRouterModels: Record<string, ModelInfo>
openAiModels: string[]
requestyModels: Record<string, ModelInfo>
@@ -56,7 +56,7 @@ export const ExtensionStateContextProvider: React.FC<{
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)
const [theme, setTheme] = useState<Record<string, string>>()
const [theme, setTheme] = useState<any>(undefined)
const [filePaths, setFilePaths] = useState<string[]>([])
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
-100
View File
@@ -1,100 +0,0 @@
import { vscode } from "../utils/vscode"
import { v4 as uuidv4 } from "uuid"
import { BrowserServiceDefinition } from "@shared/proto/browser"
import { EmptyRequest } from "@shared/proto/common"
// Generic type for any protobuf service definition
type ProtoService = {
name: string
fullName: string
methods: {
[key: string]: {
name: string
requestType: any
responseType: any
requestStream: boolean
responseStream: boolean
options: any
}
}
}
// Define a generic type that extracts method signatures from a service definition
type GrpcClientType<T extends ProtoService> = {
[K in keyof T["methods"]]: (
request: InstanceType<T["methods"][K]["requestType"]>,
) => Promise<InstanceType<T["methods"][K]["responseType"]>>
}
// Create a client for any protobuf service with inferred types
function createGrpcClient<T extends ProtoService>(service: T): GrpcClientType<T> {
const client = {} as GrpcClientType<T>
// For each method in the service
Object.values(service.methods).forEach((method) => {
// Create a function that matches the method signature
client[method.name as keyof GrpcClientType<T>] = ((request: any) => {
return new Promise((resolve, reject) => {
const requestId = uuidv4()
// Set up one-time listener for this specific request
const handleResponse = (event: MessageEvent) => {
const message = event.data
if (message.type === "grpc_response" && message.grpc_response?.request_id === requestId) {
// Remove listener once we get our response
window.removeEventListener("message", handleResponse)
if (message.grpc_response.error) {
reject(new Error(message.grpc_response.error))
} else {
// Convert JSON back to protobuf message
const responseType = method.responseType
const response = responseType.fromJSON(message.grpc_response.message)
console.log("[DEBUG] grpc-client sending response:", response)
resolve(response)
}
}
}
window.addEventListener("message", handleResponse)
let encodedRequest = {}
// Handle different types of requests
if (request === null || request === undefined) {
// Empty request
encodedRequest = {}
} else if (typeof request.toJSON === "function") {
// Proper protobuf object
encodedRequest = request.toJSON()
} else if (typeof request === "object") {
// Plain JavaScript object
encodedRequest = { ...request }
} else {
// Fallback
encodedRequest = { value: request }
}
// Send the request
vscode.postMessage({
type: "grpc_request",
grpc_request: {
service: service.fullName,
method: method.name,
message: encodedRequest, // Convert protobuf to JSON
request_id: requestId,
},
})
})
}) as any
})
return client
}
// Create the Browser Service Client singleton with inferred types
// No need for manual interface definition - types are inferred from the service definition
const BrowserServiceClient = createGrpcClient(BrowserServiceDefinition)
// Export the Browser Service Client as a static object
export { BrowserServiceClient }
+1 -2
View File
@@ -1,5 +1,4 @@
import { useEffect, useRef } from "react"
import type { DependencyList } from "react"
type VoidFn = () => void
@@ -7,7 +6,7 @@ type VoidFn = () => void
* Runs `effectRef.current()` after `delay` ms whenever any of the `deps` change,
* but cancels/re-schedules if they change again before the delay.
*/
export function useDebounceEffect(effect: VoidFn, delay: number, deps: DependencyList) {
export function useDebounceEffect(effect: VoidFn, delay: number, deps: any[]) {
const callbackRef = useRef<VoidFn>(effect)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
@@ -0,0 +1,8 @@
{
"workbench.startupEditor": "none",
"workbench.activityBar.visible": true,
"window.restoreWindows": "none",
"window.newWindowDimensions": "default",
"workbench.statusBar.visible": true,
"window.nativeTabs": true
}