Compare commits

..
Author SHA1 Message Date
kvyb f5778d86b3 feat: migrate file selection to host bridge and fix PDF opening
- Add FileService to host bridge with selectFiles RPC for multi-IDE support
- Create proto/host/file.proto and VSCode selectFiles implementation
- Update HostProvider, host-bridge-client-manager, and grpc-client for fileClient
- Simplify process-files.ts to use HostProvider abstraction pattern
- Fix VSCode openFile to route PDFs/binary files to system viewer vs text editor
- Improve ChatView.tsx file selection with defensive array handling
- Regenerate all host bridge client interfaces and configurations
- Preserve all existing functionality while enabling IntelliJ implementation
2025-07-24 14:00:09 +08:00
kvyb 597901ba7a feat: add openFile host bridge for vscode.open command 2025-07-24 07:01:18 +08:00
365 changed files with 3204 additions and 6527 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Display account balance for all org members
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Add comment about testing fix
+1 -1
View File
@@ -716,7 +716,7 @@ The Controller class manages MCP servers through the McpHub service:
class Controller {
mcpHub?: McpHub
constructor(context: vscode.ExtensionContext, webviewProvider: WebviewProvider) {
constructor(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel, webviewProvider: WebviewProvider) {
this.mcpHub = new McpHub(this)
}
+18 -12
View File
@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
**Important:** All bug reports must be reproducible using Claude 4 Sonnet. Cline uses complex prompts so less capable models may not work as expected.
**Important:** All bug reports must be reproducible using Claude 3.5 Sonnet. Cline uses complex prompts so less capable models may not work as expected.
- type: textarea
id: what-happened
attributes:
@@ -24,7 +24,7 @@ body:
2.
3.
validations:
required: false
required: true
- type: textarea
id: logs
attributes:
@@ -39,19 +39,20 @@ body:
placeholder: "e.g., cline:anthropic/claude-3.7-sonnet, gemini:gemini-2.5-pro-exp-03-25"
validations:
required: true
- type: input
id: operating-system
attributes:
label: Operating System
description: What operating system are you using?
placeholder: "e.g., Windows 11, macOS Sonoma, Ubuntu 22.04"
validations:
required: true
- type: textarea
id: system-info
attributes:
label: System Information
description: What operating system and hardware are you using?
placeholder: |
Operating System: Windows 11, macOS Sonoma, Ubuntu 22.04, etc.
Hardware: CPU, GPU, RAM specifications if relevant
e.g.,
OS: Windows 11
CPU: Intel Core i7-11700K
GPU: NVIDIA GeForce RTX 3070
RAM: 32GB DDR4
label: System Info
description: What system information is relevant to the issue?
placeholder: "e.g., CPU: Intel Core i7-11700K, GPU: NVIDIA GeForce RTX 3070, RAM: 32GB DDR4"
validations:
required: true
- type: input
@@ -62,3 +63,8 @@ body:
placeholder: "e.g., 1.2.3"
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Add any other context about the problem here, such as screenshots or related issues.
@@ -0,0 +1,116 @@
name: 💡 Feature Proposal & Contribution
description: Propose a new feature or improvement, and optionally offer to implement feature as a contributor
labels: ["proposal"]
body:
- type: markdown
attributes:
value: |
**Feature Proposal & Contribution for Cline**
Thank you for proposing a feature or improvement for Cline! This template helps us understand the problem, evaluate the solution, and coordinate implementation.
**For detailed proposals:** Please provide comprehensive information to enable fast prioritization and discussion.
**For contribution offers:** You can indicate your willingness to implement the feature yourself.
Before submitting:
- Search existing [Issues](https://github.com/cline/cline/issues) and [Discussions](https://github.com/cline/cline/discussions) to avoid duplicates
- Read the [Contributing Guide](https://github.com/cline/cline/blob/main/CONTRIBUTING.md) if you plan to contribute
- Don't start implementation until the proposal is reviewed and approved
- type: textarea
id: problem-description
attributes:
label: What problem does this solve?
description: |
Describe the problem clearly from a user's point of view. Focus on why this matters, who it affects, and when it occurs.
✅ Good examples:
- "LLM provider returns 400 error when nearing the context window instead of truncating"
- "Submit button is invisible in dark mode"
- "Users can't easily share their Cline configurations with team members"
❌ Avoid vague descriptions:
- "Performance is bad"
- "UI needs work"
Your description should include:
- Who is affected?
- When does it happen?
- What's the current vs expected behavior?
- What is the impact?
placeholder: Be specific about the problem, who it affects, and the impact.
validations:
required: true
- type: textarea
id: proposed-solution
attributes:
label: What's the proposed solution?
description: |
Describe how the problem should be solved. Be specific about UX, system behavior, and any flows that would change.
✅ Good examples:
- "Add error handling immediately after attempting to create the llm stream and retry after manually truncating"
- "Update button styling to ensure contrast in all themes"
- "Add export/import functionality in settings with JSON format"
❌ Avoid vague solutions:
- "Improve performance"
- "Fix the bug"
Your solution should include:
- What exactly will change?
- How will users interact with it?
- What's the expected outcome?
placeholder: Describe the proposed changes and how they solve the problem.
validations:
required: false
- type: dropdown
id: contribution-intent
attributes:
label: Are you interested in implementing this?
description: Let us know if you'd like to contribute to this feature
options:
- "No, just proposing the idea"
- "Yes, I'd like to implement this myself"
- "Yes, I'd like to collaborate with others"
- "Maybe, depending on complexity and guidance"
validations:
required: false
- type: textarea
id: implementation-approach
attributes:
label: Implementation approach (if contributing)
description: |
**Only fill this out if you selected "Yes" above.**
How do you plan to implement this? Include:
- High-level technical approach
- Files/components that would be affected
- Any new dependencies required
- Potential challenges or considerations you've identified
This helps us provide better guidance and ensures alignment before you start coding.
placeholder: "My implementation approach would be..."
- type: checkboxes
id: checklist
attributes:
label: Proposal checklist
options:
- label: I've checked for existing issues or related proposals
required: true
- label: I understand this needs review before implementation can start
required: true
- type: checkboxes
id: contribution-checklist
attributes:
label: Contribution checklist (if contributing)
description: Only check these if you plan to contribute
options:
- label: I've read the [Contributing Guide](https://github.com/cline/cline/blob/main/CONTRIBUTING.md)
- label: I'm willing to make changes based on feedback
- label: I understand the code review process and requirements
+5 -4
View File
@@ -2,14 +2,15 @@
Thank you for contributing to Cline!
⚠️ Important: Before submitting this PR, please ensure you have:
- For feature requests: Created a discussion in our Feature Requests discussions board https://github.com/cline/cline/discussions/categories/feature-requests and received approval from core maintainers before implementation
- For all changes: Link the associated issue/discussion in the "Related Issue" section below
- Opened an issue and discussed your proposed changes with the community / contributors
- Received approval from a core Cline contributor prior to proceeding with the implementation
- Link the associated issue in the "Related Issue" section
Limited exceptions:
Small bug fixes, typo corrections, minor wording improvements, or simple type fixes that don't change functionality may be submitted directly without prior discussion.
Small bug fixes, typo corrections, minor wording improvements, or simple type fixes that don't change functionality may be submitted directly.
Why this requirement?
We deeply appreciate all community contributions - they are essential to Cline's success! To ensure the best use of everyone's time and maintain project direction, we use our Feature Requests discussions board to gauge community interest and validate feature ideas before implementation begins. This helps us focus development efforts on features that will benefit the most users.
We deeply appreciate all community contributions - they are the core reason we're able to operate successfully and keep innovating! We welcome community input and want to make it as easy as possible for people to submit quality work. This process helps our core maintainers review new ideas faster and saves contributor time by ensuring you have the go-ahead before spending time on implementation.
-->
### Related Issue
-1
View File
@@ -97,7 +97,6 @@ jobs:
run: npm run pretest
# Unit Tests disabled due to module system conflicts between backend and webview-ui
# FIX: Right now the tests are run when the PR is being reviewed, but if main is updated after that, the PR can merge without the tests being run on the latest version of main.
# - name: Unit Tests
# run: npm run test:unit
+5 -3
View File
@@ -71,7 +71,7 @@
{
"type": "node",
"request": "launch",
"name": "Run cline-core service",
"name": "Run Standalone Service",
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true,
"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
@@ -82,9 +82,11 @@
// Turns on grpc debug log.
//"GRPC_TRACE": "all",
//"GRPC_VERBOSITY": "DEBUG",
"NODE_PATH": "${workspaceFolder}/dist-standalone/node_modules"
"NODE_PATH": "${workspaceFolder}/dist-standalone/node_modules",
"HOST_BRIDGE_ADDRESS": "localhost:50052"
},
"program": "cline-core.js"
"program": "standalone.js"
}
]
}
+1 -5
View File
@@ -9,9 +9,5 @@
"dist": true // set this to false to include "dist" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
// Protobuf settings
"protoc": {
"options": ["--proto_path=proto"]
}
"typescript.tsc.autoDetect": "off"
}
-46
View File
@@ -1,51 +1,5 @@
# Changelog
## [3.20.7]
- Fix circular dependency that affect the github workflow Tests / test (pull_request)
## [3.20.6]
- Fix login check on extension restart
## [3.20.5]
- Fix authentication persistence issues that could cause users to be logged out unexpectedly
## [3.20.4]
- Add new Cerebras models
- Update rate limits for existing Cerebras models
- Fix for delete task dialog
## [3.20.3]
- Add Huawei Cloud MaaS Provider (Thanks @ddling!)
- Add Cerebras Qwen 3 235B instruct model (Thanks @kevint-cerebras!)
- Add DeepSeek R1 0528 support under Hugging Face (Thanks @0ne0rZer0!)
- Fix Global Rules directory documentation for Linux/WSL systems
- Fix token counting when using VSCode LM API provider
- Fix input field stealing focus issue by only focusing on visible and active editor panels
- Fix duplicate tool registration for claude4-experimental
- Trim input value for URL fields
## [3.20.2]
- Fixed issue with sap ai core client credentials storage
- Fix Qwen Api option inconsistency between UI and API layer
- Fix credit balance out of sync issue on account switching
- Fix Claude Code CLAUDE_CODE_MAX_OUTPUT_TOKENS
- Fix cursor state after restoring files to be disabled after checked out
- Fix issue where checkpointing blocked UI
## [3.20.1]
- Fix for files being deleted when switching modes or closing tasks
## [3.20.0]
- Add account balance display for all organization members, allowing non-admin users to view their organization's credit balance and add credits
## [3.19.8]
- Add Claude Code support on Windows with improved system prompt handling to fix E2BIG errors (Thanks @BarreiroT!)
+8 -5
View File
@@ -14,11 +14,14 @@ Bug reports help make Cline better for everyone! Before creating a new issue, pl
## Before Contributing
All contributions must begin with a GitHub Issue, unless the change is for small bug fixes, typo corrections, minor wording improvements, or simple type fixes that don't change functionality.
**For features and contributions**:
- First check the [Feature Requests discussions board](https://github.com/cline/cline/discussions/categories/feature-requests) for similar ideas
- If your idea is new, create a new feature request
- Wait for approval from core maintainers before starting implementation
- Once approved, feel free to begin working on a PR with the help of our community!
- **Check existing issues**: Search [GitHub Issues](https://github.com/cline/cline/issues).
- **Create an issue**: Use appropriate templates:
- **Contributions:** Use the "Contribution Request" template to propose what you'd like to work on.
- **Bugs:** "Bug Report" template for reporting issues.
- **Features:** "Detailed Feature Proposal" template for suggesting new features.
- **Wait for approval**: A core Cline contributor must approve your contribution request before you start implementation.
- **Claim issues**: Once approved, the issue will be assigned to you.
**PRs without approved issues may be closed.**
+1
View File
@@ -9,6 +9,7 @@ lint:
except: # Add exceptions for current patterns that contradict STANDARD settings
- RPC_PASCAL_CASE # rpcs are camel case (start with lowercase)
- PACKAGE_DIRECTORY_MATCH # the protos in the cline package are not in a dir named cline.
- RPC_REQUEST_RESPONSE_UNIQUE # request messages are not unique.
- RPC_REQUEST_STANDARD_NAME # request messages dont all end with Request
- RPC_RESPONSE_STANDARD_NAME # response messages dont all end with Response
+1 -13
View File
@@ -11,19 +11,7 @@ You can create a rule by clicking the `+` button in the Rules tab. This will ope
Once you save the file:
- Your rule will be stored in the `.clinerules/` directory in your project (if it's a Workspace Rule)
- Or in the Global Rules directory (if it's a Global Rule):
### Global Rules Directory Location
The location of your Global Rules directory depends on your operating system:
| Operating System | Default Location | Notes |
|------------------|------------------|-------|
| **Windows** | `Documents\Cline\Rules` | Uses system Documents folder |
| **macOS** | `~/Documents/Cline/Rules` | Uses user Documents folder |
| **Linux/WSL** | `~/Documents/Cline/Rules` | May fall back to `~/Cline/Rules` on some systems |
> **Note for Linux/WSL users**: If you don't find your global rules in `~/Documents/Cline/Rules`, check `~/Cline/Rules` as the location may vary depending on your system configuration and whether the Documents directory exists.
- Or in the `Documents/Cline/Rules` directory (if it's a Global Rule).
You can also have Cline create a rule for you by using the [`/newrule` slash command](/features/slash-commands/new-rule) in the chat.
@@ -17,7 +17,6 @@ There are multiple places online to find MCP servers:
- [mcpservers.org](https://mcpservers.org/)
- [mcp.so](https://mcp.so/)
- [glama.ai/mcp/servers](https://glama.ai/mcp/servers)
- [mcp.composio.dev](https://mcp.composio.dev/)
These directories allow users to sort the servers by various criteria such as downloads, date, stars, and use case. Each entry provides information such as features, tools, and configuration instructions.
+4 -4
View File
@@ -4,17 +4,17 @@ title: "Telemetry"
### Overview
To help make Cline better for everyone, we collect usage data that helps us understand how developers are using our open-source AI coding agent. This feedback loop is crucial for improving Cline's capabilities and user experience.
To help make Cline better for everyone, we collect anonymous usage data that helps us understand how developers are using our open-source AI coding agent. This feedback loop is crucial for improving Cline's capabilities and user experience.
We use PostHog, an open-source analytics platform, for data collection and analysis. Our telemetry implementation is fully transparent - you can review the [source code](https://github.com/cline/cline/blob/main/src/services/posthog/telemetry/TelemetryService.ts) to see exactly what we track.
### Tracking Policy
Privacy is our priority. By default, all collected data is anonymized. If you log in with a Cline account, your telemetry data will be associated with your account to help us improve the product and provide better support when you encounter issues. Your code, prompts, and conversation content always remain private and are never collected.
Privacy is our priority. All collected data is anonymized before being sent to PostHog, with no personally identifiable information (PII) included. Your code, prompts, and conversation content always remain private and are never collected.
### What We Track
We collect basic usage data including:
We collect basic anonymous usage data including:
**Task Interactions:** When tasks start and finish, conversation flow (without content)\
**Mode and Tool Usage:** Switches between plan/act modes, which tools are being used\
@@ -28,7 +28,7 @@ For complete transparency, you can inspect our [telemetry implementation](https:
Telemetry in Cline is entirely optional:
- When you update or install our VS Code extension, you'll see a message about our telemetry
- When you update or install our VS Code extension, you'll see a message about our anonymous telemetry
- You can change your preference anytime in settings
Cline also respects VS Code's global telemetry settings. If you've disabled telemetry at the VS Code level, Cline's telemetry will automatically be disabled as well.
@@ -244,60 +244,13 @@ Recent macOS versions have stricter terminal permissions:
### Windows Issues
If you're using Windows and still experiencing issues with shell integration after trying the previous steps, it's recommended you use Git Bash (or PowerShell).
#### PowerShell Execution Policy
### Git Bash
Git Bash is a terminal emulator that provides a Unix-like command line experience on Windows. To use Git Bash, you need to:
1. Download and run the Git for Windows installer from [https://git-scm.com/downloads/win](https://git-scm.com/downloads/win)
2. Quit and re-open VSCode
3. Press `Ctrl + Shift + P` to open the Command Palette
4. Type "Terminal: Select Default Profile" and choose it
5. Select "Git Bash"
### PowerShell
If you'd still like to use PowerShell, make sure you're using an updated version (at least v7+).
- Check your current PowerShell version by running: `$PSVersionTable.PSVersion`
- If your version is below 7, [update PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/whats-new/migrating-from-windows-powershell-51-to-powershell-7?view=powershell-7.4#installing-powershell-7).
You may also need to adjust your PowerShell execution policy. By default, PowerShell restricts script execution for security reasons.
#### Understanding PowerShell Execution Policies
PowerShell uses execution policies to determine which scripts can run on your system. Here are the most common policies:
- `Restricted`: No PowerShell scripts can run. This is the default setting.
- `AllSigned`: All scripts, including local ones, must be signed by a trusted publisher.
- `RemoteSigned`: Scripts created locally can run, but scripts downloaded from the internet must be signed.
- `Unrestricted`: No restrictions. Any script can run, though you will be warned before running internet-downloaded scripts.
For development work in VSCode, the `RemoteSigned` policy is generally recommended. It allows locally created scripts to run without restrictions while maintaining security for downloaded scripts. To learn more about PowerShell execution policies and understand the security implications of changing them, visit Microsoft's documentation: [About Execution Policies](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies).
#### Steps to Change the Execution Policy
1. Open PowerShell as an Administrator: Press `Win + X` and select "Windows PowerShell (Administrator)" or "Windows Terminal (Administrator)".
2. Check Current Execution Policy by running this command:
```powershell
Get-ExecutionPolicy
```
- If the output is already `RemoteSigned`, `Unrestricted`, or `Bypass`, you likely don't need to change your execution policy. These policies should allow shell integration to work.
- If the output is `Restricted` or `AllSigned`, you may need to change your policy to enable shell integration.
3. Change the Execution Policy by running the following command:
```powershell
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
```
- This sets the policy to `RemoteSigned` for the current user only, which is safer than changing it system-wide.
4. Confirm the Change by typing `Y` and pressing Enter when prompted.
5. Verify the Policy Change by running `Get-ExecutionPolicy` again to confirm the new setting.
6. Restart VSCode and try the shell integration again.
If commands fail silently:
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```
#### WSL Integration
+7 -12
View File
@@ -1,10 +1,6 @@
import fs from "node:fs"
import * as esbuild from "esbuild"
import path from "node:path"
import { fileURLToPath } from "node:url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const esbuild = require("esbuild")
const fs = require("fs")
const path = require("path")
const production = process.argv.includes("--production")
const watch = process.argv.includes("--watch")
@@ -131,8 +127,10 @@ const baseConfig = {
sourcemap: !production,
logLevel: "silent",
define: production
? { "import.meta.url": "_importMetaUrl", "process.env.IS_DEV": JSON.stringify(!production) }
: { "import.meta.url": "_importMetaUrl" },
? {
"process.env.IS_DEV": JSON.stringify(!production),
}
: undefined,
tsconfig: path.resolve(__dirname, "tsconfig.json"),
plugins: [
copyWasmFiles,
@@ -143,9 +141,6 @@ const baseConfig = {
format: "cjs",
sourcesContent: false,
platform: "node",
banner: {
js: "const _importMetaUrl=require('url').pathToFileURL(__filename)",
},
}
// Extension-specific configuration
@@ -34,30 +34,25 @@ directApiRuleTester.run("no-direct-vscode-api", noDirectVscodeApiRule, {
},
// Should allow other vscode API calls
{
code: `vscode.commands.registerCommand("Hello")`,
filename: "/foo/bar.ts",
code: `vscode.window.showInformationMessage("Hello")`,
filename: "test.ts",
},
// Should allow postMessage calls on other objects
{
code: `window.postMessage({ type: "test" }, "*")`,
filename: "/foo/bar.ts",
filename: "test.ts",
},
// Should allow variables named vscode but not calling postMessage
{
code: `const vscode = { other: "method" }; vscode.other()`,
filename: "/foo/bar.ts",
},
// Should allow vscode.postMessage in test files
{
code: `vscode.postMessage({ type: "newTask", text: message.text })`,
filename: "/foo/bar.test.ts",
filename: "test.ts",
},
],
invalid: [
// Should disallow vscode.postMessage in regular files
{
code: `vscode.postMessage({ type: "test", data: {} })`,
filename: "/foo/bar.ts",
filename: "test.ts",
errors: [
{
messageId: "useGrpcClient",
@@ -74,13 +69,23 @@ directApiRuleTester.run("no-direct-vscode-api", noDirectVscodeApiRule, {
},
],
},
// Should disallow vscode.postMessage in test files
{
code: `vscode.postMessage({ type: "newTask", text: message.text })`,
filename: "test.test.ts",
errors: [
{
messageId: "useGrpcClient",
},
],
},
// Should disallow property access for disallowed APIs
{
code: `const folders = vscode.workspace.workspaceFolders;`,
filename: "workspace.ts",
errors: [
{
messageId: "useHostBridgeWorkspace",
messageId: "useHostBridge",
},
],
},
+14 -29
View File
@@ -29,30 +29,23 @@ const disallowedApis = {
"vscode.workspace.applyEdit": {
messageId: "useHostBridge",
},
"vscode.window.onDidChangeActiveTextEditor": {
messageId: "useHostBridge",
},
"vscode.env.openExternal": {
messageId: "useUtils",
},
"vscode.window.showWarningMessage": {
messageId: "useHostBridgeShowMessage",
},
// "vscode.env.openExternal": {
// messageId: "useUtils",
// },
// "vscode.window.showWarningMessage": {
// messageId: "useHostBridgeShowMessage",
// },
"vscode.window.showOpenDialog": {
messageId: "useHostBridgeShowMessage",
},
"vscode.window.showErrorMessage": {
messageId: "useHostBridgeShowMessage",
},
"vscode.window.showInformationMessage": {
messageId: "useHostBridgeShowMessage",
},
"vscode.window.showInputBox": {
messageId: "useHostBridge",
},
"vscode.workspace.findFiles": {
messageId: "useNative",
},
// There are too many warnings for these calls, uncomment the following
// when the migration is finished.
// "vscode.window.showErrorMessage": {
// messageId: "useHostBridgeShowMessage",
// },
// "vscode.window.showInformationMessage": {
// messageId: "useHostBridgeShowMessage",
// },
}
module.exports = createRule({
@@ -93,10 +86,6 @@ module.exports = createRule({
"Use utilities in @/utils instead of calling vscode APIs directly.\n" +
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
"Found: {{code}}",
useNative:
"Use a native Javascript API instead of calling the vscode API.\n" +
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
"Found: {{code}}",
},
schema: [],
},
@@ -197,10 +186,6 @@ module.exports = createRule({
if (filename.includes("/standalone/runtime-files/")) {
return true
}
// Skip checking test files
if (filename.endsWith(".test.ts")) {
return true
}
}
return {
+23 -20
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.20.5",
"version": "3.19.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.20.5",
"version": "3.19.8",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.37.0",
@@ -7907,9 +7907,10 @@
"integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -17785,9 +17786,10 @@
}
},
"node_modules/tar-fs": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
"integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
"integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
@@ -18416,9 +18418,10 @@
"license": "MIT"
},
"node_modules/undici": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
"license": "MIT",
"engines": {
"node": ">=18.17"
}
@@ -25160,9 +25163,9 @@
"integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
},
"brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
"balanced-match": "^1.0.0"
}
@@ -31798,9 +31801,9 @@
}
},
"tar-fs": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
"integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
"integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
"requires": {
"bare-fs": "^4.0.1",
"bare-path": "^3.0.0",
@@ -32223,9 +32226,9 @@
"integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g=="
},
"undici": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ=="
},
"undici-types": {
"version": "5.26.5",
+7 -8
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.20.7",
"version": "3.19.8",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
@@ -119,8 +119,7 @@
{
"type": "webview",
"id": "claude-dev.SidebarProvider",
"name": "",
"icon": "assets/icons/icon.svg"
"name": ""
}
]
},
@@ -339,14 +338,14 @@
},
"scripts": {
"vscode:prepublish": "npm run package",
"compile": "npm run check-types && npm run lint && node esbuild.mjs",
"compile-standalone": "npm run check-types && npm run lint && node esbuild.mjs --standalone",
"compile": "npm run check-types && npm run lint && node esbuild.js",
"compile-standalone": "npm run check-types && npm run lint && node esbuild.js --standalone",
"postcompile-standalone": "node scripts/package-standalone.mjs",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.mjs --watch",
"watch:esbuild": "node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.mjs --production",
"protos": "node scripts/build-proto.mjs",
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.js --production",
"protos": "node scripts/build-proto.mjs && node scripts/generate-protobus-setup.mjs && node scripts/generate-host-bridge-client.mjs",
"postprotos": "prettier src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --log-level warn",
"clean": "rimraf dist dist-standalone webview-ui/build src/generated out/",
"compile-tests": "node ./scripts/build-tests.js",
+6 -21
View File
@@ -1,32 +1,17 @@
import { defineConfig } from "@playwright/test"
const isCI = !!process?.env?.CI
const isWindow = process?.platform?.startsWith("win")
const isGitHubAction = !!process.env.CI
export default defineConfig({
workers: 1,
retries: 1,
testDir: "src/test/e2e",
timeout: isCI || isWindow ? 40000 : 20000,
timeout: 20000,
expect: {
timeout: isCI || isWindow ? 5000 : 2000,
timeout: 20000,
},
fullyParallel: true,
reporter: isCI ? [["github"], ["list"]] : [["list"]],
projects: [
{
name: "setup test environment",
testMatch: /global\.setup\.ts/,
},
{
name: "e2e tests",
testMatch: /.*\.test\.ts/,
dependencies: ["setup test environment"],
},
{
name: "cleanup test environment",
testMatch: /global\.teardown\.ts/,
dependencies: ["e2e tests"],
},
],
reporter: isGitHubAction ? [["github"], ["list"]] : [["list"]],
globalSetup: require.resolve("./src/test/e2e/utils/setup"),
globalTeardown: require.resolve("./src/test/e2e/utils/teardown"),
})
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
@@ -127,4 +127,4 @@ message OrganizationUsageTransaction {
int32 prompt_tokens = 10;
int32 total_tokens = 11;
string user_id = 12;
}
}
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
+1 -1
View File
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
+2 -10
View File
@@ -4,7 +4,7 @@ package host;
option java_package = "bot.cline.host.proto";
option java_multiple_files = true;
import "cline/common.proto";
import "common.proto";
// Provides methods for diff views.
service DiffService {
@@ -14,7 +14,6 @@ service DiffService {
rpc getDocumentText(GetDocumentTextRequest) returns (GetDocumentTextResponse);
// Replace a text selection in the diff.
rpc replaceText(ReplaceTextRequest) returns (ReplaceTextResponse);
rpc scrollDiff(ScrollDiffRequest) returns (ScrollDiffResponse);
// Truncate the diff document.
rpc truncateDocument(TruncateDocumentRequest) returns (TruncateDocumentResponse);
// Save the diff document.
@@ -55,17 +54,10 @@ message ReplaceTextRequest {
message ReplaceTextResponse {}
message ScrollDiffRequest {
optional string diff_id = 1;
optional int32 line = 2;
}
message ScrollDiffResponse {}
message TruncateDocumentRequest {
optional cline.Metadata metadata = 1;
optional string diff_id = 2;
optional int32 end_line = 3;
optional int32 end_line = 5;
}
message TruncateDocumentResponse {}
+4 -1
View File
@@ -4,7 +4,7 @@ package host;
option java_package = "bot.cline.host.proto";
option java_multiple_files = true;
import "cline/common.proto";
import "common.proto";
// Provides methods for working with the user's environment.
service EnvService {
@@ -13,4 +13,7 @@ service EnvService {
// Reads text from the system clipboard.
rpc clipboardReadText(cline.EmptyRequest) returns (cline.String);
// Opens a URL in the user's default browser or application.
rpc openExternal(cline.StringRequest) returns (cline.Empty);
}
+13
View File
@@ -0,0 +1,13 @@
syntax = "proto3";
package host;
option java_package = "bot.cline.host.proto";
option java_multiple_files = true;
import "common.proto";
// Host-specific file operations
service FileService {
// Select images and other files from the file system and returns as data URLs & paths respectively
rpc selectFiles(cline.BooleanRequest) returns (cline.StringArrays);
}
+1 -1
View File
@@ -4,7 +4,7 @@ package host;
option java_package = "bot.cline.host.proto";
option java_multiple_files = true;
import "cline/common.proto";
import "common.proto";
/**
* The watch service is only here as example of a streaming rpc in the host bridge.
+2 -20
View File
@@ -4,7 +4,7 @@ package host;
option java_package = "bot.cline.host.proto";
option java_multiple_files = true;
import "cline/common.proto";
import "common.proto";
// Provides methods for working with IDE windows and editors.
service WindowService {
@@ -15,8 +15,6 @@ service WindowService {
rpc showInputBox(ShowInputBoxRequest) returns (ShowInputBoxResponse);
rpc showSaveDialog(ShowSaveDialogRequest) returns (ShowSaveDialogResponse);
rpc openFile(OpenFileRequest) returns (OpenFileResponse);
rpc getOpenTabs(GetOpenTabsRequest) returns (GetOpenTabsResponse);
rpc getVisibleTabs(GetVisibleTabsRequest) returns (GetVisibleTabsResponse);
}
message ShowTextDocumentRequest {
@@ -112,21 +110,5 @@ message OpenFileRequest {
}
message OpenFileResponse {
// empty
}
message GetOpenTabsRequest {
// empty
}
message GetOpenTabsResponse {
repeated string paths = 1;
}
message GetVisibleTabsRequest {
// empty
}
message GetVisibleTabsResponse {
repeated string paths = 1;
bool success = 1;
}
+1 -1
View File
@@ -4,7 +4,7 @@ package host;
option java_package = "bot.cline.host.proto";
option java_multiple_files = true;
import "cline/common.proto";
import "common.proto";
// Provides methods for working with workspaces/projects.
service WorkspaceService {
+1 -1
View File
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
@@ -129,7 +129,6 @@ enum ApiProvider {
CLAUDE_CODE = 26;
MOONSHOT = 27;
HUGGINGFACE = 28;
HUAWEI_CLOUD_MAAS = 29;
}
// Model info for OpenAI-compatible models
@@ -230,7 +229,6 @@ message ModelsApiConfiguration {
optional string cline_account_id = 58;
optional string groq_api_key = 59;
optional string hugging_face_api_key = 60;
optional string huawei_cloud_maas_api_key = 61;
// Plan mode configurations
optional ApiProvider plan_mode_api_provider = 100;
@@ -257,8 +255,6 @@ message ModelsApiConfiguration {
optional OpenRouterModelInfo plan_mode_groq_model_info = 121;
optional string plan_mode_hugging_face_model_id = 122;
optional OpenRouterModelInfo plan_mode_hugging_face_model_info = 123;
optional string plan_mode_huawei_cloud_maas_model_id = 124;
optional OpenRouterModelInfo plan_mode_huawei_cloud_maas_model_info = 125;
// Act mode configurations
optional ApiProvider act_mode_api_provider = 200;
@@ -285,8 +281,6 @@ message ModelsApiConfiguration {
optional OpenRouterModelInfo act_mode_groq_model_info = 221;
optional string act_mode_hugging_face_model_id = 222;
optional OpenRouterModelInfo act_mode_hugging_face_model_info = 223;
optional string act_mode_huawei_cloud_maas_model_id = 224;
optional OpenRouterModelInfo act_mode_huawei_cloud_maas_model_info = 225;
repeated string favorited_model_ids = 300;
}
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
+18 -27
View File
@@ -1,19 +1,19 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
service StateService {
rpc getLatestState(EmptyRequest) returns (State);
rpc updateTerminalConnectionTimeout(UpdateTerminalConnectionTimeoutRequest) returns (UpdateTerminalConnectionTimeoutResponse);
rpc updateTerminalConnectionTimeout(Int64Request) returns (Int64);
rpc updateTerminalReuseEnabled(BooleanRequest) returns (Empty);
rpc updateDefaultTerminalProfile(StringRequest) returns (TerminalProfileUpdateResponse);
rpc getAvailableTerminalProfiles(EmptyRequest) returns (TerminalProfiles);
rpc subscribeToState(EmptyRequest) returns (stream State);
rpc toggleFavoriteModel(StringRequest) returns (Empty);
rpc resetState(ResetStateRequest) returns (Empty);
rpc togglePlanActModeProto(TogglePlanActModeRequest) returns (Boolean);
rpc togglePlanActMode(TogglePlanActModeRequest) returns (Boolean);
rpc updateAutoApprovalSettings(AutoApprovalSettingsRequest) returns (Empty);
rpc updateSettings(UpdateSettingsRequest) returns (Empty);
rpc updateTelemetrySetting(TelemetrySettingRequest) returns (Empty);
@@ -43,7 +43,7 @@ message TerminalProfileUpdateResponse {
message TogglePlanActModeRequest {
Metadata metadata = 1;
PlanActMode mode = 2;
ChatSettings chat_settings = 2;
optional ChatContent chat_content = 3;
}
@@ -52,6 +52,12 @@ enum PlanActMode {
ACT = 1;
}
message ChatSettings {
PlanActMode mode = 1;
optional string preferred_language = 2;
optional string open_ai_reasoning_effort = 3;
}
message ChatContent {
optional string message = 1;
repeated string images = 2;
@@ -102,14 +108,12 @@ message UpdateSettingsRequest {
optional bool plan_act_separate_models_setting = 4;
optional bool enable_checkpoints_setting = 5;
optional bool mcp_marketplace_enabled = 6;
optional int32 shell_integration_timeout = 8;
optional ChatSettings chat_settings = 7;
optional int64 shell_integration_timeout = 8;
optional bool terminal_reuse_enabled = 9;
optional bool mcp_responses_collapsed = 10;
optional string mcp_display_mode = 11;
optional int32 terminal_output_line_limit = 12;
optional PlanActMode mode = 13;
optional string preferred_language = 14;
optional string openai_reasoning_effort = 15;
optional int64 terminal_output_line_limit = 12;
}
// Complete API Configuration message
@@ -149,8 +153,8 @@ message ApiConfiguration {
optional string requesty_api_key = 32;
optional string together_api_key = 33;
optional string fireworks_api_key = 34;
optional int32 fireworks_model_max_completion_tokens = 35;
optional int32 fireworks_model_max_tokens = 36;
optional int64 fireworks_model_max_completion_tokens = 35;
optional int64 fireworks_model_max_tokens = 36;
optional string qwen_api_key = 37;
optional string doubao_api_key = 38;
optional string mistral_api_key = 39;
@@ -162,7 +166,7 @@ message ApiConfiguration {
optional string xai_api_key = 45;
optional string sambanova_api_key = 46;
optional string cerebras_api_key = 47;
optional int32 request_timeout_ms = 48;
optional int64 request_timeout_ms = 48;
optional string sap_ai_core_client_id = 49;
optional string sap_ai_core_client_secret = 50;
optional string sap_ai_resource_group = 51;
@@ -170,12 +174,11 @@ message ApiConfiguration {
optional string sap_ai_core_base_url = 53;
optional string moonshot_api_key = 54;
optional string moonshot_api_line = 55;
optional string huawei_cloud_maas_api_key = 56;
// Plan mode configurations
optional string plan_mode_api_provider = 100;
optional string plan_mode_api_model_id = 101;
optional int32 plan_mode_thinking_budget_tokens = 102;
optional int64 plan_mode_thinking_budget_tokens = 102;
optional string plan_mode_reasoning_effort = 103;
optional string plan_mode_vscode_lm_model_selector = 104; // JSON string
optional bool plan_mode_aws_bedrock_custom_selected = 105;
@@ -193,13 +196,11 @@ message ApiConfiguration {
optional string plan_mode_together_model_id = 117;
optional string plan_mode_fireworks_model_id = 118;
optional string plan_mode_sap_ai_core_model_id = 119;
optional string plan_mode_huawei_cloud_maas_model_id = 120;
optional string plan_mode_huawei_cloud_maas_model_info = 121;
// Act mode configurations
optional string act_mode_api_provider = 200;
optional string act_mode_api_model_id = 201;
optional int32 act_mode_thinking_budget_tokens = 202;
optional int64 act_mode_thinking_budget_tokens = 202;
optional string act_mode_reasoning_effort = 203;
optional string act_mode_vscode_lm_model_selector = 204; // JSON string
optional bool act_mode_aws_bedrock_custom_selected = 205;
@@ -217,8 +218,6 @@ message ApiConfiguration {
optional string act_mode_together_model_id = 217;
optional string act_mode_fireworks_model_id = 218;
optional string act_mode_sap_ai_core_model_id = 219;
optional string act_mode_huawei_cloud_maas_model_id = 220;
optional string act_mode_huawei_cloud_maas_model_info = 221;
// Favorited model IDs
repeated string favorited_model_ids = 300;
@@ -229,11 +228,3 @@ message ApiConfiguration {
optional string cline_account_id = 303;
}
message UpdateTerminalConnectionTimeoutRequest {
optional int32 timeout_ms = 1;
}
message UpdateTerminalConnectionTimeoutResponse {
optional int32 timeout_ms = 1;
}
+1 -1
View File
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
+1 -1
View File
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
+1 -1
View File
@@ -1,7 +1,7 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "common.proto";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
+3 -55
View File
@@ -7,10 +7,8 @@ import { globby } from "globby"
import { createRequire } from "module"
import os from "os"
import * as path from "path"
import { fileURLToPath } from "url"
import { rmrf } from "./file-utils.mjs"
import { main as generateProtoBusSetup } from "./generate-protobus-setup.mjs"
import { main as generateHostBridgeClient } from "./generate-host-bridge-client.mjs"
import { loadProtoDescriptorSet } from "./proto-utils.mjs"
const require = createRequire(import.meta.url)
const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc")
@@ -36,15 +34,10 @@ const TS_PROTO_OPTIONS = [
]
async function main() {
await cleanup()
await compileProtos()
await checkProtos()
await generateProtoBusSetup()
await generateHostBridgeClient()
}
async function compileProtos() {
console.log(chalk.bold.blue("Compiling Protocol Buffers..."))
await cleanup()
// Check for Apple Silicon compatibility before proceeding
checkAppleSiliconCompatibility()
@@ -187,51 +180,6 @@ function checkAppleSiliconCompatibility() {
}
}
const int64TypeNames = ["TYPE_INT64", "TYPE_UINT64", "TYPE_SINT64", "TYPE_FIXED64", "TYPE_SFIXED64"]
async function checkProtos() {
const proto = await loadProtoDescriptorSet()
const int64Fields = []
for (const [packageName, packageDef] of Object.entries(proto)) {
for (const [messageName, def] of Object.entries(packageDef)) {
// Skip service definitions
if (def && typeof def === "object" && "service" in def) {
continue
}
// Check message fields
if (def && def.type && def.type.field) {
for (const field of def.type.field) {
if (int64TypeNames.includes(field.type)) {
const name = `${packageName}.${messageName}.${field.name}`
int64Fields.push({
name: name,
type: field.type,
})
}
}
}
}
}
if (int64Fields.length > 0) {
console.log(chalk.yellow(`\nWarning: Found ${int64Fields.length} fields using 64-bit integer types`))
for (const field of int64Fields) {
const typeNames = {
TYPE_INT64: "int64",
TYPE_UINT64: "uint64",
TYPE_SINT64: "sint64",
TYPE_FIXED64: "fixed64",
TYPE_SFIXED64: "sfixed64",
}
log_verbose(chalk.yellow(` - ${field.name} (${typeNames[field.type]})`))
}
log_verbose(chalk.yellow("\nWARNING: 64-bit integer fields detected in proto definitions"))
log_verbose(chalk.yellow("JavaScript cannot safely represent integers larger than 2^53-1 (Number.MAX_SAFE_INTEGER)."))
log_verbose(chalk.yellow("Consider using string representation for large numbers or implementing BigInt support.\n"))
}
}
function log_verbose(s) {
if (process.argv.includes("-v") || process.argv.includes("--verbose")) {
console.log(s)
Executable → Regular
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env node
const { execSync } = require("child_process")
const esbuild = require("esbuild")
+6 -8
View File
@@ -15,7 +15,7 @@ const VSCODE_CLIENT_FILE = path.resolve("src/generated/hosts/vscode/hostbridge-g
/**
* Main function to generate the host bridge client
*/
export async function main() {
async function main() {
const { hostServices } = await loadServicesFromProtoDescriptor()
await generateTypesFile(hostServices)
@@ -234,10 +234,8 @@ const ${name}ServiceRegistry = createServiceRegistry("${name}")
${methods}`
}
// Only run main if this script is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
console.error(chalk.red("Error:"), error)
process.exit(1)
})
}
// Run the main function
main().catch((error) => {
console.error(chalk.red("Error:"), error)
process.exit(1)
})
+2 -8
View File
@@ -12,7 +12,7 @@ const STANDALONE_SERVER_SETUP_FILE = path.resolve("src/generated/hosts/standalon
const SCRIPT_NAME = path.relative(process.cwd(), fileURLToPath(import.meta.url))
export async function main() {
async function main() {
const { protobusServices } = await loadServicesFromProtoDescriptor()
await generateWebviewProtobusClients(protobusServices)
await generateVscodeServiceTypes(protobusServices)
@@ -205,10 +205,4 @@ function getDirName(serviceName) {
return domain.charAt(0).toLowerCase() + domain.slice(1)
}
// Only run main if this script is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
console.error(chalk.red("Error:"), error)
process.exit(1)
})
}
main()
+4 -8
View File
@@ -10,7 +10,7 @@ const DESCRIPTOR_SET = path.resolve("dist-standalone/proto/descriptor_set.pb")
const typeNameToFQN = new Map()
function addTypeNameToFqn(name, fqn) {
if (typeNameToFQN.has(name) && typeNameToFQN.get(name) !== fqn) {
if (typeNameToFQN.has(name)) {
throw new Error(`Proto type ${name} redefined (${fqn}).`)
}
typeNameToFQN.set(name, fqn)
@@ -23,15 +23,11 @@ export function getFqn(name) {
return typeNameToFQN.get(name)
}
export async function loadProtoDescriptorSet() {
const descriptorBuffer = await fs.readFile(DESCRIPTOR_SET)
const packageDefinition = protoLoader.loadFileDescriptorSetFromBuffer(descriptorBuffer)
return grpc.loadPackageDefinition(packageDefinition)
}
export async function loadServicesFromProtoDescriptor() {
// Load service definitions from descriptor set
const proto = await loadProtoDescriptorSet()
const descriptorBuffer = await fs.readFile(DESCRIPTOR_SET)
const packageDefinition = protoLoader.loadFileDescriptorSetFromBuffer(descriptorBuffer)
const proto = grpc.loadPackageDefinition(packageDefinition)
// Extract host services and proto messages from the proto definition
const hostServices = {}
Executable → Regular
View File
+3 -13
View File
@@ -1,5 +1,5 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { ApiConfiguration, ModelInfo, QwenApiRegions } from "../shared/api"
import { ApiConfiguration, ModelInfo } from "../shared/api"
import { AnthropicHandler } from "./providers/anthropic"
import { AwsBedrockHandler } from "./providers/bedrock"
import { OpenRouterHandler } from "./providers/openrouter"
@@ -29,9 +29,8 @@ import { SapAiCoreHandler } from "./providers/sapaicore"
import { ClaudeCodeHandler } from "./providers/claude-code"
import { MoonshotHandler } from "./providers/moonshot"
import { GroqHandler } from "./providers/groq"
import { Mode } from "@shared/storage/types"
import { Mode } from "../shared/ChatSettings"
import { HuggingFaceHandler } from "./providers/huggingface"
import { HuaweiCloudMaaSHandler } from "./providers/huawei-cloud-maas"
export interface ApiHandler {
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
@@ -167,8 +166,7 @@ function createHandlerForProvider(
case "qwen":
return new QwenHandler({
qwenApiKey: options.qwenApiKey,
qwenApiLine:
options.qwenApiLine === QwenApiRegions.INTERNATIONAL ? QwenApiRegions.INTERNATIONAL : QwenApiRegions.CHINA,
qwenApiLine: options.qwenApiLine,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
@@ -273,14 +271,6 @@ function createHandlerForProvider(
thinkingBudgetTokens:
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
})
case "huawei-cloud-maas":
return new HuaweiCloudMaaSHandler({
huaweiCloudMaasApiKey: options.huaweiCloudMaasApiKey,
huaweiCloudMaasModelId:
mode === "plan" ? options.planModeHuaweiCloudMaasModelId : options.actModeHuaweiCloudMaasModelId,
huaweiCloudMaasModelInfo:
mode === "plan" ? options.planModeHuaweiCloudMaasModelInfo : options.actModeHuaweiCloudMaasModelInfo,
})
default:
return new AnthropicHandler({
apiKey: options.apiKey,
+84 -85
View File
@@ -612,102 +612,101 @@ describe("AwsBedrockHandler", () => {
})
})
// TODO: Re-enable or remove these tests.
// describe("getModelId", () => {
// it("should return raw model ID for custom models", async () => {
// const customOptions: ApiHandlerOptions = {
// ...mockOptions,
// actModeAwsBedrockCustomSelected: true,
// actModeApiModelId:
// "arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
// }
// const customHandler = new AwsBedrockHandler(customOptions)
describe("getModelId", () => {
it("should return raw model ID for custom models", async () => {
const customOptions: ApiHandlerOptions = {
...mockOptions,
actModeAwsBedrockCustomSelected: true,
actModeApiModelId:
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
}
const customHandler = new AwsBedrockHandler(customOptions)
// const modelId = await customHandler.getModelId()
// modelId.should.equal(
// "arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
// )
// })
const modelId = await customHandler.getModelId()
modelId.should.equal(
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
)
})
// it("should not encode custom model IDs with slashes", async () => {
// const customOptions: ApiHandlerOptions = {
// ...mockOptions,
// actModeAwsBedrockCustomSelected: true,
// actModeApiModelId: "my-namespace/my-custom-model",
// }
// const customHandler = new AwsBedrockHandler(customOptions)
it("should not encode custom model IDs with slashes", async () => {
const customOptions: ApiHandlerOptions = {
...mockOptions,
actModeAwsBedrockCustomSelected: true,
actModeApiModelId: "my-namespace/my-custom-model",
}
const customHandler = new AwsBedrockHandler(customOptions)
// const modelId = await customHandler.getModelId()
// modelId.should.equal("my-namespace/my-custom-model")
// modelId.should.not.match(/%2F/)
// })
const modelId = await customHandler.getModelId()
modelId.should.equal("my-namespace/my-custom-model")
modelId.should.not.match(/%2F/)
})
// it("should apply cross-region prefix for non-custom models when enabled", async () => {
// const crossRegionOptions: ApiHandlerOptions = {
// ...mockOptions,
// awsUseCrossRegionInference: true,
// awsRegion: "us-west-2",
// }
// const crossRegionHandler = new AwsBedrockHandler(crossRegionOptions)
it("should apply cross-region prefix for non-custom models when enabled", async () => {
const crossRegionOptions: ApiHandlerOptions = {
...mockOptions,
awsUseCrossRegionInference: true,
awsRegion: "us-west-2",
}
const crossRegionHandler = new AwsBedrockHandler(crossRegionOptions)
// const modelId = await crossRegionHandler.getModelId()
// modelId.should.equal("us.anthropic.claude-3-7-sonnet-20250219-v1:0")
// })
const modelId = await crossRegionHandler.getModelId()
modelId.should.equal("us.anthropic.claude-3-7-sonnet-20250219-v1:0")
})
// it("should apply EU cross-region prefix", async () => {
// const euOptions: ApiHandlerOptions = {
// ...mockOptions,
// awsUseCrossRegionInference: true,
// awsRegion: "eu-central-1",
// }
// const euHandler = new AwsBedrockHandler(euOptions)
it("should apply EU cross-region prefix", async () => {
const euOptions: ApiHandlerOptions = {
...mockOptions,
awsUseCrossRegionInference: true,
awsRegion: "eu-central-1",
}
const euHandler = new AwsBedrockHandler(euOptions)
// const modelId = await euHandler.getModelId()
// modelId.should.equal("eu.anthropic.claude-3-7-sonnet-20250219-v1:0")
// })
const modelId = await euHandler.getModelId()
modelId.should.equal("eu.anthropic.claude-3-7-sonnet-20250219-v1:0")
})
// it("should apply APAC cross-region prefix", async () => {
// const apacOptions: ApiHandlerOptions = {
// ...mockOptions,
// awsUseCrossRegionInference: true,
// awsRegion: "ap-northeast-1",
// }
// const apacHandler = new AwsBedrockHandler(apacOptions)
it("should apply APAC cross-region prefix", async () => {
const apacOptions: ApiHandlerOptions = {
...mockOptions,
awsUseCrossRegionInference: true,
awsRegion: "ap-northeast-1",
}
const apacHandler = new AwsBedrockHandler(apacOptions)
// const modelId = await apacHandler.getModelId()
// modelId.should.equal("apac.anthropic.claude-3-7-sonnet-20250219-v1:0")
// })
const modelId = await apacHandler.getModelId()
modelId.should.equal("apac.anthropic.claude-3-7-sonnet-20250219-v1:0")
})
// it("should not apply cross-region prefix for custom models even when enabled", async () => {
// const customCrossRegionOptions: ApiHandlerOptions = {
// ...mockOptions,
// actModeAwsBedrockCustomSelected: true,
// actModeApiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
// awsUseCrossRegionInference: true,
// }
// const customCrossRegionHandler = new AwsBedrockHandler(customCrossRegionOptions)
it("should not apply cross-region prefix for custom models even when enabled", async () => {
const customCrossRegionOptions: ApiHandlerOptions = {
...mockOptions,
actModeAwsBedrockCustomSelected: true,
actModeApiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
awsUseCrossRegionInference: true,
}
const customCrossRegionHandler = new AwsBedrockHandler(customCrossRegionOptions)
// const modelId = await customCrossRegionHandler.getModelId()
// modelId.should.equal("arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model")
// })
const modelId = await customCrossRegionHandler.getModelId()
modelId.should.equal("arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model")
})
// it("should handle UltraThink model ARN correctly", async () => {
// const ultraThinkOptions: ApiHandlerOptions = {
// ...mockOptions,
// actModeAwsBedrockCustomSelected: true,
// actModeApiModelId:
// "arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
// actModeAwsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
// }
// const ultraThinkHandler = new AwsBedrockHandler(ultraThinkOptions)
it("should handle UltraThink model ARN correctly", async () => {
const ultraThinkOptions: ApiHandlerOptions = {
...mockOptions,
actModeAwsBedrockCustomSelected: true,
actModeApiModelId:
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
actModeAwsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
}
const ultraThinkHandler = new AwsBedrockHandler(ultraThinkOptions)
// const modelId = await ultraThinkHandler.getModelId()
// // Should return the raw ARN without any encoding
// modelId.should.equal(
// "arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
// )
// modelId.should.not.match(/%2F/)
// modelId.should.not.match(/%3A/)
// })
// })
const modelId = await ultraThinkHandler.getModelId()
// Should return the raw ARN without any encoding
modelId.should.equal(
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
)
modelId.should.not.match(/%2F/)
modelId.should.not.match(/%3A/)
})
})
})
+3 -10
View File
@@ -102,7 +102,6 @@ export class CerebrasHandler implements ApiHandler {
messages: cerebrasMessages,
temperature: 0,
stream: true,
max_tokens: this.getModel().info.maxTokens,
})
// Handle streaming response
@@ -176,15 +175,9 @@ export class CerebrasHandler implements ApiHandler {
}
getModel(): { id: string; info: ModelInfo } {
const originalModelId = this.options.apiModelId
let apiModelId = originalModelId
if (originalModelId === "qwen-3-coder-480b-free") {
apiModelId = "qwen-3-coder-480b"
return { id: apiModelId, info: cerebrasModels[originalModelId as CerebrasModelId] }
}
if (originalModelId && originalModelId in cerebrasModels) {
const id = originalModelId as CerebrasModelId
const modelId = this.options.apiModelId
if (modelId && modelId in cerebrasModels) {
const id = modelId as CerebrasModelId
return { id, info: cerebrasModels[id] }
}
return {
-53
View File
@@ -1,53 +0,0 @@
// Mock for @google/genai module to avoid ESM compatibility issues in tests
export class GoogleGenAI {
constructor(options: any) {
// Mock constructor
}
models = {
generateContentStream: async (params: any) => {
// Mock implementation that returns an async iterator
return {
async *[Symbol.asyncIterator]() {
yield {
text: "Mock response",
candidates: [],
usageMetadata: {
promptTokenCount: 100,
candidatesTokenCount: 50,
thoughtsTokenCount: 0,
cachedContentTokenCount: 0,
},
}
},
}
},
countTokens: async (params: any) => {
// Mock token counting
return {
totalTokens: 100,
}
},
}
}
// Export mock types
export interface GenerateContentConfig {
httpOptions?: any
systemInstruction?: string
temperature?: number
thinkingConfig?: any
}
export interface GenerateContentResponseUsageMetadata {
promptTokenCount?: number
candidatesTokenCount?: number
thoughtsTokenCount?: number
cachedContentTokenCount?: number
}
export interface Part {
thought?: boolean
text?: string
}
-132
View File
@@ -1,132 +0,0 @@
import { ApiHandler } from ".."
import { huaweiCloudMaasDefaultModelId, HuaweiCloudMaasModelId, huaweiCloudMaasModels, ModelInfo } from "@shared/api"
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import { withRetry } from "../retry"
interface HuaweiCloudMaaSHandlerOptions {
huaweiCloudMaasApiKey?: string
huaweiCloudMaasModelId?: string
huaweiCloudMaasModelInfo?: ModelInfo
}
export class HuaweiCloudMaaSHandler implements ApiHandler {
private options: HuaweiCloudMaaSHandlerOptions
private client: OpenAI | undefined
constructor(options: HuaweiCloudMaaSHandlerOptions) {
this.options = options
}
private ensureClient(): OpenAI {
if (!this.client) {
if (!this.options.huaweiCloudMaasApiKey) {
throw new Error("Huawei Cloud MaaS API key is required")
}
try {
this.client = new OpenAI({
baseURL: "https://api.modelarts-maas.com/v1/",
apiKey: this.options.huaweiCloudMaasApiKey,
})
} catch (error) {
throw new Error(`Error creating Huawei Cloud MaaS client: ${error.message}`)
}
}
return this.client
}
getModel(): { id: HuaweiCloudMaasModelId; info: ModelInfo } {
// First priority: huaweiCloudMaasModelId and huaweiCloudMaasModelInfo (like Groq does)
const huaweiCloudMaasModelId = this.options.huaweiCloudMaasModelId
const huaweiCloudMaasModelInfo = this.options.huaweiCloudMaasModelInfo
if (huaweiCloudMaasModelId && huaweiCloudMaasModelInfo) {
return { id: huaweiCloudMaasModelId as HuaweiCloudMaasModelId, info: huaweiCloudMaasModelInfo }
}
// Second priority: huaweiCloudMaasModelId with static model info
if (huaweiCloudMaasModelId && huaweiCloudMaasModelId in huaweiCloudMaasModels) {
const id = huaweiCloudMaasModelId as HuaweiCloudMaasModelId
return { id, info: huaweiCloudMaasModels[id] }
}
// Default fallback
return {
id: huaweiCloudMaasDefaultModelId,
info: huaweiCloudMaasModels[huaweiCloudMaasDefaultModelId],
}
}
@withRetry()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const client = this.ensureClient()
const model = this.getModel()
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
]
const stream = await client.chat.completions.create({
model: model.id,
max_completion_tokens: model.info.maxTokens,
messages: openAiMessages,
stream: true,
stream_options: { include_usage: true },
temperature: 0,
})
let reasoning: string | null = null
let didOutputUsage: boolean = false
let finalUsage: any = null
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
// Handle reasoning content detection
if (delta?.content) {
if (reasoning || delta.content.includes("<think>")) {
reasoning = (reasoning || "") + delta.content
} else if (!reasoning) {
yield {
type: "text",
text: delta.content,
}
}
}
// Handle reasoning output
if (reasoning || (delta && "reasoning_content" in delta && delta.reasoning_content)) {
const reasoningContent = delta?.content || ((delta as any)?.reasoning_content as string | undefined) || ""
if (reasoningContent.trim()) {
yield {
type: "reasoning",
reasoning: reasoningContent,
}
}
// Check if reasoning is complete
if (reasoning?.includes("</think>")) {
reasoning = null
}
}
// Store usage information for later output
if (chunk.usage) {
finalUsage = chunk.usage
}
// Output usage when stream is finished
if (!didOutputUsage && chunk.choices?.[0]?.finish_reason) {
if (finalUsage) {
yield {
type: "usage",
inputTokens: finalUsage.prompt_tokens || 0,
outputTokens: finalUsage.completion_tokens || 0,
cacheWriteTokens: 0,
cacheReadTokens: 0,
}
}
didOutputUsage = true
}
}
}
}
+7 -15
View File
@@ -9,7 +9,6 @@ import {
internationalQwenDefaultModelId,
MainlandQwenModelId,
InternationalQwenModelId,
QwenApiRegions,
} from "@shared/api"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
@@ -18,7 +17,7 @@ import { withRetry } from "../retry"
interface QwenHandlerOptions {
qwenApiKey?: string
qwenApiLine?: QwenApiRegions
qwenApiLine?: string
apiModelId?: string
thinkingBudgetTokens?: number
}
@@ -28,15 +27,7 @@ export class QwenHandler implements ApiHandler {
private client: OpenAI | undefined
constructor(options: QwenHandlerOptions) {
// Ensure options start with defaults but allow overrides
this.options = {
qwenApiLine: QwenApiRegions.CHINA,
...options,
}
}
private useChinaApi(): boolean {
return this.options.qwenApiLine === QwenApiRegions.CHINA
this.options = options
}
private ensureClient(): OpenAI {
@@ -46,9 +37,10 @@ export class QwenHandler implements ApiHandler {
}
try {
this.client = new OpenAI({
baseURL: this.useChinaApi()
? "https://dashscope.aliyuncs.com/compatible-mode/v1"
: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
baseURL:
this.options.qwenApiLine === "china"
? "https://dashscope.aliyuncs.com/compatible-mode/v1"
: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
apiKey: this.options.qwenApiKey,
})
} catch (error: any) {
@@ -61,7 +53,7 @@ export class QwenHandler implements ApiHandler {
getModel(): { id: MainlandQwenModelId | InternationalQwenModelId; info: ModelInfo } {
const modelId = this.options.apiModelId
// Branch based on API line to let poor typescript know what to do
if (this.useChinaApi()) {
if (this.options.qwenApiLine === "china") {
return {
id: (modelId as MainlandQwenModelId) ?? mainlandQwenDefaultModelId,
info: mainlandQwenModels[modelId as MainlandQwenModelId] ?? mainlandQwenModels[mainlandQwenDefaultModelId],
+14 -30
View File
@@ -1,13 +1,13 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { ApiStream } from "@api/transform/stream"
import { convertToVsCodeLmMessages } from "@api/transform/vscode-lm-format"
import { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "@shared/vsCodeSelectorUtils"
import { calculateApiCostAnthropic } from "@utils/cost"
import * as vscode from "vscode"
import { ApiHandler, SingleCompletionHandler } from "../"
import { withRetry } from "../retry"
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 { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
import type { LanguageModelChatSelector as LanguageModelChatSelectorFromTypes } from "./types"
import { withRetry } from "../retry"
interface VsCodeLmHandlerOptions {
vsCodeLmModelSelector?: any
@@ -237,28 +237,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
}
}
private extractTextFromMessage(message: vscode.LanguageModelChatMessage): string {
if (Array.isArray(message.content)) {
return message.content
.filter((part) => part instanceof vscode.LanguageModelTextPart)
.map((part) => (part as vscode.LanguageModelTextPart).value)
.join("")
}
return ""
}
private isClaudeModel(): boolean {
return this.client?.family?.startsWith("claude") || false
}
private async countTokens(text: string | vscode.LanguageModelChatMessage): Promise<number> {
// For Claude models, use character-to-token ratio instead of VSCode LM's inaccurate counting
if (this.isClaudeModel()) {
const textContent = typeof text === "string" ? text : this.extractTextFromMessage(text)
// Use 4 character-to-token ratio for Claude models
return Math.ceil(textContent.length / 4)
}
// Check for required dependencies
if (!this.client) {
console.warn("Cline <Language Model API>: No client available for token counting")
@@ -325,10 +304,15 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
}
}
private async calculateTotalInputTokens(vsCodeLmMessages: vscode.LanguageModelChatMessage[]): Promise<number> {
private async calculateTotalInputTokens(
systemPrompt: string,
vsCodeLmMessages: vscode.LanguageModelChatMessage[],
): Promise<number> {
const systemTokens: number = await this.countTokens(systemPrompt)
const messageTokens: number[] = await Promise.all(vsCodeLmMessages.map((msg) => this.countTokens(msg)))
return messageTokens.reduce((sum: number, tokens: number): number => sum + tokens, 0)
return systemTokens + messageTokens.reduce((sum: number, tokens: number): number => sum + tokens, 0)
}
private ensureCleanState(): void {
@@ -450,7 +434,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
this.currentRequestCancellation = new vscode.CancellationTokenSource()
// Calculate input tokens before starting the stream
const totalInputTokens: number = await this.calculateTotalInputTokens(vsCodeLmMessages)
const totalInputTokens: number = await this.calculateTotalInputTokens(systemPrompt, vsCodeLmMessages)
// Accumulate the text and count at the end of the stream to reduce token counting overhead.
let accumulatedText: string = ""
@@ -60,7 +60,6 @@ describe("FileContextTracker", () => {
((_) => {}) as WebviewProviderCreator,
(() => {}) as DiffViewProviderCreator,
vscodeHostBridgeClient,
(_) => {},
)
// Create tracker instance
@@ -1,6 +1,6 @@
import { Controller } from "../index"
import { AuthService } from "@/services/auth/AuthService"
import { EmptyRequest, String } from "@shared/proto/cline/common"
import { EmptyRequest, String } from "../../../shared/proto/common"
const authService = AuthService.getInstance()
@@ -1,6 +1,6 @@
import { AuthService } from "@/services/auth/AuthService"
import { Empty } from "@shared/proto/cline/common"
import type { EmptyRequest } from "@shared/proto/cline/common"
import { Empty } from "../../../shared/proto/common"
import type { EmptyRequest } from "../../../shared/proto/common"
import type { Controller } from "../index"
const authService = AuthService.getInstance()
@@ -1,4 +1,4 @@
import { AuthStateChangedRequest, AuthState } from "@shared/proto/cline/account"
import { AuthStateChangedRequest, AuthState } from "@shared/proto/account"
import type { Controller } from "../index"
import { updateGlobalState } from "../../storage/state"
@@ -1,5 +1,5 @@
import type { Controller } from "../index"
import { GetOrganizationCreditsRequest, OrganizationCreditsData, OrganizationUsageTransaction } from "@shared/proto/cline/account"
import { GetOrganizationCreditsRequest, OrganizationCreditsData, OrganizationUsageTransaction } from "@shared/proto/account"
/**
* Handles fetching all organization credits data (balance, usage, payments)
@@ -22,11 +22,6 @@ export async function getOrganizationCredits(
controller.accountService.fetchOrganizationUsageTransactionsRPC(request.organizationId),
])
// If balance call fails (returns undefined), throw an error
if (!balanceData) {
throw new Error("Failed to fetch organization credits data")
}
return OrganizationCreditsData.create({
balance: balanceData ? { currentBalance: balanceData.balance / 100 } : { currentBalance: 0 },
organizationId: balanceData?.organizationId || "",
@@ -1,6 +1,6 @@
import type { Controller } from "../index"
import type { EmptyRequest } from "@shared/proto/cline/common"
import { UserCreditsData } from "@shared/proto/cline/account"
import type { EmptyRequest } from "@shared/proto/common"
import { UserCreditsData } from "@shared/proto/account"
/**
* Handles fetching all user credits data (balance, usage, payments)
@@ -21,11 +21,6 @@ export async function getUserCredits(controller: Controller, request: EmptyReque
controller.accountService.fetchPaymentTransactionsRPC(),
])
// If either call fails (returns undefined), throw an error
if (balance === undefined) {
throw new Error("Failed to fetch user credits data")
}
return UserCreditsData.create({
balance: balance ? { currentBalance: balance.balance / 100 } : { currentBalance: 0 },
usageTransactions: usageTransactions,
@@ -1,6 +1,6 @@
import type { Controller } from "../index"
import type { EmptyRequest } from "@shared/proto/cline/common"
import { UserOrganization, UserOrganizationsResponse } from "@shared/proto/cline/account"
import type { EmptyRequest } from "@shared/proto/common"
import { UserOrganization, UserOrganizationsResponse } from "@shared/proto/account"
/**
* Handles fetching all user credits data (balance, usage, payments)
@@ -1,6 +1,6 @@
import type { Controller } from "../index"
import { Empty } from "@shared/proto/cline/common"
import { UserOrganizationUpdateRequest } from "@shared/proto/cline/account"
import { Empty } from "@shared/proto/common"
import { UserOrganizationUpdateRequest } from "@shared/proto/account"
/**
* Handles setting the user's active organization
@@ -1,5 +1,5 @@
import { BrowserConnection } from "@shared/proto/cline/browser"
import { EmptyRequest } from "@shared/proto/cline/common"
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"
@@ -1,5 +1,5 @@
import { BrowserConnectionInfo } from "@shared/proto/cline/browser"
import { EmptyRequest } from "@shared/proto/cline/common"
import { BrowserConnectionInfo } from "@shared/proto/browser"
import { EmptyRequest } from "@shared/proto/common"
import { Controller } from "../index"
import { getAllExtensionState } from "@core/storage/state"
@@ -1,5 +1,5 @@
import { ChromePath } from "@shared/proto/cline/browser"
import { EmptyRequest } from "@shared/proto/cline/common"
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"
@@ -1,4 +1,4 @@
import { EmptyRequest, String as StringMessage } from "@shared/proto/cline/common"
import { EmptyRequest, String as StringMessage } from "../../../shared/proto/common"
import { Controller } from "../index"
import { BrowserSession } from "../../../services/browser/BrowserSession"
@@ -1,5 +1,5 @@
import { BrowserConnection } from "@shared/proto/cline/browser"
import { StringRequest } from "@shared/proto/cline/common"
import { BrowserConnection } from "@shared/proto/browser"
import { StringRequest } from "@shared/proto/common"
import { Controller } from "../index"
import { getAllExtensionState } from "@core/storage/state"
import { BrowserSession } from "@services/browser/BrowserSession"
@@ -1,5 +1,5 @@
import { UpdateBrowserSettingsRequest } from "@shared/proto/cline/browser"
import { Boolean } from "@shared/proto/cline/common"
import { UpdateBrowserSettingsRequest } from "../../../shared/proto/browser"
import { Boolean } from "../../../shared/proto/common"
import { Controller } from "../index"
import { updateGlobalState, getGlobalState } from "../../storage/state"
import { BrowserSettings as SharedBrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../../shared/BrowserSettings"
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { Empty, Int64Request } from "@shared/proto/cline/common"
import { Empty, Int64Request } from "@shared/proto/common"
export async function checkpointDiff(controller: Controller, request: Int64Request): Promise<Empty> {
if (request.value) {
@@ -1,8 +1,8 @@
import { HostProvider } from "@/hosts/host-provider"
import { Controller } from ".."
import { ClineCheckpointRestore } from "../../../shared/WebviewMessage"
import { CheckpointRestoreRequest } from "@shared/proto/cline/checkpoints"
import { Empty } from "@shared/proto/cline/common"
import { CheckpointRestoreRequest } from "../../../shared/proto/checkpoints"
import { Empty } from "../../../shared/proto/common"
import pWaitFor from "p-wait-for"
import { ShowMessageType } from "@/shared/proto/index.host"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { Empty, StringRequest } from "@shared/proto/cline/common"
import { Empty, StringRequest } from "@shared/proto/common"
import { writeTextToClipboard } from "@/utils/env"
/**
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { RuleFileRequest, RuleFile } from "@shared/proto/cline/file"
import { RuleFileRequest, RuleFile } from "@shared/proto/file"
import { refreshClineRulesToggles } from "@core/context/instructions/user-instructions/cline-rules"
import { createRuleFile as createRuleFileImpl } from "@core/context/instructions/user-instructions/rule-helpers"
import * as path from "path"
+1 -1
View File
@@ -1,5 +1,5 @@
import { deleteRuleFile as deleteRuleFileImpl } from "@core/context/instructions/user-instructions/rule-helpers"
import { RuleFile, RuleFileRequest } from "@shared/proto/cline/file"
import { RuleFile, RuleFileRequest } from "@shared/proto/file"
import * as path from "path"
import { Controller } from ".."
import { HostProvider } from "@/hosts/host-provider"
+1 -1
View File
@@ -1,5 +1,5 @@
import { asRelativePath } from "@/utils/path"
import { RelativePaths, RelativePathsRequest } from "@shared/proto/cline/file"
import { RelativePaths, RelativePathsRequest } from "@shared/proto/file"
import * as path from "path"
import { URI } from "vscode-uri"
import { Controller } from ".."
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { Empty, StringRequest } from "@shared/proto/cline/common"
import { Empty, StringRequest } from "@shared/proto/common"
import { openFile as openFileIntegration } from "@integrations/misc/open-file"
/**
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { Empty, StringRequest } from "@shared/proto/cline/common"
import { Empty, StringRequest } from "@shared/proto/common"
import { openImage as openImageIntegration } from "@integrations/misc/open-file"
/**
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { Empty, StringRequest } from "@shared/proto/cline/common"
import { Empty, StringRequest } from "../../../shared/proto/common"
import { openMention as coreOpenMention } from "../../mentions"
/**
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { Empty, StringRequest } from "@shared/proto/cline/common"
import { Empty, StringRequest } from "@shared/proto/common"
import { openFile as openFileIntegration } from "@integrations/misc/open-file"
import path from "path"
/**
+2 -2
View File
@@ -1,5 +1,5 @@
import { EmptyRequest } from "@shared/proto/cline/common"
import { RefreshedRules } from "@shared/proto/cline/file"
import { EmptyRequest } from "@shared/proto/common"
import { RefreshedRules } from "@shared/proto/file"
import type { Controller } from "../index"
import { refreshClineRulesToggles } from "@core/context/instructions/user-instructions/cline-rules"
import { refreshExternalRulesToggles } from "@core/context/instructions/user-instructions/external-rules"
+2 -2
View File
@@ -1,6 +1,6 @@
import { Controller } from ".."
import { GitCommits } from "@shared/proto/cline/file"
import { StringRequest } from "@shared/proto/cline/common"
import { GitCommits } from "@shared/proto/file"
import { StringRequest } from "@shared/proto/common"
import { searchCommits as searchCommitsUtil } from "@utils/git"
import { getWorkspacePath } from "@utils/path"
import { convertGitCommitsToProtoGitCommits } from "@shared/proto-conversions/file/git-commit-conversion"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { FileSearchRequest, FileSearchResults } from "@shared/proto/cline/file"
import { FileSearchRequest, FileSearchResults } from "@shared/proto/file"
import { searchWorkspaceFiles } from "@services/search/file-search"
import { getWorkspacePath } from "@utils/path"
import { convertSearchResultsToProtoFileInfos } from "@shared/proto-conversions/file/search-result-conversion"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { BooleanRequest, StringArrays } from "@shared/proto/cline/common"
import { BooleanRequest, StringArrays } from "@shared/proto/common"
import { selectFiles as selectFilesIntegration } from "@integrations/misc/process-files"
/**
@@ -1,5 +1,5 @@
import { Controller } from "../index"
import { EmptyRequest, StringArray } from "@shared/proto/cline/common"
import { EmptyRequest, StringArray } from "@shared/proto/common"
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
// Keep track of active subscriptions
+2 -2
View File
@@ -1,5 +1,5 @@
import { ToggleClineRules } from "@shared/proto/cline/file"
import type { ToggleClineRuleRequest } from "@shared/proto/cline/file"
import { ToggleClineRules } from "../../../shared/proto/file"
import type { ToggleClineRuleRequest } from "../../../shared/proto/file"
import type { Controller } from "../index"
import { getGlobalState, getWorkspaceState, updateGlobalState, updateWorkspaceState } from "../../../core/storage/state"
import { ClineRulesToggles as AppClineRulesToggles } from "@shared/cline-rules"
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToggleCursorRuleRequest } from "@shared/proto/cline/file"
import { ClineRulesToggles } from "@shared/proto/cline/file"
import type { ToggleCursorRuleRequest } from "../../../shared/proto/file"
import { ClineRulesToggles } from "../../../shared/proto/file"
import type { Controller } from "../index"
import { getWorkspaceState, updateWorkspaceState } from "../../../core/storage/state"
import { ClineRulesToggles as AppClineRulesToggles } from "@shared/cline-rules"
@@ -1,5 +1,5 @@
import type { ToggleWindsurfRuleRequest } from "@shared/proto/cline/file"
import { ClineRulesToggles } from "@shared/proto/cline/file"
import type { ToggleWindsurfRuleRequest } from "../../../shared/proto/file"
import { ClineRulesToggles } from "../../../shared/proto/file"
import type { Controller } from "../index"
import { getWorkspaceState, updateWorkspaceState } from "../../../core/storage/state"
import { ClineRulesToggles as AppClineRulesToggles } from "@shared/cline-rules"
+2 -2
View File
@@ -1,6 +1,6 @@
import { Controller } from ".."
import { Metadata } from "@shared/proto/cline/common"
import { ToggleWorkflowRequest, ClineRulesToggles } from "@shared/proto/cline/file"
import { Metadata } from "../../../shared/proto/common"
import { ToggleWorkflowRequest, ClineRulesToggles } from "../../../shared/proto/file"
import { getWorkspaceState, updateWorkspaceState, getGlobalState, updateGlobalState } from "../../../core/storage/state"
import { ClineRulesToggles as AppClineRulesToggles } from "../../../shared/cline-rules"
-1
View File
@@ -53,7 +53,6 @@ export class GrpcHandler {
request_id: requestId,
}
} catch (error) {
console.log("Protobus error:", error)
return {
error: error instanceof Error ? error.message : String(error),
request_id: requestId,
+110 -127
View File
@@ -13,7 +13,7 @@ import { ClineAccountService } from "@services/account/ClineAccountService"
import { McpHub } from "@services/mcp/McpHub"
import { ApiProvider, ModelInfo } from "@shared/api"
import { ChatContent } from "@shared/ChatContent"
import { Mode } from "@shared/storage/types"
import { ChatSettings, Mode, StoredChatSettings } from "@shared/ChatSettings"
import { ClineRulesToggles } from "@shared/cline-rules"
import { ExtensionMessage, ExtensionState, Platform } from "@shared/ExtensionMessage"
import { HistoryItem } from "@shared/HistoryItem"
@@ -30,13 +30,11 @@ import * as path from "path"
import * as vscode from "vscode"
import { ensureMcpServersDirectoryExists, ensureSettingsDirectoryExists, GlobalFileNames } from "../storage/disk"
import { getAllExtensionState, getGlobalState, getWorkspaceState, storeSecret, updateGlobalState } from "../storage/state"
import { CacheService, PersistenceErrorEvent } from "../storage/CacheService"
import { Task } from "../task"
import { handleGrpcRequest, handleGrpcRequestCancel } from "./grpc-handler"
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
import { sendStateUpdate } from "./state/subscribeToState"
import { sendAddToInputEvent } from "./ui/subscribeToAddToInput"
import { getLatestAnnouncementId } from "@/utils/announcements"
/*
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -54,49 +52,20 @@ export class Controller {
workspaceTracker: WorkspaceTracker
mcpHub: McpHub
accountService: ClineAccountService
readonly cacheService: CacheService
authService: AuthService
get latestAnnouncementId(): string {
return this.context.extension?.packageJSON?.version?.split(".").slice(0, 2).join(".") ?? ""
}
constructor(
readonly context: vscode.ExtensionContext,
private readonly outputChannel: vscode.OutputChannel,
postMessage: (message: ExtensionMessage) => Thenable<boolean> | undefined,
id: string,
) {
this.id = id
HostProvider.get().logToChannel("ClineProvider instantiated")
this.outputChannel.appendLine("ClineProvider instantiated")
this.postMessage = postMessage
this.accountService = ClineAccountService.getInstance()
this.cacheService = new CacheService(context)
const authService = AuthService.getInstance(this)
// Initialize cache service asynchronously - critical for extension functionality
this.cacheService
.initialize()
.then(() => {
authService.restoreRefreshTokenAndRetrieveAuthInfo()
})
.catch((error) => {
console.error("CRITICAL: Failed to initialize CacheService - extension may not function properly:", error)
})
// Set up persistence error recovery
this.cacheService.onPersistenceError = async ({ error }: PersistenceErrorEvent) => {
console.error("Cache persistence failed, recovering:", error)
try {
await this.cacheService.reInitialize()
await this.postStateToWebview()
HostProvider.window.showMessage({
type: ShowMessageType.WARNING,
message: "Saving settings to storage failed.",
})
} catch (recoveryError) {
console.error("Cache recovery failed:", recoveryError)
HostProvider.window.showMessage({
type: ShowMessageType.ERROR,
message: "Failed to save settings. Please restart the extension.",
})
}
}
this.workspaceTracker = new WorkspaceTracker()
this.mcpHub = new McpHub(
@@ -105,9 +74,12 @@ export class Controller {
(msg) => this.postMessageToWebview(msg),
this.context.extension?.packageJSON?.version ?? "1.0.0",
)
this.accountService = ClineAccountService.getInstance()
this.authService = AuthService.getInstance(context)
this.authService.restoreRefreshTokenAndRetrieveAuthInfo()
// Clean up legacy checkpoints
cleanupLegacyCheckpoints(this.context.globalStorageUri.fsPath).catch((error) => {
cleanupLegacyCheckpoints(this.context.globalStorageUri.fsPath, this.outputChannel).catch((error) => {
console.error("Failed to cleanup legacy checkpoints:", error)
})
}
@@ -139,18 +111,12 @@ export class Controller {
async handleSignOut() {
try {
// TODO: update to clineAccountId and then move clineApiKey to a clear function.
this.cacheService.setSecret("clineAccountId", undefined)
await storeSecret(this.context, "clineAccountId", undefined)
await updateGlobalState(this.context, "userInfo", undefined)
// Update API providers through cache service
const apiConfiguration = this.cacheService.getApiConfiguration()
const updatedConfig = {
...apiConfiguration,
planModeApiProvider: "openrouter" as ApiProvider,
actModeApiProvider: "openrouter" as ApiProvider,
}
this.cacheService.setApiConfiguration(updatedConfig)
await Promise.all([
updateGlobalState(this.context, "planModeApiProvider", "openrouter"),
updateGlobalState(this.context, "actModeApiProvider", "openrouter"),
])
await this.postStateToWebview()
HostProvider.window.showMessage({
type: ShowMessageType.INFORMATION,
@@ -170,16 +136,11 @@ export class Controller {
async initTask(task?: string, images?: string[], files?: string[], historyItem?: HistoryItem) {
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
// Get API configuration from cache for immediate access
const apiConfiguration = this.cacheService.getApiConfiguration()
const {
apiConfiguration,
autoApprovalSettings,
browserSettings,
preferredLanguage,
openaiReasoningEffort,
mode,
chatSettings: storedChatSettings,
shellIntegrationTimeout,
terminalReuseEnabled,
terminalOutputLineLimit,
@@ -189,6 +150,15 @@ export class Controller {
taskHistory,
} = await getAllExtensionState(this.context)
// Get current mode using helper function
const currentMode = await this.getCurrentMode()
// Reconstruct ChatSettings with mode from global state and stored preferences
const chatSettings: ChatSettings = {
...storedChatSettings, // Spread stored preferences (preferredLanguage, openAIReasoningEffort)
mode: currentMode, // Use mode from global state
}
const NEW_USER_TASK_COUNT_THRESHOLD = 10
// Check if the user has completed enough tasks to no longer be considered a "new user"
@@ -215,16 +185,13 @@ export class Controller {
apiConfiguration,
autoApprovalSettings,
browserSettings,
preferredLanguage,
openaiReasoningEffort,
mode,
chatSettings,
shellIntegrationTimeout,
terminalReuseEnabled ?? true,
terminalOutputLineLimit ?? 500,
defaultTerminalProfile ?? "default",
enableCheckpointsSetting ?? true,
await getCwd(getDesktopDir()),
this.cacheService,
task,
images,
files,
@@ -281,25 +248,28 @@ export class Controller {
await this.postStateToWebview()
}
async togglePlanActMode(modeToSwitchTo: Mode, chatContent?: ChatContent): Promise<boolean> {
const didSwitchToActMode = modeToSwitchTo === "act"
async togglePlanActModeWithChatSettings(chatSettings: ChatSettings, chatContent?: ChatContent): Promise<boolean> {
const didSwitchToActMode = chatSettings.mode === "act"
// Store mode to global state
await updateGlobalState(this.context, "mode", modeToSwitchTo)
await updateGlobalState(this.context, "mode", chatSettings.mode)
// Capture mode switch telemetry | Capture regardless of if we know the taskId
telemetryService.captureModeSwitch(this.task?.taskId ?? "0", modeToSwitchTo)
telemetryService.captureModeSwitch(this.task?.taskId ?? "0", chatSettings.mode)
// Update API handler with new mode (buildApiHandler now selects provider based on mode)
if (this.task) {
const apiConfiguration = this.cacheService.getApiConfiguration()
this.task.api = buildApiHandler({ ...apiConfiguration, taskId: this.task.taskId }, modeToSwitchTo)
const { apiConfiguration } = await getAllExtensionState(this.context)
this.task.api = buildApiHandler({ ...apiConfiguration, taskId: this.task.taskId }, chatSettings.mode)
}
// Save only non-mode properties to global storage
const { mode, ...persistentChatSettings }: { mode: string } & StoredChatSettings = chatSettings
await updateGlobalState(this.context, "chatSettings", persistentChatSettings)
await this.postStateToWebview()
if (this.task) {
this.task.mode = modeToSwitchTo
this.task.chatSettings = chatSettings
if (this.task.taskState.isAwaitingPlanResponse && didSwitchToActMode) {
this.task.taskState.didRespondToPlanAskBySwitchingMode = true
// Use chatContent if provided, otherwise use default message
@@ -351,7 +321,7 @@ export class Controller {
async handleAuthCallback(customToken: string, provider: string | null = null) {
try {
await AuthService.getInstance(this).handleAuthCallback(customToken, provider ? provider : "google")
await this.authService.handleAuthCallback(customToken, provider ? provider : "google")
const clineProvider: ApiProvider = "cline"
@@ -359,26 +329,27 @@ export class Controller {
const { planActSeparateModelsSetting } = await getAllExtensionState(this.context)
const currentMode = await this.getCurrentMode()
// Get current API configuration from cache
const currentApiConfiguration = this.cacheService.getApiConfiguration()
let updatedConfig = { ...currentApiConfiguration }
if (planActSeparateModelsSetting) {
// Only update the current mode's provider
if (currentMode === "plan") {
updatedConfig.planModeApiProvider = clineProvider
await updateGlobalState(this.context, "planModeApiProvider", clineProvider)
} else {
updatedConfig.actModeApiProvider = clineProvider
await updateGlobalState(this.context, "actModeApiProvider", clineProvider)
}
} else {
// Update both modes to keep them in sync
updatedConfig.planModeApiProvider = clineProvider
updatedConfig.actModeApiProvider = clineProvider
await Promise.all([
updateGlobalState(this.context, "planModeApiProvider", clineProvider),
updateGlobalState(this.context, "actModeApiProvider", clineProvider),
])
}
// Update the API configuration through cache service
this.cacheService.setApiConfiguration(updatedConfig)
// Get the updated API configuration (now includes the updated providers)
const { apiConfiguration } = await getAllExtensionState(this.context)
const updatedConfig = {
...apiConfiguration,
apiProvider: clineProvider,
}
// Mark welcome view as completed since user has successfully logged in
await updateGlobalState(this.context, "welcomeViewCompleted", true)
@@ -540,20 +511,21 @@ export class Controller {
const openrouter: ApiProvider = "openrouter"
const currentMode = await this.getCurrentMode()
// Update API configuration through cache service
const currentApiConfiguration = this.cacheService.getApiConfiguration()
const updatedConfig = {
...currentApiConfiguration,
planModeApiProvider: openrouter,
actModeApiProvider: openrouter,
openRouterApiKey: apiKey,
}
this.cacheService.setApiConfiguration(updatedConfig)
await Promise.all([
updateGlobalState(this.context, "planModeApiProvider", openrouter),
updateGlobalState(this.context, "actModeApiProvider", openrouter),
])
await storeSecret(this.context, "openRouterApiKey", apiKey)
await this.postStateToWebview()
if (this.task) {
this.task.api = buildApiHandler({ ...updatedConfig, taskId: this.task.taskId }, currentMode)
// Get the updated API configuration (now includes the updated providers)
const { apiConfiguration } = await getAllExtensionState(this.context)
const updatedConfig = {
...apiConfiguration,
openRouterApiKey: apiKey,
taskId: this.task.taskId,
}
this.task.api = buildApiHandler(updatedConfig, currentMode)
}
// await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome
}
@@ -727,17 +699,13 @@ export class Controller {
}
async getStateToPostToWebview(): Promise<ExtensionState> {
// Get API configuration from cache for immediate access
const apiConfiguration = this.cacheService.getApiConfiguration()
const {
apiConfiguration,
lastShownAnnouncementId,
taskHistory,
autoApprovalSettings,
browserSettings,
preferredLanguage,
openaiReasoningEffort,
mode,
chatSettings: storedChatSettings,
userInfo,
mcpMarketplaceEnabled,
mcpDisplayMode,
@@ -753,50 +721,51 @@ export class Controller {
welcomeViewCompleted,
mcpResponsesCollapsed,
terminalOutputLineLimit,
localClineRulesToggles,
localWindsurfRulesToggles,
localCursorRulesToggles,
localWorkflowToggles,
} = await getAllExtensionState(this.context)
const currentTaskItem = this.task?.taskId ? (taskHistory || []).find((item) => item.id === this.task?.taskId) : undefined
const checkpointTrackerErrorMessage = this.task?.taskState.checkpointTrackerErrorMessage
const clineMessages = this.task?.messageStateHandler.getClineMessages() || []
// Get current mode using helper function
const currentMode = await this.getCurrentMode()
const processedTaskHistory = (taskHistory || [])
.filter((item) => item.ts && item.task)
.sort((a, b) => b.ts - a.ts)
.slice(0, 100) // for now we're only getting the latest 100 tasks, but a better solution here is to only pass in 3 for recent task history, and then get the full task history on demand when going to the task history view (maybe with pagination?)
// Reconstruct ChatSettings with mode from global state and stored preferences
const chatSettings: ChatSettings = {
...storedChatSettings, // Spread stored preferences (preferredLanguage, openAIReasoningEffort)
mode: currentMode, // Use mode from global state
}
const latestAnnouncementId = getLatestAnnouncementId(this.context)
const shouldShowAnnouncement = lastShownAnnouncementId !== latestAnnouncementId
const platform = process.platform as Platform
const distinctId = telemetryService.distinctId
const version = this.context.extension?.packageJSON?.version ?? ""
const uriScheme = vscode.env.uriScheme
const localClineRulesToggles =
((await getWorkspaceState(this.context, "localClineRulesToggles")) as ClineRulesToggles) || {}
const localWindsurfRulesToggles =
((await getWorkspaceState(this.context, "localWindsurfRulesToggles")) as ClineRulesToggles) || {}
const localCursorRulesToggles =
((await getWorkspaceState(this.context, "localCursorRulesToggles")) as ClineRulesToggles) || {}
const localWorkflowToggles = ((await getWorkspaceState(this.context, "workflowToggles")) as ClineRulesToggles) || {}
return {
version,
version: this.context.extension?.packageJSON?.version ?? "",
apiConfiguration,
uriScheme,
currentTaskItem,
checkpointTrackerErrorMessage,
clineMessages,
taskHistory: processedTaskHistory,
shouldShowAnnouncement,
platform,
uriScheme: vscode.env.uriScheme,
currentTaskItem: this.task?.taskId ? (taskHistory || []).find((item) => item.id === this.task?.taskId) : undefined,
checkpointTrackerErrorMessage: this.task?.taskState.checkpointTrackerErrorMessage,
clineMessages: this.task?.messageStateHandler.getClineMessages() || [],
taskHistory: (taskHistory || [])
.filter((item) => item.ts && item.task)
.sort((a, b) => b.ts - a.ts)
.slice(0, 100), // for now we're only getting the latest 100 tasks, but a better solution here is to only pass in 3 for recent task history, and then get the full task history on demand when going to the task history view (maybe with pagination?)
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
platform: process.platform as Platform,
autoApprovalSettings,
browserSettings,
preferredLanguage,
openaiReasoningEffort,
mode,
chatSettings,
userInfo,
mcpMarketplaceEnabled,
mcpDisplayMode,
telemetrySetting,
planActSeparateModelsSetting,
enableCheckpointsSetting: enableCheckpointsSetting ?? true,
distinctId,
distinctId: telemetryService.distinctId,
globalClineRulesToggles: globalClineRulesToggles || {},
localClineRulesToggles: localClineRulesToggles || {},
localWindsurfRulesToggles: localWindsurfRulesToggles || {},
@@ -871,4 +840,18 @@ export class Controller {
await updateGlobalState(this.context, "taskHistory", history)
return history
}
// private async clearState() {
// this.context.workspaceState.keys().forEach((key) => {
// this.context.workspaceState.update(key, undefined)
// })
// this.context.globalState.keys().forEach((key) => {
// this.context.globalState.update(key, undefined)
// })
// this.context.secrets.delete("apiKey")
// }
// secrets
// dev
}
@@ -1,6 +1,6 @@
import { convertMcpServersToProtoMcpServers } from "@/shared/proto-conversions/mcp/mcp-server-conversion"
import type { AddRemoteMcpServerRequest } from "@shared/proto/cline/mcp"
import { McpServers } from "@shared/proto/cline/mcp"
import type { AddRemoteMcpServerRequest } from "../../../shared/proto/mcp"
import { McpServers } from "../../../shared/proto/mcp"
import type { Controller } from "../index"
/**
+2 -2
View File
@@ -1,7 +1,7 @@
import type { Controller } from "../index"
import { McpServers } from "@shared/proto/cline/mcp"
import { McpServers } from "../../../shared/proto/mcp"
import { convertMcpServersToProtoMcpServers } from "../../../shared/proto-conversions/mcp/mcp-server-conversion"
import { StringRequest } from "@shared/proto/cline/common"
import { StringRequest } from "@/shared/proto/common"
/**
* Deletes an MCP server
+5 -5
View File
@@ -1,6 +1,6 @@
import { Controller } from ".."
import { StringRequest } from "@shared/proto/cline/common"
import { McpDownloadResponse } from "@shared/proto/cline/mcp"
import { StringRequest } from "../../../shared/proto/common"
import { McpDownloadResponse } from "../../../shared/proto/mcp"
import { McpServer } from "@shared/mcp"
import axios from "axios"
import { sendChatButtonClickedEvent } from "../ui/subscribeToChatButtonClicked"
@@ -66,9 +66,9 @@ export async function downloadMcp(controller: Controller, request: StringRequest
- Once installed, demonstrate the server's capabilities by using one of its tools.
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
const { mode } = await controller.getStateToPostToWebview()
if (mode === "plan") {
await controller.togglePlanActMode("act")
const { chatSettings } = await controller.getStateToPostToWebview()
if (chatSettings.mode === "plan") {
await controller.togglePlanActModeWithChatSettings({ mode: "act" })
}
// Initialize task and show chat view
@@ -1,5 +1,5 @@
import type { Empty } from "@shared/proto/cline/common"
import { McpServers } from "@shared/proto/cline/mcp"
import type { Empty } from "@shared/proto/common"
import { McpServers } from "@shared/proto/mcp"
import type { Controller } from "../index"
import { convertMcpServersToProtoMcpServers } from "@/shared/proto-conversions/mcp/mcp-server-conversion"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller } from ".."
import { Empty, EmptyRequest } from "@shared/proto/cline/common"
import { Empty, EmptyRequest } from "@shared/proto/common"
import { openFile as openFileIntegration } from "@integrations/misc/open-file"
/**
@@ -1,5 +1,5 @@
import type { EmptyRequest } from "@shared/proto/cline/common"
import { McpMarketplaceCatalog } from "@shared/proto/cline/mcp"
import type { EmptyRequest } from "../../../shared/proto/common"
import { McpMarketplaceCatalog } from "../../../shared/proto/mcp"
import type { Controller } from "../index"
/**
+2 -2
View File
@@ -1,7 +1,7 @@
import { McpServers } from "@shared/proto/cline/mcp"
import { McpServers } from "@shared/proto/mcp"
import type { Controller } from "../index"
import { convertMcpServersToProtoMcpServers } from "@shared/proto-conversions/mcp/mcp-server-conversion"
import { StringRequest } from "@shared/proto/cline/common"
import { StringRequest } from "@/shared/proto/common"
/**
* Restarts an MCP server connection
@@ -1,6 +1,6 @@
import { Controller } from "../index"
import { EmptyRequest } from "@shared/proto/cline/common"
import { McpMarketplaceCatalog } from "@shared/proto/cline/mcp"
import { EmptyRequest } from "@shared/proto/common"
import { McpMarketplaceCatalog } from "@shared/proto/mcp"
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
// Keep track of active subscriptions
@@ -1,6 +1,6 @@
import { Controller } from "../index"
import { EmptyRequest } from "@shared/proto/cline/common"
import { McpServers } from "@shared/proto/cline/mcp"
import { EmptyRequest } from "@shared/proto/common"
import { McpServers } from "@shared/proto/mcp"
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
import { convertMcpServersToProtoMcpServers } from "@shared/proto-conversions/mcp/mcp-server-conversion"
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToggleMcpServerRequest } from "@shared/proto/cline/mcp"
import { McpServers } from "@shared/proto/cline/mcp"
import type { ToggleMcpServerRequest } from "../../../shared/proto/mcp"
import { McpServers } from "../../../shared/proto/mcp"
import type { Controller } from "../index"
import { convertMcpServersToProtoMcpServers } from "../../../shared/proto-conversions/mcp/mcp-server-conversion"
@@ -1,5 +1,5 @@
import type { ToggleToolAutoApproveRequest } from "@shared/proto/cline/mcp"
import { McpServers } from "@shared/proto/cline/mcp"
import type { ToggleToolAutoApproveRequest } from "@shared/proto/mcp"
import { McpServers } from "@shared/proto/mcp"
import type { Controller } from "../index"
import { convertMcpServersToProtoMcpServers } from "@shared/proto-conversions/mcp/mcp-server-conversion"
+1 -1
View File
@@ -1,6 +1,6 @@
import { convertMcpServersToProtoMcpServers } from "@/shared/proto-conversions/mcp/mcp-server-conversion"
import { Controller } from ".."
import { UpdateMcpTimeoutRequest, McpServers } from "@shared/proto/cline/mcp"
import { UpdateMcpTimeoutRequest, McpServers } from "../../../shared/proto/mcp"
/**
* Updates the timeout configuration for an MCP server.

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