mirror of
https://github.com/cline/cline.git
synced 2026-09-04 11:44:01 +08:00
Compare commits
262 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67ef89a4cb | |||
| c24ba53400 | |||
| 01c4ead155 | |||
| 6f4a4e1ccd | |||
| dc03b3bf1f | |||
| cf52db8f49 | |||
| 26ce4a31b6 | |||
| 06e0973c04 | |||
| 54faddb3ff | |||
| d6935623f9 | |||
| 803574e7f3 | |||
| f676f2be78 | |||
| c88a852344 | |||
| 5709f34479 | |||
| a7eb7defc9 | |||
| a45278bb61 | |||
| 5d75a311f4 | |||
| 0054b21d67 | |||
| ea4433d705 | |||
| 4aaca09389 | |||
| feb92c45ce | |||
| e7c4757271 | |||
| a462a7aded | |||
| 2e729441ed | |||
| c1d7faee1a | |||
| 38d6af3a8d | |||
| 612b8352ff | |||
| d4f26fe1de | |||
| e242825dff | |||
| 8fbccff853 | |||
| 2615ea88b6 | |||
| 61c2943715 | |||
| b176772cd5 | |||
| e6b00527ed | |||
| 2635f5f575 | |||
| 44f370f295 | |||
| 8c49ce56f6 | |||
| 08543e051e | |||
| f4828d3344 | |||
| 385e952935 | |||
| 9a2aaf0881 | |||
| 5d65260611 | |||
| 7098c1a32a | |||
| d46e672990 | |||
| 7b2dddd4a5 | |||
| a6e657e0d1 | |||
| 612a67ee89 | |||
| 383826f7f7 | |||
| b44d3c8793 | |||
| fbc517c9e5 | |||
| 3175e19bd7 | |||
| 8e80c18c52 | |||
| b5be6f57d5 | |||
| 28737ac62a | |||
| 61112aaa03 | |||
| 922cfed632 | |||
| 6d7cca7d38 | |||
| d81fb542a9 | |||
| a16fc09a68 | |||
| 2ec21eda34 | |||
| 88000d4f39 | |||
| 6318eb5948 | |||
| 0fd1c0a3aa | |||
| e0478493a2 | |||
| 9355d3eea4 | |||
| f485d0cc8f | |||
| 79bda976cb | |||
| 94acfec39f | |||
| 0f67508fa3 | |||
| e31bc6147f | |||
| 8d69e63d72 | |||
| 49a678b835 | |||
| 67610b1f87 | |||
| 45f837e1e9 | |||
| d0793e51c4 | |||
| 00740de01d | |||
| 1ffa4085a6 | |||
| 4db5581cfc | |||
| 675cd1779b | |||
| af0f0b3d7c | |||
| 45767b87fd | |||
| 8f4c6038dd | |||
| 9dc021a881 | |||
| 3e2bdf8b12 | |||
| d4a99a4060 | |||
| f4bbb45b07 | |||
| 088deebd63 | |||
| dcc744dd87 | |||
| 9ad8525bd0 | |||
| 8bf6268952 | |||
| 2081bb8dc6 | |||
| ac22b63796 | |||
| 44eb2cc65e | |||
| 2cf1d8628b | |||
| 669e018b85 | |||
| 2aa5156905 | |||
| 51b619e0d5 | |||
| 85fb76a996 | |||
| d73a7cfd06 | |||
| 489dfbc932 | |||
| 314c416788 | |||
| 3b19c2ec95 | |||
| e04cbea504 | |||
| affac119f5 | |||
| 3847a2545c | |||
| 15593bac2a | |||
| 84267efb9e | |||
| 985ce56809 | |||
| cad28c4c0c | |||
| 4a22f7dbd2 | |||
| a430226caa | |||
| 5885a3cc1d | |||
| 759ef873ae | |||
| 782e4ff6e0 | |||
| 4bb00241bf | |||
| c325faf8db | |||
| 20f8f9c9cf | |||
| 5be163f49d | |||
| 7843ab937a | |||
| 5ed4319d21 | |||
| a8971b807a | |||
| 51c4e0aceb | |||
| 1cf62941cd | |||
| cc2472f500 | |||
| 677e544c51 | |||
| d3c8fbbf1d | |||
| 1b06633253 | |||
| 4ab8559fce | |||
| 259368e0a3 | |||
| 9b7839efcd | |||
| 47a2ae83de | |||
| 1b5590e26c | |||
| 9e493341d2 | |||
| 1d4cd3187b | |||
| 32f0f9618c | |||
| 3001f883c2 | |||
| a64e60b8f6 | |||
| 3a0e6a471b | |||
| c10f4e0a66 | |||
| 3e11271cf8 | |||
| 29ae2c286d | |||
| 82aee44a9a | |||
| 031604ddf6 | |||
| f2101e375f | |||
| 8a65f0c68b | |||
| 32b8fa44cb | |||
| 0d067f7470 | |||
| de6166392c | |||
| 5f21a9162a | |||
| 4de991f1b0 | |||
| 6e5d4a3f9e | |||
| 95af95badf | |||
| 61dcbd390c | |||
| 6255ac0a51 | |||
| 616800fcb9 | |||
| df3826a59f | |||
| 873917810d | |||
| e3c966f4e9 | |||
| 7eeb43ab41 | |||
| 7620f177ac | |||
| 67bab94911 | |||
| eb91bfd738 | |||
| b8227c19c3 | |||
| 8fee09f09e | |||
| 1c026c26d2 | |||
| 5bc4e5a4a0 | |||
| 2cfce5734e | |||
| e2045bf5c3 | |||
| 0d933e804f | |||
| 16f73532f4 | |||
| 88bea8eeb4 | |||
| 1a570e98ba | |||
| d86b7dd036 | |||
| 0178c3fa90 | |||
| a107f45c6a | |||
| 3dd2ed9161 | |||
| 9a6603fdfb | |||
| 6d5c3e6aa4 | |||
| 24b9e821bb | |||
| 9960a3c57c | |||
| 88947592f0 | |||
| ef4d11df19 | |||
| 23dec509bc | |||
| 07ab6b19b8 | |||
| 0ddef94d1f | |||
| b9ae83b1cd | |||
| e4eaf34827 | |||
| 6d3ed43c74 | |||
| cbb67b48f2 | |||
| a5f6a97be8 | |||
| f309b062e7 | |||
| 768df130ab | |||
| 9980cb0938 | |||
| aca4f842fa | |||
| 3fc91e2afe | |||
| 5f4700ce95 | |||
| dbaf5e3ee3 | |||
| 576176c24f | |||
| c8abcbfdf9 | |||
| 8e984f2d98 | |||
| 81564faa4e | |||
| f8b5f1fd72 | |||
| 2eb57384ab | |||
| c80bae504a | |||
| 80f955be9e | |||
| 7435ffcd2f | |||
| a05d438612 | |||
| b4b7512d9f | |||
| e08c65618e | |||
| d653f1cc27 | |||
| c3a97c3eda | |||
| 0e56272d65 | |||
| fdc2e2655a | |||
| 6050413b8b | |||
| c54f0da737 | |||
| 6cbfb2b8b0 | |||
| 22788f0f12 | |||
| 708b785a97 | |||
| 099bc44d42 | |||
| b9f4678dba | |||
| f7d17384f6 | |||
| bb5a64afb3 | |||
| 61224734f8 | |||
| a28b995ab1 | |||
| a91878efc6 | |||
| 65c21e7b7d | |||
| 56e388c90f | |||
| 586d804a01 | |||
| 85fbbcbe3f | |||
| 25c5310383 | |||
| 19ef843d4f | |||
| 2b3dd14271 | |||
| 721e7ae305 | |||
| f6273e0661 | |||
| 68d0af2afc | |||
| 4286f301d2 | |||
| 28536084bf | |||
| dfbac3eefd | |||
| 04f0710a03 | |||
| 195e15db32 | |||
| 19cb70de55 | |||
| e2a2ecde44 | |||
| 1a466c0a44 | |||
| 31cbf489c4 | |||
| c667d34f27 | |||
| a1bf1f95a3 | |||
| 5da75af616 | |||
| 38babe12f7 | |||
| 66fb8835a4 | |||
| 214e157360 | |||
| 7a9dce4f86 | |||
| af7e3a4d20 | |||
| 3abdc9ad0f | |||
| 28b15d8b9b | |||
| bc6b3e54be | |||
| 9fad0aa4ae | |||
| 23fea0e16b | |||
| e8aaa61494 | |||
| d93080304c | |||
| 1ce5f72bc1 | |||
| fb676add2e | |||
| 31cda0ce5e |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Display account balance for all org members
|
||||
@@ -716,7 +716,7 @@ The Controller class manages MCP servers through the McpHub service:
|
||||
class Controller {
|
||||
mcpHub?: McpHub
|
||||
|
||||
constructor(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel, webviewProvider: WebviewProvider) {
|
||||
constructor(context: vscode.ExtensionContext, webviewProvider: WebviewProvider) {
|
||||
this.mcpHub = new McpHub(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,28 +6,14 @@ Analyze the current branch's changes against main to provide informed insights a
|
||||
## Step 1: Gather Git Information
|
||||
<important>Do not return any text or conversation other than what is necessary to run these commands</important>
|
||||
|
||||
**First, check the expected output size:**
|
||||
```shell
|
||||
(git branch --show-current && echo "=== STATUS ===" && git status --porcelain | cat && echo "=== COMMIT MESSAGES ===" && git log main..HEAD --oneline | cat && echo "=== CHANGED FILES ===" && git diff main --name-only | cat && echo "=== FULL DIFF ===" && git diff main | cat) | wc -l
|
||||
```
|
||||
**Run the following command to get the latest changes (bash):**
|
||||
```bash
|
||||
B=$(for c in main master origin/main origin/master; do git rev-parse --verify -q "$c" >/dev/null && echo "$c" && break; done); B=${B:-HEAD}; r(){ git branch --show-current; printf "=== STATUS ===\n"; git status --porcelain | cat; printf "=== COMMIT MESSAGES ===\n"; git log "$B"..HEAD --oneline | cat; printf "=== CHANGED FILES ===\n"; git diff "$B" --name-only | cat; printf "=== FULL DIFF ===\n"; git diff "$B" | cat; }; L=$(r | wc -l); if [ "$L" -gt 500 ]; then r > cline-git-analysis.temp && echo "::OUTPUT_FILE=cline-git-analysis.temp"; else r; fi
|
||||
```
|
||||
|
||||
**If the expected line count is greater than 500 lines, use the file-based approach:**
|
||||
```shell
|
||||
git branch --show-current > cline-git-analysis.temp && echo "=== STATUS ===" >> cline-git-analysis.temp && git status --porcelain >> cline-git-analysis.temp && echo "=== COMMIT MESSAGES ===" >> cline-git-analysis.temp && git log main..HEAD --oneline >> cline-git-analysis.temp && echo "=== CHANGED FILES ===" >> cline-git-analysis.temp && git diff main --name-only >> cline-git-analysis.temp && echo "=== FULL DIFF ===" >> cline-git-analysis.temp && git diff main >> cline-git-analysis.temp
|
||||
```
|
||||
|
||||
Then, read the file using the read_file tool. After you have read the file but before you proceed with subsequent steps, delete it:
|
||||
```shell
|
||||
rm cline-git-analysis.temp
|
||||
```
|
||||
|
||||
**If the expected line count is 500 lines or fewer, use the direct approach:**
|
||||
```shell
|
||||
git branch --show-current && echo "=== STATUS ===" && git status --porcelain | cat && echo "=== COMMIT MESSAGES ===" && git log main..HEAD --oneline | cat && echo "=== CHANGED FILES ===" && git diff main --name-only | cat && echo "=== FULL DIFF ===" && git diff main | cat
|
||||
```
|
||||
|
||||
<important>If using the direct approach, pipe outputs through `cat` to avoid interactive terminals. If the user's shell is not bash/zsh, adjust the command and chaining
|
||||
syntax accordingly.</important>
|
||||
```powershell
|
||||
$B=$null;foreach($c in 'main','master','origin/main','origin/master'){git rev-parse --verify -q $c *> $null;if($LASTEXITCODE -eq 0){$B=$c;break}};if(-not $B){$B='HEAD'};function r([string]$b){git rev-parse --abbrev-ref HEAD; '=== STATUS ==='; git status --porcelain | cat; '=== COMMIT MESSAGES ==='; git log "$b"..HEAD --oneline | cat; '=== CHANGED FILES ==='; git diff "$b" --name-only | cat; '=== FULL DIFF ==='; git diff "$b" | cat};$out=r $B|Out-String;$lines=($out -split "`r?`n").Count;if($lines -gt 500){$out|Set-Content -NoNewline cline-git-analysis.temp; '::OUTPUT_FILE=cline-git-analysis.temp'}else{$out}
|
||||
```
|
||||
|
||||
## Step 2: Silent, Structured Analysis Phase
|
||||
- Analyze all git output without providing commentary or narration
|
||||
|
||||
@@ -219,6 +219,9 @@ EOF
|
||||
|
||||
## Basic PR Commands
|
||||
```bash
|
||||
# Get current PR number
|
||||
gh pr view --json number -q .number
|
||||
|
||||
# List open PRs
|
||||
gh pr list
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "eslint-rules"],
|
||||
"rules": {
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
"selector": "import",
|
||||
"format": ["camelCase", "PascalCase"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": "off",
|
||||
"curly": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off",
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"eslint-rules/no-direct-vscode-api": "warn",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
"selector": "VariableDeclarator[id.type=\"ObjectPattern\"][init.object.name=\"process\"][init.property.name=\"env\"]",
|
||||
"message": "Use process.env.VARIABLE_NAME directly instead of destructuring"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ignorePatterns": ["out", "dist", "**/*.d.ts"]
|
||||
}
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
* @saoudrizwan @ocasta181 @NightTrek @pashpashpash @dcbartlett @saito-sv @Garoth
|
||||
/docs/
|
||||
/.github/ @saoudrizwan @dcbartlett
|
||||
/README.md @saoudrizwan @nickbaumann98
|
||||
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Important:** All bug reports must be reproducible using Claude 3.5 Sonnet. Cline uses complex prompts so less capable models may not work as expected.
|
||||
**Important:** All bug reports must be reproducible using Claude 4 Sonnet. Cline uses complex prompts so less capable models may not work as expected.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
@@ -24,7 +24,7 @@ body:
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
@@ -39,20 +39,19 @@ body:
|
||||
placeholder: "e.g., cline:anthropic/claude-3.7-sonnet, gemini:gemini-2.5-pro-exp-03-25"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What operating system are you using?
|
||||
placeholder: "e.g., Windows 11, macOS Sonoma, Ubuntu 22.04"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Info
|
||||
description: What system information is relevant to the issue?
|
||||
placeholder: "e.g., CPU: Intel Core i7-11700K, GPU: NVIDIA GeForce RTX 3070, RAM: 32GB DDR4"
|
||||
label: System Information
|
||||
description: What operating system and hardware are you using?
|
||||
placeholder: |
|
||||
Operating System: Windows 11, macOS Sonoma, Ubuntu 22.04, etc.
|
||||
Hardware: CPU, GPU, RAM specifications if relevant
|
||||
e.g.,
|
||||
OS: Windows 11
|
||||
CPU: Intel Core i7-11700K
|
||||
GPU: NVIDIA GeForce RTX 3070
|
||||
RAM: 32GB DDR4
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
@@ -63,8 +62,3 @@ body:
|
||||
placeholder: "e.g., 1.2.3"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here, such as screenshots or related issues.
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
name: 💡 Feature Proposal & Contribution
|
||||
description: Propose a new feature or improvement, and optionally offer to implement feature as a contributor
|
||||
labels: ["proposal"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Feature Proposal & Contribution for Cline**
|
||||
|
||||
Thank you for proposing a feature or improvement for Cline! This template helps us understand the problem, evaluate the solution, and coordinate implementation.
|
||||
|
||||
**For detailed proposals:** Please provide comprehensive information to enable fast prioritization and discussion.
|
||||
**For contribution offers:** You can indicate your willingness to implement the feature yourself.
|
||||
|
||||
Before submitting:
|
||||
- Search existing [Issues](https://github.com/cline/cline/issues) and [Discussions](https://github.com/cline/cline/discussions) to avoid duplicates
|
||||
- Read the [Contributing Guide](https://github.com/cline/cline/blob/main/CONTRIBUTING.md) if you plan to contribute
|
||||
- Don't start implementation until the proposal is reviewed and approved
|
||||
|
||||
- type: textarea
|
||||
id: problem-description
|
||||
attributes:
|
||||
label: What problem does this solve?
|
||||
description: |
|
||||
Describe the problem clearly from a user's point of view. Focus on why this matters, who it affects, and when it occurs.
|
||||
|
||||
✅ Good examples:
|
||||
- "LLM provider returns 400 error when nearing the context window instead of truncating"
|
||||
- "Submit button is invisible in dark mode"
|
||||
- "Users can't easily share their Cline configurations with team members"
|
||||
|
||||
❌ Avoid vague descriptions:
|
||||
- "Performance is bad"
|
||||
- "UI needs work"
|
||||
|
||||
Your description should include:
|
||||
- Who is affected?
|
||||
- When does it happen?
|
||||
- What's the current vs expected behavior?
|
||||
- What is the impact?
|
||||
placeholder: Be specific about the problem, who it affects, and the impact.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposed-solution
|
||||
attributes:
|
||||
label: What's the proposed solution?
|
||||
description: |
|
||||
Describe how the problem should be solved. Be specific about UX, system behavior, and any flows that would change.
|
||||
|
||||
✅ Good examples:
|
||||
- "Add error handling immediately after attempting to create the llm stream and retry after manually truncating"
|
||||
- "Update button styling to ensure contrast in all themes"
|
||||
- "Add export/import functionality in settings with JSON format"
|
||||
|
||||
❌ Avoid vague solutions:
|
||||
- "Improve performance"
|
||||
- "Fix the bug"
|
||||
|
||||
Your solution should include:
|
||||
- What exactly will change?
|
||||
- How will users interact with it?
|
||||
- What's the expected outcome?
|
||||
placeholder: Describe the proposed changes and how they solve the problem.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: contribution-intent
|
||||
attributes:
|
||||
label: Are you interested in implementing this?
|
||||
description: Let us know if you'd like to contribute to this feature
|
||||
options:
|
||||
- "No, just proposing the idea"
|
||||
- "Yes, I'd like to implement this myself"
|
||||
- "Yes, I'd like to collaborate with others"
|
||||
- "Maybe, depending on complexity and guidance"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: implementation-approach
|
||||
attributes:
|
||||
label: Implementation approach (if contributing)
|
||||
description: |
|
||||
**Only fill this out if you selected "Yes" above.**
|
||||
|
||||
How do you plan to implement this? Include:
|
||||
- High-level technical approach
|
||||
- Files/components that would be affected
|
||||
- Any new dependencies required
|
||||
- Potential challenges or considerations you've identified
|
||||
|
||||
This helps us provide better guidance and ensures alignment before you start coding.
|
||||
placeholder: "My implementation approach would be..."
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Proposal checklist
|
||||
options:
|
||||
- label: I've checked for existing issues or related proposals
|
||||
required: true
|
||||
- label: I understand this needs review before implementation can start
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution-checklist
|
||||
attributes:
|
||||
label: Contribution checklist (if contributing)
|
||||
description: Only check these if you plan to contribute
|
||||
options:
|
||||
- label: I've read the [Contributing Guide](https://github.com/cline/cline/blob/main/CONTRIBUTING.md)
|
||||
- label: I'm willing to make changes based on feedback
|
||||
- label: I understand the code review process and requirements
|
||||
@@ -2,15 +2,14 @@
|
||||
Thank you for contributing to Cline!
|
||||
|
||||
⚠️ Important: Before submitting this PR, please ensure you have:
|
||||
- Opened an issue and discussed your proposed changes with the community / contributors
|
||||
- Received approval from a core Cline contributor prior to proceeding with the implementation
|
||||
- Link the associated issue in the "Related Issue" section
|
||||
- For feature requests: Created a discussion in our Feature Requests discussions board https://github.com/cline/cline/discussions/categories/feature-requests and received approval from core maintainers before implementation
|
||||
- For all changes: Link the associated issue/discussion in the "Related Issue" section below
|
||||
|
||||
Limited exceptions:
|
||||
Small bug fixes, typo corrections, minor wording improvements, or simple type fixes that don't change functionality may be submitted directly.
|
||||
Small bug fixes, typo corrections, minor wording improvements, or simple type fixes that don't change functionality may be submitted directly without prior discussion.
|
||||
|
||||
Why this requirement?
|
||||
We deeply appreciate all community contributions - they are the core reason we're able to operate successfully and keep innovating! We welcome community input and want to make it as easy as possible for people to submit quality work. This process helps our core maintainers review new ideas faster and saves contributor time by ensuring you have the go-ahead before spending time on implementation.
|
||||
We deeply appreciate all community contributions - they are essential to Cline's success! To ensure the best use of everyone's time and maintain project direction, we use our Feature Requests discussions board to gauge community interest and validate feature ideas before implementation begins. This helps us focus development efforts on features that will benefit the most users.
|
||||
-->
|
||||
|
||||
### Related Issue
|
||||
|
||||
+15
-24
@@ -71,13 +71,7 @@ jobs:
|
||||
- name: Install xvfb on Linux
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb
|
||||
|
||||
- name: Install local modules on windows
|
||||
if: runner.os == 'Windows' && steps.root-cache.outputs.cache-hit == 'true'
|
||||
run: |
|
||||
npm install eslint-plugin-eslint-rules
|
||||
cd webview-ui/ && npm install eslint-plugin-eslint-rules
|
||||
|
||||
|
||||
- name: Set up NPM on Windows
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
@@ -86,26 +80,25 @@ jobs:
|
||||
- name: Type Check
|
||||
run: npm run check-types
|
||||
|
||||
- name: ESLint Check
|
||||
- name: Lint Check
|
||||
run: npm run lint
|
||||
|
||||
- name: Prettier / Format Check
|
||||
- name: Format Check
|
||||
run: npm run format
|
||||
|
||||
# Build the extension before running tests
|
||||
- name: Build Tests and Extension
|
||||
run: npm run pretest
|
||||
|
||||
# Unit Tests disabled due to module system conflicts between backend and webview-ui
|
||||
# - 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
|
||||
- name: Extension Integration Tests with Coverage
|
||||
id: extension_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
node ./scripts/test-ci.js > extension_coverage.txt 2>&1
|
||||
node ./scripts/test-ci.js 2>&1 | tee extension_coverage.txt
|
||||
# Default the encoding to UTF-8 - It's not the default on Windows
|
||||
PYTHONUTF8=1 PYTHONPATH=.github/scripts python -m coverage_check extract-coverage extension_coverage.txt --type=extension --github-output --verbose
|
||||
|
||||
@@ -117,7 +110,7 @@ jobs:
|
||||
cd webview-ui
|
||||
# Ensure coverage dependency is installed
|
||||
npm install --no-save @vitest/coverage-v8
|
||||
npm run test:coverage > webview_coverage.txt 2>&1
|
||||
npm run test:coverage 2>&1 | tee webview_coverage.txt
|
||||
cd ..
|
||||
# Default the encoding to UTF-8 - It's not the default on Windows
|
||||
PYTHONUTF8=1 PYTHONPATH=.github/scripts python -m coverage_check extract-coverage webview-ui/webview_coverage.txt --type=webview --github-output --verbose
|
||||
@@ -132,21 +125,19 @@ jobs:
|
||||
path: |
|
||||
extension_coverage.txt
|
||||
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: Print test results and check for failures
|
||||
- name: Check for test failures
|
||||
run: |
|
||||
echo "Extension Tests Result: ${{ steps.extension_coverage.outcome }}"
|
||||
cat extension_coverage.txt
|
||||
|
||||
echo "Webview Tests Result: ${{ steps.webview_coverage.outcome }}"
|
||||
cat webview-ui/webview_coverage.txt
|
||||
|
||||
# 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" ]; then
|
||||
echo "Extension Integration Tests failed, see previous step for test output."
|
||||
fi
|
||||
if [ "${{ steps.webview_coverage.outcome }}" != "success" ]; then
|
||||
echo "Webview Tests failed, see previous step for test output."
|
||||
fi
|
||||
if [ "${{ steps.extension_coverage.outcome }}" != "success" ] || [ "${{ steps.webview_coverage.outcome }}" != "success" ]; then
|
||||
echo "Tests failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
Executable → Regular
+1
-17
@@ -1,17 +1 @@
|
||||
echo "Running pre-commit checks..."
|
||||
|
||||
# Run ESLint
|
||||
echo "Running ESLint..."
|
||||
npm run lint || {
|
||||
echo "❌ ESLint check failed. Please fix the errors and try committing again."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Run Prettier
|
||||
echo "Running Prettier..."
|
||||
npx lint-staged --verbose || {
|
||||
echo "❌ Prettier failed. Please fix the errors and try committing again."
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "✅ All checks passed!"
|
||||
lint-staged --no-stash
|
||||
|
||||
+12
-3
@@ -1,6 +1,15 @@
|
||||
{
|
||||
"extension": ["ts"],
|
||||
"spec": ["src/**/__tests__/*.ts", "eslint-rules/__tests__/**/*.test.ts"],
|
||||
"require": ["ts-node/register", "source-map-support/register", "./src/test/requires.ts"],
|
||||
"extension": [
|
||||
"ts"
|
||||
],
|
||||
"spec": [
|
||||
"src/**/__tests__/*.ts",
|
||||
"eslint-rules/__tests__/**/*.test.ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register",
|
||||
"source-map-support/register",
|
||||
"./src/test/requires.ts"
|
||||
],
|
||||
"recursive": true
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
dist/
|
||||
node_modules
|
||||
webview-ui/build/
|
||||
*.md
|
||||
package-lock.json
|
||||
src/core/prompts/system.ts
|
||||
src/core/prompts/model_prompts/claude4.ts
|
||||
evals/
|
||||
docs/
|
||||
out/
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"printWidth": 130,
|
||||
"semi": false,
|
||||
"bracketSameLine": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
Vendored
+2
-2
@@ -2,9 +2,9 @@
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"connor4312.esbuild-problem-matchers",
|
||||
"ms-vscode.extension-test-runner",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+40
-15
@@ -9,8 +9,14 @@
|
||||
"name": "Run Extension (production)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--disable-workspace-trust", "${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
@@ -22,8 +28,14 @@
|
||||
"name": "Run Extension (staging)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--disable-workspace-trust", "${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
@@ -35,8 +47,14 @@
|
||||
"name": "Run Extension (local)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--disable-workspace-trust", "${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
@@ -57,7 +75,9 @@
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "clean-tmp-user",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"postDebugTask": "stop",
|
||||
@@ -71,22 +91,27 @@
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Run Standalone Service",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"name": "Run cline-core service",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/**",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/dist-standalone",
|
||||
"outFiles": ["${workspaceFolder}/dist-standalone/**/*.js"],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist-standalone/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "compile-standalone",
|
||||
"env": {
|
||||
// Turns on grpc debug log.
|
||||
//"GRPC_TRACE": "all",
|
||||
//"GRPC_VERBOSITY": "DEBUG",
|
||||
"NODE_PATH": "${workspaceFolder}/dist-standalone/node_modules",
|
||||
|
||||
"HOST_BRIDGE_ADDRESS": "localhost:50052"
|
||||
"NODE_PATH": "${workspaceFolder}/dist-standalone/node_modules"
|
||||
},
|
||||
"program": "standalone.js"
|
||||
"program": "cline-core.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+25
-2
@@ -6,8 +6,31 @@
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true, // set this to false to include "out" folder in search results
|
||||
"dist": true // set this to false to include "dist" folder in search results
|
||||
"dist": true, // set this to false to include "dist" folder in search results,
|
||||
"node_modules": true,
|
||||
"dist-standalone": true
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"typescript.preferences.quoteStyle": "double",
|
||||
// Protobuf settings
|
||||
"protoc": {
|
||||
"options": [
|
||||
"--proto_path=proto"
|
||||
]
|
||||
},
|
||||
// Enable Lint and format using Biome
|
||||
"biome.enabled": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+36
-10
@@ -30,7 +30,13 @@
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"dependsOn": ["npm: protos", "npm: build:webview", "npm: dev:webview", "npm: watch:tsc", "npm: watch:esbuild"],
|
||||
"dependsOn": [
|
||||
"npm: protos",
|
||||
"npm: build:webview",
|
||||
"npm: dev:webview",
|
||||
"npm: watch:tsc",
|
||||
"npm: watch:esbuild"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
@@ -60,7 +66,9 @@
|
||||
"problemMatcher": [],
|
||||
"isBackground": true,
|
||||
"label": "npm: build:webview",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"dependsOn": [
|
||||
"npm: protos"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "always"
|
||||
@@ -78,7 +86,9 @@
|
||||
"problemMatcher": [],
|
||||
"isBackground": true,
|
||||
"label": "npm: build:webview:test",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"dependsOn": [
|
||||
"npm: protos"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "always"
|
||||
@@ -113,7 +123,9 @@
|
||||
],
|
||||
"isBackground": true,
|
||||
"label": "npm: dev:webview",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"dependsOn": [
|
||||
"npm: protos"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "always"
|
||||
@@ -149,7 +161,9 @@
|
||||
},
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"dependsOn": [
|
||||
"npm: protos"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "always"
|
||||
@@ -185,7 +199,9 @@
|
||||
},
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild:test",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"dependsOn": [
|
||||
"npm: protos"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "always"
|
||||
@@ -204,7 +220,9 @@
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:tsc",
|
||||
"dependsOn": ["npm: protos"],
|
||||
"dependsOn": [
|
||||
"npm: protos"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "always"
|
||||
@@ -215,7 +233,9 @@
|
||||
"script": "watch-tests",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"dependsOn": ["npm: protos"],
|
||||
"dependsOn": [
|
||||
"npm: protos"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"group": "watchers"
|
||||
@@ -224,7 +244,11 @@
|
||||
},
|
||||
{
|
||||
"label": "tasks: watch-tests",
|
||||
"dependsOn": ["npm: protos", "npm: watch", "npm: watch-tests"],
|
||||
"dependsOn": [
|
||||
"npm: protos",
|
||||
"npm: watch",
|
||||
"npm: watch-tests"
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
@@ -235,7 +259,9 @@
|
||||
{
|
||||
"label": "clean-tmp-user",
|
||||
"type": "shell",
|
||||
"dependsOn": ["watch"],
|
||||
"dependsOn": [
|
||||
"watch"
|
||||
],
|
||||
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
|
||||
}
|
||||
],
|
||||
|
||||
+1
-1
@@ -61,6 +61,6 @@ old_docs/**
|
||||
!assets/icons/**
|
||||
|
||||
# Ignore E2E build files
|
||||
e2e-build.js
|
||||
e2e-build.mjs
|
||||
e2e.vsix
|
||||
test-results/
|
||||
|
||||
+137
@@ -1,5 +1,142 @@
|
||||
# Changelog
|
||||
|
||||
## [3.26.0]
|
||||
|
||||
- Add Z AI as a new API provider with GLM-4.5 and GLM-4.5 Air models, offering competitive performance with cost-effective pricing especially for Chinese language tasks (Thanks @jues!)
|
||||
- Add Cline Sonic Alpha model - experimental advanced model with 262K context window for complex coding tasks
|
||||
- Add support for LM Studio local models from v0 API endpoint with configurable max tokens
|
||||
- Fix Ollama context window configuration not being used in requests
|
||||
|
||||
## [3.25.3]
|
||||
|
||||
- Fix bug where 'Enable checkpoints' and 'Disable MCP Marketplace' settings would be reset to default on reload
|
||||
- Move the position of the focus chain edit button when a scrollbar is present. Make the pencil icon bigger and better centered.
|
||||
|
||||
## [3.25.2]
|
||||
|
||||
- Fix attempt_completion showing twice in chat due to partial logic not being handled correctly
|
||||
- Fix OpenRouter showing cline credits error after 402 response
|
||||
|
||||
## [3.25.1]
|
||||
|
||||
- Fix attempt_completion command showing twice in chat view when updating progress checklist
|
||||
- Fix bug where announcement banner could not be dismissed
|
||||
- Add GPT-OSS models to AWS Bedrock
|
||||
|
||||
## [3.25.0]
|
||||
|
||||
- **Focus Chain:** Automatically creates and maintains todo lists as you work with Cline, breaking down complex tasks into manageable steps with real-time progress tracking
|
||||
- **Auto Compact:** Intelligently manages conversation context to prevent token limit errors by automatically compacting older messages while preserving important context
|
||||
- **Deep Planning:** New `/deep-planning` slash command for structured 4-step implementation planning that integrates with Focus Chain for automatic progress tracking
|
||||
- Add support for 200k context window for Claude Sonnet 4 in OpenRouter and Cline providers
|
||||
- Add option to configure custom base URL for Requesty provider
|
||||
|
||||
## [3.24.0]
|
||||
|
||||
- Add OpenAI GPT-5 Chat(gpt-5-chat-latest)
|
||||
- Add custom browser arguments setting to allow passing flags to the Chrome executable for better headless compatibility.
|
||||
- Add 1m context window model support for claude sonnet 4
|
||||
- Fis the API Keys URL for Requesty
|
||||
- Set gpt5 max tokens to 8_192 to fix 'context window exceeded' error
|
||||
- Fix issue where fallback request to retrieve cost was not using correct auth token
|
||||
- Add OpenAI context window exceeded error handling
|
||||
- Calibrate input token counts when using anthropic models of sap ai core provider
|
||||
|
||||
## [3.23.0]
|
||||
|
||||
- Add caching support for Bedrock inferences using SAP AI Core and minor refactor
|
||||
- Improve visibility for mode switch background color on different themes
|
||||
- Fix terminal commands putting webview in blocked state
|
||||
|
||||
## [3.22.0]
|
||||
|
||||
- Implemented a retry strategy for Cerebras to handle rate limit issues due to its generation speed
|
||||
- Add support for GPT-5 models to SAP AI Core Provider
|
||||
- Support sending context to active webview when editor panels are opened.
|
||||
- Fix bug where running out of credits on Cline accounts would show '402 empty body' response instead of 'buy credits' component
|
||||
- Fix LiteLLM Proxy Provider Cost Tracking
|
||||
|
||||
## [3.21.0]
|
||||
|
||||
- Add support for GPT-5 model family including GPT-5, GPT-5 Mini, and GPT-5 Nano with prompt caching support and set GPT-5 as the new default model
|
||||
- Add "Take a Tour" button for new users to easily access the VSCode walkthrough and improve onboarding experience
|
||||
- Enhance plan mode response handling with better exploration parameter support
|
||||
|
||||
## [3.20.13]
|
||||
|
||||
- Fix prompt caching support for Opus 4.1 on OpenRouter/Cline
|
||||
|
||||
## [3.20.12]
|
||||
|
||||
- Add Claude Opus 4.1 model support to AWS Bedrock provider (Thanks @omercelik!)
|
||||
- Fix prompt caching and extended thinking support for Claude Opus 4.1 in Anthropic provider
|
||||
|
||||
## [3.20.11]
|
||||
|
||||
Add gpt-oss-120b as a Cerebras model
|
||||
Add Opus 4.1 through Claude Code
|
||||
|
||||
## [3.20.10]
|
||||
|
||||
- Add OpenAI's new open-source models (GPT-OSS-120B and GPT-OSS-20B) to Hugging Face and Groq providers
|
||||
|
||||
## [3.20.9]
|
||||
|
||||
- Add support for Claude Opus 4.1 model in Anthropic provider
|
||||
- Add Baseten as a new API provider with support for DeepSeek, Llama, and Kimi K2 models (Thanks @AlexKer!)
|
||||
- Fix error messages not clearing from UI when retrying failed tasks
|
||||
- Fix chat input box positioning issues
|
||||
|
||||
## [3.20.8]
|
||||
|
||||
- Add navbar tooltips on hover
|
||||
|
||||
## [3.20.7]
|
||||
|
||||
- Fix circular dependency that affect the github workflow Tests / test (pull_request)
|
||||
|
||||
## [3.20.6]
|
||||
|
||||
- Fix login check on extension restart
|
||||
|
||||
## [3.20.5]
|
||||
|
||||
- Fix authentication persistence issues that could cause users to be logged out unexpectedly
|
||||
|
||||
## [3.20.4]
|
||||
|
||||
- Add new Cerebras models
|
||||
- Update rate limits for existing Cerebras models
|
||||
- Fix for delete task dialog
|
||||
|
||||
## [3.20.3]
|
||||
|
||||
- Add Huawei Cloud MaaS Provider (Thanks @ddling!)
|
||||
- Add Cerebras Qwen 3 235B instruct model (Thanks @kevint-cerebras!)
|
||||
- Add DeepSeek R1 0528 support under Hugging Face (Thanks @0ne0rZer0!)
|
||||
- Fix Global Rules directory documentation for Linux/WSL systems
|
||||
- Fix token counting when using VSCode LM API provider
|
||||
- Fix input field stealing focus issue by only focusing on visible and active editor panels
|
||||
- Fix duplicate tool registration for claude4-experimental
|
||||
- Trim input value for URL fields
|
||||
|
||||
## [3.20.2]
|
||||
|
||||
- Fixed issue with sap ai core client credentials storage
|
||||
- Fix Qwen Api option inconsistency between UI and API layer
|
||||
- Fix credit balance out of sync issue on account switching
|
||||
- Fix Claude Code CLAUDE_CODE_MAX_OUTPUT_TOKENS
|
||||
- Fix cursor state after restoring files to be disabled after checked out
|
||||
- Fix issue where checkpointing blocked UI
|
||||
|
||||
## [3.20.1]
|
||||
|
||||
- Fix for files being deleted when switching modes or closing tasks
|
||||
|
||||
## [3.20.0]
|
||||
|
||||
- Add account balance display for all organization members, allowing non-admin users to view their organization's credit balance and add credits
|
||||
|
||||
## [3.19.8]
|
||||
|
||||
- Add Claude Code support on Windows with improved system prompt handling to fix E2BIG errors (Thanks @BarreiroT!)
|
||||
|
||||
+6
-9
@@ -14,14 +14,11 @@ Bug reports help make Cline better for everyone! Before creating a new issue, pl
|
||||
## Before Contributing
|
||||
|
||||
All contributions must begin with a GitHub Issue, unless the change is for small bug fixes, typo corrections, minor wording improvements, or simple type fixes that don't change functionality.
|
||||
|
||||
- **Check existing issues**: Search [GitHub Issues](https://github.com/cline/cline/issues).
|
||||
- **Create an issue**: Use appropriate templates:
|
||||
- **Contributions:** Use the "Contribution Request" template to propose what you'd like to work on.
|
||||
- **Bugs:** "Bug Report" template for reporting issues.
|
||||
- **Features:** "Detailed Feature Proposal" template for suggesting new features.
|
||||
- **Wait for approval**: A core Cline contributor must approve your contribution request before you start implementation.
|
||||
- **Claim issues**: Once approved, the issue will be assigned to you.
|
||||
**For features and contributions**:
|
||||
- First check the [Feature Requests discussions board](https://github.com/cline/cline/discussions/categories/feature-requests) for similar ideas
|
||||
- If your idea is new, create a new feature request
|
||||
- Wait for approval from core maintainers before starting implementation
|
||||
- Once approved, feel free to begin working on a PR with the help of our community!
|
||||
|
||||
**PRs without approved issues may be closed.**
|
||||
|
||||
@@ -150,7 +147,7 @@ Anyone can contribute code to Cline, but we ask that you follow these guidelines
|
||||
- Run `npm run lint` to check code style
|
||||
- Run `npm run format` to automatically format code
|
||||
- All PRs must pass CI checks which include both linting and formatting
|
||||
- Address any ESLint warnings or errors before submitting
|
||||
- Address any warnings or errors from linter before submitting
|
||||
- Follow TypeScript best practices and maintain type safety
|
||||
|
||||
3. **Testing**
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.1.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true,
|
||||
"defaultBranch": "main"
|
||||
},
|
||||
"assist": {
|
||||
"enabled": true,
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on",
|
||||
"useSortedAttributes": "on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"domains": {
|
||||
"react": "recommended"
|
||||
},
|
||||
// Ideally we would want to turn on all the rules that are currently off,
|
||||
// keeping them off currently to make sure only changes on the migrations
|
||||
// are included in the initial PR before we apply the format and lint changes.
|
||||
// TODO: turn on all rules that are currently off if applicable.
|
||||
// TODO: Remove --diagnostic-level=error from CI commands.
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"correctness": {
|
||||
"useExhaustiveDependencies": "off",
|
||||
"noUndeclaredVariables": "off",
|
||||
"noEmptyPattern": "off",
|
||||
"useJsxKeyInIterable": "off",
|
||||
"noInnerDeclarations": "off",
|
||||
"useHookAtTopLevel": "off",
|
||||
"useYield": "off",
|
||||
"noConstructorReturn": "off",
|
||||
"noInvalidPositionAtImportRule": "off",
|
||||
"noSwitchDeclarations": "off"
|
||||
},
|
||||
"a11y": "off",
|
||||
"style": {
|
||||
"useNodejsImportProtocol": "off",
|
||||
"useImportType": "off",
|
||||
"useBlockStatements": "warn",
|
||||
"useNamingConvention": "off",
|
||||
"useThrowOnlyError": "info",
|
||||
"useConsistentArrayType": "off",
|
||||
"noParameterAssign": "off",
|
||||
"useAsConstAssertion": "off",
|
||||
"useDefaultParameterLast": "off",
|
||||
"noNonNullAssertion": "off",
|
||||
"useEnumInitializers": "off",
|
||||
"useSelfClosingElements": "off",
|
||||
"useSingleVarDeclarator": "off",
|
||||
"useNumberNamespace": "off",
|
||||
"noInferrableTypes": "off",
|
||||
"useTemplate": "off",
|
||||
"noUselessElse": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noDoubleEquals": "warn",
|
||||
"noImplicitAnyLet": "info",
|
||||
"noThenProperty": "off",
|
||||
"noAsyncPromiseExecutor": "off",
|
||||
"noImportAssign": "off",
|
||||
"noExplicitAny": "off",
|
||||
"noControlCharactersInRegex": "off",
|
||||
"noShadowRestrictedNames": "off",
|
||||
"noArrayIndexKey": "info",
|
||||
"noAssignInExpressions": "warn"
|
||||
},
|
||||
"complexity": {
|
||||
"noUselessConstructor": "off",
|
||||
"useOptionalChain": "off",
|
||||
"noBannedTypes": "off",
|
||||
"useLiteralKeys": "off",
|
||||
"noUselessCatch": "off",
|
||||
"noUselessSwitchCase": "off",
|
||||
"noStaticOnlyClass": "off"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "warn"
|
||||
}
|
||||
}
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 130,
|
||||
"lineEnding": "lf",
|
||||
"formatWithErrors": true
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"semicolons": "asNeeded",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSameLine": true,
|
||||
"bracketSpacing": true,
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "all"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
"trailingCommas": "none",
|
||||
"expand": "always"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/dist/**",
|
||||
"!**/dist-*/**",
|
||||
"!**/out/**",
|
||||
"!**/evals/**",
|
||||
"!**/playwright/**",
|
||||
"!**/test-results/**",
|
||||
"!**/node_modules/**",
|
||||
"!**/webview-ui/build/**",
|
||||
"!**/generated/**",
|
||||
"!**/proto/**"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
"src/dev/grit/process-env.grit"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/hosts/vscode/**",
|
||||
"!**/test/**",
|
||||
"!src/extension.ts"
|
||||
],
|
||||
"plugins": [
|
||||
"src/dev/grit/vscode-api.grit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"includes": [
|
||||
"**",
|
||||
"!src/core/storage/state-migrations.ts",
|
||||
"!src/core/storage/FileContextTracker.ts",
|
||||
"!src/core/context/context-tracking/FileContextTracker.ts",
|
||||
"!src/common.ts",
|
||||
"!src/core/storage/utils/state-helpers.ts",
|
||||
"!src/extension.ts"
|
||||
],
|
||||
"plugins": [
|
||||
"src/dev/grit/use-cache-service.grit"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,6 @@ lint:
|
||||
|
||||
except: # Add exceptions for current patterns that contradict STANDARD settings
|
||||
- RPC_PASCAL_CASE # rpcs are camel case (start with lowercase)
|
||||
- PACKAGE_DIRECTORY_MATCH # the protos in the cline package are not in a dir named cline.
|
||||
- RPC_REQUEST_RESPONSE_UNIQUE # request messages are not unique.
|
||||
- RPC_REQUEST_STANDARD_NAME # request messages dont all end with Request
|
||||
- RPC_RESPONSE_STANDARD_NAME # response messages dont all end with Response
|
||||
|
||||
+26
-8
@@ -68,7 +68,10 @@
|
||||
},
|
||||
{
|
||||
"group": "Improving Your Prompting Skills",
|
||||
"pages": ["prompting/prompt-engineering-guide", "prompting/cline-memory-bank"]
|
||||
"pages": [
|
||||
"prompting/prompt-engineering-guide",
|
||||
"prompting/cline-memory-bank"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Features",
|
||||
@@ -79,6 +82,8 @@
|
||||
"features/drag-and-drop",
|
||||
"features/plan-and-act",
|
||||
"features/slash-commands/workflows",
|
||||
"features/focus-chain",
|
||||
"features/auto-compact",
|
||||
"features/editing-messages",
|
||||
{
|
||||
"group": "@ Mentions",
|
||||
@@ -97,7 +102,8 @@
|
||||
"features/slash-commands/new-task",
|
||||
"features/slash-commands/new-rule",
|
||||
"features/slash-commands/smol",
|
||||
"features/slash-commands/report-bug"
|
||||
"features/slash-commands/report-bug",
|
||||
"features/slash-commands/deep-planning"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -146,9 +152,14 @@
|
||||
"pages": [
|
||||
"provider-config/anthropic",
|
||||
"provider-config/claude-code",
|
||||
"provider-config/aws-bedrock-with-apikey-authentication",
|
||||
"provider-config/aws-bedrock-with-credentials-authentication",
|
||||
"provider-config/aws-bedrock-with-profile-authentication",
|
||||
{
|
||||
"group": "AWS Bedrock",
|
||||
"pages": [
|
||||
"provider-config/aws-bedrock/api-key",
|
||||
"provider-config/aws-bedrock/iam-credentials",
|
||||
"provider-config/aws-bedrock/cli-profile"
|
||||
]
|
||||
},
|
||||
"provider-config/gcp-vertex-ai",
|
||||
"provider-config/litellm-and-cline-using-codestral",
|
||||
"provider-config/vscode-language-model-api",
|
||||
@@ -173,11 +184,16 @@
|
||||
},
|
||||
{
|
||||
"group": "Troubleshooting",
|
||||
"pages": ["troubleshooting/terminal-quick-fixes", "troubleshooting/terminal-integration-guide"]
|
||||
"pages": [
|
||||
"troubleshooting/terminal-quick-fixes",
|
||||
"troubleshooting/terminal-integration-guide"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "More Info",
|
||||
"pages": ["more-info/telemetry"]
|
||||
"pages": [
|
||||
"more-info/telemetry"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -192,6 +208,8 @@
|
||||
"prompt": "Search Cline documentation..."
|
||||
},
|
||||
"contextual": {
|
||||
"options": ["copy"]
|
||||
"options": [
|
||||
"copy"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "Automatic Context Summarization"
|
||||
sidebarTitle: "Auto Compact"
|
||||
---
|
||||
|
||||
When your conversation approaches the model's context window limit, Cline automatically summarizes it to free up space and keep working.
|
||||
|
||||
## How It Works
|
||||
|
||||
Cline monitors token usage during your conversation. When you're getting close to the limit, he:
|
||||
|
||||
1. Creates a comprehensive summary of everything that's happened
|
||||
2. Preserves all the technical details, code changes, and decisions
|
||||
3. Replaces the conversation history with the summary
|
||||
4. Continues exactly where he left off
|
||||
|
||||
You'll see a summarization tool call when this happens, showing the total cost like any other api call in the chat view.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
Previously, Cline would truncate older messages when hitting context limits. This meant losing important context from earlier in the conversation.
|
||||
|
||||
Now with summarization:
|
||||
- All technical decisions and code patterns are preserved
|
||||
- File changes and project context remain intact
|
||||
- Cline remembers everything he's done
|
||||
- You can work on much larger projects without interruption
|
||||
|
||||
<Tip>
|
||||
Context Summarization synergizes beautifully with [Focus Chain](/features/focus-chain). When Focus Chain is enabled, todo lists persist across summarizations. This means Cline can work on long-horizon tasks that span multiple context windows while staying on track with the todo list guiding him through each reset.
|
||||
</Tip>
|
||||
|
||||
## Technical Details
|
||||
|
||||
The summarization happens through your configured API provider using the same model you're already using. It leverages prompt caching to minimize costs.
|
||||
|
||||
1. Cline uses a [summarization prompt](https://github.com/cline/cline/blob/main/src/core/prompts/contextManagement.ts) to request a summary of the conversation.
|
||||
|
||||
2. Once the summary is generated, Cline replaces the conversation history with a [continuation prompt](https://github.com/cline/cline/blob/main/src/core/prompts/contextManagement.ts#L69) that asks Cline to keep working and provides the summary as context.
|
||||
|
||||
Different models have different context window thresholds for when auto-summarization kicks in. You can see how thresholds are determined in [context-window-utils.ts](https://github.com/cline/cline/blob/main/src/core/context/context-management/context-window-utils.ts).
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
Summarization leverages your existing prompt cache from the conversation, so it costs about the same as any other tool call.
|
||||
|
||||
Since most input tokens are already cached, you're primarily paying for the summary generation (output tokens), making it very cost-effective.
|
||||
|
||||
## Restoring Context with Checkpoints
|
||||
|
||||
You can use [checkpoints](/features/checkpoints) to restore your task state from before a summarization occurred. This means you never truly lose context - you can always roll back to previous versions of your conversation.
|
||||
|
||||
<Note>
|
||||
Editing a message before a summarization tool call will work similarly to a checkpoint, allowing you to restore the conversation to that point.
|
||||
</Note>
|
||||
@@ -11,7 +11,19 @@ You can create a rule by clicking the `+` button in the Rules tab. This will ope
|
||||
Once you save the file:
|
||||
|
||||
- Your rule will be stored in the `.clinerules/` directory in your project (if it's a Workspace Rule)
|
||||
- Or in the `Documents/Cline/Rules` directory (if it's a Global Rule).
|
||||
- Or in the Global Rules directory (if it's a Global Rule):
|
||||
|
||||
### Global Rules Directory Location
|
||||
|
||||
The location of your Global Rules directory depends on your operating system:
|
||||
|
||||
| Operating System | Default Location | Notes |
|
||||
|------------------|------------------|-------|
|
||||
| **Windows** | `Documents\Cline\Rules` | Uses system Documents folder |
|
||||
| **macOS** | `~/Documents/Cline/Rules` | Uses user Documents folder |
|
||||
| **Linux/WSL** | `~/Documents/Cline/Rules` | May fall back to `~/Cline/Rules` on some systems |
|
||||
|
||||
> **Note for Linux/WSL users**: If you don't find your global rules in `~/Documents/Cline/Rules`, check `~/Cline/Rules` as the location may vary depending on your system configuration and whether the Documents directory exists.
|
||||
|
||||
You can also have Cline create a rule for you by using the [`/newrule` slash command](/features/slash-commands/new-rule) in the chat.
|
||||
|
||||
|
||||
@@ -11,4 +11,4 @@ Dragging and dropping workspace files into Cline will automatically create a [fi
|
||||
|
||||
### Supported File Types
|
||||
|
||||
Cline supports dragging external images from your file system, as well as files from your workspace.
|
||||
Cline supports dragging external images, pdfs, csv, excel, and other text files from your file system, as well as files from your workspace.
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
---
|
||||
title: "Focus Chain"
|
||||
sidebarTitle: "Focus Chain"
|
||||
---
|
||||
|
||||
Focus Chain is a task management enhancement feature in Cline that provides automatic todo list management with real-time progress tracking throughout your tasks.
|
||||
|
||||
This enables Cline to work on long-horizon tasks, seamlessly managing the context sent to LLMs, and keeping Cline on track across many context window resets.
|
||||
|
||||
<Tip>
|
||||
Focus Chain works particularly well with Cline's [Deep Planning slash command](/features/slash-commands/deep-planning), providing seamless progress tracking for implementation tasks created through the [planning process](/features/plan-and-act).
|
||||
</Tip>
|
||||
|
||||
## Key Features
|
||||
|
||||
### Automatic Todo List Generation
|
||||
|
||||
Cline analyzes your task and automatically creates a comprehensive todo list with:
|
||||
- Clear, actionable items in markdown checklist format
|
||||
- Logical breakdown of complex tasks into manageable steps
|
||||
- Real-time updates as work progresses
|
||||
|
||||
### User-Editable Todo Lists
|
||||
|
||||
Todo lists are stored as editable markdown files:
|
||||
- Direct editing through your preferred markdown editor
|
||||
- Automatic detection of changes you make
|
||||
- Seamless integration back into Cline's workflow
|
||||
- Quick access through the edit button in the task header
|
||||
|
||||
### Visual Progress Tracking
|
||||
|
||||
The task header displays clear progress indicators:
|
||||
- **Step counters** showing current progress (e.g., "3/8")
|
||||
- **Completed items** clearly marked with checkmarks
|
||||
- **Current work** highlighted with indicators
|
||||
- **Expandable view** to see the full todo list
|
||||
|
||||
### Smart Reminder System
|
||||
|
||||
Configurable reminders ensure todo lists stay current:
|
||||
- Default reminder every 6 messages (customizable 1-100)
|
||||
- Automatic prompts when switching from Plan Mode to Act Mode
|
||||
- User-triggered updates when todo lists are manually edited
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
<Steps>
|
||||
<Step title="Open Cline Settings">
|
||||
- Click the gear icon in the Cline sidebar
|
||||
- Navigate to the "Features" section
|
||||
</Step>
|
||||
<Step title="Enable Focus Chain">
|
||||
- Check "Enable Focus Chain"
|
||||
- Optionally adjust "Remind Cline Interval" (default: 6 messages)
|
||||
</Step>
|
||||
<Step title="Start a New Task">
|
||||
- Begin a new task
|
||||
- Cline will automatically start creating and managing todo lists
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
| Setting | Default | Range | Description |
|
||||
|---------|---------|-------|-------------|
|
||||
| Enable Focus Chain | Disabled | On/Off | Enables enhanced task progress tracking |
|
||||
| Remind Cline Interval | 6 | 1-100 messages | How often Cline updates the todo list |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
#### 1. Task Initiation
|
||||
|
||||
When you start a new task with Focus Chain enabled:
|
||||
|
||||
``` markdown User Request
|
||||
User: "Create a user authentication system for my React app"
|
||||
|
||||
Cline: [Analyzes request and creates todo list]
|
||||
```
|
||||
|
||||
#### 2. Todo List Created
|
||||
|
||||
Cline creates a comprehensive plan for the task, stored in a markdown file:
|
||||
|
||||
```markdown Todo List Created
|
||||
- [ ] Set up project structure
|
||||
- [ ] Install authentication dependencies
|
||||
- [ ] Create user registration component
|
||||
- [ ] Implement login functionality
|
||||
- [ ] Add password validation
|
||||
- [ ] Set up user database schema
|
||||
- [ ] Write authentication tests
|
||||
- [ ] Deploy to staging environment
|
||||
```
|
||||
|
||||
#### 3. Progress Tracking
|
||||
|
||||
As Cline works, the task header shows real-time progress:
|
||||
|
||||
```markdown Todo List Header
|
||||
[3/8] Implement login functionality ⌄
|
||||
```
|
||||
|
||||
Click to expand and see the full list:
|
||||
|
||||
```markdown Full Todo List
|
||||
✓ Set up project structure
|
||||
✓ Install authentication dependencies
|
||||
✓ Create user registration component
|
||||
○ Implement login functionality ← Currently working
|
||||
○ Add password validation
|
||||
○ Set up user database schema
|
||||
○ Write authentication tests
|
||||
○ Deploy to staging environment
|
||||
```
|
||||
|
||||
#### 4. User Editing
|
||||
|
||||
Need to tweak the todo list? No problem.
|
||||
|
||||
<Steps>
|
||||
<Step title="Open the todo list">
|
||||
Click the edit button in the expanded todo view
|
||||
</Step>
|
||||
<Step title="Edit the markdown file">
|
||||
A markdown file opens in your editor:
|
||||
|
||||
```markdown Editing Todo List
|
||||
# Focus Chain Todo List for Task abc123
|
||||
|
||||
<!-- Edit this markdown file to update your focus chain todo list -->
|
||||
<!-- Use - [ ] for incomplete items and - [x] for completed items -->
|
||||
|
||||
- [x] Set up project structure
|
||||
- [x] Install authentication dependencies (e.g., Firebase Auth)
|
||||
- [x] Create user registration component
|
||||
- [ ] Implement login functionality
|
||||
- [ ] Add password reset feature
|
||||
- [ ] Set up protected routes
|
||||
- [ ] Implement logout functionality
|
||||
- [ ] Add user profile page
|
||||
- [ ] Write authentication tests
|
||||
- [ ] Deploy to staging environment
|
||||
|
||||
<!-- Save this file to update the task's todo list -->
|
||||
```
|
||||
</Step>
|
||||
<Step title="Make your changes">
|
||||
Add, remove, or reorder items as needed
|
||||
</Step>
|
||||
<Step title="Save the file">
|
||||
Cline automatically detects and uses your updates
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## File Structure
|
||||
|
||||
### Todo List Storage
|
||||
|
||||
Todo lists are stored as markdown files in your task directory:
|
||||
|
||||
``` markdown
|
||||
<VSCode Global Storage>/
|
||||
tasks/
|
||||
<taskId>/
|
||||
focus_chain_taskid_<taskId>.md
|
||||
... other task files
|
||||
```
|
||||
|
||||
### Markdown Format
|
||||
|
||||
Todo files use standard markdown checklist syntax:
|
||||
|
||||
```markdown Example Todo Syntax
|
||||
# Focus Chain Todo List for Task abc123
|
||||
|
||||
<!-- Edit this markdown file to update your focus chain todo list -->
|
||||
<!-- Use the format: - [ ] for incomplete items and - [x] for completed items -->
|
||||
|
||||
- [x] Set up project structure
|
||||
- [x] Install authentication dependencies
|
||||
- [ ] Create user registration component
|
||||
- [ ] Implement login functionality
|
||||
- [ ] Add password validation
|
||||
- [ ] Set up user database schema
|
||||
- [ ] Write authentication tests
|
||||
- [ ] Deploy to staging environment
|
||||
|
||||
<!-- Save this file and the todo list will be updated in the task -->
|
||||
```
|
||||
|
||||
|
||||
## Integration with Plan/Act Mode
|
||||
|
||||
Focus Chain works seamlessly with Cline's [Plan/Act mode](/features/plan-and-act):
|
||||
|
||||
- **Plan Mode**: Optional todo lists for presenting concrete steps
|
||||
- **Act Mode**: Automatic todo creation when switching from Plan Mode
|
||||
|
||||
<Tip>
|
||||
For complex projects, start in Plan Mode to discuss and refine your approach before switching to Act Mode for implementation.
|
||||
</Tip>
|
||||
|
||||
## Best Practices
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="For Effective Todo Lists">
|
||||
1. **Start with Clear Requests**
|
||||
- Provide detailed initial task descriptions
|
||||
- Include specific requirements and constraints
|
||||
- Mention any preferred technologies or approaches
|
||||
|
||||
2. **Review Generated Lists**
|
||||
- Check that Cline's breakdown aligns with your expectations
|
||||
- Verify that all important steps are included
|
||||
- Ensure the order makes sense for your project
|
||||
|
||||
3. **Edit When Needed**
|
||||
- Add missing steps you identify
|
||||
- Remove unnecessary items
|
||||
- Reorder steps for better workflow
|
||||
- Add more specific details to general items
|
||||
</Accordion>
|
||||
<Accordion title="For Complex Projects">
|
||||
1. **Use Plan Mode First**
|
||||
- Discuss the approach before implementation
|
||||
- Refine requirements through conversation
|
||||
- Switch to Act Mode when ready to begin work
|
||||
|
||||
2. **Break Down Large Tasks**
|
||||
- Split complex projects into smaller, manageable tasks
|
||||
- Create separate todo lists for different components
|
||||
- Focus on one major area at a time
|
||||
|
||||
3. **Regular Reviews**
|
||||
- Check progress periodically during long tasks
|
||||
- Update todo lists as requirements evolve
|
||||
- Communicate changes to Cline through edits
|
||||
</Accordion>
|
||||
<Accordion title="For Collaboration">
|
||||
1. **Share Todo Files**
|
||||
- Todo markdown files can be shared with team members
|
||||
- Include in version control for project documentation
|
||||
- Use as basis for project planning discussions
|
||||
|
||||
2. **Consistent Format**
|
||||
- Follow the standard markdown checklist format
|
||||
- Keep item descriptions clear and actionable
|
||||
- Use consistent terminology across todo lists
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Having issues? Try these quick fixes:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Todo list not updating?">
|
||||
- Check that Focus Chain is enabled in settings
|
||||
- Focus Chain may not work as well with smaller, less capable models
|
||||
- Ensure file permissions are correct in the task directory
|
||||
</Accordion>
|
||||
<Accordion title="Can't edit todo file?">
|
||||
- Verify your editor supports markdown
|
||||
- Check VSCode has write permissions for the directory
|
||||
</Accordion>
|
||||
<Accordion title="Progress not displaying?">
|
||||
- Ensure todo items use correct syntax (`- [ ]` and `- [x]`)
|
||||
- Verify the markdown file is properly formatted
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Still stuck? Use the [/reportbug](/features/slash-commands/report-bug) command in Cline to get help.
|
||||
|
||||
## Technical Details (for the curious)
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="File Monitoring">
|
||||
- Real-time file watching detects changes to todo markdown files
|
||||
- Automatic synchronization between file edits and UI updates
|
||||
- Graceful handling of file creation, modification, and deletion
|
||||
</Accordion>
|
||||
<Accordion title="Progress Calculation">
|
||||
- Dynamic counting of completed vs. total todo items
|
||||
- Support for both `- [x]` and `- [X]` completion syntax
|
||||
- Unicode symbols (✓, ○) for enhanced visual display
|
||||
</Accordion>
|
||||
<Accordion title="Privacy Considerations">
|
||||
- Todo lists stored locally in VSCode workspace
|
||||
- No todo content transmitted to external services
|
||||
- Usage telemetry (can be disabled in settings)
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Focus Chain turns Cline into your personal project manager, keeping you on track and your tasks organized. Give it a try on your next project!
|
||||
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: "Deep Planning Command"
|
||||
sidebarTitle: "/deep-planning"
|
||||
---
|
||||
|
||||
`/deep-planning` transforms Cline into a meticulous architect who investigates your codebase, asks clarifying questions, and creates a comprehensive implementation plan before writing a single line of code.
|
||||
|
||||
When you use `/deep-planning`, Cline follows a four-step process that mirrors how senior developers approach complex features: thorough investigation, discussion & clarification of requirements, detailed planning, and structured task creation with progress tracking.
|
||||
|
||||
## The Four-Step Process
|
||||
|
||||
### Step 1: Silent Investigation
|
||||
|
||||
Cline becomes a detective, silently exploring your codebase to understand its structure, patterns, and constraints. He examines source files, analyzes import patterns, discovers class hierarchies, and identifies technical debt markers. No commentary, no narration - just focused research.
|
||||
|
||||
During this phase, Cline runs commands like:
|
||||
- Finding all class and function definitions across your codebase
|
||||
- Analyzing import patterns to understand dependencies
|
||||
- Discovering project structure and file organization
|
||||
- Identifying TODOs and technical debt
|
||||
|
||||
### Step 2: Discussion and Questions
|
||||
|
||||
Once Cline understands your codebase, he asks targeted questions that will shape the implementation. These aren't generic questions - they're specific to your project and the feature you're building.
|
||||
|
||||
Questions might cover:
|
||||
- Clarifying ambiguous requirements
|
||||
- Choosing between equally valid implementation approaches
|
||||
- Confirming assumptions about system behavior
|
||||
- Understanding preferences for technical decisions
|
||||
|
||||
### Step 3: Implementation Plan Document
|
||||
|
||||
Cline creates a structured markdown document (`implementation_plan.md`) that serves as your implementation blueprint. This isn't a vague outline - it's a detailed specification with exact file paths, function signatures, and implementation order.
|
||||
|
||||
The plan includes eight comprehensive sections:
|
||||
- **Overview**: The goal and high-level approach
|
||||
- **Types**: Complete type definitions and data structures
|
||||
- **Files**: Exact files to create, modify, or delete
|
||||
- **Functions**: New and modified functions with signatures
|
||||
- **Classes**: Class modifications and inheritance details
|
||||
- **Dependencies**: Package requirements and versions
|
||||
- **Testing**: Validation strategies and test requirements
|
||||
- **Implementation Order**: Step-by-step execution sequence
|
||||
|
||||
### Step 4: Implementation Task Creation
|
||||
|
||||
Cline creates a new task that references the plan document and includes trackable implementation steps. The task comes with specific commands to read each section of the plan, ensuring the implementing agent (whether that's you or Cline in Act Mode) can navigate the blueprint efficiently.
|
||||
|
||||
<Tip>
|
||||
Deep Planning works beautifully with [Focus Chain](/features/focus-chain). The implementation steps automatically become a todo list with real-time progress tracking, keeping complex projects organized and on track.
|
||||
</Tip>
|
||||
|
||||
## Using Deep Planning
|
||||
|
||||
Start a deep planning session by typing `/deep-planning` followed by your feature description:
|
||||
|
||||
```
|
||||
/deep-planning Add user authentication with JWT tokens and role-based access control
|
||||
```
|
||||
|
||||
Cline will begin his investigation immediately. You'll see him reading files and running commands to understand your codebase. Once he's gathered enough context, he'll engage you in discussion before creating the plan.
|
||||
|
||||
## Example Workflow
|
||||
|
||||
Here's how I use `/deep-planning` for a real feature:
|
||||
|
||||
<Steps>
|
||||
<Step title="Initiate Planning">
|
||||
I type `/deep-planning implement a caching layer for API responses`
|
||||
</Step>
|
||||
<Step title="Silent Investigation">
|
||||
Cline explores my codebase, examining:
|
||||
- Current API structure and endpoints
|
||||
- Existing data flow patterns
|
||||
- Database queries and performance bottlenecks
|
||||
- Configuration and environment setup
|
||||
</Step>
|
||||
<Step title="Targeted Discussion">
|
||||
Cline asks me:
|
||||
- "Should we use Redis or in-memory caching?"
|
||||
- "What's the acceptable cache staleness for user data?"
|
||||
- "Do you need cache invalidation webhooks?"
|
||||
</Step>
|
||||
<Step title="Plan Creation">
|
||||
Cline generates `implementation_plan.md` with:
|
||||
- Cache service class specifications
|
||||
- Redis connection configuration
|
||||
- Modified API endpoints with caching logic
|
||||
- Cache key generation strategies
|
||||
- TTL configurations for different data types
|
||||
</Step>
|
||||
<Step title="Task Generation">
|
||||
Cline creates a new task with:
|
||||
- Reference to the implementation plan
|
||||
- Commands to read specific sections
|
||||
- Trackable todo items for each implementation step
|
||||
- Request to switch to Act Mode for execution
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Integration with Plan/Act Mode
|
||||
|
||||
Deep Planning is designed to work seamlessly with [Plan/Act Mode](/features/plan-and-act):
|
||||
|
||||
- Use `/deep-planning` in Plan Mode for the investigation and planning phases
|
||||
- The generated task requests switching to Act Mode for implementation
|
||||
- Focus Chain automatically tracks progress through the implementation steps
|
||||
|
||||
This separation ensures planning stays focused on architecture while implementation stays focused on execution.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use Deep Planning
|
||||
|
||||
Use `/deep-planning` for:
|
||||
- Features touching multiple parts of your codebase
|
||||
- Architectural changes requiring careful coordination
|
||||
- Complex integrations with external services
|
||||
- Refactoring efforts that need systematic execution
|
||||
- Any feature where you'd normally spend time whiteboarding
|
||||
|
||||
### Making the Most of Investigation
|
||||
|
||||
Let Cline complete his investigation thoroughly. The quality of the plan directly correlates with how well he understands your codebase. If you have specific areas he should examine, mention them in your initial request.
|
||||
|
||||
### Reviewing the Plan
|
||||
|
||||
Always review `implementation_plan.md` before starting implementation. The plan is comprehensive but not immutable - you can edit it directly if needed. Think of it as a collaborative document between you and Cline.
|
||||
|
||||
### Tracking Progress
|
||||
|
||||
With Focus Chain enabled, your implementation progress displays in the task header. Each completed step gets checked off automatically as Cline works through the plan, giving you real-time visibility into complex implementations.
|
||||
|
||||
## Inspiration
|
||||
|
||||
I use `/deep-planning` whenever I'm about to build something that would normally require a design document. Recent examples from my workflow:
|
||||
|
||||
- **Migrating authentication systems**: Deep Planning mapped every endpoint, identified all authentication touchpoints, and created a migration plan that avoided breaking changes.
|
||||
|
||||
- **Adding real-time features**: The plan covered WebSocket integration, event handling, state synchronization, and fallback mechanisms for disconnections.
|
||||
|
||||
- **Database schema refactoring**: Cline identified all affected queries, created migration scripts, and planned the rollout to minimize downtime.
|
||||
|
||||
- **API versioning implementation**: The plan detailed route changes, backward compatibility layers, deprecation notices, and client migration paths.
|
||||
|
||||
The power of `/deep-planning` is that it forces thoughtful architecture before implementation. It's like having a senior developer review your approach before you write code, except that developer has perfect knowledge of your entire codebase.
|
||||
|
||||
<Note>
|
||||
Deep Planning requires models with strong reasoning capabilities. It works best with the latest generation of models, like GPT-5, Claude 4, Gemini 2.5, or Grok 4. Smaller models may struggle with the comprehensive analysis required.
|
||||
</Note>
|
||||
|
||||
For simpler tasks that don't require extensive planning, consider using [/newtask](/features/slash-commands/new-task) to create focused tasks with context, or jump straight into implementation if the path forward is clear.
|
||||
@@ -55,7 +55,8 @@ Think of context like a whiteboard you and Cline share:
|
||||
- Each model has a fixed size:
|
||||
- Claude 3.5 Sonnet: 200,000 tokens
|
||||
- DeepSeek: 64,000 tokens
|
||||
- When the whiteboard is full, you need to erase (clear context) to write more
|
||||
- When the whiteboard is full, Cline automatically summarizes the conversation to free up space
|
||||
- [Learn about Automatic Context Summarization](/features/automatic-context-summarization)
|
||||
- [How Cline manages context under the hood](https://cline.bot/blog/understanding-the-new-context-window-progress-bar-in-cline)
|
||||
|
||||
⚠️ **Important**: Having a large context window (like Claude's 200k tokens) doesn't mean you should fill it completely. Just like a cluttered whiteboard, too much information can make it harder to focus on what's important.
|
||||
@@ -85,7 +86,28 @@ Cline provides a visual way to monitor your context window usage through a progr
|
||||
- Before starting complex tasks
|
||||
- When Cline seems to lose context
|
||||
|
||||
💡 **Tip**: Consider starting a fresh session when usage reaches 70-80% to maintain optimal performance.
|
||||
💡 **Tip**: With [Automatic Context Summarization](/features/automatic-context-summarization), Cline can now handle long conversations automatically. When combined with [Focus Chain](/features/focus-chain), you can work on complex projects that span multiple context windows without losing progress.
|
||||
|
||||
## Automatic Context Management
|
||||
|
||||
Cline now includes intelligent features to manage context automatically:
|
||||
|
||||
### Automatic Context Summarization
|
||||
|
||||
When your conversation approaches the context window limit, Cline automatically:
|
||||
- Creates a comprehensive summary of the conversation
|
||||
- Preserves all essential technical details and decisions
|
||||
- Seamlessly continues work without interruption
|
||||
- Maintains full task continuity
|
||||
|
||||
This means you can work on larger projects without manually managing context. [Learn more about Automatic Context Summarization](/features/automatic-context-summarization).
|
||||
|
||||
### Focus Chain Integration
|
||||
|
||||
When combined with [Focus Chain](/features/focus-chain), Cline can:
|
||||
- Maintain todo lists across context resets
|
||||
- Track progress through multiple summarizations
|
||||
- Keep working on long-horizon tasks without losing direction
|
||||
|
||||
## Working with Context Files
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ There are multiple places online to find MCP servers:
|
||||
- [mcpservers.org](https://mcpservers.org/)
|
||||
- [mcp.so](https://mcp.so/)
|
||||
- [glama.ai/mcp/servers](https://glama.ai/mcp/servers)
|
||||
- [mcp.composio.dev](https://mcp.composio.dev/)
|
||||
|
||||
These directories allow users to sort the servers by various criteria such as downloads, date, stars, and use case. Each entry provides information such as features, tools, and configuration instructions.
|
||||
|
||||
|
||||
@@ -4,17 +4,17 @@ title: "Telemetry"
|
||||
|
||||
### Overview
|
||||
|
||||
To help make Cline better for everyone, we collect anonymous usage data that helps us understand how developers are using our open-source AI coding agent. This feedback loop is crucial for improving Cline's capabilities and user experience.
|
||||
To help make Cline better for everyone, we collect usage data that helps us understand how developers are using our open-source AI coding agent. This feedback loop is crucial for improving Cline's capabilities and user experience.
|
||||
|
||||
We use PostHog, an open-source analytics platform, for data collection and analysis. Our telemetry implementation is fully transparent - you can review the [source code](https://github.com/cline/cline/blob/main/src/services/posthog/telemetry/TelemetryService.ts) to see exactly what we track.
|
||||
|
||||
### Tracking Policy
|
||||
|
||||
Privacy is our priority. All collected data is anonymized before being sent to PostHog, with no personally identifiable information (PII) included. Your code, prompts, and conversation content always remain private and are never collected.
|
||||
Privacy is our priority. By default, all collected data is anonymized. If you log in with a Cline account, your telemetry data will be associated with your account to help us improve the product and provide better support when you encounter issues. Your code, prompts, and conversation content always remain private and are never collected.
|
||||
|
||||
### What We Track
|
||||
|
||||
We collect basic anonymous usage data including:
|
||||
We collect basic usage data including:
|
||||
|
||||
**Task Interactions:** When tasks start and finish, conversation flow (without content)\
|
||||
**Mode and Tool Usage:** Switches between plan/act modes, which tools are being used\
|
||||
@@ -28,7 +28,7 @@ For complete transparency, you can inspect our [telemetry implementation](https:
|
||||
|
||||
Telemetry in Cline is entirely optional:
|
||||
|
||||
- When you update or install our VS Code extension, you'll see a message about our anonymous telemetry
|
||||
- When you update or install our VS Code extension, you'll see a message about our telemetry
|
||||
- You can change your preference anytime in settings
|
||||
|
||||
Cline also respects VS Code's global telemetry settings. If you've disabled telemetry at the VS Code level, Cline's telemetry will automatically be disabled as well.
|
||||
|
||||
@@ -16,6 +16,7 @@ description: "Learn how to configure and use Anthropic Claude models with Cline.
|
||||
|
||||
Cline supports the following Anthropic Claude models:
|
||||
|
||||
- `claude-opus-4-1-20250805`
|
||||
- `claude-opus-4-20250514`
|
||||
- `claude-opus-4-20250514:thinking` (Extended Thinking variant)
|
||||
- `claude-sonnet-4-20250514` (Recommended)
|
||||
|
||||
+7
-6
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "AWS Bedrock"
|
||||
description: "Learn how to set up AWS Bedrock with Cline using credentials authentication. This guide covers AWS environment setup, regional access verification, and secure integration with the Cline VS Code extension."
|
||||
title: "API Key (Simple Setup)"
|
||||
sidebarTitle: "API Key"
|
||||
description: "Set up AWS Bedrock with Cline using Bedrock API Keys. Simplest setup for individual developers to access frontier models."
|
||||
---
|
||||
|
||||
### Overview
|
||||
@@ -121,14 +122,14 @@ You can create a custom IAM policy with these permissions and attach it to your
|
||||
|
||||
### Conclusion
|
||||
|
||||
By following these steps, your enterprise team can securely integrate AWS Bedrock with the Cline VS Code extension to accelerate development:
|
||||
By following these steps, you can quickly integrate AWS Bedrock with the Cline VS Code extension to accelerate development:
|
||||
|
||||
1. **Prepare Your AWS Environment:** Create or use a secure IAM role/user, attach the `AmazonBedrockLimitedAccess` policy, and ensure necessary permissions.
|
||||
1. **Prepare Your AWS Environment:** Create a Bedrock API Key with the necessary permissions.
|
||||
2. **Verify Region and Model Access:** Confirm that your selected region supports your required models.
|
||||
3. **Configure Cline in VS Code:** Install and set up Cline with your AWS credentials and choose an appropriate model.
|
||||
3. **Configure Cline in VS Code:** Install and set up Cline with your AWS API Key and choose an appropriate model.
|
||||
4. **Implement Security and Monitoring:** Use best practices for IAM, network security, monitoring, and cost management.
|
||||
|
||||
For further details, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) and coordinate with your internal cloud team. Happy coding!
|
||||
For further details, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html). Happy coding!
|
||||
|
||||
---
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "AWS Bedrock w/ Profile Authentication"
|
||||
description: "Learn how to configure AWS Bedrock to use AWS Profiles for authentication with Cline, focusing on SSO/Federated roles for secure access."
|
||||
title: "CLI Profile (SSO)"
|
||||
sidebarTitle: "CLI Profile (SSO)"
|
||||
description: "Configure AWS Bedrock to use AWS CLI profiles for authentication with Cline. Best for SSO/federated roles and secure enterprise access."
|
||||
---
|
||||
|
||||
### Overview
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "AWS Bedrock"
|
||||
description: "Learn how to set up AWS Bedrock with Cline using credentials authentication. This guide covers AWS environment setup, regional access verification, and secure integration with the Cline VS Code extension."
|
||||
title: "IAM Credentials"
|
||||
sidebarTitle: "IAM Credentials"
|
||||
description: "Set up AWS Bedrock with Cline using IAM Access Key and Secret Key credentials. Best for enterprise environments with established IAM policies."
|
||||
---
|
||||
|
||||
### Overview
|
||||
@@ -52,6 +52,7 @@ If you're not sure where Claude Code is installed:
|
||||
The Claude Code provider supports these models:
|
||||
|
||||
- `claude-sonnet-4-20250514` (Recommended)
|
||||
- `claude-opus-4-1-20250805`
|
||||
- `claude-opus-4-20250514`
|
||||
- `claude-3-7-sonnet-20250219`
|
||||
- `claude-3-5-sonnet-20241022`
|
||||
|
||||
@@ -43,7 +43,6 @@ While the "OpenAI Compatible" provider type allows connecting to various endpoin
|
||||
- `o1`
|
||||
- `o1-preview`
|
||||
- `o1-mini`
|
||||
- `gpt-4.5-preview`
|
||||
- `gpt-4o`
|
||||
- `gpt-4o-mini`
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ Cline is compatible with a variety of OpenAI models, including but not limited t
|
||||
- `o1`
|
||||
- `o1-preview`
|
||||
- `o1-mini`
|
||||
- `gpt-4.5-preview`
|
||||
- `gpt-4o`
|
||||
- `gpt-4o-mini`
|
||||
- 'gpt-4.1'
|
||||
|
||||
@@ -10,7 +10,7 @@ Cline supports accessing models through the [Requesty](https://www.requesty.ai/)
|
||||
### Getting an API Key
|
||||
|
||||
1. **Sign Up/Sign In:** Go to the [Requesty website](https://www.requesty.ai/) and create an account or sign in.
|
||||
2. **Get API Key:** You can get an API key from the [API Management](https://app.requesty.ai/manage-api) section of your Requesty dashboard.
|
||||
2. **Get API Key:** You can get an API key from the [API Management](https://app.requesty.ai/api-keys) section of your Requesty dashboard.
|
||||
|
||||
### Supported Models
|
||||
|
||||
@@ -26,7 +26,7 @@ Requesty provides access to a wide range of models. Cline will automatically fet
|
||||
### Tips and Notes
|
||||
|
||||
- **Optimizations**: Requesty offers a range of in-flight cost optimizations to lower your costs.
|
||||
- **Unified and simplified billing**: Unrestricted access to all providers and models, automatic balance top ups and more via a single [API key](https://app.requesty.ai/manage-api).
|
||||
- **Unified and simplified billing**: Unrestricted access to all providers and models, automatic balance top ups and more via a single [API key](https://app.requesty.ai/api-keys).
|
||||
- **Cost tracking**: Track cost per model, coding language, changed file, and more via the [Cost dashboard](https://app.requesty.ai/cost-management) or the [Requesty VS Code extension](https://marketplace.visualstudio.com/items?itemName=Requesty.requesty).
|
||||
- **Stats and logs**: See your [coding stats dashboard](https://app.requesty.ai/usage-stats) or go through your [LLM interaction logs](https://app.requesty.ai/logs).
|
||||
- **Fallback policies**: Keep your LLM working for you with fallback policies when providers are down.
|
||||
|
||||
@@ -244,13 +244,60 @@ Recent macOS versions have stricter terminal permissions:
|
||||
|
||||
### Windows Issues
|
||||
|
||||
#### PowerShell Execution Policy
|
||||
If you're using Windows and still experiencing issues with shell integration after trying the previous steps, it's recommended you use Git Bash (or PowerShell).
|
||||
|
||||
If commands fail silently:
|
||||
### Git Bash
|
||||
|
||||
Git Bash is a terminal emulator that provides a Unix-like command line experience on Windows. To use Git Bash, you need to:
|
||||
|
||||
1. Download and run the Git for Windows installer from [https://git-scm.com/downloads/win](https://git-scm.com/downloads/win)
|
||||
2. Quit and re-open VSCode
|
||||
3. Press `Ctrl + Shift + P` to open the Command Palette
|
||||
4. Type "Terminal: Select Default Profile" and choose it
|
||||
5. Select "Git Bash"
|
||||
|
||||
### PowerShell
|
||||
|
||||
If you'd still like to use PowerShell, make sure you're using an updated version (at least v7+).
|
||||
- Check your current PowerShell version by running: `$PSVersionTable.PSVersion`
|
||||
- If your version is below 7, [update PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/whats-new/migrating-from-windows-powershell-51-to-powershell-7?view=powershell-7.4#installing-powershell-7).
|
||||
|
||||
You may also need to adjust your PowerShell execution policy. By default, PowerShell restricts script execution for security reasons.
|
||||
|
||||
#### Understanding PowerShell Execution Policies
|
||||
|
||||
PowerShell uses execution policies to determine which scripts can run on your system. Here are the most common policies:
|
||||
|
||||
- `Restricted`: No PowerShell scripts can run. This is the default setting.
|
||||
- `AllSigned`: All scripts, including local ones, must be signed by a trusted publisher.
|
||||
- `RemoteSigned`: Scripts created locally can run, but scripts downloaded from the internet must be signed.
|
||||
- `Unrestricted`: No restrictions. Any script can run, though you will be warned before running internet-downloaded scripts.
|
||||
|
||||
For development work in VSCode, the `RemoteSigned` policy is generally recommended. It allows locally created scripts to run without restrictions while maintaining security for downloaded scripts. To learn more about PowerShell execution policies and understand the security implications of changing them, visit Microsoft's documentation: [About Execution Policies](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies).
|
||||
|
||||
#### Steps to Change the Execution Policy
|
||||
|
||||
1. Open PowerShell as an Administrator: Press `Win + X` and select "Windows PowerShell (Administrator)" or "Windows Terminal (Administrator)".
|
||||
|
||||
2. Check Current Execution Policy by running this command:
|
||||
```powershell
|
||||
Get-ExecutionPolicy
|
||||
```
|
||||
- If the output is already `RemoteSigned`, `Unrestricted`, or `Bypass`, you likely don't need to change your execution policy. These policies should allow shell integration to work.
|
||||
- If the output is `Restricted` or `AllSigned`, you may need to change your policy to enable shell integration.
|
||||
|
||||
3. Change the Execution Policy by running the following command:
|
||||
```powershell
|
||||
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
- This sets the policy to `RemoteSigned` for the current user only, which is safer than changing it system-wide.
|
||||
|
||||
4. Confirm the Change by typing `Y` and pressing Enter when prompted.
|
||||
|
||||
5. Verify the Policy Change by running `Get-ExecutionPolicy` again to confirm the new setting.
|
||||
|
||||
6. Restart VSCode and try the shell integration again.
|
||||
|
||||
```powershell
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
|
||||
#### WSL Integration
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
const esbuild = require("esbuild")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import * as esbuild from "esbuild"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const production = process.argv.includes("--production")
|
||||
const watch = process.argv.includes("--watch")
|
||||
@@ -16,7 +20,6 @@ const aliasResolverPlugin = {
|
||||
setup(build) {
|
||||
const aliases = {
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
"@api": path.resolve(__dirname, "src/api"),
|
||||
"@core": path.resolve(__dirname, "src/core"),
|
||||
"@integrations": path.resolve(__dirname, "src/integrations"),
|
||||
"@services": path.resolve(__dirname, "src/services"),
|
||||
@@ -127,10 +130,8 @@ const baseConfig = {
|
||||
sourcemap: !production,
|
||||
logLevel: "silent",
|
||||
define: production
|
||||
? {
|
||||
"process.env.IS_DEV": JSON.stringify(!production),
|
||||
}
|
||||
: undefined,
|
||||
? { "import.meta.url": "_importMetaUrl", "process.env.IS_DEV": JSON.stringify(!production) }
|
||||
: { "import.meta.url": "_importMetaUrl" },
|
||||
tsconfig: path.resolve(__dirname, "tsconfig.json"),
|
||||
plugins: [
|
||||
copyWasmFiles,
|
||||
@@ -141,6 +142,9 @@ const baseConfig = {
|
||||
format: "cjs",
|
||||
sourcesContent: false,
|
||||
platform: "node",
|
||||
banner: {
|
||||
js: "const _importMetaUrl=require('url').pathToFileURL(__filename)",
|
||||
},
|
||||
}
|
||||
|
||||
// Extension-specific configuration
|
||||
@@ -165,7 +169,7 @@ const standaloneConfig = {
|
||||
const e2eBuildConfig = {
|
||||
...baseConfig,
|
||||
entryPoints: ["src/test/e2e/utils/build.ts"],
|
||||
outfile: `${destDir}/e2e-build.js`,
|
||||
outfile: `${destDir}/e2e-build.mjs`,
|
||||
external: ["@vscode/test-electron", "execa"],
|
||||
sourcemap: false,
|
||||
plugins: [aliasResolverPlugin, esbuildProblemMatcherPlugin],
|
||||
@@ -1,123 +0,0 @@
|
||||
const { RuleTester: DirectApiRuleTester } = require("eslint")
|
||||
const noDirectVscodeApiRule = require("../no-direct-vscode-api")
|
||||
|
||||
const directApiRuleTester = new DirectApiRuleTester({
|
||||
parser: require.resolve("@typescript-eslint/parser"),
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
directApiRuleTester.run("no-direct-vscode-api", noDirectVscodeApiRule, {
|
||||
valid: [
|
||||
// Should allow vscode.postMessage in grpc-client-base.ts
|
||||
{
|
||||
code: `vscode.postMessage({ type: "grpc_request", data: {} })`,
|
||||
filename: "grpc-client-base.ts",
|
||||
},
|
||||
{
|
||||
code: `vscode.postMessage({ type: "grpc_request_cancel" })`,
|
||||
filename: "/path/to/grpc-client-base.ts",
|
||||
},
|
||||
// Should allow in exception directories
|
||||
{
|
||||
code: `vscode.workspace.workspaceFolders`,
|
||||
filename: "/src/hosts/vscode/host-bridge.ts",
|
||||
},
|
||||
{
|
||||
code: `vscode.workspace.fs.stat(uri)`,
|
||||
filename: "/standalone/runtime-files/helpers.ts",
|
||||
},
|
||||
// Should allow other vscode API calls
|
||||
{
|
||||
code: `vscode.window.showInformationMessage("Hello")`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
// Should allow postMessage calls on other objects
|
||||
{
|
||||
code: `window.postMessage({ type: "test" }, "*")`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
// Should allow variables named vscode but not calling postMessage
|
||||
{
|
||||
code: `const vscode = { other: "method" }; vscode.other()`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
// Should disallow vscode.postMessage in regular files
|
||||
{
|
||||
code: `vscode.postMessage({ type: "test", data: {} })`,
|
||||
filename: "test.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow vscode.postMessage in components
|
||||
{
|
||||
code: `vscode.postMessage({ type: "apiConfiguration", apiConfiguration })`,
|
||||
filename: "ApiOptions.tsx",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow vscode.postMessage in test files
|
||||
{
|
||||
code: `vscode.postMessage({ type: "newTask", text: message.text })`,
|
||||
filename: "test.test.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow property access for disallowed APIs
|
||||
{
|
||||
code: `const folders = vscode.workspace.workspaceFolders;`,
|
||||
filename: "workspace.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow method calls for disallowed APIs
|
||||
{
|
||||
code: `const relativePath = vscode.workspace.asRelativePath(filePath);`,
|
||||
filename: "path-utils.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow nested property access
|
||||
{
|
||||
code: `const stats = await vscode.workspace.fs.stat(uri);`,
|
||||
filename: "file-utils.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow getting a workspace folder
|
||||
{
|
||||
code: `const folder = vscode.workspace.getWorkspaceFolder(uri);`,
|
||||
filename: "path-helper.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -1,16 +0,0 @@
|
||||
// eslint-rules/index.js
|
||||
const noDirectVscodeApi = require("./no-direct-vscode-api")
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
"no-direct-vscode-api": noDirectVscodeApi,
|
||||
},
|
||||
configs: {
|
||||
recommended: {
|
||||
plugins: ["local"],
|
||||
rules: {
|
||||
"local/no-direct-vscode-api": "warn",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
const { ESLintUtils } = require("@typescript-eslint/utils")
|
||||
const path = require("path")
|
||||
|
||||
const createRule = ESLintUtils.RuleCreator((name) => `https://cline.bot/eslint-rules/${name}`)
|
||||
|
||||
// Configuration of disallowed VSCode APIs and their recommended alternatives
|
||||
const disallowedApis = {
|
||||
"vscode.postMessage": {
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
"vscode.workspace.fs.stat": {
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
"vscode.workspace.fs.writeFile": {
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
"vscode.workspace.workspaceFolders": {
|
||||
messageId: "useHostBridgeWorkspace",
|
||||
},
|
||||
"vscode.workspace.asRelativePath": {
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
"vscode.workspace.getWorkspaceFolder": {
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
"vscode.window.showTextDocument": {
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
"vscode.workspace.applyEdit": {
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
// "vscode.env.openExternal": {
|
||||
// messageId: "useUtils",
|
||||
// },
|
||||
// "vscode.window.showWarningMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
"vscode.window.showOpenDialog": {
|
||||
messageId: "useHostBridgeShowMessage",
|
||||
},
|
||||
// There are too many warnings for these calls, uncomment the following
|
||||
// when the migration is finished.
|
||||
// "vscode.window.showErrorMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
// "vscode.window.showInformationMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
}
|
||||
|
||||
module.exports = createRule({
|
||||
name: "no-direct-vscode-api",
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Disallow direct VSCode API usage in favor of Cline's abstraction layers, except in src/hosts/vscode and standalone/runtime-files directories",
|
||||
recommended: "error",
|
||||
},
|
||||
messages: {
|
||||
useGrpcClient:
|
||||
"Use gRPC service clients instead of vscode.postMessage().\n" +
|
||||
"Example: AccountServiceClient.methodName(RequestType.create({...})) instead of vscode.postMessage({type: '...'}).\n" +
|
||||
"Found: {{code}}",
|
||||
useFsUtils:
|
||||
"Use utilities in @/utils/fs instead of vscode.workspace.fs\n" +
|
||||
"Example: import { isDirectory } from '@/utils/fs' or use the file system methods from the host bridge provider.\n" +
|
||||
"Found: {{code}}",
|
||||
usePathUtils:
|
||||
"Use path utilities from @/utils/path instead of VSCode workspace path methods.\n" +
|
||||
"This provides consistent path handling across different environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridgeWorkspace:
|
||||
"Use HostProvider.workspace.getWorkspacePaths({}) instead of vscode.workspace.workspaceFolders.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridgeShowMessage:
|
||||
"Use HostProvider.window.showMessage instead of the vscode.window.showMessage.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridge:
|
||||
"Use the host bridge instead of calling vscode APIs directly.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useUtils:
|
||||
"Use utilities in @/utils instead of calling vscode APIs directly.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
|
||||
create(context) {
|
||||
// Pattern for checking memberExpressions like vscode.workspace.fs.stat
|
||||
function checkMemberExpression(node) {
|
||||
if (isExcluded(context.filename)) {
|
||||
// Skip if this file is being excluded.
|
||||
return
|
||||
}
|
||||
|
||||
// For handling nested properties like vscode.workspace.fs.stat
|
||||
function getFullPropertyPath(node) {
|
||||
if (node.type !== "MemberExpression") {
|
||||
return node.name || ""
|
||||
}
|
||||
|
||||
const objectPart = getFullPropertyPath(node.object)
|
||||
const propertyPart = node.property.name || ""
|
||||
|
||||
return objectPart ? `${objectPart}.${propertyPart}` : propertyPart
|
||||
}
|
||||
|
||||
// Check if the expression matches one of our disallowed patterns
|
||||
if (node.object && node.object.type === "Identifier" && node.object.name === "vscode") {
|
||||
const fullPath = `vscode.${node.property.name}`
|
||||
checkDisallowedApi(fullPath, node)
|
||||
}
|
||||
// Handle nested expressions like vscode.workspace.fs.stat
|
||||
else if (node.object && node.object.type === "MemberExpression") {
|
||||
const fullPath = getFullPropertyPath(node)
|
||||
|
||||
// Only proceed if it starts with vscode
|
||||
if (fullPath.startsWith("vscode.")) {
|
||||
checkDisallowedApi(fullPath, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if an expression matches a disallowed API and report if it does
|
||||
function checkDisallowedApi(expressionPath, node) {
|
||||
// Check exact matches
|
||||
if (disallowedApis[expressionPath]) {
|
||||
reportViolation(expressionPath, node)
|
||||
return
|
||||
}
|
||||
|
||||
// Check prefix matches (for nested properties)
|
||||
for (const disallowedApi in disallowedApis) {
|
||||
// For direct property access like vscode.workspace.workspaceFolders
|
||||
if (expressionPath === disallowedApi) {
|
||||
reportViolation(disallowedApi, node)
|
||||
return
|
||||
}
|
||||
|
||||
// For method calls like vscode.workspace.asRelativePath(...)
|
||||
if (expressionPath.startsWith(`${disallowedApi}.`) || expressionPath.startsWith(`${disallowedApi}(`)) {
|
||||
reportViolation(disallowedApi, node)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report a violation with the appropriate message
|
||||
function reportViolation(disallowedApi, node) {
|
||||
const sourceCode = context.sourceCode
|
||||
const config = disallowedApis[disallowedApi]
|
||||
|
||||
// For method calls, get the whole call expression
|
||||
let reportNode = node
|
||||
let parentNode = sourceCode.getAncestors(node).pop()
|
||||
if (parentNode && parentNode.type === "CallExpression" && parentNode.callee === node) {
|
||||
reportNode = parentNode
|
||||
}
|
||||
|
||||
const callText = sourceCode.getText(reportNode).trim()
|
||||
|
||||
context.report({
|
||||
node: reportNode,
|
||||
messageId: config.messageId,
|
||||
data: {
|
||||
code: callText,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function isExcluded(filename) {
|
||||
// Check if current file is in an exception directory or is grpc-client-base.ts
|
||||
if (path.basename(filename) === "grpc-client-base.ts") {
|
||||
return true
|
||||
}
|
||||
// Skip checking files in src/hosts/vscode or standalone/runtime-files
|
||||
if (filename.includes("/src/hosts/vscode/")) {
|
||||
return true
|
||||
}
|
||||
if (filename.includes("/standalone/runtime-files/")) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// Detect basic member expressions (e.g., vscode.postMessage)
|
||||
MemberExpression(node) {
|
||||
checkMemberExpression(node)
|
||||
},
|
||||
|
||||
// Detect property access through destructuring
|
||||
VariableDeclarator(node) {
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isExcluded(context.filename)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Destructuring pattern checks removed as developers don't use the API this way
|
||||
// They always use direct imports: import * as vscode from "vscode" and direct access: vscode.thing.foo
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
Generated
-2479
File diff suppressed because it is too large
Load Diff
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "eslint-plugin-eslint-rules",
|
||||
"version": "1.0.0",
|
||||
"description": "Custom ESLint rules for Cline",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha --no-config --require ts-node/register __tests__/**/*.test.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"eslint",
|
||||
"eslintplugin"
|
||||
],
|
||||
"author": "Cline Bot Inc.",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/utils": "^8.33.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/eslint": "^8.0.0",
|
||||
"@types/mocha": "^10.0.7",
|
||||
"@types/node": "^20.0.0",
|
||||
"@typescript-eslint/parser": "^7.14.1",
|
||||
"eslint": "^8.57.0",
|
||||
"mocha": "^10.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "dist",
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js", "**/*.tsx", "__tests__/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -10,6 +10,7 @@ interface RunDiffEvalOptions {
|
||||
parsingFunction: string
|
||||
diffEditFunction: string
|
||||
thinkingBudget: number
|
||||
provider: string
|
||||
parallel: boolean
|
||||
verbose: boolean
|
||||
testPath: string
|
||||
@@ -39,6 +40,8 @@ export async function runDiffEvalHandler(options: RunDiffEvalOptions) {
|
||||
options.parsingFunction,
|
||||
"--diff-edit-function",
|
||||
options.diffEditFunction,
|
||||
"--provider",
|
||||
options.provider,
|
||||
]
|
||||
|
||||
// Conditionally add the optional arguments
|
||||
|
||||
@@ -92,6 +92,7 @@ program
|
||||
.option("--parsing-function <name>", "The parsing function to use", "parseAssistantMessageV2")
|
||||
.option("--diff-edit-function <name>", "The diff editing function to use", "constructNewFileContentV2")
|
||||
.option("--thinking-budget <tokens>", "Set the thinking tokens budget", "0")
|
||||
.option("--provider <provider>", "API provider to use (openrouter, openai)", "openrouter")
|
||||
.option("--parallel", "Run tests in parallel", false)
|
||||
.option("--replay", "Run evaluation from a pre-recorded LLM output, skipping the API call", false)
|
||||
.option("--replay-run-id <run_id>", "The ID of the run to replay from the database")
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { OpenRouterHandler } from "../../src/api/providers/openrouter"
|
||||
import { ApiHandlerOptions } from "../../src/shared/api"
|
||||
import { OpenAiNativeHandler } from "../../src/api/providers/openai-native"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
import {
|
||||
parseAssistantMessageV1,
|
||||
parseAssistantMessageV2,
|
||||
parseAssistantMessageV3,
|
||||
AssistantMessageContent,
|
||||
} from "./parsing/parse-assistant-message-06-06-25" // "../../src/core/assistant-message"
|
||||
import { constructNewFileContent as constructNewFileContent_06_06_25 } from "./diff-apply/diff-06-06-25"
|
||||
@@ -17,9 +15,7 @@ type ParseAssistantMessageFn = (message: string) => AssistantMessageContent[]
|
||||
type ConstructNewFileContentFn = (diff: string, original: string, strict: boolean) => Promise<string | any>
|
||||
|
||||
const parsingFunctions: Record<string, ParseAssistantMessageFn> = {
|
||||
parseAssistantMessageV1: parseAssistantMessageV1,
|
||||
parseAssistantMessageV2: parseAssistantMessageV2,
|
||||
parseAssistantMessageV3: parseAssistantMessageV3,
|
||||
}
|
||||
|
||||
const diffEditingFunctions: Record<string, ConstructNewFileContentFn> = {
|
||||
@@ -54,7 +50,7 @@ interface StreamResult {
|
||||
* Process the stream and return full response with timing data
|
||||
*/
|
||||
async function processStream(
|
||||
handler: OpenRouterHandler,
|
||||
handler: OpenRouterHandler | OpenAiNativeHandler,
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
): Promise<StreamResult> {
|
||||
@@ -190,19 +186,7 @@ export async function runSingleEvaluation(input: TestInput): Promise<TestResult>
|
||||
}
|
||||
}
|
||||
|
||||
const options: ApiHandlerOptions = {
|
||||
openRouterApiKey: apiKey,
|
||||
openRouterModelId: modelId,
|
||||
thinkingBudgetTokens: thinkingBudgetTokens,
|
||||
openRouterModelInfo: {
|
||||
maxTokens: 10_000,
|
||||
contextWindow: 1_000_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true, // may need to turn this on
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
},
|
||||
}
|
||||
const provider = input.provider || "openrouter"
|
||||
|
||||
// Get the output of streaming output of this llm call
|
||||
let streamResult: StreamResult
|
||||
@@ -214,10 +198,34 @@ export async function runSingleEvaluation(input: TestInput): Promise<TestResult>
|
||||
usage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0, totalCost: 0 },
|
||||
}
|
||||
} else {
|
||||
// Live mode: existing API call logic
|
||||
// Live mode: provider-specific API call logic
|
||||
try {
|
||||
const openRouterHandler = new OpenRouterHandler(options)
|
||||
streamResult = await processStream(openRouterHandler, systemPrompt, messages)
|
||||
let handler: OpenRouterHandler | OpenAiNativeHandler
|
||||
|
||||
if (provider === "openai") {
|
||||
const openAiOptions = {
|
||||
openAiNativeApiKey: apiKey,
|
||||
apiModelId: modelId,
|
||||
}
|
||||
handler = new OpenAiNativeHandler(openAiOptions)
|
||||
} else {
|
||||
const openRouterOptions = {
|
||||
openRouterApiKey: apiKey,
|
||||
openRouterModelId: modelId,
|
||||
thinkingBudgetTokens: thinkingBudgetTokens,
|
||||
openRouterModelInfo: {
|
||||
maxTokens: 10_000,
|
||||
contextWindow: 1_000_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
},
|
||||
}
|
||||
handler = new OpenRouterHandler(openRouterOptions)
|
||||
}
|
||||
|
||||
streamResult = await processStream(handler, systemPrompt, messages)
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -49,16 +49,25 @@ type TestResultSet = { [test_id: string]: (TestResult & { test_id?: string })[]
|
||||
|
||||
class NodeTestRunner {
|
||||
private apiKey: string | undefined
|
||||
private provider: string
|
||||
private currentRunId: string | null = null
|
||||
private systemPromptHash: string | null = null
|
||||
private processingFunctionsHash: string | null = null
|
||||
private caseIdMap: Map<string, string> = new Map() // test_id -> case_id mapping
|
||||
|
||||
constructor(isReplay: boolean) {
|
||||
constructor(isReplay: boolean, provider: string = "openrouter") {
|
||||
this.provider = provider
|
||||
if (!isReplay) {
|
||||
this.apiKey = process.env.OPENROUTER_API_KEY
|
||||
if (!this.apiKey) {
|
||||
throw new Error("OPENROUTER_API_KEY environment variable not set for a non-replay run.")
|
||||
if (provider === "openai") {
|
||||
this.apiKey = process.env.OPENAI_API_KEY
|
||||
if (!this.apiKey) {
|
||||
throw new Error("OPENAI_API_KEY environment variable not set for a non-replay run with OpenAI provider.")
|
||||
}
|
||||
} else {
|
||||
this.apiKey = process.env.OPENROUTER_API_KEY
|
||||
if (!this.apiKey) {
|
||||
throw new Error("OPENROUTER_API_KEY environment variable not set for a non-replay run with OpenRouter provider.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -635,6 +644,7 @@ class NodeTestRunner {
|
||||
thinkingBudgetTokens: testConfig.thinking_tokens_budget,
|
||||
originalDiffEditToolCallMessage: testConfig.replay ? testCase.original_diff_edit_tool_call_message : undefined,
|
||||
diffApplyFile: testConfig.diff_apply_file,
|
||||
provider: this.provider,
|
||||
isVerbose: isVerbose,
|
||||
}
|
||||
|
||||
@@ -927,6 +937,7 @@ async function main() {
|
||||
.option("--parsing-function <name>", "The parsing function to use", "parseAssistantMessageV2")
|
||||
.option("--diff-edit-function <name>", "The diff editing function to use", "diff-06-26-25")
|
||||
.option("--thinking-budget <tokens>", "Set the thinking tokens budget", "0")
|
||||
.option("--provider <provider>", "API provider to use (openrouter, openai)", "openrouter")
|
||||
.option("--parallel", "Run tests in parallel", false)
|
||||
.option("--replay", "Run evaluation from a pre-recorded LLM output, skipping the API call", false)
|
||||
.option("--replay-run-id <run_id>", "The ID of the run to replay from the database")
|
||||
@@ -959,7 +970,7 @@ async function main() {
|
||||
? parseInt(options.maxAttemptsPerCase, 10)
|
||||
: validAttemptsPerCase * 10;
|
||||
|
||||
const runner = new NodeTestRunner(options.replay || !!options.replayRunId)
|
||||
const runner = new NodeTestRunner(options.replay || !!options.replayRunId, options.provider)
|
||||
|
||||
if (options.replayRunId) {
|
||||
if (!options.diffApplyFile) {
|
||||
@@ -979,7 +990,7 @@ async function main() {
|
||||
log(isVerbose, "Warning: Could not load OpenRouter model data. Context window filtering might be affected for OpenRouter models.");
|
||||
}
|
||||
|
||||
const runner = new NodeTestRunner(options.replay)
|
||||
const runner = new NodeTestRunner(options.replay, options.provider)
|
||||
let allLoadedTestCases = runner.loadTestCases(testPath, isVerbose) // Pass isVerbose
|
||||
|
||||
const allProcessedTestCasesGlobal: ProcessedTestCase[] = allLoadedTestCases.map((tc) => ({
|
||||
|
||||
@@ -331,6 +331,42 @@ def get_performance_grade(success_rate):
|
||||
else:
|
||||
return "C", "poor"
|
||||
|
||||
def get_error_description(error_enum, error_string=None):
|
||||
"""Map error enum values to user-friendly descriptions"""
|
||||
error_map = {
|
||||
1: "No tool calls - Model didn't use the replace_in_file tool",
|
||||
2: "Multiple tool calls - Model called multiple tools instead of one",
|
||||
3: "Wrong tool call - Model used wrong tool (not replace_in_file)",
|
||||
4: "Missing parameters - Tool call missing required path or diff",
|
||||
5: "Wrong file edited - Model edited different file than expected",
|
||||
6: "Wrong tool call - Model used wrong tool type",
|
||||
7: "Wrong file edited - Model targeted incorrect file path",
|
||||
8: "API/Stream error - Problem with model API connection",
|
||||
9: "Configuration error - Invalid evaluation parameters",
|
||||
10: "Function error - Invalid parsing/diff functions",
|
||||
11: "Other error - Unexpected failure"
|
||||
}
|
||||
|
||||
base_description = error_map.get(error_enum, f"Unknown error (code: {error_enum})")
|
||||
|
||||
if error_string:
|
||||
return f"{base_description}: {error_string}"
|
||||
return base_description
|
||||
|
||||
def get_error_guidance(error_enum):
|
||||
"""Provide specific guidance based on error type"""
|
||||
guidance_map = {
|
||||
1: "💡 The model provided a response but didn't use the replace_in_file tool. Check the raw output to see what the model actually said.",
|
||||
2: "💡 The model called multiple tools when it should only call replace_in_file once. Check the parsed tool call section.",
|
||||
3: "💡 The model used a different tool instead of replace_in_file. This might indicate confusion about the task.",
|
||||
4: "💡 The model called replace_in_file but didn't provide the required 'path' or 'diff' parameters.",
|
||||
5: "💡 The model tried to edit a different file than expected. Check the parsed tool call to see which file it targeted.",
|
||||
6: "💡 The model used the wrong tool type. Check the raw output to see what tool it attempted to use.",
|
||||
7: "💡 The model tried to edit a different file path than expected. This could indicate path confusion or hallucination.",
|
||||
}
|
||||
|
||||
return guidance_map.get(error_enum, "")
|
||||
|
||||
def render_hero_section(current_run, model_performance):
|
||||
"""Render the hero section with key metrics"""
|
||||
run_title = current_run['description'] if current_run['description'] else f"Run {current_run['run_id'][:8]}..."
|
||||
@@ -570,12 +606,16 @@ def render_result_detail(result):
|
||||
"""Render detailed view of a single result"""
|
||||
st.markdown("### 🔬 Result Deep Dive")
|
||||
|
||||
# Check if this is a valid result
|
||||
is_valid = (result['error_enum'] not in [1, 6, 7]) if not pd.isna(result['error_enum']) else True
|
||||
# Check if this is a valid result (only invalid if no tool calls or wrong file)
|
||||
is_valid = True
|
||||
if not pd.isna(result['error_enum']):
|
||||
# Only these specific errors make a result "invalid" for the benchmark:
|
||||
# 1 = no_tool_calls, 5 = wrong_file_edited, 7 = wrong_file_edited
|
||||
is_valid = result['error_enum'] not in [1, 5, 7]
|
||||
|
||||
# Show validity warning if needed
|
||||
if not is_valid:
|
||||
st.warning("⚠️ **This is an invalid result** - The model didn't properly call the diff edit tool or edited the wrong file. This result is excluded from success rate calculations.")
|
||||
st.warning("⚠️ **This is an invalid result** - The model didn't call the replace_in_file tool or edited the wrong file. This result is excluded from success rate calculations.")
|
||||
|
||||
# Result metadata
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
@@ -591,7 +631,10 @@ def render_result_detail(result):
|
||||
st.markdown(f"**Round Trip:** {result['time_round_trip_ms']:.0f}ms")
|
||||
|
||||
with col4:
|
||||
st.markdown(f"**Cost:** ${result['cost_usd']:.4f}")
|
||||
if pd.notna(result['cost_usd']) and result['cost_usd'] is not None:
|
||||
st.markdown(f"**Cost:** ${result['cost_usd']:.4f}")
|
||||
else:
|
||||
st.markdown(f"**Cost:** Free")
|
||||
|
||||
# Tabbed interface for different views
|
||||
tab1, tab2, tab3, tab4 = st.tabs(["📄 File & Edits", "🤖 Raw Output", "🔧 Parsed Tool Call", "📊 Metrics"])
|
||||
@@ -693,8 +736,46 @@ def render_file_and_edits_view(result):
|
||||
# Show error information
|
||||
st.error("❌ **Edit Failed**")
|
||||
|
||||
# Show detailed error reason
|
||||
if not pd.isna(result['error_enum']):
|
||||
st.markdown(f"**Error Code:** {result['error_enum']}")
|
||||
error_description = get_error_description(
|
||||
result['error_enum'],
|
||||
result.get('error_string')
|
||||
)
|
||||
st.markdown(f"**Reason:** {error_description}")
|
||||
|
||||
# Show specific guidance based on error type
|
||||
guidance = get_error_guidance(result['error_enum'])
|
||||
if guidance:
|
||||
st.info(guidance)
|
||||
|
||||
# For valid results that failed, check for diff application failures
|
||||
elif not result['succeeded']:
|
||||
# This is a valid result that failed - likely due to diff application issues
|
||||
raw_output = result.get('raw_model_output', '')
|
||||
|
||||
# Check if we have specific error information in the raw output
|
||||
if 'does not match anything in the file' in str(raw_output).lower():
|
||||
st.warning("⚠️ **Diff Application Failed**")
|
||||
st.info("💡 The SEARCH block in the diff didn't match any content in the original file. This usually means the model hallucinated code that doesn't exist.")
|
||||
elif 'malformatted' in str(raw_output).lower() or 'malformed' in str(raw_output).lower():
|
||||
st.warning("⚠️ **Diff Format Error**")
|
||||
st.info("💡 The diff format was incorrect. Check the raw tool call to see the formatting issues.")
|
||||
elif 'error:' in str(raw_output).lower():
|
||||
# Try to extract the specific error message
|
||||
lines = str(raw_output).split('\n')
|
||||
error_lines = [line for line in lines if 'error:' in line.lower()]
|
||||
if error_lines:
|
||||
error_msg = error_lines[0].strip()
|
||||
st.warning("⚠️ **Diff Application Failed**")
|
||||
st.info(f"💡 {error_msg}")
|
||||
else:
|
||||
st.warning("⚠️ **Diff Application Failed**")
|
||||
st.info("💡 The diff couldn't be applied to the original file. Check the raw output and parsed tool call for more details.")
|
||||
else:
|
||||
# Generic diff application failure
|
||||
st.warning("⚠️ **Diff Application Failed**")
|
||||
st.info("💡 The model made a valid tool call but the diff couldn't be applied to the original file. This usually indicates a mismatch between the expected and actual file content.")
|
||||
else:
|
||||
# Show successful edit information
|
||||
st.success("✅ **Edit Successful**")
|
||||
@@ -725,8 +806,25 @@ def render_file_and_edits_view(result):
|
||||
if len(edited_lines) > 50:
|
||||
st.text(f"... ({len(edited_lines) - 50} more lines)")
|
||||
|
||||
# Show parsed tool call if available
|
||||
# Show raw and parsed tool calls if available
|
||||
if not pd.isna(result['parsed_tool_call_json']):
|
||||
with st.expander("View Raw Tool Call"):
|
||||
# Extract the raw tool call text from the model output
|
||||
raw_output = result['raw_model_output'] if not pd.isna(result['raw_model_output']) else ""
|
||||
|
||||
# Try to extract just the tool call portion
|
||||
if raw_output and '<replace_in_file>' in raw_output:
|
||||
# Find the tool call block
|
||||
start_idx = raw_output.find('<replace_in_file>')
|
||||
end_idx = raw_output.find('</replace_in_file>') + len('</replace_in_file>')
|
||||
if start_idx != -1 and end_idx != -1:
|
||||
raw_tool_call = raw_output[start_idx:end_idx]
|
||||
st.code(raw_tool_call, language='xml')
|
||||
else:
|
||||
st.text("Tool call not found in raw output")
|
||||
else:
|
||||
st.text("No raw tool call available")
|
||||
|
||||
with st.expander("View Parsed Tool Call"):
|
||||
try:
|
||||
parsed_call = json.loads(result['parsed_tool_call_json'])
|
||||
@@ -795,8 +893,10 @@ def render_metrics_view(result):
|
||||
if not pd.isna(result['completion_tokens']):
|
||||
st.metric("Completion Tokens", int(result['completion_tokens']))
|
||||
|
||||
if not pd.isna(result['cost_usd']):
|
||||
if pd.notna(result['cost_usd']) and result['cost_usd'] is not None:
|
||||
st.metric("Cost", f"${result['cost_usd']:.4f}")
|
||||
else:
|
||||
st.metric("Cost", "Free")
|
||||
|
||||
if not pd.isna(result['tokens_in_context']):
|
||||
st.metric("Context Tokens", int(result['tokens_in_context']))
|
||||
|
||||
@@ -70,246 +70,7 @@ export interface ToolUse {
|
||||
partial: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @description **Version 1**
|
||||
* Parses an assistant message string potentially containing mixed text and tool usage blocks
|
||||
* marked with XML-like tags into an array of structured content objects.
|
||||
*
|
||||
* This version iterates through the message character by character, building an accumulator string.
|
||||
* It maintains state to track whether it's currently parsing text, a tool use block, or a specific tool parameter.
|
||||
* It detects the start and end of tool uses and parameters by checking if the accumulator ends with
|
||||
* the corresponding opening or closing tags.
|
||||
* Special handling is included for `write_to_file` and `new_rule` tool uses to correctly parse
|
||||
* the `content` parameter, which might contain the closing tag itself, by looking for the *last*
|
||||
* occurrence of the closing tag.
|
||||
* If the input string ends mid-tag or mid-content, the last block (text or tool use) is marked as partial.
|
||||
*
|
||||
* @param assistantMessage The raw string output from the assistant.
|
||||
* @returns An array of `AssistantMessageContent` objects, which can be `TextContent` or `ToolUse`.
|
||||
* Blocks that were not fully closed by the end of the input string will have their `partial` flag set to `true`.
|
||||
*/
|
||||
export function parseAssistantMessageV1(assistantMessage: string): AssistantMessageContent[] {
|
||||
const contentBlocks: AssistantMessageContent[] = []
|
||||
let currentTextContent: TextContent | undefined = undefined
|
||||
let currentTextContentStartIndex = 0
|
||||
let currentToolUse: ToolUse | undefined = undefined
|
||||
let currentToolUseStartIndex = 0
|
||||
let currentParamName: ToolParamName | undefined = undefined
|
||||
let currentParamValueStartIndex = 0
|
||||
let accumulator = ""
|
||||
|
||||
for (let i = 0; i < assistantMessage.length; i++) {
|
||||
const char = assistantMessage[i]
|
||||
accumulator += char
|
||||
|
||||
// --- State: Parsing a Tool Parameter ---
|
||||
// there should not be a param without a tool use
|
||||
if (currentToolUse && currentParamName) {
|
||||
const currentParamValue = accumulator.slice(currentParamValueStartIndex)
|
||||
const paramClosingTag = `</${currentParamName}>`
|
||||
if (currentParamValue.endsWith(paramClosingTag)) {
|
||||
// End of param value found
|
||||
currentToolUse.params[currentParamName] = currentParamValue.slice(0, -paramClosingTag.length).trim()
|
||||
currentParamName = undefined // Go back to parsing tool content or looking for next param
|
||||
continue // Move to next character
|
||||
} else {
|
||||
// Partial param value is accumulating
|
||||
continue // Move to next character
|
||||
}
|
||||
}
|
||||
|
||||
// --- State: Parsing a Tool Use (but not a specific parameter) ---
|
||||
// no currentParamName
|
||||
if (currentToolUse) {
|
||||
const currentToolValue = accumulator.slice(currentToolUseStartIndex)
|
||||
const toolUseClosingTag = `</${currentToolUse.name}>`
|
||||
|
||||
if (currentToolValue.endsWith(toolUseClosingTag)) {
|
||||
// End of a tool use found
|
||||
currentToolUse.partial = false
|
||||
contentBlocks.push(currentToolUse)
|
||||
currentToolUse = undefined // Go back to parsing text or looking for next tool
|
||||
// Reset text start index in case text follows immediately
|
||||
currentTextContentStartIndex = i + 1
|
||||
continue // Move to next character
|
||||
} else {
|
||||
// Check if starting a new parameter within the current tool use
|
||||
const possibleParamOpeningTags = toolParamNames.map((name) => `<${name}>`)
|
||||
let foundParamStart = false
|
||||
for (const paramOpeningTag of possibleParamOpeningTags) {
|
||||
if (accumulator.endsWith(paramOpeningTag)) {
|
||||
// Start of a new parameter found
|
||||
currentParamName = paramOpeningTag.slice(1, -1) as ToolParamName
|
||||
currentParamValueStartIndex = accumulator.length
|
||||
foundParamStart = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (foundParamStart) {
|
||||
continue // Move to next character
|
||||
}
|
||||
|
||||
// Special case for write_to_file/new_rule content param allowing nested tags
|
||||
// Check if a </content> tag appears, potentially indicating the end of the content param
|
||||
// even if the main tool closing tag hasn't been seen yet.
|
||||
const contentParamName: ToolParamName = "content"
|
||||
if (
|
||||
(currentToolUse.name === "write_to_file" || currentToolUse.name === "new_rule") &&
|
||||
accumulator.endsWith(`</${contentParamName}>`)
|
||||
) {
|
||||
const toolContent = accumulator.slice(currentToolUseStartIndex)
|
||||
const contentStartTag = `<${contentParamName}>`
|
||||
const contentEndTag = `</${contentParamName}>`
|
||||
const contentStartIndex = toolContent.indexOf(contentStartTag) + contentStartTag.length
|
||||
// Use lastIndexOf to handle cases where </content> might appear within the content itself
|
||||
const contentEndIndex = toolContent.lastIndexOf(contentEndTag)
|
||||
|
||||
// Ensure we found valid start/end tags and end is after start
|
||||
if (
|
||||
contentStartIndex !== -1 &&
|
||||
contentEndIndex !== -1 &&
|
||||
contentEndIndex > contentStartIndex - contentStartTag.length // Ensure end tag is after start tag begins
|
||||
) {
|
||||
// Check if this content param was already being parsed. If so, update it.
|
||||
// If not, and we just found the closing tag, assign it.
|
||||
// This handles cases where the </content> detection might fire before
|
||||
// the <content> tag detection logic, or if the content is very short.
|
||||
if (currentParamName === contentParamName) {
|
||||
// Already parsing content, now we found the end tag
|
||||
currentToolUse.params[contentParamName] = toolContent.slice(contentStartIndex, contentEndIndex).trim()
|
||||
currentParamName = undefined // Finished with this param
|
||||
} else if (currentParamName === undefined) {
|
||||
// Not parsing a param, but found </content>. Assume it closes the content block.
|
||||
currentToolUse.params[contentParamName] = toolContent.slice(contentStartIndex, contentEndIndex).trim()
|
||||
// We stay in the "parsing tool use" state, looking for more params or the tool end tag.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the above, partial tool value is accumulating
|
||||
continue // Move to next character
|
||||
}
|
||||
}
|
||||
|
||||
// --- State: Parsing Text (or looking for start of a tool use) ---
|
||||
// no currentToolUse
|
||||
let didStartToolUse = false
|
||||
const possibleToolUseOpeningTags = toolUseNames.map((name) => `<${name}>`)
|
||||
for (const toolUseOpeningTag of possibleToolUseOpeningTags) {
|
||||
if (accumulator.endsWith(toolUseOpeningTag)) {
|
||||
// Start of a new tool use found
|
||||
const toolName = toolUseOpeningTag.slice(1, -1) as ToolUseName
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: toolName,
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
currentToolUseStartIndex = accumulator.length
|
||||
|
||||
// This also indicates the end of the current text content block (if any)
|
||||
if (currentTextContent) {
|
||||
currentTextContent.partial = false
|
||||
// Extract text content, removing the part that formed the tool opening tag
|
||||
const textEndIndex = accumulator.length - toolUseOpeningTag.length
|
||||
currentTextContent.content = accumulator.slice(currentTextContentStartIndex, textEndIndex).trim()
|
||||
// Only add if there's actual content
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
currentTextContent = undefined
|
||||
} else {
|
||||
// Check if there was text before this tool use started
|
||||
const textEndIndex = accumulator.length - toolUseOpeningTag.length
|
||||
const potentialText = accumulator.slice(currentTextContentStartIndex, textEndIndex).trim()
|
||||
if (potentialText.length > 0) {
|
||||
contentBlocks.push({
|
||||
type: "text",
|
||||
content: potentialText,
|
||||
partial: false, // Ended because tool use started
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
didStartToolUse = true
|
||||
break // Found tool start, stop checking for others
|
||||
}
|
||||
}
|
||||
|
||||
if (!didStartToolUse) {
|
||||
// No tool use started, so it must be text content accumulating
|
||||
// (or continuing after a closed tool use)
|
||||
if (currentTextContent === undefined) {
|
||||
// Start of a new text block
|
||||
currentTextContentStartIndex = i - (accumulator.length - currentTextContentStartIndex - 1) // Adjust start index based on how much we've accumulated since the last block ended or the beginning
|
||||
// If accumulator starts from 0, start index is i
|
||||
if (contentBlocks.length === 0 && currentToolUse === undefined) {
|
||||
currentTextContentStartIndex = accumulator.length - 1 // i
|
||||
} else {
|
||||
// Re-calculate based on the actual start of the current text segment
|
||||
// Find the end of the last block
|
||||
let lastBlockEndIndex = 0
|
||||
if (contentBlocks.length > 0) {
|
||||
const lastBlock = contentBlocks[contentBlocks.length - 1]
|
||||
// Approximation: find where the accumulator matches the end of the message string representation of the last block. This is complex.
|
||||
// Simpler: Assume text starts right after the last block ended implicitly at index i.
|
||||
lastBlockEndIndex = i // Where the loop *was* when the last block finished processing
|
||||
// Need a more robust way to track the end index of the *raw string* corresponding to the last block.
|
||||
// Let's stick to the accumulator slice approach for simplicity in this version.
|
||||
// The start index should be where the current *unmatched* text began.
|
||||
let lastProcessedIndex = -1
|
||||
if (contentBlocks.length > 0) {
|
||||
// This requires knowing the raw string length of the previous block, which V1 doesn't explicitly track easily.
|
||||
// We'll approximate based on the current accumulator and start index logic.
|
||||
// The issue arises if a tool tag was just closed. accumulator contains everything up to i.
|
||||
// lastBlockEndIndex should point to the character *after* the closing tag of the last block.
|
||||
}
|
||||
// Reset start index to the beginning of the *current* potential text block
|
||||
currentTextContentStartIndex = accumulator.length - 1 // Start accumulating from the current character `i`
|
||||
}
|
||||
|
||||
// If we just closed a tool, text starts *after* its closing tag
|
||||
// The logic needs refinement here for accurate start index after a tool closure.
|
||||
// Let's assume for now the start index logic inside the loop handles it via slicing.
|
||||
}
|
||||
|
||||
currentTextContent = {
|
||||
type: "text",
|
||||
content: "", // Content will be filled by slicing accumulator
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
// Update text content based on the accumulator from its start index
|
||||
currentTextContent.content = accumulator.slice(currentTextContentStartIndex).trimStart() // Trim start to avoid leading space if text follows tool
|
||||
}
|
||||
} // End of loop
|
||||
|
||||
// --- Finalization after loop ---
|
||||
|
||||
// If a tool use was open at the end
|
||||
if (currentToolUse) {
|
||||
// If a parameter was open within that tool use
|
||||
if (currentParamName) {
|
||||
// The remaining accumulator content belongs to this partial parameter
|
||||
currentToolUse.params[currentParamName] = accumulator.slice(currentParamValueStartIndex).trim()
|
||||
}
|
||||
// Add the potentially partial tool use block
|
||||
contentBlocks.push(currentToolUse)
|
||||
}
|
||||
// If text content was being accumulated at the end
|
||||
// Note: Only one of currentToolUse or currentTextContent can be defined here,
|
||||
// as starting a tool use finalizes the preceding text block.
|
||||
else if (currentTextContent) {
|
||||
// Update content one last time
|
||||
currentTextContent.content = accumulator.slice(currentTextContentStartIndex).trim()
|
||||
// Add the potentially partial text block only if it contains content
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
}
|
||||
|
||||
return contentBlocks
|
||||
}
|
||||
// parseAssistantmessageV1 removed in https://github.com/cline/cline/pull/5425
|
||||
|
||||
/**
|
||||
* @description **Version 2**
|
||||
@@ -543,621 +304,3 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess
|
||||
|
||||
return contentBlocks
|
||||
}
|
||||
|
||||
export function parseAssistantMessageV3(assistantMessage: string): AssistantMessageContent[] {
|
||||
const contentBlocks: AssistantMessageContent[] = []
|
||||
let currentTextContentStart = 0 // Index where the current text block started
|
||||
let currentTextContent: TextContent | undefined = undefined
|
||||
let currentToolUseStart = 0 // Index *after* the opening tag of the current tool use
|
||||
let currentToolUse: ToolUse | undefined = undefined
|
||||
let currentParamValueStart = 0 // Index *after* the opening tag of the current param
|
||||
let currentParamName: ToolParamName | undefined = undefined
|
||||
|
||||
// Precompute tags for faster lookups
|
||||
const toolUseOpenTags = new Map<string, ToolUseName>()
|
||||
const toolParamOpenTags = new Map<string, ToolParamName>()
|
||||
for (const name of toolUseNames) {
|
||||
toolUseOpenTags.set(`<${name}>`, name)
|
||||
}
|
||||
for (const name of toolParamNames) {
|
||||
toolParamOpenTags.set(`<${name}>`, name)
|
||||
}
|
||||
|
||||
// Function calls format detection
|
||||
const isFunctionCallsOpen = "<function_calls>"
|
||||
const isFunctionCallsClose = "</function_calls>"
|
||||
const isInvokeStart = '<invoke name="'
|
||||
const isInvokeEnd = '">'
|
||||
const isInvokeClose = "</invoke>"
|
||||
const isParameterStart = '<parameter name="'
|
||||
const isParameterNameEnd = '">'
|
||||
const isParameterClose = "</parameter>"
|
||||
|
||||
// Variables for function calls parsing
|
||||
let inFunctionCalls = false
|
||||
let currentInvokeName = ""
|
||||
let currentParameterName = ""
|
||||
|
||||
const len = assistantMessage.length
|
||||
for (let i = 0; i < len; i++) {
|
||||
const currentCharIndex = i
|
||||
|
||||
// --- State: Parsing Function Calls ---
|
||||
// Check for opening function_calls tag
|
||||
if (
|
||||
!inFunctionCalls &&
|
||||
currentCharIndex >= isFunctionCallsOpen.length - 1 &&
|
||||
assistantMessage.startsWith(isFunctionCallsOpen, currentCharIndex - isFunctionCallsOpen.length + 1)
|
||||
) {
|
||||
// End current text block if one was active
|
||||
if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(currentTextContentStart, currentCharIndex - isFunctionCallsOpen.length + 1)
|
||||
.trim()
|
||||
currentTextContent.partial = false
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
currentTextContent = undefined
|
||||
}
|
||||
|
||||
inFunctionCalls = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for invoke start within function_calls
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName === "" &&
|
||||
!currentToolUse && // Don't create a new tool if we already have one
|
||||
currentCharIndex >= isInvokeStart.length - 1 &&
|
||||
assistantMessage.startsWith(isInvokeStart, currentCharIndex - isInvokeStart.length + 1)
|
||||
) {
|
||||
// Find the end of the invoke name
|
||||
const nameEndPos = assistantMessage.indexOf(isInvokeEnd, currentCharIndex + 1)
|
||||
if (nameEndPos !== -1) {
|
||||
// Extract the invoke name
|
||||
currentInvokeName = assistantMessage.slice(currentCharIndex + 1, nameEndPos)
|
||||
i = nameEndPos + isInvokeEnd.length - 1 // Skip to after the '">
|
||||
|
||||
// If this is an LS invoke, create a list_files tool
|
||||
if (currentInvokeName === "LS") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "list_files",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a Grep invoke, create a search_files tool
|
||||
if (currentInvokeName === "Grep") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "search_files",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Bash") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "execute_command",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Read") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "read_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Write") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "write_to_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "WebFetch") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "web_fetch",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AskQuestion") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "ask_followup_question",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "UseMCPTool") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "use_mcp_tool",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AccessMCPResource") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "access_mcp_resource",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "ListCodeDefinitionNames") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "list_code_definition_names",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "PlanModeRespond") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "plan_mode_respond",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "LoadMcpDocumentation") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "load_mcp_documentation",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AttemptCompletion") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "attempt_completion",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "BrowserAction") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "browser_action",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "NewTask") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "new_task",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a MultiEdit invoke, create a replace_in_file tool
|
||||
if (currentInvokeName === "MultiEdit") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "replace_in_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for parameter start within invoke
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentParameterName === "" &&
|
||||
currentCharIndex >= isParameterStart.length - 1 &&
|
||||
assistantMessage.startsWith(isParameterStart, currentCharIndex - isParameterStart.length + 1)
|
||||
) {
|
||||
// Find the end of the parameter name
|
||||
const nameEndPos = assistantMessage.indexOf(isParameterNameEnd, currentCharIndex + 1)
|
||||
if (nameEndPos !== -1) {
|
||||
// Extract the parameter name
|
||||
currentParameterName = assistantMessage.slice(currentCharIndex + 1, nameEndPos)
|
||||
currentParamValueStart = nameEndPos + isParameterNameEnd.length
|
||||
i = nameEndPos + isParameterNameEnd.length - 1 // Skip to after the '">'
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for parameter end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentParameterName !== "" &&
|
||||
currentCharIndex >= isParameterClose.length - 1 &&
|
||||
assistantMessage.startsWith(isParameterClose, currentCharIndex - isParameterClose.length + 1)
|
||||
) {
|
||||
// Extract parameter value
|
||||
const value = assistantMessage.slice(currentParamValueStart, currentCharIndex - isParameterClose.length + 1).trim()
|
||||
|
||||
// Map parameter to tool params
|
||||
if (currentToolUse && currentInvokeName === "LS" && currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
// Default recursive to false - only show top level
|
||||
currentToolUse.params["recursive"] = "false"
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Read" && currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "PlanModeRespond" && currentParameterName === "response") {
|
||||
currentToolUse.params["response"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "WebFetch" && currentParameterName === "url") {
|
||||
currentToolUse.params["url"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "ListCodeDefinitionNames" && currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "NewTask" && currentParameterName === "context") {
|
||||
currentToolUse.params["context"] = value
|
||||
}
|
||||
|
||||
// Map parameter to tool params for Grep
|
||||
if (currentToolUse && currentInvokeName === "Grep") {
|
||||
if (currentParameterName === "pattern") {
|
||||
currentToolUse.params["regex"] = value
|
||||
} else if (currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "include") {
|
||||
currentToolUse.params["file_pattern"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Bash") {
|
||||
if (currentParameterName === "command") {
|
||||
currentToolUse.params["command"] = value
|
||||
} else if (currentParameterName === "requires_approval") {
|
||||
currentToolUse.params["requires_approval"] = value === "true" ? "true" : "false"
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Write") {
|
||||
if (currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "content") {
|
||||
currentToolUse.params["content"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AskQuestion") {
|
||||
if (currentParameterName === "question") {
|
||||
currentToolUse.params["question"] = value
|
||||
} else if (currentParameterName === "options") {
|
||||
currentToolUse.params["options"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "UseMCPTool") {
|
||||
if (currentParameterName === "server_name") {
|
||||
currentToolUse.params["server_name"] = value
|
||||
} else if (currentParameterName === "tool_name") {
|
||||
currentToolUse.params["tool_name"] = value
|
||||
} else if (currentParameterName === "arguments") {
|
||||
currentToolUse.params["arguments"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AccessMCPResource") {
|
||||
if (currentParameterName === "server_name") {
|
||||
currentToolUse.params["server_name"] = value
|
||||
} else if (currentParameterName === "uri") {
|
||||
currentToolUse.params["uri"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AttemptCompletion") {
|
||||
if (currentParameterName === "result") {
|
||||
currentToolUse.params["result"] = value
|
||||
}
|
||||
if (currentParameterName === "command") {
|
||||
currentToolUse.params["command"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "BrowserAction") {
|
||||
if (currentParameterName === "action") {
|
||||
currentToolUse.params["action"] = value
|
||||
} else if (currentParameterName === "url") {
|
||||
currentToolUse.params["url"] = value
|
||||
} else if (currentParameterName === "coordinate") {
|
||||
currentToolUse.params["coordinate"] = value
|
||||
} else if (currentParameterName === "text") {
|
||||
currentToolUse.params["text"] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Map parameter to tool params for MultiEdit
|
||||
if (currentToolUse && currentInvokeName === "MultiEdit") {
|
||||
if (currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "edits") {
|
||||
// Save the value to the diff parameter for replace_in_file
|
||||
currentToolUse.params["diff"] = value
|
||||
}
|
||||
}
|
||||
|
||||
currentParameterName = ""
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for invoke end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentCharIndex >= isInvokeClose.length - 1 &&
|
||||
assistantMessage.startsWith(isInvokeClose, currentCharIndex - isInvokeClose.length + 1)
|
||||
) {
|
||||
// If we have a tool use from this invoke, finalize it
|
||||
if (
|
||||
currentToolUse &&
|
||||
(currentInvokeName === "LS" ||
|
||||
currentInvokeName === "Grep" ||
|
||||
currentInvokeName === "Bash" ||
|
||||
currentInvokeName === "Read" ||
|
||||
currentInvokeName === "Write" ||
|
||||
currentInvokeName === "WebFetch" ||
|
||||
currentInvokeName === "AskQuestion" ||
|
||||
currentInvokeName === "UseMCPTool" ||
|
||||
currentInvokeName === "AccessMCPResource" ||
|
||||
currentInvokeName === "ListCodeDefinitionNames" ||
|
||||
currentInvokeName === "PlanModeRespond" ||
|
||||
currentInvokeName === "LoadMcpDocumentation" ||
|
||||
currentInvokeName === "AttemptCompletion" ||
|
||||
currentInvokeName === "BrowserAction" ||
|
||||
currentInvokeName === "NewTask" ||
|
||||
currentInvokeName === "MultiEdit")
|
||||
) {
|
||||
currentToolUse.partial = false
|
||||
contentBlocks.push(currentToolUse)
|
||||
currentToolUse = undefined
|
||||
}
|
||||
currentInvokeName = ""
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for function_calls end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentCharIndex >= isFunctionCallsClose.length - 1 &&
|
||||
assistantMessage.startsWith(isFunctionCallsClose, currentCharIndex - isFunctionCallsClose.length + 1)
|
||||
) {
|
||||
inFunctionCalls = false
|
||||
currentTextContentStart = currentCharIndex + 1
|
||||
// Start a new text content block for any text after function_calls
|
||||
currentTextContent = {
|
||||
type: "text",
|
||||
content: "",
|
||||
partial: true,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip normal parsing when inside function_calls
|
||||
if (inFunctionCalls) {
|
||||
continue
|
||||
}
|
||||
|
||||
// --- State: Parsing a Tool Parameter ---
|
||||
if (currentToolUse && currentParamName) {
|
||||
const closeTag = `</${currentParamName}>`
|
||||
// Check if the string *ending* at index `i` matches the closing tag
|
||||
if (
|
||||
currentCharIndex >= closeTag.length - 1 &&
|
||||
assistantMessage.startsWith(
|
||||
closeTag,
|
||||
currentCharIndex - closeTag.length + 1, // Start checking from potential start of tag
|
||||
)
|
||||
) {
|
||||
// Found the closing tag for the parameter
|
||||
const value = assistantMessage
|
||||
.slice(
|
||||
currentParamValueStart, // Start after the opening tag
|
||||
currentCharIndex - closeTag.length + 1, // End before the closing tag
|
||||
)
|
||||
.trim()
|
||||
currentToolUse.params[currentParamName] = value
|
||||
currentParamName = undefined // Go back to parsing tool content
|
||||
// We don't continue loop here, need to check for tool close or other params at index i
|
||||
} else {
|
||||
continue // Still inside param value, move to next char
|
||||
}
|
||||
}
|
||||
|
||||
// --- State: Parsing a Tool Use (but not a specific parameter) ---
|
||||
if (currentToolUse && !currentParamName) {
|
||||
// Ensure we are not inside a parameter already
|
||||
// Check if starting a new parameter
|
||||
let startedNewParam = false
|
||||
for (const [tag, paramName] of toolParamOpenTags.entries()) {
|
||||
if (currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1)) {
|
||||
currentParamName = paramName
|
||||
currentParamValueStart = currentCharIndex + 1 // Value starts after the tag
|
||||
startedNewParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (startedNewParam) {
|
||||
continue // Handled start of param, move to next char
|
||||
}
|
||||
|
||||
// Check if closing the current tool use
|
||||
const toolCloseTag = `</${currentToolUse.name}>`
|
||||
if (
|
||||
currentCharIndex >= toolCloseTag.length - 1 &&
|
||||
assistantMessage.startsWith(toolCloseTag, currentCharIndex - toolCloseTag.length + 1)
|
||||
) {
|
||||
// End of the tool use found
|
||||
// Special handling for content params *before* finalizing the tool
|
||||
const toolContentSlice = assistantMessage.slice(
|
||||
currentToolUseStart, // From after the tool opening tag
|
||||
currentCharIndex - toolCloseTag.length + 1, // To before the tool closing tag
|
||||
)
|
||||
|
||||
// Check if content parameter needs special handling (write_to_file/new_rule)
|
||||
// This check is important if the closing </content> tag was missed by the parameter parsing logic
|
||||
// (e.g., if content is empty or parsing logic prioritizes tool close)
|
||||
const contentParamName: ToolParamName = "content"
|
||||
if (
|
||||
currentToolUse.name === "write_to_file" /* || currentToolUse.name === "new_rule" */ &&
|
||||
toolContentSlice.includes(`<${contentParamName}>`)
|
||||
) {
|
||||
const contentStartTag = `<${contentParamName}>`
|
||||
const contentEndTag = `</${contentParamName}>`
|
||||
const contentStart = toolContentSlice.indexOf(contentStartTag)
|
||||
// Use lastIndexOf for robustness against nested tags
|
||||
const contentEnd = toolContentSlice.lastIndexOf(contentEndTag)
|
||||
|
||||
if (contentStart !== -1 && contentEnd !== -1 && contentEnd > contentStart) {
|
||||
const contentValue = toolContentSlice.slice(contentStart + contentStartTag.length, contentEnd).trim()
|
||||
currentToolUse.params[contentParamName] = contentValue
|
||||
}
|
||||
}
|
||||
|
||||
currentToolUse.partial = false // Mark as complete
|
||||
contentBlocks.push(currentToolUse)
|
||||
currentToolUse = undefined // Reset state
|
||||
currentTextContentStart = currentCharIndex + 1 // Potential text starts after this tag
|
||||
continue // Move to next char
|
||||
}
|
||||
// If not starting a param and not closing the tool, continue accumulating tool content implicitly
|
||||
continue
|
||||
}
|
||||
|
||||
// --- State: Parsing Text / Looking for Tool Start ---
|
||||
if (!currentToolUse) {
|
||||
// Check if starting a new tool use
|
||||
let startedNewTool = false
|
||||
for (const [tag, toolName] of toolUseOpenTags.entries()) {
|
||||
if (currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1)) {
|
||||
// End current text block if one was active
|
||||
if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(
|
||||
currentTextContentStart, // From where text started
|
||||
currentCharIndex - tag.length + 1, // To before the tool tag starts
|
||||
)
|
||||
.trim()
|
||||
currentTextContent.partial = false // Ended because tool started
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
currentTextContent = undefined
|
||||
} else {
|
||||
// Check for any text between the last block and this tag
|
||||
const potentialText = assistantMessage
|
||||
.slice(
|
||||
currentTextContentStart, // From where text *might* have started
|
||||
currentCharIndex - tag.length + 1, // To before the tool tag starts
|
||||
)
|
||||
.trim()
|
||||
if (potentialText.length > 0) {
|
||||
contentBlocks.push({
|
||||
type: "text",
|
||||
content: potentialText,
|
||||
partial: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Start the new tool use
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: toolName,
|
||||
params: {},
|
||||
partial: true, // Assume partial until closing tag is found
|
||||
}
|
||||
currentToolUseStart = currentCharIndex + 1 // Tool content starts after the opening tag
|
||||
startedNewTool = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (startedNewTool) {
|
||||
continue // Handled start of tool, move to next char
|
||||
}
|
||||
|
||||
// If not starting a tool, it must be text content
|
||||
if (!currentTextContent) {
|
||||
// Start a new text block if we aren't already in one
|
||||
currentTextContentStart = currentCharIndex // Text starts at the current character
|
||||
// Check if the current char is the start of potential text *immediately* after a tag
|
||||
// This needs the previous state - simpler to let slicing handle it later.
|
||||
// Resetting start index accurately is key.
|
||||
// It should be the index *after* the last processed tag.
|
||||
// The logic managing currentTextContentStart after closing tags handles this.
|
||||
|
||||
currentTextContent = {
|
||||
type: "text",
|
||||
content: "", // Will be determined by slicing at the end or when a tool starts
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
// Continue accumulating text implicitly; content is extracted later.
|
||||
}
|
||||
} // End of loop
|
||||
|
||||
// --- Finalization after loop ---
|
||||
|
||||
// Finalize any open parameter within an open tool use
|
||||
if (currentToolUse && currentParamName) {
|
||||
currentToolUse.params[currentParamName] = assistantMessage
|
||||
.slice(currentParamValueStart) // From param start to end of string
|
||||
.trim()
|
||||
// Tool use remains partial
|
||||
}
|
||||
|
||||
// Finalize any open tool use (which might contain the finalized partial param)
|
||||
if (currentToolUse) {
|
||||
// Tool use is partial because the loop finished before its closing tag
|
||||
contentBlocks.push(currentToolUse)
|
||||
}
|
||||
// Finalize any trailing text content
|
||||
// Only possible if a tool use wasn't open at the very end
|
||||
else if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(currentTextContentStart) // From text start to end of string
|
||||
.trim()
|
||||
// Text is partial because the loop finished
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
}
|
||||
|
||||
return contentBlocks
|
||||
}
|
||||
|
||||
@@ -104,5 +104,6 @@ export interface TestInput {
|
||||
thinkingBudgetTokens: number
|
||||
originalDiffEditToolCallMessage?: string
|
||||
diffApplyFile?: string
|
||||
provider?: string
|
||||
isVerbose: boolean
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"entry": [
|
||||
"src/extension.ts",
|
||||
"src/standalone/cline-core.ts",
|
||||
"src/generated/hosts/standalone/protobus-server-setup.ts",
|
||||
"src/generated/hosts/standalone/host-bridge-clients.ts",
|
||||
"src/generated/hosts/vscode/protobus-services.ts",
|
||||
"src/generated/hosts/vscode/hostbridge-grpc-service-config.ts"
|
||||
],
|
||||
"project": ["src/**/*.ts"],
|
||||
"ignore": ["out/**", "node_modules/**", "*.d.ts", "**/*.test.ts", "**/__tests__", "src/test/**", "src/shared/**"],
|
||||
"vite": true
|
||||
}
|
||||
Generated
+392
-18013
File diff suppressed because it is too large
Load Diff
+20
-20
@@ -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.19.8",
|
||||
"version": "3.26.0",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -119,7 +119,8 @@
|
||||
{
|
||||
"type": "webview",
|
||||
"id": "claude-dev.SidebarProvider",
|
||||
"name": ""
|
||||
"name": "",
|
||||
"icon": "assets/icons/icon.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -338,22 +339,23 @@
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run package",
|
||||
"compile": "npm run check-types && npm run lint && node esbuild.js",
|
||||
"compile-standalone": "npm run check-types && npm run lint && node esbuild.js --standalone",
|
||||
"compile": "npm run check-types && npm run lint && node esbuild.mjs",
|
||||
"compile-standalone": "npm run check-types && npm run lint && node esbuild.mjs --standalone",
|
||||
"postcompile-standalone": "node scripts/package-standalone.mjs",
|
||||
"watch": "npm-run-all -p watch:*",
|
||||
"watch:esbuild": "node esbuild.js --watch",
|
||||
"watch:esbuild": "node esbuild.mjs --watch",
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.js --production",
|
||||
"protos": "node scripts/build-proto.mjs && node scripts/generate-protobus-setup.mjs && node scripts/generate-host-bridge-client.mjs",
|
||||
"postprotos": "prettier src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --log-level warn",
|
||||
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.mjs --production",
|
||||
"protos": "node scripts/build-proto.mjs",
|
||||
"postprotos": "biome format src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --no-errors-on-unmatched",
|
||||
"clean": "rimraf dist dist-standalone webview-ui/build src/generated out/",
|
||||
"compile-tests": "node ./scripts/build-tests.js",
|
||||
"watch-tests": "tsc -p . -w --outDir out",
|
||||
"check-types": "npm run protos && npx tsc --noEmit && cd webview-ui && npx tsc -b --noEmit",
|
||||
"lint": "eslint src --ext ts && eslint webview-ui/src --ext ts && buf lint && cd webview-ui && npm run lint",
|
||||
"format": "prettier . --check",
|
||||
"format:fix": "prettier . --write",
|
||||
"lint": "biome lint --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error && buf lint",
|
||||
"format": "biome format --changed --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error",
|
||||
"format:fix": "biome check --changed --no-errors-on-unmatched --files-ignore-unknown=true --write",
|
||||
"fix:all": "biome check --no-errors-on-unmatched --files-ignore-unknown=true --write --diagnostic-level=error --unsafe",
|
||||
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
|
||||
"test": "npm-run-all test:unit test:integration",
|
||||
"test:ci": "node scripts/test-ci.js",
|
||||
@@ -361,8 +363,8 @@
|
||||
"test:unit": "TS_NODE_PROJECT='./tsconfig.unit-test.json' mocha",
|
||||
"test:coverage": "vscode-test --coverage",
|
||||
"e2e": "playwright test -c playwright.config.ts",
|
||||
"test:e2e": "playwright install && vsce package --no-dependencies --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
"test:e2e:optimal": "vsce package --no-dependencies --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
"test:e2e": "playwright install && vsce package --no-dependencies --allow-package-secrets sendgrid --out dist/e2e.vsix && node src/test/e2e/utils/build.mjs && playwright test",
|
||||
"test:e2e:optimal": "vsce package --no-dependencies --allow-package-secrets sendgrid --out dist/e2e.vsix && node src/test/e2e/utils/build.mjs && playwright test",
|
||||
"install:all": "npm install && cd webview-ui && npm install",
|
||||
"dev:webview": "cd webview-ui && npm run dev",
|
||||
"build:webview": "cd webview-ui && npm run build",
|
||||
@@ -379,10 +381,11 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
"prettier --write --ignore-unknown --log-level=log"
|
||||
"biome check --write --staged --no-errors-on-unmatched --files-ignore-unknown=true"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.1.4",
|
||||
"@bufbuild/buf": "^1.54.0",
|
||||
"@changesets/cli": "^2.27.12",
|
||||
"@types/chai": "^5.0.1",
|
||||
@@ -397,23 +400,17 @@
|
||||
"@types/sinon": "^17.0.4",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"@types/vscode": "^1.84.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@typescript-eslint/utils": "^8.33.0",
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "^3.6.0",
|
||||
"chai": "^4.3.10",
|
||||
"chalk": "^5.3.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-eslint-rules": "file:eslint-rules",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.0",
|
||||
"minimatch": "^3.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
"protoc-gen-ts": "^0.8.7",
|
||||
"proxyquire": "^2.1.3",
|
||||
"rimraf": "^6.0.1",
|
||||
@@ -446,6 +443,7 @@
|
||||
"@playwright/test": "^1.53.2",
|
||||
"@sentry/browser": "^9.12.0",
|
||||
"@streamparser/json": "^0.0.22",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.8.2",
|
||||
@@ -490,6 +488,8 @@
|
||||
"tree-sitter-wasms": "^0.1.11",
|
||||
"ts-morph": "^25.0.1",
|
||||
"turndown": "^7.2.0",
|
||||
"ulid": "^2.4.0",
|
||||
"uuid": "^11.1.0",
|
||||
"vscode-uri": "^3.1.0",
|
||||
"web-tree-sitter": "^0.22.6",
|
||||
"zod": "^3.24.2"
|
||||
|
||||
+20
-6
@@ -1,17 +1,31 @@
|
||||
import { defineConfig } from "@playwright/test"
|
||||
|
||||
const isGitHubAction = !!process.env.CI
|
||||
const isCI = !!process?.env?.CI
|
||||
const isWindow = process?.platform?.startsWith("win")
|
||||
|
||||
export default defineConfig({
|
||||
workers: 1,
|
||||
retries: 1,
|
||||
forbidOnly: isCI,
|
||||
testDir: "src/test/e2e",
|
||||
timeout: 20000,
|
||||
testMatch: /.*\.test\.ts/,
|
||||
timeout: isCI || isWindow ? 40000 : 20000,
|
||||
expect: {
|
||||
timeout: 20000,
|
||||
timeout: isCI || isWindow ? 5000 : 2000,
|
||||
},
|
||||
fullyParallel: true,
|
||||
reporter: isGitHubAction ? [["github"], ["list"]] : [["list"]],
|
||||
globalSetup: require.resolve("./src/test/e2e/utils/setup"),
|
||||
globalTeardown: require.resolve("./src/test/e2e/utils/teardown"),
|
||||
reporter: isCI ? [["github"], ["list"]] : [["list"]],
|
||||
use: {
|
||||
video: "retain-on-failure",
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "setup test environment",
|
||||
testMatch: /global\.setup\.ts/,
|
||||
},
|
||||
{
|
||||
name: "e2e tests",
|
||||
dependencies: ["setup test environment"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -36,6 +36,8 @@ service AccountService {
|
||||
rpc getUserOrganizations(EmptyRequest) returns (UserOrganizationsResponse);
|
||||
|
||||
rpc setUserOrganization(UserOrganizationUpdateRequest) returns (Empty);
|
||||
|
||||
rpc openrouterAuthClicked(EmptyRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message AuthStateChangedRequest {
|
||||
@@ -127,4 +129,4 @@ message OrganizationUsageTransaction {
|
||||
int32 prompt_tokens = 10;
|
||||
int32 total_tokens = 11;
|
||||
string user_id = 12;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -42,6 +42,7 @@ message BrowserSettings {
|
||||
optional bool remote_browser_enabled = 3;
|
||||
optional string chrome_executable_path = 4;
|
||||
optional bool disable_tool_use = 5;
|
||||
optional string custom_args = 6;
|
||||
}
|
||||
|
||||
message UpdateBrowserSettingsRequest {
|
||||
@@ -51,4 +52,5 @@ message UpdateBrowserSettingsRequest {
|
||||
optional bool remote_browser_enabled = 4;
|
||||
optional string chrome_executable_path = 5;
|
||||
optional bool disable_tool_use = 6;
|
||||
optional string custom_args = 7;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
// Service for running IDE commands, for example context menu actions,
|
||||
// commands, etc.
|
||||
// In contrast to the rest of the ProtoBus services, these are
|
||||
// intended to be called by the IDE directly instead of through the webview,
|
||||
// because they are triggered by interactions in the IDE.
|
||||
service CommandsService {
|
||||
rpc addToCline(CommandContext) returns (Empty);
|
||||
rpc fixWithCline(CommandContext) returns (Empty);
|
||||
rpc explainWithCline(CommandContext) returns (Empty);
|
||||
rpc improveWithCline(CommandContext) returns (Empty);
|
||||
}
|
||||
|
||||
message CommandContext {
|
||||
// The absolute path of the current file.
|
||||
optional string file_path = 1;
|
||||
// The selected source text.
|
||||
optional string selected_text = 2;
|
||||
// The language identifier for the current file.
|
||||
optional string language = 3;
|
||||
// Any diagnostic problems for the current file.
|
||||
repeated cline.Diagnostic diagnostics = 4;
|
||||
}
|
||||
@@ -55,6 +55,11 @@ message Boolean {
|
||||
bool value = 1;
|
||||
}
|
||||
|
||||
// the same as Boolean, but avoiding name conflicts
|
||||
message BooleanResponse {
|
||||
bool value = 1;
|
||||
}
|
||||
|
||||
message StringArray {
|
||||
repeated string values = 1;
|
||||
}
|
||||
@@ -68,3 +73,32 @@ message KeyValuePair {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message FileDiagnostics {
|
||||
string file_path = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
|
||||
message Diagnostic {
|
||||
string message = 1;
|
||||
DiagnosticRange range = 2;
|
||||
DiagnosticSeverity severity = 3;
|
||||
optional string source = 4;
|
||||
}
|
||||
|
||||
message DiagnosticRange {
|
||||
DiagnosticPosition start = 1;
|
||||
DiagnosticPosition end = 2;
|
||||
}
|
||||
|
||||
message DiagnosticPosition {
|
||||
int32 line = 1;
|
||||
int32 character = 2;
|
||||
}
|
||||
|
||||
enum DiagnosticSeverity {
|
||||
DIAGNOSTIC_ERROR = 0;
|
||||
DIAGNOSTIC_WARNING = 1;
|
||||
DIAGNOSTIC_INFORMATION = 2;
|
||||
DIAGNOSTIC_HINT = 3;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -55,8 +55,14 @@ service FileService {
|
||||
// Toggles a workflow on or off
|
||||
rpc toggleWorkflow(ToggleWorkflowRequest) returns (ClineRulesToggles);
|
||||
|
||||
// Subscribe to workspace file updates
|
||||
rpc subscribeToWorkspaceUpdates(EmptyRequest) returns (stream StringArray);
|
||||
// Check if file exists in the project
|
||||
rpc ifFileExistsRelativePath(StringRequest) returns (BooleanResponse);
|
||||
|
||||
// Open a file in editor by a relative path
|
||||
rpc openFileRelativePath(StringRequest) returns (Empty);
|
||||
|
||||
// Opens or creates a focus chain checklist markdown file for editing
|
||||
rpc openFocusChainFile(StringRequest) returns (Empty);
|
||||
}
|
||||
|
||||
// Response for refreshRules operation
|
||||
@@ -87,12 +93,19 @@ message RelativePaths {
|
||||
repeated string paths = 1;
|
||||
}
|
||||
|
||||
// Enum for file search type filtering
|
||||
enum FileSearchType {
|
||||
FILE = 0;
|
||||
FOLDER = 1;
|
||||
}
|
||||
|
||||
// Request for file search operations
|
||||
message FileSearchRequest {
|
||||
Metadata metadata = 1;
|
||||
string query = 2; // Search query string
|
||||
optional string mentions_request_id = 3; // Optional request ID for tracking requests
|
||||
optional int32 limit = 4; // Optional limit for results (default: 20)
|
||||
optional FileSearchType selected_type = 5; // Optional selected type filter
|
||||
}
|
||||
|
||||
// Result for file search operations
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -25,8 +25,10 @@ service ModelsService {
|
||||
rpc subscribeToOpenRouterModels(EmptyRequest) returns (stream OpenRouterCompatibleModelInfo);
|
||||
// Updates API configuration
|
||||
rpc updateApiConfigurationProto(UpdateApiConfigurationRequest) returns (Empty);
|
||||
// Refreshes and returns Groq models
|
||||
// Refreshes and returns Groq models
|
||||
rpc refreshGroqModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns Baseten models
|
||||
rpc refreshBasetenModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
}
|
||||
|
||||
// List of VS Code LM models
|
||||
@@ -129,6 +131,9 @@ enum ApiProvider {
|
||||
CLAUDE_CODE = 26;
|
||||
MOONSHOT = 27;
|
||||
HUGGINGFACE = 28;
|
||||
HUAWEI_CLOUD_MAAS = 29;
|
||||
BASETEN = 30;
|
||||
ZAI = 31;
|
||||
}
|
||||
|
||||
// Model info for OpenAI-compatible models
|
||||
@@ -171,7 +176,7 @@ message ModelsApiConfiguration {
|
||||
// Global configuration fields (not mode-specific)
|
||||
optional string api_key = 1;
|
||||
optional string cline_api_key = 2;
|
||||
optional string task_id = 3;
|
||||
optional string ulid = 3;
|
||||
optional string lite_llm_base_url = 4;
|
||||
optional string lite_llm_api_key = 5;
|
||||
optional bool lite_llm_use_prompt_cache = 6;
|
||||
@@ -201,34 +206,41 @@ message ModelsApiConfiguration {
|
||||
optional string open_ai_native_api_key = 30;
|
||||
optional string deep_seek_api_key = 31;
|
||||
optional string requesty_api_key = 32;
|
||||
optional string together_api_key = 33;
|
||||
optional string fireworks_api_key = 34;
|
||||
optional int32 fireworks_model_max_completion_tokens = 35;
|
||||
optional int32 fireworks_model_max_tokens = 36;
|
||||
optional string qwen_api_key = 37;
|
||||
optional string doubao_api_key = 38;
|
||||
optional string mistral_api_key = 39;
|
||||
optional string azure_api_version = 40;
|
||||
optional string qwen_api_line = 41;
|
||||
optional string nebius_api_key = 42;
|
||||
optional string asksage_api_url = 43;
|
||||
optional string asksage_api_key = 44;
|
||||
optional string xai_api_key = 45;
|
||||
optional string sambanova_api_key = 46;
|
||||
optional string cerebras_api_key = 47;
|
||||
optional int32 request_timeout_ms = 48;
|
||||
optional string sap_ai_core_client_id = 49;
|
||||
optional string sap_ai_core_client_secret = 50;
|
||||
optional string sap_ai_resource_group = 51;
|
||||
optional string sap_ai_core_token_url = 52;
|
||||
optional string sap_ai_core_base_url = 53;
|
||||
optional string moonshot_api_key = 54;
|
||||
optional string moonshot_api_line = 55;
|
||||
optional string aws_authentication = 56;
|
||||
optional string aws_bedrock_api_key = 57;
|
||||
optional string cline_account_id = 58;
|
||||
optional string groq_api_key = 59;
|
||||
optional string hugging_face_api_key = 60;
|
||||
optional string requesty_base_url = 33;
|
||||
optional string together_api_key = 34;
|
||||
optional string fireworks_api_key = 35;
|
||||
optional int32 fireworks_model_max_completion_tokens = 36;
|
||||
optional int32 fireworks_model_max_tokens = 37;
|
||||
optional string qwen_api_key = 38;
|
||||
optional string doubao_api_key = 39;
|
||||
optional string mistral_api_key = 40;
|
||||
optional string azure_api_version = 41;
|
||||
optional string qwen_api_line = 42;
|
||||
optional string nebius_api_key = 43;
|
||||
optional string asksage_api_url = 44;
|
||||
optional string asksage_api_key = 45;
|
||||
optional string xai_api_key = 46;
|
||||
optional string sambanova_api_key = 47;
|
||||
optional string cerebras_api_key = 48;
|
||||
optional int32 request_timeout_ms = 49;
|
||||
optional string sap_ai_core_client_id = 50;
|
||||
optional string sap_ai_core_client_secret = 51;
|
||||
optional string sap_ai_resource_group = 52;
|
||||
optional string sap_ai_core_token_url = 53;
|
||||
optional string sap_ai_core_base_url = 54;
|
||||
optional string moonshot_api_key = 55;
|
||||
optional string moonshot_api_line = 56;
|
||||
optional string aws_authentication = 57;
|
||||
optional string aws_bedrock_api_key = 58;
|
||||
optional string cline_account_id = 59;
|
||||
optional string groq_api_key = 60;
|
||||
optional string hugging_face_api_key = 61;
|
||||
optional string huawei_cloud_maas_api_key = 62;
|
||||
optional string baseten_api_key = 63;
|
||||
optional string ollama_api_key = 64;
|
||||
optional string zai_api_key = 65;
|
||||
optional string zai_api_line = 66;
|
||||
optional string lm_studio_max_tokens = 67;
|
||||
|
||||
// Plan mode configurations
|
||||
optional ApiProvider plan_mode_api_provider = 100;
|
||||
@@ -255,6 +267,10 @@ message ModelsApiConfiguration {
|
||||
optional OpenRouterModelInfo plan_mode_groq_model_info = 121;
|
||||
optional string plan_mode_hugging_face_model_id = 122;
|
||||
optional OpenRouterModelInfo plan_mode_hugging_face_model_info = 123;
|
||||
optional string plan_mode_huawei_cloud_maas_model_id = 124;
|
||||
optional OpenRouterModelInfo plan_mode_huawei_cloud_maas_model_info = 125;
|
||||
optional string plan_mode_baseten_model_id = 126;
|
||||
optional OpenRouterModelInfo plan_mode_baseten_model_info = 127;
|
||||
|
||||
// Act mode configurations
|
||||
optional ApiProvider act_mode_api_provider = 200;
|
||||
@@ -281,6 +297,10 @@ message ModelsApiConfiguration {
|
||||
optional OpenRouterModelInfo act_mode_groq_model_info = 221;
|
||||
optional string act_mode_hugging_face_model_id = 222;
|
||||
optional OpenRouterModelInfo act_mode_hugging_face_model_info = 223;
|
||||
optional string act_mode_huawei_cloud_maas_model_id = 224;
|
||||
optional OpenRouterModelInfo act_mode_huawei_cloud_maas_model_info = 225;
|
||||
optional string act_mode_baseten_model_id = 226;
|
||||
optional OpenRouterModelInfo act_mode_baseten_model_info = 227;
|
||||
|
||||
repeated string favorited_model_ids = 300;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
syntax = "proto3";
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
service StateService {
|
||||
rpc getLatestState(EmptyRequest) returns (State);
|
||||
rpc updateTerminalConnectionTimeout(Int64Request) returns (Int64);
|
||||
rpc updateTerminalConnectionTimeout(UpdateTerminalConnectionTimeoutRequest) returns (UpdateTerminalConnectionTimeoutResponse);
|
||||
rpc updateTerminalReuseEnabled(BooleanRequest) returns (Empty);
|
||||
rpc updateDefaultTerminalProfile(StringRequest) returns (TerminalProfileUpdateResponse);
|
||||
rpc getAvailableTerminalProfiles(EmptyRequest) returns (TerminalProfiles);
|
||||
rpc subscribeToState(EmptyRequest) returns (stream State);
|
||||
rpc toggleFavoriteModel(StringRequest) returns (Empty);
|
||||
rpc resetState(ResetStateRequest) returns (Empty);
|
||||
rpc togglePlanActMode(TogglePlanActModeRequest) returns (Boolean);
|
||||
rpc togglePlanActModeProto(TogglePlanActModeRequest) returns (Boolean);
|
||||
rpc updateAutoApprovalSettings(AutoApprovalSettingsRequest) returns (Empty);
|
||||
rpc updateSettings(UpdateSettingsRequest) returns (Empty);
|
||||
rpc updateTelemetrySetting(TelemetrySettingRequest) returns (Empty);
|
||||
@@ -43,7 +43,7 @@ message TerminalProfileUpdateResponse {
|
||||
|
||||
message TogglePlanActModeRequest {
|
||||
Metadata metadata = 1;
|
||||
ChatSettings chat_settings = 2;
|
||||
PlanActMode mode = 2;
|
||||
optional ChatContent chat_content = 3;
|
||||
}
|
||||
|
||||
@@ -52,10 +52,16 @@ enum PlanActMode {
|
||||
ACT = 1;
|
||||
}
|
||||
|
||||
message ChatSettings {
|
||||
PlanActMode mode = 1;
|
||||
optional string preferred_language = 2;
|
||||
optional string open_ai_reasoning_effort = 3;
|
||||
enum OpenaiReasoningEffort {
|
||||
LOW = 0;
|
||||
MEDIUM = 1;
|
||||
HIGH = 2;
|
||||
}
|
||||
|
||||
enum McpDisplayMode {
|
||||
RICH = 0;
|
||||
PLAIN = 1;
|
||||
MARKDOWN = 2;
|
||||
}
|
||||
|
||||
message ChatContent {
|
||||
@@ -108,12 +114,16 @@ message UpdateSettingsRequest {
|
||||
optional bool plan_act_separate_models_setting = 4;
|
||||
optional bool enable_checkpoints_setting = 5;
|
||||
optional bool mcp_marketplace_enabled = 6;
|
||||
optional ChatSettings chat_settings = 7;
|
||||
optional int64 shell_integration_timeout = 8;
|
||||
optional int32 shell_integration_timeout = 8;
|
||||
optional bool terminal_reuse_enabled = 9;
|
||||
optional bool mcp_responses_collapsed = 10;
|
||||
optional string mcp_display_mode = 11;
|
||||
optional int64 terminal_output_line_limit = 12;
|
||||
optional McpDisplayMode mcp_display_mode = 11;
|
||||
optional int32 terminal_output_line_limit = 12;
|
||||
optional PlanActMode mode = 13;
|
||||
optional string preferred_language = 14;
|
||||
optional OpenaiReasoningEffort openai_reasoning_effort = 15;
|
||||
optional bool strict_plan_mode_enabled = 16;
|
||||
optional FocusChainSettings focus_chain_settings = 17;
|
||||
}
|
||||
|
||||
// Complete API Configuration message
|
||||
@@ -121,7 +131,7 @@ message ApiConfiguration {
|
||||
// Global configuration fields (not mode-specific)
|
||||
optional string api_key = 1; // anthropic
|
||||
optional string cline_api_key = 2;
|
||||
optional string task_id = 3;
|
||||
optional string ulid = 3;
|
||||
optional string lite_llm_base_url = 4;
|
||||
optional string lite_llm_api_key = 5;
|
||||
optional bool lite_llm_use_prompt_cache = 6;
|
||||
@@ -151,34 +161,40 @@ message ApiConfiguration {
|
||||
optional string openai_native_api_key = 30;
|
||||
optional string deep_seek_api_key = 31;
|
||||
optional string requesty_api_key = 32;
|
||||
optional string together_api_key = 33;
|
||||
optional string fireworks_api_key = 34;
|
||||
optional int64 fireworks_model_max_completion_tokens = 35;
|
||||
optional int64 fireworks_model_max_tokens = 36;
|
||||
optional string qwen_api_key = 37;
|
||||
optional string doubao_api_key = 38;
|
||||
optional string mistral_api_key = 39;
|
||||
optional string azure_api_version = 40;
|
||||
optional string qwen_api_line = 41;
|
||||
optional string nebius_api_key = 42;
|
||||
optional string asksage_api_url = 43;
|
||||
optional string asksage_api_key = 44;
|
||||
optional string xai_api_key = 45;
|
||||
optional string sambanova_api_key = 46;
|
||||
optional string cerebras_api_key = 47;
|
||||
optional int64 request_timeout_ms = 48;
|
||||
optional string sap_ai_core_client_id = 49;
|
||||
optional string sap_ai_core_client_secret = 50;
|
||||
optional string sap_ai_resource_group = 51;
|
||||
optional string sap_ai_core_token_url = 52;
|
||||
optional string sap_ai_core_base_url = 53;
|
||||
optional string moonshot_api_key = 54;
|
||||
optional string moonshot_api_line = 55;
|
||||
optional string requesty_base_url = 33;
|
||||
optional string together_api_key = 34;
|
||||
optional string fireworks_api_key = 35;
|
||||
optional int32 fireworks_model_max_completion_tokens = 36;
|
||||
optional int32 fireworks_model_max_tokens = 37;
|
||||
optional string qwen_api_key = 38;
|
||||
optional string doubao_api_key = 39;
|
||||
optional string mistral_api_key = 40;
|
||||
optional string azure_api_version = 41;
|
||||
optional string qwen_api_line = 42;
|
||||
optional string nebius_api_key = 43;
|
||||
optional string asksage_api_url = 44;
|
||||
optional string asksage_api_key = 45;
|
||||
optional string xai_api_key = 46;
|
||||
optional string sambanova_api_key = 47;
|
||||
optional string cerebras_api_key = 48;
|
||||
optional int32 request_timeout_ms = 49;
|
||||
optional string sap_ai_core_client_id = 50;
|
||||
optional string sap_ai_core_client_secret = 51;
|
||||
optional string sap_ai_resource_group = 52;
|
||||
optional string sap_ai_core_token_url = 53;
|
||||
optional string sap_ai_core_base_url = 54;
|
||||
optional string moonshot_api_key = 55;
|
||||
optional string moonshot_api_line = 56;
|
||||
optional string huawei_cloud_maas_api_key = 57;
|
||||
optional string ollama_api_key = 58;
|
||||
optional string zai_api_key = 59;
|
||||
optional string zai_api_line = 60;
|
||||
optional string lm_studio_max_tokens = 61;
|
||||
|
||||
// Plan mode configurations
|
||||
optional string plan_mode_api_provider = 100;
|
||||
optional string plan_mode_api_model_id = 101;
|
||||
optional int64 plan_mode_thinking_budget_tokens = 102;
|
||||
optional int32 plan_mode_thinking_budget_tokens = 102;
|
||||
optional string plan_mode_reasoning_effort = 103;
|
||||
optional string plan_mode_vscode_lm_model_selector = 104; // JSON string
|
||||
optional bool plan_mode_aws_bedrock_custom_selected = 105;
|
||||
@@ -196,11 +212,13 @@ message ApiConfiguration {
|
||||
optional string plan_mode_together_model_id = 117;
|
||||
optional string plan_mode_fireworks_model_id = 118;
|
||||
optional string plan_mode_sap_ai_core_model_id = 119;
|
||||
optional string plan_mode_huawei_cloud_maas_model_id = 120;
|
||||
optional string plan_mode_huawei_cloud_maas_model_info = 121;
|
||||
|
||||
// Act mode configurations
|
||||
optional string act_mode_api_provider = 200;
|
||||
optional string act_mode_api_model_id = 201;
|
||||
optional int64 act_mode_thinking_budget_tokens = 202;
|
||||
optional int32 act_mode_thinking_budget_tokens = 202;
|
||||
optional string act_mode_reasoning_effort = 203;
|
||||
optional string act_mode_vscode_lm_model_selector = 204; // JSON string
|
||||
optional bool act_mode_aws_bedrock_custom_selected = 205;
|
||||
@@ -218,6 +236,8 @@ message ApiConfiguration {
|
||||
optional string act_mode_together_model_id = 217;
|
||||
optional string act_mode_fireworks_model_id = 218;
|
||||
optional string act_mode_sap_ai_core_model_id = 219;
|
||||
optional string act_mode_huawei_cloud_maas_model_id = 220;
|
||||
optional string act_mode_huawei_cloud_maas_model_info = 221;
|
||||
|
||||
// Favorited model IDs
|
||||
repeated string favorited_model_ids = 300;
|
||||
@@ -228,3 +248,16 @@ message ApiConfiguration {
|
||||
|
||||
optional string cline_account_id = 303;
|
||||
}
|
||||
|
||||
message UpdateTerminalConnectionTimeoutRequest {
|
||||
optional int32 timeout_ms = 1;
|
||||
}
|
||||
|
||||
message FocusChainSettings {
|
||||
bool enabled = 1;
|
||||
int32 remind_cline_interval = 2;
|
||||
}
|
||||
|
||||
message UpdateTerminalConnectionTimeoutResponse {
|
||||
optional int32 timeout_ms = 1;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
@@ -41,6 +41,7 @@ enum ClineAsk {
|
||||
NEW_TASK = 13;
|
||||
CONDENSE = 14;
|
||||
REPORT_BUG = 15;
|
||||
SUMMARIZE_TASK = 16;
|
||||
}
|
||||
|
||||
// Enum for ClineSay types
|
||||
@@ -72,6 +73,7 @@ enum ClineSay {
|
||||
CHECKPOINT_CREATED = 24;
|
||||
LOAD_MCP_DOCUMENTATION = 25;
|
||||
INFO = 26;
|
||||
TASK_PROGRESS = 27;
|
||||
}
|
||||
|
||||
// Enum for ClineSayTool tool types
|
||||
@@ -227,7 +229,7 @@ service UiService {
|
||||
rpc onDidShowAnnouncement(EmptyRequest) returns (Boolean);
|
||||
|
||||
// Subscribe to addToInput events (when user adds content via context menu)
|
||||
rpc subscribeToAddToInput(EmptyRequest) returns (stream String);
|
||||
rpc subscribeToAddToInput(StringRequest) returns (stream String);
|
||||
|
||||
// Subscribe to MCP button clicked events
|
||||
rpc subscribeToMcpButtonClicked(WebviewProviderTypeRequest) returns (stream Empty);
|
||||
@@ -267,4 +269,7 @@ service UiService {
|
||||
|
||||
// Opens a URL in the default browser
|
||||
rpc openUrl(StringRequest) returns (Empty);
|
||||
|
||||
// Opens the Cline walkthrough
|
||||
rpc openWalkthrough(EmptyRequest) returns (Empty);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
+39
-9
@@ -4,22 +4,34 @@ package host;
|
||||
option java_package = "bot.cline.host.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
|
||||
// Provides methods for diff views.
|
||||
service DiffService {
|
||||
// Open the diff view/editor.
|
||||
rpc openDiff(OpenDiffRequest) returns (OpenDiffResponse);
|
||||
|
||||
// Get the contents of the diff view.
|
||||
rpc getDocumentText(GetDocumentTextRequest) returns (GetDocumentTextResponse);
|
||||
|
||||
// Replace a text selection in the diff.
|
||||
rpc replaceText(ReplaceTextRequest) returns (ReplaceTextResponse);
|
||||
|
||||
rpc scrollDiff(ScrollDiffRequest) returns (ScrollDiffResponse);
|
||||
|
||||
// Truncate the diff document.
|
||||
rpc truncateDocument(TruncateDocumentRequest) returns (TruncateDocumentResponse);
|
||||
|
||||
// Save the diff document.
|
||||
rpc saveDocument(SaveDocumentRequest) returns (SaveDocumentResponse);
|
||||
// Close the diff editor UI.
|
||||
rpc closeDiff(CloseDiffRequest) returns (CloseDiffResponse);
|
||||
|
||||
// Close all the diff editor windows/tabs.
|
||||
// Any diff editors with unsaved content should not be closed.
|
||||
rpc closeAllDiffs(CloseAllDiffsRequest) returns (CloseAllDiffsResponse);
|
||||
|
||||
// Display a diff view comparing before/after states for multiple files.
|
||||
// Content is passed as in-memory data, not read from the file system.
|
||||
rpc openMultiFileDiff(OpenMultiFileDiffRequest) returns (OpenMultiFileDiffResponse);
|
||||
}
|
||||
|
||||
message OpenDiffRequest {
|
||||
@@ -54,20 +66,24 @@ message ReplaceTextRequest {
|
||||
|
||||
message ReplaceTextResponse {}
|
||||
|
||||
message ScrollDiffRequest {
|
||||
optional string diff_id = 1;
|
||||
optional int32 line = 2;
|
||||
}
|
||||
|
||||
message ScrollDiffResponse {}
|
||||
|
||||
message TruncateDocumentRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
optional int32 end_line = 5;
|
||||
optional int32 end_line = 3;
|
||||
}
|
||||
|
||||
message TruncateDocumentResponse {}
|
||||
|
||||
message CloseDiffRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
}
|
||||
message CloseAllDiffsRequest {}
|
||||
|
||||
message CloseDiffResponse {}
|
||||
message CloseAllDiffsResponse {}
|
||||
|
||||
message SaveDocumentRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
@@ -75,3 +91,17 @@ message SaveDocumentRequest {
|
||||
}
|
||||
|
||||
message SaveDocumentResponse {}
|
||||
|
||||
message OpenMultiFileDiffRequest {
|
||||
optional string title = 1;
|
||||
repeated ContentDiff diffs = 2;
|
||||
}
|
||||
|
||||
message ContentDiff {
|
||||
// The absolute file path.
|
||||
optional string file_path = 1;
|
||||
optional string left_content = 2;
|
||||
optional string right_content = 3;
|
||||
}
|
||||
|
||||
message OpenMultiFileDiffResponse {}
|
||||
|
||||
+12
-3
@@ -4,7 +4,7 @@ package host;
|
||||
option java_package = "bot.cline.host.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
|
||||
// Provides methods for working with the user's environment.
|
||||
service EnvService {
|
||||
@@ -14,6 +14,15 @@ service EnvService {
|
||||
// Reads text from the system clipboard.
|
||||
rpc clipboardReadText(cline.EmptyRequest) returns (cline.String);
|
||||
|
||||
// Opens a URL in the user's default browser or application.
|
||||
rpc openExternal(cline.StringRequest) returns (cline.Empty);
|
||||
// Returns a stable machine identifier for telemetry distinctId purposes.
|
||||
rpc getMachineId(cline.EmptyRequest) returns (cline.String);
|
||||
|
||||
rpc getHostVersion(cline.EmptyRequest) returns (GetHostVersionResponse);
|
||||
}
|
||||
|
||||
message GetHostVersionResponse {
|
||||
// The name of the host platform, e.g VSCode
|
||||
optional string platform = 1;
|
||||
// The version of the host platform, e.g. 1.103.0
|
||||
optional string version = 2;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package host;
|
||||
option java_package = "bot.cline.host.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
// This is for use in integration tests to get the contents of the webview.
|
||||
service TestingService {
|
||||
rpc getWebviewHtml(GetWebviewHtmlRequest) returns (GetWebviewHtmlResponse);
|
||||
}
|
||||
|
||||
message GetWebviewHtmlRequest {
|
||||
}
|
||||
|
||||
message GetWebviewHtmlResponse {
|
||||
optional string html = 1;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ package host;
|
||||
option java_package = "bot.cline.host.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
|
||||
/**
|
||||
* The watch service is only here as example of a streaming rpc in the host bridge.
|
||||
|
||||
+58
-13
@@ -4,16 +4,36 @@ package host;
|
||||
option java_package = "bot.cline.host.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
|
||||
// Provides methods for working with IDE windows and editors.
|
||||
service WindowService {
|
||||
// Opens a text document in the editor and returns editor information.
|
||||
// Opens a text document in the IDE editor and returns editor information.
|
||||
rpc showTextDocument(ShowTextDocumentRequest) returns (TextEditorInfo);
|
||||
|
||||
// Shows the open file dialogue / file picker.
|
||||
rpc showOpenDialogue(ShowOpenDialogueRequest) returns (SelectedResources);
|
||||
|
||||
// Shows a notification.
|
||||
rpc showMessage(ShowMessageRequest) returns (SelectedResponse);
|
||||
|
||||
// Prompts the user for input and returns the response.
|
||||
rpc showInputBox(ShowInputBoxRequest) returns (ShowInputBoxResponse);
|
||||
|
||||
// Shows the file save dialogue / file picker.
|
||||
rpc showSaveDialog(ShowSaveDialogRequest) returns (ShowSaveDialogResponse);
|
||||
|
||||
// Opens a file in the IDE.
|
||||
rpc openFile(OpenFileRequest) returns (OpenFileResponse);
|
||||
|
||||
// Returns the open tabs.
|
||||
rpc getOpenTabs(GetOpenTabsRequest) returns (GetOpenTabsResponse);
|
||||
|
||||
// Returns the visible tabs.
|
||||
rpc getVisibleTabs(GetVisibleTabsRequest) returns (GetVisibleTabsResponse);
|
||||
|
||||
// Returns information about the current editor
|
||||
rpc getActiveEditor(GetActiveEditorRequest) returns (GetActiveEditorResponse);
|
||||
}
|
||||
|
||||
message ShowTextDocumentRequest {
|
||||
@@ -57,10 +77,9 @@ enum ShowMessageType {
|
||||
}
|
||||
|
||||
message ShowMessageRequest {
|
||||
cline.Metadata metadata = 1;
|
||||
ShowMessageType type = 2;
|
||||
string message = 3;
|
||||
optional ShowMessageRequestOptions options = 4;
|
||||
ShowMessageType type = 1;
|
||||
string message = 2;
|
||||
optional ShowMessageRequestOptions options = 3;
|
||||
}
|
||||
|
||||
message ShowMessageRequestOptions {
|
||||
@@ -75,30 +94,56 @@ message SelectedResponse {
|
||||
}
|
||||
|
||||
message ShowSaveDialogRequest {
|
||||
cline.Metadata metadata = 1;
|
||||
optional ShowSaveDialogOptions options = 2;
|
||||
optional ShowSaveDialogOptions options = 1;
|
||||
}
|
||||
|
||||
message ShowSaveDialogOptions {
|
||||
optional string default_path = 1;
|
||||
// A map of file types to extensions, e.g
|
||||
// "Text Files": { "extensions": ["txt", "md"] }
|
||||
map<string, FileExtensionList> filters = 2;
|
||||
}
|
||||
|
||||
message FileExtensionList {
|
||||
// A list of file extension (without the dot).
|
||||
repeated string extensions = 1;
|
||||
}
|
||||
|
||||
message ShowSaveDialogResponse {
|
||||
// If the user cancelled the dialog, this will be empty.
|
||||
optional string selected_path = 1;
|
||||
}
|
||||
|
||||
message ShowInputBoxRequest {
|
||||
cline.Metadata metadata = 1;
|
||||
string title = 2;
|
||||
optional string prompt = 3;
|
||||
optional string value = 4;
|
||||
string title = 1;
|
||||
optional string prompt = 2;
|
||||
optional string value = 3;
|
||||
}
|
||||
|
||||
message ShowInputBoxResponse {
|
||||
optional string response = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message OpenFileRequest {
|
||||
string file_path = 1;
|
||||
}
|
||||
|
||||
message OpenFileResponse {}
|
||||
|
||||
message GetOpenTabsRequest {}
|
||||
|
||||
message GetOpenTabsResponse {
|
||||
repeated string paths = 1;
|
||||
}
|
||||
|
||||
message GetVisibleTabsRequest {}
|
||||
|
||||
message GetVisibleTabsResponse {
|
||||
repeated string paths = 1;
|
||||
}
|
||||
|
||||
message GetActiveEditorRequest {}
|
||||
|
||||
message GetActiveEditorResponse {
|
||||
optional string file_path = 1;
|
||||
}
|
||||
|
||||
@@ -4,14 +4,23 @@ package host;
|
||||
option java_package = "bot.cline.host.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
import "cline/common.proto";
|
||||
|
||||
// Provides methods for working with workspaces/projects.
|
||||
service WorkspaceService {
|
||||
// Returns a list of the top level directories of the workspace.
|
||||
rpc getWorkspacePaths(GetWorkspacePathsRequest) returns (GetWorkspacePathsResponse);
|
||||
// Saves an open document if it's dirty
|
||||
rpc saveOpenDocumentIfDirty(SaveOpenDocumentIfDirtyRequest) returns (cline.Empty);
|
||||
|
||||
// Saves an open document if it's open in the editor and has unsaved changes.
|
||||
// Returns true if the document was saved, returns false if the document was not found, or did not
|
||||
// need to be saved.
|
||||
rpc saveOpenDocumentIfDirty(SaveOpenDocumentIfDirtyRequest) returns (SaveOpenDocumentIfDirtyResponse);
|
||||
|
||||
// Get diagnostics from the workspace.
|
||||
rpc getDiagnostics(GetDiagnosticsRequest) returns (GetDiagnosticsResponse);
|
||||
|
||||
// Makes the problems panel/pane visible in the IDE and focuses it.
|
||||
rpc openProblemsPanel(OpenProblemsPanelRequest) returns (OpenProblemsPanelResponse);
|
||||
}
|
||||
|
||||
message GetWorkspacePathsRequest {
|
||||
@@ -28,6 +37,20 @@ message GetWorkspacePathsResponse {
|
||||
}
|
||||
|
||||
message SaveOpenDocumentIfDirtyRequest {
|
||||
cline.Metadata metadata = 1;
|
||||
string file_path = 2;
|
||||
optional string file_path = 2;
|
||||
}
|
||||
message SaveOpenDocumentIfDirtyResponse {
|
||||
// Returns true if the document was saved.
|
||||
optional bool was_saved = 1;
|
||||
}
|
||||
|
||||
message GetDiagnosticsRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
}
|
||||
|
||||
message GetDiagnosticsResponse {
|
||||
repeated cline.FileDiagnostics file_diagnostics = 1;
|
||||
}
|
||||
|
||||
message OpenProblemsPanelRequest {}
|
||||
message OpenProblemsPanelResponse {}
|
||||
|
||||
+56
-4
@@ -7,8 +7,10 @@ import { globby } from "globby"
|
||||
import { createRequire } from "module"
|
||||
import os from "os"
|
||||
import * as path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { rmrf } from "./file-utils.mjs"
|
||||
import { main as generateHostBridgeClient } from "./generate-host-bridge-client.mjs"
|
||||
import { main as generateProtoBusSetup } from "./generate-protobus-setup.mjs"
|
||||
import { loadProtoDescriptorSet } from "./proto-utils.mjs"
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc")
|
||||
@@ -34,9 +36,14 @@ const TS_PROTO_OPTIONS = [
|
||||
]
|
||||
|
||||
async function main() {
|
||||
console.log(chalk.bold.blue("Compiling Protocol Buffers..."))
|
||||
|
||||
await cleanup()
|
||||
await compileProtos()
|
||||
await checkProtos()
|
||||
await generateProtoBusSetup()
|
||||
await generateHostBridgeClient()
|
||||
}
|
||||
async function compileProtos() {
|
||||
console.log(chalk.bold.blue("Compiling Protocol Buffers..."))
|
||||
|
||||
// Check for Apple Silicon compatibility before proceeding
|
||||
checkAppleSiliconCompatibility()
|
||||
@@ -174,12 +181,57 @@ function checkAppleSiliconCompatibility() {
|
||||
console.log(chalk.red("Aborting build process."))
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
console.log(chalk.yellow("Could not determine Rosetta installation status. Proceeding anyway."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int64TypeNames = ["TYPE_INT64", "TYPE_UINT64", "TYPE_SINT64", "TYPE_FIXED64", "TYPE_SFIXED64"]
|
||||
|
||||
async function checkProtos() {
|
||||
const proto = await loadProtoDescriptorSet()
|
||||
const int64Fields = []
|
||||
|
||||
for (const [packageName, packageDef] of Object.entries(proto)) {
|
||||
for (const [messageName, def] of Object.entries(packageDef)) {
|
||||
// Skip service definitions
|
||||
if (def && typeof def === "object" && "service" in def) {
|
||||
continue
|
||||
}
|
||||
// Check message fields
|
||||
if (def && def.type && def.type.field) {
|
||||
for (const field of def.type.field) {
|
||||
if (int64TypeNames.includes(field.type)) {
|
||||
const name = `${packageName}.${messageName}.${field.name}`
|
||||
int64Fields.push({
|
||||
name: name,
|
||||
type: field.type,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (int64Fields.length > 0) {
|
||||
console.log(chalk.yellow(`\nWarning: Found ${int64Fields.length} fields using 64-bit integer types`))
|
||||
for (const field of int64Fields) {
|
||||
const typeNames = {
|
||||
TYPE_INT64: "int64",
|
||||
TYPE_UINT64: "uint64",
|
||||
TYPE_SINT64: "sint64",
|
||||
TYPE_FIXED64: "fixed64",
|
||||
TYPE_SFIXED64: "sfixed64",
|
||||
}
|
||||
log_verbose(chalk.yellow(` - ${field.name} (${typeNames[field.type]})`))
|
||||
}
|
||||
log_verbose(chalk.yellow("\nWARNING: 64-bit integer fields detected in proto definitions"))
|
||||
log_verbose(chalk.yellow("JavaScript cannot safely represent integers larger than 2^53-1 (Number.MAX_SAFE_INTEGER)."))
|
||||
log_verbose(chalk.yellow("Consider using string representation for large numbers or implementing BigInt support.\n"))
|
||||
}
|
||||
}
|
||||
|
||||
function log_verbose(s) {
|
||||
if (process.argv.includes("-v") || process.argv.includes("--verbose")) {
|
||||
console.log(s)
|
||||
|
||||
Regular → Executable
+1
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
const { execSync } = require("child_process")
|
||||
const esbuild = require("esbuild")
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { writeFileWithMkdirs } from "./file-utils.mjs"
|
||||
import * as path from "path"
|
||||
import chalk from "chalk"
|
||||
import { loadServicesFromProtoDescriptor, getFqn } from "./proto-utils.mjs"
|
||||
import * as path from "path"
|
||||
import { writeFileWithMkdirs } from "./file-utils.mjs"
|
||||
import { getFqn, loadServicesFromProtoDescriptor } from "./proto-utils.mjs"
|
||||
|
||||
// Contains the interface definitions for the host bridge clients.
|
||||
const TYPES_FILE = path.resolve("src/generated/hosts/host-bridge-client-types.ts")
|
||||
@@ -15,7 +15,7 @@ const VSCODE_CLIENT_FILE = path.resolve("src/generated/hosts/vscode/hostbridge-g
|
||||
/**
|
||||
* Main function to generate the host bridge client
|
||||
*/
|
||||
async function main() {
|
||||
export async function main() {
|
||||
const { hostServices } = await loadServicesFromProtoDescriptor()
|
||||
|
||||
await generateTypesFile(hostServices)
|
||||
@@ -234,8 +234,10 @@ const ${name}ServiceRegistry = createServiceRegistry("${name}")
|
||||
${methods}`
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main().catch((error) => {
|
||||
console.error(chalk.red("Error:"), error)
|
||||
process.exit(1)
|
||||
})
|
||||
// Only run main if this script is executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch((error) => {
|
||||
console.error(chalk.red("Error:"), error)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { writeFileWithMkdirs } from "./file-utils.mjs"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { loadServicesFromProtoDescriptor, getFqn } from "./proto-utils.mjs"
|
||||
import { writeFileWithMkdirs } from "./file-utils.mjs"
|
||||
import { getFqn, loadServicesFromProtoDescriptor } from "./proto-utils.mjs"
|
||||
|
||||
const WEBVIEW_CLIENTS_FILE = path.resolve("webview-ui/src/services/grpc-client.ts")
|
||||
const VSCODE_SERVICES_FILE = path.resolve("src/generated/hosts/vscode/protobus-services.ts")
|
||||
@@ -12,7 +12,7 @@ const STANDALONE_SERVER_SETUP_FILE = path.resolve("src/generated/hosts/standalon
|
||||
|
||||
const SCRIPT_NAME = path.relative(process.cwd(), fileURLToPath(import.meta.url))
|
||||
|
||||
async function main() {
|
||||
export async function main() {
|
||||
const { protobusServices } = await loadServicesFromProtoDescriptor()
|
||||
await generateWebviewProtobusClients(protobusServices)
|
||||
await generateVscodeServiceTypes(protobusServices)
|
||||
@@ -40,11 +40,11 @@ async function generateWebviewProtobusClients(protobusServices) {
|
||||
}
|
||||
if (!rpc.responseStream) {
|
||||
rpcs.push(` static async ${rpcName}(request: ${requestType}): Promise<${responseType}> {
|
||||
return this.makeRequest("${rpcName}", request)
|
||||
return this.makeUnaryRequest("${rpcName}", request, ${requestType}.toJSON, ${responseType}.fromJSON)
|
||||
}`)
|
||||
} else {
|
||||
rpcs.push(` static ${rpcName}(request: ${requestType}, callbacks: Callbacks<${responseType}>): ()=>void {
|
||||
return this.makeStreamingRequest("${rpcName}", request, callbacks)
|
||||
return this.makeStreamingRequest("${rpcName}", request, ${requestType}.toJSON, ${responseType}.fromJSON, callbacks)
|
||||
}`)
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ ${rpcs.join("\n")}
|
||||
}
|
||||
|
||||
// Create output file
|
||||
let output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
const output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
// Generated by ${SCRIPT_NAME}
|
||||
import * as proto from "@shared/proto/index"
|
||||
import { ProtoBusClient, Callbacks } from "./grpc-client-base"
|
||||
@@ -94,7 +94,7 @@ async function generateVscodeServiceTypes(protobusServices) {
|
||||
}
|
||||
|
||||
// Create output file
|
||||
let output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
const output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
// Generated by ${SCRIPT_NAME}
|
||||
import * as proto from "@shared/proto/index"
|
||||
import { Controller } from "@core/controller"
|
||||
@@ -128,7 +128,7 @@ async function generateVscodeProtobusServers(protobusServices) {
|
||||
}
|
||||
|
||||
// Create output file
|
||||
let output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
const output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
// Generated by ${SCRIPT_NAME}
|
||||
import * as serviceTypes from "src/generated/hosts/vscode/protobus-service-types"
|
||||
|
||||
@@ -176,7 +176,7 @@ async function generateStandaloneProtobusServiceSetup(protobusServices) {
|
||||
}
|
||||
|
||||
// Create output file
|
||||
let output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
const output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
// Generated by ${SCRIPT_NAME}
|
||||
import * as grpc from "@grpc/grpc-js"
|
||||
import { cline } from "@generated/grpc-js"
|
||||
@@ -205,4 +205,10 @@ function getDirName(serviceName) {
|
||||
return domain.charAt(0).toLowerCase() + domain.slice(1)
|
||||
}
|
||||
|
||||
main()
|
||||
// Only run main if this script is executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch((error) => {
|
||||
console.error(chalk.red("Error:"), error)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ function traverse(container, output, prefix = "") {
|
||||
const name = decl.getName()
|
||||
output.push(`${prefix}.${name} = createStub("${prefix}.${name}");`)
|
||||
}
|
||||
} else if (kind == SyntaxKind.ClassDeclaration) {
|
||||
} else if (kind === SyntaxKind.ClassDeclaration) {
|
||||
const name = node.getName()
|
||||
output.push(
|
||||
`${prefix}.${name} = class { constructor(...args) {
|
||||
@@ -55,13 +55,27 @@ function traverse(container, output, prefix = "") {
|
||||
}
|
||||
|
||||
function mapReturn(typeStr) {
|
||||
if (!typeStr) return ""
|
||||
if (typeStr.includes("void")) return ""
|
||||
if (typeStr.includes("string")) return `return '';`
|
||||
if (typeStr.includes("number")) return `return 0;`
|
||||
if (typeStr.includes("boolean")) return `return false;`
|
||||
if (typeStr.includes("[]")) return `return [];`
|
||||
if (typeStr.includes("Thenable")) return `return Promise.resolve(null);`
|
||||
if (!typeStr) {
|
||||
return ""
|
||||
}
|
||||
if (typeStr.includes("void")) {
|
||||
return ""
|
||||
}
|
||||
if (typeStr.includes("string")) {
|
||||
return `return '';`
|
||||
}
|
||||
if (typeStr.includes("number")) {
|
||||
return `return 0;`
|
||||
}
|
||||
if (typeStr.includes("boolean")) {
|
||||
return `return false;`
|
||||
}
|
||||
if (typeStr.includes("[]")) {
|
||||
return `return [];`
|
||||
}
|
||||
if (typeStr.includes("Thenable")) {
|
||||
return `return Promise.resolve(null);`
|
||||
}
|
||||
return `return createStub("unknown");`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
DIR=${1:-src/}
|
||||
FILES=$(git ls-files src|grep -v test|grep -v vscode|grep -v extension.ts|grep -v evals|grep -v standalone|grep -v /dev/)
|
||||
DEST_DIR=dist-standalone
|
||||
SDK_DEST=$DEST_DIR/vscode-sdk-uses.txt
|
||||
CSS_DEST=$DEST_DIR/vscode-css-uses.txt
|
||||
TMP=/tmp/vscode-sdk-uses.txt.tmp
|
||||
mkdir -p $DEST_DIR
|
||||
|
||||
if [[ ${1:-} == "-v" ]]; then
|
||||
grep -Er --color=always 'vscode[?]?\.' $FILES
|
||||
fi
|
||||
|
||||
{
|
||||
git grep -h 'vscode\.' $DIR |
|
||||
grep -Ev '//.*vscode' | # remove commented out code
|
||||
grep -v vscode.commands.executeCommand | # executeCommand is handled separately
|
||||
grep -Ev '"vscode' | # remove command strings that get included because they start with vscode
|
||||
sed 's|.*vscode\.|vscode.|'| # remove everything before vscode.
|
||||
sed 's/[^a-zA-Z0-9_.].*$//' | # remove everything after last identifier
|
||||
grep -E '\.[a-z][^.]+$' | # remove types (last part of identifier should be lowercase)
|
||||
cat > $TMP
|
||||
grep -Ehr 'vscode[?]?\.' $FILES |
|
||||
grep -Ev '//.*vscode' | # remove commented out code
|
||||
grep -v vscode.commands.executeCommand | # executeCommand is handled separately
|
||||
#grep -Ev '"vscode' | # remove command strings that get included because they start with vscode
|
||||
sed 's|.*vscode|vscode|'| # remove everything before vscode.
|
||||
sed 's|?||g' | # remove ? from vcode?.env?.foo
|
||||
sed 's/[^a-zA-Z0-9_.?].*$//' | # remove everything after last identifier
|
||||
grep -E '\.[a-z][^.]+$' | # remove types (last part of identifier should be lowercase)
|
||||
cat > $TMP
|
||||
}
|
||||
{
|
||||
grep -rh vscode.commands.executeCommand $DIR |
|
||||
grep -hr 'vscode.commands.executeCommand' $FILES |
|
||||
perl -ne 'print if /["\x27"]/' | # Remove occurrences where the command is not on the same line (line doesnt contain quote chars) :(
|
||||
sed -n 's|.*\(vscode.commands.executeCommand[^,]*\).*|\1|p'| # Remove all params after the first one
|
||||
sed 's|\(".*"\).*|\1)|'| # Close the parantheses
|
||||
@@ -40,4 +45,3 @@ sed 's/[),"\\].*$//' | # remove from the end of the var name to the end of the l
|
||||
sort | uniq > $CSS_DEST
|
||||
}
|
||||
echo Wrote vscode vars used to $(realpath $CSS_DEST)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { cp } from "fs/promises"
|
||||
import { glob } from "glob"
|
||||
import minimatch from "minimatch"
|
||||
import path from "path"
|
||||
|
||||
const BUILD_DIR = "dist-standalone"
|
||||
const RUNTIME_DEPS_DIR = "standalone/runtime-files"
|
||||
|
||||
@@ -104,6 +105,7 @@ function createIsIgnored(standaloneIgnores) {
|
||||
".eslintrc*",
|
||||
".babelrc*",
|
||||
".prettierrc*",
|
||||
"biome.json*",
|
||||
".cz-config.js",
|
||||
".commitlintrc*",
|
||||
"webpack.config.js",
|
||||
|
||||
+15
-6
@@ -1,16 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import * as grpc from "@grpc/grpc-js"
|
||||
import * as protoLoader from "@grpc/proto-loader"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
|
||||
const DESCRIPTOR_SET = path.resolve("dist-standalone/proto/descriptor_set.pb")
|
||||
|
||||
const typeNameToFQN = new Map()
|
||||
|
||||
function addTypeNameToFqn(name, fqn) {
|
||||
if (typeNameToFQN.has(name)) {
|
||||
if (typeNameToFQN.has(name) && typeNameToFQN.get(name) !== fqn) {
|
||||
throw new Error(`Proto type ${name} redefined (${fqn}).`)
|
||||
}
|
||||
typeNameToFQN.set(name, fqn)
|
||||
@@ -23,11 +23,20 @@ export function getFqn(name) {
|
||||
return typeNameToFQN.get(name)
|
||||
}
|
||||
|
||||
export async function getPackageDefinition() {
|
||||
const descriptorBuffer = await fs.readFile(DESCRIPTOR_SET)
|
||||
const options = { longs: Number } // Encode int64 fields as numbers
|
||||
return protoLoader.loadFileDescriptorSetFromBuffer(descriptorBuffer, options)
|
||||
}
|
||||
|
||||
export async function loadProtoDescriptorSet() {
|
||||
const packageDefinition = await getPackageDefinition()
|
||||
return grpc.loadPackageDefinition(packageDefinition)
|
||||
}
|
||||
|
||||
export async function loadServicesFromProtoDescriptor() {
|
||||
// Load service definitions from descriptor set
|
||||
const descriptorBuffer = await fs.readFile(DESCRIPTOR_SET)
|
||||
const packageDefinition = protoLoader.loadFileDescriptorSetFromBuffer(descriptorBuffer)
|
||||
const proto = grpc.loadPackageDefinition(packageDefinition)
|
||||
const proto = await loadProtoDescriptorSet()
|
||||
|
||||
// Extract host services and proto messages from the proto definition
|
||||
const hostServices = {}
|
||||
|
||||
@@ -14,7 +14,7 @@ const getClineVersion = () => {
|
||||
const extensions = execSync("code --list-extensions --show-versions").toString()
|
||||
const clineMatch = extensions.match(/claude-dev@(\d+\.\d+\.\d+)/)
|
||||
return clineMatch ? clineMatch[1] : "Not installed"
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
return "Error getting version"
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ const collectSystemInfo = () => {
|
||||
cpuInfo = execSync("lscpu").toString().split("\n").slice(0, 5).join("\n")
|
||||
memoryInfo = execSync("free -h").toString()
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
// Fallback for unsupported systems
|
||||
cpuInfo = Array.from(new Set(os.cpus().map((c) => c.model))).join("\n")
|
||||
memoryInfo = `${Math.round(os.totalmem() / 1e9)} GB RAM`
|
||||
@@ -52,7 +52,7 @@ const checkGitHubAuth = async () => {
|
||||
try {
|
||||
execSync("gh auth status", { stdio: "ignore" })
|
||||
return true
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.log("\nGitHub authentication required.")
|
||||
console.log("\nPlease run the following command in your terminal to authenticate:")
|
||||
console.log("\n gh auth login\n")
|
||||
@@ -92,7 +92,7 @@ const openUrl = (url) => {
|
||||
console.log("\nPlease open this URL in your browser:")
|
||||
console.log(url)
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.log("\nFailed to open URL automatically. Please open this URL in your browser:")
|
||||
console.log(url)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu #x
|
||||
|
||||
# This installs the cline-core app to the user's home directory,
|
||||
# and starts the service.
|
||||
|
||||
if [[ "${1:-}" == "-h" ]]; then
|
||||
./scripts/test-hostbridge-server.ts &
|
||||
fi
|
||||
|
||||
CORE_DIR=~/.cline/core
|
||||
INSTALL_DIR=$CORE_DIR/0.0.1
|
||||
LOG_FILE=~/.cline/cline-core-service.log
|
||||
|
||||
ZIP_FILE=standalone.zip
|
||||
ZIP=dist-standalone/${ZIP_FILE}
|
||||
@@ -18,4 +24,5 @@ cd $INSTALL_DIR
|
||||
unp $ZIP_FILE > /dev/null
|
||||
|
||||
pkill -f cline-core.js || true
|
||||
NODE_PATH=./node_modules DEV_WORKSPACE_FOLDER=/tmp/ node cline-core.js
|
||||
|
||||
NODE_PATH=./node_modules DEV_WORKSPACE_FOLDER=/tmp/ node cline-core.js 2>&1 | tee $LOG_FILE
|
||||
|
||||
Regular → Executable
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
import * as grpc from "@grpc/grpc-js"
|
||||
import { ReflectionService } from "@grpc/reflection"
|
||||
import * as health from "grpc-health-check"
|
||||
import * as os from "os"
|
||||
import { host } from "src/generated/grpc-js/index"
|
||||
import { getPackageDefinition } from "./proto-utils.mjs"
|
||||
|
||||
export async function startTestHostBridgeServer() {
|
||||
const server = new grpc.Server()
|
||||
|
||||
// Set up health check
|
||||
const healthImpl = new health.HealthImplementation({ "": "SERVING" })
|
||||
healthImpl.addToServer(server)
|
||||
|
||||
// Add host bridge services using the mock implementations
|
||||
server.addService(host.WorkspaceServiceService, createMockService<host.WorkspaceServiceServer>("WorkspaceService"))
|
||||
server.addService(host.WindowServiceService, createMockService<host.WindowServiceServer>("WindowService"))
|
||||
server.addService(host.EnvServiceService, createMockService<host.EnvServiceServer>("EnvService"))
|
||||
server.addService(host.DiffServiceService, createMockService<host.DiffServiceServer>("DiffService"))
|
||||
server.addService(host.WatchServiceService, createMockService<host.WatchServiceServer>("WatchService"))
|
||||
|
||||
// Load package definition for reflection service
|
||||
const packageDefinition = await getPackageDefinition()
|
||||
// Filter service names to only include host services
|
||||
const hostBridgeServiceNames = Object.keys(packageDefinition).filter(
|
||||
(name) => name.startsWith("host.") || name.startsWith("grpc.health"),
|
||||
)
|
||||
const reflection = new ReflectionService(packageDefinition, {
|
||||
services: hostBridgeServiceNames,
|
||||
})
|
||||
reflection.addToServer(server)
|
||||
|
||||
const bindAddress = process.env.HOST_BRIDGE_ADDRESS || `127.0.0.1:26041`
|
||||
|
||||
server.bindAsync(bindAddress, grpc.ServerCredentials.createInsecure(), (err) => {
|
||||
if (err) {
|
||||
console.error(`Failed to bind test host bridge server to ${bindAddress}:`, err)
|
||||
process.exit(1)
|
||||
}
|
||||
server.start()
|
||||
console.log(`Test HostBridge gRPC server listening on ${bindAddress}`)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock gRPC service implementation using Proxy
|
||||
* @param serviceName Name of the service for logging
|
||||
* @returns A proxy that implements the service interface
|
||||
*/
|
||||
function createMockService<T extends grpc.UntypedServiceImplementation>(serviceName: string): T {
|
||||
const handler: ProxyHandler<T> = {
|
||||
get(_target, prop) {
|
||||
// Return a function that handles the gRPC call
|
||||
return (call: any, callback: any) => {
|
||||
console.log(`Hostbridge: ${serviceName}.${String(prop)} called with:`, call.request)
|
||||
|
||||
// Special cases that need specific return values
|
||||
switch (prop) {
|
||||
case "getWorkspacePaths":
|
||||
callback(null, {
|
||||
paths: ["/test-workspace"],
|
||||
})
|
||||
return
|
||||
|
||||
case "getMachineId":
|
||||
callback(null, {
|
||||
value: "fake-machine-id-" + os.hostname(),
|
||||
})
|
||||
return
|
||||
|
||||
case "clipboardReadText":
|
||||
callback(null, {
|
||||
value: "",
|
||||
})
|
||||
return
|
||||
|
||||
case "getWebviewHtml":
|
||||
callback(null, {
|
||||
html: "<html><body>Fake Webview</body></html>",
|
||||
})
|
||||
return
|
||||
|
||||
case "showTextDocument":
|
||||
callback(null, {
|
||||
document_path: call.request?.path || "",
|
||||
view_column: 1,
|
||||
is_active: true,
|
||||
})
|
||||
return
|
||||
|
||||
case "openDiff":
|
||||
callback(null, {
|
||||
diff_id: "fake-diff-" + Date.now(),
|
||||
})
|
||||
return
|
||||
|
||||
case "getDocumentText":
|
||||
callback(null, {
|
||||
content: "",
|
||||
})
|
||||
return
|
||||
|
||||
case "getOpenTabs":
|
||||
case "getVisibleTabs":
|
||||
case "showOpenDialogue":
|
||||
callback(null, {
|
||||
paths: [],
|
||||
})
|
||||
return
|
||||
|
||||
case "getDiagnostics":
|
||||
callback(null, {
|
||||
file_diagnostics: [],
|
||||
})
|
||||
return
|
||||
|
||||
// For streaming methods (like subscribeToFile)
|
||||
case "subscribeToFile":
|
||||
// Just end the stream immediately
|
||||
call.end()
|
||||
return
|
||||
}
|
||||
|
||||
// Default: return empty object for all other methods
|
||||
callback(null, {})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return new Proxy({} as T, handler)
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
startTestHostBridgeServer().catch((err) => {
|
||||
console.error("Failed to start test host bridge server:", err)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import * as vscode from "vscode"
|
||||
import {
|
||||
migrateCustomInstructionsToGlobalRules,
|
||||
migrateWelcomeViewCompleted,
|
||||
migrateWorkspaceToGlobalStorage,
|
||||
} from "./core/storage/state-migrations"
|
||||
import { WebviewProvider } from "./core/webview"
|
||||
import { Logger } from "./services/logging/Logger"
|
||||
import { PostHogClientProvider } from "./services/posthog/PostHogClientProvider"
|
||||
import { EmptyRequest } from "./shared/proto/cline/common"
|
||||
import { WebviewProviderType } from "./shared/webview/types"
|
||||
import "./utils/path" // necessary to have access to String.prototype.toPosix
|
||||
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { FileContextTracker } from "./core/context/context-tracking/FileContextTracker"
|
||||
import { telemetryService } from "./services/posthog/PostHogClientProvider"
|
||||
import { ShowMessageType } from "./shared/proto/host/window"
|
||||
import { getLatestAnnouncementId } from "./utils/announcements"
|
||||
/**
|
||||
* Performs intialization for Cline that is common to all platforms.
|
||||
*
|
||||
* @param context
|
||||
* @returns The webview provider
|
||||
*/
|
||||
export async function initialize(context: vscode.ExtensionContext): Promise<WebviewProvider> {
|
||||
// Initialize PostHog client provider
|
||||
let distinctId = context.globalState.get<string>("cline.distinctId")
|
||||
if (!distinctId) {
|
||||
try {
|
||||
const response = await HostProvider.env.getMachineId(EmptyRequest.create({}))
|
||||
distinctId = response.value
|
||||
} catch (e) {
|
||||
Logger.warn(`Failed to get machine ID: ${e instanceof Error ? e.message : String(e)}`)
|
||||
// PostHogProvider will fall back to uuid
|
||||
}
|
||||
}
|
||||
PostHogClientProvider.getInstance(distinctId)
|
||||
|
||||
// Migrate custom instructions to global Cline rules (one-time cleanup)
|
||||
await migrateCustomInstructionsToGlobalRules(context)
|
||||
|
||||
// Migrate welcomeViewCompleted setting based on existing API keys (one-time cleanup)
|
||||
await migrateWelcomeViewCompleted(context)
|
||||
|
||||
// Migrate workspace storage values back to global storage (reverting previous migration)
|
||||
await migrateWorkspaceToGlobalStorage(context)
|
||||
|
||||
// Clean up orphaned file context warnings (startup cleanup)
|
||||
await FileContextTracker.cleanupOrphanedWarnings(context)
|
||||
|
||||
const sidebarWebview = HostProvider.get().createWebviewProvider(WebviewProviderType.SIDEBAR)
|
||||
|
||||
await showVersionUpdateAnnouncement(context)
|
||||
|
||||
telemetryService.captureExtensionActivated()
|
||||
|
||||
return sidebarWebview
|
||||
}
|
||||
|
||||
async function showVersionUpdateAnnouncement(context: vscode.ExtensionContext) {
|
||||
// Version checking for autoupdate notification
|
||||
const currentVersion = context.extension.packageJSON.version
|
||||
const previousVersion = context.globalState.get<string>("clineVersion")
|
||||
// Perform post-update actions if necessary
|
||||
try {
|
||||
if (!previousVersion || currentVersion !== previousVersion) {
|
||||
Logger.log(`Cline version changed: ${previousVersion} -> ${currentVersion}. First run or update detected.`)
|
||||
|
||||
// Use the same condition as announcements: focus when there's a new announcement to show
|
||||
const lastShownAnnouncementId = context.globalState.get<string>("lastShownAnnouncementId")
|
||||
const latestAnnouncementId = getLatestAnnouncementId(context)
|
||||
|
||||
if (lastShownAnnouncementId !== latestAnnouncementId) {
|
||||
// Focus Cline when there's a new announcement to show (major/minor updates or fresh installs)
|
||||
const message = previousVersion
|
||||
? `Cline has been updated to v${currentVersion}`
|
||||
: `Welcome to Cline v${currentVersion}`
|
||||
await vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
}
|
||||
// Always update the main version tracker for the next launch.
|
||||
await context.globalState.update("clineVersion", currentVersion)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error(`Error during post-update actions: ${errorMessage}, Stack trace: ${error.stack}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs cleanup when Cline is deactivated that is common to all platforms.
|
||||
*/
|
||||
export async function tearDown(): Promise<void> {
|
||||
PostHogClientProvider.getInstance().dispose()
|
||||
|
||||
// Dispose all webview instances
|
||||
await WebviewProvider.disposeAllInstances()
|
||||
}
|
||||
@@ -1,43 +1,51 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiConfiguration, ModelInfo } from "../shared/api"
|
||||
import { ApiConfiguration, ModelInfo, QwenApiRegions } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { AnthropicHandler } from "./providers/anthropic"
|
||||
import { AwsBedrockHandler } from "./providers/bedrock"
|
||||
import { OpenRouterHandler } from "./providers/openrouter"
|
||||
import { VertexHandler } from "./providers/vertex"
|
||||
import { OpenAiHandler } from "./providers/openai"
|
||||
import { OllamaHandler } from "./providers/ollama"
|
||||
import { LmStudioHandler } from "./providers/lmstudio"
|
||||
import { GeminiHandler } from "./providers/gemini"
|
||||
import { OpenAiNativeHandler } from "./providers/openai-native"
|
||||
import { ApiStream, ApiStreamUsageChunk } from "./transform/stream"
|
||||
import { DeepSeekHandler } from "./providers/deepseek"
|
||||
import { RequestyHandler } from "./providers/requesty"
|
||||
import { TogetherHandler } from "./providers/together"
|
||||
import { NebiusHandler } from "./providers/nebius"
|
||||
import { QwenHandler } from "./providers/qwen"
|
||||
import { MistralHandler } from "./providers/mistral"
|
||||
import { DoubaoHandler } from "./providers/doubao"
|
||||
import { VsCodeLmHandler } from "./providers/vscode-lm"
|
||||
import { ClineHandler } from "./providers/cline"
|
||||
import { LiteLlmHandler } from "./providers/litellm"
|
||||
import { FireworksHandler } from "./providers/fireworks"
|
||||
import { AskSageHandler } from "./providers/asksage"
|
||||
import { XAIHandler } from "./providers/xai"
|
||||
import { SambanovaHandler } from "./providers/sambanova"
|
||||
import { BasetenHandler } from "./providers/baseten"
|
||||
import { AwsBedrockHandler } from "./providers/bedrock"
|
||||
import { CerebrasHandler } from "./providers/cerebras"
|
||||
import { SapAiCoreHandler } from "./providers/sapaicore"
|
||||
import { ClaudeCodeHandler } from "./providers/claude-code"
|
||||
import { MoonshotHandler } from "./providers/moonshot"
|
||||
import { ClineHandler } from "./providers/cline"
|
||||
import { DeepSeekHandler } from "./providers/deepseek"
|
||||
import { DoubaoHandler } from "./providers/doubao"
|
||||
import { FireworksHandler } from "./providers/fireworks"
|
||||
import { GeminiHandler } from "./providers/gemini"
|
||||
import { GroqHandler } from "./providers/groq"
|
||||
import { Mode } from "../shared/ChatSettings"
|
||||
import { HuaweiCloudMaaSHandler } from "./providers/huawei-cloud-maas"
|
||||
import { HuggingFaceHandler } from "./providers/huggingface"
|
||||
import { LiteLlmHandler } from "./providers/litellm"
|
||||
import { LmStudioHandler } from "./providers/lmstudio"
|
||||
import { MistralHandler } from "./providers/mistral"
|
||||
import { MoonshotHandler } from "./providers/moonshot"
|
||||
import { NebiusHandler } from "./providers/nebius"
|
||||
import { OllamaHandler } from "./providers/ollama"
|
||||
import { OpenAiHandler } from "./providers/openai"
|
||||
import { OpenAiNativeHandler } from "./providers/openai-native"
|
||||
import { OpenRouterHandler } from "./providers/openrouter"
|
||||
import { QwenHandler } from "./providers/qwen"
|
||||
import { RequestyHandler } from "./providers/requesty"
|
||||
import { SambanovaHandler } from "./providers/sambanova"
|
||||
import { SapAiCoreHandler } from "./providers/sapaicore"
|
||||
import { TogetherHandler } from "./providers/together"
|
||||
import { VertexHandler } from "./providers/vertex"
|
||||
import { VsCodeLmHandler } from "./providers/vscode-lm"
|
||||
import { XAIHandler } from "./providers/xai"
|
||||
import { ZAiHandler } from "./providers/zai"
|
||||
import { ApiStream, ApiStreamUsageChunk } from "./transform/stream"
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
|
||||
getModel(): { id: string; info: ModelInfo }
|
||||
getModel(): ApiHandlerModel
|
||||
getApiStreamUsage?(): Promise<ApiStreamUsageChunk | undefined>
|
||||
}
|
||||
|
||||
export interface ApiHandlerModel {
|
||||
id: string
|
||||
info: ModelInfo
|
||||
}
|
||||
|
||||
export interface SingleCompletionHandler {
|
||||
completePrompt(prompt: string): Promise<string>
|
||||
}
|
||||
@@ -96,7 +104,7 @@ function createHandlerForProvider(
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
geminiApiKey: options.geminiApiKey,
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
taskId: options.taskId,
|
||||
ulid: options.ulid,
|
||||
})
|
||||
case "openai":
|
||||
return new OpenAiHandler({
|
||||
@@ -111,6 +119,7 @@ function createHandlerForProvider(
|
||||
case "ollama":
|
||||
return new OllamaHandler({
|
||||
ollamaBaseUrl: options.ollamaBaseUrl,
|
||||
ollamaApiKey: options.ollamaApiKey,
|
||||
ollamaModelId: mode === "plan" ? options.planModeOllamaModelId : options.actModeOllamaModelId,
|
||||
ollamaApiOptionsCtxNum: options.ollamaApiOptionsCtxNum,
|
||||
requestTimeoutMs: options.requestTimeoutMs,
|
||||
@@ -119,6 +128,7 @@ function createHandlerForProvider(
|
||||
return new LmStudioHandler({
|
||||
lmStudioBaseUrl: options.lmStudioBaseUrl,
|
||||
lmStudioModelId: mode === "plan" ? options.planModeLmStudioModelId : options.actModeLmStudioModelId,
|
||||
lmStudioMaxTokens: options.lmStudioMaxTokens,
|
||||
})
|
||||
case "gemini":
|
||||
return new GeminiHandler({
|
||||
@@ -129,7 +139,7 @@ function createHandlerForProvider(
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
taskId: options.taskId,
|
||||
ulid: options.ulid,
|
||||
})
|
||||
case "openai-native":
|
||||
return new OpenAiNativeHandler({
|
||||
@@ -144,6 +154,7 @@ function createHandlerForProvider(
|
||||
})
|
||||
case "requesty":
|
||||
return new RequestyHandler({
|
||||
requestyBaseUrl: options.requestyBaseUrl,
|
||||
requestyApiKey: options.requestyApiKey,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
@@ -166,7 +177,8 @@ function createHandlerForProvider(
|
||||
case "qwen":
|
||||
return new QwenHandler({
|
||||
qwenApiKey: options.qwenApiKey,
|
||||
qwenApiLine: options.qwenApiLine,
|
||||
qwenApiLine:
|
||||
options.qwenApiLine === QwenApiRegions.INTERNATIONAL ? QwenApiRegions.INTERNATIONAL : QwenApiRegions.CHINA,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
@@ -189,7 +201,7 @@ function createHandlerForProvider(
|
||||
case "cline":
|
||||
return new ClineHandler({
|
||||
clineAccountId: options.clineAccountId,
|
||||
taskId: options.taskId,
|
||||
ulid: options.ulid,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
@@ -206,7 +218,7 @@ function createHandlerForProvider(
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
liteLlmUsePromptCache: options.liteLlmUsePromptCache,
|
||||
taskId: options.taskId,
|
||||
ulid: options.ulid,
|
||||
})
|
||||
case "moonshot":
|
||||
return new MoonshotHandler({
|
||||
@@ -255,6 +267,13 @@ function createHandlerForProvider(
|
||||
groqModelInfo: mode === "plan" ? options.planModeGroqModelInfo : options.actModeGroqModelInfo,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
})
|
||||
case "baseten":
|
||||
return new BasetenHandler({
|
||||
basetenApiKey: options.basetenApiKey,
|
||||
basetenModelId: mode === "plan" ? options.planModeBasetenModelId : options.actModeBasetenModelId,
|
||||
basetenModelInfo: mode === "plan" ? options.planModeBasetenModelInfo : options.actModeBasetenModelInfo,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
})
|
||||
case "sapaicore":
|
||||
return new SapAiCoreHandler({
|
||||
sapAiCoreClientId: options.sapAiCoreClientId,
|
||||
@@ -263,6 +282,8 @@ function createHandlerForProvider(
|
||||
sapAiResourceGroup: options.sapAiResourceGroup,
|
||||
sapAiCoreBaseUrl: options.sapAiCoreBaseUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
})
|
||||
case "claude-code":
|
||||
return new ClaudeCodeHandler({
|
||||
@@ -271,6 +292,20 @@ function createHandlerForProvider(
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
})
|
||||
case "huawei-cloud-maas":
|
||||
return new HuaweiCloudMaaSHandler({
|
||||
huaweiCloudMaasApiKey: options.huaweiCloudMaasApiKey,
|
||||
huaweiCloudMaasModelId:
|
||||
mode === "plan" ? options.planModeHuaweiCloudMaasModelId : options.actModeHuaweiCloudMaasModelId,
|
||||
huaweiCloudMaasModelInfo:
|
||||
mode === "plan" ? options.planModeHuaweiCloudMaasModelInfo : options.actModeHuaweiCloudMaasModelInfo,
|
||||
})
|
||||
case "zai":
|
||||
return new ZAiHandler({
|
||||
zaiApiLine: options.zaiApiLine,
|
||||
zaiApiKey: options.zaiApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
})
|
||||
default:
|
||||
return new AnthropicHandler({
|
||||
apiKey: options.apiKey,
|
||||
+87
-86
@@ -1,8 +1,8 @@
|
||||
import "should"
|
||||
import { AwsBedrockHandler } from "../bedrock"
|
||||
import { ApiHandlerOptions } from "@shared/api"
|
||||
import { ConverseStreamCommand } from "@aws-sdk/client-bedrock-runtime"
|
||||
import { ApiHandlerOptions } from "@shared/api"
|
||||
import { Readable } from "stream"
|
||||
import { AwsBedrockHandler } from "../bedrock"
|
||||
|
||||
describe("AwsBedrockHandler", () => {
|
||||
// Helper function to create a mock stream
|
||||
@@ -612,101 +612,102 @@ describe("AwsBedrockHandler", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("getModelId", () => {
|
||||
it("should return raw model ID for custom models", async () => {
|
||||
const customOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId:
|
||||
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
}
|
||||
const customHandler = new AwsBedrockHandler(customOptions)
|
||||
// TODO: Re-enable or remove these tests.
|
||||
// describe("getModelId", () => {
|
||||
// it("should return raw model ID for custom models", async () => {
|
||||
// const customOptions: ApiHandlerOptions = {
|
||||
// ...mockOptions,
|
||||
// actModeAwsBedrockCustomSelected: true,
|
||||
// actModeApiModelId:
|
||||
// "arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
// }
|
||||
// const customHandler = new AwsBedrockHandler(customOptions)
|
||||
|
||||
const modelId = await customHandler.getModelId()
|
||||
modelId.should.equal(
|
||||
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
)
|
||||
})
|
||||
// const modelId = await customHandler.getModelId()
|
||||
// modelId.should.equal(
|
||||
// "arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
// )
|
||||
// })
|
||||
|
||||
it("should not encode custom model IDs with slashes", async () => {
|
||||
const customOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId: "my-namespace/my-custom-model",
|
||||
}
|
||||
const customHandler = new AwsBedrockHandler(customOptions)
|
||||
// it("should not encode custom model IDs with slashes", async () => {
|
||||
// const customOptions: ApiHandlerOptions = {
|
||||
// ...mockOptions,
|
||||
// actModeAwsBedrockCustomSelected: true,
|
||||
// actModeApiModelId: "my-namespace/my-custom-model",
|
||||
// }
|
||||
// const customHandler = new AwsBedrockHandler(customOptions)
|
||||
|
||||
const modelId = await customHandler.getModelId()
|
||||
modelId.should.equal("my-namespace/my-custom-model")
|
||||
modelId.should.not.match(/%2F/)
|
||||
})
|
||||
// const modelId = await customHandler.getModelId()
|
||||
// modelId.should.equal("my-namespace/my-custom-model")
|
||||
// modelId.should.not.match(/%2F/)
|
||||
// })
|
||||
|
||||
it("should apply cross-region prefix for non-custom models when enabled", async () => {
|
||||
const crossRegionOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
awsUseCrossRegionInference: true,
|
||||
awsRegion: "us-west-2",
|
||||
}
|
||||
const crossRegionHandler = new AwsBedrockHandler(crossRegionOptions)
|
||||
// it("should apply cross-region prefix for non-custom models when enabled", async () => {
|
||||
// const crossRegionOptions: ApiHandlerOptions = {
|
||||
// ...mockOptions,
|
||||
// awsUseCrossRegionInference: true,
|
||||
// awsRegion: "us-west-2",
|
||||
// }
|
||||
// const crossRegionHandler = new AwsBedrockHandler(crossRegionOptions)
|
||||
|
||||
const modelId = await crossRegionHandler.getModelId()
|
||||
modelId.should.equal("us.anthropic.claude-3-7-sonnet-20250219-v1:0")
|
||||
})
|
||||
// const modelId = await crossRegionHandler.getModelId()
|
||||
// modelId.should.equal("us.anthropic.claude-3-7-sonnet-20250219-v1:0")
|
||||
// })
|
||||
|
||||
it("should apply EU cross-region prefix", async () => {
|
||||
const euOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
awsUseCrossRegionInference: true,
|
||||
awsRegion: "eu-central-1",
|
||||
}
|
||||
const euHandler = new AwsBedrockHandler(euOptions)
|
||||
// it("should apply EU cross-region prefix", async () => {
|
||||
// const euOptions: ApiHandlerOptions = {
|
||||
// ...mockOptions,
|
||||
// awsUseCrossRegionInference: true,
|
||||
// awsRegion: "eu-central-1",
|
||||
// }
|
||||
// const euHandler = new AwsBedrockHandler(euOptions)
|
||||
|
||||
const modelId = await euHandler.getModelId()
|
||||
modelId.should.equal("eu.anthropic.claude-3-7-sonnet-20250219-v1:0")
|
||||
})
|
||||
// const modelId = await euHandler.getModelId()
|
||||
// modelId.should.equal("eu.anthropic.claude-3-7-sonnet-20250219-v1:0")
|
||||
// })
|
||||
|
||||
it("should apply APAC cross-region prefix", async () => {
|
||||
const apacOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
awsUseCrossRegionInference: true,
|
||||
awsRegion: "ap-northeast-1",
|
||||
}
|
||||
const apacHandler = new AwsBedrockHandler(apacOptions)
|
||||
// it("should apply APAC cross-region prefix", async () => {
|
||||
// const apacOptions: ApiHandlerOptions = {
|
||||
// ...mockOptions,
|
||||
// awsUseCrossRegionInference: true,
|
||||
// awsRegion: "ap-northeast-1",
|
||||
// }
|
||||
// const apacHandler = new AwsBedrockHandler(apacOptions)
|
||||
|
||||
const modelId = await apacHandler.getModelId()
|
||||
modelId.should.equal("apac.anthropic.claude-3-7-sonnet-20250219-v1:0")
|
||||
})
|
||||
// const modelId = await apacHandler.getModelId()
|
||||
// modelId.should.equal("apac.anthropic.claude-3-7-sonnet-20250219-v1:0")
|
||||
// })
|
||||
|
||||
it("should not apply cross-region prefix for custom models even when enabled", async () => {
|
||||
const customCrossRegionOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
|
||||
awsUseCrossRegionInference: true,
|
||||
}
|
||||
const customCrossRegionHandler = new AwsBedrockHandler(customCrossRegionOptions)
|
||||
// it("should not apply cross-region prefix for custom models even when enabled", async () => {
|
||||
// const customCrossRegionOptions: ApiHandlerOptions = {
|
||||
// ...mockOptions,
|
||||
// actModeAwsBedrockCustomSelected: true,
|
||||
// actModeApiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
|
||||
// awsUseCrossRegionInference: true,
|
||||
// }
|
||||
// const customCrossRegionHandler = new AwsBedrockHandler(customCrossRegionOptions)
|
||||
|
||||
const modelId = await customCrossRegionHandler.getModelId()
|
||||
modelId.should.equal("arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model")
|
||||
})
|
||||
// const modelId = await customCrossRegionHandler.getModelId()
|
||||
// modelId.should.equal("arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model")
|
||||
// })
|
||||
|
||||
it("should handle UltraThink model ARN correctly", async () => {
|
||||
const ultraThinkOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId:
|
||||
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
actModeAwsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
}
|
||||
const ultraThinkHandler = new AwsBedrockHandler(ultraThinkOptions)
|
||||
// it("should handle UltraThink model ARN correctly", async () => {
|
||||
// const ultraThinkOptions: ApiHandlerOptions = {
|
||||
// ...mockOptions,
|
||||
// actModeAwsBedrockCustomSelected: true,
|
||||
// actModeApiModelId:
|
||||
// "arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
// actModeAwsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
// }
|
||||
// const ultraThinkHandler = new AwsBedrockHandler(ultraThinkOptions)
|
||||
|
||||
const modelId = await ultraThinkHandler.getModelId()
|
||||
// Should return the raw ARN without any encoding
|
||||
modelId.should.equal(
|
||||
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
)
|
||||
modelId.should.not.match(/%2F/)
|
||||
modelId.should.not.match(/%3A/)
|
||||
})
|
||||
})
|
||||
// const modelId = await ultraThinkHandler.getModelId()
|
||||
// // Should return the raw ARN without any encoding
|
||||
// modelId.should.equal(
|
||||
// "arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
// )
|
||||
// modelId.should.not.match(/%2F/)
|
||||
// modelId.should.not.match(/%3A/)
|
||||
// })
|
||||
// })
|
||||
})
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
import { describe, it, beforeEach, afterEach, before } from "mocha"
|
||||
import { afterEach, before, beforeEach, describe, it } from "mocha"
|
||||
import "should"
|
||||
import sinon from "sinon"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { OllamaHandler } from "../ollama"
|
||||
import { ApiHandlerOptions } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import sinon from "sinon"
|
||||
import { OllamaHandler } from "../ollama"
|
||||
|
||||
describe("OllamaHandler", () => {
|
||||
let ollamaAvailable = false
|
||||
@@ -15,7 +15,7 @@ describe("OllamaHandler", () => {
|
||||
try {
|
||||
await axios.get("http://localhost:11434/api/version", { timeout: 2000 })
|
||||
ollamaAvailable = true
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
console.log("Ollama server not available, skipping tests")
|
||||
ollamaAvailable = false
|
||||
}
|
||||
@@ -94,7 +94,7 @@ describe("OllamaHandler", () => {
|
||||
const testHandler = new OllamaHandler(options)
|
||||
|
||||
// Replace the createMessage method with one that has a shorter timeout
|
||||
testHandler.createMessage = async function* (systemPrompt, messages) {
|
||||
testHandler.createMessage = async function* (_systemPrompt, _messages) {
|
||||
try {
|
||||
// Create a promise that rejects after a short timeout
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
@@ -192,7 +192,7 @@ describe("OllamaHandler", () => {
|
||||
const testHandler = new OllamaHandler(options)
|
||||
|
||||
// Replace the createMessage method with one that simulates a stream error
|
||||
testHandler.createMessage = async function* (systemPrompt, messages) {
|
||||
testHandler.createMessage = async function* (_systemPrompt, _messages) {
|
||||
// First yield a successful chunk
|
||||
yield {
|
||||
type: "text",
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { withRetry } from "../retry"
|
||||
import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "@shared/api"
|
||||
import { AnthropicModelId, anthropicDefaultModelId, anthropicModels, CLAUDE_SONNET_4_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface AnthropicHandlerOptions {
|
||||
@@ -43,10 +43,14 @@ export class AnthropicHandler implements ApiHandler {
|
||||
|
||||
const model = this.getModel()
|
||||
let stream: AnthropicStream<Anthropic.RawMessageStreamEvent>
|
||||
const modelId = model.id
|
||||
|
||||
const modelId = model.id.endsWith(CLAUDE_SONNET_4_1M_SUFFIX)
|
||||
? model.id.slice(0, -CLAUDE_SONNET_4_1M_SUFFIX.length)
|
||||
: model.id
|
||||
const enable1mContextWindow = model.id.endsWith(CLAUDE_SONNET_4_1M_SUFFIX)
|
||||
|
||||
const budget_tokens = this.options.thinkingBudgetTokens || 0
|
||||
const reasoningOn = (modelId.includes("3-7") || modelId.includes("4-")) && budget_tokens !== 0 ? true : false
|
||||
const reasoningOn = !!((modelId.includes("3-7") || modelId.includes("4-")) && budget_tokens !== 0)
|
||||
|
||||
switch (modelId) {
|
||||
// 'latest' alias does not support cache_control
|
||||
@@ -55,6 +59,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
case "claude-3-5-sonnet-20241022":
|
||||
case "claude-3-5-haiku-20241022":
|
||||
case "claude-opus-4-20250514":
|
||||
case "claude-opus-4-1-20250805":
|
||||
case "claude-3-opus-20240229":
|
||||
case "claude-3-haiku-20240307": {
|
||||
/*
|
||||
@@ -116,24 +121,15 @@ export class AnthropicHandler implements ApiHandler {
|
||||
stream: true,
|
||||
},
|
||||
(() => {
|
||||
// prompt caching: https://x.com/alexalbert__/status/1823751995901272068
|
||||
// https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
|
||||
// https://github.com/anthropics/anthropic-sdk-typescript/commit/c920b77fc67bd839bfeb6716ceab9d7c9bbe7393
|
||||
switch (modelId) {
|
||||
case "claude-sonnet-4-20250514":
|
||||
case "claude-opus-4-20250514":
|
||||
case "claude-3-7-sonnet-20250219":
|
||||
case "claude-3-5-sonnet-20241022":
|
||||
case "claude-3-5-haiku-20241022":
|
||||
case "claude-3-opus-20240229":
|
||||
case "claude-3-haiku-20240307":
|
||||
return {
|
||||
headers: {
|
||||
"anthropic-beta": "prompt-caching-2024-07-31",
|
||||
},
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
// 1m context window beta header
|
||||
if (enable1mContextWindow) {
|
||||
return {
|
||||
headers: {
|
||||
"anthropic-beta": "context-1m-2025-08-07",
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
})(),
|
||||
)
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { AskSageModelId, askSageDefaultModelId, askSageDefaultURL, askSageModels, ModelInfo } from "@shared/api"
|
||||
import { ApiHandler } from ".."
|
||||
import { ModelInfo, AskSageModelId, askSageModels, askSageDefaultModelId, askSageDefaultURL } from "@shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface AskSageHandlerOptions {
|
||||
asksageApiKey?: string
|
||||
@@ -0,0 +1,165 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { BasetenModelId, basetenDefaultModelId, basetenModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface BasetenHandlerOptions {
|
||||
basetenApiKey?: string
|
||||
basetenModelId?: string
|
||||
basetenModelInfo?: ModelInfo
|
||||
apiModelId?: string // For backward compatibility
|
||||
}
|
||||
|
||||
export class BasetenHandler implements ApiHandler {
|
||||
private options: BasetenHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: BasetenHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
private ensureClient(): OpenAI {
|
||||
if (!this.client) {
|
||||
if (!this.options.basetenApiKey) {
|
||||
throw new Error("Baseten API key is required")
|
||||
}
|
||||
try {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://inference.baseten.co/v1",
|
||||
apiKey: this.options.basetenApiKey,
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Baseten client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the optimal max_tokens based on model capabilities
|
||||
*/
|
||||
private getOptimalMaxTokens(model: { id: BasetenModelId; info: ModelInfo }): number {
|
||||
// Use model-specific max tokens if available
|
||||
if (model.info.maxTokens && model.info.maxTokens > 0) {
|
||||
return model.info.maxTokens
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return 8192
|
||||
}
|
||||
|
||||
getModel(): { id: BasetenModelId; info: ModelInfo } {
|
||||
// First priority: basetenModelId and basetenModelInfo
|
||||
const basetenModelId = this.options.basetenModelId
|
||||
const basetenModelInfo = this.options.basetenModelInfo
|
||||
if (basetenModelId && basetenModelInfo) {
|
||||
return { id: basetenModelId as BasetenModelId, info: basetenModelInfo }
|
||||
}
|
||||
|
||||
// Second priority: basetenModelId with static model info
|
||||
if (basetenModelId && basetenModelId in basetenModels) {
|
||||
const id = basetenModelId as BasetenModelId
|
||||
return { id, info: basetenModels[id] }
|
||||
}
|
||||
|
||||
// Third priority: apiModelId (for backward compatibility)
|
||||
const apiModelId = this.options.apiModelId
|
||||
if (apiModelId && apiModelId in basetenModels) {
|
||||
const id = apiModelId as BasetenModelId
|
||||
return { id, info: basetenModels[id] }
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return {
|
||||
id: basetenDefaultModelId,
|
||||
info: basetenModels[basetenDefaultModelId],
|
||||
}
|
||||
}
|
||||
|
||||
private async *yieldUsage(modelInfo: ModelInfo, usage: any): ApiStream {
|
||||
if (usage.prompt_tokens || usage.completion_tokens) {
|
||||
const cost = calculateApiCostOpenAI(modelInfo, usage.prompt_tokens || 0, usage.completion_tokens || 0)
|
||||
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: usage.prompt_tokens || 0,
|
||||
outputTokens: usage.completion_tokens || 0,
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: 0,
|
||||
totalCost: cost,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const maxTokens = this.getOptimalMaxTokens(model)
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
max_tokens: maxTokens,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
})
|
||||
|
||||
let didOutputUsage = false
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
// Handle reasoning field if present (for reasoning models with parsed output)
|
||||
if ((delta as any)?.reasoning) {
|
||||
const reasoningContent = (delta as any).reasoning as string
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: reasoningContent,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle content field
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle usage information - only output once
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
didOutputUsage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current model supports vision/images
|
||||
*/
|
||||
supportsImages(): boolean {
|
||||
const model = this.getModel()
|
||||
return model.info.supportsImages === true
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current model supports tools
|
||||
*/
|
||||
supportsTools(): boolean {
|
||||
const _model = this.getModel()
|
||||
// Baseten models support tools via OpenAI-compatible API
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
// Import proper AWS SDK types
|
||||
import type { ContentBlock, Message } from "@aws-sdk/client-bedrock-runtime"
|
||||
import {
|
||||
BedrockRuntimeClient,
|
||||
ConversationRole,
|
||||
ConverseCommand,
|
||||
ConverseStreamCommand,
|
||||
InvokeModelWithResponseStreamCommand,
|
||||
} from "@aws-sdk/client-bedrock-runtime"
|
||||
|
||||
// Import proper AWS SDK types
|
||||
import type { Message, ContentBlock } from "@aws-sdk/client-bedrock-runtime"
|
||||
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import { BedrockModelId, bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_4_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import { ApiHandler } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface AwsBedrockHandlerOptions {
|
||||
apiModelId?: string
|
||||
@@ -117,7 +117,14 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
@withRetry({ maxRetries: 4 })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
// cross region inference requires prefixing the model id with the region
|
||||
const modelId = await this.getModelId()
|
||||
const rawModelId = await this.getModelId()
|
||||
|
||||
const modelId = rawModelId.endsWith(CLAUDE_SONNET_4_1M_SUFFIX)
|
||||
? rawModelId.slice(0, -CLAUDE_SONNET_4_1M_SUFFIX.length)
|
||||
: rawModelId
|
||||
|
||||
const enable1mContextWindow = rawModelId.endsWith(CLAUDE_SONNET_4_1M_SUFFIX)
|
||||
|
||||
const model = this.getModel()
|
||||
|
||||
// This baseModelId is used to indicate the capabilities of the model.
|
||||
@@ -132,6 +139,11 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
return
|
||||
}
|
||||
|
||||
if (baseModelId.includes("openai")) {
|
||||
yield* this.createOpenAIMessage(systemPrompt, messages, modelId, model)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this is a Deepseek model
|
||||
if (baseModelId.includes("deepseek")) {
|
||||
yield* this.createDeepseekMessage(systemPrompt, messages, modelId, model)
|
||||
@@ -139,7 +151,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Default: Use Anthropic Converse API for all Anthropic models
|
||||
yield* this.createAnthropicMessage(systemPrompt, messages, modelId, model)
|
||||
yield* this.createAnthropicMessage(systemPrompt, messages, modelId, model, enable1mContextWindow)
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
@@ -743,6 +755,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
enable1mContextWindow: boolean,
|
||||
): ApiStream {
|
||||
// Format messages for Anthropic model using unified formatter
|
||||
const formattedMessages = this.formatMessagesForConverseAPI(messages)
|
||||
@@ -773,15 +786,18 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
messages: messagesWithCache,
|
||||
system: systemMessages,
|
||||
inferenceConfig: this.getInferenceConfig(model.info, "anthropic"),
|
||||
// Add thinking configuration as per LangChain documentation
|
||||
additionalModelRequestFields: reasoningOn
|
||||
? {
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budget_tokens: budget_tokens,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
additionalModelRequestFields: {
|
||||
// Add thinking configuration as per LangChain documentation
|
||||
...(reasoningOn && {
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budget_tokens: budget_tokens,
|
||||
},
|
||||
}),
|
||||
...(enable1mContextWindow && {
|
||||
anthropic_beta: ["context-1m-2025-08-07"],
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
// Execute the streaming request using unified handler
|
||||
@@ -958,4 +974,139 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
// Execute the streaming request using unified handler
|
||||
yield* this.executeConverseStream(command, model.info)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message using OpenAI models through AWS Bedrock
|
||||
* Uses non-streaming Converse API and simulates streaming for models that don't support it
|
||||
*/
|
||||
private async *createOpenAIMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
// Get Bedrock client with proper credentials
|
||||
const client = await this.getBedrockClient()
|
||||
|
||||
// Format messages for Converse API
|
||||
const formattedMessages = this.formatMessagesForConverseAPI(messages)
|
||||
|
||||
// Prepare system message
|
||||
const systemMessages = systemPrompt ? [{ text: systemPrompt }] : undefined
|
||||
|
||||
// Prepare the non-streaming Converse command
|
||||
const command = new ConverseCommand({
|
||||
modelId: modelId,
|
||||
messages: formattedMessages,
|
||||
system: systemMessages,
|
||||
inferenceConfig: {
|
||||
maxTokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
// Track token usage
|
||||
const inputTokenEstimate = this.estimateInputTokens(systemPrompt, messages)
|
||||
let outputTokens = 0
|
||||
|
||||
// Execute the non-streaming request
|
||||
const response = await client.send(command)
|
||||
|
||||
// Extract the complete response text and reasoning content
|
||||
let fullText = ""
|
||||
let reasoningText = ""
|
||||
|
||||
if (response.output?.message?.content) {
|
||||
for (const contentBlock of response.output.message.content) {
|
||||
// Check for reasoning content first
|
||||
if ("reasoningContent" in contentBlock && contentBlock.reasoningContent) {
|
||||
// Handle nested reasoning structure
|
||||
const reasoning = contentBlock.reasoningContent
|
||||
if ("reasoningText" in reasoning && reasoning.reasoningText && "text" in reasoning.reasoningText) {
|
||||
reasoningText += reasoning.reasoningText.text
|
||||
}
|
||||
}
|
||||
// Handle regular text content
|
||||
else if ("text" in contentBlock && contentBlock.text) {
|
||||
fullText += contentBlock.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have actual usage data from the response, use it
|
||||
if (response.usage) {
|
||||
const actualInputTokens = response.usage.inputTokens || inputTokenEstimate
|
||||
const actualOutputTokens = response.usage.outputTokens || this.estimateTokenCount(fullText + reasoningText)
|
||||
outputTokens = actualOutputTokens
|
||||
|
||||
// Report actual usage after processing content
|
||||
const actualCost = calculateApiCostOpenAI(model.info, actualInputTokens, actualOutputTokens, 0, 0)
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: actualInputTokens,
|
||||
outputTokens: actualOutputTokens,
|
||||
totalCost: actualCost,
|
||||
}
|
||||
} else {
|
||||
// Estimate output tokens if not provided (includes both regular text and reasoning)
|
||||
outputTokens = this.estimateTokenCount(fullText + reasoningText)
|
||||
}
|
||||
|
||||
// Yield reasoning content first if present
|
||||
if (reasoningText) {
|
||||
const reasoningChunkSize = 1000 // Characters per chunk
|
||||
for (let i = 0; i < reasoningText.length; i += reasoningChunkSize) {
|
||||
const chunk = reasoningText.slice(i, Math.min(i + reasoningChunkSize, reasoningText.length))
|
||||
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate streaming by chunking the response text
|
||||
if (fullText) {
|
||||
const chunkSize = 1000 // Characters per chunk
|
||||
|
||||
for (let i = 0; i < fullText.length; i += chunkSize) {
|
||||
const chunk = fullText.slice(i, Math.min(i + chunkSize, fullText.length))
|
||||
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report final usage if we didn't have actual usage data earlier
|
||||
if (!response.usage) {
|
||||
const finalCost = calculateApiCostOpenAI(model.info, inputTokenEstimate, outputTokens, 0, 0)
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: inputTokenEstimate,
|
||||
outputTokens: outputTokens,
|
||||
totalCost: finalCost,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error with OpenAI model via Converse API:", error)
|
||||
|
||||
// Try to extract more detailed error information
|
||||
let errorMessage = "Failed to process OpenAI model request"
|
||||
if (error instanceof Error) {
|
||||
errorMessage = error.message
|
||||
// Check for specific AWS SDK errors
|
||||
if ("name" in error) {
|
||||
errorMessage = `${error.name}: ${error.message}`
|
||||
}
|
||||
}
|
||||
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] ${errorMessage}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import Cerebras from "@cerebras/cerebras_cloud_sdk"
|
||||
import { withRetry } from "../retry"
|
||||
import { ModelInfo, CerebrasModelId, cerebrasDefaultModelId, cerebrasModels } from "@shared/api"
|
||||
import { CerebrasModelId, cerebrasDefaultModelId, cerebrasModels, ModelInfo } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface CerebrasHandlerOptions {
|
||||
cerebrasApiKey?: string
|
||||
@@ -39,7 +39,11 @@ export class CerebrasHandler implements ApiHandler {
|
||||
return this.client
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
@withRetry({
|
||||
maxRetries: 6, // More retries to be patient with rate limits
|
||||
baseDelay: 5000, // Start with 5 second delay
|
||||
maxDelay: 60000, // Allow up to 60 second delays to respect rate limits
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
@@ -102,6 +106,7 @@ export class CerebrasHandler implements ApiHandler {
|
||||
messages: cerebrasMessages,
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
max_tokens: this.getModel().info.maxTokens,
|
||||
})
|
||||
|
||||
// Handle streaming response
|
||||
@@ -121,7 +126,7 @@ export class CerebrasHandler implements ApiHandler {
|
||||
reasoning = (reasoning || "") + content
|
||||
|
||||
// Clean the content by removing think tags for display
|
||||
let cleanContent = content.replace(/<think>/g, "").replace(/<\/think>/g, "")
|
||||
const cleanContent = content.replace(/<think>/g, "").replace(/<\/think>/g, "")
|
||||
|
||||
// Only yield reasoning content if there's actual content after cleaning
|
||||
if (cleanContent.trim()) {
|
||||
@@ -169,15 +174,39 @@ export class CerebrasHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
// Enhanced error handling for Cerebras API
|
||||
if (error?.status === 429 || error?.code === "rate_limit_exceeded") {
|
||||
// Rate limit error - will be handled by retry decorator with patient backoff
|
||||
const _limits = this.getRateLimits()
|
||||
throw new Error(`Cerebras API rate limit exceeded.`)
|
||||
} else if (error?.status === 401) {
|
||||
throw new Error("Cerebras API authentication failed. Please check your API key.")
|
||||
} else if (error?.status === 403) {
|
||||
throw new Error("Cerebras API access denied. Please check your API key permissions.")
|
||||
} else if (error?.status >= 500) {
|
||||
// Server errors - retryable
|
||||
throw new Error(`Cerebras API server error (${error.status}): ${error.message || "Unknown server error"}`)
|
||||
} else if (error?.status === 400) {
|
||||
// Client errors - not retryable
|
||||
throw new Error(`Cerebras API bad request: ${error.message || "Invalid request parameters"}`)
|
||||
}
|
||||
|
||||
// Re-throw original error for other cases
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in cerebrasModels) {
|
||||
const id = modelId as CerebrasModelId
|
||||
const originalModelId = this.options.apiModelId
|
||||
let apiModelId = originalModelId
|
||||
if (originalModelId === "qwen-3-coder-480b-free") {
|
||||
apiModelId = "qwen-3-coder-480b"
|
||||
return { id: apiModelId, info: cerebrasModels[originalModelId as CerebrasModelId] }
|
||||
}
|
||||
|
||||
if (originalModelId && originalModelId in cerebrasModels) {
|
||||
const id = originalModelId as CerebrasModelId
|
||||
return { id, info: cerebrasModels[id] }
|
||||
}
|
||||
return {
|
||||
@@ -186,6 +215,35 @@ export class CerebrasHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rate limit information for the current model
|
||||
*
|
||||
* These limits are used for informational purposes and to calculate appropriate
|
||||
* retry delays. Since Cerebras inference is extremely fast, users hit these limits
|
||||
* quickly, so we need to be patient with retries to maximize usage efficiency.
|
||||
*
|
||||
* @returns Rate limit configuration for the model
|
||||
*/
|
||||
private getRateLimits(): { requestsPerMinute: number; tokensPerMinute: number } {
|
||||
const modelId = this.getModel().id
|
||||
|
||||
switch (modelId) {
|
||||
case "qwen-3-coder-480b":
|
||||
case "qwen-3-coder-480b-free":
|
||||
return { requestsPerMinute: 10, tokensPerMinute: 150_000 }
|
||||
case "qwen-3-235b-a22b-instruct-2507":
|
||||
case "qwen-3-235b-a22b-thinking-2507":
|
||||
return { requestsPerMinute: 30, tokensPerMinute: 60_000 }
|
||||
case "llama-3.3-70b":
|
||||
case "gpt-oss-120b":
|
||||
case "qwen-3-32b":
|
||||
return { requestsPerMinute: 30, tokensPerMinute: 64_000 }
|
||||
default:
|
||||
// Default rate limits for unknown models
|
||||
return { requestsPerMinute: 30, tokensPerMinute: 60_000 }
|
||||
}
|
||||
}
|
||||
|
||||
private calculateCost({ inputTokens, outputTokens }: { inputTokens: number; outputTokens: number }): number {
|
||||
const model = this.getModel()
|
||||
const inputPrice = model.info.inputPrice || 0
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { claudeCodeDefaultModelId, ClaudeCodeModelId, claudeCodeModels } from "@/shared/api"
|
||||
import { type ApiHandler } from ".."
|
||||
import { ApiStreamUsageChunk, type ApiStream } from "../transform/stream"
|
||||
import { withRetry } from "../retry"
|
||||
import { runClaudeCode } from "@/integrations/claude-code/run"
|
||||
import { filterMessagesForClaudeCode } from "@/integrations/claude-code/message-filter"
|
||||
import { runClaudeCode } from "@/integrations/claude-code/run"
|
||||
import { ClaudeCodeModelId, claudeCodeDefaultModelId, claudeCodeModels } from "@/shared/api"
|
||||
import { type ApiHandler } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { type ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
|
||||
interface ClaudeCodeHandlerOptions {
|
||||
claudeCodePath?: string
|
||||
@@ -38,7 +38,7 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
|
||||
// Usage is included with assistant messages,
|
||||
// but cost is included in the result chunk
|
||||
let usage: ApiStreamUsageChunk = {
|
||||
const usage: ApiStreamUsageChunk = {
|
||||
type: "usage",
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
@@ -137,7 +137,7 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
private attemptParse(str: string) {
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user