Compare commits

..
Author SHA1 Message Date
Trevor Hudson b702104dd3 capture telemetry when the extension is closed 2025-04-30 18:39:32 -07:00
Trevor Hudson 2b165999c9 remove launch.json change 2025-04-28 18:03:32 -07:00
Trevor Hudson 2bf1132bef remove conversation turn tracking and move to the task closed property 2025-04-28 17:35:40 -07:00
Evan df7f9fcba4 It depends... (#3126)
* add protos to more dependsOn, also make it so that the scripts are always displayed and the window does not automatically close

* changeset

* add back build script
2025-04-27 21:47:49 -07:00
nomaven db0b022b6e ENG-318 feat: Show openrouter balance next to provider (#3003)
* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component
2025-04-27 16:27:28 -07:00
Wesley SmithandWesley Smith 459adf0450 Markdown copy (#3060)
* add markdown copy

* add changeset

* fmt

---------

Co-authored-by: Wesley Smith <wes@neofactory.ai>
2025-04-27 15:24:08 -07:00
WingsDrafterworkandnomaven df37f29746 Support for custom timeout (#3029)
* Support for custom timeout

* Make custom timeout visible only for ollama

* Remove parameters from other providers, only kept for ollama

* Update webview-ui/src/components/settings/ApiOptions.tsx

Co-authored-by: nomaven <arafat.da.khan@gmail.com>

---------

Co-authored-by: nomaven <arafat.da.khan@gmail.com>
2025-04-27 15:17:04 -07:00
Sarah Fortune 60c210b017 Add java options to protobufs (#3141)
Set the java class path.
2025-04-27 09:25:37 -10:00
Tomás Barreiro d4bd755e60 Set the default output cost (#3079) 2025-04-26 23:03:04 -07:00
Toshii aed152b530 narrative narrative narrative (#3121)
* notice

* changeset
2025-04-25 19:42:24 -07:00
canvrno 5a8e9d8fa8 [PROTOBUS] file openImage (#3106)
* openFile protobus

* openImage protobus

* Delete .changeset/nine-numbers-boil.md
2025-04-25 19:21:07 -07:00
Sarah FortuneandAndrei Edell 7610cecde6 [PROTOBUS] Move accountLoginClicked to protobus (#3115)
* Move accountLoginClick to protobus

* Remove ununsed import

* Add account service to handleRequest in grpc handler.

* Add documentation

* github pr un-stick empty commit

---------

Co-authored-by: Andrei Edell <andrei@nugbase.com>
2025-04-25 12:45:28 -10:00
github-actions[bot] bb26b3b64d v3.13.3 Release Notes
v3.13.3 Release Notes
2025-04-25 13:43:23 -07:00
Dennis Bartlett aabdeba0f3 Feat/change reset state colors (#3112)
* Update Reset Button color to Red

* Add Changeset
2025-04-24 23:38:32 -07:00
Dennis Bartlett fd68a81a26 Alias smol to compact (#3111) 2025-04-24 22:02:36 -07:00
ToshiiandDennis Bartlett 0e07b92be2 Caching (#3110)
* add

* changeset

* Fix spelling error

---------

Co-authored-by: Dennis Bartlett <bartlett.dc.1@gmail.com>
2025-04-24 21:38:07 -07:00
Andrei EternalandAndrei Edell 4addffe94c [PROTOBUS] browser getDetectedChromePath (#3001)
* protobus impl for getDetectedChromePath

* [PROTOBUS] browserSettings (#3007)

* protobus for browserSettings

* Update index.ts

---------

Co-authored-by: Andrei Edell <andrei@nugbase.com>

---------

Co-authored-by: Andrei Edell <andrei@nugbase.com>
2025-04-24 21:36:55 -07:00
Evan 0836e4d45a add npm protos to tests.json for dev build (#3107) 2025-04-24 17:07:31 -07:00
0x23d11 4a57e5a075 fix(settings): rename AWS Bedrock to Amazon Bedrock (#3094) 2025-04-24 16:17:05 -07:00
Wesley SmithandWesley Smith 29458d7675 actually stop tasks (#3061)
Co-authored-by: Wesley Smith <wes@neofactory.ai>
2025-04-24 13:00:32 -10:00
pashpashpashandCline Evaluation 90b0d6a73b evals formatting (#3105)
Co-authored-by: Cline Evaluation <cline@example.com>
2025-04-24 15:26:12 -07:00
pashpashpashandCline Evaluation 547051bfa8 enabling download count on marketplace (#3104)
* enabling download count on marketplace

* changeset

---------

Co-authored-by: Cline Evaluation <cline@example.com>
2025-04-24 15:25:40 -07:00
Trevor Hudsonandcelestial-vault 93595af09f Trevhud/eng 592 add title tags to buttons in the bottom left corner (#3080)
* add title tags

* add tooltips, change Cline Rules name, introduce contrast to auto approve + dismiss when click outside

* change to prompts and add hook for click outside to close

* use useClickAway, delete unused component, rename back to cline rules

---------

Co-authored-by: celestial-vault <58194240+celestial-vault@users.noreply.github.com>
2025-04-24 15:24:49 -07:00
Toshiiandellipsis-dev[bot] 3828c0d1bc smol (#3086)
* base

* button callback

* prompt

* smol

* full truncate

* base 2

* changeset

* dup new task resp

* Update src/core/prompts/commands.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* comments

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-24 15:00:44 -07:00
Evan a2263de7cb Protobus: updateMcpTimeout message (#3085)
* updateMcpTimeout protobus conversion

* changeset
2025-04-24 14:57:00 -07:00
Andrei EternalandAndrei Edell e53fa8307d run protos, uh, more -- to prevent WAT maybe (#3091)
Co-authored-by: Andrei Edell <andrei@nugbase.com>
2025-04-24 11:01:21 -10:00
Dennis Bartlett b8cfb87121 Update CHANGELOG.md (#3100) 2025-04-24 12:54:00 -07:00
Dennis Bartlett 76a64ef77d Fix Protobuf WAT 2025-04-24 01:38:40 -07:00
github-actions[bot] 29bdb6c981 v3.13.2 Release Notes
v3.13.2 Release Notes
2025-04-24 00:46:46 -07:00
9bbc0da821 [PROTOBUS] file openFile (#3052)
* openFile protobus

* merge conflicts

* fix whitespace

* Fix whitespace... Again

---------

Co-authored-by: Andrei Eternal <garoth@gmail.com>
Co-authored-by: Dennis Bartlett <bartlett.dc.1@gmail.com>
2025-04-23 21:40:46 -10:00
canvrnoandAndrei Eternal d2080c1f93 [PROTOBUS] task cancelTask (#3005)
* cancelTask protobuf

* changeset

* corrected changeset

* fixing bad push

* more fixes

* one small change

* ONE more change

* missing await

---------

Co-authored-by: Andrei Eternal <garoth@gmail.com>
2025-04-24 00:20:03 -07:00
canvrno 044dd686a0 Fix for terminal outputs missing commas and non-alphanumeric outputs (#3066) 2025-04-23 19:55:27 -10:00
ea4f571463 [PROTOBUS] checkpoints checkpointRestore (#3046)
* restoreTask protobus

* changeset

* changeset correction

* type safety change

* Fix Non-UTF-8 File Handling: Improve Encoding Detection to Prevent Garbled Text and Binary Misclassification (#2347)

* Fix Non-UTF-8 File Handling: Improve Encoding Detection to Prevent Garbled Text and Binary Misclassification

* update package-lock.json

* update

* update

* fix

* fix

* fix

* ENG 526/Fix: Versioned Auto Approve settings (#3014)

* added verisoning for autoApprove settings

* removed lines from source branch

* rebase

* changeset

* one small change

* activating extension with evals.env (#3041)

Co-authored-by: Cline Evaluation <cline@example.com>

* ripping out test build flag (#3043)

Co-authored-by: Cline Evaluation <cline@example.com>

* cleaning up evals.env logic in extension.ts (#3045)

Co-authored-by: Cline Evaluation <cline@example.com>

* ENG-516 Slash commands (#3044)

* scroll

* menu

* changeset

* nit

* What's yer path? (#3047)

* update extension imports to use aliasing

* changeset

* ENG-484 Enhance fixWithCline command execution by focusing chat input  (#3028)

* Enhance fixWithCline command execution by focusing chat input and adding a delay before processing the fixWithCline command.

* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component

* new_task prompt (#3049)

* prompt

* changeset

* words

* prettier

* Added metadata

---------

Co-authored-by: yt3trees <57471763+yt3trees@users.noreply.github.com>
Co-authored-by: pashpashpash <nik@cline.bot>
Co-authored-by: Cline Evaluation <cline@example.com>
Co-authored-by: Toshii <94262432+0xToshii@users.noreply.github.com>
Co-authored-by: Evan <58194240+celestial-vault@users.noreply.github.com>
Co-authored-by: nomaven <arafat.da.khan@gmail.com>
2025-04-23 19:48:06 -10:00
Sarah Fortune 8cddbcfd99 Install protoc with npm, don't ask the user to install it manually (#3082)
* Use grpc-tools module to install protoc

Add dependencies for npm modules that provide the protoc binary and the ts plugin.
Don't include protos in sub-directories to prevent including node_modules.

* Move proto generator dependencies into top level package.json

* Keep package.json

Otherwise node cannot tell build-proto.js is a module.
2025-04-23 19:39:27 -10:00
XiaoliandFrostbourne 4e0cb64e77 fix: fix mermaid render problem on github caused by space in subgraph name (#2985)
Co-authored-by: Frostbourne <frostbournesb@protonmail.com>
2025-04-23 00:19:05 -07:00
Evan 4aa3764beb Fix: Protoc script version check (#3071)
* update to split by parts

* remove comment
2025-04-22 15:30:40 -10:00
nomaven a525d6dd5e Adding Caching to gemini provider (#3072) 2025-04-23 06:39:49 +05:30
Trevor Hudson 59dd3236e4 add github action for creating linear tickets for unconnected PRs (#3021)
* add github action for creating linear tickets for unconnected PRs

* changset

* only load fetch if not present

* omit fetch

* add error handling

* fix gql query

* only run for opened PRs

* break out into actions

* fix folders

* checkout first

* remove the actions

* add sync

* remove sync
2025-04-22 18:08:47 -07:00
nomaven 5439426ff6 ENG-524 Remove supportsComputerUse restriction and support browser use through any model that supports images (#3048)
* Enhance fixWithCline command execution by focusing chat input and adding a delay before processing the fixWithCline command.

* feat: add OpenRouter base URL and balance display component

* refactor: remove supportsComputerUse from modelInfo and related components, replacing with supportsImages where applicable

* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component
2025-04-23 04:21:18 +04:00
monotykamary fffcc80477 feat: update gemini sdk and add thinking budget support (#2964)
* feat: update gemini sdk and add thinking budget support

* chore: remove redundant comments

* refactor(thinking-budget): abstract calculation for budget slider

* chore: remove some more redundant comments
2025-04-22 16:50:59 -07:00
Evan dfcb3d5d9b PROTOBUS: toggleMcpServer (#3063)
* wip

* migrate toggleMcpServer

* changeset

* support optional types and enum type
2025-04-22 16:29:13 -07:00
Andrei Eternal 4af5150823 Add @Garoth as a code owner (#3070) 2025-04-22 13:13:01 -10:00
Y.Yamamoto ddbdfbc96d docs: Fix mermaid syntax error (#3053) 2025-04-22 14:53:33 -07:00
Suvarchal Kumar Cheedela a405df5dc0 Fix #2941 ollama timeout (#3024)
* Fix: Increase Ollama provider timeout from 30s to 120s

* Add changeset for Ollama provider timeout fix
2025-04-22 23:59:22 +05:30
Toshii 04d1f1d4e7 new_task prompt (#3049)
* prompt

* changeset

* words
2025-04-21 18:26:02 -07:00
nomaven 0572933c32 ENG-484 Enhance fixWithCline command execution by focusing chat input (#3028)
* Enhance fixWithCline command execution by focusing chat input and adding a delay before processing the fixWithCline command.

* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component

* feat: add OpenRouter base URL and balance display component
2025-04-22 06:41:10 +05:30
Evan 99bbe17df9 What's yer path? (#3047)
* update extension imports to use aliasing

* changeset
2025-04-21 16:49:44 -07:00
Toshii b3b7b9da5f ENG-516 Slash commands (#3044)
* scroll

* menu

* changeset

* nit
2025-04-21 16:41:34 -07:00
pashpashpashandCline Evaluation b0df763ae7 cleaning up evals.env logic in extension.ts (#3045)
Co-authored-by: Cline Evaluation <cline@example.com>
2025-04-21 15:38:46 -07:00
pashpashpashandCline Evaluation 280374f30d ripping out test build flag (#3043)
Co-authored-by: Cline Evaluation <cline@example.com>
2025-04-21 15:11:37 -07:00
pashpashpashandCline Evaluation 9d9e54360b activating extension with evals.env (#3041)
Co-authored-by: Cline Evaluation <cline@example.com>
2025-04-21 15:06:13 -07:00
canvrno 552146d8b5 ENG 526/Fix: Versioned Auto Approve settings (#3014)
* added verisoning for autoApprove settings

* removed lines from source branch

* rebase

* changeset

* one small change
2025-04-21 12:29:59 -07:00
yt3trees 552054a026 Fix Non-UTF-8 File Handling: Improve Encoding Detection to Prevent Garbled Text and Binary Misclassification (#2347)
* Fix Non-UTF-8 File Handling: Improve Encoding Detection to Prevent Garbled Text and Binary Misclassification

* update package-lock.json

* update

* update

* fix

* fix

* fix
2025-04-21 12:05:48 -07:00
Daniel Steigmanandellipsis-dev[bot] cff8a237cd ENG-464 Fix Settings state issue with API provider reseting other settings. (#3004)
* added a a difference between react state saves and core state saves so that the provider settings dont reset other set settings

* added changeset

* Update .changeset/thirty-bugs-admire.md

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* changed button text to say Save

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-21 11:04:05 -07:00
pashpashpash e70264a56c Running Commands Old-School + Living on the Edge with the Latest VSIX (#2999)
* using ndoe shell instead of vs code terminal for commands + always using latest vsix

* 30s max time for commands in test mode

* removed overwhelming logs

* better 30s termination
2025-04-21 11:00:35 -07:00
Evan 06196cf53d Don't call me by my name (#2982)
* add path aliases to the extension side

* changeset
2025-04-21 10:59:43 -07:00
canvrno 1761c0e9e8 protoc version check (#2981) 2025-04-21 10:59:18 -07:00
Evan fbb13f102c Fix new rule button click (#3010)
* fix add new rule file button click

* changeset
2025-04-21 09:44:44 -07:00
treeleaves30760 4850df722b Add the o1 model in the isReasoningModelFamily to avoid 'temperature' parameter passed to azure (#2963) 2025-04-20 12:41:19 -07:00
watany 6e71b3f7cc feat: Add !include .file directive support for .clineignore (#1777)
* feat: Add `!include .file` directive support for `.clineignore`

* changeset

* add warning

* fix

* revert

* reduce diff

* reduce diff
2025-04-19 23:00:00 -07:00
Andrei EdellandAndrei Edell a198f71986 [PROTOBUS] gRPC-ized discoverBrowser (#2976)
* gRPC-ized discoverBrowser

* clean up a string

* remove useless comments

---------

Co-authored-by: Andrei Edell <andrei@nugbase.com>
2025-04-19 16:08:11 -10:00
canvrno 0d38381573 checkpointDiff --> protobuf (#2980)
* Checkpoints diff protobuf

* Requested changes to checkpointDiff protobuf
2025-04-19 14:21:10 -10:00
Saoud Rizwan ba6dcb5bc9 Prepare for release 2025-04-19 01:56:00 -07:00
Saoud Rizwan ecb8633534 fix: task cancellation during thinking stream would result in 'Cline aborted stream' error (#2986)
* Fix task cancellation handling to prevent errors during reasoning message streaming

* Create happy-lies-dress.md
2025-04-19 01:52:05 -07:00
189 changed files with 6846 additions and 1448 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add markdown copy to chat
+5
View File
@@ -0,0 +1,5 @@
---
"cline": minor
---
Add support for custom API request timeout. Previously, timeouts were hardcoded to 30 seconds for providers like Ollama or 15 seconds for OpenRouter and Cline. Now users can set a custom timeout value in milliseconds through the settings interface.
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Move to conversation tracking to the task close event
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
fix: task cancellation during thinking stream would result in 'Cline aborted stream' error
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add dependsOn to more blocks in the tasks.json
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
add truncation notice when truncating manually
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
protobus migration for openImage
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Adding UI to show openrouter balance next to provider
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
fix cost calculation
+7 -7
View File
@@ -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
View File
@@ -1 +1 @@
* @saoudrizwan @ocasta181 @NightTrek @pashpashpash @dcbartlett @saito-sv
* @saoudrizwan @ocasta181 @NightTrek @pashpashpash @dcbartlett @saito-sv @Garoth
+177
View File
@@ -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
});
}
+2
View File
@@ -17,3 +17,5 @@ pnpm-lock.yaml
coverage
# But don't ignore the coverage scripts in .github/scripts/
!.github/scripts/coverage/
*evals.env
+1 -13
View File
@@ -12,23 +12,11 @@
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "${defaultBuildTask}",
"postDebugTask": "stop",
"env": {
"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}"
}
}
]
}
+41 -19
View File
@@ -4,10 +4,25 @@
"version": "2.0.0",
"tasks": [
{
"label": "watch",
"dependsOn": ["npm: build:webview", "npm: dev:webview", "npm: watch:tsc", "npm: watch:esbuild"],
"label": "npm: protos",
"type": "npm",
"script": "protos",
"problemMatcher": [],
"isBackground": false,
"presentation": {
"reveal": "never"
"reveal": "always"
},
"options": {
"env": {
"IS_DEV": "true"
}
}
},
{
"label": "watch",
"dependsOn": ["npm: protos", "npm: build:webview", "npm: dev:webview", "npm: watch:tsc", "npm: watch:esbuild"],
"presentation": {
"reveal": "always"
},
"group": {
"kind": "build",
@@ -16,9 +31,15 @@
},
{
"label": "watch:test",
"dependsOn": ["npm: build:webview:test", "npm: dev:webview", "npm: watch:tsc", "npm: watch:esbuild:test"],
"dependsOn": [
"npm: protos",
"npm: build:webview:test",
"npm: dev:webview",
"npm: watch:tsc",
"npm: watch:esbuild:test"
],
"presentation": {
"reveal": "never"
"reveal": "always"
},
"group": "build"
},
@@ -29,10 +50,10 @@
"problemMatcher": [],
"isBackground": true,
"label": "npm: build:webview",
"dependsOn": ["npm: protos"],
"presentation": {
"group": "watch",
"reveal": "never",
"close": true
"reveal": "always"
},
"options": {
"env": {
@@ -47,10 +68,10 @@
"problemMatcher": [],
"isBackground": true,
"label": "npm: build:webview:test",
"dependsOn": ["npm: protos"],
"presentation": {
"group": "watch",
"reveal": "never",
"close": true
"reveal": "always"
},
"options": {
"env": {
@@ -82,10 +103,10 @@
],
"isBackground": true,
"label": "npm: dev:webview",
"dependsOn": ["npm: protos"],
"presentation": {
"group": "watch",
"reveal": "never",
"close": true
"reveal": "always"
},
"options": {
"env": {
@@ -100,10 +121,10 @@
"problemMatcher": "$esbuild-watch",
"isBackground": true,
"label": "npm: watch:esbuild",
"dependsOn": ["npm: protos"],
"presentation": {
"group": "watch",
"reveal": "never",
"close": true
"reveal": "always"
},
"options": {
"env": {
@@ -118,10 +139,10 @@
"problemMatcher": "$esbuild-watch",
"isBackground": true,
"label": "npm: watch:esbuild:test",
"dependsOn": ["npm: protos"],
"presentation": {
"group": "watch",
"reveal": "never",
"close": true
"reveal": "always"
},
"options": {
"env": {
@@ -137,10 +158,10 @@
"problemMatcher": "$tsc-watch",
"isBackground": true,
"label": "npm: watch:tsc",
"dependsOn": ["npm: protos"],
"presentation": {
"group": "watch",
"reveal": "never",
"close": true
"reveal": "always"
}
},
{
@@ -148,15 +169,16 @@
"script": "watch-tests",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"dependsOn": ["npm: protos"],
"presentation": {
"reveal": "never",
"reveal": "always",
"group": "watchers"
},
"group": "build"
},
{
"label": "tasks: watch-tests",
"dependsOn": ["npm: watch", "npm: watch-tests"],
"dependsOn": ["npm: protos", "npm: watch", "npm: watch-tests"],
"problemMatcher": []
},
{
+30 -1
View File
@@ -1,5 +1,35 @@
# Changelog
## [3.13.3]
- Add download counts to MCP marketplace items
- Add `/compact` command
- Add prompt caching to gemini models in cline / openrouter providers
- Add tooltips to bottom row menu
## [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 +47,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
View File
@@ -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,
{
+24
View File
@@ -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
+6 -2
View File
@@ -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"
}
}
}
+53
View File
@@ -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
}
}
+16
View File
@@ -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)
+79
View File
@@ -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
}
+57 -12
View File
@@ -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)
+411 -10
View File
@@ -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",
+9 -12
View File
@@ -2,7 +2,7 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.13.0",
"version": "3.13.3",
"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,13 +289,9 @@
"compile": "npm run check-types && npm run lint && node esbuild.js",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.js --watch",
"watch:esbuild:test": "IS_TEST=true node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
"protos": "node proto/build-proto.js && prettier src/shared/proto --write && prettier src/core/controller --write",
"package:test": "IS_TEST=true npm run build:webview:test && npm run check-types && npm run lint && IS_TEST=true node esbuild.js --production",
"build:webview:test": "cd webview-ui && IS_TEST=true npm run build",
"watch:test": "IS_TEST=true npm-run-all -p watch:tsc watch:esbuild:test",
"compile-tests": "tsc -p ./tsconfig.test.json --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "npm run compile-tests && npm run compile && npm run lint",
@@ -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",
+15
View File
@@ -0,0 +1,15 @@
syntax = "proto3";
package cline;
option java_package = "bot.cline.proto";
option java_multiple_files = true;
import "common.proto";
// Service for account-related operations
service AccountService {
// Handles the user clicking the login link in the UI.
// Generates a secure nonce for state validation, stores it in secrets,
// and opens the authentication URL in the external browser.
rpc accountLoginClicked(EmptyRequest) returns (String);
}
+28
View File
@@ -1,12 +1,17 @@
syntax = "proto3";
package cline;
option java_package = "bot.cline.proto";
option java_multiple_files = true;
import "common.proto";
service BrowserService {
rpc getBrowserConnectionInfo(EmptyRequest) returns (BrowserConnectionInfo);
rpc testBrowserConnection(StringRequest) returns (BrowserConnection);
rpc discoverBrowser(EmptyRequest) returns (BrowserConnection);
rpc getDetectedChromePath(EmptyRequest) returns (ChromePath);
rpc updateBrowserSettings(UpdateBrowserSettingsRequest) returns (Boolean);
}
message BrowserConnectionInfo {
@@ -20,3 +25,26 @@ message BrowserConnection {
string message = 2;
optional string endpoint = 3;
}
message ChromePath {
string path = 1;
bool is_bundled = 2;
}
message Viewport {
int32 width = 1;
int32 height = 2;
}
message BrowserSettings {
Viewport viewport = 1;
optional string remote_browser_host = 2;
optional bool remote_browser_enabled = 3;
}
message UpdateBrowserSettingsRequest {
Metadata metadata = 1;
Viewport viewport = 2;
optional string remote_browser_host = 3;
optional bool remote_browser_enabled = 4;
}
+13 -23
View File
@@ -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,12 @@ async function generateMethodRegistrations() {
console.log(chalk.cyan("Generating method registration files..."))
const serviceDirs = [
path.join(ROOT_DIR, "src", "core", "controller", "account"),
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", "mcp"),
path.join(ROOT_DIR, "src", "core", "controller", "task"),
// Add more service directories here as needed
]
+19
View File
@@ -0,0 +1,19 @@
syntax = "proto3";
package cline;
option java_package = "bot.cline.proto";
option java_multiple_files = true;
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;
}
+11
View File
@@ -1,6 +1,8 @@
syntax = "proto3";
package cline;
option java_package = "bot.cline.proto";
option java_multiple_files = true;
message Metadata {
}
@@ -38,3 +40,12 @@ message BytesRequest {
message Bytes {
bytes value = 1;
}
message BooleanRequest {
Metadata metadata = 1;
bool value = 2;
}
message Boolean {
bool value = 1;
}
+16
View File
@@ -0,0 +1,16 @@
syntax = "proto3";
package cline;
option java_package = "bot.cline.proto";
option java_multiple_files = true;
import "common.proto";
// Service for file-related operations
service FileService {
// Opens a file in the editor
rpc openFile(StringRequest) returns (Empty);
// Opens an image in the system viewer
rpc openImage(StringRequest) returns (Empty);
}
+69
View File
@@ -0,0 +1,69 @@
syntax = "proto3";
package cline;
option java_package = "bot.cline.proto";
option java_multiple_files = true;
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;
}
+23
View File
@@ -0,0 +1,23 @@
syntax = "proto3";
package cline;
option java_package = "bot.cline.proto";
option java_multiple_files = true;
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 -3
View File
@@ -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 -1
View File
@@ -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"
+1 -1
View File
@@ -7,7 +7,7 @@ import {
askSageModels,
askSageDefaultModelId,
askSageDefaultURL,
} from "../../shared/api"
} from "@shared/api"
import { ApiStream } from "../transform/stream"
type AskSageRequest = {
+1 -1
View File
@@ -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 -1
View File
@@ -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"
+1 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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"
+1 -10
View File
@@ -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"
+4 -2
View File
@@ -21,8 +21,9 @@ export class OllamaHandler implements ApiHandler {
try {
// Create a promise that rejects after timeout
const timeoutMs = this.options.requestTimeoutMs || 30000
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 ${timeoutMs / 1000} seconds`)), timeoutMs)
})
// Create the actual API request promise
@@ -63,7 +64,8 @@ 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")
const timeoutMs = this.options.requestTimeoutMs || 30000
throw new Error(`Ollama request timed out after ${timeoutMs / 1000} seconds`)
}
// Enhance error reporting
+1 -7
View File
@@ -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"
+2 -2
View File
@@ -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 },
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+4 -4
View File
@@ -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
+4 -4
View File
@@ -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
+4 -4
View File
@@ -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
+3 -3
View File
@@ -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 {
+5 -5
View File
@@ -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.
+3 -3
View File
@@ -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 {
+8 -11
View File
@@ -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,
+9 -6
View File
@@ -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,
@@ -22,7 +20,8 @@ export async function createOpenRouterStream(
]
// prompt caching: https://openrouter.ai/docs/prompt-caching
// this is specifically for claude models (some models may 'support prompt caching' automatically without this)
// this was initially specifically for claude models (some models may 'support prompt caching' automatically without this)
// gemini models only use the last breakpoint for caching, so the others will be ignored
switch (model.id) {
case "anthropic/claude-3.7-sonnet":
case "anthropic/claude-3.7-sonnet:beta":
@@ -41,6 +40,10 @@ export async function createOpenRouterStream(
case "anthropic/claude-3-haiku:beta":
case "anthropic/claude-3-opus":
case "anthropic/claude-3-opus:beta":
case "google/gemini-2.5-pro-preview-03-25":
case "google/gemini-2.0-flash-001":
case "google/gemini-flash-1.5":
case "google/gemini-pro-1.5":
openAiMessages[0] = {
role: "system",
content: [
+1
View File
@@ -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,30 @@
import * as vscode from "vscode"
import crypto from "crypto"
import { Controller } from "../index"
import { storeSecret } from "../../storage/state"
/**
* Handles the user clicking the login link in the UI.
* Generates a secure nonce for state validation, stores it in secrets,
* and opens the authentication URL in the external browser.
*
* @param controller The controller instance.
* @returns The login URL as a string.
*/
export async function accountLoginClicked(controller: Controller): Promise<String> {
// Generate nonce for state validation
const nonce = crypto.randomBytes(32).toString("hex")
await storeSecret(controller.context, "authNonce", nonce)
// Open browser for authentication with state param
console.log("Login button clicked in account page")
console.log("Opening auth page with state param")
const uriScheme = vscode.env.uriScheme
const authUrl = vscode.Uri.parse(
`https://app.cline.bot/auth?state=${encodeURIComponent(nonce)}&callback_url=${encodeURIComponent(`${uriScheme || "vscode"}://saoudrizwan.claude-dev/auth`)}`,
)
vscode.env.openExternal(authUrl)
return authUrl.toString()
}
+14
View File
@@ -0,0 +1,14 @@
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
import { registerAllMethods } from "./methods"
const accountService = createServiceRegistry("account")
// Export the method handler type and registration function
export type AccountMethodHandler = ServiceMethodHandler
export const registerMethod = accountService.registerMethod
// Export the request handler
export const handleAccountServiceRequest = accountService.handleRequest
// Register all account methods
registerAllMethods()
+12
View File
@@ -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 { accountLoginClicked } from "./accountLoginClicked"
// Register all account service methods
export function registerAllMethods(): void {
// Register each method with the registry
registerMethod("accountLoginClicked", accountLoginClicked)
}
@@ -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
@@ -0,0 +1,30 @@
import { ChromePath } from "../../../shared/proto/browser"
import { EmptyRequest } from "../../../shared/proto/common"
import { Controller } from "../index"
import { getAllExtensionState } from "../../storage/state"
import { BrowserSession } from "../../../services/browser/BrowserSession"
/**
* Get the detected Chrome executable path
* @param controller The controller instance
* @param request The empty request message
* @returns The detected Chrome path and whether it's bundled
*/
export async function getDetectedChromePath(controller: Controller, request: EmptyRequest): Promise<ChromePath> {
try {
const { browserSettings } = await getAllExtensionState(controller.context)
const browserSession = new BrowserSession(controller.context, browserSettings)
const result = await browserSession.getDetectedChromePath()
return {
path: result.path,
isBundled: result.isBundled,
}
} catch (error) {
console.error("Error getting detected Chrome path:", error)
return {
path: "",
isBundled: false,
}
}
}
+6
View File
@@ -3,12 +3,18 @@
// Import all method implementations
import { registerMethod } from "./index"
import { discoverBrowser } from "./discoverBrowser"
import { getBrowserConnectionInfo } from "./getBrowserConnectionInfo"
import { getDetectedChromePath } from "./getDetectedChromePath"
import { testBrowserConnection } from "./testBrowserConnection"
import { updateBrowserSettings } from "./updateBrowserSettings"
// Register all browser service methods
export function registerAllMethods(): void {
// Register each method with the registry
registerMethod("discoverBrowser", discoverBrowser)
registerMethod("getBrowserConnectionInfo", getBrowserConnectionInfo)
registerMethod("getDetectedChromePath", getDetectedChromePath)
registerMethod("testBrowserConnection", testBrowserConnection)
registerMethod("updateBrowserSettings", updateBrowserSettings)
}
@@ -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,46 @@
import { UpdateBrowserSettingsRequest } from "../../../shared/proto/browser"
import { Boolean } from "../../../shared/proto/common"
import { Controller } from "../index"
import { updateGlobalState } from "../../storage/state"
import { BrowserSettings as SharedBrowserSettings } from "../../../shared/BrowserSettings"
/**
* Update browser settings
* @param controller The controller instance
* @param request The browser settings request message
* @returns Success response
*/
export async function updateBrowserSettings(controller: Controller, request: UpdateBrowserSettingsRequest): Promise<Boolean> {
try {
// Convert from protobuf format to shared format
const browserSettings: SharedBrowserSettings = {
viewport: {
width: request.viewport?.width || 900,
height: request.viewport?.height || 600,
},
remoteBrowserEnabled: request.remoteBrowserEnabled || false,
remoteBrowserHost: request.remoteBrowserHost || undefined,
}
// Update global state with new settings
await updateGlobalState(controller.context, "browserSettings", browserSettings)
// Update task browser settings if task exists
if (controller.task) {
controller.task.browserSettings = browserSettings
controller.task.browserSession.browserSettings = browserSettings
}
// Post updated state to webview
await controller.postStateToWebview()
return {
value: true,
}
} catch (error) {
console.error("Error updating browser settings:", error)
return {
value: false,
}
}
}
@@ -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 {}
}
+15
View File
@@ -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)
}
+15
View File
@@ -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()
+14
View File
@@ -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 { openFile } from "./openFile"
import { openImage } from "./openImage"
// Register all file service methods
export function registerAllMethods(): void {
// Register each method with the registry
registerMethod("openFile", openFile)
registerMethod("openImage", openImage)
}
+17
View File
@@ -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()
}
+17
View File
@@ -0,0 +1,17 @@
import { Controller } from ".."
import { Empty, StringRequest } from "@shared/proto/common"
import { openImage as openImageIntegration } from "@integrations/misc/open-file"
import { FileMethodHandler } from "./index"
/**
* Opens an image in the system viewer
* @param controller The controller instance
* @param request The request message containing the image path or data URI in the 'value' field
* @returns Empty response
*/
export const openImage: FileMethodHandler = async (controller: Controller, request: StringRequest): Promise<Empty> => {
if (request.value) {
await openImageIntegration(request.value)
}
return Empty.create()
}
+38 -9
View File
@@ -1,6 +1,10 @@
import { Controller } from "./index"
import { handleAccountServiceRequest } from "./account"
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 +31,40 @@ 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.AccountService":
return {
message: await handleAccountServiceRequest(this.controller, method, message),
request_id: requestId,
}
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),
+90 -174
View File
@@ -1,39 +1,38 @@
import { Anthropic } from "@anthropic-ai/sdk"
import axios from "axios"
import type { AxiosRequestConfig } from "axios"
import crypto from "crypto"
import fs from "fs/promises"
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
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 { 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 +47,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"
/*
@@ -94,6 +93,20 @@ export class Controller {
cleanupLegacyCheckpoints(this.context.globalStorageUri.fsPath, this.outputChannel).catch((error) => {
console.error("Failed to cleanup legacy checkpoints:", error)
})
// Capture telemetry when the extension is disposed
this.context.subscriptions.push({
dispose: () => {
if (this.task) {
telemetryService.captureClosedEvent(
this.task.taskId,
this.task.apiConversationHistory.length,
this.task.apiConversationHistory.filter((msg) => msg.role === "user").length,
this.task.apiConversationHistory.filter((msg) => msg.role === "assistant").length,
)
}
},
})
}
/*
@@ -139,6 +152,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 +298,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,62 +312,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":
if (message.browserSettings) {
// remoteBrowserEnabled now means "enable remote browser connection"
// commenting out since this is being done in BrowserSettingsSection updateRemoteBrowserEnabled
// if (!message.browserSettings.remoteBrowserEnabled) {
// // If disabling remote browser connection, clear the remoteBrowserHost
// message.browserSettings.remoteBrowserHost = undefined
// }
await updateGlobalState(this.context, "browserSettings", message.browserSettings)
if (this.task) {
this.task.browserSettings = message.browserSettings
this.task.browserSession.browserSettings = message.browserSettings
}
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":
@@ -366,11 +344,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()
@@ -433,9 +406,6 @@ export class Controller {
await refreshClineRulesToggles(this.context, cwd)
await this.postStateToWebview()
break
case "openImage":
openImage(message.text!)
break
case "openInBrowser":
if (message.url) {
vscode.env.openExternal(vscode.Uri.parse(message.url))
@@ -447,9 +417,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 +430,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,56 +450,15 @@ 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
case "accountLoginClicked": {
// Generate nonce for state validation
const nonce = crypto.randomBytes(32).toString("hex")
await storeSecret(this.context, "authNonce", nonce)
// Open browser for authentication with state param
console.log("Login button clicked in account page")
console.log("Opening auth page with state param")
const uriScheme = vscode.env.uriScheme
const authUrl = vscode.Uri.parse(
`https://app.cline.bot/auth?state=${encodeURIComponent(nonce)}&callback_url=${encodeURIComponent(`${uriScheme || "vscode"}://saoudrizwan.claude-dev/auth`)}`,
)
vscode.env.openExternal(authUrl)
break
}
case "accountLogoutClicked": {
await this.handleSignOut()
break
@@ -552,7 +478,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 +543,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 +637,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(
@@ -801,21 +709,6 @@ export class Controller {
this.postMessageToWebview({ type: "relinquishControl" })
break
}
case "getDetectedChromePath": {
try {
const { browserSettings } = await getAllExtensionState(this.context)
const browserSession = new BrowserSession(this.context, browserSettings)
const { path, isBundled } = await browserSession.getDetectedChromePath()
await this.postMessageToWebview({
type: "detectedChromePath",
text: path,
isBundled,
})
} catch (error) {
console.error("Error getting detected Chrome path:", error)
}
break
}
case "getRelativePaths": {
if (message.uris && message.uris.length > 0) {
const resolvedPaths = await Promise.all(
@@ -922,6 +815,15 @@ export class Controller {
}
break
}
case "copyToClipboard": {
try {
await vscode.env.clipboard.writeText(message.text || "")
} catch (error) {
console.error("Error copying to clipboard:", error)
}
break
}
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
@@ -1530,7 +1432,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
@@ -1572,6 +1473,14 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
modelInfo.cacheWritesPrice = 0.14
modelInfo.cacheReadsPrice = 0.014
break
case "google/gemini-2.5-pro-preview-03-25":
case "google/gemini-2.0-flash-001":
case "google/gemini-flash-1.5":
case "google/gemini-pro-1.5":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = parsePrice(rawModel.pricing?.input_cache_write)
modelInfo.cacheReadsPrice = parsePrice(rawModel.pricing?.input_cache_read)
break
}
models[rawModel.id] = modelInfo
@@ -1613,7 +1522,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),
@@ -1933,7 +1841,15 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
}
}
async clearTask() {
async clearTask(isCloseTaskEvent: boolean = false) {
if (isCloseTaskEvent && this.task) {
await telemetryService.captureClosedEvent(
this.task.taskId,
this.task.apiConversationHistory.length,
this.task.apiConversationHistory.filter((msg) => msg.role === "user").length,
this.task.apiConversationHistory.filter((msg) => msg.role === "assistant").length,
)
}
this.task?.abortTask()
this.task = undefined // removes reference to it, so once promises end it will be garbage collected
}
+15
View File
@@ -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()
+14
View File
@@ -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
}
}
+13
View File
@@ -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()
}
+16
View File
@@ -0,0 +1,16 @@
import { telemetryService } from "@/services/telemetry/TelemetryService"
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> {
// passing in true because this is only called when the user closes the task
await controller.clearTask(true)
await controller.postStateToWebview()
return Empty.create()
}
+15
View File
@@ -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()
+16
View File
@@ -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)
}
+14
View File
@@ -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()
})
})
})
+59 -3
View File
@@ -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)
+8 -8
View File
@@ -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 {
+75 -29
View File
@@ -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 -1
View File
@@ -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

Some files were not shown because too many files have changed in this diff Show More