Compare commits

..
Author SHA1 Message Date
Saoud Rizwan 3423602151 Update bug report template to make system information and logs fields optional 2025-07-24 22:54:53 -07:00
Sarah Fortune 66fb8835a4 Add an RPC to the host bridge to scroll the diff viewer. (#5151) 2025-07-24 23:09:33 -04:00
BeeandCopilot 214e157360 Fix organization state reset when switching between accounts (#5154)
* Fix organization state reset when switching between accounts

Move user authentication check into getUserOrganizations callback to properly reset state when switching between personal and organization accounts. This prevents stale organization data from persisting across account switches.

* add changeset

* Add error handling and refactor credit display components

Fix issues with balance display out of sync on org change or when API calls received 405 (rate limited) error

- Add error handling for failed API calls in getUserCredits and getOrganizationCredits
- Extract animated credit display logic into reusable StyledCreditDisplay component
- Simplify AccountView by removing inline credit animation code
- Improve organization state management and loading behavior

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* format

* reset on mount

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-24 19:59:42 -07:00
Igor Tceglevskii 7a9dce4f86 Moved open and visible tab retrieval to a separate hostbridge module (#5150) 2025-07-24 17:46:31 -07:00
Tomás Barreiro af7e3a4d20 Change the CLAUDE_CODE_MAX_OUTPUT_TOKENS (#5142)
* Change the CLAUDE_CODE_MAX_OUTPUT_TOKENS

* Add changeset

* Add comment and variable to explain the changes
2025-07-24 09:24:30 -07:00
schardosin 3abdc9ad0f Fixed issue affecting first-time credential entry for SAP AI Core (#5132)
* removed the reduced mask, which was making the client secret to fail in the first save

* added changeset
2025-07-23 22:19:47 -07:00
AraandCopilot 28b15d8b9b Adding gitbash terminal support and docs for solving windows terminal issues (#5110)
* Adding gitbash terminal support and docs for solving windows terminal issues

* Update docs/troubleshooting/terminal-integration-guide.mdx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-23 14:07:02 -07:00
bc6b3e54be v3.20.1 Release Notes (#5128)
* changeset version bump

* Updating CHANGELOG.md format

* Update CHANGELOG.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-07-23 11:46:55 -07:00
9fad0aa4ae Fix file deletion bug (#5125)
* Fix for files being deleted when switching modes or closing tasks

* changeset

* Added check to see if we are waiting for API response

* More targetted fix

* Create hot-onions-promise.md

* Delete .changeset/hot-onions-promise.md

---------

Co-authored-by: Kevin Bond <kevin@Kevins-MacBook-Pro.local>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-07-23 11:44:24 -07:00
Ara 23fea0e16b Stop auto focus of Cline window on Every update (#5117) 2025-07-23 04:03:28 -07:00
Bee e8aaa61494 Improve auth state management for account view (#5107)
- Fix AccountView state management when user is not authenticated or is authenticated after the webview is loaded
- Add proper loading state reset and conditional data fetching
2025-07-22 20:00:14 -07:00
d93080304c v3.20.0 Release Notes (#5096)
* changeset version bump

* Updating CHANGELOG.md format

* Update CHANGELOG.md and version for 3.20.0 release

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: pashpashpash <nik@cline.bot>
2025-07-22 19:52:16 -07:00
Ara 1ce5f72bc1 Updating the new release announcement note (#5101)
* Updating the new release announcement note

* Adding gitbash terminal support and docs for solving windows terminal issues
2025-07-22 19:37:44 -07:00
Saoud Rizwan fb676add2e Fix hugging face model description (#5105) 2025-07-22 19:06:51 -07:00
Ara 31cda0ce5e Adding support for new models Qwen 3 models on Qwen provider (#5106) 2025-07-22 19:00:49 -07:00
Ara e173dad69c Updating the ordering to move cerebras provider upwards in the list of providers (#5102) 2025-07-22 16:22:52 -07:00
Toshii 92f32522c5 devtral medium (#5100) 2025-07-22 16:07:00 -07:00
30 changed files with 659 additions and 252 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Display account balance for all org members
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Fixed issue with sap ai core client credentials storage
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Fix credit balance out of sync issue on account switching
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Fix Claude Code CLAUDE_CODE_MAX_OUTPUT_TOKENS
+2 -2
View File
@@ -24,7 +24,7 @@ body:
2.
3.
validations:
required: true
required: false
- type: textarea
id: logs
attributes:
@@ -54,7 +54,7 @@ body:
description: What system information is relevant to the issue?
placeholder: "e.g., CPU: Intel Core i7-11700K, GPU: NVIDIA GeForce RTX 3070, RAM: 32GB DDR4"
validations:
required: true
required: false
- type: input
id: cline-version
attributes:
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## [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!)
@@ -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
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.19.8",
"version": "3.20.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.19.8",
"version": "3.20.0",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.37.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.19.8",
"version": "3.20.1",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
+9 -1
View File
@@ -14,6 +14,7 @@ service DiffService {
rpc getDocumentText(GetDocumentTextRequest) returns (GetDocumentTextResponse);
// Replace a text selection in the diff.
rpc replaceText(ReplaceTextRequest) returns (ReplaceTextResponse);
rpc scrollDiff(ScrollDiffRequest) returns (ScrollDiffResponse);
// Truncate the diff document.
rpc truncateDocument(TruncateDocumentRequest) returns (TruncateDocumentResponse);
// Save the diff document.
@@ -54,10 +55,17 @@ 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 {}
+18
View File
@@ -14,6 +14,8 @@ service WindowService {
rpc showMessage(ShowMessageRequest) returns (SelectedResponse);
rpc showInputBox(ShowInputBoxRequest) returns (ShowInputBoxResponse);
rpc showSaveDialog(ShowSaveDialogRequest) returns (ShowSaveDialogResponse);
rpc getOpenTabs(GetOpenTabsRequest) returns (GetOpenTabsResponse);
rpc getVisibleTabs(GetVisibleTabsRequest) returns (GetVisibleTabsResponse);
}
message ShowTextDocumentRequest {
@@ -101,4 +103,20 @@ message ShowInputBoxRequest {
message ShowInputBoxResponse {
optional string response = 1;
}
message GetOpenTabsRequest {
// empty
}
message GetOpenTabsResponse {
repeated string paths = 1;
}
message GetVisibleTabsRequest {
// empty
}
message GetVisibleTabsResponse {
repeated string paths = 1;
}
@@ -22,6 +22,11 @@ export async function getOrganizationCredits(
controller.accountService.fetchOrganizationUsageTransactionsRPC(request.organizationId),
])
// If balance call fails (returns undefined), throw an error
if (!balanceData) {
throw new Error("Failed to fetch organization credits data")
}
return OrganizationCreditsData.create({
balance: balanceData ? { currentBalance: balanceData.balance / 100 } : { currentBalance: 0 },
organizationId: balanceData?.organizationId || "",
@@ -21,6 +21,11 @@ export async function getUserCredits(controller: Controller, request: EmptyReque
controller.accountService.fetchPaymentTransactionsRPC(),
])
// If either call fails (returns undefined), throw an error
if (balance === undefined) {
throw new Error("Failed to fetch user credits data")
}
return UserCreditsData.create({
balance: balance ? { currentBalance: balance.balance / 100 } : { currentBalance: 0 },
usageTransactions: usageTransactions,
@@ -46,6 +46,7 @@ export async function refreshHuggingFaceModels(
// Transform HF models to OpenRouter-compatible format
for (const rawModel of rawModels) {
const providersList = rawModel.providers?.map((provider: { provider: string }) => provider.provider)?.join(", ")
const modelInfo = OpenRouterModelInfo.create({
maxTokens: 8192, // HF doesn't provide max_tokens, use default
contextWindow: 128_000, // FIXME: HF doesn't provide context window, use default
@@ -55,7 +56,7 @@ export async function refreshHuggingFaceModels(
outputPrice: 0, // Will be set based on providers
cacheWritesPrice: 0,
cacheReadsPrice: 0,
description: `Available on providers: ${rawModel.providers?.join(", ") || "unknown"}`,
description: `Available on providers: ${providersList || "unknown"}`,
})
// Add model-specific configurations if we have them in our static models
+7 -9
View File
@@ -86,6 +86,7 @@ import { MessageStateHandler } from "./message-state"
import { TaskState } from "./TaskState"
import { ToolExecutor } from "./ToolExecutor"
import { updateApiReqMsg } from "./utils"
export const USE_EXPERIMENTAL_CLAUDE4_FEATURES = false
export type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
@@ -2525,10 +2526,9 @@ export class Task {
// It could be useful for cline to know if the user went from one or no file to another between messages, so we always include this context
details += "\n\n# VSCode Visible Files"
const visibleFilePaths = vscode.window.visibleTextEditors
?.map((editor) => editor.document?.uri?.fsPath)
.filter(Boolean)
.map((absolutePath) => path.relative(this.cwd, absolutePath))
const visibleFilePaths = (await HostProvider.window.getVisibleTabs({})).paths.map((absolutePath) =>
path.relative(this.cwd, absolutePath),
)
// Filter paths through clineIgnoreController
const allowedVisibleFiles = this.clineIgnoreController
@@ -2543,11 +2543,9 @@ export class Task {
}
details += "\n\n# VSCode Open Tabs"
const openTabPaths = vscode.window.tabGroups.all
.flatMap((group) => group.tabs)
.map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath)
.filter(Boolean)
.map((absolutePath) => path.relative(this.cwd, absolutePath))
const openTabPaths = (await HostProvider.window.getOpenTabs({})).paths.map((absolutePath) =>
path.relative(this.cwd, absolutePath),
)
// Filter paths through clineIgnoreController
const allowedOpenTabs = this.clineIgnoreController
+9 -6
View File
@@ -102,16 +102,19 @@ export async function activate(context: vscode.ExtensionContext) {
try {
if (!previousVersion || currentVersion !== previousVersion) {
Logger.log(`Cline version changed: ${previousVersion} -> ${currentVersion}. First run or update detected.`)
const lastShownPopupNotificationVersion = context.globalState.get<string>("clineLastPopupNotificationVersion")
if (currentVersion !== lastShownPopupNotificationVersion && previousVersion) {
// Show VS Code popup notification as this version hasn't been notified yet without doing it for fresh installs
const message = `Cline has been updated to v${currentVersion}`
// Use the same condition as announcements: focus when there's a new announcement to show
const lastShownAnnouncementId = context.globalState.get<string>("lastShownAnnouncementId")
const latestAnnouncementId = context.extension?.packageJSON?.version?.split(".").slice(0, 2).join(".") ?? ""
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 })
// Record that we've shown the popup for this version.
await context.globalState.update("clineLastPopupNotificationVersion", currentVersion)
}
// Always update the main version tracker for the next launch.
await context.globalState.update("clineVersion", currentVersion)
+6 -1
View File
@@ -61,7 +61,12 @@ export class ExternalDiffViewProvider extends DiffViewProvider {
}
}
protected override async scrollEditorToLine(_line: number): Promise<void> {}
protected override async scrollEditorToLine(line: number): Promise<void> {
if (!this.activeDiffEditorId) {
return
}
await HostProvider.diff.scrollDiff({ diffId: this.activeDiffEditorId, line: line })
}
override async scrollAnimation(_startLine: number, _endLine: number): Promise<void> {}
@@ -0,0 +1,5 @@
import { ScrollDiffRequest, ScrollDiffResponse } from "@/shared/proto/index.host"
export async function scrollDiff(_request: ScrollDiffRequest): Promise<ScrollDiffResponse> {
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
}
@@ -0,0 +1,90 @@
/* eslint-disable eslint-rules/no-direct-vscode-api */
import { describe, it, beforeEach, afterEach } from "mocha"
import { strict as assert } from "assert"
import * as vscode from "vscode"
import { getOpenTabs } from "@/hosts/vscode/hostbridge/window/getOpenTabs"
import { GetOpenTabsRequest } from "@/shared/proto/host/window"
describe("Hostbridge - Window - getOpenTabs", () => {
async function createAndOpenTestDocument(fileNumber: number, column: vscode.ViewColumn): Promise<void> {
const content = `// Test file ${fileNumber}\nconsole.log('Hello from file ${fileNumber}');`
// Create an untitled document with a custom name
const uri = vscode.Uri.parse(`untitled:test-file-${fileNumber}.js`)
const doc = await vscode.workspace.openTextDocument(uri)
// Set the content
const edit = new vscode.WorkspaceEdit()
edit.insert(uri, new vscode.Position(0, 0), content)
await vscode.workspace.applyEdit(edit)
await vscode.window.showTextDocument(doc, {
viewColumn: column,
preview: false,
})
}
beforeEach(async () => {
// Clean up any existing editors
await vscode.commands.executeCommand("workbench.action.closeAllEditors")
})
afterEach(async () => {
// Clean up test documents and editors
await vscode.commands.executeCommand("workbench.action.closeAllEditors")
})
it("should return empty array when no tabs are open", async () => {
// Ensure no tabs are open
await vscode.commands.executeCommand("workbench.action.closeAllEditors")
const request = GetOpenTabsRequest.create({})
const response = await getOpenTabs(request)
assert.strictEqual(
response.paths.length,
0,
`Should return empty array when no tabs are open. Found: ${JSON.stringify(response.paths)}`,
)
})
it("should return paths of open text document tabs", async () => {
// Open the documents in editors (this creates the tabs)
await createAndOpenTestDocument(1, vscode.ViewColumn.One)
await createAndOpenTestDocument(2, vscode.ViewColumn.Two)
// Wait a bit for tabs to be fully created
await new Promise((resolve) => setTimeout(resolve, 100))
const request = GetOpenTabsRequest.create({})
const response = await getOpenTabs(request)
// Should have 2 tabs open
assert.strictEqual(
response.paths.length,
2,
`Expected 2 tabs, got ${response.paths.length}. Found tabs: ${JSON.stringify(response.paths)}`,
)
})
it("should return all open tabs even when multiple files are opened in the same ViewColumn", async () => {
// Open all documents in the same column (only the last one will be visible, but all are open as tabs)
await createAndOpenTestDocument(1, vscode.ViewColumn.One)
await createAndOpenTestDocument(2, vscode.ViewColumn.One)
await createAndOpenTestDocument(3, vscode.ViewColumn.One)
// Wait a bit for tabs to be fully created
await new Promise((resolve) => setTimeout(resolve, 100))
const request = GetOpenTabsRequest.create({})
const response = await getOpenTabs(request)
// Should have all 3 tabs open, even though only 1 is visible
assert.strictEqual(
response.paths.length,
3,
`Expected 3 open tabs, got ${response.paths.length}. Found: ${JSON.stringify(response.paths)}`,
)
})
})
@@ -0,0 +1,11 @@
import { window, TabInputText } from "vscode"
import { GetOpenTabsRequest, GetOpenTabsResponse } from "@/shared/proto/host/window"
export async function getOpenTabs(_: GetOpenTabsRequest): Promise<GetOpenTabsResponse> {
const openTabPaths = window.tabGroups.all
.flatMap((group) => group.tabs)
.map((tab) => (tab.input as TabInputText)?.uri?.fsPath)
.filter(Boolean)
return GetOpenTabsResponse.create({ paths: openTabPaths })
}
@@ -0,0 +1,147 @@
/* eslint-disable eslint-rules/no-direct-vscode-api */
import { describe, it, beforeEach, afterEach } from "mocha"
import { strict as assert } from "assert"
import * as vscode from "vscode"
import { getVisibleTabs } from "@/hosts/vscode/hostbridge/window/getVisibleTabs"
import { GetVisibleTabsRequest } from "@/shared/proto/host/window"
describe("Hostbridge - Window - getVisibleTabs", () => {
/**
* Helper function to create and open a test document in a specific column
*/
async function createAndOpenTestDocument(fileNumber: number, column: vscode.ViewColumn): Promise<void> {
const content = `// Test file ${fileNumber}\nconsole.log('Hello from file ${fileNumber}');`
// Create an untitled document with a custom name
const uri = vscode.Uri.parse(`untitled:test-file-${fileNumber}.js`)
const doc = await vscode.workspace.openTextDocument(uri)
// Set the content
const edit = new vscode.WorkspaceEdit()
edit.insert(uri, new vscode.Position(0, 0), content)
await vscode.workspace.applyEdit(edit)
await vscode.window.showTextDocument(doc, {
viewColumn: column,
preview: false,
})
}
beforeEach(async () => {
// Clean up any existing editors
await vscode.commands.executeCommand("workbench.action.closeAllEditors")
})
afterEach(async () => {
// Clean up test documents and editors
await vscode.commands.executeCommand("workbench.action.closeAllEditors")
})
it("should return empty array when no visible editors are open", async () => {
// Ensure no editors are open
await vscode.commands.executeCommand("workbench.action.closeAllEditors")
const request = GetVisibleTabsRequest.create({})
const response = await getVisibleTabs(request)
assert.strictEqual(
response.paths.length,
0,
`Should return empty array when no visible editors are open. Found tabs: ${JSON.stringify(response.paths)}`,
)
})
it("should return paths of visible text editors", async () => {
// Open the first document in an editor (this makes it visible)
await createAndOpenTestDocument(1, vscode.ViewColumn.One)
// Wait a bit for editor to be fully created
await new Promise((resolve) => setTimeout(resolve, 100))
const request = GetVisibleTabsRequest.create({})
const response = await getVisibleTabs(request)
// Should have 1 visible editor
assert.strictEqual(
response.paths.length,
1,
`Expected 1 visible editor, got ${response.paths.length}. Found: ${JSON.stringify(response.paths)}`,
)
// Open the second document in a different column (both should now be visible)
await createAndOpenTestDocument(2, vscode.ViewColumn.Two)
// Wait a bit for editor to be fully created
await new Promise((resolve) => setTimeout(resolve, 100))
const response2 = await getVisibleTabs(request)
// Should have 2 visible editors
assert.strictEqual(
response2.paths.length,
2,
`Expected 2 visible editors, got ${response2.paths.length}. Found: ${JSON.stringify(response2.paths)}`,
)
})
it("should only return visible editors, not all open tabs", async () => {
// Open all documents in the same column (only the last one will be visible)
await createAndOpenTestDocument(1, vscode.ViewColumn.One)
await createAndOpenTestDocument(2, vscode.ViewColumn.One)
await createAndOpenTestDocument(3, vscode.ViewColumn.One)
// Wait a bit for editors to be fully created
await new Promise((resolve) => setTimeout(resolve, 100))
const request = GetVisibleTabsRequest.create({})
const response = await getVisibleTabs(request)
// Should have only 1 visible editor (the last one opened in the same column)
assert.strictEqual(
response.paths.length,
1,
`Expected 1 visible editor, got ${response.paths.length}. Found: ${JSON.stringify(response.paths)}`,
)
// Verify that we have the correct number of visible text editors
const actualVisibleEditors = vscode.window.visibleTextEditors.length
assert.strictEqual(
response.paths.length,
actualVisibleEditors,
`Response should match actual visible editors count: ${actualVisibleEditors}`,
)
})
it("should return only visible editors from multiple columns with multiple files", async () => {
// Open multiple documents in column one (only the last one will be visible in that column)
await createAndOpenTestDocument(1, vscode.ViewColumn.One)
await createAndOpenTestDocument(2, vscode.ViewColumn.One)
await createAndOpenTestDocument(3, vscode.ViewColumn.One)
// Open multiple documents in column two (only the last one will be visible in that column)
await createAndOpenTestDocument(4, vscode.ViewColumn.Two)
await createAndOpenTestDocument(5, vscode.ViewColumn.Two)
// Wait a bit for editors to be fully created
await new Promise((resolve) => setTimeout(resolve, 100))
const request = GetVisibleTabsRequest.create({})
const response = await getVisibleTabs(request)
// Should have only 2 visible editors (one from each column, despite having 5 total open tabs)
assert.strictEqual(
response.paths.length,
2,
`Expected 2 visible editors, got ${response.paths.length}. Found: ${JSON.stringify(response.paths)}`,
)
// Verify that we have the correct number of visible text editors
const actualVisibleEditors = vscode.window.visibleTextEditors.length
assert.strictEqual(
response.paths.length,
actualVisibleEditors,
`Response should match actual visible editors count: ${actualVisibleEditors}`,
)
})
})
@@ -0,0 +1,8 @@
import { window, TabInputText } from "vscode"
import { GetVisibleTabsRequest, GetVisibleTabsResponse } from "@/shared/proto/host/window"
export async function getVisibleTabs(_: GetVisibleTabsRequest): Promise<GetVisibleTabsResponse> {
const visibleTabPaths = window.visibleTextEditors?.map((editor) => editor.document?.uri?.fsPath).filter(Boolean)
return GetVisibleTabsResponse.create({ paths: visibleTabPaths })
}
+4 -2
View File
@@ -182,6 +182,9 @@ const CLAUDE_CODE_TIMEOUT = 600000 // 10 minutes
// https://github.com/sindresorhus/execa/blob/main/docs/api.md#optionsmaxbuffer
const BUFFER_SIZE = 20_000_000 // 20 MB
// This is the limit imposed by the CLI
const CLAUDE_CODE_MAX_OUTPUT_TOKENS = "32000"
function runProcess(
{ systemPrompt, messages, path, modelId, thinkingBudgetTokens, shouldUseFile }: ClaudeCodeOptions,
cwd: string,
@@ -210,8 +213,7 @@ function runProcess(
const env: NodeJS.ProcessEnv = {
...process.env,
// Respect the user's environment variables but set defaults.
// The default is 32000. However, I've gotten larger responses.
CLAUDE_CODE_MAX_OUTPUT_TOKENS: process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS || "64000",
CLAUDE_CODE_MAX_OUTPUT_TOKENS: process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS || CLAUDE_CODE_MAX_OUTPUT_TOKENS,
// Disable telemetry, auto-updater and error reporting.
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: process.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC || "1",
DISABLE_NON_ESSENTIAL_MODEL_CALLS: process.env.DISABLE_NON_ESSENTIAL_MODEL_CALLS || "1",
+1 -1
View File
@@ -295,7 +295,7 @@ export abstract class DiffViewProvider {
}
async revertChanges(): Promise<void> {
if (!this.absolutePath) {
if (!this.absolutePath || !this.isEditing) {
return
}
const fileExists = this.editType === "modify"
+24
View File
@@ -1156,6 +1156,22 @@ export type InternationalQwenModelId = keyof typeof internationalQwenModels
export const internationalQwenDefaultModelId: InternationalQwenModelId = "qwen-coder-plus-latest"
export const mainlandQwenDefaultModelId: MainlandQwenModelId = "qwen-coder-plus-latest"
export const internationalQwenModels = {
"qwen3-coder-plus": {
maxTokens: 65_536,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 1,
outputPrice: 5,
},
"qwen3-coder-480b-a35b-instruct": {
maxTokens: 65_536,
contextWindow: 204_800,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 1.5,
outputPrice: 7.5,
},
"qwen3-235b-a22b": {
maxTokens: 16_384,
contextWindow: 131_072,
@@ -1970,6 +1986,14 @@ export const mistralModels = {
inputPrice: 0.1,
outputPrice: 0.3,
},
"devstral-medium-latest": {
maxTokens: 128_000,
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.4,
outputPrice: 2.0,
},
} as const satisfies Record<string, ModelInfo>
// LiteLLM
+7
View File
@@ -7,6 +7,7 @@ const SHELL_PATHS = {
POWERSHELL_LEGACY: "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
CMD: "C:\\Windows\\System32\\cmd.exe",
WSL_BASH: "/bin/bash",
GIT_BASH: "C:\\Program Files\\Git\\bin\\bash.exe",
// Unix paths
MAC_DEFAULT: "/bin/zsh",
LINUX_DEFAULT: "/bin/bash",
@@ -221,6 +222,12 @@ export function getAvailableTerminalProfiles(): TerminalProfile[] {
path: SHELL_PATHS.WSL_BASH,
description: "Windows Subsystem for Linux Bash",
},
{
id: "git-bash",
name: "Git Bash",
path: SHELL_PATHS.GIT_BASH,
description: "Git Bash (bash.exe from Git for Windows)",
},
)
} else if (process.platform === "darwin") {
// macOS terminal profiles
+131 -206
View File
@@ -1,9 +1,7 @@
import { useClineAuth } from "@/context/ClineAuthContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { AccountServiceClient } from "@/services/grpc-client"
import { formatCreditsBalance } from "@/utils/format"
import { UsageTransaction as ClineAccountUsageTransaction, PaymentTransaction } from "@shared/ClineAccount"
import { UsageTransaction as ProtoUsageTransaction, UserOrganization, UserOrganizationUpdateRequest } from "@shared/proto/account"
import { UsageTransaction as ProtoUsageTransaction, UserOrganization } from "@shared/proto/account"
import { EmptyRequest } from "@shared/proto/common"
import {
VSCodeButton,
@@ -13,71 +11,13 @@ import {
VSCodeOption,
VSCodeTag,
} from "@vscode/webview-ui-toolkit/react"
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { memo, useCallback, useEffect, useMemo, useState } from "react"
import ClineLogoWhite from "../../assets/ClineLogoWhite"
import CreditsHistoryTable from "./CreditsHistoryTable"
import { GetOrganizationCreditsRequest } from "@shared/proto/account"
import debounce from "debounce"
import deepEqual from "fast-deep-equal"
import VSCodeButtonLink from "../common/VSCodeButtonLink"
// Custom hook for animated credit display with styled decimals
const useAnimatedCredits = (targetValue: number, duration: number = 660) => {
const [currentValue, setCurrentValue] = useState(0)
const animationRef = useRef<number>()
const startTimeRef = useRef<number>()
useEffect(() => {
const animate = (timestamp: number) => {
if (!startTimeRef.current) {
startTimeRef.current = timestamp
}
const elapsed = timestamp - startTimeRef.current
const progress = Math.min(elapsed / duration, 1)
// Easing function (ease-out)
const easedProgress = 1 - (1 - progress) ** 3
const newValue = easedProgress * targetValue
setCurrentValue(newValue)
if (progress < 1) {
animationRef.current = requestAnimationFrame(animate)
}
}
// Reset and start animation
startTimeRef.current = undefined
animationRef.current = requestAnimationFrame(animate)
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current)
}
}
}, [targetValue, duration])
return currentValue
}
// Custom component to handle styled credit display
const StyledCreditDisplay = ({ balance }: { balance: number }) => {
const animatedValue = useAnimatedCredits(formatCreditsBalance(balance))
const formatted = animatedValue.toFixed(4)
const parts = formatted.split(".")
const wholePart = parts[0]
const decimalPart = parts[1] || "0000"
const firstTwoDecimals = decimalPart.slice(0, 2)
const lastTwoDecimals = decimalPart.slice(2)
return (
<span className="font-azeret-mono font-light tabular-nums">
{wholePart}.{firstTwoDecimals}
<span className="text-[var(--vscode-descriptionForeground)]">{lastTwoDecimals}</span>
</span>
)
}
import { StyledCreditDisplay } from "./StyledCreditDisplay"
type VSCodeDropdownChangeEvent = Event & {
target: {
@@ -118,15 +58,14 @@ const CLINE_APP_URL = "https://app.cline.bot"
export const ClineAccountView = () => {
const { clineUser, handleSignIn, handleSignOut } = useClineAuth()
const { userInfo, apiConfiguration } = useExtensionState()
const user = apiConfiguration?.clineAccountId ? clineUser || userInfo : undefined
// Source of truth: Dedicated state for dropdown value that persists through failures
// and represents that user's current selection.
const [dropdownValue, setDropdownValue] = useState<string>("personal")
const [isLoading, setIsLoading] = useState(true)
const [balance, setBalance] = useState<number | null>(null)
const [userOrganizations, setUserOrganizations] = useState<UserOrganization[]>([])
const [activeOrganization, setActiveOrganization] = useState<UserOrganization | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [isSwitchingProfile, setIsSwitchingProfile] = useState(false)
const [usageData, setUsageData] = useState<ClineAccountUsageTransaction[]>([])
const [paymentsData, setPaymentsData] = useState<PaymentTransaction[]>([])
const [lastFetchTime, setLastFetchTime] = useState<number>(Date.now())
@@ -134,7 +73,7 @@ export const ClineAccountView = () => {
const clineUris = useMemo(() => {
const base = new URL(clineUser?.appBaseUrl || CLINE_APP_URL)
const dashboard = new URL("dashboard", base)
const credits = new URL(activeOrganization ? "/organization" : "/account", dashboard)
const credits = new URL(dropdownValue === "personal" ? "/account" : "/organization", dashboard)
credits.searchParams.set("tab", "credits")
credits.searchParams.set("redirect", "true")
@@ -142,171 +81,153 @@ export const ClineAccountView = () => {
dashboard,
credits,
}
}, [clineUser?.appBaseUrl, activeOrganization])
}, [clineUser?.appBaseUrl, dropdownValue])
// Add a ref to track the intended organization during transitions
const pendingOrganizationRef = useRef<string | null>(null)
const [userOrganizations, setUserOrganizations] = useState<UserOrganization[]>([])
const activeOrganization = useMemo(() => {
return userOrganizations.find((org) => org.organizationId === dropdownValue)
}, [userOrganizations, dropdownValue])
const getUserOrganizations = useCallback(async () => {
try {
if (!clineUser?.uid) {
setBalance(null)
setUserOrganizations([])
setActiveOrganization(null)
setIsSwitchingProfile(false)
setIsLoading(false)
return
}
const response = await AccountServiceClient.getUserOrganizations(EmptyRequest.create())
if (response.organizations && !deepEqual(userOrganizations, response.organizations)) {
setUserOrganizations(response.organizations)
// Only update activeOrganization if we're not in the middle of a switch
// or if the server response matches our pending change
const serverActiveOrg = response.organizations.find((org: UserOrganization) => org.active)
const serverActiveOrgId = serverActiveOrg?.organizationId || ""
if (!isSwitchingProfile || pendingOrganizationRef.current === serverActiveOrgId) {
if (serverActiveOrgId !== (activeOrganization?.organizationId || "")) {
setActiveOrganization(serverActiveOrg || null)
}
// Clear pending ref if the server state matches
if (pendingOrganizationRef.current === serverActiveOrgId) {
pendingOrganizationRef.current = null
}
if (clineUser?.uid) {
const response = await AccountServiceClient.getUserOrganizations(EmptyRequest.create())
if (response?.organizations && !deepEqual(userOrganizations, response.organizations)) {
setUserOrganizations(response.organizations)
}
}
} catch (error) {
console.error("Failed to fetch user organizations:", error)
}
}, [clineUser?.uid, userOrganizations, isSwitchingProfile, activeOrganization?.organizationId])
}, [userOrganizations, clineUser?.uid, dropdownValue])
const fetchCreditBalance = useCallback(async () => {
try {
setIsLoading(true)
const fetchCreditBalance = useCallback(
async (orgId?: string) => {
try {
setIsLoading(true)
const targetOrgId = orgId ?? dropdownValue
const isPersonal = targetOrgId === "personal"
// Use the pending organization if we're switching, otherwise use current active org
const targetOrgId =
pendingOrganizationRef.current !== null ? pendingOrganizationRef.current : activeOrganization?.organizationId
// Use targetOrgId consistently for all requests
const response = isPersonal
? await AccountServiceClient.getUserCredits(EmptyRequest.create())
: await AccountServiceClient.getOrganizationCredits({ organizationId: targetOrgId })
const response = targetOrgId
? await AccountServiceClient.getOrganizationCredits(
GetOrganizationCreditsRequest.fromPartial({
organizationId: targetOrgId,
}),
)
: await AccountServiceClient.getUserCredits(EmptyRequest.create())
// Update balance if changed
const newBalance = response.balance?.currentBalance
if (newBalance !== undefined && newBalance !== balance) {
setBalance(newBalance)
}
if (response.usageTransactions && !deepEqual(usageData, response.usageTransactions)) {
const clineUsage = convertProtoUsageTransactions(response.usageTransactions) || []
setUsageData(clineUsage)
}
// Update balance if changed
const newBalance = response.balance?.currentBalance
if (newBalance !== balance) {
setBalance(newBalance ?? null)
}
// Organizations don't have payment transactions
if (targetOrgId !== "personal") {
setPaymentsData([])
setIsLoading(false)
return
}
const clineUsage = convertProtoUsageTransactions(response.usageTransactions)
setUsageData(clineUsage || [])
if (activeOrganization?.organizationId) {
setPaymentsData([]) // Organizations don't have payment transactions
} else {
// Check if response is UserCreditsData type
if (typeof response !== "object" || !("paymentTransactions" in response)) {
return
}
const paymentsData = response.paymentTransactions || []
const newPaymentsData = response.paymentTransactions
// Check if paymentTransactions is part of the response
if (response.paymentTransactions?.length !== paymentsData?.length) {
setPaymentsData(paymentsData)
if (newPaymentsData?.length && !deepEqual(paymentsData, newPaymentsData)) {
setPaymentsData(newPaymentsData)
}
} catch (error) {
console.error("Failed to fetch credit balance:", error)
} finally {
setLastFetchTime(Date.now())
setIsLoading(false)
}
} finally {
setLastFetchTime(Date.now())
setIsLoading(false)
}
}, [activeOrganization?.organizationId])
const handleManualRefresh = useCallback(
debounce(() => !isLoading && fetchCreditBalance(), 500, { immediate: true }),
[fetchCreditBalance, isLoading],
},
[dropdownValue, balance],
)
// Create a debounced version of fetchCreditBalance
const debouncedFetchCreditBalance = useMemo(
() => debounce(() => fetchCreditBalance(), 500, { immediate: true }),
[fetchCreditBalance],
)
const handleManualRefresh = useCallback(() => {
if (!isLoading) {
debouncedFetchCreditBalance()
}
}, [debouncedFetchCreditBalance, isLoading])
const handleOrganizationChange = useCallback(
async (event: any) => {
const newOrgId = (event.target as VSCodeDropdownChangeEvent["target"]).value
const currentOrgId = activeOrganization?.organizationId || ""
const newValue = (event.target as VSCodeDropdownChangeEvent["target"]).value || "personal"
const organizationId = newValue === "personal" ? undefined : newValue
if (currentOrgId !== newOrgId) {
setIsSwitchingProfile(true)
if (newValue === dropdownValue) {
return // No change, do nothing
}
try {
console.info("Changing selection to:", newValue)
// Send the change to the server
AccountServiceClient.setUserOrganization({ organizationId })
// Update dropdownValue immediately - this persists through failures
setDropdownValue(newValue)
setIsLoading(true)
setBalance(null)
setUsageData([])
setPaymentsData([])
// Set the pending organization immediately to prevent race conditions
pendingOrganizationRef.current = newOrgId
try {
// Update local state immediately for UI responsiveness
if (newOrgId === "") {
setActiveOrganization(null)
} else {
const org = userOrganizations.find((org: UserOrganization) => org.organizationId === newOrgId)
if (org) {
setActiveOrganization(org)
}
}
// Send the change to the server
await AccountServiceClient.setUserOrganization(
UserOrganizationUpdateRequest.create({ organizationId: newOrgId }),
)
// Fetch fresh data for the new organization
await fetchCreditBalance()
// Refresh organizations to get the updated active state from server
await getUserOrganizations()
} catch (error) {
console.error("Failed to update organization:", error)
// Reset pending ref on error
pendingOrganizationRef.current = null
} finally {
setIsSwitchingProfile(false)
}
await fetchCreditBalance(organizationId)
} catch (error) {
console.error("Failed to update organization:", error)
// Don't reset selectedOrgId on error - keep the user's selection
// The next refresh will use the correct selectedOrgId
} finally {
setIsLoading(false)
}
},
[activeOrganization?.organizationId, fetchCreditBalance, getUserOrganizations, userOrganizations],
[fetchCreditBalance, getUserOrganizations, dropdownValue],
)
// Handle organization changes and initial load
// Fetching initial data
useEffect(() => {
const loadData = async () => {
await getUserOrganizations()
await fetchCreditBalance()
if (clineUser?.uid) {
// Start with personal account as we do not have the user's organizations yet
AccountServiceClient.setUserOrganization({ organizationId: undefined })
await getUserOrganizations()
await fetchCreditBalance()
}
}
loadData()
}, [activeOrganization?.organizationId])
}, [clineUser?.uid])
// Periodic refresh
useEffect(() => {
const refreshData = async () => {
try {
if (clineUser?.uid) {
await Promise.all([getUserOrganizations(), fetchCreditBalance()])
await getUserOrganizations()
await fetchCreditBalance()
}
} catch (error) {
console.error("Error during periodic refresh:", error)
}
}
const intervalId = setInterval(refreshData, 30000)
const intervalId = setInterval(refreshData, 60000)
return () => clearInterval(intervalId)
}, [clineUser?.uid, getUserOrganizations, fetchCreditBalance])
// Determine the current dropdown value, considering pending changes
const dropdownValue =
pendingOrganizationRef.current !== null ? pendingOrganizationRef.current : activeOrganization?.organizationId || ""
}, [clineUser?.uid])
return (
<div className="h-full flex flex-col">
{user ? (
{clineUser ? (
<div className="flex flex-col pr-3 h-full">
<div className="flex flex-col w-full">
<div className="flex items-center mb-6 flex-wrap gap-y-4">
@@ -314,37 +235,37 @@ export const ClineAccountView = () => {
<img src={user.photoUrl} alt="Profile" className="size-16 rounded-full mr-4" />
) : ( */}
<div className="size-16 rounded-full bg-[var(--vscode-button-background)] flex items-center justify-center text-2xl text-[var(--vscode-button-foreground)] mr-4">
{user.displayName?.[0] || user.email?.[0] || "?"}
{clineUser.displayName?.[0] || clineUser.email?.[0] || "?"}
</div>
{/* )} */}
<div className="flex flex-col">
{user.displayName && (
{clineUser.displayName && (
<h2 className="text-[var(--vscode-foreground)] m-0 text-lg font-medium">
{user.displayName}
{clineUser.displayName}
</h2>
)}
{user.email && (
<div className="text-sm text-[var(--vscode-descriptionForeground)]">{user.email}</div>
{clineUser.email && (
<div className="text-sm text-[var(--vscode-descriptionForeground)]">{clineUser.email}</div>
)}
<div className="flex gap-2 items-center mt-1">
{userOrganizations && (
<VSCodeDropdown
currentValue={dropdownValue}
onChange={handleOrganizationChange}
disabled={isSwitchingProfile}
className="w-full">
<VSCodeOption value="">Personal</VSCodeOption>
{userOrganizations.map((org: UserOrganization) => (
<VSCodeOption key={org.organizationId} value={org.organizationId}>
{org.name}
</VSCodeOption>
))}
</VSCodeDropdown>
)}
{activeOrganization?.roles && (
<VSCodeDropdown
currentValue={dropdownValue}
onChange={handleOrganizationChange}
disabled={isLoading}
className="w-full">
<VSCodeOption value="personal" key="personal">
Personal
</VSCodeOption>
{userOrganizations?.map((org: UserOrganization) => (
<VSCodeOption key={org.organizationId} value={org.organizationId}>
{org.name}
</VSCodeOption>
))}
</VSCodeDropdown>
{activeOrganization && (
<VSCodeTag className="text-xs p-2" title="Role">
{getMainRole(activeOrganization.roles)}
</VSCodeTag>
@@ -376,7 +297,11 @@ export const ClineAccountView = () => {
<div className="text-4xl font-bold text-[var(--vscode-foreground)] mb-6 flex items-center gap-2">
{balance === null ? <span>----</span> : <StyledCreditDisplay balance={balance} />}
<VSCodeButton appearance="icon" className="mt-1" onClick={handleManualRefresh}>
<VSCodeButton
appearance="icon"
className={`mt-1 ${isLoading ? "animate-spin" : ""}`}
onClick={handleManualRefresh}
disabled={isLoading}>
<span className="codicon codicon-refresh"></span>
</VSCodeButton>
</div>
@@ -392,10 +317,10 @@ export const ClineAccountView = () => {
<div className="flex-grow flex flex-col min-h-0 pb-[0px]">
<CreditsHistoryTable
isLoading={isSwitchingProfile}
isLoading={isLoading}
usageData={usageData}
paymentsData={paymentsData}
showPayments={!activeOrganization?.active}
showPayments={dropdownValue === "personal"}
/>
</div>
</div>
@@ -0,0 +1,60 @@
import { useEffect, useRef, useState } from "react"
import { formatCreditsBalance } from "@/utils/format"
// Custom hook for animated credit display with styled decimals
const useAnimatedCredits = (targetValue: number, duration: number = 660) => {
const [currentValue, setCurrentValue] = useState(0)
const animationRef = useRef<number>()
const startTimeRef = useRef<number>()
useEffect(() => {
const animate = (timestamp: number) => {
if (!startTimeRef.current) {
startTimeRef.current = timestamp
}
const elapsed = timestamp - startTimeRef.current
const progress = Math.min(elapsed / duration, 1)
// Easing function (ease-out)
const easedProgress = 1 - (1 - progress) ** 3
const newValue = easedProgress * targetValue
setCurrentValue(newValue)
if (progress < 1) {
animationRef.current = requestAnimationFrame(animate)
}
}
// Reset and start animation
startTimeRef.current = undefined
animationRef.current = requestAnimationFrame(animate)
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current)
}
}
}, [targetValue, duration])
return currentValue
}
// Custom component to handle styled credit display
export const StyledCreditDisplay = ({ balance }: { balance: number }) => {
const animatedValue = useAnimatedCredits(formatCreditsBalance(balance))
const formatted = animatedValue.toFixed(4)
const parts = formatted.split(".")
const wholePart = parts[0]
const decimalPart = parts[1] || "0000"
const firstTwoDecimals = decimalPart.slice(0, 2)
const lastTwoDecimals = decimalPart.slice(2)
return (
<span className="font-azeret-mono font-light tabular-nums">
{wholePart}.{firstTwoDecimals}
<span className="text-[var(--vscode-descriptionForeground)]">{lastTwoDecimals}</span>
</span>
)
}
@@ -46,23 +46,23 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
</h3>
<ul style={ulStyle}>
<li>
<b>Optimized for Claude 4:</b> Cline is now optimized to work with the Claude 4 family of models, resulting in
improved performance, reliability, and new capabilities.
<b>Cerebras Provider Support:</b> Enhanced performance with updated model selection (Qwen and Llama 3.3 70B
only) and increased context window for Qwen 3 32B from 16K to 64K tokens.
</li>
<li>
<b>Gemini CLI Provider:</b> Added a new Gemini CLI provider that allows you to use your local Gemini CLI
authentication to access Gemini models for free.
<b>Claude Code for Windows:</b> Improved system prompt handling to fix E2BIG errors and better error messages
with guidance for common setup issues.
</li>
<li>
<b>WebFetch Tool:</b> Gemini 2.5 Pro and Claude 4 models now support the WebFetch tool, allowing Cline to
retrieve and summarize web content directly in conversations.
<b>Hugging Face Provider:</b> Added as a new API provider with support for their inference API models.
</li>
<li>
<b>Self Knowledge:</b> When using frontier models, Cline is self-aware about his capabilities and featureset.
<b>Moonshot Chinese Endpoints:</b> Added ability to choose Chinese endpoint for Moonshot provider and added
Moonshot AI as a new provider.
</li>
<li>
<b>Improved Diff Editing:</b> Improved diff editing to achieve record lows in diff edit failures for frontier
models
<b>Enhanced Stability:</b> Robust checkpoint timeout handling, fixed MCP servers starting when disabled, and
improved authentication sync across multiple VSCode windows.
</li>
</ul>
<Accordion isCompact className="pl-0">
@@ -77,6 +77,26 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
"text-[var(--vscode-foreground)] mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
}}>
<ul style={ulStyle}>
<li>
<b>Optimized for Claude 4:</b> Cline is now optimized to work with the Claude 4 family of models,
resulting in improved performance, reliability, and new capabilities.
</li>
<li>
<b>Gemini CLI Provider:</b> Added a new Gemini CLI provider that allows you to use your local Gemini
CLI authentication to access Gemini models for free.
</li>
<li>
<b>WebFetch Tool:</b> Gemini 2.5 Pro and Claude 4 models now support the WebFetch tool, allowing Cline
to retrieve and summarize web content directly in conversations.
</li>
<li>
<b>Self Knowledge:</b> When using frontier models, Cline is self-aware about his capabilities and
featureset.
</li>
<li>
<b>Improved Diff Editing:</b> Improved diff editing to achieve record lows in diff edit failures for
frontier models.
</li>
<li>
<b>Claude 4 Models:</b> Now with support for Anthropic Claude Sonnet 4 and Claude Opus 4 in both
Anthropic and Vertex providers.
@@ -43,7 +43,7 @@ export const SapAiCoreProvider = ({ showModelOptions, isPopup, currentMode }: Sa
)}
<DebouncedTextField
initialValue={apiConfiguration?.sapAiCoreClientSecret ? "********" : ""}
initialValue={apiConfiguration?.sapAiCoreClientSecret || ""}
onChange={(value) => handleFieldChange("sapAiCoreClientSecret", value)}
style={{ width: "100%" }}
type="password"