mirror of
https://github.com/cline/cline.git
synced 2026-09-04 20:02:30 +08:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 388aec10b5 | |||
| f97415eabe | |||
| 384707678f | |||
| d676fb2555 | |||
| 03d44105cc | |||
| 79b76fd783 | |||
| 19cc8bc9f8 | |||
| 08c04a3c67 | |||
| 41ae7326c0 | |||
| b0961f4538 | |||
| 26242f6378 | |||
| 13228ed46f | |||
| d162a4b420 | |||
| 1704684af8 | |||
| c63d9a13a5 | |||
| 65243adb24 | |||
| e35f7b4e21 | |||
| 82449dabd6 | |||
| 74ec823017 | |||
| 91e222fe37 | |||
| 14230e7221 | |||
| 4b697d8695 | |||
| deeda6e273 | |||
| 7e7844529f | |||
| 4196c14c9c | |||
| 5294e78dde | |||
| d97424fcab | |||
| 2b3c0bb633 | |||
| b8e2fd669d | |||
| df7f9fcba4 | |||
| db0b022b6e | |||
| 459adf0450 | |||
| df37f29746 | |||
| 60c210b017 | |||
| d4bd755e60 | |||
| aed152b530 | |||
| 5a8e9d8fa8 | |||
| 7610cecde6 | |||
| bb26b3b64d | |||
| aabdeba0f3 | |||
| fd68a81a26 | |||
| 0e07b92be2 | |||
| 4addffe94c | |||
| 0836e4d45a | |||
| 4a57e5a075 | |||
| 29458d7675 | |||
| 90b0d6a73b | |||
| 547051bfa8 | |||
| 93595af09f | |||
| 3828c0d1bc | |||
| a2263de7cb | |||
| e53fa8307d | |||
| b8cfb87121 | |||
| 76a64ef77d | |||
| 29bdb6c981 | |||
| 9bbc0da821 | |||
| d2080c1f93 | |||
| 044dd686a0 | |||
| ea4f571463 | |||
| 8cddbcfd99 | |||
| 4e0cb64e77 | |||
| 4aa3764beb | |||
| a525d6dd5e | |||
| 59dd3236e4 | |||
| 5439426ff6 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
updated drag and drop text to say "drop" instead of "drag"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add markdown copy to chat
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
menu fix for slash commands
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add support for custom model ID in AWS Bedrock provider, enabling use of Application Inference Profile.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cline": minor
|
||||
---
|
||||
|
||||
Add support for custom API request timeout. Previously, timeouts were hardcoded to 30 seconds for providers like Ollama or 15 seconds for OpenRouter and Cline. Now users can set a custom timeout value in milliseconds through the settings interface.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Minor UX improvement to drag and drop ux
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Remove linear pull request action
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add aliasing to imports in the extension
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add the o1 to the isReasoningModelFamily to avoid temperature be passed to the azure api
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add dependsOn to more blocks in the tasks.json
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix for git commit mentions in repos with no git commits
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adding args to allow Cursor to open workspaces (for checkpoint testing/development)
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
add truncation notice when truncating manually
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Fix add new rule file button not working
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add a timeout setting for the terminal connection, allowing users to adjust this if they are having timeout issues
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Added copy button to code blocks.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add !include .file directive support for .clineignore
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
add checkpoints after more messages
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Introduce UI library for future UI development
|
||||
@@ -2,4 +2,4 @@
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
update prompt for new task
|
||||
add newrule slash command
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add remote config with posthog allowing for disabling new features until they're reading, making for a better developer experience.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Ollama provider timeout by increasing it from 30 sec to 120 seconds to accommodate model loading time
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
protobus migration for openImage
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Non-UTF-8 File Handling: Improve Encoding Detection to Prevent Garbled Text and Binary Misclassification
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
add cache ui for open router and cline provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
showing expanded task by default
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Update the extension import paths to use aliasing
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Refactor to not pass a message for showing the MCP View from the servers modal
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Migrate the addRemoteServer to protobus
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Lowering Gemini cache TTL time
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixed bug causing saved settings to get reset by changing providers
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adding UI to show openrouter balance next to provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixes auto approve settings becoming unset
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix cost calculation
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Migrate the toggleMcpServer message to protobus
|
||||
@@ -30,7 +30,7 @@ graph TB
|
||||
CheckpointSystem[Git-based Checkpoints]
|
||||
end
|
||||
|
||||
subgraph APIProviders[API Providers]
|
||||
subgraph apiProviders[API Providers]
|
||||
AnthropicAPI[Anthropic]
|
||||
OpenRouterAPI[OpenRouter]
|
||||
BedrockAPI[AWS Bedrock]
|
||||
@@ -51,7 +51,7 @@ graph TB
|
||||
Task --> SecretsStorage
|
||||
Task --> TaskStorage
|
||||
Task --> CheckpointSystem
|
||||
Task --> |API Requests| APIProviders
|
||||
Task --> |API Requests| apiProviders
|
||||
McpHub --> |Connects to| ExternalMcpServers
|
||||
Task --> |Uses| McpHub
|
||||
|
||||
@@ -67,7 +67,7 @@ graph TB
|
||||
style ExtStateContext fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style WebviewProvider fill:#bfb,stroke:#333,stroke-width:2px
|
||||
style McpHub fill:#bfb,stroke:#333,stroke-width:2px
|
||||
style APIProviders fill:#fdb,stroke:#333,stroke-width:2px
|
||||
style apiProviders fill:#fdb,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
## Definitions
|
||||
|
||||
@@ -13,6 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
if: false
|
||||
name: Check for spelling errors
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -23,3 +24,5 @@ jobs:
|
||||
uses: codespell-project/codespell-problem-matcher@v1
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
with:
|
||||
only_warn: 1
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.15.1
|
||||
node-version: 22
|
||||
|
||||
# Setup Python for coverage script
|
||||
- name: Setup Python
|
||||
@@ -70,19 +70,18 @@ jobs:
|
||||
run: npm run format
|
||||
|
||||
# Build the extension before running tests
|
||||
- name: Build Extension
|
||||
run: npm run compile
|
||||
- name: Build Tests and Extension
|
||||
run: npm run pretest
|
||||
|
||||
# Disabling due to compatability with test framework and ESM modules
|
||||
# - name: Unit Tests
|
||||
# run: npm run test:unit
|
||||
- name: Unit Tests
|
||||
run: npm run test:unit
|
||||
|
||||
# Run extension tests with coverage
|
||||
- name: Extension Tests with Coverage
|
||||
id: extension_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
xvfb-run -a npm run test:coverage > extension_coverage.txt 2>&1 || true
|
||||
xvfb-run -a npm run test:coverage > extension_coverage.txt 2>&1
|
||||
PYTHONPATH=.github/scripts python -m coverage_check extract-coverage extension_coverage.txt --type=extension --github-output --verbose
|
||||
|
||||
# Run webview tests with coverage
|
||||
@@ -107,6 +106,18 @@ jobs:
|
||||
webview-ui/webview_coverage.txt
|
||||
retention-period: workflow # Artifacts are automatically deleted when the workflow completes
|
||||
|
||||
# Set the check as failed if any of the tests failed
|
||||
- name: Check for test failures
|
||||
run: |
|
||||
# Check if any of the test steps failed
|
||||
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context
|
||||
if [ "${{ steps.extension_coverage.outcome }}" != "success" ] || [ "${{ steps.webview_coverage.outcome }}" != "success" ]; then
|
||||
echo "Tests failed."
|
||||
cat extension_coverage.txt
|
||||
cat webview-ui/webview_coverage.txt
|
||||
exit 1
|
||||
fi
|
||||
|
||||
coverage:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -132,7 +143,7 @@ jobs:
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.15.1
|
||||
node-version: 22
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extension": ["ts"],
|
||||
"spec": "src/**/__tests__/*.ts",
|
||||
"require": ["ts-node/register", "source-map-support/register"],
|
||||
"require": ["ts-node/register", "source-map-support/register", "./src/test/requires.ts"],
|
||||
"recursive": true
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ export default defineConfig({
|
||||
mocha: {
|
||||
ui: "bdd",
|
||||
timeout: 20000, // Maximum time (in ms) that a test can run before failing
|
||||
/** Set up alias path resolution during tests
|
||||
* @See {@link file://./test-setup.js}
|
||||
*/
|
||||
require: ["./test-setup.js"],
|
||||
},
|
||||
workspaceFolder: "test-workspace",
|
||||
version: "stable",
|
||||
|
||||
Vendored
+1
-1
@@ -9,7 +9,7 @@
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--disable-workspace-trust", "${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"env": {
|
||||
|
||||
Vendored
+41
-19
@@ -4,10 +4,25 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "watch",
|
||||
"dependsOn": ["npm: build:webview", "npm: dev:webview", "npm: watch:tsc", "npm: watch:esbuild"],
|
||||
"label": "npm: protos",
|
||||
"type": "npm",
|
||||
"script": "protos",
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"dependsOn": ["npm: protos", "npm: build:webview", "npm: dev:webview", "npm: watch:tsc", "npm: watch:esbuild"],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
@@ -16,9 +31,15 @@
|
||||
},
|
||||
{
|
||||
"label": "watch:test",
|
||||
"dependsOn": ["npm: build:webview:test", "npm: dev:webview", "npm: watch:tsc", "npm: watch:esbuild:test"],
|
||||
"dependsOn": [
|
||||
"npm: protos",
|
||||
"npm: build:webview:test",
|
||||
"npm: dev:webview",
|
||||
"npm: watch:tsc",
|
||||
"npm: watch:esbuild:test"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
"reveal": "always"
|
||||
},
|
||||
"group": "build"
|
||||
},
|
||||
@@ -29,10 +50,10 @@
|
||||
"problemMatcher": [],
|
||||
"isBackground": true,
|
||||
"label": "npm: build:webview",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "never",
|
||||
"close": true
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
@@ -47,10 +68,10 @@
|
||||
"problemMatcher": [],
|
||||
"isBackground": true,
|
||||
"label": "npm: build:webview:test",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "never",
|
||||
"close": true
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
@@ -82,10 +103,10 @@
|
||||
],
|
||||
"isBackground": true,
|
||||
"label": "npm: dev:webview",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "never",
|
||||
"close": true
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
@@ -100,10 +121,10 @@
|
||||
"problemMatcher": "$esbuild-watch",
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "never",
|
||||
"close": true
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
@@ -118,10 +139,10 @@
|
||||
"problemMatcher": "$esbuild-watch",
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild:test",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "never",
|
||||
"close": true
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
@@ -137,10 +158,10 @@
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:tsc",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "never",
|
||||
"close": true
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -148,15 +169,16 @@
|
||||
"script": "watch-tests",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"dependsOn": ["npm: protos"],
|
||||
"presentation": {
|
||||
"reveal": "never",
|
||||
"reveal": "always",
|
||||
"group": "watchers"
|
||||
},
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "tasks: watch-tests",
|
||||
"dependsOn": ["npm: watch", "npm: watch-tests"],
|
||||
"dependsOn": ["npm: protos", "npm: watch", "npm: watch-tests"],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## [3.13.3]
|
||||
|
||||
- Add download counts to MCP marketplace items
|
||||
- Add `/compact` command
|
||||
- Add prompt caching to gemini models in cline / openrouter providers
|
||||
- Add tooltips to bottom row menu
|
||||
|
||||
## [3.13.2]
|
||||
|
||||
- Add Gemini 2.5 Flash model to Vertex and Gemini Providers (Thanks monotykamary!)
|
||||
- Add Caching to gemini provider (Thanks arafatkatze!)
|
||||
- Add thinking budget support to Gemini Models (Thanks monotykamary!)
|
||||
- Add !include .file directive support for .clineignore (Thanks watany-dev!)
|
||||
- Improve slash command functionality
|
||||
- Improve prompting for new task tool
|
||||
- Fix o1 temperature being passed to the azure api (Thanks treeleaves30760!)
|
||||
- Fix to make "add new rule file" button functional
|
||||
- Fix Ollama provider timeout, allowing for a larger loading time (Thanks suvarchal!)
|
||||
- Fix Non-UTF-8 File Handling: Improve Encoding Detection to Prevent Garbled Text and Binary Misclassification (Thanks yt3trees!)
|
||||
- Fix settings to not reset by changing providers
|
||||
- Fix terminal outputs missing commas
|
||||
- Fix terminal errors caused by starting non-alphanumeric outputs
|
||||
- Fix auto approve settings becoming unset
|
||||
- Fix Mermaid syntax error in documentation (Thanks tuki0918!)
|
||||
- Remove supportsComputerUse restriction and support browser use through any model that supports images (Thanks arafatkatze!)
|
||||
|
||||
## [3.13.1]
|
||||
|
||||
- Fix bug where task cancellation during thinking stream would result in error state
|
||||
|
||||
@@ -19,6 +19,7 @@ const aliasResolverPlugin = {
|
||||
"@services": path.resolve(__dirname, "src/services"),
|
||||
"@shared": path.resolve(__dirname, "src/shared"),
|
||||
"@utils": path.resolve(__dirname, "src/utils"),
|
||||
"@packages": path.resolve(__dirname, "src/packages"),
|
||||
}
|
||||
|
||||
// For each alias entry, create a resolver
|
||||
|
||||
@@ -167,8 +167,12 @@ export class ExercismAdapter implements BenchmarkAdapter {
|
||||
output += stdout + "\n"
|
||||
} catch (error: any) {
|
||||
success = false
|
||||
if (error.stdout) output += error.stdout + "\n"
|
||||
if (error.stderr) output += error.stderr + "\n"
|
||||
if (error.stdout) {
|
||||
output += error.stdout + "\n"
|
||||
}
|
||||
if (error.stderr) {
|
||||
output += error.stderr + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as path from "path"
|
||||
import * as fs from "fs"
|
||||
import fetch from "node-fetch"
|
||||
import * as os from "os"
|
||||
import * as child_process from "child_process"
|
||||
import { installRequiredExtensions, configureExtensionSettings } from "./extensions"
|
||||
|
||||
// Store temporary directories for cleanup
|
||||
|
||||
Generated
+355
@@ -86,6 +86,7 @@
|
||||
"chalk": "^5.3.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.57.0",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"husky": "^9.1.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
@@ -5913,6 +5914,90 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@mistralai/mistralai": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.0.tgz",
|
||||
@@ -9204,6 +9289,13 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
@@ -9389,6 +9481,36 @@
|
||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/are-we-there-yet/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
@@ -10060,6 +10182,16 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/chrome-launcher": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz",
|
||||
@@ -10600,6 +10732,13 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -12101,6 +12240,32 @@
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@@ -12545,6 +12710,20 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/grpc-tools": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.13.0.tgz",
|
||||
"integrity": "sha512-7CbkJ1yWPfX0nHjbYG58BQThNhbICXBZynzCUxCb3LzX5X9B3hQbRY2STiRgIEiLILlK9fgl0z0QVGwPCdXf5g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"grpc_tools_node_protoc": "bin/protoc.js",
|
||||
"grpc_tools_node_protoc_plugin": "bin/protoc_plugin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/gtoken": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
|
||||
@@ -14032,12 +14211,52 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz",
|
||||
@@ -14405,6 +14624,22 @@
|
||||
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
@@ -14666,6 +14901,84 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^2.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
"gauge": "^3.0.0",
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/npmlog/node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.2",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.1",
|
||||
"object-assign": "^4.1.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wide-align": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog/node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/npmlog/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
@@ -16134,6 +16447,13 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@@ -16835,6 +17155,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
|
||||
@@ -16860,6 +17198,16 @@
|
||||
"streamx": "^2.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/term-size": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
|
||||
@@ -17944,6 +18292,13 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
|
||||
+3
-2
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.13.1",
|
||||
"version": "3.13.3",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -292,7 +292,7 @@
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
|
||||
"protos": "node proto/build-proto.js && prettier src/shared/proto --write && prettier src/core/controller --write",
|
||||
"compile-tests": "tsc -p ./tsconfig.test.json --outDir out",
|
||||
"compile-tests": "node ./scripts/build-tests.js",
|
||||
"watch-tests": "tsc -p . -w --outDir out",
|
||||
"pretest": "npm run compile-tests && npm run compile && npm run lint",
|
||||
"check-types": "tsc --noEmit",
|
||||
@@ -336,6 +336,7 @@
|
||||
"chalk": "^5.3.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.57.0",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"husky": "^9.1.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// Service for account-related operations
|
||||
service AccountService {
|
||||
// Handles the user clicking the login link in the UI.
|
||||
// Generates a secure nonce for state validation, stores it in secrets,
|
||||
// and opens the authentication URL in the external browser.
|
||||
rpc accountLoginClicked(EmptyRequest) returns (String);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
@@ -8,6 +10,8 @@ service BrowserService {
|
||||
rpc getBrowserConnectionInfo(EmptyRequest) returns (BrowserConnectionInfo);
|
||||
rpc testBrowserConnection(StringRequest) returns (BrowserConnection);
|
||||
rpc discoverBrowser(EmptyRequest) returns (BrowserConnection);
|
||||
rpc getDetectedChromePath(EmptyRequest) returns (ChromePath);
|
||||
rpc updateBrowserSettings(UpdateBrowserSettingsRequest) returns (Boolean);
|
||||
}
|
||||
|
||||
message BrowserConnectionInfo {
|
||||
@@ -21,3 +25,26 @@ message BrowserConnection {
|
||||
string message = 2;
|
||||
optional string endpoint = 3;
|
||||
}
|
||||
|
||||
message ChromePath {
|
||||
string path = 1;
|
||||
bool is_bundled = 2;
|
||||
}
|
||||
|
||||
message Viewport {
|
||||
int32 width = 1;
|
||||
int32 height = 2;
|
||||
}
|
||||
|
||||
message BrowserSettings {
|
||||
Viewport viewport = 1;
|
||||
optional string remote_browser_host = 2;
|
||||
optional bool remote_browser_enabled = 3;
|
||||
}
|
||||
|
||||
message UpdateBrowserSettingsRequest {
|
||||
Metadata metadata = 1;
|
||||
Viewport viewport = 2;
|
||||
optional string remote_browser_host = 3;
|
||||
optional bool remote_browser_enabled = 4;
|
||||
}
|
||||
|
||||
+12
-46
@@ -6,6 +6,11 @@ import { execSync } from "child_process"
|
||||
import { globby } from "globby"
|
||||
import chalk from "chalk"
|
||||
|
||||
import { createRequire } from "module"
|
||||
const require = createRequire(import.meta.url)
|
||||
const protoc = path.join(require.resolve("grpc-tools"), "../bin/protoc")
|
||||
const tsProtoPlugin = require.resolve("ts-proto/protoc-gen-ts_proto")
|
||||
|
||||
// Get script directory and root directory
|
||||
const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname)
|
||||
const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
|
||||
@@ -13,48 +18,6 @@ const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
|
||||
async function main() {
|
||||
console.log(chalk.bold.blue("Starting Protocol Buffer code generation..."))
|
||||
|
||||
// Check if protoc is installed and has the correct version
|
||||
try {
|
||||
const protocOutput = execSync("protoc --version", { encoding: "utf8" }).trim()
|
||||
console.log(chalk.cyan(`Found ${protocOutput}`))
|
||||
const versionMatch = protocOutput.match(/libprotoc\s+(\d+\.\d+)/)
|
||||
if (!versionMatch) {
|
||||
console.warn(chalk.yellow("Warning: Could not determine protoc version. Continuing anyway..."))
|
||||
} else {
|
||||
const version = versionMatch[1]
|
||||
const requiredVersion = "30.1"
|
||||
if (version !== requiredVersion) {
|
||||
console.warn(
|
||||
chalk.yellow(`Warning: protoc version ${version} found, but version ${requiredVersion} is required.`),
|
||||
)
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
`To install the correct version, visit: https://github.com/protocolbuffers/protobuf/releases/tag/v${requiredVersion}`,
|
||||
),
|
||||
)
|
||||
process.exit(0) // Exit with success as requested
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(chalk.yellow("Warning: protoc is not installed. Skipping proto generation."))
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
"To install Protocol Buffers compiler, visit: https://github.com/protocolbuffers/protobuf/releases/tag/v30.1",
|
||||
),
|
||||
)
|
||||
process.exit(0) // Exit with success as requested
|
||||
}
|
||||
|
||||
// Check if ts-proto plugin is available
|
||||
const TS_PROTO_PLUGIN = path.join(ROOT_DIR, "node_modules", ".bin", "protoc-gen-ts_proto")
|
||||
try {
|
||||
await fs.access(TS_PROTO_PLUGIN)
|
||||
} catch (error) {
|
||||
console.error(chalk.red("Error: ts-proto plugin not found at"), TS_PROTO_PLUGIN)
|
||||
console.error(chalk.red('Please run "npm install" to install the required dependencies.'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Define output directories
|
||||
const TS_OUT_DIR = path.join(ROOT_DIR, "src", "shared", "proto")
|
||||
|
||||
@@ -70,15 +33,15 @@ async function main() {
|
||||
|
||||
// Process all proto files
|
||||
console.log(chalk.cyan("Processing proto files from"), SCRIPT_DIR)
|
||||
const protoFiles = await globby("**/*.proto", { cwd: SCRIPT_DIR })
|
||||
const protoFiles = await globby("*.proto", { cwd: SCRIPT_DIR })
|
||||
|
||||
for (const protoFile of protoFiles) {
|
||||
console.log(chalk.cyan(`Generating TypeScript code for ${protoFile}...`))
|
||||
|
||||
// Build the protoc command with proper path handling for cross-platform
|
||||
const protocCommand = [
|
||||
"protoc",
|
||||
`--plugin=protoc-gen-ts_proto="${TS_PROTO_PLUGIN}"`,
|
||||
protoc,
|
||||
`--plugin=protoc-gen-ts_proto="${tsProtoPlugin}"`,
|
||||
`--ts_proto_out="${TS_OUT_DIR}"`,
|
||||
"--ts_proto_opt=outputServices=generic-definitions,env=node,esModuleInterop=true,useDate=false,useOptionals=messages",
|
||||
`--proto_path="${SCRIPT_DIR}"`,
|
||||
@@ -114,9 +77,12 @@ async function generateMethodRegistrations() {
|
||||
console.log(chalk.cyan("Generating method registration files..."))
|
||||
|
||||
const serviceDirs = [
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "mcp"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "account"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "browser"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "checkpoints"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "file"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "mcp"),
|
||||
path.join(ROOT_DIR, "src", "core", "controller", "task"),
|
||||
// Add more service directories here as needed
|
||||
]
|
||||
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service CheckpointsService {
|
||||
rpc checkpointDiff(Int64Request) returns (Empty);
|
||||
rpc checkpointRestore(CheckpointRestoreRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message CheckpointRestoreRequest {
|
||||
Metadata metadata = 1;
|
||||
int64 number = 2;
|
||||
string restore_type = 3;
|
||||
optional int64 offset = 4;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Metadata {
|
||||
}
|
||||
@@ -38,3 +40,12 @@ message BytesRequest {
|
||||
message Bytes {
|
||||
bytes value = 1;
|
||||
}
|
||||
|
||||
message BooleanRequest {
|
||||
Metadata metadata = 1;
|
||||
bool value = 2;
|
||||
}
|
||||
|
||||
message Boolean {
|
||||
bool value = 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// Service for file-related operations
|
||||
service FileService {
|
||||
// Opens a file in the editor
|
||||
rpc openFile(StringRequest) returns (Empty);
|
||||
|
||||
// Opens an image in the system viewer
|
||||
rpc openImage(StringRequest) returns (Empty);
|
||||
}
|
||||
+22
-6
@@ -1,11 +1,33 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service McpService {
|
||||
rpc toggleMcpServer(ToggleMcpServerRequest) returns (McpServers);
|
||||
rpc updateMcpTimeout(UpdateMcpTimeoutRequest) returns (McpServers);
|
||||
rpc addRemoteMcpServer(AddRemoteMcpServerRequest) returns (McpServers);
|
||||
}
|
||||
|
||||
message ToggleMcpServerRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
bool disabled = 3;
|
||||
}
|
||||
|
||||
message UpdateMcpTimeoutRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
int32 timeout = 3;
|
||||
}
|
||||
|
||||
message AddRemoteMcpServerRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
string server_url = 3;
|
||||
}
|
||||
|
||||
message McpTool {
|
||||
@@ -49,12 +71,6 @@ message McpServer {
|
||||
optional int32 timeout = 9;
|
||||
}
|
||||
|
||||
message ToggleMcpServerRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
bool disabled = 3;
|
||||
}
|
||||
|
||||
message McpServers {
|
||||
repeated McpServer mcp_servers = 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service TaskService {
|
||||
// Cancels the currently running task
|
||||
rpc cancelTask(EmptyRequest) returns (Empty);
|
||||
// Clears the current task
|
||||
rpc clearTask(EmptyRequest) returns (Empty);
|
||||
// Creates a new task with the given text and optional images
|
||||
rpc newTask(NewTaskRequest) returns (Empty);
|
||||
}
|
||||
|
||||
// Request message for creating a new task
|
||||
message NewTaskRequest {
|
||||
Metadata metadata = 1;
|
||||
string text = 2;
|
||||
repeated string images = 3;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
const { execSync } = require("child_process")
|
||||
const esbuild = require("esbuild")
|
||||
|
||||
const watch = process.argv.includes("--watch")
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').Plugin}
|
||||
*/
|
||||
const esbuildProblemMatcherPlugin = {
|
||||
name: "esbuild-problem-matcher",
|
||||
|
||||
setup(build) {
|
||||
build.onStart(() => {
|
||||
console.log("[watch] build started")
|
||||
})
|
||||
build.onEnd((result) => {
|
||||
result.errors.forEach(({ text, location }) => {
|
||||
console.error(`✘ [ERROR] ${text}`)
|
||||
console.error(` ${location.file}:${location.line}:${location.column}:`)
|
||||
})
|
||||
console.log("[watch] build finished")
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const srcConfig = {
|
||||
bundle: true,
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
sourcesContent: true,
|
||||
logLevel: "silent",
|
||||
entryPoints: ["src/packages/**/*.ts"],
|
||||
outdir: "out/packages",
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
define: {
|
||||
"process.env.IS_DEV": "true",
|
||||
"process.env.IS_TEST": "true",
|
||||
},
|
||||
external: ["vscode"],
|
||||
plugins: [esbuildProblemMatcherPlugin],
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const srcCtx = await esbuild.context(srcConfig)
|
||||
|
||||
if (watch) {
|
||||
await srcCtx.watch()
|
||||
} else {
|
||||
await srcCtx.rebuild()
|
||||
|
||||
await srcCtx.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
execSync("tsc -p ./tsconfig.test.json --outDir out", { encoding: "utf-8" })
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -28,20 +28,26 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
const modelId = await this.getModelId()
|
||||
const model = this.getModel()
|
||||
|
||||
// This baseModelId is used to indicate the capabilities of the model.
|
||||
// If the user selects a custom model, baseModelId will be set to the base model ID of the custom model.
|
||||
// Otherwise, baseModelId will be the same as modelId.
|
||||
const baseModelId =
|
||||
(this.options.awsBedrockCustomSelected ? this.options.awsBedrockCustomModelBaseId : modelId) || modelId
|
||||
|
||||
// Check if this is an Amazon Nova model
|
||||
if (modelId.includes("amazon.nova")) {
|
||||
if (baseModelId.includes("amazon.nova")) {
|
||||
yield* this.createNovaMessage(systemPrompt, messages, modelId, model)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this is a Deepseek model
|
||||
if (modelId.includes("deepseek")) {
|
||||
if (baseModelId.includes("deepseek")) {
|
||||
yield* this.createDeepseekMessage(systemPrompt, messages, modelId, model)
|
||||
return
|
||||
}
|
||||
|
||||
const budget_tokens = this.options.thinkingBudgetTokens || 0
|
||||
const reasoningOn = modelId.includes("3-7") && budget_tokens !== 0 ? true : false
|
||||
const reasoningOn = baseModelId.includes("3-7") && budget_tokens !== 0 ? true : false
|
||||
|
||||
// Get model info and message indices for caching
|
||||
const userMsgIndices = messages.reduce((acc, msg, index) => (msg.role === "user" ? [...acc, index] : acc), [] as number[])
|
||||
@@ -167,12 +173,23 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: BedrockModelId; info: ModelInfo } {
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in bedrockModels) {
|
||||
const id = modelId as BedrockModelId
|
||||
return { id, info: bedrockModels[id] }
|
||||
}
|
||||
|
||||
const customSelected = this.options.awsBedrockCustomSelected
|
||||
const baseModel = this.options.awsBedrockCustomModelBaseId
|
||||
if (customSelected && modelId && baseModel && baseModel in bedrockModels) {
|
||||
// Use the user-input model ID but inherit capabilities from the base model
|
||||
return {
|
||||
id: modelId,
|
||||
info: bedrockModels[baseModel],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: bedrockDefaultModelId,
|
||||
info: bedrockModels[bedrockDefaultModelId],
|
||||
@@ -290,7 +307,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: BedrockModelId; info: ModelInfo },
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
// Get Bedrock client with proper credentials
|
||||
const client = await this.getBedrockClient()
|
||||
@@ -482,7 +499,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: BedrockModelId; info: ModelInfo },
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
// Get Bedrock client with proper credentials
|
||||
const client = await this.getBedrockClient()
|
||||
|
||||
@@ -74,6 +74,8 @@ export class ClineHandler implements ApiHandler {
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
// @ts-ignore-next-line
|
||||
@@ -105,6 +107,9 @@ export class ClineHandler implements ApiHandler {
|
||||
const generation = response.data
|
||||
return {
|
||||
type: "usage",
|
||||
// at this time there's no support for gatting cached_tokens from generation endpoint
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: 0,
|
||||
inputTokens: generation?.native_tokens_prompt || 0,
|
||||
outputTokens: generation?.native_tokens_completion || 0,
|
||||
totalCost: generation?.total_cost || 0,
|
||||
|
||||
+70
-10
@@ -1,22 +1,24 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
|
||||
import {
|
||||
GoogleGenAI,
|
||||
type GenerationConfig,
|
||||
type Content,
|
||||
type GenerateContentConfig,
|
||||
type GenerateContentResponseUsageMetadata,
|
||||
} from "@google/genai"
|
||||
import { GoogleGenAI, type Content, type GenerateContentConfig, type GenerateContentResponseUsageMetadata } from "@google/genai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "@shared/api"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
// Define a default TTL for the cache (e.g., 15 minutes in seconds)
|
||||
const DEFAULT_CACHE_TTL_SECONDS = 900
|
||||
|
||||
export class GeminiHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: GoogleGenAI // Updated client type
|
||||
|
||||
// Internal state for caching
|
||||
private cacheName: string | null = null
|
||||
private cacheExpireTime: number | null = null
|
||||
private isFirstApiCall = true
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
if (!options.geminiApiKey) {
|
||||
throw new Error("API key is required for Google Gemini")
|
||||
@@ -30,6 +32,32 @@ export class GeminiHandler implements ApiHandler {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const { id: modelId, info: modelInfo } = this.getModel()
|
||||
|
||||
// --- Cache Handling Logic ---
|
||||
const isCacheValid = this.cacheName && this.cacheExpireTime && Date.now() < this.cacheExpireTime
|
||||
let useCache = !this.isFirstApiCall && isCacheValid
|
||||
|
||||
if (this.isFirstApiCall && !isCacheValid && systemPrompt) {
|
||||
// It's the first call, no valid cache exists, and we have a system prompt. Attempt cache creation.
|
||||
this.isFirstApiCall = false
|
||||
|
||||
// Minimum token check heuristic (simple length check for now, could be improved)
|
||||
// Gemini requires minimum 4096 tokens. A simple length check isn't accurate but avoids complex token counting here.
|
||||
// Let's assume a generous average of 4 chars/token. 4096 tokens * 4 chars/token = 16384 chars.
|
||||
const MIN_SYSTEM_PROMPT_LENGTH_FOR_CACHE = 16384
|
||||
if (systemPrompt.length >= MIN_SYSTEM_PROMPT_LENGTH_FOR_CACHE) {
|
||||
// Start cache creation asynchronously, don't block the main request
|
||||
this.createCacheInBackground(modelId, systemPrompt)
|
||||
}
|
||||
// Proceed with the first request *without* using the cache, as it's being created.
|
||||
useCache = false
|
||||
} else if (!isCacheValid && this.cacheName) {
|
||||
// Cache exists but has expired
|
||||
this.cacheName = null
|
||||
this.cacheExpireTime = null
|
||||
useCache = false
|
||||
}
|
||||
// --- End Cache Handling Logic ---
|
||||
|
||||
// Re-implement thinking budget logic based on new SDK structure
|
||||
const thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const maxBudget = modelInfo.thinkingConfig?.maxBudget ?? 0
|
||||
@@ -37,11 +65,12 @@ export class GeminiHandler implements ApiHandler {
|
||||
// port add baseUrl configuration for gemini api requests (#2843)
|
||||
const httpOptions = this.options.geminiBaseUrl ? { baseUrl: this.options.geminiBaseUrl } : undefined
|
||||
|
||||
// Base generation config - Restore type and systemInstruction
|
||||
// Base generation config - Conditionally include systemInstruction based on cache usage
|
||||
const generationConfig: GenerateContentConfig = {
|
||||
httpOptions,
|
||||
temperature: 0, // Default temperature
|
||||
systemInstruction: systemPrompt, // System prompt belongs here
|
||||
// Only include systemInstruction if NOT using the cache
|
||||
...(useCache ? {} : { systemInstruction: systemPrompt }),
|
||||
}
|
||||
|
||||
// Convert messages to the format expected by @google/genai
|
||||
@@ -64,7 +93,11 @@ export class GeminiHandler implements ApiHandler {
|
||||
const result = await this.client.models.generateContentStream({
|
||||
model: modelId, // Pass model ID directly
|
||||
contents,
|
||||
config: requestConfig, // Pass the combined config (which includes systemInstruction)
|
||||
// Add cachedContent if using the cache
|
||||
config: {
|
||||
...requestConfig,
|
||||
...(useCache ? { cachedContent: this.cacheName! } : {}),
|
||||
},
|
||||
})
|
||||
|
||||
// Declare variable to hold the last usage metadata found
|
||||
@@ -88,10 +121,37 @@ export class GeminiHandler implements ApiHandler {
|
||||
type: "usage",
|
||||
inputTokens: lastUsageMetadata.promptTokenCount ?? 0,
|
||||
outputTokens: lastUsageMetadata.candidatesTokenCount ?? 0,
|
||||
cacheWriteTokens: lastUsageMetadata.cachedContentTokenCount ?? 0,
|
||||
cacheReadTokens: useCache ? (lastUsageMetadata.promptTokenCount ?? 0) : 0, // If cache used, prompt tokens are read from cache
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async createCacheInBackground(modelId: string, systemInstruction: string): Promise<void> {
|
||||
try {
|
||||
const cache = await this.client.caches.create({
|
||||
model: modelId,
|
||||
config: {
|
||||
systemInstruction: systemInstruction,
|
||||
ttl: `${DEFAULT_CACHE_TTL_SECONDS}s`,
|
||||
},
|
||||
})
|
||||
|
||||
if (cache?.name) {
|
||||
this.cacheName = cache.name
|
||||
// Calculate expiry timestamp using the default TTL, as the response object might not contain it directly.
|
||||
this.cacheExpireTime = Date.now() + DEFAULT_CACHE_TTL_SECONDS * 1000
|
||||
} else {
|
||||
console.warn("Gemini cache creation call succeeded but returned no cache name.")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to create Gemini cache in background:", error)
|
||||
// Reset state if creation failed definitively
|
||||
this.cacheName = null
|
||||
this.cacheExpireTime = null
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: GeminiModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in geminiModels) {
|
||||
|
||||
@@ -21,8 +21,9 @@ export class OllamaHandler implements ApiHandler {
|
||||
|
||||
try {
|
||||
// Create a promise that rejects after timeout
|
||||
const timeoutMs = this.options.requestTimeoutMs || 30000
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => reject(new Error("Ollama request timed out after 120 seconds")), 120000)
|
||||
setTimeout(() => reject(new Error(`Ollama request timed out after ${timeoutMs / 1000} seconds`)), timeoutMs)
|
||||
})
|
||||
|
||||
// Create the actual API request promise
|
||||
@@ -63,7 +64,8 @@ export class OllamaHandler implements ApiHandler {
|
||||
} catch (error: any) {
|
||||
// Check if it's a timeout error
|
||||
if (error.message && error.message.includes("timed out")) {
|
||||
throw new Error("Ollama request timed out after 120 seconds")
|
||||
const timeoutMs = this.options.requestTimeoutMs || 30000
|
||||
throw new Error(`Ollama request timed out after ${timeoutMs / 1000} seconds`)
|
||||
}
|
||||
|
||||
// Enhance error reporting
|
||||
|
||||
@@ -76,6 +76,8 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
// @ts-ignore-next-line
|
||||
@@ -103,8 +105,9 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
// console.log("OpenRouter generation details:", generation)
|
||||
return {
|
||||
type: "usage",
|
||||
// cacheWriteTokens: 0,
|
||||
// cacheReadTokens: 0,
|
||||
// at this time there's no support for gatting cached_tokens from generation endpoint
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: 0,
|
||||
// openrouter generation endpoint fails often
|
||||
inputTokens: generation?.native_tokens_prompt || 0,
|
||||
outputTokens: generation?.native_tokens_completion || 0,
|
||||
|
||||
@@ -20,7 +20,8 @@ export async function createOpenRouterStream(
|
||||
]
|
||||
|
||||
// prompt caching: https://openrouter.ai/docs/prompt-caching
|
||||
// this is specifically for claude models (some models may 'support prompt caching' automatically without this)
|
||||
// this was initially specifically for claude models (some models may 'support prompt caching' automatically without this)
|
||||
// gemini models only use the last breakpoint for caching, so the others will be ignored
|
||||
switch (model.id) {
|
||||
case "anthropic/claude-3.7-sonnet":
|
||||
case "anthropic/claude-3.7-sonnet:beta":
|
||||
@@ -39,6 +40,10 @@ export async function createOpenRouterStream(
|
||||
case "anthropic/claude-3-haiku:beta":
|
||||
case "anthropic/claude-3-opus":
|
||||
case "anthropic/claude-3-opus:beta":
|
||||
case "google/gemini-2.5-pro-preview-03-25":
|
||||
case "google/gemini-2.0-flash-001":
|
||||
case "google/gemini-flash-1.5":
|
||||
case "google/gemini-pro-1.5":
|
||||
openAiMessages[0] = {
|
||||
role: "system",
|
||||
content: [
|
||||
|
||||
@@ -24,6 +24,8 @@ export const toolUseNames = [
|
||||
"load_mcp_documentation",
|
||||
"attempt_completion",
|
||||
"new_task",
|
||||
"condense",
|
||||
"new_rule",
|
||||
] as const
|
||||
|
||||
// Converts array of tool call names into a union type ("execute_command" | "read_file" | ...)
|
||||
|
||||
@@ -55,7 +55,10 @@ export function parseAssistantMessage(assistantMessage: string) {
|
||||
|
||||
// special case for write_to_file where file contents could contain the closing tag, in which case the param would have closed and we end up with the rest of the file contents here. To work around this, we get the string between the starting content tag and the LAST content tag.
|
||||
const contentParamName: ToolParamName = "content"
|
||||
if (currentToolUse.name === "write_to_file" && accumulator.endsWith(`</${contentParamName}>`)) {
|
||||
if (
|
||||
(currentToolUse.name === "write_to_file" || currentToolUse.name === "new_rule") &&
|
||||
accumulator.endsWith(`</${contentParamName}>`)
|
||||
) {
|
||||
const toolContent = accumulator.slice(currentToolUseStartIndex)
|
||||
const contentStartTag = `<${contentParamName}>`
|
||||
const contentEndTag = `</${contentParamName}>`
|
||||
|
||||
@@ -193,14 +193,20 @@ export class ContextManager {
|
||||
public getNextTruncationRange(
|
||||
apiMessages: Anthropic.Messages.MessageParam[],
|
||||
currentDeletedRange: [number, number] | undefined,
|
||||
keep: "half" | "quarter",
|
||||
keep: "none" | "lastTwo" | "half" | "quarter",
|
||||
): [number, number] {
|
||||
// We always keep the first user-assistant pairing, and truncate an even number of messages from there
|
||||
const rangeStartIndex = 2 // index 0 and 1 are kept
|
||||
const startOfRest = currentDeletedRange ? currentDeletedRange[1] + 1 : 2 // inclusive starting index
|
||||
|
||||
let messagesToRemove: number
|
||||
if (keep === "half") {
|
||||
if (keep === "none") {
|
||||
// Removes all messages beyond the first core user/assistant message pair
|
||||
messagesToRemove = Math.max(apiMessages.length - startOfRest, 0)
|
||||
} else if (keep === "lastTwo") {
|
||||
// Keep the last user-assistant pair in addition to the first core user/assistant message pair
|
||||
messagesToRemove = Math.max(apiMessages.length - startOfRest - 2, 0)
|
||||
} else if (keep === "half") {
|
||||
// Remove half of remaining user-assistant pairs
|
||||
// We first calculate half of the messages then divide by 2 to get the number of pairs.
|
||||
// After flooring, we multiply by 2 to get the number of messages.
|
||||
@@ -382,6 +388,17 @@ export class ContextManager {
|
||||
return [contextHistoryUpdated, uniqueFileReadIndices]
|
||||
}
|
||||
|
||||
/**
|
||||
* Public function for triggering potentially setting the truncation message
|
||||
* If the truncation message already exists, does nothing, otherwise adds the message
|
||||
*/
|
||||
async triggerApplyStandardContextTruncationNoticeChange(timestamp: number, taskDirectory: string) {
|
||||
const updated = this.applyStandardContextTruncationNoticeChange(timestamp)
|
||||
if (updated) {
|
||||
await this.saveContextHistory(taskDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if there is any truncation and there is no other alteration already set, alter the assistant message to indicate this occurred
|
||||
*/
|
||||
|
||||
@@ -34,24 +34,24 @@ describe("ContextManager", () => {
|
||||
const messages = createMessages(11)
|
||||
const result = contextManager.getNextTruncationRange(messages, undefined, "half")
|
||||
|
||||
expect(result).to.deep.equal([1, 4])
|
||||
expect(result).to.deep.equal([2, 5])
|
||||
})
|
||||
|
||||
it("first truncation with quarter keep", () => {
|
||||
const messages = createMessages(11)
|
||||
const result = contextManager.getNextTruncationRange(messages, undefined, "quarter")
|
||||
|
||||
expect(result).to.deep.equal([1, 6])
|
||||
expect(result).to.deep.equal([2, 7])
|
||||
})
|
||||
|
||||
it("sequential truncation with half keep", () => {
|
||||
const messages = createMessages(21)
|
||||
const firstRange = contextManager.getNextTruncationRange(messages, undefined, "half")
|
||||
expect(firstRange).to.deep.equal([1, 10])
|
||||
expect(firstRange).to.deep.equal([2, 9])
|
||||
|
||||
// Pass the previous range for sequential truncation
|
||||
const secondRange = contextManager.getNextTruncationRange(messages, firstRange, "half")
|
||||
expect(secondRange).to.deep.equal([1, 14])
|
||||
expect(secondRange).to.deep.equal([2, 13])
|
||||
})
|
||||
|
||||
it("sequential truncation with quarter keep", () => {
|
||||
@@ -60,7 +60,7 @@ describe("ContextManager", () => {
|
||||
|
||||
const secondRange = contextManager.getNextTruncationRange(messages, firstRange, "quarter")
|
||||
|
||||
expect(secondRange[0]).to.equal(1)
|
||||
expect(secondRange[0]).to.equal(2)
|
||||
expect(secondRange[1]).to.be.greaterThan(firstRange[1])
|
||||
})
|
||||
|
||||
@@ -68,20 +68,20 @@ describe("ContextManager", () => {
|
||||
const messages = createMessages(14)
|
||||
const result = contextManager.getNextTruncationRange(messages, undefined, "half")
|
||||
|
||||
// Check if the message at the end of range is a user message
|
||||
// Check if the message at the end of range is an assistant message
|
||||
const lastRemovedMessage = messages[result[1]]
|
||||
expect(lastRemovedMessage.role).to.equal("user")
|
||||
expect(lastRemovedMessage.role).to.equal("assistant")
|
||||
|
||||
// Check if the next message after the range is an assistant message
|
||||
// Check if the next message after the range is a user message
|
||||
const nextMessage = messages[result[1] + 1]
|
||||
expect(nextMessage.role).to.equal("assistant")
|
||||
expect(nextMessage.role).to.equal("user")
|
||||
})
|
||||
|
||||
it("handles small message arrays", () => {
|
||||
const messages = createMessages(3)
|
||||
const result = contextManager.getNextTruncationRange(messages, undefined, "half")
|
||||
|
||||
expect(result).to.deep.equal([1, 0])
|
||||
expect(result).to.deep.equal([2, 1])
|
||||
})
|
||||
|
||||
it("preserves the message structure when truncating", () => {
|
||||
@@ -120,9 +120,10 @@ describe("ContextManager", () => {
|
||||
const range: [number, number] = [1, 3]
|
||||
const result = contextManager.getTruncatedMessages(messages, range)
|
||||
|
||||
expect(result).to.have.lengthOf(2)
|
||||
expect(result).to.have.lengthOf(3)
|
||||
expect(result[0]).to.deep.equal(messages[0])
|
||||
expect(result[1]).to.deep.equal(messages[4])
|
||||
expect(result[1]).to.deep.equal(messages[1])
|
||||
expect(result[2]).to.deep.equal(messages[4])
|
||||
})
|
||||
|
||||
it("works with a range that starts at the first message after task", () => {
|
||||
@@ -131,20 +132,21 @@ describe("ContextManager", () => {
|
||||
const range: [number, number] = [1, 2]
|
||||
const result = contextManager.getTruncatedMessages(messages, range)
|
||||
|
||||
expect(result).to.have.lengthOf(2)
|
||||
expect(result).to.have.lengthOf(3)
|
||||
expect(result[0]).to.deep.equal(messages[0])
|
||||
expect(result[1]).to.deep.equal(messages[3])
|
||||
expect(result[1]).to.deep.equal(messages[1])
|
||||
expect(result[2]).to.deep.equal(messages[3])
|
||||
})
|
||||
|
||||
it("correctly handles removing a range while preserving alternation pattern", () => {
|
||||
const messages = createMessages(5)
|
||||
|
||||
const range: [number, number] = [1, 2]
|
||||
const range: [number, number] = [2, 3]
|
||||
const result = contextManager.getTruncatedMessages(messages, range)
|
||||
|
||||
expect(result).to.have.lengthOf(3)
|
||||
expect(result[0]).to.deep.equal(messages[0])
|
||||
expect(result[1]).to.deep.equal(messages[3])
|
||||
expect(result[1]).to.deep.equal(messages[1])
|
||||
expect(result[2]).to.deep.equal(messages[4])
|
||||
|
||||
expect(result[0].role).to.equal("user")
|
||||
|
||||
@@ -76,26 +76,6 @@ describe("ModelContextTracker", () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw an error when controller is dereferenced", async () => {
|
||||
// Create a new tracker with a controller that will be garbage collected
|
||||
const weakTracker = new ModelContextTracker(mockContext, taskId)
|
||||
|
||||
// Force the WeakRef to return null by overriding the deref method
|
||||
const weakRef = { deref: sandbox.stub().returns(null) }
|
||||
sandbox.stub(WeakRef.prototype, "deref").callsFake(() => weakRef.deref())
|
||||
|
||||
try {
|
||||
// Try to call the method - this should throw
|
||||
await weakTracker.recordModelUsage("any-provider", "any-model", "any-mode")
|
||||
|
||||
// If we get here, the test should fail
|
||||
expect.fail("Expected an error to be thrown")
|
||||
} catch (error) {
|
||||
// Verify the error message
|
||||
expect(error.message).to.equal("Unable to access extension context")
|
||||
}
|
||||
})
|
||||
|
||||
it("should append model usage to existing entries", async () => {
|
||||
// Add an existing model usage entry
|
||||
const existingTimestamp = 1617200000000
|
||||
|
||||
@@ -7,6 +7,45 @@ import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { getGlobalState, getWorkspaceState, updateGlobalState, updateWorkspaceState } from "@core/storage/state"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
/**
|
||||
* Converts .clinerules file to directory and places old .clinerule file inside directory, renaming it
|
||||
* Doesn't do anything if .clinerules dir already exists or doesn't exist
|
||||
* Returns whether there are any uncaught errors
|
||||
*/
|
||||
export async function ensureLocalClinerulesDirExists(cwd: string): Promise<boolean> {
|
||||
const clinerulePath = path.resolve(cwd, GlobalFileNames.clineRules)
|
||||
const defaultRuleFilename = "default-rules.md"
|
||||
|
||||
try {
|
||||
const exists = await fileExistsAtPath(clinerulePath)
|
||||
|
||||
if (exists && !(await isDirectory(clinerulePath))) {
|
||||
// logic to convert .clinerules file into directory, and rename the rules file to {defaultRuleFilename}
|
||||
const content = await fs.readFile(clinerulePath, "utf8")
|
||||
const tempPath = clinerulePath + ".bak"
|
||||
await fs.rename(clinerulePath, tempPath) // create backup
|
||||
try {
|
||||
await fs.mkdir(clinerulePath, { recursive: true })
|
||||
await fs.writeFile(path.join(clinerulePath, defaultRuleFilename), content, "utf8")
|
||||
await fs.unlink(tempPath).catch(() => {}) // delete backup
|
||||
|
||||
return false // conversion successful with no errors
|
||||
} catch (conversionError) {
|
||||
// attempt to restore backup on conversion failure
|
||||
try {
|
||||
await fs.rm(clinerulePath, { recursive: true, force: true }).catch(() => {})
|
||||
await fs.rename(tempPath, clinerulePath) // restore backup
|
||||
} catch (restoreError) {}
|
||||
return true // in either case here we consider this an error
|
||||
}
|
||||
}
|
||||
// exists and is a dir or doesn't exist, either of these cases we dont need to handle here
|
||||
return false
|
||||
} catch (error) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export const getGlobalClineRules = async (globalClineRulesFilePath: string, toggles: ClineRulesToggles) => {
|
||||
if (await fileExistsAtPath(globalClineRulesFilePath)) {
|
||||
if (await isDirectory(globalClineRulesFilePath)) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as vscode from "vscode"
|
||||
import crypto from "crypto"
|
||||
import { Controller } from "../index"
|
||||
import { storeSecret } from "../../storage/state"
|
||||
|
||||
/**
|
||||
* Handles the user clicking the login link in the UI.
|
||||
* Generates a secure nonce for state validation, stores it in secrets,
|
||||
* and opens the authentication URL in the external browser.
|
||||
*
|
||||
* @param controller The controller instance.
|
||||
* @returns The login URL as a string.
|
||||
*/
|
||||
export async function accountLoginClicked(controller: Controller): Promise<String> {
|
||||
// Generate nonce for state validation
|
||||
const nonce = crypto.randomBytes(32).toString("hex")
|
||||
await storeSecret(controller.context, "authNonce", nonce)
|
||||
|
||||
// Open browser for authentication with state param
|
||||
console.log("Login button clicked in account page")
|
||||
console.log("Opening auth page with state param")
|
||||
|
||||
const uriScheme = vscode.env.uriScheme
|
||||
|
||||
const authUrl = vscode.Uri.parse(
|
||||
`https://app.cline.bot/auth?state=${encodeURIComponent(nonce)}&callback_url=${encodeURIComponent(`${uriScheme || "vscode"}://saoudrizwan.claude-dev/auth`)}`,
|
||||
)
|
||||
vscode.env.openExternal(authUrl)
|
||||
return authUrl.toString()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
const accountService = createServiceRegistry("account")
|
||||
|
||||
// Export the method handler type and registration function
|
||||
export type AccountMethodHandler = ServiceMethodHandler
|
||||
export const registerMethod = accountService.registerMethod
|
||||
|
||||
// Export the request handler
|
||||
export const handleAccountServiceRequest = accountService.handleRequest
|
||||
|
||||
// Register all account methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,12 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { accountLoginClicked } from "./accountLoginClicked"
|
||||
|
||||
// Register all account service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("accountLoginClicked", accountLoginClicked)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ChromePath } from "../../../shared/proto/browser"
|
||||
import { EmptyRequest } from "../../../shared/proto/common"
|
||||
import { Controller } from "../index"
|
||||
import { getAllExtensionState } from "../../storage/state"
|
||||
import { BrowserSession } from "../../../services/browser/BrowserSession"
|
||||
|
||||
/**
|
||||
* Get the detected Chrome executable path
|
||||
* @param controller The controller instance
|
||||
* @param request The empty request message
|
||||
* @returns The detected Chrome path and whether it's bundled
|
||||
*/
|
||||
export async function getDetectedChromePath(controller: Controller, request: EmptyRequest): Promise<ChromePath> {
|
||||
try {
|
||||
const { browserSettings } = await getAllExtensionState(controller.context)
|
||||
const browserSession = new BrowserSession(controller.context, browserSettings)
|
||||
const result = await browserSession.getDetectedChromePath()
|
||||
|
||||
return {
|
||||
path: result.path,
|
||||
isBundled: result.isBundled,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error getting detected Chrome path:", error)
|
||||
return {
|
||||
path: "",
|
||||
isBundled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,16 @@
|
||||
import { registerMethod } from "./index"
|
||||
import { discoverBrowser } from "./discoverBrowser"
|
||||
import { getBrowserConnectionInfo } from "./getBrowserConnectionInfo"
|
||||
import { getDetectedChromePath } from "./getDetectedChromePath"
|
||||
import { testBrowserConnection } from "./testBrowserConnection"
|
||||
import { updateBrowserSettings } from "./updateBrowserSettings"
|
||||
|
||||
// Register all browser service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("discoverBrowser", discoverBrowser)
|
||||
registerMethod("getBrowserConnectionInfo", getBrowserConnectionInfo)
|
||||
registerMethod("getDetectedChromePath", getDetectedChromePath)
|
||||
registerMethod("testBrowserConnection", testBrowserConnection)
|
||||
registerMethod("updateBrowserSettings", updateBrowserSettings)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { UpdateBrowserSettingsRequest } from "../../../shared/proto/browser"
|
||||
import { Boolean } from "../../../shared/proto/common"
|
||||
import { Controller } from "../index"
|
||||
import { updateGlobalState } from "../../storage/state"
|
||||
import { BrowserSettings as SharedBrowserSettings } from "../../../shared/BrowserSettings"
|
||||
|
||||
/**
|
||||
* Update browser settings
|
||||
* @param controller The controller instance
|
||||
* @param request The browser settings request message
|
||||
* @returns Success response
|
||||
*/
|
||||
export async function updateBrowserSettings(controller: Controller, request: UpdateBrowserSettingsRequest): Promise<Boolean> {
|
||||
try {
|
||||
// Convert from protobuf format to shared format
|
||||
const browserSettings: SharedBrowserSettings = {
|
||||
viewport: {
|
||||
width: request.viewport?.width || 900,
|
||||
height: request.viewport?.height || 600,
|
||||
},
|
||||
remoteBrowserEnabled: request.remoteBrowserEnabled || false,
|
||||
remoteBrowserHost: request.remoteBrowserHost || undefined,
|
||||
}
|
||||
|
||||
// Update global state with new settings
|
||||
await updateGlobalState(controller.context, "browserSettings", browserSettings)
|
||||
|
||||
// Update task browser settings if task exists
|
||||
if (controller.task) {
|
||||
controller.task.browserSettings = browserSettings
|
||||
controller.task.browserSession.browserSettings = browserSettings
|
||||
}
|
||||
|
||||
// Post updated state to webview
|
||||
await controller.postStateToWebview()
|
||||
|
||||
return {
|
||||
value: true,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating browser settings:", error)
|
||||
return {
|
||||
value: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Controller } from ".."
|
||||
import { ClineCheckpointRestore } from "../../../shared/WebviewMessage"
|
||||
import { CheckpointRestoreRequest } from "../../../shared/proto/checkpoints"
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import pWaitFor from "p-wait-for"
|
||||
|
||||
export async function checkpointRestore(controller: Controller, request: CheckpointRestoreRequest): Promise<Empty> {
|
||||
await controller.cancelTask() // we cannot alter message history say if the task is active, as it could be in the middle of editing a file or running a command, which expect the ask to be responded to rather than being superseded by a new message eg add deleted_api_reqs
|
||||
|
||||
if (request.number) {
|
||||
// wait for messages to be loaded
|
||||
await pWaitFor(() => controller.task?.isInitialized === true, {
|
||||
timeout: 3_000,
|
||||
}).catch(() => {
|
||||
console.error("Failed to init new cline instance")
|
||||
})
|
||||
|
||||
// NOTE: cancelTask awaits abortTask, which awaits diffViewProvider.revertChanges, which reverts any edited files, allowing us to reset to a checkpoint rather than running into a state where the revertChanges function is called alongside or after the checkpoint reset
|
||||
await controller.task?.restoreCheckpoint(request.number, request.restoreType as ClineCheckpointRestore, request.offset)
|
||||
}
|
||||
return {}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export type CheckpointsMethodHandler = ServiceMethodHandler
|
||||
export const registerMethod = checkpointsService.registerMethod
|
||||
|
||||
// Export the request handler
|
||||
export const handleCheckpointsDiffServiceRequest = checkpointsService.handleRequest
|
||||
export const handleCheckpointsServiceRequest = checkpointsService.handleRequest
|
||||
|
||||
// Register all checkpoints methods
|
||||
registerAllMethods()
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { checkpointDiff } from "./checkpointDiff"
|
||||
import { checkpointRestore } from "./checkpointRestore"
|
||||
|
||||
// Register all checkpoints service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("checkpointDiff", checkpointDiff)
|
||||
registerMethod("checkpointRestore", checkpointRestore)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create file service registry
|
||||
const fileService = createServiceRegistry("file")
|
||||
|
||||
// Export the method handler type and registration function
|
||||
export type FileMethodHandler = ServiceMethodHandler
|
||||
export const registerMethod = fileService.registerMethod
|
||||
|
||||
// Export the request handler
|
||||
export const handleFileServiceRequest = fileService.handleRequest
|
||||
|
||||
// Register all file methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,14 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { openFile } from "./openFile"
|
||||
import { openImage } from "./openImage"
|
||||
|
||||
// Register all file service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("openFile", openFile)
|
||||
registerMethod("openImage", openImage)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty, StringRequest } from "@shared/proto/common"
|
||||
import { openFile as openFileIntegration } from "@integrations/misc/open-file"
|
||||
import { FileMethodHandler } from "./index"
|
||||
|
||||
/**
|
||||
* Opens a file in the editor
|
||||
* @param controller The controller instance
|
||||
* @param request The request message containing the file path in the 'value' field
|
||||
* @returns Empty response
|
||||
*/
|
||||
export const openFile: FileMethodHandler = async (controller: Controller, request: StringRequest): Promise<Empty> => {
|
||||
if (request.value) {
|
||||
openFileIntegration(request.value)
|
||||
}
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty, StringRequest } from "@shared/proto/common"
|
||||
import { openImage as openImageIntegration } from "@integrations/misc/open-file"
|
||||
import { FileMethodHandler } from "./index"
|
||||
|
||||
/**
|
||||
* Opens an image in the system viewer
|
||||
* @param controller The controller instance
|
||||
* @param request The request message containing the image path or data URI in the 'value' field
|
||||
* @returns Empty response
|
||||
*/
|
||||
export const openImage: FileMethodHandler = async (controller: Controller, request: StringRequest): Promise<Empty> => {
|
||||
if (request.value) {
|
||||
await openImageIntegration(request.value)
|
||||
}
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Controller } from "./index"
|
||||
import { handleAccountServiceRequest } from "./account"
|
||||
import { handleBrowserServiceRequest } from "./browser/index"
|
||||
import { handleCheckpointsDiffServiceRequest } from "./checkpoints"
|
||||
import { handleFileServiceRequest } from "./file"
|
||||
import { handleTaskServiceRequest } from "./task"
|
||||
import { handleCheckpointsServiceRequest } from "./checkpoints"
|
||||
import { handleMcpServiceRequest } from "./mcp"
|
||||
|
||||
/**
|
||||
@@ -29,6 +32,11 @@ export class GrpcHandler {
|
||||
}> {
|
||||
try {
|
||||
switch (service) {
|
||||
case "cline.AccountService":
|
||||
return {
|
||||
message: await handleAccountServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
case "cline.BrowserService":
|
||||
return {
|
||||
message: await handleBrowserServiceRequest(this.controller, method, message),
|
||||
@@ -36,7 +44,17 @@ export class GrpcHandler {
|
||||
}
|
||||
case "cline.CheckpointsService":
|
||||
return {
|
||||
message: await handleCheckpointsDiffServiceRequest(this.controller, method, message),
|
||||
message: await handleCheckpointsServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
case "cline.FileService":
|
||||
return {
|
||||
message: await handleFileServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
case "cline.TaskService":
|
||||
return {
|
||||
message: await handleTaskServiceRequest(this.controller, method, message),
|
||||
request_id: requestId,
|
||||
}
|
||||
case "cline.McpService":
|
||||
|
||||
+71
-124
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import axios from "axios"
|
||||
import type { AxiosRequestConfig } from "axios"
|
||||
import crypto from "crypto"
|
||||
|
||||
import fs from "fs/promises"
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import pWaitFor from "p-wait-for"
|
||||
@@ -12,7 +12,7 @@ import { buildApiHandler } from "@api/index"
|
||||
import { cleanupLegacyCheckpoints } from "@integrations/checkpoints/CheckpointMigration"
|
||||
import { downloadTask } from "@integrations/misc/export-markdown"
|
||||
import { fetchOpenGraphData, isImageUrl } from "@integrations/misc/link-preview"
|
||||
import { openFile, openImage } from "@integrations/misc/open-file"
|
||||
import { handleFileServiceRequest } from "./file"
|
||||
import { selectImages } from "@integrations/misc/process-images"
|
||||
import { getTheme } from "@integrations/theme/getTheme"
|
||||
import WorkspaceTracker from "@integrations/workspace/WorkspaceTracker"
|
||||
@@ -136,8 +136,14 @@ export class Controller {
|
||||
|
||||
async initTask(task?: string, images?: 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
|
||||
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } =
|
||||
await getAllExtensionState(this.context)
|
||||
const {
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
shellIntegrationTimeout,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
if (autoApprovalSettings) {
|
||||
const updatedAutoApprovalSettings = {
|
||||
@@ -159,6 +165,7 @@ export class Controller {
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
shellIntegrationTimeout,
|
||||
customInstructions,
|
||||
task,
|
||||
images,
|
||||
@@ -186,28 +193,6 @@ export class Controller {
|
||||
*/
|
||||
async handleWebviewMessage(message: WebviewMessage) {
|
||||
switch (message.type) {
|
||||
case "addRemoteServer": {
|
||||
try {
|
||||
await this.mcpHub?.addRemoteServer(message.serverName!, message.serverUrl!)
|
||||
await this.postMessageToWebview({
|
||||
type: "addRemoteServerResult",
|
||||
addRemoteServerResult: {
|
||||
success: true,
|
||||
serverName: message.serverName!,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
await this.postMessageToWebview({
|
||||
type: "addRemoteServerResult",
|
||||
addRemoteServerResult: {
|
||||
success: false,
|
||||
serverName: message.serverName!,
|
||||
error: error.message,
|
||||
},
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case "authStateChanged":
|
||||
await this.setUserInfo(message.user || undefined)
|
||||
await this.postStateToWebview()
|
||||
@@ -284,6 +269,9 @@ export class Controller {
|
||||
// initializing new instance of Cline will make sure that any agentically running promises in old instance don't affect our new task. this essentially creates a fresh slate for the new task
|
||||
await this.initTask(message.text, message.images)
|
||||
break
|
||||
case "condense":
|
||||
this.task?.handleWebviewAskResponse("yesButtonClicked")
|
||||
break
|
||||
case "apiConfiguration":
|
||||
if (message.apiConfiguration) {
|
||||
await updateApiConfiguration(this.context, message.apiConfiguration)
|
||||
@@ -307,22 +295,6 @@ export class Controller {
|
||||
}
|
||||
}
|
||||
break
|
||||
case "browserSettings":
|
||||
if (message.browserSettings) {
|
||||
// remoteBrowserEnabled now means "enable remote browser connection"
|
||||
// commenting out since this is being done in BrowserSettingsSection updateRemoteBrowserEnabled
|
||||
// if (!message.browserSettings.remoteBrowserEnabled) {
|
||||
// // If disabling remote browser connection, clear the remoteBrowserHost
|
||||
// message.browserSettings.remoteBrowserHost = undefined
|
||||
// }
|
||||
await updateGlobalState(this.context, "browserSettings", message.browserSettings)
|
||||
if (this.task) {
|
||||
this.task.browserSettings = message.browserSettings
|
||||
this.task.browserSession.browserSettings = message.browserSettings
|
||||
}
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
break
|
||||
case "togglePlanActMode":
|
||||
if (message.chatSettings) {
|
||||
await this.togglePlanActModeWithChatSettings(message.chatSettings, message.chatContent)
|
||||
@@ -343,11 +315,6 @@ export class Controller {
|
||||
case "askResponse":
|
||||
this.task?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
|
||||
break
|
||||
case "clearTask":
|
||||
// newTask will start a new task with a given task text, while clear task resets the current session and allows for a new task to be started
|
||||
await this.clearTask()
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "didShowAnnouncement":
|
||||
await updateGlobalState(this.context, "lastShownAnnouncementId", this.latestAnnouncementId)
|
||||
await this.postStateToWebview()
|
||||
@@ -410,9 +377,6 @@ export class Controller {
|
||||
await refreshClineRulesToggles(this.context, cwd)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "openImage":
|
||||
openImage(message.text!)
|
||||
break
|
||||
case "openInBrowser":
|
||||
if (message.url) {
|
||||
vscode.env.openExternal(vscode.Uri.parse(message.url))
|
||||
@@ -424,9 +388,6 @@ export class Controller {
|
||||
case "checkIsImageUrl":
|
||||
this.checkIsImageUrl(message.text!)
|
||||
break
|
||||
case "openFile":
|
||||
openFile(message.text!)
|
||||
break
|
||||
case "createRuleFile":
|
||||
if (typeof message.isGlobal !== "boolean" || typeof message.filename !== "string" || !message.filename) {
|
||||
console.error("createRuleFile: Missing or invalid parameters", {
|
||||
@@ -440,13 +401,13 @@ export class Controller {
|
||||
if (fileExists && filePath) {
|
||||
vscode.window.showWarningMessage(`Rule file "${message.filename}" already exists.`)
|
||||
// Still open it for editing
|
||||
openFile(filePath)
|
||||
await handleFileServiceRequest(this, "openFile", { value: filePath })
|
||||
return
|
||||
} else if (filePath && !fileExists) {
|
||||
await refreshClineRulesToggles(this.context, cwd)
|
||||
await this.postStateToWebview()
|
||||
|
||||
openFile(filePath)
|
||||
await handleFileServiceRequest(this, "openFile", { value: filePath })
|
||||
|
||||
vscode.window.showInformationMessage(
|
||||
`Created new ${message.isGlobal ? "global" : "workspace"} rule file: ${message.filename}`,
|
||||
@@ -460,50 +421,15 @@ export class Controller {
|
||||
case "openMention":
|
||||
openMention(message.text)
|
||||
break
|
||||
case "checkpointRestore": {
|
||||
await this.cancelTask() // we cannot alter message history say if the task is active, as it could be in the middle of editing a file or running a command, which expect the ask to be responded to rather than being superseded by a new message eg add deleted_api_reqs
|
||||
// cancel task waits for any open editor to be reverted and starts a new cline instance
|
||||
if (message.number) {
|
||||
// wait for messages to be loaded
|
||||
await pWaitFor(() => this.task?.isInitialized === true, {
|
||||
timeout: 3_000,
|
||||
}).catch(() => {
|
||||
console.error("Failed to init new cline instance")
|
||||
})
|
||||
// NOTE: cancelTask awaits abortTask, which awaits diffViewProvider.revertChanges, which reverts any edited files, allowing us to reset to a checkpoint rather than running into a state where the revertChanges function is called alongside or after the checkpoint reset
|
||||
await this.task?.restoreCheckpoint(message.number, message.text! as ClineCheckpointRestore, message.offset)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "taskCompletionViewChanges": {
|
||||
if (message.number) {
|
||||
await this.task?.presentMultifileDiff(message.number, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "cancelTask":
|
||||
this.cancelTask()
|
||||
break
|
||||
case "getLatestState":
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "accountLoginClicked": {
|
||||
// Generate nonce for state validation
|
||||
const nonce = crypto.randomBytes(32).toString("hex")
|
||||
await storeSecret(this.context, "authNonce", nonce)
|
||||
|
||||
// Open browser for authentication with state param
|
||||
console.log("Login button clicked in account page")
|
||||
console.log("Opening auth page with state param")
|
||||
|
||||
const uriScheme = vscode.env.uriScheme
|
||||
|
||||
const authUrl = vscode.Uri.parse(
|
||||
`https://app.cline.bot/auth?state=${encodeURIComponent(nonce)}&callback_url=${encodeURIComponent(`${uriScheme || "vscode"}://saoudrizwan.claude-dev/auth`)}`,
|
||||
)
|
||||
vscode.env.openExternal(authUrl)
|
||||
break
|
||||
}
|
||||
case "accountLogoutClicked": {
|
||||
await this.handleSignOut()
|
||||
break
|
||||
@@ -516,14 +442,10 @@ export class Controller {
|
||||
await this.fetchUserCreditsData()
|
||||
break
|
||||
}
|
||||
case "showMcpView": {
|
||||
await this.postMessageToWebview({ type: "action", action: "mcpButtonClicked", tab: message.tab || undefined })
|
||||
break
|
||||
}
|
||||
case "openMcpSettings": {
|
||||
const mcpSettingsFilePath = await this.mcpHub?.getMcpSettingsFilePath()
|
||||
if (mcpSettingsFilePath) {
|
||||
openFile(mcpSettingsFilePath)
|
||||
await handleFileServiceRequest(this, "openFile", { value: mcpSettingsFilePath })
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -682,16 +604,6 @@ export class Controller {
|
||||
}
|
||||
break
|
||||
}
|
||||
case "updateMcpTimeout": {
|
||||
try {
|
||||
if (message.serverName && message.timeout) {
|
||||
await this.mcpHub?.updateServerTimeout(message.serverName, message.timeout)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to update timeout for server ${message.serverName}:`, error)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "openExtensionSettings": {
|
||||
const settingsFilter = message.text || ""
|
||||
await vscode.commands.executeCommand(
|
||||
@@ -764,21 +676,6 @@ export class Controller {
|
||||
this.postMessageToWebview({ type: "relinquishControl" })
|
||||
break
|
||||
}
|
||||
case "getDetectedChromePath": {
|
||||
try {
|
||||
const { browserSettings } = await getAllExtensionState(this.context)
|
||||
const browserSession = new BrowserSession(this.context, browserSettings)
|
||||
const { path, isBundled } = await browserSession.getDetectedChromePath()
|
||||
await this.postMessageToWebview({
|
||||
type: "detectedChromePath",
|
||||
text: path,
|
||||
isBundled,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error getting detected Chrome path:", error)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "getRelativePaths": {
|
||||
if (message.uris && message.uris.length > 0) {
|
||||
const resolvedPaths = await Promise.all(
|
||||
@@ -885,6 +782,30 @@ export class Controller {
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "copyToClipboard": {
|
||||
try {
|
||||
await vscode.env.clipboard.writeText(message.text || "")
|
||||
} catch (error) {
|
||||
console.error("Error copying to clipboard:", error)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "updateTerminalConnectionTimeout": {
|
||||
if (message.shellIntegrationTimeout !== undefined) {
|
||||
const timeout = message.shellIntegrationTimeout
|
||||
|
||||
if (typeof timeout === "number" && !isNaN(timeout) && timeout > 0) {
|
||||
await updateGlobalState(this.context, "shellIntegrationTimeout", timeout)
|
||||
await this.postStateToWebview()
|
||||
} else {
|
||||
console.warn(
|
||||
`Invalid shell integration timeout value received: ${timeout}. ` + `Expected a positive number.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
// Add more switch case statements here as more webview message commands
|
||||
// are created within the webview context (i.e. inside media/main.js)
|
||||
}
|
||||
@@ -911,6 +832,8 @@ export class Controller {
|
||||
previousModeVsCodeLmModelSelector: newVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens: newThinkingBudgetTokens,
|
||||
previousModeReasoningEffort: newReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected: newAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId: newAwsBedrockCustomModelBaseId,
|
||||
planActSeparateModelsSetting,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
@@ -923,7 +846,6 @@ export class Controller {
|
||||
await updateGlobalState(this.context, "previousModeReasoningEffort", apiConfiguration.reasoningEffort)
|
||||
switch (apiConfiguration.apiProvider) {
|
||||
case "anthropic":
|
||||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "asksage":
|
||||
@@ -933,6 +855,19 @@ export class Controller {
|
||||
case "xai":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
break
|
||||
case "bedrock":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeAwsBedrockCustomSelected",
|
||||
apiConfiguration.awsBedrockCustomSelected,
|
||||
)
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeAwsBedrockCustomModelBaseId",
|
||||
apiConfiguration.awsBedrockCustomModelBaseId,
|
||||
)
|
||||
break
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.openRouterModelId)
|
||||
@@ -978,7 +913,6 @@ export class Controller {
|
||||
await updateGlobalState(this.context, "reasoningEffort", newReasoningEffort)
|
||||
switch (newApiProvider) {
|
||||
case "anthropic":
|
||||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "asksage":
|
||||
@@ -988,6 +922,11 @@ export class Controller {
|
||||
case "xai":
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
break
|
||||
case "bedrock":
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
await updateGlobalState(this.context, "awsBedrockCustomSelected", newAwsBedrockCustomSelected)
|
||||
await updateGlobalState(this.context, "awsBedrockCustomModelBaseId", newAwsBedrockCustomModelBaseId)
|
||||
break
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
await updateGlobalState(this.context, "openRouterModelId", newModelId)
|
||||
@@ -1493,7 +1432,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
case "anthropic/claude-3.5-sonnet":
|
||||
case "anthropic/claude-3.5-sonnet:beta":
|
||||
// NOTE: this needs to be synced with api.ts/openrouter default model info
|
||||
modelInfo.supportsComputerUse = true
|
||||
modelInfo.supportsPromptCache = true
|
||||
modelInfo.cacheWritesPrice = 3.75
|
||||
modelInfo.cacheReadsPrice = 0.3
|
||||
@@ -1535,6 +1473,14 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
modelInfo.cacheWritesPrice = 0.14
|
||||
modelInfo.cacheReadsPrice = 0.014
|
||||
break
|
||||
case "google/gemini-2.5-pro-preview-03-25":
|
||||
case "google/gemini-2.0-flash-001":
|
||||
case "google/gemini-flash-1.5":
|
||||
case "google/gemini-pro-1.5":
|
||||
modelInfo.supportsPromptCache = true
|
||||
modelInfo.cacheWritesPrice = parsePrice(rawModel.pricing?.input_cache_write)
|
||||
modelInfo.cacheReadsPrice = parsePrice(rawModel.pricing?.input_cache_read)
|
||||
break
|
||||
}
|
||||
|
||||
models[rawModel.id] = modelInfo
|
||||
@@ -1576,7 +1522,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
maxTokens: model.max_output_tokens || undefined,
|
||||
contextWindow: model.context_window,
|
||||
supportsImages: model.supports_vision || undefined,
|
||||
supportsComputerUse: model.supports_computer_use || undefined,
|
||||
supportsPromptCache: model.supports_caching || undefined,
|
||||
inputPrice: parsePrice(model.input_price),
|
||||
outputPrice: parsePrice(model.output_price),
|
||||
@@ -1864,6 +1809,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
telemetrySetting,
|
||||
planActSeparateModelsSetting,
|
||||
globalClineRulesToggles,
|
||||
shellIntegrationTimeout,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
const localClineRulesToggles =
|
||||
@@ -1893,6 +1839,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
vscMachineId: vscode.env.machineId,
|
||||
globalClineRulesToggles: globalClineRulesToggles || {},
|
||||
localClineRulesToggles: localClineRulesToggles || {},
|
||||
shellIntegrationTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { convertMcpServersToProtoMcpServers } from "@/shared/proto-conversions/mcp/mcp-server-conversion"
|
||||
import type { AddRemoteMcpServerRequest, McpServers } from "../../../shared/proto/mcp"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
/**
|
||||
* Adds a new remote MCP server via gRPC
|
||||
* @param controller The controller instance
|
||||
* @param request The request containing server name and URL
|
||||
* @returns An array of McpServer objects
|
||||
*/
|
||||
export async function addRemoteMcpServer(controller: Controller, request: AddRemoteMcpServerRequest): Promise<McpServers> {
|
||||
try {
|
||||
// Validate required fields
|
||||
if (!request.serverName) {
|
||||
throw new Error("Server name is required")
|
||||
}
|
||||
if (!request.serverUrl) {
|
||||
throw new Error("Server URL is required")
|
||||
}
|
||||
|
||||
// Call the McpHub method to add the remote server
|
||||
const servers = await controller.mcpHub?.addRemoteServer(request.serverName, request.serverUrl)
|
||||
|
||||
const protoServers = convertMcpServersToProtoMcpServers(servers)
|
||||
|
||||
return { mcpServers: protoServers }
|
||||
} catch (error) {
|
||||
console.error(`Failed to add remote MCP server ${request.serverName}:`, error)
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,14 @@
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { addRemoteMcpServer } from "./addRemoteMcpServer"
|
||||
import { toggleMcpServer } from "./toggleMcpServer"
|
||||
import { updateMcpTimeout } from "./updateMcpTimeout"
|
||||
|
||||
// Register all mcp service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("addRemoteMcpServer", addRemoteMcpServer)
|
||||
registerMethod("toggleMcpServer", toggleMcpServer)
|
||||
registerMethod("updateMcpTimeout", updateMcpTimeout)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { convertMcpServersToProtoMcpServers } from "@/shared/proto-conversions/mcp/mcp-server-conversion"
|
||||
import { Controller } from ".."
|
||||
import { UpdateMcpTimeoutRequest, McpServers } from "../../../shared/proto/mcp"
|
||||
|
||||
/**
|
||||
* Updates the timeout configuration for an MCP server.
|
||||
* @param controller - The Controller instance
|
||||
* @param request - Contains server name and timeout value
|
||||
* @returns Array of updated McpServer objects
|
||||
*/
|
||||
export async function updateMcpTimeout(controller: Controller, request: UpdateMcpTimeoutRequest): Promise<McpServers> {
|
||||
try {
|
||||
if (request.serverName && typeof request.serverName === "string" && typeof request.timeout === "number") {
|
||||
const mcpServers = await controller.mcpHub?.updateServerTimeoutRPC(request.serverName, request.timeout)
|
||||
console.log("mcpServers", mcpServers)
|
||||
const convertedMcpServers = convertMcpServersToProtoMcpServers(mcpServers)
|
||||
console.log("convertedMcpServers", convertedMcpServers)
|
||||
return { mcpServers: convertedMcpServers }
|
||||
} else {
|
||||
console.error("Server name and timeout are required")
|
||||
throw new Error("Server name and timeout are required")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to update timeout for server ${request.serverName}:`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty, EmptyRequest } from "../../../shared/proto/common"
|
||||
|
||||
/**
|
||||
* Cancel the currently running task
|
||||
* @param controller The controller instance
|
||||
* @param _request The empty request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function cancelTask(controller: Controller, _request: EmptyRequest): Promise<Empty> {
|
||||
await controller.cancelTask()
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty, EmptyRequest } from "../../../shared/proto/common"
|
||||
|
||||
/**
|
||||
* Clears the current task
|
||||
* @param controller The controller instance
|
||||
* @param _request The empty request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function clearTask(controller: Controller, _request: EmptyRequest): Promise<Empty> {
|
||||
await controller.clearTask()
|
||||
await controller.postStateToWebview()
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create task service registry
|
||||
const taskService = createServiceRegistry("task")
|
||||
|
||||
// Export the method handler type and registration function
|
||||
export type TaskMethodHandler = ServiceMethodHandler
|
||||
export const registerMethod = taskService.registerMethod
|
||||
|
||||
// Export the request handler
|
||||
export const handleTaskServiceRequest = taskService.handleRequest
|
||||
|
||||
// Register all task methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,16 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { cancelTask } from "./cancelTask"
|
||||
import { clearTask } from "./clearTask"
|
||||
import { newTask } from "./newTask"
|
||||
|
||||
// Register all task service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("cancelTask", cancelTask)
|
||||
registerMethod("clearTask", clearTask)
|
||||
registerMethod("newTask", newTask)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import { NewTaskRequest } from "../../../shared/proto/task"
|
||||
|
||||
/**
|
||||
* Creates a new task with the given text and optional images
|
||||
* @param controller The controller instance
|
||||
* @param request The new task request containing text and optional images
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function newTask(controller: Controller, request: NewTaskRequest): Promise<Empty> {
|
||||
await controller.initTask(request.text, request.images)
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -25,3 +25,123 @@ Usage:
|
||||
Below is the the user's input when they indicated that they wanted to create a new task.
|
||||
</explicit_instructions>\n
|
||||
`
|
||||
|
||||
export const condenseToolResponse = () =>
|
||||
`<explicit_instructions type="condense">
|
||||
The user has explicitly asked you to create a detailed summary of the conversation so far, which will be used to compact the current context window while retaining key information. The user may have provided instructions or additional information for you to consider when summarizing the conversation.
|
||||
Irrespective of whether additional information or instructions are given, you are only allowed to respond to this message by calling the condense tool.
|
||||
|
||||
The condense tool is defined below:
|
||||
|
||||
Description:
|
||||
Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions. This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the conversation and supporting any continuing tasks.
|
||||
The user will be presented with a preview of your generated summary and can choose to use it to compact their context window or keep chatting in the current conversation.
|
||||
Users may refer to this tool as 'smol' or 'compact' as well. You should consider these to be equivalent to 'condense' when used in a similar context.
|
||||
|
||||
Parameters:
|
||||
- Context: (required) The context to continue the conversation with. If applicable based on the current task, this should include:
|
||||
1. Previous Conversation: High level details about what was discussed throughout the entire conversation with the user. This should be written to allow someone to be able to follow the general overarching conversation flow.
|
||||
2. Current Work: Describe in detail what was being worked on prior to this request to compact the context window. Pay special attention to the more recent messages / conversation.
|
||||
3. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for continuing with this work.
|
||||
4. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
|
||||
5. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
|
||||
6. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks.
|
||||
|
||||
Usage:
|
||||
<condense>
|
||||
<context>Your detailed summary</context>
|
||||
</condense>
|
||||
|
||||
Example:
|
||||
<condense>
|
||||
<context>
|
||||
1. Previous Conversation:
|
||||
[Detailed description]
|
||||
|
||||
2. Current Work:
|
||||
[Detailed description]
|
||||
|
||||
3. Key Technical Concepts:
|
||||
- [Concept 1]
|
||||
- [Concept 2]
|
||||
- [...]
|
||||
|
||||
4. Relevant Files and Code:
|
||||
- [File Name 1]
|
||||
- [Summary of why this file is important]
|
||||
- [Summary of the changes made to this file, if any]
|
||||
- [Important Code Snippet]
|
||||
- [File Name 2]
|
||||
- [Important Code Snippet]
|
||||
- [...]
|
||||
|
||||
5. Problem Solving:
|
||||
[Detailed description]
|
||||
|
||||
6. Pending Tasks and Next Steps:
|
||||
- [Task 1 details & next steps]
|
||||
- [Task 2 details & next steps]
|
||||
- [...]
|
||||
</context>
|
||||
</condense>
|
||||
|
||||
</explicit_instructions>\n
|
||||
`
|
||||
|
||||
export const newRuleToolResponse = () =>
|
||||
`<explicit_instructions type="new_rule">
|
||||
The user has explicitly asked you to help them create a new Cline rule file inside the .clinerules top-level directory based on the conversation up to this point in time. The user may have provided instructions or additional information for you to consider when creating the new Cline rule.
|
||||
When creating a new Cline rule file, you should NOT overwrite or alter an existing Cline rule file. To create the Cline rule file you MUST use the new_rule tool. The new_rule tool can be used in either of the PLAN or ACT modes.
|
||||
|
||||
The new_rule tool is defined below:
|
||||
|
||||
Description:
|
||||
Your task is to create a new Cline rule file which includes guidelines on how to approach developing code in tandem with the user, which can be either project specific or cover more global rules. This includes but is not limited to: desired conversational style, favorite project dependencies, coding styles, naming conventions, architectural choices, ui/ux preferences, etc.
|
||||
The Cline rule file must be formatted as markdown and be a '.md' file. The name of the file you generate must be as succinct as possible and be encompassing the main overarching concept of the rules you added to the file (e.g., 'memory-bank.md' or 'project-overview.md').
|
||||
|
||||
Parameters:
|
||||
- Path: (required) The path of the file to write to (relative to the current working directory). This will be the Cline rule file you create, and it must be placed inside the .clinerules top-level directory (create this if it doesn't exist). The filename created CANNOT be "default-clineignore.md". For filenames, use hyphens ("-") instead of underscores ("_") to separate words.
|
||||
- Content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. The content for the Cline rule file MUST be created according to the following instructions:
|
||||
1. Format the Cline rule file to have distinct guideline sections, each with their own markdown heading, starting with "## Brief overview". Under each of these headings, include bullet points fully fleshing out the details, with examples and/or trigger cases ONLY when applicable.
|
||||
2. These guidelines can be specific to the task(s) or project worked on thus far, or cover more high-level concepts. Guidelines can include coding conventions, general design patterns, preferred tech stack including favorite libraries and language, communication style with Cline (verbose vs concise), prompting strategies, naming conventions, testing strategies, comment verbosity, time spent on architecting prior to development, and other preferences.
|
||||
3. When creating guidelines, you should not invent preferences or make assumptions based on what you think a typical user might want. These should be specific to the conversation you had with the user. Your guidelines / rules should not be overly verbose.
|
||||
4. Your guidelines should NOT be a recollection of the conversation up to this point in time, meaning you should NOT be including arbitrary details of the conversation.
|
||||
|
||||
Usage:
|
||||
<new_rule>
|
||||
<path>.clinerules/{file name}.md</path>
|
||||
<content>Cline rule file content here</content>
|
||||
</new_rule>
|
||||
|
||||
Example:
|
||||
<new_rule>
|
||||
<path>.clinerules/project-preferences.md</path>
|
||||
<content>
|
||||
## Brief overview
|
||||
[Brief description of the rules, including if this set of guidelines is project-specific or global]
|
||||
|
||||
## Communication style
|
||||
- [Description, rule, preference, instruction]
|
||||
- [...]
|
||||
|
||||
## Development workflow
|
||||
- [Description, rule, preference, instruction]
|
||||
- [...]
|
||||
|
||||
## Coding best practices
|
||||
- [Description, rule, preference, instruction]
|
||||
- [...]
|
||||
|
||||
## Project context
|
||||
- [Description, rule, preference, instruction]
|
||||
- [...]
|
||||
|
||||
## Other guidelines
|
||||
- [Description, rule, preference, instruction]
|
||||
- [...]
|
||||
</content>
|
||||
</new_rule>
|
||||
|
||||
Below is the user's input when they indicated that they wanted to create a new Cline rule file.
|
||||
</explicit_instructions>\n
|
||||
`
|
||||
|
||||
@@ -10,6 +10,9 @@ export const formatResponse = {
|
||||
contextTruncationNotice: () =>
|
||||
`[NOTE] Some previous conversation history with the user has been removed to maintain optimal context window length. The initial user task and the most recent exchanges have been retained for continuity, while intermediate conversation history has been removed. Please keep this in mind as you continue assisting the user.`,
|
||||
|
||||
condense: () =>
|
||||
`The user has accepted the condensed conversation summary you generated. This summary covers important details of the historical conversation with the user which has been truncated.\n<explicit_instructions type="condense_response">It's crucial that you respond by ONLY asking the user what you should work on next. You should NOT take any initiative or make any assumptions about continuing with work. For example you should NOT suggest file changes or attempt to read any files.\nWhen asking the user what you should work on next, you can reference information in the summary which was just generated. However, you should NOT reference information outside of what's contained in the summary for this response. Keep this response CONCISE.</explicit_instructions>`,
|
||||
|
||||
toolDenied: () => `The user denied this operation.`,
|
||||
|
||||
toolError: (error?: string) => `The tool execution failed with the following error:\n<error>\n${error}\n</error>`,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
|
||||
export const SYSTEM_PROMPT = async (
|
||||
cwd: string,
|
||||
supportsComputerUse: boolean,
|
||||
supportsBrowserUse: boolean,
|
||||
mcpHub: McpHub,
|
||||
browserSettings: BrowserSettings,
|
||||
) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||
@@ -138,7 +138,7 @@ Usage:
|
||||
<list_code_definition_names>
|
||||
<path>Directory path here</path>
|
||||
</list_code_definition_names>${
|
||||
supportsComputerUse
|
||||
supportsBrowserUse
|
||||
? `
|
||||
|
||||
## browser_action
|
||||
@@ -561,14 +561,14 @@ In each user message, the environment_details will specify the current mode. The
|
||||
CAPABILITIES
|
||||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${
|
||||
supportsComputerUse ? ", use the browser" : ""
|
||||
supportsBrowserUse ? ", use the browser" : ""
|
||||
}, read and edit files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the replace_in_file tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${
|
||||
supportsComputerUse
|
||||
supportsBrowserUse
|
||||
? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser."
|
||||
: ""
|
||||
}
|
||||
@@ -592,7 +592,7 @@ RULES
|
||||
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ask_followup_question tool to request the user to copy and paste it back to you.
|
||||
- The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it.
|
||||
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.${
|
||||
supportsComputerUse
|
||||
supportsBrowserUse
|
||||
? `\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.`
|
||||
: ""
|
||||
}
|
||||
@@ -604,7 +604,7 @@ RULES
|
||||
- When using the replace_in_file tool, you must include complete lines in your SEARCH blocks, not partial lines. The system requires exact line matches and cannot match partial lines. For example, if you want to match a line containing "const x = 5;", your SEARCH block must include the entire line, not just "x = 5" or other fragments.
|
||||
- When using the replace_in_file tool, if you use multiple SEARCH/REPLACE blocks, list them in the order they appear in the file. For example if you need to make changes to both line 10 and line 50, first include the SEARCH/REPLACE block for line 10, followed by the SEARCH/REPLACE block for line 50.
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${
|
||||
supportsComputerUse
|
||||
supportsBrowserUse
|
||||
? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser."
|
||||
: ""
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { newTaskToolResponse } from "../prompts/commands"
|
||||
import { newTaskToolResponse, condenseToolResponse, newRuleToolResponse } from "../prompts/commands"
|
||||
|
||||
/**
|
||||
* Processes text for slash commands and transforms them with appropriate instructions
|
||||
* This is called after parseMentions() to process any slash commands in the user's message
|
||||
*/
|
||||
export function parseSlashCommands(text: string): string {
|
||||
const SUPPORTED_COMMANDS = ["newtask"]
|
||||
export function parseSlashCommands(text: string): { processedText: string; needsClinerulesFileCheck: boolean } {
|
||||
const SUPPORTED_COMMANDS = ["newtask", "smol", "compact", "newrule"]
|
||||
|
||||
const commandReplacements: Record<string, string> = {
|
||||
newtask: newTaskToolResponse(),
|
||||
smol: condenseToolResponse(),
|
||||
compact: condenseToolResponse(),
|
||||
newrule: newRuleToolResponse(),
|
||||
}
|
||||
|
||||
// this currently allows matching prepended whitespace prior to /slash-command
|
||||
@@ -45,11 +48,11 @@ export function parseSlashCommands(text: string): string {
|
||||
const textWithoutSlashCommand = text.substring(0, slashCommandStartIndex) + text.substring(slashCommandEndIndex)
|
||||
const processedText = commandReplacements[commandName] + textWithoutSlashCommand
|
||||
|
||||
return processedText
|
||||
return { processedText: processedText, needsClinerulesFileCheck: commandName === "newrule" ? true : false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no supported commands are found, return the original text
|
||||
return text
|
||||
return { processedText: text, needsClinerulesFileCheck: false }
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { fileExistsAtPath } from "@utils/fs"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { TaskMetadata } from "@core/context/context-tracking/ContextTrackerTypes"
|
||||
import os from "os"
|
||||
import { execa } from "execa"
|
||||
import { execa } from "@packages/execa"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
|
||||
@@ -29,6 +29,8 @@ export type GlobalStateKey =
|
||||
| "awsBedrockEndpoint"
|
||||
| "awsProfile"
|
||||
| "awsUseProfile"
|
||||
| "awsBedrockCustomSelected"
|
||||
| "awsBedrockCustomModelBaseId"
|
||||
| "vertexProjectId"
|
||||
| "vertexRegion"
|
||||
| "lastShownAnnouncementId"
|
||||
@@ -60,6 +62,8 @@ export type GlobalStateKey =
|
||||
| "previousModeThinkingBudgetTokens"
|
||||
| "previousModeReasoningEffort"
|
||||
| "previousModeVsCodeLmModelSelector"
|
||||
| "previousModeAwsBedrockCustomSelected"
|
||||
| "previousModeAwsBedrockCustomModelBaseId"
|
||||
| "previousModeModelInfo"
|
||||
| "liteLlmBaseUrl"
|
||||
| "liteLlmModelId"
|
||||
@@ -75,5 +79,7 @@ export type GlobalStateKey =
|
||||
| "reasoningEffort"
|
||||
| "planActSeparateModelsSetting"
|
||||
| "favoritedModelIds"
|
||||
| "requestTimeoutMs"
|
||||
| "shellIntegrationTimeout"
|
||||
|
||||
export type LocalStateKey = "localClineRulesToggles"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
|
||||
import { GlobalStateKey, SecretKey } from "./state-keys"
|
||||
import { ApiConfiguration, ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { ApiConfiguration, ApiProvider, BedrockModelId, ModelInfo } from "@shared/api"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
@@ -67,6 +67,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
awsBedrockEndpoint,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
@@ -113,6 +115,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
previousModeVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens,
|
||||
previousModeReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
qwenApiLine,
|
||||
liteLlmApiKey,
|
||||
telemetrySetting,
|
||||
@@ -125,6 +129,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
planActSeparateModelsSettingRaw,
|
||||
favoritedModelIds,
|
||||
globalClineRulesToggles,
|
||||
requestTimeoutMs,
|
||||
shellIntegrationTimeout,
|
||||
] = await Promise.all([
|
||||
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
|
||||
@@ -140,6 +146,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "awsBedrockEndpoint") as Promise<string | undefined>,
|
||||
getGlobalState(context, "awsProfile") as Promise<string | undefined>,
|
||||
getGlobalState(context, "awsUseProfile") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "awsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "awsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "vertexProjectId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "vertexRegion") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openAiBaseUrl") as Promise<string | undefined>,
|
||||
@@ -186,6 +194,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "previousModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getGlobalState(context, "previousModeThinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "previousModeReasoningEffort") as Promise<string | undefined>,
|
||||
getGlobalState(context, "previousModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "previousModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "qwenApiLine") as Promise<string | undefined>,
|
||||
getSecret(context, "liteLlmApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "telemetrySetting") as Promise<TelemetrySetting | undefined>,
|
||||
@@ -198,6 +208,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "planActSeparateModelsSetting") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
|
||||
getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
|
||||
getGlobalState(context, "requestTimeoutMs") as Promise<number | undefined>,
|
||||
getGlobalState(context, "shellIntegrationTimeout") as Promise<number | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -254,6 +266,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
awsBedrockEndpoint,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
@@ -297,6 +311,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
xaiApiKey,
|
||||
sambanovaApiKey,
|
||||
favoritedModelIds,
|
||||
requestTimeoutMs,
|
||||
},
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
@@ -313,9 +328,12 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
previousModeVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens,
|
||||
previousModeReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
mcpMarketplaceEnabled,
|
||||
telemetrySetting: telemetrySetting || "unset",
|
||||
planActSeparateModelsSetting,
|
||||
shellIntegrationTimeout: shellIntegrationTimeout || 4000,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +352,8 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
awsBedrockEndpoint,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
@@ -391,6 +411,8 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
await updateGlobalState(context, "awsBedrockEndpoint", awsBedrockEndpoint)
|
||||
await updateGlobalState(context, "awsProfile", awsProfile)
|
||||
await updateGlobalState(context, "awsUseProfile", awsUseProfile)
|
||||
await updateGlobalState(context, "awsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
await updateGlobalState(context, "awsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
await updateGlobalState(context, "vertexProjectId", vertexProjectId)
|
||||
await updateGlobalState(context, "vertexRegion", vertexRegion)
|
||||
await updateGlobalState(context, "openAiBaseUrl", openAiBaseUrl)
|
||||
@@ -434,6 +456,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
await storeSecret(context, "clineApiKey", clineApiKey)
|
||||
await storeSecret(context, "sambanovaApiKey", sambanovaApiKey)
|
||||
await updateGlobalState(context, "favoritedModelIds", favoritedModelIds)
|
||||
await updateGlobalState(context, "requestTimeoutMs", apiConfiguration.requestTimeoutMs)
|
||||
}
|
||||
|
||||
export async function resetExtensionState(context: vscode.ExtensionContext) {
|
||||
|
||||
+202
-55
@@ -86,6 +86,7 @@ import {
|
||||
getGlobalClineRules,
|
||||
getLocalClineRules,
|
||||
refreshClineRulesToggles,
|
||||
ensureLocalClinerulesDirExists,
|
||||
} from "@core/context/instructions/user-instructions/cline-rules"
|
||||
import { getGlobalState } from "@core/storage/state"
|
||||
import { parseSlashCommands } from "@core/slash-commands"
|
||||
@@ -172,6 +173,7 @@ export class Task {
|
||||
autoApprovalSettings: AutoApprovalSettings,
|
||||
browserSettings: BrowserSettings,
|
||||
chatSettings: ChatSettings,
|
||||
shellIntegrationTimeout: number,
|
||||
customInstructions?: string,
|
||||
task?: string,
|
||||
images?: string[],
|
||||
@@ -190,6 +192,7 @@ export class Task {
|
||||
console.error("Failed to initialize ClineIgnoreController:", error)
|
||||
})
|
||||
this.terminalManager = new TerminalManager()
|
||||
this.terminalManager.setShellIntegrationTimeout(shellIntegrationTimeout)
|
||||
this.urlContentFetcher = new UrlContentFetcher(context)
|
||||
this.browserSession = new BrowserSession(context, browserSettings)
|
||||
this.contextManager = new ContextManager()
|
||||
@@ -926,6 +929,7 @@ export class Task {
|
||||
let responseImages: string[] | undefined
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
await this.saveCheckpoint()
|
||||
responseText = text
|
||||
responseImages = images
|
||||
}
|
||||
@@ -1325,6 +1329,7 @@ export class Task {
|
||||
|
||||
if (userFeedback) {
|
||||
await this.say("user_feedback", userFeedback.text, userFeedback.images)
|
||||
await this.saveCheckpoint()
|
||||
return [
|
||||
true,
|
||||
formatResponse.toolResult(
|
||||
@@ -1361,6 +1366,7 @@ export class Task {
|
||||
this.autoApprovalSettings.actions.readFiles,
|
||||
this.autoApprovalSettings.actions.readFilesExternally ?? false,
|
||||
]
|
||||
case "new_rule":
|
||||
case "write_to_file":
|
||||
case "replace_in_file":
|
||||
return [
|
||||
@@ -1423,11 +1429,12 @@ export class Task {
|
||||
})
|
||||
|
||||
const disableBrowserTool = vscode.workspace.getConfiguration("cline").get<boolean>("disableBrowserTool") ?? false
|
||||
const modelSupportsComputerUse = this.api.getModel().info.supportsComputerUse ?? false
|
||||
// cline browser tool uses image recognition for navigation (requires model image support).
|
||||
const modelSupportsBrowserUse = this.api.getModel().info.supportsImages ?? false
|
||||
|
||||
const supportsComputerUse = modelSupportsComputerUse && !disableBrowserTool // only enable computer use if the model supports it and the user hasn't disabled it
|
||||
const supportsBrowserUse = modelSupportsBrowserUse && !disableBrowserTool // only enable browser use if the model supports it and the user hasn't disabled it
|
||||
|
||||
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsComputerUse, this.mcpHub, this.browserSettings)
|
||||
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsBrowserUse, this.mcpHub, this.browserSettings)
|
||||
|
||||
let settingsCustomInstructions = this.customInstructions?.trim()
|
||||
const preferredLanguage = getLanguageKey(
|
||||
@@ -1504,6 +1511,10 @@ export class Task {
|
||||
"quarter", // Force aggressive truncation
|
||||
)
|
||||
await this.saveClineMessagesAndUpdateHistory()
|
||||
await this.contextManager.triggerApplyStandardContextTruncationNoticeChange(
|
||||
Date.now(),
|
||||
await ensureTaskDirectoryExists(this.getContext(), this.taskId),
|
||||
)
|
||||
|
||||
this.didAutomaticallyRetryFailedApiRequest = true
|
||||
} else if (isOpenRouter && !this.didAutomaticallyRetryFailedApiRequest) {
|
||||
@@ -1514,6 +1525,10 @@ export class Task {
|
||||
"quarter", // Force aggressive truncation
|
||||
)
|
||||
await this.saveClineMessagesAndUpdateHistory()
|
||||
await this.contextManager.triggerApplyStandardContextTruncationNoticeChange(
|
||||
Date.now(),
|
||||
await ensureTaskDirectoryExists(this.getContext(), this.taskId),
|
||||
)
|
||||
}
|
||||
|
||||
console.log("first chunk failed, waiting 1 second before retrying")
|
||||
@@ -1675,6 +1690,10 @@ export class Task {
|
||||
return `[${block.name}]`
|
||||
case "new_task":
|
||||
return `[${block.name} for creating a new task]`
|
||||
case "condense":
|
||||
return `[${block.name}]`
|
||||
case "new_rule":
|
||||
return `[${block.name} for '${block.params.path}']`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1748,6 +1767,7 @@ export class Task {
|
||||
if (text || images?.length) {
|
||||
pushAdditionalToolFeedback(text, images)
|
||||
await this.say("user_feedback", text, images)
|
||||
await this.saveCheckpoint()
|
||||
}
|
||||
this.didRejectTool = true // Prevent further tool uses in this message
|
||||
return false
|
||||
@@ -1756,6 +1776,7 @@ export class Task {
|
||||
if (text || images?.length) {
|
||||
pushAdditionalToolFeedback(text, images)
|
||||
await this.say("user_feedback", text, images)
|
||||
await this.saveCheckpoint()
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1814,6 +1835,7 @@ export class Task {
|
||||
}
|
||||
|
||||
switch (block.name) {
|
||||
case "new_rule":
|
||||
case "write_to_file":
|
||||
case "replace_in_file": {
|
||||
const relPath: string | undefined = block.params.path
|
||||
@@ -1829,7 +1851,7 @@ export class Task {
|
||||
if (!accessAllowed) {
|
||||
await this.say("clineignore_error", relPath)
|
||||
pushToolResult(formatResponse.toolError(formatResponse.clineIgnoreError(relPath)))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1885,6 +1907,7 @@ export class Task {
|
||||
)
|
||||
await this.diffViewProvider.revertChanges()
|
||||
await this.diffViewProvider.reset()
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} else if (content) {
|
||||
@@ -1942,21 +1965,28 @@ export class Task {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError(block.name, "path"))
|
||||
await this.diffViewProvider.reset()
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
if (block.name === "replace_in_file" && !diff) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("replace_in_file", "diff"))
|
||||
await this.diffViewProvider.reset()
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
if (block.name === "write_to_file" && !content) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "content"))
|
||||
await this.diffViewProvider.reset()
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
if (block.name === "new_rule" && !content) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("new_rule", "content"))
|
||||
await this.diffViewProvider.reset()
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2015,6 +2045,7 @@ export class Task {
|
||||
if (text || images?.length) {
|
||||
pushAdditionalToolFeedback(text, images)
|
||||
await this.say("user_feedback", text, images)
|
||||
await this.saveCheckpoint()
|
||||
}
|
||||
this.didRejectTool = true
|
||||
didApprove = false
|
||||
@@ -2024,12 +2055,14 @@ export class Task {
|
||||
if (text || images?.length) {
|
||||
pushAdditionalToolFeedback(text, images)
|
||||
await this.say("user_feedback", text, images)
|
||||
await this.saveCheckpoint()
|
||||
}
|
||||
telemetryService.captureToolUsage(this.taskId, block.name, false, true)
|
||||
}
|
||||
|
||||
if (!didApprove) {
|
||||
await this.diffViewProvider.revertChanges()
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2090,7 +2123,7 @@ export class Task {
|
||||
await handleError("writing file", error)
|
||||
await this.diffViewProvider.revertChanges()
|
||||
await this.diffViewProvider.reset()
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2119,7 +2152,7 @@ export class Task {
|
||||
if (!relPath) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("read_file", "path"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2127,7 +2160,7 @@ export class Task {
|
||||
if (!accessAllowed) {
|
||||
await this.say("clineignore_error", relPath)
|
||||
pushToolResult(formatResponse.toolError(formatResponse.clineIgnoreError(relPath)))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2150,6 +2183,7 @@ export class Task {
|
||||
this.removeLastPartialMessageIfExistsWithType("say", "tool")
|
||||
const didApprove = await askApproval("tool", completeMessage)
|
||||
if (!didApprove) {
|
||||
await this.saveCheckpoint()
|
||||
telemetryService.captureToolUsage(this.taskId, block.name, false, false)
|
||||
break
|
||||
}
|
||||
@@ -2162,12 +2196,12 @@ export class Task {
|
||||
await this.fileContextTracker.trackFileContext(relPath, "read_tool")
|
||||
|
||||
pushToolResult(content)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("reading file", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2198,7 +2232,7 @@ export class Task {
|
||||
if (!relDirPath) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("list_files", "path"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
@@ -2231,17 +2265,18 @@ export class Task {
|
||||
const didApprove = await askApproval("tool", completeMessage)
|
||||
if (!didApprove) {
|
||||
telemetryService.captureToolUsage(this.taskId, block.name, false, false)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
telemetryService.captureToolUsage(this.taskId, block.name, false, true)
|
||||
}
|
||||
pushToolResult(result)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("listing files", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2270,7 +2305,7 @@ export class Task {
|
||||
if (!relDirPath) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("list_code_definition_names", "path"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2300,17 +2335,18 @@ export class Task {
|
||||
const didApprove = await askApproval("tool", completeMessage)
|
||||
if (!didApprove) {
|
||||
telemetryService.captureToolUsage(this.taskId, block.name, false, false)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
telemetryService.captureToolUsage(this.taskId, block.name, false, true)
|
||||
}
|
||||
pushToolResult(result)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("parsing source code definitions", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2343,13 +2379,13 @@ export class Task {
|
||||
if (!relDirPath) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("search_files", "path"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
if (!regex) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("search_files", "regex"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
@@ -2381,17 +2417,18 @@ export class Task {
|
||||
const didApprove = await askApproval("tool", completeMessage)
|
||||
if (!didApprove) {
|
||||
telemetryService.captureToolUsage(this.taskId, block.name, false, false)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
telemetryService.captureToolUsage(this.taskId, block.name, false, true)
|
||||
}
|
||||
pushToolResult(results)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("searching files", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2407,6 +2444,7 @@ export class Task {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "action"))
|
||||
await this.browserSession.closeBrowser()
|
||||
await this.saveCheckpoint()
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -2450,7 +2488,7 @@ export class Task {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "url"))
|
||||
await this.browserSession.closeBrowser()
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
@@ -2466,6 +2504,7 @@ export class Task {
|
||||
this.removeLastPartialMessageIfExistsWithType("say", "browser_action_launch")
|
||||
const didApprove = await askApproval("browser_action_launch", url)
|
||||
if (!didApprove) {
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2491,7 +2530,7 @@ export class Task {
|
||||
await this.sayAndCreateMissingParamError("browser_action", "coordinate"),
|
||||
)
|
||||
await this.browserSession.closeBrowser()
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break // can't be within an inner switch
|
||||
}
|
||||
}
|
||||
@@ -2500,7 +2539,7 @@ export class Task {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "text"))
|
||||
await this.browserSession.closeBrowser()
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2549,7 +2588,7 @@ export class Task {
|
||||
browserActionResult.screenshot ? [browserActionResult.screenshot] : [],
|
||||
),
|
||||
)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
case "close":
|
||||
pushToolResult(
|
||||
@@ -2557,7 +2596,7 @@ export class Task {
|
||||
`The browser has been closed. You may now proceed to using other tools.`,
|
||||
),
|
||||
)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2566,7 +2605,7 @@ export class Task {
|
||||
} catch (error) {
|
||||
await this.browserSession.closeBrowser() // if any error occurs, the browser session is terminated
|
||||
await handleError("executing browser action", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2594,7 +2633,7 @@ export class Task {
|
||||
if (!command) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("execute_command", "command"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
if (!requiresApprovalRaw) {
|
||||
@@ -2602,7 +2641,7 @@ export class Task {
|
||||
pushToolResult(
|
||||
await this.sayAndCreateMissingParamError("execute_command", "requires_approval"),
|
||||
)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
@@ -2618,7 +2657,7 @@ export class Task {
|
||||
pushToolResult(
|
||||
formatResponse.toolError(formatResponse.clineIgnoreError(ignoredFileAttemptedToAccess)),
|
||||
)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2650,6 +2689,7 @@ export class Task {
|
||||
`${this.shouldAutoApproveTool(block.name) && requiresApprovalPerLLM ? COMMAND_REQ_APP_STRING : ""}`, // ugly hack until we refactor combineCommandSequences
|
||||
)
|
||||
if (!didApprove) {
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2685,7 +2725,7 @@ export class Task {
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("executing command", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2715,13 +2755,13 @@ export class Task {
|
||||
if (!server_name) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "server_name"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
if (!tool_name) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "tool_name"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
// arguments are optional, but if they are provided they must be valid JSON
|
||||
@@ -2745,7 +2785,7 @@ export class Task {
|
||||
formatResponse.invalidMcpToolArgumentError(server_name, tool_name),
|
||||
),
|
||||
)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2772,6 +2812,7 @@ export class Task {
|
||||
this.removeLastPartialMessageIfExistsWithType("say", "use_mcp_server")
|
||||
const didApprove = await askApproval("use_mcp_server", completeMessage)
|
||||
if (!didApprove) {
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2823,7 +2864,7 @@ export class Task {
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("executing MCP tool", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2851,13 +2892,13 @@ export class Task {
|
||||
if (!server_name) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("access_mcp_resource", "server_name"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
if (!uri) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("access_mcp_resource", "uri"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
@@ -2878,6 +2919,7 @@ export class Task {
|
||||
this.removeLastPartialMessageIfExistsWithType("say", "use_mcp_server")
|
||||
const didApprove = await askApproval("use_mcp_server", completeMessage)
|
||||
if (!didApprove) {
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2897,12 +2939,12 @@ export class Task {
|
||||
.join("\n\n") || "(Empty response)"
|
||||
await this.say("mcp_server_response", resourceResultPretty)
|
||||
pushToolResult(formatResponse.toolResult(resourceResultPretty))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("accessing MCP resource", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2921,7 +2963,7 @@ export class Task {
|
||||
if (!question) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("ask_followup_question", "question"))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
@@ -2958,12 +3000,12 @@ export class Task {
|
||||
}
|
||||
|
||||
pushToolResult(formatResponse.toolResult(`<answer>\n${text}\n</answer>`, images))
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("asking question", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2977,6 +3019,7 @@ export class Task {
|
||||
if (!context) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("new_task", "context"))
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
@@ -3005,10 +3048,74 @@ export class Task {
|
||||
formatResponse.toolResult(`The user has created a new task with the provided context.`),
|
||||
)
|
||||
}
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("creating new task", error)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
case "condense": {
|
||||
const context: string | undefined = block.params.context
|
||||
try {
|
||||
if (block.partial) {
|
||||
await this.ask("condense", removeClosingTag("context", context), block.partial).catch(() => {})
|
||||
break
|
||||
} else {
|
||||
if (!context) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("condense", "context"))
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.consecutiveMistakeCount = 0
|
||||
|
||||
if (this.autoApprovalSettings.enabled && this.autoApprovalSettings.enableNotifications) {
|
||||
showSystemNotification({
|
||||
subtitle: "Cline wants to condense the conversation...",
|
||||
message: `Cline is suggesting to condense your conversation with: ${context}`,
|
||||
})
|
||||
}
|
||||
|
||||
const { text, images } = await this.ask("condense", context, false)
|
||||
|
||||
// If the user provided a response, treat it as feedback
|
||||
if (text || images?.length) {
|
||||
await this.say("user_feedback", text ?? "", images)
|
||||
pushToolResult(
|
||||
formatResponse.toolResult(
|
||||
`The user provided feedback on the condensed conversation summary:\n<feedback>\n${text}\n</feedback>`,
|
||||
images,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// If no response, the user accepted the condensed version
|
||||
pushToolResult(formatResponse.toolResult(formatResponse.condense()))
|
||||
|
||||
const lastMessage = this.apiConversationHistory[this.apiConversationHistory.length - 1]
|
||||
const summaryAlreadyAppended = lastMessage && lastMessage.role === "assistant"
|
||||
const keepStrategy = summaryAlreadyAppended ? "lastTwo" : "none"
|
||||
|
||||
// clear the context history at this point in time
|
||||
this.conversationHistoryDeletedRange = this.contextManager.getNextTruncationRange(
|
||||
this.apiConversationHistory,
|
||||
this.conversationHistoryDeletedRange,
|
||||
keepStrategy,
|
||||
)
|
||||
await this.saveClineMessagesAndUpdateHistory()
|
||||
await this.contextManager.triggerApplyStandardContextTruncationNoticeChange(
|
||||
Date.now(),
|
||||
await ensureTaskDirectoryExists(this.getContext(), this.taskId),
|
||||
)
|
||||
}
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("condensing context window", error)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -3069,6 +3176,7 @@ export class Task {
|
||||
if (text || images?.length) {
|
||||
telemetryService.captureOptionsIgnored(this.taskId, options.length, "plan")
|
||||
await this.say("user_feedback", text ?? "", images)
|
||||
await this.saveCheckpoint()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3215,12 +3323,14 @@ export class Task {
|
||||
// complete command message
|
||||
const didApprove = await askApproval("command", command)
|
||||
if (!didApprove) {
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
const [userRejected, execCommandResult] = await this.executeCommandTool(command!)
|
||||
if (userRejected) {
|
||||
this.didRejectTool = true
|
||||
pushToolResult(execCommandResult)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
// user didn't reject, but the command may have output
|
||||
@@ -3239,6 +3349,7 @@ export class Task {
|
||||
break
|
||||
}
|
||||
await this.say("user_feedback", text ?? "", images)
|
||||
await this.saveCheckpoint()
|
||||
|
||||
const toolResults: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = []
|
||||
if (commandResult) {
|
||||
@@ -3267,7 +3378,7 @@ export class Task {
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("attempting completion", error)
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -3369,9 +3480,6 @@ export class Task {
|
||||
|
||||
// Save checkpoint if this is the first API request
|
||||
const isFirstRequest = this.clineMessages.filter((m) => m.say === "api_req_started").length === 0
|
||||
if (isFirstRequest) {
|
||||
await this.say("checkpoint_created") // no hash since we need to wait for CheckpointTracker to be initialized
|
||||
}
|
||||
|
||||
// getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds
|
||||
// for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
|
||||
@@ -3382,6 +3490,10 @@ export class Task {
|
||||
}),
|
||||
)
|
||||
|
||||
if (isFirstRequest) {
|
||||
await this.say("checkpoint_created") // no hash since we need to wait for CheckpointTracker to be initialized
|
||||
}
|
||||
|
||||
// use this opportunity to initialize the checkpoint tracker (can be expensive to initialize in the constructor)
|
||||
// FIXME: right now we're letting users init checkpoints for old tasks, but this could be a problem if opening a task in the wrong workspace
|
||||
// isNewTask &&
|
||||
@@ -3412,7 +3524,16 @@ export class Task {
|
||||
}
|
||||
}
|
||||
|
||||
const [parsedUserContent, environmentDetails] = await this.loadContext(userContent, includeFileDetails)
|
||||
const [parsedUserContent, environmentDetails, clinerulesError] = await this.loadContext(userContent, includeFileDetails)
|
||||
|
||||
// error handling if the user uses the /newrule command & their .clinerules is a file, for file read operations didnt work properly
|
||||
if (clinerulesError === true) {
|
||||
await this.say(
|
||||
"error",
|
||||
"Issue with processing the /newrule command. Double check that, if '.clinerules' already exists, it's a directory and not a file. Otherwise there was an issue referencing this file/directory.",
|
||||
)
|
||||
}
|
||||
|
||||
userContent = parsedUserContent
|
||||
// add environment details as its own text block, separate from tool results
|
||||
userContent.push({ type: "text", text: environmentDetails })
|
||||
@@ -3697,11 +3818,14 @@ export class Task {
|
||||
}
|
||||
}
|
||||
|
||||
async loadContext(userContent: UserContent, includeFileDetails: boolean = false) {
|
||||
return await Promise.all([
|
||||
async loadContext(userContent: UserContent, includeFileDetails: boolean = false): Promise<[UserContent, string, boolean]> {
|
||||
// Track if we need to check clinerulesFile
|
||||
let needsClinerulesFileCheck = false
|
||||
|
||||
const processUserContent = async () => {
|
||||
// This is a temporary solution to dynamically load context mentions from tool results. It checks for the presence of tags that indicate that the tool was rejected and feedback was provided (see formatToolDeniedFeedback, attemptCompletion, executeCommand, and consecutiveMistakeCount >= 3) or "<answer>" (see askFollowupQuestion), we place all user generated content in these tags so they can effectively be used as markers for when we should parse mentions). However if we allow multiple tools responses in the future, we will need to parse mentions specifically within the user content tags.
|
||||
// (Note: this caused the @/ import alias bug where file contents were being parsed as well, since v2 converted tool results to text blocks)
|
||||
Promise.all(
|
||||
return await Promise.all(
|
||||
userContent.map(async (block) => {
|
||||
if (block.type === "text") {
|
||||
// We need to ensure any user generated content is wrapped in one of these tags so that we know to parse mentions
|
||||
@@ -3712,22 +3836,45 @@ export class Task {
|
||||
block.text.includes("<task>") ||
|
||||
block.text.includes("<user_message>")
|
||||
) {
|
||||
let parsedText = await parseMentions(block.text, cwd, this.urlContentFetcher, this.fileContextTracker)
|
||||
const parsedText = await parseMentions(
|
||||
block.text,
|
||||
cwd,
|
||||
this.urlContentFetcher,
|
||||
this.fileContextTracker,
|
||||
)
|
||||
|
||||
// when parsing slash commands, we still want to allow the user to provide their desired context
|
||||
parsedText = parseSlashCommands(parsedText)
|
||||
const { processedText, needsClinerulesFileCheck: needsCheck } = parseSlashCommands(parsedText)
|
||||
|
||||
if (needsCheck) {
|
||||
needsClinerulesFileCheck = true
|
||||
}
|
||||
|
||||
return {
|
||||
...block,
|
||||
text: parsedText,
|
||||
text: processedText,
|
||||
}
|
||||
}
|
||||
}
|
||||
return block
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Run initial promises in parallel
|
||||
const [processedUserContent, environmentDetails] = await Promise.all([
|
||||
processUserContent(),
|
||||
this.getEnvironmentDetails(includeFileDetails),
|
||||
])
|
||||
|
||||
// After processing content, check clinerulesData if needed
|
||||
let clinerulesError = false
|
||||
if (needsClinerulesFileCheck) {
|
||||
clinerulesError = await ensureLocalClinerulesDirExists(cwd)
|
||||
}
|
||||
|
||||
// Return all results
|
||||
return [processedUserContent, environmentDetails, clinerulesError]
|
||||
}
|
||||
|
||||
async getEnvironmentDetails(includeFileDetails: boolean = false) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user