mirror of
https://github.com/cline/cline.git
synced 2026-09-13 09:50:12 +08:00
Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7b5a2b98 | ||
|
|
3828c0d1bc | ||
|
|
a2263de7cb | ||
|
|
e53fa8307d | ||
|
|
b8cfb87121 | ||
|
|
76a64ef77d | ||
|
|
29bdb6c981 | ||
|
|
9bbc0da821 | ||
|
|
d2080c1f93 | ||
|
|
044dd686a0 | ||
|
|
ea4f571463 | ||
|
|
8cddbcfd99 | ||
|
|
4e0cb64e77 | ||
|
|
4aa3764beb | ||
|
|
a525d6dd5e | ||
|
|
59dd3236e4 | ||
|
|
5439426ff6 | ||
|
|
fffcc80477 | ||
|
|
dfcb3d5d9b | ||
|
|
4af5150823 | ||
|
|
ddbdfbc96d | ||
|
|
a405df5dc0 | ||
|
|
04d1f1d4e7 | ||
|
|
0572933c32 | ||
|
|
99bbe17df9 | ||
|
|
b3b7b9da5f | ||
|
|
b0df763ae7 | ||
|
|
280374f30d | ||
|
|
9d9e54360b | ||
|
|
552146d8b5 | ||
|
|
552054a026 | ||
|
|
cff8a237cd | ||
|
|
e70264a56c | ||
|
|
06196cf53d | ||
|
|
1761c0e9e8 | ||
|
|
fbb13f102c | ||
|
|
4850df722b | ||
|
|
6e71b3f7cc | ||
|
|
a198f71986 | ||
|
|
0d38381573 | ||
|
|
ba6dcb5bc9 | ||
|
|
ecb8633534 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Move updateMcpTimeout message to protobus
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
add smol command
|
||||
@@ -8,8 +8,8 @@ Cline is a VSCode extension that provides AI assistance through a combination of
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph VSCode Extension Host
|
||||
subgraph Core Extension
|
||||
subgraph VSCodeExtensionHost[VSCode Extension Host]
|
||||
subgraph CoreExtension[Core Extension]
|
||||
ExtensionEntry[Extension Entry<br/>src/extension.ts]
|
||||
WebviewProvider[WebviewProvider<br/>src/core/webview/index.ts]
|
||||
Controller[Controller<br/>src/core/controller/index.ts]
|
||||
@@ -19,7 +19,7 @@ graph TB
|
||||
McpHub[McpHub<br/>src/services/mcp/McpHub.ts]
|
||||
end
|
||||
|
||||
subgraph Webview UI
|
||||
subgraph WebviewUI[Webview UI]
|
||||
WebviewApp[React App<br/>webview-ui/src/App.tsx]
|
||||
ExtStateContext[ExtensionStateContext<br/>webview-ui/src/context/ExtensionStateContext.tsx]
|
||||
ReactComponents[React Components]
|
||||
@@ -30,14 +30,14 @@ graph TB
|
||||
CheckpointSystem[Git-based Checkpoints]
|
||||
end
|
||||
|
||||
subgraph API Providers
|
||||
subgraph apiProviders[API Providers]
|
||||
AnthropicAPI[Anthropic]
|
||||
OpenRouterAPI[OpenRouter]
|
||||
BedrockAPI[AWS Bedrock]
|
||||
OtherAPIs[Other Providers]
|
||||
end
|
||||
|
||||
subgraph MCP Servers
|
||||
subgraph MCPServers[MCP Servers]
|
||||
ExternalMcpServers[External MCP Servers]
|
||||
end
|
||||
end
|
||||
@@ -51,7 +51,7 @@ graph TB
|
||||
Task --> SecretsStorage
|
||||
Task --> TaskStorage
|
||||
Task --> CheckpointSystem
|
||||
Task --> |API Requests| API Providers
|
||||
Task --> |API Requests| apiProviders
|
||||
McpHub --> |Connects to| ExternalMcpServers
|
||||
Task --> |Uses| McpHub
|
||||
|
||||
@@ -67,7 +67,7 @@ graph TB
|
||||
style ExtStateContext fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style WebviewProvider fill:#bfb,stroke:#333,stroke-width:2px
|
||||
style McpHub fill:#bfb,stroke:#333,stroke-width:2px
|
||||
style API Providers fill:#fdb,stroke:#333,stroke-width:2px
|
||||
style apiProviders fill:#fdb,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
## Definitions
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
* @saoudrizwan @ocasta181 @NightTrek @pashpashpash @dcbartlett @saito-sv
|
||||
* @saoudrizwan @ocasta181 @NightTrek @pashpashpash @dcbartlett @saito-sv @Garoth
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
name: Create Linear Issue on Pull Request
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
create-linear-issue-on-pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for existing Linear link
|
||||
id: check-linear
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
// 1) PR body
|
||||
if (/https?:\/\/linear\.app/.test(pr.body||"")) {
|
||||
return "true";
|
||||
}
|
||||
// 2) Any linked GitHub issues?
|
||||
const res = await github.graphql(
|
||||
`query($owner:String!,$repo:String!,$prNumber:Int!){
|
||||
repository(owner:$owner,name:$repo){
|
||||
pullRequest(number:$prNumber){
|
||||
closingIssuesReferences(first:10){
|
||||
nodes{number}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
prNumber: pr.number
|
||||
}
|
||||
);
|
||||
for (const {number} of res.repository.pullRequest.closingIssuesReferences.nodes) {
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: number
|
||||
});
|
||||
if (comments.data.some(c=>/https?:\/\/linear\.app/.test(c.body))) {
|
||||
return "true";
|
||||
}
|
||||
}
|
||||
return "false";
|
||||
|
||||
- name: Find or create Linear issue via GraphQL
|
||||
if: steps.check-linear.outputs.result == 'false'
|
||||
id: linear
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const API = 'https://api.linear.app/graphql';
|
||||
const apiKey = process.env.LINEAR_API_KEY;
|
||||
|
||||
// Check if API key exists
|
||||
if (!apiKey) {
|
||||
core.setFailed('LINEAR_API_KEY is not set. Please add it to your repository secrets.');
|
||||
core.setOutput('error', 'true');
|
||||
core.setOutput('error-message', 'LINEAR_API_KEY is not set. Please add it to your repository secrets.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper to call Linear with error handling
|
||||
async function gql(q, v) {
|
||||
try {
|
||||
const r = await fetch(API, {
|
||||
method:'POST',
|
||||
headers:{
|
||||
'Content-Type':'application/json',
|
||||
'Authorization': apiKey
|
||||
},
|
||||
body: JSON.stringify({ query: q, variables: v })
|
||||
});
|
||||
|
||||
if (!r.ok) {
|
||||
throw new Error(`Linear API responded with status ${r.status}: ${await r.text()}`);
|
||||
}
|
||||
|
||||
const json = await r.json();
|
||||
|
||||
// Check for GraphQL errors
|
||||
if (json.errors && json.errors.length > 0) {
|
||||
const errorMessages = json.errors.map(e => e.message).join(', ');
|
||||
throw new Error(`Linear GraphQL errors: ${errorMessages}`);
|
||||
}
|
||||
|
||||
return json.data;
|
||||
} catch (error) {
|
||||
core.error(`Error calling Linear API: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 1) Set team ID
|
||||
const teamId = "19b9c1b2-5f58-498c-b1bf-23ee8f52a677"
|
||||
|
||||
// 2) Look for existing issue by PR URL
|
||||
const pr = context.payload.pull_request;
|
||||
const searchData = await gql(
|
||||
`query($team:ID!,$q:String!){
|
||||
issues(filter: { team: { id: { eq: $team } } attachments: { some: { url: { eq: $q } } } }){nodes{id,url}}
|
||||
}`,
|
||||
{ team: teamId, q: pr.html_url }
|
||||
);
|
||||
let issue = searchData.issues.nodes[0];
|
||||
|
||||
// 3) Create if missing
|
||||
if (!issue) {
|
||||
const createData = await gql(
|
||||
`mutation($input:IssueCreateInput!){
|
||||
issueCreate(input:$input){issue{id,url}}
|
||||
}`,
|
||||
{
|
||||
input: {
|
||||
teamId,
|
||||
title: `[GITHUB] ${pr.title}`,
|
||||
description: `${pr.body||''}\n\n${pr.html_url}`,
|
||||
stateId: "4d9bcba2-6712-47e3-b577-6ec1ee023dc2",
|
||||
labelIds: ["504e7d60-5037-483f-a9b8-7e298bdf116f"]
|
||||
}
|
||||
}
|
||||
);
|
||||
issue = createData.issueCreate.issue;
|
||||
}
|
||||
|
||||
// Set output for next steps
|
||||
core.setOutput('linear-issue-url', issue.url);
|
||||
core.setOutput('error', 'false');
|
||||
} catch (error) {
|
||||
core.setOutput('error', 'true');
|
||||
core.setOutput('error-message', error.message);
|
||||
core.setFailed(`Failed to create or find Linear issue: ${error.message}`);
|
||||
}
|
||||
|
||||
- name: Comment PR with Linear link
|
||||
if: steps.check-linear.outputs.result == 'false'
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const url = `${{ steps.linear.outputs.linear-issue-url }}`;
|
||||
const body = `🔗 Linear issue created: ${url}`;
|
||||
// Fetch existing comments
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
...context.repo,
|
||||
issue_number: pr.number
|
||||
});
|
||||
const botComment = comments.find(c =>
|
||||
c.user.type === "Bot" && c.body.startsWith("🔗 Linear issue created:")
|
||||
);
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
...context.repo,
|
||||
comment_id: botComment.id,
|
||||
body
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: pr.number,
|
||||
body
|
||||
});
|
||||
}
|
||||
@@ -17,3 +17,5 @@ pnpm-lock.yaml
|
||||
coverage
|
||||
# But don't ignore the coverage scripts in .github/scripts/
|
||||
!.github/scripts/coverage/
|
||||
|
||||
*evals.env
|
||||
Vendored
-13
@@ -16,19 +16,6 @@
|
||||
"IS_DEV": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Run Extension (Test Mode)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"IS_TEST": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+24
-1
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## [3.13.2]
|
||||
|
||||
- Add Gemini 2.5 Flash model to Vertex and Gemini Providers (Thanks monotykamary!)
|
||||
- Add Caching to gemini provider (Thanks arafatkatze!)
|
||||
- Add thinking budget support to Gemini Models (Thanks monotykamary!)
|
||||
- Add !include .file directive support for .clineignore (Thanks watany-dev!)
|
||||
- Improve slash command functionality
|
||||
- Improve prompting for new task tool
|
||||
- Fix o1 temperature being passed to the azure api (Thanks treeleaves30760!)
|
||||
- Fix to make "add new rule file" button functional
|
||||
- Fix Ollama provider timeout, allowing for a larger loading time (Thanks suvarchal!)
|
||||
- Fix Non-UTF-8 File Handling: Improve Encoding Detection to Prevent Garbled Text and Binary Misclassification (Thanks yt3trees!)
|
||||
- Fix settings to not reset by changing providers
|
||||
- Fix terminal outputs missing commas
|
||||
- Fix terminal errors caused by starting non-alphanumeric outputs
|
||||
- Fix auto approve settings becoming unset
|
||||
- Fix Mermaid syntax error in documentation (Thanks tuki0918!)
|
||||
- Remove supportsComputerUse restriction and support browser use through any model that supports images (Thanks arafatkatze!)
|
||||
|
||||
|
||||
## [3.13.1]
|
||||
|
||||
- Fix bug where task cancellation during thinking stream would result in error state
|
||||
|
||||
## [3.13.0]
|
||||
|
||||
- Add Cline rules popover under the chat field, allowing you to easily add, enable & disable workspace level or global rule files
|
||||
@@ -17,7 +41,6 @@
|
||||
- Fix token usage tracking issues with vertex provider (Thanks @mzsima!)
|
||||
- Fix issue with xAI reasoning content not being parsed (Thanks @mrubens!)
|
||||
|
||||
|
||||
## [3.12.3]
|
||||
|
||||
- Add copy button to MermaidBlock component (Thanks @cacosub7!)
|
||||
|
||||
+54
-2
@@ -4,11 +4,63 @@ const path = require("path")
|
||||
|
||||
const production = process.argv.includes("--production")
|
||||
const watch = process.argv.includes("--watch")
|
||||
const test = process.env.IS_TEST === "true"
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').Plugin}
|
||||
*/
|
||||
const aliasResolverPlugin = {
|
||||
name: "alias-resolver",
|
||||
setup(build) {
|
||||
const aliases = {
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
"@api": path.resolve(__dirname, "src/api"),
|
||||
"@core": path.resolve(__dirname, "src/core"),
|
||||
"@integrations": path.resolve(__dirname, "src/integrations"),
|
||||
"@services": path.resolve(__dirname, "src/services"),
|
||||
"@shared": path.resolve(__dirname, "src/shared"),
|
||||
"@utils": path.resolve(__dirname, "src/utils"),
|
||||
}
|
||||
|
||||
// For each alias entry, create a resolver
|
||||
Object.entries(aliases).forEach(([alias, aliasPath]) => {
|
||||
const aliasRegex = new RegExp(`^${alias}($|/.*)`)
|
||||
build.onResolve({ filter: aliasRegex }, (args) => {
|
||||
const importPath = args.path.replace(alias, aliasPath)
|
||||
|
||||
// First, check if the path exists as is
|
||||
if (fs.existsSync(importPath)) {
|
||||
const stats = fs.statSync(importPath)
|
||||
if (stats.isDirectory()) {
|
||||
// If it's a directory, try to find index files
|
||||
const extensions = [".ts", ".tsx", ".js", ".jsx"]
|
||||
for (const ext of extensions) {
|
||||
const indexFile = path.join(importPath, `index${ext}`)
|
||||
if (fs.existsSync(indexFile)) {
|
||||
return { path: indexFile }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// It's a file that exists, so return it
|
||||
return { path: importPath }
|
||||
}
|
||||
}
|
||||
|
||||
// If the path doesn't exist, try appending extensions
|
||||
const extensions = [".ts", ".tsx", ".js", ".jsx"]
|
||||
for (const ext of extensions) {
|
||||
const pathWithExtension = `${importPath}${ext}`
|
||||
if (fs.existsSync(pathWithExtension)) {
|
||||
return { path: pathWithExtension }
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing worked, return the original path and let esbuild handle the error
|
||||
return { path: importPath }
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const esbuildProblemMatcherPlugin = {
|
||||
name: "esbuild-problem-matcher",
|
||||
|
||||
@@ -71,10 +123,10 @@ const extensionConfig = {
|
||||
logLevel: "silent",
|
||||
define: {
|
||||
"process.env.IS_DEV": JSON.stringify(!production),
|
||||
"process.env.IS_TEST": JSON.stringify(test),
|
||||
},
|
||||
plugins: [
|
||||
copyWasmFiles,
|
||||
aliasResolverPlugin,
|
||||
/* add to the end of plugins array */
|
||||
esbuildProblemMatcherPlugin,
|
||||
{
|
||||
|
||||
@@ -60,6 +60,17 @@ cline-repo/
|
||||
- VSCode with Cline extension installed
|
||||
- Git
|
||||
|
||||
### Activation Mechanism
|
||||
|
||||
The evaluation system uses an `evals.env` file approach to activate test mode in the Cline extension. When an evaluation is run:
|
||||
|
||||
1. The CLI creates an `evals.env` file in the workspace directory
|
||||
2. The Cline extension activates due to the `workspaceContains:evals.env` activation event
|
||||
3. The extension detects this file and automatically enters test mode
|
||||
4. After evaluation completes, the file is automatically removed
|
||||
|
||||
This approach eliminates the need for environment variables during the build process and allows for targeted activation only when needed for evaluations. The extension remains dormant during normal use, only activating when an evals.env file is present. For more details, see [Evals Env Activation](./docs/evals-env-activation.md).
|
||||
|
||||
### Installation
|
||||
|
||||
1. Build the CLI tool:
|
||||
@@ -106,6 +117,19 @@ Options:
|
||||
- `--format`: Report format (json, markdown) (default: markdown)
|
||||
- `--output`: Output path for the report
|
||||
|
||||
#### Managing Test Mode Activation
|
||||
|
||||
The CLI provides a command to manually manage the evals.env file for test mode activation:
|
||||
|
||||
```bash
|
||||
node dist/index.js evals-env create # Create evals.env file in current directory
|
||||
node dist/index.js evals-env remove # Remove evals.env file from current directory
|
||||
node dist/index.js evals-env check # Check if evals.env file exists in current directory
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--directory`: Specify a directory other than the current one
|
||||
|
||||
## Benchmarks
|
||||
|
||||
### Exercism
|
||||
|
||||
@@ -167,8 +167,12 @@ export class ExercismAdapter implements BenchmarkAdapter {
|
||||
output += stdout + "\n"
|
||||
} catch (error: any) {
|
||||
success = false
|
||||
if (error.stdout) output += error.stdout + "\n"
|
||||
if (error.stderr) output += error.stderr + "\n"
|
||||
if (error.stdout) {
|
||||
output += error.stdout + "\n"
|
||||
}
|
||||
if (error.stderr) {
|
||||
output += error.stderr + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import * as path from "path"
|
||||
import chalk from "chalk"
|
||||
import { createEvalsEnvFile, removeEvalsEnvFile, checkEvalsEnvFile } from "../utils/evals-env"
|
||||
|
||||
interface EvalsEnvOptions {
|
||||
action: "create" | "remove" | "check"
|
||||
directory?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the evals-env command
|
||||
* @param options Command options
|
||||
*/
|
||||
export async function evalsEnvHandler(options: EvalsEnvOptions): Promise<void> {
|
||||
// Determine the directory to use - default to repository root instead of current directory
|
||||
const currentDir = process.cwd()
|
||||
const repoRoot = path.resolve(currentDir, "..", "..") // Navigate up from evals/cli to root
|
||||
const directory = options.directory || repoRoot
|
||||
|
||||
console.log(chalk.blue(`Working with directory: ${directory}`))
|
||||
|
||||
// Perform the requested action
|
||||
switch (options.action) {
|
||||
case "create":
|
||||
console.log(chalk.blue("Creating evals.env file..."))
|
||||
createEvalsEnvFile(directory)
|
||||
console.log(chalk.green("The Cline extension should now detect this file and enter test mode."))
|
||||
console.log(chalk.yellow("Note: You may need to reload VSCode for the changes to take effect."))
|
||||
break
|
||||
|
||||
case "remove":
|
||||
console.log(chalk.blue("Removing evals.env file..."))
|
||||
removeEvalsEnvFile(directory)
|
||||
console.log(chalk.green("The Cline extension should now exit test mode."))
|
||||
console.log(chalk.yellow("Note: You may need to reload VSCode for the changes to take effect."))
|
||||
break
|
||||
|
||||
case "check":
|
||||
console.log(chalk.blue("Checking for evals.env file..."))
|
||||
const exists = checkEvalsEnvFile(directory)
|
||||
if (exists) {
|
||||
console.log(chalk.green("The Cline extension should be in test mode."))
|
||||
} else {
|
||||
console.log(chalk.yellow("The Cline extension should not be in test mode."))
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
console.error(chalk.red(`Unknown action: ${options.action}`))
|
||||
console.log(chalk.yellow("Valid actions are: create, remove, check"))
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import chalk from "chalk"
|
||||
import { setupHandler } from "./commands/setup"
|
||||
import { runHandler } from "./commands/run"
|
||||
import { reportHandler } from "./commands/report"
|
||||
import { evalsEnvHandler } from "./commands/evals-env"
|
||||
|
||||
// Create the CLI program
|
||||
const program = new Command()
|
||||
@@ -61,6 +62,21 @@ program
|
||||
}
|
||||
})
|
||||
|
||||
// Evals-env command
|
||||
program
|
||||
.command("evals-env")
|
||||
.description("Manage evals.env files for test mode activation")
|
||||
.argument("<action>", "Action to perform: create, remove, or check")
|
||||
.option("-d, --directory <directory>", "Directory to create/remove/check evals.env file in (defaults to current directory)")
|
||||
.action(async (action, options) => {
|
||||
try {
|
||||
await evalsEnvHandler({ action, ...options })
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error managing evals.env file: ${error instanceof Error ? error.message : String(error)}`))
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
// Parse command line arguments
|
||||
program.parse(process.argv)
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import chalk from "chalk"
|
||||
|
||||
/**
|
||||
* Creates an evals.env file in the specified directory
|
||||
* @param directory The directory where the evals.env file should be created
|
||||
* @returns True if the file was created, false if it already exists
|
||||
*/
|
||||
export function createEvalsEnvFile(directory: string): boolean {
|
||||
const evalsEnvPath = path.join(directory, "evals.env")
|
||||
|
||||
// Check if the file already exists
|
||||
if (fs.existsSync(evalsEnvPath)) {
|
||||
console.log(chalk.yellow(`evals.env file already exists at ${evalsEnvPath}`))
|
||||
return false
|
||||
}
|
||||
|
||||
// Create the file
|
||||
try {
|
||||
const content = `# This file activates Cline test mode
|
||||
# Created at: ${new Date().toISOString()}
|
||||
#
|
||||
# This file is automatically detected by the Cline extension
|
||||
# and enables test mode for automated evaluations.
|
||||
#
|
||||
# Delete this file to deactivate test mode.
|
||||
`
|
||||
fs.writeFileSync(evalsEnvPath, content)
|
||||
console.log(chalk.green(`Created evals.env file at ${evalsEnvPath}`))
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error creating evals.env file: ${error}`))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an evals.env file from the specified directory
|
||||
* @param directory The directory where the evals.env file should be removed
|
||||
* @returns True if the file was removed, false if it doesn't exist
|
||||
*/
|
||||
export function removeEvalsEnvFile(directory: string): boolean {
|
||||
const evalsEnvPath = path.join(directory, "evals.env")
|
||||
|
||||
// Check if the file exists
|
||||
if (!fs.existsSync(evalsEnvPath)) {
|
||||
console.log(chalk.yellow(`No evals.env file found at ${evalsEnvPath}`))
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove the file
|
||||
try {
|
||||
fs.unlinkSync(evalsEnvPath)
|
||||
console.log(chalk.green(`Removed evals.env file from ${evalsEnvPath}`))
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error removing evals.env file: ${error}`))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an evals.env file exists in the specified directory
|
||||
* @param directory The directory to check for an evals.env file
|
||||
* @returns True if the file exists, false otherwise
|
||||
*/
|
||||
export function checkEvalsEnvFile(directory: string): boolean {
|
||||
const evalsEnvPath = path.join(directory, "evals.env")
|
||||
const exists = fs.existsSync(evalsEnvPath)
|
||||
|
||||
if (exists) {
|
||||
console.log(chalk.green(`evals.env file found at ${evalsEnvPath}`))
|
||||
} else {
|
||||
console.log(chalk.yellow(`No evals.env file found at ${evalsEnvPath}`))
|
||||
}
|
||||
|
||||
return exists
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import * as path from "path"
|
||||
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
|
||||
@@ -31,23 +30,43 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
|
||||
// If no VSIX path is provided, build one with IS_TEST=true
|
||||
if (!vsixPath) {
|
||||
try {
|
||||
// Build the VSIX with IS_TEST=true
|
||||
console.log("Building test VSIX...")
|
||||
// Build the VSIX (no longer need to set IS_TEST=true as we'll use evals.env file)
|
||||
console.log("Building VSIX...")
|
||||
const clineRoot = path.resolve(process.cwd(), "..", "..")
|
||||
await execa("npx", ["vsce", "package"], {
|
||||
cwd: clineRoot,
|
||||
env: {
|
||||
IS_TEST: "true",
|
||||
},
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
// Find the generated VSIX file
|
||||
// Find the generated VSIX file(s)
|
||||
const files = fs.readdirSync(clineRoot)
|
||||
const vsixFile = files.find((file) => file.endsWith(".vsix"))
|
||||
if (vsixFile) {
|
||||
vsixPath = path.join(clineRoot, vsixFile)
|
||||
console.log(`Using built VSIX: ${vsixPath}`)
|
||||
const vsixFiles = files.filter((file) => file.endsWith(".vsix"))
|
||||
|
||||
if (vsixFiles.length > 0) {
|
||||
// Get file stats to find the most recent one
|
||||
const vsixFilesWithStats = vsixFiles.map((file) => {
|
||||
const filePath = path.join(clineRoot, file)
|
||||
return {
|
||||
file,
|
||||
path: filePath,
|
||||
mtime: fs.statSync(filePath).mtime,
|
||||
}
|
||||
})
|
||||
|
||||
// Sort by modification time (most recent first)
|
||||
vsixFilesWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
|
||||
|
||||
// Use the most recent VSIX
|
||||
vsixPath = vsixFilesWithStats[0].path
|
||||
console.log(`Using most recent VSIX: ${vsixPath} (modified ${vsixFilesWithStats[0].mtime.toISOString()})`)
|
||||
|
||||
// Log all found VSIX files for debugging
|
||||
if (vsixFiles.length > 1) {
|
||||
console.log(`Found ${vsixFiles.length} VSIX files:`)
|
||||
vsixFilesWithStats.forEach((f) => {
|
||||
console.log(` - ${f.file} (modified ${f.mtime.toISOString()})`)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.warn("Could not find generated VSIX file")
|
||||
}
|
||||
@@ -66,6 +85,21 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
|
||||
fs.mkdirSync(tempExtensionsDir, { recursive: true })
|
||||
console.log(`Created temporary extensions directory: ${tempExtensionsDir}`)
|
||||
|
||||
// Create evals.env file in the workspace to trigger test mode
|
||||
console.log(`Creating evals.env file in workspace: ${workspacePath}`)
|
||||
const evalsEnvPath = path.join(workspacePath, "evals.env")
|
||||
fs.writeFileSync(
|
||||
evalsEnvPath,
|
||||
`# This file activates Cline test mode
|
||||
# Created at: ${new Date().toISOString()}
|
||||
#
|
||||
# This file is automatically detected by the Cline extension
|
||||
# and enables test mode for automated evaluations.
|
||||
#
|
||||
# Delete this file to deactivate test mode.
|
||||
`,
|
||||
)
|
||||
|
||||
// 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")
|
||||
@@ -548,7 +582,7 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
|
||||
console.warn(`Error closing VS Code: ${error}`)
|
||||
}
|
||||
|
||||
// Clean up temporary directories
|
||||
// Clean up temporary directories and evals.env file
|
||||
try {
|
||||
console.log(`Removing temporary user data directory: ${resources.tempUserDataDir}`)
|
||||
fs.rmSync(resources.tempUserDataDir, { recursive: true, force: true })
|
||||
@@ -563,6 +597,17 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
|
||||
console.warn(`Error removing temporary extensions directory: ${error}`)
|
||||
}
|
||||
|
||||
// Remove the evals.env file
|
||||
try {
|
||||
const evalsEnvPath = path.join(workspacePath, "evals.env")
|
||||
if (fs.existsSync(evalsEnvPath)) {
|
||||
console.log(`Removing evals.env file: ${evalsEnvPath}`)
|
||||
fs.unlinkSync(evalsEnvPath)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Error removing evals.env file: ${error}`)
|
||||
}
|
||||
|
||||
// Remove from the global map
|
||||
workspaceResources.delete(workspacePath)
|
||||
|
||||
|
||||
Generated
+411
-10
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.12.3",
|
||||
"version": "3.13.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.12.3",
|
||||
"version": "3.13.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/bedrock-sdk": "^0.12.4",
|
||||
@@ -15,7 +15,7 @@
|
||||
"@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",
|
||||
"@google/genai": "^0.9.0",
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
"@modelcontextprotocol/sdk": "^1.7.0",
|
||||
@@ -40,8 +40,10 @@
|
||||
"fzf": "^0.5.2",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"globby": "^14.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ignore": "^7.0.3",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"mammoth": "^1.8.0",
|
||||
"monaco-vscode-textmate-theme-converter": "^0.1.7",
|
||||
"ollama": "^0.5.13",
|
||||
@@ -84,6 +86,7 @@
|
||||
"chalk": "^5.3.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.57.0",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"husky": "^9.1.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
@@ -93,6 +96,7 @@
|
||||
"sinon": "^19.0.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-proto": "^2.6.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"engines": {
|
||||
@@ -5564,11 +5568,17 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@google/generative-ai": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.18.0.tgz",
|
||||
"integrity": "sha512-AhaIWSpk2tuhYHrBhUqC0xrWWznmYEja1/TRDIb+5kruBU5kUzMlFsXCQNO9PzyTZ4clUJ3CX/Rvy+Xm9x+w3g==",
|
||||
"node_modules/@google/genai": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.9.0.tgz",
|
||||
"integrity": "sha512-FD2RizYGInsvfjeaN6O+wQGpRnGVglS1XWrGQr8K7D04AfMmvPodDSw94U9KyFtsVLzWH9kmlPyFM+G4jbmkqg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"google-auth-library": "^9.14.2",
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.22.4",
|
||||
"zod-to-json-schema": "^3.22.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
@@ -5904,6 +5914,90 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@mistralai/mistralai": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.0.tgz",
|
||||
@@ -9195,6 +9289,13 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
@@ -9380,6 +9481,36 @@
|
||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/are-we-there-yet/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
@@ -10051,6 +10182,16 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/chrome-launcher": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz",
|
||||
@@ -10591,6 +10732,13 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -12092,6 +12240,32 @@
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@@ -12495,9 +12669,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/google-auth-library": {
|
||||
"version": "9.14.0",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.0.tgz",
|
||||
"integrity": "sha512-Y/eq+RWVs55Io/anIsm24sDS8X79Tq948zVLGaa7+KlJYYqaGwp1YI37w48nzrNi12RgnzMrQD4NzdmCowT90g==",
|
||||
"version": "9.15.1",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
|
||||
"integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0",
|
||||
"ecdsa-sig-formatter": "^1.0.11",
|
||||
@@ -12535,6 +12710,20 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/grpc-tools": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.13.0.tgz",
|
||||
"integrity": "sha512-7CbkJ1yWPfX0nHjbYG58BQThNhbICXBZynzCUxCb3LzX5X9B3hQbRY2STiRgIEiLILlK9fgl0z0QVGwPCdXf5g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"grpc_tools_node_protoc": "bin/protoc.js",
|
||||
"grpc_tools_node_protoc_plugin": "bin/protoc_plugin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/gtoken": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
|
||||
@@ -13496,6 +13685,14 @@
|
||||
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jschardet": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.1.4.tgz",
|
||||
"integrity": "sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==",
|
||||
"engines": {
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/json-bigint": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||
@@ -13532,6 +13729,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
@@ -14001,12 +14211,52 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz",
|
||||
@@ -14374,6 +14624,22 @@
|
||||
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
@@ -14635,6 +14901,84 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^2.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
"gauge": "^3.0.0",
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/npmlog/node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.2",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.1",
|
||||
"object-assign": "^4.1.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wide-align": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog/node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/npmlog/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
@@ -16103,6 +16447,13 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@@ -16804,6 +17155,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
|
||||
@@ -16829,6 +17198,16 @@
|
||||
"streamx": "^2.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/term-size": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
|
||||
@@ -17098,6 +17477,21 @@
|
||||
"@bufbuild/protobuf": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
||||
"integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json5": "^2.2.2",
|
||||
"minimist": "^1.2.6",
|
||||
"strip-bom": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
@@ -17898,6 +18292,13 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
|
||||
+13
-16
@@ -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.13.0",
|
||||
"version": "3.13.2",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -39,7 +39,9 @@
|
||||
"ai",
|
||||
"llama"
|
||||
],
|
||||
"activationEvents": [],
|
||||
"activationEvents": [
|
||||
"workspaceContains:evals.env"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"viewsContainers": {
|
||||
@@ -112,11 +114,6 @@
|
||||
"title": "Add to Cline",
|
||||
"category": "Cline"
|
||||
},
|
||||
{
|
||||
"command": "cline.fixWithCline",
|
||||
"title": "Fix with Cline",
|
||||
"category": "Cline"
|
||||
},
|
||||
{
|
||||
"command": "cline.focusChatInput",
|
||||
"title": "Jump to Chat Input",
|
||||
@@ -292,17 +289,13 @@
|
||||
"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",
|
||||
"watch:tsc": "npm run protos && 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",
|
||||
"compile-tests": "tsc -p ./tsconfig.test.json --outDir out",
|
||||
"watch-tests": "tsc -p . -w --outDir out",
|
||||
"compile-tests": "npm run protos && tsc -p ./tsconfig.test.json --outDir out",
|
||||
"watch-tests": "npm run protos && tsc -p . -w --outDir out",
|
||||
"pretest": "npm run compile-tests && npm run compile && npm run lint",
|
||||
"check-types": "tsc --noEmit",
|
||||
"check-types": "npm run protos && tsc --noEmit",
|
||||
"lint": "eslint src --ext ts && eslint webview-ui/src --ext ts",
|
||||
"format": "prettier . --check",
|
||||
"format:fix": "prettier . --write",
|
||||
@@ -343,6 +336,7 @@
|
||||
"chalk": "^5.3.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.57.0",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"husky": "^9.1.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
@@ -352,6 +346,7 @@
|
||||
"sinon": "^19.0.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-proto": "^2.6.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -361,7 +356,7 @@
|
||||
"@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",
|
||||
"@google/genai": "^0.9.0",
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
"@modelcontextprotocol/sdk": "^1.7.0",
|
||||
@@ -386,8 +381,10 @@
|
||||
"fzf": "^0.5.2",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"globby": "^14.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ignore": "^7.0.3",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"mammoth": "^1.8.0",
|
||||
"monaco-vscode-textmate-theme-converter": "^0.1.7",
|
||||
"ollama": "^0.5.13",
|
||||
|
||||
@@ -7,6 +7,7 @@ import "common.proto";
|
||||
service BrowserService {
|
||||
rpc getBrowserConnectionInfo(EmptyRequest) returns (BrowserConnectionInfo);
|
||||
rpc testBrowserConnection(StringRequest) returns (BrowserConnection);
|
||||
rpc discoverBrowser(EmptyRequest) returns (BrowserConnection);
|
||||
}
|
||||
|
||||
message BrowserConnectionInfo {
|
||||
|
||||
+12
-23
@@ -6,6 +6,11 @@ import { execSync } from "child_process"
|
||||
import { globby } from "globby"
|
||||
import chalk from "chalk"
|
||||
|
||||
import { createRequire } from "module"
|
||||
const require = createRequire(import.meta.url)
|
||||
const protoc = path.join(require.resolve("grpc-tools"), "../bin/protoc")
|
||||
const tsProtoPlugin = require.resolve("ts-proto/protoc-gen-ts_proto")
|
||||
|
||||
// Get script directory and root directory
|
||||
const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname)
|
||||
const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
|
||||
@@ -13,26 +18,6 @@ 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")
|
||||
|
||||
@@ -48,15 +33,15 @@ async function main() {
|
||||
|
||||
// Process all proto files
|
||||
console.log(chalk.cyan("Processing proto files from"), SCRIPT_DIR)
|
||||
const protoFiles = await globby("**/*.proto", { cwd: 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}"`,
|
||||
protoc,
|
||||
`--plugin=protoc-gen-ts_proto="${tsProtoPlugin}"`,
|
||||
`--ts_proto_out="${TS_OUT_DIR}"`,
|
||||
"--ts_proto_opt=outputServices=generic-definitions,env=node,esModuleInterop=true,useDate=false,useOptionals=messages",
|
||||
`--proto_path="${SCRIPT_DIR}"`,
|
||||
@@ -92,7 +77,11 @@ async function generateMethodRegistrations() {
|
||||
console.log(chalk.cyan("Generating method registration files..."))
|
||||
|
||||
const serviceDirs = [
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "mcp"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "browser"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "checkpoints"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "file"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "task"),
|
||||
// Add more service directories here as needed
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service CheckpointsService {
|
||||
rpc checkpointDiff(Int64Request) returns (Empty);
|
||||
rpc checkpointRestore(CheckpointRestoreRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message CheckpointRestoreRequest {
|
||||
Metadata metadata = 1;
|
||||
int64 number = 2;
|
||||
string restore_type = 3;
|
||||
optional int64 offset = 4;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// Service for file-related operations
|
||||
service FileService {
|
||||
// Opens a file in the editor
|
||||
rpc openFile(StringRequest) returns (Empty);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service McpService {
|
||||
rpc toggleMcpServer(ToggleMcpServerRequest) returns (McpServers);
|
||||
rpc updateMcpTimeout(UpdateMcpTimeoutRequest) returns (McpServers);
|
||||
}
|
||||
|
||||
message ToggleMcpServerRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
bool disabled = 3;
|
||||
}
|
||||
|
||||
message UpdateMcpTimeoutRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
int32 timeout = 3;
|
||||
}
|
||||
|
||||
message McpTool {
|
||||
string name = 1;
|
||||
optional string description = 2;
|
||||
optional string input_schema = 3;
|
||||
optional bool auto_approve = 4;
|
||||
}
|
||||
|
||||
message McpResource {
|
||||
string uri = 1;
|
||||
string name = 2;
|
||||
optional string mime_type = 3;
|
||||
optional string description = 4;
|
||||
}
|
||||
|
||||
message McpResourceTemplate {
|
||||
string uri_template = 1;
|
||||
string name = 2;
|
||||
optional string mime_type = 3;
|
||||
optional string description = 4;
|
||||
}
|
||||
|
||||
enum McpServerStatus {
|
||||
// Protobuf enums (in proto3) must have a zero value defined, which serves as the default if the field isn't explicitly set.
|
||||
// To align with the required nature of the TypeScript type and avoid an unnecessary UNSPECIFIED state, we map one of the existing statuses to this zero value.
|
||||
MCP_SERVER_STATUS_DISCONNECTED = 0; // default
|
||||
MCP_SERVER_STATUS_CONNECTED = 1;
|
||||
MCP_SERVER_STATUS_CONNECTING = 2;
|
||||
}
|
||||
|
||||
message McpServer {
|
||||
string name = 1;
|
||||
string config = 2;
|
||||
McpServerStatus status = 3;
|
||||
optional string error = 4;
|
||||
repeated McpTool tools = 5;
|
||||
repeated McpResource resources = 6;
|
||||
repeated McpResourceTemplate resource_templates = 7;
|
||||
optional bool disabled = 8;
|
||||
optional int32 timeout = 9;
|
||||
}
|
||||
|
||||
message McpServers {
|
||||
repeated McpServer mcp_servers = 1;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service TaskService {
|
||||
// Cancels the currently running task
|
||||
rpc cancelTask(EmptyRequest) returns (Empty);
|
||||
// Clears the current task
|
||||
rpc clearTask(EmptyRequest) returns (Empty);
|
||||
// Creates a new task with the given text and optional images
|
||||
rpc newTask(NewTaskRequest) returns (Empty);
|
||||
}
|
||||
|
||||
// Request message for creating a new task
|
||||
message NewTaskRequest {
|
||||
Metadata metadata = 1;
|
||||
string text = 2;
|
||||
repeated string images = 3;
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import "should"
|
||||
import sinon from "sinon"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { OllamaHandler } from "../ollama"
|
||||
import { ApiHandlerOptions } from "../../../shared/api"
|
||||
import { ApiHandlerOptions } from "@shared/api"
|
||||
import axios from "axios"
|
||||
|
||||
describe("OllamaHandler", () => {
|
||||
@@ -96,7 +96,7 @@ describe("OllamaHandler", () => {
|
||||
try {
|
||||
// Create a promise that rejects after a short timeout
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => reject(new Error("Ollama request timed out after 30 seconds")), 100)
|
||||
setTimeout(() => reject(new Error("Ollama request timed out after 120 seconds")), 100)
|
||||
})
|
||||
|
||||
// Create a promise that never resolves
|
||||
@@ -125,7 +125,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
// Check the result
|
||||
errorMessage.should.equal("Ollama request timed out after 30 seconds")
|
||||
errorMessage.should.equal("Ollama request timed out after 120 seconds")
|
||||
|
||||
// Restore the fake timers for other tests
|
||||
clock = sinon.useFakeTimers()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { withRetry } from "../retry"
|
||||
import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "../../shared/api"
|
||||
import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
askSageModels,
|
||||
askSageDefaultModelId,
|
||||
askSageDefaultURL,
|
||||
} from "../../shared/api"
|
||||
} from "@shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
type AskSageRequest = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { ApiHandlerOptions, bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "../../shared/api"
|
||||
import { ApiHandlerOptions, bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
import axios from "axios"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, DeepSeekModelId, ModelInfo, deepSeekDefaultModelId, deepSeekModels } from "../../shared/api"
|
||||
import { ApiHandlerOptions, DeepSeekModelId, ModelInfo, deepSeekDefaultModelId, deepSeekModels } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiHandler } from ".."
|
||||
import { ApiHandlerOptions, doubaoDefaultModelId, DoubaoModelId, doubaoModels, ModelInfo } from "../../shared/api"
|
||||
import { ApiHandlerOptions, doubaoDefaultModelId, DoubaoModelId, doubaoModels, ModelInfo } from "@shared/api"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
|
||||
+128
-26
@@ -1,52 +1,154 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { GoogleGenerativeAI } from "@google/generative-ai"
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
|
||||
import { GoogleGenAI, type Content, type GenerateContentConfig, type GenerateContentResponseUsageMetadata } from "@google/genai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "../../shared/api"
|
||||
import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "@shared/api"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
// Define a default TTL for the cache (e.g., 1 hour in seconds)
|
||||
const DEFAULT_CACHE_TTL_SECONDS = 3600
|
||||
|
||||
export class GeminiHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: GoogleGenerativeAI
|
||||
private client: GoogleGenAI // Updated client type
|
||||
|
||||
// Internal state for caching
|
||||
private cacheName: string | null = null
|
||||
private cacheExpireTime: number | null = null
|
||||
private isFirstApiCall = true
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
if (!options.geminiApiKey) {
|
||||
throw new Error("API key is required for Google Gemini")
|
||||
}
|
||||
this.options = options
|
||||
this.client = new GoogleGenerativeAI(options.geminiApiKey)
|
||||
// Updated client initialization
|
||||
this.client = new GoogleGenAI({ apiKey: options.geminiApiKey })
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const modelOptions = {
|
||||
model: this.getModel().id,
|
||||
systemInstruction: systemPrompt,
|
||||
const { id: modelId, info: modelInfo } = this.getModel()
|
||||
|
||||
// --- Cache Handling Logic ---
|
||||
const isCacheValid = this.cacheName && this.cacheExpireTime && Date.now() < this.cacheExpireTime
|
||||
let useCache = !this.isFirstApiCall && isCacheValid
|
||||
|
||||
if (this.isFirstApiCall && !isCacheValid && systemPrompt) {
|
||||
// It's the first call, no valid cache exists, and we have a system prompt. Attempt cache creation.
|
||||
this.isFirstApiCall = false
|
||||
|
||||
// Minimum token check heuristic (simple length check for now, could be improved)
|
||||
// Gemini requires minimum 4096 tokens. A simple length check isn't accurate but avoids complex token counting here.
|
||||
// Let's assume a generous average of 4 chars/token. 4096 tokens * 4 chars/token = 16384 chars.
|
||||
const MIN_SYSTEM_PROMPT_LENGTH_FOR_CACHE = 16384
|
||||
if (systemPrompt.length >= MIN_SYSTEM_PROMPT_LENGTH_FOR_CACHE) {
|
||||
// Start cache creation asynchronously, don't block the main request
|
||||
this.createCacheInBackground(modelId, systemPrompt)
|
||||
}
|
||||
// Proceed with the first request *without* using the cache, as it's being created.
|
||||
useCache = false
|
||||
} else if (!isCacheValid && this.cacheName) {
|
||||
// Cache exists but has expired
|
||||
this.cacheName = null
|
||||
this.cacheExpireTime = null
|
||||
useCache = false
|
||||
}
|
||||
// --- End Cache Handling Logic ---
|
||||
|
||||
// Re-implement thinking budget logic based on new SDK structure
|
||||
const thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const maxBudget = modelInfo.thinkingConfig?.maxBudget ?? 0
|
||||
|
||||
// port add baseUrl configuration for gemini api requests (#2843)
|
||||
const httpOptions = this.options.geminiBaseUrl ? { baseUrl: this.options.geminiBaseUrl } : undefined
|
||||
|
||||
// Base generation config - Conditionally include systemInstruction based on cache usage
|
||||
const generationConfig: GenerateContentConfig = {
|
||||
httpOptions,
|
||||
temperature: 0, // Default temperature
|
||||
// Only include systemInstruction if NOT using the cache
|
||||
...(useCache ? {} : { systemInstruction: systemPrompt }),
|
||||
}
|
||||
|
||||
const clientOptions = this.options.geminiBaseUrl ? { baseUrl: this.options.geminiBaseUrl } : undefined
|
||||
const model = this.client.getGenerativeModel(modelOptions, clientOptions)
|
||||
const result = await model.generateContentStream({
|
||||
contents: messages.map(convertAnthropicMessageToGemini),
|
||||
generationConfig: {
|
||||
// maxOutputTokens: this.getModel().info.maxTokens,
|
||||
temperature: 0,
|
||||
},
|
||||
})
|
||||
// Convert messages to the format expected by @google/genai
|
||||
// Note: convertAnthropicMessageToGemini might need adjustments
|
||||
const contents: Content[] = messages.map(convertAnthropicMessageToGemini)
|
||||
|
||||
for await (const chunk of result.stream) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.text(),
|
||||
// Construct the main request config - Type as GenerateContentConfig
|
||||
const requestConfig: GenerateContentConfig = {
|
||||
...generationConfig,
|
||||
}
|
||||
|
||||
// Add thinking config if the model supports it
|
||||
if (modelInfo.thinkingConfig?.outputPrice !== undefined && maxBudget > 0) {
|
||||
requestConfig.thinkingConfig = {
|
||||
thinkingBudget: thinkingBudget,
|
||||
}
|
||||
}
|
||||
|
||||
const response = await result.response
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
|
||||
outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
|
||||
// Generate content using the new SDK structure via client.models
|
||||
const result = await this.client.models.generateContentStream({
|
||||
model: modelId, // Pass model ID directly
|
||||
contents,
|
||||
// Add cachedContent if using the cache
|
||||
config: {
|
||||
...requestConfig,
|
||||
...(useCache ? { cachedContent: this.cacheName! } : {}),
|
||||
},
|
||||
})
|
||||
|
||||
// Declare variable to hold the last usage metadata found
|
||||
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
|
||||
|
||||
// Iterate directly over the stream
|
||||
for await (const chunk of result) {
|
||||
if (chunk.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.text,
|
||||
}
|
||||
}
|
||||
if (chunk.usageMetadata) {
|
||||
lastUsageMetadata = chunk.usageMetadata
|
||||
}
|
||||
}
|
||||
|
||||
if (lastUsageMetadata) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: lastUsageMetadata.promptTokenCount ?? 0,
|
||||
outputTokens: lastUsageMetadata.candidatesTokenCount ?? 0,
|
||||
cacheWriteTokens: lastUsageMetadata.cachedContentTokenCount ?? 0,
|
||||
cacheReadTokens: useCache ? (lastUsageMetadata.promptTokenCount ?? 0) : 0, // If cache used, prompt tokens are read from cache
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async createCacheInBackground(modelId: string, systemInstruction: string): Promise<void> {
|
||||
try {
|
||||
const cache = await this.client.caches.create({
|
||||
model: modelId,
|
||||
config: {
|
||||
systemInstruction: systemInstruction,
|
||||
ttl: `${DEFAULT_CACHE_TTL_SECONDS}s`,
|
||||
},
|
||||
})
|
||||
|
||||
if (cache?.name) {
|
||||
this.cacheName = cache.name
|
||||
// Calculate expiry timestamp using the default TTL, as the response object might not contain it directly.
|
||||
this.cacheExpireTime = Date.now() + DEFAULT_CACHE_TTL_SECONDS * 1000
|
||||
} else {
|
||||
console.warn("Gemini cache creation call succeeded but returned no cache name.")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to create Gemini cache in background:", error)
|
||||
// Reset state if creation failed definitively
|
||||
this.cacheName = null
|
||||
this.cacheExpireTime = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandlerOptions, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "../../shared/api"
|
||||
import { ApiHandlerOptions, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import { ApiHandler } from ".."
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
|
||||
@@ -2,16 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Mistral } from "@mistralai/mistralai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import {
|
||||
ApiHandlerOptions,
|
||||
mistralDefaultModelId,
|
||||
MistralModelId,
|
||||
mistralModels,
|
||||
ModelInfo,
|
||||
openAiNativeDefaultModelId,
|
||||
OpenAiNativeModelId,
|
||||
openAiNativeModels,
|
||||
} from "../../shared/api"
|
||||
import { ApiHandlerOptions, mistralDefaultModelId, MistralModelId, mistralModels, ModelInfo } from "@shared/api"
|
||||
import { convertToMistralMessages } from "../transform/mistral-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
try {
|
||||
// Create a promise that rejects after timeout
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => reject(new Error("Ollama request timed out after 30 seconds")), 30000)
|
||||
setTimeout(() => reject(new Error("Ollama request timed out after 120 seconds")), 120000)
|
||||
})
|
||||
|
||||
// Create the actual API request promise
|
||||
@@ -63,7 +63,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
} catch (error: any) {
|
||||
// Check if it's a timeout error
|
||||
if (error.message && error.message.includes("timed out")) {
|
||||
throw new Error("Ollama request timed out after 30 seconds")
|
||||
throw new Error("Ollama request timed out after 120 seconds")
|
||||
}
|
||||
|
||||
// Enhance error reporting
|
||||
|
||||
@@ -2,13 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import {
|
||||
ApiHandlerOptions,
|
||||
ModelInfo,
|
||||
openAiNativeDefaultModelId,
|
||||
OpenAiNativeModelId,
|
||||
openAiNativeModels,
|
||||
} from "../../shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiNativeDefaultModelId, OpenAiNativeModelId, openAiNativeModels } from "@shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI, { AzureOpenAI } from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandlerOptions, azureOpenAiDefaultApiVersion, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
|
||||
import { ApiHandlerOptions, azureOpenAiDefaultApiVersion, ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -41,7 +41,7 @@ export class OpenAiHandler implements ApiHandler {
|
||||
const modelId = this.options.openAiModelId ?? ""
|
||||
const isDeepseekReasoner = modelId.includes("deepseek-reasoner")
|
||||
const isR1FormatRequired = this.options.openAiModelInfo?.isR1FormatRequired ?? false
|
||||
const isReasoningModelFamily = modelId.includes("o3") || modelId.includes("o4")
|
||||
const isReasoningModelFamily = modelId.includes("o1") || modelId.includes("o3") || modelId.includes("o4")
|
||||
|
||||
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
|
||||
@@ -3,7 +3,7 @@ import axios from "axios"
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { withRetry } from "../retry"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
internationalQwenDefaultModelId,
|
||||
MainlandQwenModelId,
|
||||
InternationalQwenModelId,
|
||||
} from "../../shared/api"
|
||||
} from "@shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandlerOptions, ModelInfo, requestyDefaultModelId, requestyDefaultModelInfo } from "../../shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, requestyDefaultModelId, requestyDefaultModelInfo } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToOpenAiMessages } from "@api/transform/openai-format"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
|
||||
export class RequestyHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandlerOptions, ModelInfo, SambanovaModelId, sambanovaDefaultModelId, sambanovaModels } from "../../shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, SambanovaModelId, sambanovaDefaultModelId, sambanovaModels } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { convertToOpenAiMessages } from "@/api/transform/openai-format"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { convertToR1Format } from "@api/transform/r1-format"
|
||||
|
||||
export class SambanovaHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { convertToOpenAiMessages } from "@api/transform/openai-format"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { convertToR1Format } from "@api/transform/r1-format"
|
||||
|
||||
export class TogetherHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
|
||||
@@ -2,10 +2,10 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, ModelInfo, vertexDefaultModelId, VertexModelId, vertexModels } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { ApiHandlerOptions, ModelInfo, vertexDefaultModelId, VertexModelId, vertexModels } from "@shared/api"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { VertexAI } from "@google-cloud/vertexai"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
|
||||
// https://docs.anthropic.com/en/api/claude-on-vertex-ai
|
||||
export class VertexHandler implements ApiHandler {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as vscode from "vscode"
|
||||
import { ApiHandler, SingleCompletionHandler } from "../"
|
||||
import { calculateApiCostAnthropic } from "../../utils/cost"
|
||||
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 { calculateApiCostAnthropic } from "@utils/cost"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { convertToVsCodeLmMessages } from "@api/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.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, XAIModelId, ModelInfo, xaiDefaultModelId, xaiModels } from "../../shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { ApiHandlerOptions, XAIModelId, ModelInfo, xaiDefaultModelId, xaiModels } from "@shared/api"
|
||||
import { convertToOpenAiMessages } from "@api/transform/openai-format"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
|
||||
export class XAIHandler implements ApiHandler {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Content, EnhancedGenerateContentResponse, InlineDataPart, Part, TextPart } from "@google/generative-ai"
|
||||
import { Content, GenerateContentResponse, Part } from "@google/genai"
|
||||
|
||||
export function convertAnthropicContentToGemini(content: string | Anthropic.ContentBlockParam[]): Part[] {
|
||||
if (typeof content === "string") {
|
||||
return [{ text: content } as TextPart]
|
||||
return [{ text: content }]
|
||||
}
|
||||
return content.flatMap((block) => {
|
||||
return content.flatMap((block): Part => {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return { text: block.text } as TextPart
|
||||
return { text: block.text }
|
||||
case "image":
|
||||
if (block.source.type !== "base64") {
|
||||
throw new Error("Unsupported image source type")
|
||||
@@ -18,7 +18,7 @@ export function convertAnthropicContentToGemini(content: string | Anthropic.Cont
|
||||
data: block.source.data,
|
||||
mimeType: block.source.media_type,
|
||||
},
|
||||
} as InlineDataPart
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported content block type: ${block.type}`)
|
||||
}
|
||||
@@ -39,16 +39,14 @@ export function unescapeGeminiContent(content: string) {
|
||||
return content.replace(/\\n/g, "\n").replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\r/g, "\r").replace(/\\t/g, "\t")
|
||||
}
|
||||
|
||||
export function convertGeminiResponseToAnthropic(response: EnhancedGenerateContentResponse): Anthropic.Messages.Message {
|
||||
export function convertGeminiResponseToAnthropic(response: GenerateContentResponse): Anthropic.Messages.Message {
|
||||
const content: Anthropic.Messages.ContentBlock[] = []
|
||||
|
||||
// Add the main text response
|
||||
const text = response.text()
|
||||
const text = response.text
|
||||
if (text) {
|
||||
content.push({ type: "text", text, citations: null })
|
||||
}
|
||||
|
||||
// Determine stop reason
|
||||
let stop_reason: Anthropic.Messages.Message["stop_reason"] = null
|
||||
const finishReason = response.candidates?.[0]?.finishReason
|
||||
if (finishReason) {
|
||||
@@ -64,12 +62,11 @@ export function convertGeminiResponseToAnthropic(response: EnhancedGenerateConte
|
||||
case "OTHER":
|
||||
stop_reason = "stop_sequence"
|
||||
break
|
||||
// Add more cases if needed
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: `msg_${Date.now()}`, // Generate a unique ID
|
||||
id: `msg_${Date.now()}`,
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { ModelInfo } from "../../shared/api"
|
||||
import { convertToOpenAiMessages } from "./openai-format"
|
||||
import { convertToR1Format } from "./r1-format"
|
||||
import { ApiStream, ApiStreamChunk } from "./stream"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { convertToOpenAiMessages } from "@api/transform/openai-format"
|
||||
import { convertToR1Format } from "@api/transform/r1-format"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { OpenRouterErrorResponse } from "../providers/types"
|
||||
|
||||
export async function createOpenRouterStream(
|
||||
client: OpenAI,
|
||||
|
||||
@@ -24,6 +24,7 @@ export const toolUseNames = [
|
||||
"load_mcp_documentation",
|
||||
"attempt_completion",
|
||||
"new_task",
|
||||
"condense",
|
||||
] as const
|
||||
|
||||
// Converts array of tool call names into a union type ("execute_command" | "read_file" | ...)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ClineApiReqInfo, ClineMessage } from "../../../shared/ExtensionMessage"
|
||||
import { ApiHandler } from "../../../api"
|
||||
import { ClineApiReqInfo, ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { ApiHandler } from "@api/index"
|
||||
import { getContextWindowInfo } from "./context-window-utils"
|
||||
|
||||
class ContextManager {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { getContextWindowInfo } from "./context-window-utils"
|
||||
import { formatResponse } from "../../prompts/responses"
|
||||
import { GlobalFileNames } from "../../storage/disk"
|
||||
import { fileExistsAtPath } from "../../../utils/fs"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import * as path from "path"
|
||||
import fs from "fs/promises"
|
||||
import cloneDeep from "clone-deep"
|
||||
import { ClineApiReqInfo, ClineMessage } from "../../../shared/ExtensionMessage"
|
||||
import { ApiHandler } from "../../../api"
|
||||
import { ClineApiReqInfo, ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { ApiHandler } from "@api/index"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
enum EditType {
|
||||
@@ -193,14 +193,20 @@ export class ContextManager {
|
||||
public getNextTruncationRange(
|
||||
apiMessages: Anthropic.Messages.MessageParam[],
|
||||
currentDeletedRange: [number, number] | undefined,
|
||||
keep: "half" | "quarter",
|
||||
keep: "none" | "lastTwo" | "half" | "quarter",
|
||||
): [number, number] {
|
||||
// We always keep the first user-assistant pairing, and truncate an even number of messages from there
|
||||
const rangeStartIndex = 2 // index 0 and 1 are kept
|
||||
const startOfRest = currentDeletedRange ? currentDeletedRange[1] + 1 : 2 // inclusive starting index
|
||||
|
||||
let messagesToRemove: number
|
||||
if (keep === "half") {
|
||||
if (keep === "none") {
|
||||
// Removes all messages beyond the first core user/assistant message pair
|
||||
messagesToRemove = Math.max(apiMessages.length - startOfRest, 0)
|
||||
} else if (keep === "lastTwo") {
|
||||
// Keep the last user-assistant pair in addition to the first core user/assistant message pair
|
||||
messagesToRemove = Math.max(apiMessages.length - startOfRest - 2, 0)
|
||||
} else if (keep === "half") {
|
||||
// Remove half of remaining user-assistant pairs
|
||||
// We first calculate half of the messages then divide by 2 to get the number of pairs.
|
||||
// After flooring, we multiply by 2 to get the number of messages.
|
||||
@@ -382,6 +388,17 @@ export class ContextManager {
|
||||
return [contextHistoryUpdated, uniqueFileReadIndices]
|
||||
}
|
||||
|
||||
/**
|
||||
* Public function for triggering potentially setting the truncation message
|
||||
* If the truncation message already exists, does nothing, otherwise adds the message
|
||||
*/
|
||||
async triggerApplyStandardContextTruncationNoticeChange(timestamp: number, taskDirectory: string) {
|
||||
const updated = this.applyStandardContextTruncationNoticeChange(timestamp)
|
||||
if (updated) {
|
||||
await this.saveContextHistory(taskDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if there is any truncation and there is no other alteration already set, alter the assistant message to indicate this occurred
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiHandler } from "../../../api"
|
||||
import { OpenAiHandler } from "../../../api/providers/openai"
|
||||
import { ApiHandler } from "@api/index"
|
||||
import { OpenAiHandler } from "@api/providers/openai"
|
||||
|
||||
/**
|
||||
* Gets context window information for the given API handler
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import * as vscode from "vscode"
|
||||
|
||||
// Type definitions for FileContextTracker
|
||||
export interface FileMetadataEntry {
|
||||
path: string
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as sinon from "sinon"
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import { FileContextTracker } from "./FileContextTracker"
|
||||
import * as diskModule from "../../storage/disk"
|
||||
import * as diskModule from "@core/storage/disk"
|
||||
import type { TaskMetadata, FileMetadataEntry } from "./ContextTrackerTypes"
|
||||
|
||||
describe("FileContextTracker", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { getTaskMetadata, saveTaskMetadata } from "../../storage/disk"
|
||||
import { getTaskMetadata, saveTaskMetadata } from "@core/storage/disk"
|
||||
import type { FileMetadataEntry } from "./ContextTrackerTypes"
|
||||
|
||||
// This class is responsible for tracking file operations that may result in stale context.
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect } from "chai"
|
||||
import * as sinon from "sinon"
|
||||
import * as vscode from "vscode"
|
||||
import { ModelContextTracker } from "./ModelContextTracker"
|
||||
import * as diskModule from "../../storage/disk"
|
||||
import * as diskModule from "@core/storage/disk"
|
||||
import type { TaskMetadata } from "./ContextTrackerTypes"
|
||||
|
||||
describe("ModelContextTracker", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as vscode from "vscode"
|
||||
import { getTaskMetadata, saveTaskMetadata } from "../../storage/disk"
|
||||
import { getTaskMetadata, saveTaskMetadata } from "@core/storage/disk"
|
||||
|
||||
export class ModelContextTracker {
|
||||
readonly taskId: string
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import path from "path"
|
||||
import { ensureRulesDirectoryExists, GlobalFileNames } from "../../../storage/disk"
|
||||
import { fileExistsAtPath, isDirectory, readDirectory } from "../../../../utils/fs"
|
||||
import { formatResponse } from "../../../prompts/responses"
|
||||
import { ensureRulesDirectoryExists, GlobalFileNames } from "@core/storage/disk"
|
||||
import { fileExistsAtPath, isDirectory, readDirectory } from "@utils/fs"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import fs from "fs/promises"
|
||||
import { ClineRulesToggles } from "../../../../shared/cline-rules"
|
||||
import { getGlobalState, getWorkspaceState, updateGlobalState, updateWorkspaceState } from "../../../storage/state"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { getGlobalState, getWorkspaceState, updateGlobalState, updateWorkspaceState } from "@core/storage/state"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
export const getGlobalClineRules = async (globalClineRulesFilePath: string, toggles: ClineRulesToggles) => {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { BrowserConnection } from "@shared/proto/browser"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { Controller } from "../index"
|
||||
import { getAllExtensionState } from "@core/storage/state"
|
||||
import { BrowserSession } from "@services/browser/BrowserSession"
|
||||
import { discoverChromeInstances } from "@services/browser/BrowserDiscovery"
|
||||
|
||||
/**
|
||||
* Discover Chrome instances
|
||||
* @param controller The controller instance
|
||||
* @param request The request message
|
||||
* @returns The browser connection result
|
||||
*/
|
||||
export async function discoverBrowser(controller: Controller, request: EmptyRequest): Promise<BrowserConnection> {
|
||||
try {
|
||||
const discoveredHost = await discoverChromeInstances()
|
||||
|
||||
if (discoveredHost) {
|
||||
// Don't update the remoteBrowserHost state when auto-discovering
|
||||
// This way we don't override the user's preference
|
||||
|
||||
// Test the connection to get the endpoint
|
||||
const { browserSettings } = await getAllExtensionState(controller.context)
|
||||
const browserSession = new BrowserSession(controller.context, browserSettings)
|
||||
const result = await browserSession.testConnection(discoveredHost)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully discovered and connected to Chrome at ${discoveredHost}`,
|
||||
endpoint: result.endpoint || "",
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
"No Chrome instances found. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
|
||||
endpoint: "",
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Error discovering browser: ${error instanceof Error ? error.message : String(error)}`,
|
||||
endpoint: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BrowserConnectionInfo } from "../../../shared/proto/browser"
|
||||
import { EmptyRequest } from "../../../shared/proto/common"
|
||||
import { BrowserConnectionInfo } from "@shared/proto/browser"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { Controller } from "../index"
|
||||
import { getAllExtensionState } from "../../storage/state"
|
||||
import { getAllExtensionState } from "@core/storage/state"
|
||||
|
||||
/**
|
||||
* Get information about the current browser connection
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { discoverBrowser } from "./discoverBrowser"
|
||||
import { getBrowserConnectionInfo } from "./getBrowserConnectionInfo"
|
||||
import { testBrowserConnection } from "./testBrowserConnection"
|
||||
|
||||
// Register all browser service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("discoverBrowser", discoverBrowser)
|
||||
registerMethod("getBrowserConnectionInfo", getBrowserConnectionInfo)
|
||||
registerMethod("testBrowserConnection", testBrowserConnection)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BrowserConnection } from "../../../shared/proto/browser"
|
||||
import { StringRequest } from "../../../shared/proto/common"
|
||||
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"
|
||||
import { getAllExtensionState } from "@core/storage/state"
|
||||
import { BrowserSession } from "@services/browser/BrowserSession"
|
||||
import { discoverChromeInstances } from "@services/browser/BrowserDiscovery"
|
||||
|
||||
/**
|
||||
* Test connection to a browser instance
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty, Int64Request } from "@shared/proto/common"
|
||||
|
||||
export async function checkpointDiff(controller: Controller, request: Int64Request): Promise<Empty> {
|
||||
if (request.value) {
|
||||
await controller.task?.presentMultifileDiff(request.value, false)
|
||||
}
|
||||
return Empty
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Controller } from ".."
|
||||
import { ClineCheckpointRestore } from "../../../shared/WebviewMessage"
|
||||
import { CheckpointRestoreRequest } from "../../../shared/proto/checkpoints"
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import pWaitFor from "p-wait-for"
|
||||
|
||||
export async function checkpointRestore(controller: Controller, request: CheckpointRestoreRequest): Promise<Empty> {
|
||||
await controller.cancelTask() // we cannot alter message history say if the task is active, as it could be in the middle of editing a file or running a command, which expect the ask to be responded to rather than being superseded by a new message eg add deleted_api_reqs
|
||||
|
||||
if (request.number) {
|
||||
// wait for messages to be loaded
|
||||
await pWaitFor(() => controller.task?.isInitialized === true, {
|
||||
timeout: 3_000,
|
||||
}).catch(() => {
|
||||
console.error("Failed to init new cline instance")
|
||||
})
|
||||
|
||||
// NOTE: cancelTask awaits abortTask, which awaits diffViewProvider.revertChanges, which reverts any edited files, allowing us to reset to a checkpoint rather than running into a state where the revertChanges function is called alongside or after the checkpoint reset
|
||||
await controller.task?.restoreCheckpoint(request.number, request.restoreType as ClineCheckpointRestore, request.offset)
|
||||
}
|
||||
return {}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create checkpoints service registry
|
||||
const checkpointsService = createServiceRegistry("checkpoints")
|
||||
|
||||
// Export the method handler type and registration function
|
||||
export type CheckpointsMethodHandler = ServiceMethodHandler
|
||||
export const registerMethod = checkpointsService.registerMethod
|
||||
|
||||
// Export the request handler
|
||||
export const handleCheckpointsServiceRequest = checkpointsService.handleRequest
|
||||
|
||||
// Register all checkpoints methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,14 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { checkpointDiff } from "./checkpointDiff"
|
||||
import { checkpointRestore } from "./checkpointRestore"
|
||||
|
||||
// Register all checkpoints service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("checkpointDiff", checkpointDiff)
|
||||
registerMethod("checkpointRestore", checkpointRestore)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create file service registry
|
||||
const fileService = createServiceRegistry("file")
|
||||
|
||||
// Export the method handler type and registration function
|
||||
export type FileMethodHandler = ServiceMethodHandler
|
||||
export const registerMethod = fileService.registerMethod
|
||||
|
||||
// Export the request handler
|
||||
export const handleFileServiceRequest = fileService.handleRequest
|
||||
|
||||
// Register all file methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,12 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { openFile } from "./openFile"
|
||||
|
||||
// Register all file service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("openFile", openFile)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty, StringRequest } from "@shared/proto/common"
|
||||
import { openFile as openFileIntegration } from "@integrations/misc/open-file"
|
||||
import { FileMethodHandler } from "./index"
|
||||
|
||||
/**
|
||||
* Opens a file in the editor
|
||||
* @param controller The controller instance
|
||||
* @param request The request message containing the file path in the 'value' field
|
||||
* @returns Empty response
|
||||
*/
|
||||
export const openFile: FileMethodHandler = async (controller: Controller, request: StringRequest): Promise<Empty> => {
|
||||
if (request.value) {
|
||||
openFileIntegration(request.value)
|
||||
}
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Controller } from "./index"
|
||||
import { handleBrowserServiceRequest } from "./browser/index"
|
||||
import { ExtensionMessage } from "../../shared/ExtensionMessage"
|
||||
import { handleFileServiceRequest } from "./file"
|
||||
import { handleTaskServiceRequest } from "./task"
|
||||
import { handleCheckpointsServiceRequest } from "./checkpoints"
|
||||
import { handleMcpServiceRequest } from "./mcp"
|
||||
|
||||
/**
|
||||
* Handles gRPC requests from the webview
|
||||
@@ -27,15 +30,35 @@ export class GrpcHandler {
|
||||
request_id: string
|
||||
}> {
|
||||
try {
|
||||
// Handle BrowserService requests
|
||||
if (service === "cline.BrowserService") {
|
||||
return {
|
||||
message: await handleBrowserServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
switch (service) {
|
||||
case "cline.BrowserService":
|
||||
return {
|
||||
message: await handleBrowserServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
case "cline.CheckpointsService":
|
||||
return {
|
||||
message: await handleCheckpointsServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
case "cline.FileService":
|
||||
return {
|
||||
message: await handleFileServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
case "cline.TaskService":
|
||||
return {
|
||||
message: await handleTaskServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
case "cline.McpService":
|
||||
return {
|
||||
message: await handleMcpServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown service: ${service}`)
|
||||
}
|
||||
|
||||
throw new Error(`Unknown service: ${service}`)
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
|
||||
+50
-121
@@ -8,32 +8,32 @@ 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"
|
||||
import { fetchOpenGraphData, isImageUrl } from "../../integrations/misc/link-preview"
|
||||
import { openFile, openImage } from "../../integrations/misc/open-file"
|
||||
import { selectImages } from "../../integrations/misc/process-images"
|
||||
import { getTheme } from "../../integrations/theme/getTheme"
|
||||
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
|
||||
import { ClineAccountService } from "../../services/account/ClineAccountService"
|
||||
import { discoverChromeInstances } from "../../services/browser/BrowserDiscovery"
|
||||
import { BrowserSession } from "../../services/browser/BrowserSession"
|
||||
import { McpHub } from "../../services/mcp/McpHub"
|
||||
import { searchWorkspaceFiles } from "../../services/search/file-search"
|
||||
import { telemetryService } from "../../services/telemetry/TelemetryService"
|
||||
import { ApiProvider, ModelInfo } from "../../shared/api"
|
||||
import { ChatContent } from "../../shared/ChatContent"
|
||||
import { ChatSettings } from "../../shared/ChatSettings"
|
||||
import { ExtensionMessage, ExtensionState, Invoke, Platform } from "../../shared/ExtensionMessage"
|
||||
import { HistoryItem } from "../../shared/HistoryItem"
|
||||
import { McpDownloadResponse, McpMarketplaceCatalog, McpServer } from "../../shared/mcp"
|
||||
import { TelemetrySetting } from "../../shared/TelemetrySetting"
|
||||
import { ClineCheckpointRestore, WebviewMessage } from "../../shared/WebviewMessage"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { searchCommits } from "../../utils/git"
|
||||
import { getWorkspacePath } from "../../utils/path"
|
||||
import { getTotalTasksSize } from "../../utils/storage"
|
||||
import { buildApiHandler } from "@api/index"
|
||||
import { cleanupLegacyCheckpoints } from "@integrations/checkpoints/CheckpointMigration"
|
||||
import { downloadTask } from "@integrations/misc/export-markdown"
|
||||
import { fetchOpenGraphData, isImageUrl } from "@integrations/misc/link-preview"
|
||||
import { openImage } from "@integrations/misc/open-file"
|
||||
import { handleFileServiceRequest } from "./file"
|
||||
import { selectImages } from "@integrations/misc/process-images"
|
||||
import { getTheme } from "@integrations/theme/getTheme"
|
||||
import WorkspaceTracker from "@integrations/workspace/WorkspaceTracker"
|
||||
import { ClineAccountService } from "@services/account/ClineAccountService"
|
||||
import { BrowserSession } from "@services/browser/BrowserSession"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { searchWorkspaceFiles } from "@services/search/file-search"
|
||||
import { telemetryService } from "@services/telemetry/TelemetryService"
|
||||
import { ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { ChatContent } from "@shared/ChatContent"
|
||||
import { ChatSettings } from "@shared/ChatSettings"
|
||||
import { ExtensionMessage, ExtensionState, Invoke, Platform } from "@shared/ExtensionMessage"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { McpDownloadResponse, McpMarketplaceCatalog, McpServer } from "@shared/mcp"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { ClineCheckpointRestore, WebviewMessage } from "@shared/WebviewMessage"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
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 {
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
updateWorkspaceState,
|
||||
} from "../storage/state"
|
||||
import { Task, cwd } from "../task"
|
||||
import { ClineRulesToggles } from "../../shared/cline-rules"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { createRuleFile, deleteRuleFile, refreshClineRulesToggles } from "../context/instructions/user-instructions/cline-rules"
|
||||
|
||||
/*
|
||||
@@ -139,6 +139,14 @@ export class Controller {
|
||||
await this.clearTask() // ensures that an existing task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one
|
||||
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } =
|
||||
await getAllExtensionState(this.context)
|
||||
|
||||
if (autoApprovalSettings) {
|
||||
const updatedAutoApprovalSettings = {
|
||||
...autoApprovalSettings,
|
||||
version: (autoApprovalSettings.version ?? 1) + 1,
|
||||
}
|
||||
await updateGlobalState(this.context, "autoApprovalSettings", updatedAutoApprovalSettings)
|
||||
}
|
||||
this.task = new Task(
|
||||
this.context,
|
||||
this.mcpHub,
|
||||
@@ -277,6 +285,9 @@ export class Controller {
|
||||
// initializing new instance of Cline will make sure that any agentically running promises in old instance don't affect our new task. this essentially creates a fresh slate for the new task
|
||||
await this.initTask(message.text, message.images)
|
||||
break
|
||||
case "condense":
|
||||
this.task?.handleWebviewAskResponse("yesButtonClicked")
|
||||
break
|
||||
case "apiConfiguration":
|
||||
if (message.apiConfiguration) {
|
||||
await updateApiConfiguration(this.context, message.apiConfiguration)
|
||||
@@ -288,11 +299,16 @@ export class Controller {
|
||||
break
|
||||
case "autoApprovalSettings":
|
||||
if (message.autoApprovalSettings) {
|
||||
await updateGlobalState(this.context, "autoApprovalSettings", message.autoApprovalSettings)
|
||||
if (this.task) {
|
||||
this.task.autoApprovalSettings = message.autoApprovalSettings
|
||||
const currentSettings = (await getAllExtensionState(this.context)).autoApprovalSettings
|
||||
const incomingVersion = message.autoApprovalSettings.version ?? 1
|
||||
const currentVersion = currentSettings?.version ?? 1
|
||||
if (incomingVersion > currentVersion) {
|
||||
await updateGlobalState(this.context, "autoApprovalSettings", message.autoApprovalSettings)
|
||||
if (this.task) {
|
||||
this.task.autoApprovalSettings = message.autoApprovalSettings
|
||||
}
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
break
|
||||
case "browserSettings":
|
||||
@@ -311,41 +327,6 @@ export class Controller {
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
break
|
||||
case "discoverBrowser":
|
||||
try {
|
||||
const discoveredHost = await discoverChromeInstances()
|
||||
|
||||
if (discoveredHost) {
|
||||
// Don't update the remoteBrowserHost state when auto-discovering
|
||||
// This way we don't override the user's preference
|
||||
|
||||
// Test the connection to get the endpoint
|
||||
const { browserSettings } = await getAllExtensionState(this.context)
|
||||
const browserSession = new BrowserSession(this.context, browserSettings)
|
||||
const result = await browserSession.testConnection(discoveredHost)
|
||||
|
||||
// Send the result back to the webview
|
||||
await this.postMessageToWebview({
|
||||
type: "browserConnectionResult",
|
||||
success: true,
|
||||
text: `Successfully discovered and connected to Chrome at ${discoveredHost}`,
|
||||
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 discovering browser: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
}
|
||||
break
|
||||
case "togglePlanActMode":
|
||||
if (message.chatSettings) {
|
||||
await this.togglePlanActModeWithChatSettings(message.chatSettings, message.chatContent)
|
||||
@@ -366,11 +347,6 @@ export class Controller {
|
||||
case "askResponse":
|
||||
this.task?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
|
||||
break
|
||||
case "clearTask":
|
||||
// newTask will start a new task with a given task text, while clear task resets the current session and allows for a new task to be started
|
||||
await this.clearTask()
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "didShowAnnouncement":
|
||||
await updateGlobalState(this.context, "lastShownAnnouncementId", this.latestAnnouncementId)
|
||||
await this.postStateToWebview()
|
||||
@@ -447,9 +423,6 @@ export class Controller {
|
||||
case "checkIsImageUrl":
|
||||
this.checkIsImageUrl(message.text!)
|
||||
break
|
||||
case "openFile":
|
||||
openFile(message.text!)
|
||||
break
|
||||
case "createRuleFile":
|
||||
if (typeof message.isGlobal !== "boolean" || typeof message.filename !== "string" || !message.filename) {
|
||||
console.error("createRuleFile: Missing or invalid parameters", {
|
||||
@@ -463,13 +436,13 @@ export class Controller {
|
||||
if (fileExists && filePath) {
|
||||
vscode.window.showWarningMessage(`Rule file "${message.filename}" already exists.`)
|
||||
// Still open it for editing
|
||||
openFile(filePath)
|
||||
await handleFileServiceRequest(this, "openFile", { value: filePath })
|
||||
return
|
||||
} else if (filePath && !fileExists) {
|
||||
await refreshClineRulesToggles(this.context, cwd)
|
||||
await this.postStateToWebview()
|
||||
|
||||
openFile(filePath)
|
||||
await handleFileServiceRequest(this, "openFile", { value: filePath })
|
||||
|
||||
vscode.window.showInformationMessage(
|
||||
`Created new ${message.isGlobal ? "global" : "workspace"} rule file: ${message.filename}`,
|
||||
@@ -483,36 +456,12 @@ export class Controller {
|
||||
case "openMention":
|
||||
openMention(message.text)
|
||||
break
|
||||
case "checkpointDiff": {
|
||||
if (message.number) {
|
||||
await this.task?.presentMultifileDiff(message.number, false)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "checkpointRestore": {
|
||||
await this.cancelTask() // we cannot alter message history say if the task is active, as it could be in the middle of editing a file or running a command, which expect the ask to be responded to rather than being superseded by a new message eg add deleted_api_reqs
|
||||
// cancel task waits for any open editor to be reverted and starts a new cline instance
|
||||
if (message.number) {
|
||||
// wait for messages to be loaded
|
||||
await pWaitFor(() => this.task?.isInitialized === true, {
|
||||
timeout: 3_000,
|
||||
}).catch(() => {
|
||||
console.error("Failed to init new cline instance")
|
||||
})
|
||||
// NOTE: cancelTask awaits abortTask, which awaits diffViewProvider.revertChanges, which reverts any edited files, allowing us to reset to a checkpoint rather than running into a state where the revertChanges function is called alongside or after the checkpoint reset
|
||||
await this.task?.restoreCheckpoint(message.number, message.text! as ClineCheckpointRestore, message.offset)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "taskCompletionViewChanges": {
|
||||
if (message.number) {
|
||||
await this.task?.presentMultifileDiff(message.number, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "cancelTask":
|
||||
this.cancelTask()
|
||||
break
|
||||
case "getLatestState":
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
@@ -552,7 +501,7 @@ export class Controller {
|
||||
case "openMcpSettings": {
|
||||
const mcpSettingsFilePath = await this.mcpHub?.getMcpSettingsFilePath()
|
||||
if (mcpSettingsFilePath) {
|
||||
openFile(mcpSettingsFilePath)
|
||||
await handleFileServiceRequest(this, "openFile", { value: mcpSettingsFilePath })
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -617,14 +566,6 @@ export class Controller {
|
||||
|
||||
// break
|
||||
// }
|
||||
case "toggleMcpServer": {
|
||||
try {
|
||||
await this.mcpHub?.toggleServerDisabled(message.serverName!, message.disabled!)
|
||||
} catch (error) {
|
||||
console.error(`Failed to toggle MCP server ${message.serverName}:`, error)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "toggleToolAutoApprove": {
|
||||
try {
|
||||
await this.mcpHub?.toggleToolAutoApprove(message.serverName!, message.toolNames!, message.autoApprove!)
|
||||
@@ -719,16 +660,6 @@ export class Controller {
|
||||
}
|
||||
break
|
||||
}
|
||||
case "updateMcpTimeout": {
|
||||
try {
|
||||
if (message.serverName && message.timeout) {
|
||||
await this.mcpHub?.updateServerTimeout(message.serverName, message.timeout)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to update timeout for server ${message.serverName}:`, error)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "openExtensionSettings": {
|
||||
const settingsFilter = message.text || ""
|
||||
await vscode.commands.executeCommand(
|
||||
@@ -1530,7 +1461,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
case "anthropic/claude-3.5-sonnet":
|
||||
case "anthropic/claude-3.5-sonnet:beta":
|
||||
// NOTE: this needs to be synced with api.ts/openrouter default model info
|
||||
modelInfo.supportsComputerUse = true
|
||||
modelInfo.supportsPromptCache = true
|
||||
modelInfo.cacheWritesPrice = 3.75
|
||||
modelInfo.cacheReadsPrice = 0.3
|
||||
@@ -1613,7 +1543,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
maxTokens: model.max_output_tokens || undefined,
|
||||
contextWindow: model.context_window,
|
||||
supportsImages: model.supports_vision || undefined,
|
||||
supportsComputerUse: model.supports_computer_use || undefined,
|
||||
supportsPromptCache: model.supports_caching || undefined,
|
||||
inputPrice: parsePrice(model.input_price),
|
||||
outputPrice: parsePrice(model.output_price),
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create MCP service registry
|
||||
const mcpService = createServiceRegistry("mcp")
|
||||
|
||||
// Export the method handler type and registration function
|
||||
export type McpMethodHandler = ServiceMethodHandler
|
||||
export const registerMethod = mcpService.registerMethod
|
||||
|
||||
// Export the request handler
|
||||
export const handleMcpServiceRequest = mcpService.handleRequest
|
||||
|
||||
// Register all mcp methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,14 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { toggleMcpServer } from "./toggleMcpServer"
|
||||
import { updateMcpTimeout } from "./updateMcpTimeout"
|
||||
|
||||
// Register all mcp service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("toggleMcpServer", toggleMcpServer)
|
||||
registerMethod("updateMcpTimeout", updateMcpTimeout)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ToggleMcpServerRequest, McpServers } from "../../../shared/proto/mcp"
|
||||
import type { Controller } from "../index"
|
||||
import { convertMcpServersToProtoMcpServers } from "../../../shared/proto-conversions/mcp/mcp-server-conversion"
|
||||
|
||||
/**
|
||||
* Toggles an MCP server's enabled/disabled status
|
||||
* @param controller The controller instance
|
||||
* @param request The request containing server ID and disabled status
|
||||
* @returns A response indicating success or failure
|
||||
*/
|
||||
export async function toggleMcpServer(controller: Controller, request: ToggleMcpServerRequest): Promise<McpServers> {
|
||||
try {
|
||||
const mcpServers = await controller.mcpHub?.toggleServerDisabledRPC(request.serverName, request.disabled)
|
||||
|
||||
// Convert from McpServer[] to ProtoMcpServer[] ensuring all required fields are set
|
||||
const protoServers = convertMcpServersToProtoMcpServers(mcpServers)
|
||||
|
||||
return { mcpServers: protoServers }
|
||||
} catch (error) {
|
||||
console.error(`Failed to toggle MCP server ${request.serverName}:`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { convertMcpServersToProtoMcpServers } from "@/shared/proto-conversions/mcp/mcp-server-conversion"
|
||||
import { Controller } from ".."
|
||||
import { UpdateMcpTimeoutRequest, McpServers } from "../../../shared/proto/mcp"
|
||||
|
||||
/**
|
||||
* Updates the timeout configuration for an MCP server.
|
||||
* @param controller - The Controller instance
|
||||
* @param request - Contains server name and timeout value
|
||||
* @returns Array of updated McpServer objects
|
||||
*/
|
||||
export async function updateMcpTimeout(controller: Controller, request: UpdateMcpTimeoutRequest): Promise<McpServers> {
|
||||
try {
|
||||
if (request.serverName && typeof request.serverName === "string" && typeof request.timeout === "number") {
|
||||
const mcpServers = await controller.mcpHub?.updateServerTimeoutRPC(request.serverName, request.timeout)
|
||||
console.log("mcpServers", mcpServers)
|
||||
const convertedMcpServers = convertMcpServersToProtoMcpServers(mcpServers)
|
||||
console.log("convertedMcpServers", convertedMcpServers)
|
||||
return { mcpServers: convertedMcpServers }
|
||||
} else {
|
||||
console.error("Server name and timeout are required")
|
||||
throw new Error("Server name and timeout are required")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to update timeout for server ${request.serverName}:`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty, EmptyRequest } from "../../../shared/proto/common"
|
||||
|
||||
/**
|
||||
* Cancel the currently running task
|
||||
* @param controller The controller instance
|
||||
* @param _request The empty request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function cancelTask(controller: Controller, _request: EmptyRequest): Promise<Empty> {
|
||||
await controller.cancelTask()
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty, EmptyRequest } from "../../../shared/proto/common"
|
||||
|
||||
/**
|
||||
* Clears the current task
|
||||
* @param controller The controller instance
|
||||
* @param _request The empty request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function clearTask(controller: Controller, _request: EmptyRequest): Promise<Empty> {
|
||||
await controller.clearTask()
|
||||
await controller.postStateToWebview()
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create task service registry
|
||||
const taskService = createServiceRegistry("task")
|
||||
|
||||
// Export the method handler type and registration function
|
||||
export type TaskMethodHandler = ServiceMethodHandler
|
||||
export const registerMethod = taskService.registerMethod
|
||||
|
||||
// Export the request handler
|
||||
export const handleTaskServiceRequest = taskService.handleRequest
|
||||
|
||||
// Register all task methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,16 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { cancelTask } from "./cancelTask"
|
||||
import { clearTask } from "./clearTask"
|
||||
import { newTask } from "./newTask"
|
||||
|
||||
// Register all task service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("cancelTask", cancelTask)
|
||||
registerMethod("clearTask", clearTask)
|
||||
registerMethod("newTask", newTask)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import { NewTaskRequest } from "../../../shared/proto/task"
|
||||
|
||||
/**
|
||||
* Creates a new task with the given text and optional images
|
||||
* @param controller The controller instance
|
||||
* @param request The new task request containing text and optional images
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function newTask(controller: Controller, request: NewTaskRequest): Promise<Empty> {
|
||||
await controller.initTask(request.text, request.images)
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -242,4 +242,52 @@ describe("ClineIgnoreController", () => {
|
||||
result.should.be.true()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Include Directive", () => {
|
||||
it("should load patterns from an included file", async () => {
|
||||
// Create a .gitignore file with patterns "*.log" and "debug/"
|
||||
await fs.writeFile(path.join(tempDir, ".gitignore"), ["*.log", "debug/"].join("\n"))
|
||||
|
||||
// Create a .clineignore file that includes .gitignore and adds an extra pattern "secret.txt"
|
||||
await fs.writeFile(path.join(tempDir, ".clineignore"), ["!include .gitignore", "secret.txt"].join("\n"))
|
||||
|
||||
// Initialize the controller to load the updated .clineignore
|
||||
controller = new ClineIgnoreController(tempDir)
|
||||
await controller.initialize()
|
||||
|
||||
// "server.log" should be ignored due to the "*.log" pattern from .gitignore
|
||||
controller.validateAccess("server.log").should.be.false()
|
||||
// "debug/app.js" should be ignored due to the "debug/" pattern from .gitignore
|
||||
controller.validateAccess("debug/app.js").should.be.false()
|
||||
// "secret.txt" should be ignored as specified directly in .clineignore
|
||||
controller.validateAccess("secret.txt").should.be.false()
|
||||
// Other files should be allowed
|
||||
controller.validateAccess("app.js").should.be.true()
|
||||
})
|
||||
|
||||
it("should handle non-existent included file gracefully", async () => {
|
||||
// Create a .clineignore file that includes a non-existent file
|
||||
await fs.writeFile(path.join(tempDir, ".clineignore"), ["!include missing-file.txt"].join("\n"))
|
||||
|
||||
// Initialize the controller
|
||||
controller = new ClineIgnoreController(tempDir)
|
||||
await controller.initialize()
|
||||
|
||||
// Validate access to a regular file; it should be allowed because the missing include should not break everything
|
||||
controller.validateAccess("regular-file.txt").should.be.true()
|
||||
})
|
||||
|
||||
it("should handle non-existent included file gracefully alongside a valid pattern", async () => {
|
||||
// Test with an include directive for a non-existent file alongside a valid pattern ("*.tmp")
|
||||
await fs.writeFile(path.join(tempDir, ".clineignore"), ["!include non-existent.txt", "*.tmp"].join("\n"))
|
||||
|
||||
controller = new ClineIgnoreController(tempDir)
|
||||
await controller.initialize()
|
||||
|
||||
// "file.tmp" should be ignored because of the "*.tmp" pattern
|
||||
controller.validateAccess("file.tmp").should.be.false()
|
||||
// Files that do not match "*.tmp" should be allowed
|
||||
controller.validateAccess("file.log").should.be.true()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
import ignore, { Ignore } from "ignore"
|
||||
import * as vscode from "vscode"
|
||||
@@ -58,7 +58,8 @@ export class ClineIgnoreController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load custom patterns from .clineignore if it exists
|
||||
* Load custom patterns from .clineignore if it exists.
|
||||
* Supports "!include <filename>" to load additional ignore patterns from other files.
|
||||
*/
|
||||
private async loadClineIgnore(): Promise<void> {
|
||||
try {
|
||||
@@ -68,7 +69,7 @@ export class ClineIgnoreController {
|
||||
if (await fileExistsAtPath(ignorePath)) {
|
||||
const content = await fs.readFile(ignorePath, "utf8")
|
||||
this.clineIgnoreContent = content
|
||||
this.ignoreInstance.add(content)
|
||||
await this.processIgnoreContent(content)
|
||||
this.ignoreInstance.add(".clineignore")
|
||||
} else {
|
||||
this.clineIgnoreContent = undefined
|
||||
@@ -79,6 +80,61 @@ export class ClineIgnoreController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process ignore content and apply all ignore patterns
|
||||
*/
|
||||
private async processIgnoreContent(content: string): Promise<void> {
|
||||
// Optimization: first check if there are any !include directives
|
||||
if (!content.includes("!include ")) {
|
||||
this.ignoreInstance.add(content)
|
||||
return
|
||||
}
|
||||
|
||||
// Process !include directives
|
||||
const combinedContent = await this.processClineIgnoreIncludes(content)
|
||||
this.ignoreInstance.add(combinedContent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process !include directives and combine all included file contents
|
||||
*/
|
||||
private async processClineIgnoreIncludes(content: string): Promise<string> {
|
||||
let combinedContent = ""
|
||||
const lines = content.split(/\r?\n/)
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim()
|
||||
|
||||
if (!trimmedLine.startsWith("!include ")) {
|
||||
combinedContent += "\n" + line
|
||||
continue
|
||||
}
|
||||
|
||||
// Process !include directive
|
||||
const includedContent = await this.readIncludedFile(trimmedLine)
|
||||
if (includedContent) {
|
||||
combinedContent += "\n" + includedContent
|
||||
}
|
||||
}
|
||||
|
||||
return combinedContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Read content from an included file specified by !include directive
|
||||
*/
|
||||
private async readIncludedFile(includeLine: string): Promise<string | null> {
|
||||
const includePath = includeLine.substring("!include ".length).trim()
|
||||
const resolvedIncludePath = path.join(this.cwd, includePath)
|
||||
|
||||
if (!(await fileExistsAtPath(resolvedIncludePath))) {
|
||||
console.debug(`[ClineIgnore] Included file not found: ${resolvedIncludePath}`)
|
||||
return null
|
||||
}
|
||||
|
||||
return await fs.readFile(resolvedIncludePath, "utf8")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file should be accessible to the LLM
|
||||
* @param filePath - Path to check (relative to cwd)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import { openFile } from "../../integrations/misc/open-file"
|
||||
import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"
|
||||
import { mentionRegexGlobal } from "../../shared/context-mentions"
|
||||
import { openFile } from "@integrations/misc/open-file"
|
||||
import { UrlContentFetcher } from "@services/browser/UrlContentFetcher"
|
||||
import { mentionRegexGlobal } from "@shared/context-mentions"
|
||||
import fs from "fs/promises"
|
||||
import { extractTextFromFile } from "../../integrations/misc/extract-text"
|
||||
import { extractTextFromFile } from "@integrations/misc/extract-text"
|
||||
import { isBinaryFile } from "isbinaryfile"
|
||||
import { diagnosticsToProblemsString } from "../../integrations/diagnostics"
|
||||
import { getLatestTerminalOutput } from "../../integrations/terminal/get-latest-output"
|
||||
import { getCommitInfo } from "../../utils/git"
|
||||
import { getWorkingState } from "../../utils/git"
|
||||
import { diagnosticsToProblemsString } from "@integrations/diagnostics"
|
||||
import { getLatestTerminalOutput } from "@integrations/terminal/get-latest-output"
|
||||
import { getCommitInfo } from "@utils/git"
|
||||
import { getWorkingState } from "@utils/git"
|
||||
import { FileContextTracker } from "../context/context-tracking/FileContextTracker"
|
||||
|
||||
export function openMention(mention?: string): void {
|
||||
|
||||
@@ -1,43 +1,89 @@
|
||||
export const newTaskToolResponse = () =>
|
||||
`<explicit_instructions type="new_task">
|
||||
The user has explicitly asked you to help them create a new task with preloaded context, which you will create. In this message the user has potentially added instructions or context which you should consider, if given, when creating the new task.
|
||||
Irrespective of whether additional information or instructions are given, you are only allowed to respond to this message by calling the new_task tool.
|
||||
The user has explicitly asked you to help them create a new task with preloaded context, which you will generate. The user may have provided instructions or additional information for you to consider when summarizing existing work and creating the context for the new task.
|
||||
Irrespective of whether additional information or instructions are given, you are ONLY allowed to respond to this message by calling the new_task tool.
|
||||
|
||||
To refresh your memory, the tool definition for new_task and an example for calling the tool is described below:
|
||||
The new_task tool is defined below:
|
||||
|
||||
## new_task tool definition:
|
||||
Description:
|
||||
Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions. This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task.
|
||||
The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation.
|
||||
|
||||
Description: Request to create a new task with preloaded context. The user will be presented with a preview of the context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
|
||||
Parameters:
|
||||
- context: (required) The context to preload the new task with. This should include:
|
||||
* Comprehensively explain what has been accomplished in the current task - mention specific file names that are relevant
|
||||
* The specific next steps or focus for the new task - mention specific file names that are relevant
|
||||
* Any critical information needed to continue the work
|
||||
* Clear indication of how this new task relates to the overall workflow
|
||||
* This should be akin to a long handoff file, enough for a totally new developer to be able to pick up where you left off and know exactly what to do next and which files to look at.
|
||||
- Context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
|
||||
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
|
||||
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
|
||||
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
|
||||
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
|
||||
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks.
|
||||
|
||||
Usage:
|
||||
<new_task>
|
||||
<context>context to preload new task with</context>
|
||||
</new_task>
|
||||
|
||||
## Tool use example:
|
||||
|
||||
<new_task>
|
||||
<context>
|
||||
Authentication System Implementation:
|
||||
- We've implemented the basic user model with email/password
|
||||
- Password hashing is working with bcrypt
|
||||
- Login endpoint is functional with proper validation
|
||||
- JWT token generation is implemented
|
||||
|
||||
Next Steps:
|
||||
- Implement refresh token functionality
|
||||
- Add token validation middleware
|
||||
- Create password reset flow
|
||||
- Implement role-based access control
|
||||
</context>
|
||||
</new_task>
|
||||
|
||||
Below is the the user's input when they indicated that they wanted to create a new task.
|
||||
</explicit_instructions>\n
|
||||
`
|
||||
|
||||
export const condenseToolResponse = () =>
|
||||
`<explicit_instructions type="condense">
|
||||
The user has explicitly asked you to create a detailed summary of the conversation so far, which will be used to compact the current context window while retaining key information. The user may have provided instructions or additional information for you to consider when summarizing the conversation.
|
||||
Irrespective of whether additional information or instructions are given, you are only allowed to respond to this message by calling the condense tool.
|
||||
|
||||
The condense tool is defined below:
|
||||
|
||||
Description:
|
||||
Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions. This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the conversation and supporting any continuing tasks.
|
||||
The user will be presented with a preview of your generated summary and can choose to use it to compact their context window or keep chatting in the current conversation.
|
||||
Users may refer to this tool as 'smol' or 'compact' as well. You should consider these to be equivalent to 'condense' when used in a similar context.
|
||||
|
||||
Parameters:
|
||||
- Context: (required) The context to continue the conversation with. If applicable based on the current task, this should include:
|
||||
1. Previous Conversation: High level details about what was discussed throughout the entire conversation with the user. This should be written to allow someone to be able to follow the general overarching conversation flow.
|
||||
2. Current Work: Describe in detail what was being worked on prior to this request to compact the context window. Pay special attention to the more recent messages / conversation.
|
||||
3. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for continuing with this work.
|
||||
4. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
|
||||
5. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
|
||||
6. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks.
|
||||
|
||||
Usage:
|
||||
<condense>
|
||||
<context>Your detailed summary</context>
|
||||
</condense>
|
||||
|
||||
Example:
|
||||
<condense>
|
||||
<context>
|
||||
1. Previous Conversation:
|
||||
[Detailed description]
|
||||
|
||||
2. Current Work:
|
||||
[Detailed description]
|
||||
|
||||
3. Key Technical Concepts:
|
||||
- [Concept 1]
|
||||
- [Concept 2]
|
||||
- [...]
|
||||
|
||||
4. Relevant Files and Code:
|
||||
- [File Name 1]
|
||||
- [Summary of why this file is important]
|
||||
- [Summary of the changes made to this file, if any]
|
||||
- [Important Code Snippet]
|
||||
- [File Name 2]
|
||||
- [Important Code Snippet]
|
||||
- [...]
|
||||
|
||||
5. Problem Solving:
|
||||
[Detailed description]
|
||||
|
||||
6. Pending Tasks and Next Steps:
|
||||
- [Task 1 details & next steps]
|
||||
- [Task 2 details & next steps]
|
||||
- [...]
|
||||
</context>
|
||||
</condense>
|
||||
|
||||
</explicit_instructions>\n
|
||||
`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { McpHub } from "../../services/mcp/McpHub"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
|
||||
export async function loadMcpDocumentation(mcpHub: McpHub) {
|
||||
return `## Creating an MCP Server
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as diff from "diff"
|
||||
import * as path from "path"
|
||||
import { ClineIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/ClineIgnoreController"
|
||||
import { McpToolCallResponse } from "../../shared/mcp"
|
||||
|
||||
export const formatResponse = {
|
||||
duplicateFileReadNotice: () =>
|
||||
@@ -11,6 +10,9 @@ export const formatResponse = {
|
||||
contextTruncationNotice: () =>
|
||||
`[NOTE] Some previous conversation history with the user has been removed to maintain optimal context window length. The initial user task and the most recent exchanges have been retained for continuity, while intermediate conversation history has been removed. Please keep this in mind as you continue assisting the user.`,
|
||||
|
||||
condense: () =>
|
||||
`The user has accepted the condensed conversation summary you generated. This summary covers important details of the historical conversation with the user which has been truncated.\n<explicit_instructions type="condense_response">It's crucial that you respond by ONLY asking the user what you should work on next. You should NOT take any initiative or make any assumptions about continuing with work. For example you should NOT suggest file changes or attempt to read any files.\nWhen asking the user what you should work on next, you can reference information in the summary which was just generated. However, you should NOT reference information outside of what's contained in the summary for this response. Keep this response CONCISE.</explicit_instructions>`,
|
||||
|
||||
toolDenied: () => `The user denied this operation.`,
|
||||
|
||||
toolError: (error?: string) => `The tool execution failed with the following error:\n<error>\n${error}\n</error>`,
|
||||
|
||||
+40
-26
@@ -1,12 +1,12 @@
|
||||
import { getShell } from "../../utils/shell"
|
||||
import { getShell } from "@utils/shell"
|
||||
import os from "os"
|
||||
import osName from "os-name"
|
||||
import { McpHub } from "../../services/mcp/McpHub"
|
||||
import { BrowserSettings } from "../../shared/BrowserSettings"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
|
||||
export const SYSTEM_PROMPT = async (
|
||||
cwd: string,
|
||||
supportsComputerUse: boolean,
|
||||
supportsBrowserUse: boolean,
|
||||
mcpHub: McpHub,
|
||||
browserSettings: BrowserSettings,
|
||||
) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||
@@ -138,7 +138,7 @@ Usage:
|
||||
<list_code_definition_names>
|
||||
<path>Directory path here</path>
|
||||
</list_code_definition_names>${
|
||||
supportsComputerUse
|
||||
supportsBrowserUse
|
||||
? `
|
||||
|
||||
## browser_action
|
||||
@@ -234,14 +234,15 @@ Your final result description here
|
||||
</attempt_completion>
|
||||
|
||||
## new_task
|
||||
Description: Request to create a new task with preloaded context. The user will be presented with a preview of the context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
|
||||
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
|
||||
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
|
||||
Parameters:
|
||||
- context: (required) The context to preload the new task with. This should include:
|
||||
* Comprehensively explain what has been accomplished in the current task - mention specific file names that are relevant
|
||||
* The specific next steps or focus for the new task - mention specific file names that are relevant
|
||||
* Any critical information needed to continue the work
|
||||
* Clear indication of how this new task relates to the overall workflow
|
||||
* This should be akin to a long handoff file, enough for a totally new developer to be able to pick up where you left off and know exactly what to do next and which files to look at.
|
||||
- Context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
|
||||
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
|
||||
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
|
||||
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
|
||||
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
|
||||
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
|
||||
Usage:
|
||||
<new_task>
|
||||
<context>context to preload new task with</context>
|
||||
@@ -298,17 +299,30 @@ Usage:
|
||||
|
||||
<new_task>
|
||||
<context>
|
||||
Authentication System Implementation:
|
||||
- We've implemented the basic user model with email/password
|
||||
- Password hashing is working with bcrypt
|
||||
- Login endpoint is functional with proper validation
|
||||
- JWT token generation is implemented
|
||||
1. Current Work:
|
||||
[Detailed description]
|
||||
|
||||
Next Steps:
|
||||
- Implement refresh token functionality
|
||||
- Add token validation middleware
|
||||
- Create password reset flow
|
||||
- Implement role-based access control
|
||||
2. Key Technical Concepts:
|
||||
- [Concept 1]
|
||||
- [Concept 2]
|
||||
- [...]
|
||||
|
||||
3. Relevant Files and Code:
|
||||
- [File Name 1]
|
||||
- [Summary of why this file is important]
|
||||
- [Summary of the changes made to this file, if any]
|
||||
- [Important Code Snippet]
|
||||
- [File Name 2]
|
||||
- [Important Code Snippet]
|
||||
- [...]
|
||||
|
||||
4. Problem Solving:
|
||||
[Detailed description]
|
||||
|
||||
5. Pending Tasks and Next Steps:
|
||||
- [Task 1 details & next steps]
|
||||
- [Task 2 details & next steps]
|
||||
- [...]
|
||||
</context>
|
||||
</new_task>
|
||||
|
||||
@@ -547,14 +561,14 @@ In each user message, the environment_details will specify the current mode. The
|
||||
CAPABILITIES
|
||||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${
|
||||
supportsComputerUse ? ", use the browser" : ""
|
||||
supportsBrowserUse ? ", use the browser" : ""
|
||||
}, read and edit files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the replace_in_file tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${
|
||||
supportsComputerUse
|
||||
supportsBrowserUse
|
||||
? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser."
|
||||
: ""
|
||||
}
|
||||
@@ -578,7 +592,7 @@ RULES
|
||||
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ask_followup_question tool to request the user to copy and paste it back to you.
|
||||
- The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it.
|
||||
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.${
|
||||
supportsComputerUse
|
||||
supportsBrowserUse
|
||||
? `\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.`
|
||||
: ""
|
||||
}
|
||||
@@ -590,7 +604,7 @@ RULES
|
||||
- When using the replace_in_file tool, you must include complete lines in your SEARCH blocks, not partial lines. The system requires exact line matches and cannot match partial lines. For example, if you want to match a line containing "const x = 5;", your SEARCH block must include the entire line, not just "x = 5" or other fragments.
|
||||
- When using the replace_in_file tool, if you use multiple SEARCH/REPLACE blocks, list them in the order they appear in the file. For example if you need to make changes to both line 10 and line 50, first include the SEARCH/REPLACE block for line 10, followed by the SEARCH/REPLACE block for line 50.
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${
|
||||
supportsComputerUse
|
||||
supportsBrowserUse
|
||||
? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser."
|
||||
: ""
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { newTaskToolResponse } from "../prompts/commands"
|
||||
import { newTaskToolResponse, condenseToolResponse } from "../prompts/commands"
|
||||
|
||||
/**
|
||||
* Processes text for slash commands and transforms them with appropriate instructions
|
||||
* This is called after parseMentions() to process any slash commands in the user's message
|
||||
*/
|
||||
export function parseSlashCommands(text: string): string {
|
||||
const SUPPORTED_COMMANDS = ["newtask"]
|
||||
const SUPPORTED_COMMANDS = ["newtask", "smol"]
|
||||
|
||||
const commandReplacements: Record<string, string> = {
|
||||
newtask: newTaskToolResponse(),
|
||||
smol: condenseToolResponse(),
|
||||
}
|
||||
|
||||
// this currently allows matching prepended whitespace prior to /slash-command
|
||||
|
||||
@@ -2,9 +2,9 @@ import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import fs from "fs/promises"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { ClineMessage } from "../../shared/ExtensionMessage"
|
||||
import { TaskMetadata } from "../context/context-tracking/ContextTrackerTypes"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { TaskMetadata } from "@core/context/context-tracking/ContextTrackerTypes"
|
||||
import os from "os"
|
||||
import { execa } from "execa"
|
||||
|
||||
|
||||
+11
-11
@@ -1,16 +1,16 @@
|
||||
import * as vscode from "vscode"
|
||||
import { DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "../../shared/BrowserSettings"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings"
|
||||
import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
|
||||
import { GlobalStateKey, SecretKey } from "./state-keys"
|
||||
import { ApiConfiguration, ApiProvider, ModelInfo } from "../../shared/api"
|
||||
import { HistoryItem } from "../../shared/HistoryItem"
|
||||
import { AutoApprovalSettings } from "../../shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "../../shared/BrowserSettings"
|
||||
import { ChatSettings } from "../../shared/ChatSettings"
|
||||
import { TelemetrySetting } from "../../shared/TelemetrySetting"
|
||||
import { UserInfo } from "../../shared/UserInfo"
|
||||
import { ClineRulesToggles } from "../../shared/cline-rules"
|
||||
import { ApiConfiguration, ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { ChatSettings } from "@shared/ChatSettings"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { UserInfo } from "@shared/UserInfo"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
/*
|
||||
Storage
|
||||
https://dev.to/kompotkot/how-to-use-secretstorage-in-your-vscode-extensions-2hco
|
||||
|
||||
+209
-55
@@ -1,5 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import cloneDeep from "clone-deep"
|
||||
import { execa } from "execa"
|
||||
import getFolderSize from "get-folder-size"
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import os from "os"
|
||||
@@ -8,30 +9,31 @@ import pWaitFor from "p-wait-for"
|
||||
import * as path from "path"
|
||||
import { serializeError } from "serialize-error"
|
||||
import * as vscode from "vscode"
|
||||
import { ApiHandler, buildApiHandler } from "../../api"
|
||||
import { AnthropicHandler } from "../../api/providers/anthropic"
|
||||
import { ClineHandler } from "../../api/providers/cline"
|
||||
import { OpenRouterHandler } from "../../api/providers/openrouter"
|
||||
import { ApiStream } from "../../api/transform/stream"
|
||||
import CheckpointTracker from "../../integrations/checkpoints/CheckpointTracker"
|
||||
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../../integrations/editor/DiffViewProvider"
|
||||
import { formatContentBlockToMarkdown } from "../../integrations/misc/export-markdown"
|
||||
import { extractTextFromFile } from "../../integrations/misc/extract-text"
|
||||
import { showSystemNotification } from "../../integrations/notifications"
|
||||
import { TerminalManager } from "../../integrations/terminal/TerminalManager"
|
||||
import { BrowserSession } from "../../services/browser/BrowserSession"
|
||||
import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"
|
||||
import { listFiles } from "../../services/glob/list-files"
|
||||
import { regexSearchFiles } from "../../services/ripgrep"
|
||||
import { telemetryService } from "../../services/telemetry/TelemetryService"
|
||||
import { parseSourceCodeForDefinitionsTopLevel } from "../../services/tree-sitter"
|
||||
import { ApiConfiguration } from "../../shared/api"
|
||||
import { findLast, findLastIndex, parsePartialArrayString } from "../../shared/array"
|
||||
import { AutoApprovalSettings } from "../../shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "../../shared/BrowserSettings"
|
||||
import { ChatSettings } from "../../shared/ChatSettings"
|
||||
import { combineApiRequests } from "../../shared/combineApiRequests"
|
||||
import { combineCommandSequences, COMMAND_REQ_APP_STRING } from "../../shared/combineCommandSequences"
|
||||
import { Logger } from "@services/logging/Logger"
|
||||
import { ApiHandler, buildApiHandler } from "@api/index"
|
||||
import { AnthropicHandler } from "@api/providers/anthropic"
|
||||
import { ClineHandler } from "@api/providers/cline"
|
||||
import { OpenRouterHandler } from "@api/providers/openrouter"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import CheckpointTracker from "@integrations/checkpoints/CheckpointTracker"
|
||||
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "@integrations/editor/DiffViewProvider"
|
||||
import { formatContentBlockToMarkdown } from "@integrations/misc/export-markdown"
|
||||
import { extractTextFromFile } from "@integrations/misc/extract-text"
|
||||
import { showSystemNotification } from "@integrations/notifications"
|
||||
import { TerminalManager } from "@integrations/terminal/TerminalManager"
|
||||
import { BrowserSession } from "@services/browser/BrowserSession"
|
||||
import { UrlContentFetcher } from "@services/browser/UrlContentFetcher"
|
||||
import { listFiles } from "@services/glob/list-files"
|
||||
import { regexSearchFiles } from "@services/ripgrep"
|
||||
import { telemetryService } from "@services/telemetry/TelemetryService"
|
||||
import { parseSourceCodeForDefinitionsTopLevel } from "@services/tree-sitter"
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import { findLast, findLastIndex, parsePartialArrayString } from "@shared/array"
|
||||
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { ChatSettings } from "@shared/ChatSettings"
|
||||
import { combineApiRequests } from "@shared/combineApiRequests"
|
||||
import { combineCommandSequences, COMMAND_REQ_APP_STRING } from "@shared/combineCommandSequences"
|
||||
import {
|
||||
BrowserAction,
|
||||
BrowserActionResult,
|
||||
@@ -48,30 +50,30 @@ import {
|
||||
ClineSayTool,
|
||||
COMPLETION_RESULT_CHANGES_FLAG,
|
||||
ExtensionMessage,
|
||||
} from "../../shared/ExtensionMessage"
|
||||
import { getApiMetrics } from "../../shared/getApiMetrics"
|
||||
import { HistoryItem } from "../../shared/HistoryItem"
|
||||
import { DEFAULT_LANGUAGE_SETTINGS, getLanguageKey, LanguageDisplay } from "../../shared/Languages"
|
||||
import { ClineAskResponse, ClineCheckpointRestore } from "../../shared/WebviewMessage"
|
||||
import { calculateApiCostAnthropic } from "../../utils/cost"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { arePathsEqual, getReadablePath, isLocatedInWorkspace } from "../../utils/path"
|
||||
import { fixModelHtmlEscaping, removeInvalidChars } from "../../utils/string"
|
||||
import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from ".././assistant-message"
|
||||
import { constructNewFileContent } from ".././assistant-message/diff"
|
||||
import { ClineIgnoreController } from ".././ignore/ClineIgnoreController"
|
||||
import { parseMentions } from ".././mentions"
|
||||
import { formatResponse } from ".././prompts/responses"
|
||||
import { addUserInstructions, SYSTEM_PROMPT } from ".././prompts/system"
|
||||
import { getContextWindowInfo } from "../context/context-management/context-window-utils"
|
||||
import { FileContextTracker } from "../context/context-tracking/FileContextTracker"
|
||||
import { ModelContextTracker } from "../context/context-tracking/ModelContextTracker"
|
||||
} from "@shared/ExtensionMessage"
|
||||
import { getApiMetrics } from "@shared/getApiMetrics"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { DEFAULT_LANGUAGE_SETTINGS, getLanguageKey, LanguageDisplay } from "@shared/Languages"
|
||||
import { ClineAskResponse, ClineCheckpointRestore } from "@shared/WebviewMessage"
|
||||
import { calculateApiCostAnthropic } from "@utils/cost"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { arePathsEqual, getReadablePath, isLocatedInWorkspace } from "@utils/path"
|
||||
import { fixModelHtmlEscaping, removeInvalidChars } from "@utils/string"
|
||||
import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "@core/assistant-message"
|
||||
import { constructNewFileContent } from "@core/assistant-message/diff"
|
||||
import { ClineIgnoreController } from "@core/ignore/ClineIgnoreController"
|
||||
import { parseMentions } from "@core/mentions"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { addUserInstructions, SYSTEM_PROMPT } from "@core/prompts/system"
|
||||
import { getContextWindowInfo } from "@core/context/context-management/context-window-utils"
|
||||
import { FileContextTracker } from "@core/context/context-tracking/FileContextTracker"
|
||||
import { ModelContextTracker } from "@core/context/context-tracking/ModelContextTracker"
|
||||
import {
|
||||
checkIsAnthropicContextWindowError,
|
||||
checkIsOpenRouterContextWindowError,
|
||||
} from "../context/context-management/context-error-handling"
|
||||
import { ContextManager } from "../context/context-management/ContextManager"
|
||||
import { loadMcpDocumentation } from "../prompts/loadMcpDocumentation"
|
||||
} from "@core/context/context-management/context-error-handling"
|
||||
import { ContextManager } from "@core/context/context-management/ContextManager"
|
||||
import { loadMcpDocumentation } from "@core/prompts/loadMcpDocumentation"
|
||||
import {
|
||||
ensureRulesDirectoryExists,
|
||||
ensureTaskDirectoryExists,
|
||||
@@ -79,16 +81,17 @@ import {
|
||||
getSavedClineMessages,
|
||||
saveApiConversationHistory,
|
||||
saveClineMessages,
|
||||
} from "../storage/disk"
|
||||
} from "@core/storage/disk"
|
||||
import {
|
||||
getGlobalClineRules,
|
||||
getLocalClineRules,
|
||||
refreshClineRulesToggles,
|
||||
} from "../context/instructions/user-instructions/cline-rules"
|
||||
import { getGlobalState } from "../storage/state"
|
||||
import { parseSlashCommands } from ".././slash-commands"
|
||||
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
|
||||
import { McpHub } from "../../services/mcp/McpHub"
|
||||
} from "@core/context/instructions/user-instructions/cline-rules"
|
||||
import { getGlobalState } from "@core/storage/state"
|
||||
import { parseSlashCommands } from "@core/slash-commands"
|
||||
import WorkspaceTracker from "@integrations/workspace/WorkspaceTracker"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { isInTestMode } from "../../services/test/TestMode"
|
||||
|
||||
export 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
|
||||
@@ -1131,7 +1134,93 @@ export class Task {
|
||||
|
||||
// Tools
|
||||
|
||||
/**
|
||||
* Executes a command directly in Node.js using execa
|
||||
* This is used in test mode to capture the full output without using the VS Code terminal
|
||||
* Commands are automatically terminated after 30 seconds using Promise.race
|
||||
*/
|
||||
private async executeCommandInNode(command: string): Promise<[boolean, ToolResponse]> {
|
||||
try {
|
||||
// Create a child process
|
||||
const childProcess = execa(command, {
|
||||
shell: true,
|
||||
cwd,
|
||||
reject: false,
|
||||
all: true, // Merge stdout and stderr
|
||||
})
|
||||
|
||||
// Set up variables to collect output
|
||||
let output = ""
|
||||
|
||||
// Collect output in real-time
|
||||
if (childProcess.all) {
|
||||
childProcess.all.on("data", (data) => {
|
||||
output += data.toString()
|
||||
})
|
||||
}
|
||||
|
||||
// Create a timeout promise that rejects after 30 seconds
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
if (childProcess.pid) {
|
||||
childProcess.kill("SIGKILL") // Use SIGKILL for more forceful termination
|
||||
}
|
||||
reject(new Error("Command timeout after 30s"))
|
||||
}, 30000)
|
||||
})
|
||||
|
||||
// Race between command completion and timeout
|
||||
const result = await Promise.race([childProcess, timeoutPromise]).catch((error) => {
|
||||
// If we get here due to timeout, return a partial result with timeout flag
|
||||
Logger.info(`Command timed out after 30s: ${command}`)
|
||||
return {
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
exitCode: 124, // Standard timeout exit code
|
||||
timedOut: true,
|
||||
}
|
||||
})
|
||||
|
||||
// Check if timeout occurred
|
||||
const wasTerminated = result.timedOut === true
|
||||
|
||||
// Use collected output or result output
|
||||
if (!output) {
|
||||
output = result.stdout || result.stderr || ""
|
||||
}
|
||||
|
||||
Logger.info(`Command executed in Node: ${command}\nOutput:\n${output}`)
|
||||
|
||||
// Add termination message if the command was terminated
|
||||
if (wasTerminated) {
|
||||
output += "\nCommand was taking a while to run so it was auto terminated after 30s"
|
||||
}
|
||||
|
||||
// Format the result similar to terminal output
|
||||
return [
|
||||
false,
|
||||
`Command executed${wasTerminated ? " (terminated after 30s)" : ""} with exit code ${
|
||||
result.exitCode
|
||||
}.${output.length > 0 ? `\nOutput:\n${output}` : ""}`,
|
||||
]
|
||||
} catch (error) {
|
||||
// Handle any errors that might occur
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
return [false, `Error executing command: ${errorMessage}`]
|
||||
}
|
||||
}
|
||||
|
||||
async executeCommandTool(command: string): Promise<[boolean, ToolResponse]> {
|
||||
Logger.info("IS_TEST: " + isInTestMode())
|
||||
|
||||
// Check if we're in test mode
|
||||
if (isInTestMode()) {
|
||||
// In test mode, execute the command directly in Node
|
||||
Logger.info("Executing command in Node: " + command)
|
||||
return this.executeCommandInNode(command)
|
||||
}
|
||||
Logger.info("Executing command in VS code terminal: " + command)
|
||||
|
||||
const terminalInfo = await this.terminalManager.getOrCreateTerminal(cwd)
|
||||
terminalInfo.terminal.show() // weird visual bug when creating new terminals (even manually) where there's an empty space at the top.
|
||||
const process = this.terminalManager.runCommand(terminalInfo, command)
|
||||
@@ -1334,11 +1423,12 @@ export class Task {
|
||||
})
|
||||
|
||||
const disableBrowserTool = vscode.workspace.getConfiguration("cline").get<boolean>("disableBrowserTool") ?? false
|
||||
const modelSupportsComputerUse = this.api.getModel().info.supportsComputerUse ?? false
|
||||
// cline browser tool uses image recognition for navigation (requires model image support).
|
||||
const modelSupportsBrowserUse = this.api.getModel().info.supportsImages ?? false
|
||||
|
||||
const supportsComputerUse = modelSupportsComputerUse && !disableBrowserTool // only enable computer use if the model supports it and the user hasn't disabled it
|
||||
const supportsBrowserUse = modelSupportsBrowserUse && !disableBrowserTool // only enable browser use if the model supports it and the user hasn't disabled it
|
||||
|
||||
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsComputerUse, this.mcpHub, this.browserSettings)
|
||||
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsBrowserUse, this.mcpHub, this.browserSettings)
|
||||
|
||||
let settingsCustomInstructions = this.customInstructions?.trim()
|
||||
const preferredLanguage = getLanguageKey(
|
||||
@@ -1586,6 +1676,8 @@ export class Task {
|
||||
return `[${block.name}]`
|
||||
case "new_task":
|
||||
return `[${block.name} for creating a new task]`
|
||||
case "condense":
|
||||
return `[${block.name}]`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2923,6 +3015,65 @@ export class Task {
|
||||
break
|
||||
}
|
||||
}
|
||||
case "condense": {
|
||||
const context: string | undefined = block.params.context
|
||||
try {
|
||||
if (block.partial) {
|
||||
await this.ask("condense", removeClosingTag("context", context), block.partial).catch(() => {})
|
||||
break
|
||||
} else {
|
||||
if (!context) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("condense", "context"))
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
|
||||
if (this.autoApprovalSettings.enabled && this.autoApprovalSettings.enableNotifications) {
|
||||
showSystemNotification({
|
||||
subtitle: "Cline wants to condense the conversation...",
|
||||
message: `Cline is suggesting to condense your conversation with: ${context}`,
|
||||
})
|
||||
}
|
||||
|
||||
const { text, images } = await this.ask("condense", context, false)
|
||||
|
||||
// If the user provided a response, treat it as feedback
|
||||
if (text || images?.length) {
|
||||
await this.say("user_feedback", text ?? "", images)
|
||||
pushToolResult(
|
||||
formatResponse.toolResult(
|
||||
`The user provided feedback on the condensed conversation summary:\n<feedback>\n${text}\n</feedback>`,
|
||||
images,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// If no response, the user accepted the condensed version
|
||||
pushToolResult(formatResponse.toolResult(formatResponse.condense()))
|
||||
|
||||
const lastMessage = this.apiConversationHistory[this.apiConversationHistory.length - 1]
|
||||
const summaryAlreadyAppended = lastMessage && lastMessage.role === "assistant"
|
||||
const keepStrategy = summaryAlreadyAppended ? "lastTwo" : "none"
|
||||
|
||||
// clear the context history at this point in time
|
||||
this.conversationHistoryDeletedRange = this.contextManager.getNextTruncationRange(
|
||||
this.apiConversationHistory,
|
||||
this.conversationHistoryDeletedRange,
|
||||
keepStrategy,
|
||||
)
|
||||
await this.saveClineMessagesAndUpdateHistory()
|
||||
await this.contextManager.triggerApplyStandardContextTruncationNoticeChange(
|
||||
Date.now(),
|
||||
await ensureTaskDirectoryExists(this.getContext(), this.taskId),
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("condensing context window", error)
|
||||
break
|
||||
}
|
||||
}
|
||||
case "plan_mode_respond": {
|
||||
const response: string | undefined = block.params.response
|
||||
const optionsRaw: string | undefined = block.params.options
|
||||
@@ -3451,7 +3602,10 @@ export class Task {
|
||||
case "reasoning":
|
||||
// reasoning will always come before assistant message
|
||||
reasoningMessage += chunk.reasoning
|
||||
await this.say("reasoning", reasoningMessage, undefined, true)
|
||||
// fixes bug where cancelling task > aborts task > for loop may be in middle of streaming reasoning > say function throws error before we get a chance to properly clean up and cancel the task.
|
||||
if (!this.abort) {
|
||||
await this.say("reasoning", reasoningMessage, undefined, true)
|
||||
}
|
||||
break
|
||||
case "text":
|
||||
if (reasoningMessage && assistantMessage.length === 0) {
|
||||
|
||||
@@ -2,9 +2,9 @@ import axios from "axios"
|
||||
import * as vscode from "vscode"
|
||||
import { getNonce } from "./getNonce"
|
||||
import { getUri } from "./getUri"
|
||||
import { getTheme } from "../../integrations/theme/getTheme"
|
||||
import { Controller } from "../controller"
|
||||
import { findLast } from "../../shared/array"
|
||||
import { getTheme } from "@integrations/theme/getTheme"
|
||||
import { Controller } from "@core/controller/index"
|
||||
import { findLast } from "@shared/array"
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as vscode from "vscode"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import { Controller } from "../../core/controller"
|
||||
import { HistoryItem } from "../../shared/HistoryItem"
|
||||
import { ClineMessage } from "../../shared/ExtensionMessage"
|
||||
import { Controller } from "@core/controller"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
|
||||
/**
|
||||
* Registers development-only commands for task manipulation.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as vscode from "vscode"
|
||||
import { Controller } from "../core/controller"
|
||||
import { Controller } from "@core/controller"
|
||||
import { ClineAPI } from "./cline"
|
||||
import { getGlobalState } from "../core/storage/state"
|
||||
import { getGlobalState } from "@core/storage/state"
|
||||
|
||||
export function createClineAPI(outputChannel: vscode.OutputChannel, sidebarController: Controller): ClineAPI {
|
||||
const api: ClineAPI = {
|
||||
|
||||
+12
-10
@@ -2,6 +2,7 @@
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import * as vscode from "vscode"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import { Logger } from "./services/logging/Logger"
|
||||
import { createClineAPI } from "./exports"
|
||||
import "./utils/path" // necessary to have access to String.prototype.toPosix
|
||||
@@ -9,8 +10,8 @@ import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
|
||||
import assert from "node:assert"
|
||||
import { telemetryService } from "./services/telemetry/TelemetryService"
|
||||
import { WebviewProvider } from "./core/webview"
|
||||
import { createTestServer, shutdownTestServer } from "./services/test/TestServer"
|
||||
import { ErrorService } from "./services/error/ErrorService"
|
||||
import { initializeTestMode, cleanupTestMode } from "./services/test/TestMode"
|
||||
|
||||
/*
|
||||
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
|
||||
@@ -35,8 +36,10 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const sidebarWebview = new WebviewProvider(context, outputChannel)
|
||||
|
||||
// Initialize test mode and add disposables to context
|
||||
context.subscriptions.push(...initializeTestMode(context, sidebarWebview))
|
||||
|
||||
vscode.commands.executeCommand("setContext", "cline.isDevMode", IS_DEV && IS_DEV === "true")
|
||||
vscode.commands.executeCommand("setContext", "cline.isTestMode", IS_TEST && IS_TEST === "true")
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(WebviewProvider.sideBarId, sidebarWebview, {
|
||||
@@ -382,6 +385,10 @@ 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[]) => {
|
||||
// Add this line to focus the chat input first
|
||||
await vscode.commands.executeCommand("cline.focusChatInput")
|
||||
// Wait for a webview instance to become visible after focusing
|
||||
await pWaitFor(() => !!WebviewProvider.getVisibleInstance())
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) {
|
||||
return
|
||||
@@ -415,11 +422,6 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}),
|
||||
)
|
||||
|
||||
// Set up test server if in test mode
|
||||
if (IS_TEST === "true") {
|
||||
createTestServer(sidebarWebview)
|
||||
}
|
||||
|
||||
return createClineAPI(outputChannel, sidebarWebview.controller)
|
||||
}
|
||||
|
||||
@@ -429,12 +431,12 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
//
|
||||
// This is a workaround to reload the extension when the source code changes
|
||||
// since vscode doesn't support hot reload for extensions
|
||||
const { IS_DEV, DEV_WORKSPACE_FOLDER, IS_TEST } = process.env
|
||||
const { IS_DEV, DEV_WORKSPACE_FOLDER } = process.env
|
||||
|
||||
// This method is called when your extension is deactivated
|
||||
export function deactivate() {
|
||||
// Shutdown the test server if it exists
|
||||
shutdownTestServer()
|
||||
// Clean up test mode
|
||||
cleanupTestMode()
|
||||
|
||||
telemetryService.shutdown()
|
||||
Logger.log("Cline extension deactivated")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from "fs/promises"
|
||||
import { join } from "path"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { GIT_DISABLED_SUFFIX } from "./CheckpointGitOperations"
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,9 @@ import fs from "fs/promises"
|
||||
import { globby } from "globby"
|
||||
import * as path from "path"
|
||||
import simpleGit, { SimpleGit } from "simple-git"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { getLfsPatterns, writeExcludesFile } from "./CheckpointExclusions"
|
||||
import { telemetryService } from "../../services/telemetry/TelemetryService"
|
||||
import { telemetryService } from "@services/telemetry/TelemetryService"
|
||||
|
||||
interface CheckpointAddResult {
|
||||
success: boolean
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
|
||||
/**
|
||||
* Cleans up legacy checkpoints from task folders.
|
||||
|
||||
@@ -3,8 +3,8 @@ import os from "os"
|
||||
import * as path from "path"
|
||||
import simpleGit, { SimpleGit } from "simple-git"
|
||||
import * as vscode from "vscode"
|
||||
import { Controller as ClineProvider } from "../../core/controller"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { Controller as ClineProvider } from "@core/controller"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { globby } from "globby"
|
||||
|
||||
class CheckpointTracker {
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import simpleGit from "simple-git"
|
||||
import * as vscode from "vscode"
|
||||
import { telemetryService } from "../../services/telemetry/TelemetryService"
|
||||
import { telemetryService } from "@services/telemetry/TelemetryService"
|
||||
import { GitOperations } from "./CheckpointGitOperations"
|
||||
import { getShadowGitPath, getWorkingDirectory, hashWorkingDir } from "./CheckpointUtils"
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import * as fs from "fs/promises"
|
||||
import { createDirectoriesForFile } from "../../utils/fs"
|
||||
import { arePathsEqual } from "../../utils/path"
|
||||
import { formatResponse } from "../../core/prompts/responses"
|
||||
import { createDirectoriesForFile } from "@utils/fs"
|
||||
import { arePathsEqual } from "@utils/path"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { DecorationController } from "./DecorationController"
|
||||
import * as diff from "diff"
|
||||
import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
|
||||
import { detectEncoding } from "../misc/extract-text"
|
||||
import * as iconv from "iconv-lite"
|
||||
|
||||
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
|
||||
|
||||
@@ -23,6 +25,7 @@ export class DiffViewProvider {
|
||||
private activeLineController?: DecorationController
|
||||
private streamedLines: string[] = []
|
||||
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
|
||||
private fileEncoding: string = "utf8"
|
||||
|
||||
constructor(private cwd: string) {}
|
||||
|
||||
@@ -43,9 +46,12 @@ export class DiffViewProvider {
|
||||
this.preDiagnostics = vscode.languages.getDiagnostics()
|
||||
|
||||
if (fileExists) {
|
||||
this.originalContent = await fs.readFile(absolutePath, "utf-8")
|
||||
const fileBuffer = await fs.readFile(absolutePath)
|
||||
this.fileEncoding = await detectEncoding(fileBuffer)
|
||||
this.originalContent = iconv.decode(fileBuffer, this.fileEncoding)
|
||||
} else {
|
||||
this.originalContent = ""
|
||||
this.fileEncoding = "utf8"
|
||||
}
|
||||
// for new files, create any necessary directories and keep track of new directories to delete if the user denies the operation
|
||||
this.createdDirs = await createDirectoriesForFile(absolutePath)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user