mirror of
https://github.com/cline/cline.git
synced 2026-09-11 05:47:07 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f263073ebb | ||
|
|
c7a0df622f | ||
|
|
d6a3ac681d | ||
|
|
102a49fc72 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
gRPC over vscode message bus to make messaging better
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix vertexai token count
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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}`)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
@@ -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.
Generated
+36
-164
@@ -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
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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-error(Testing 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/)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -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: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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}`,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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}`)
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+13
-23
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}, [])
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
Reference in New Issue
Block a user