Compare commits

...
Author SHA1 Message Date
0xtoshii 535f0b72e2 typing 2025-08-06 14:04:59 -07:00
0xtoshii 99f8093b81 toggle showing key box 2025-08-06 13:50:08 -07:00
0xtoshiiandEndoTheDev b3ceccbb20 base
Co-authored-by: EndoTheDev <endothedev@gmail.com>
2025-08-06 13:45:23 -07:00
kvyb 1d4cd3187b Hostbridge diff diagnostics (#5368)
* feat: migrate diff edit diagnostics to hostbridge; Migrate diagnostics functionality from direct VS Code API calls to the hostbridge layer to enable multi-host support (VS Code + IntelliJ).

* remove test logging

* refactor: migrate diagnostics to workspace service and host separation
2025-08-06 18:16:17 +03:00
Ara 32f0f9618c Adding UUID to task creation for tracking the metrics of a Task in telemetry (#5379) 2025-08-06 00:14:32 -07:00
Ara 3001f883c2 Add walkthrough button and enable quick wins for new users (#5047)
* Add walkthrough button and enable quick wins for new users

- Add openWalkthrough RPC method to ui.proto
- Enable quick wins display for users with <3 tasks in history
- Add "Take a Tour" button in HomeHeader when quick wins are shown
- Update WelcomeSection to pass shouldShowQuickWins prop to HomeHeader

* Adding Gpt-oss through groq

* Adding Gpt-oss through groq

* Support prompt caching and thinking for Opus 4.1

* Support prompt caching and thinking for Opus 4.1

* Support prompt caching and thinking for Opus 4.1
2025-08-06 00:13:31 -07:00
a64e60b8f6 v3.20.13 Release Notes (#5391)
* 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-08-06 00:12:50 -07:00
Saoud Rizwan 3a0e6a471b Add prompt caching for Opus 4.1 (#5389)
* Add prompt caching for Opus 4.1

* Create forty-poets-doubt.md
2025-08-06 00:10:50 -07:00
c10f4e0a66 v3.20.12 Release Notes (#5387)
* changeset version bump

* Updating CHANGELOG.md format

* Update CHANGELOG.md for version 3.20.12

---------

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-08-05 22:25:33 -07:00
Ara 3e11271cf8 Fix support for prompt caching and thinking for Opus 4.1 (#5386)
* Support prompt caching and thinking for Opus 4.1

* Support prompt caching and thinking for Opus 4.1
2025-08-05 22:12:14 -07:00
48 changed files with 547 additions and 156 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
feat: Added Claude Opus 4.1 to Bedrock
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Enabling quickwins and adding a button to show that you can do a walkthrough
+9
View File
@@ -1,5 +1,14 @@
# Changelog
## [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
+1
View File
@@ -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)
+1
View File
@@ -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`
+85 -15
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.20.11",
"version": "3.20.12",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.20.11",
"version": "3.20.12",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.37.0",
@@ -30,6 +30,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",
@@ -74,6 +75,7 @@
"tree-sitter-wasms": "^0.1.11",
"ts-morph": "^25.0.1",
"turndown": "^7.2.0",
"uuid": "^11.1.0",
"vscode-uri": "^3.1.0",
"web-tree-sitter": "^0.22.6",
"zod": "^3.24.2"
@@ -439,12 +441,31 @@
"node": ">=18.0.0"
}
},
"node_modules/@aws-sdk/client-bedrock-runtime/node_modules/@types/uuid": {
"version": "9.0.8",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
"integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
"license": "MIT"
},
"node_modules/@aws-sdk/client-bedrock-runtime/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/@aws-sdk/client-bedrock-runtime/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@aws-sdk/client-cognito-identity": {
"version": "3.840.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.840.0.tgz",
@@ -4981,6 +5002,19 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/@smithy/middleware-retry/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@smithy/middleware-serde": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz",
@@ -5907,9 +5941,9 @@
"license": "MIT"
},
"node_modules/@types/uuid": {
"version": "9.0.8",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
"integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"license": "MIT"
},
"node_modules/@types/vscode": {
@@ -11178,6 +11212,19 @@
"node": ">=14"
}
},
"node_modules/gaxios/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/gcp-metadata": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
@@ -18516,15 +18563,16 @@
}
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {
@@ -19580,10 +19628,20 @@
"tslib": "^2.6.2"
}
},
"@types/uuid": {
"version": "9.0.8",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
"integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="
},
"tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
}
}
},
@@ -23109,6 +23167,11 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
}
}
},
@@ -23896,9 +23959,9 @@
"dev": true
},
"@types/uuid": {
"version": "9.0.8",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
"integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="
},
"@types/vscode": {
"version": "1.84.0",
@@ -27352,6 +27415,13 @@
"is-stream": "^2.0.0",
"node-fetch": "^2.6.9",
"uuid": "^9.0.1"
},
"dependencies": {
"uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
}
}
},
"gcp-metadata": {
@@ -32295,9 +32365,9 @@
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
},
"uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="
},
"v8-compile-cache-lib": {
"version": "3.0.1",
+3 -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.20.11",
"version": "3.20.13",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
@@ -447,6 +447,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",
@@ -491,6 +492,7 @@
"tree-sitter-wasms": "^0.1.11",
"ts-morph": "^25.0.1",
"turndown": "^7.2.0",
"uuid": "^11.1.0",
"vscode-uri": "^3.1.0",
"web-tree-sitter": "^0.22.6",
"zod": "^3.24.2"
+1
View File
@@ -235,6 +235,7 @@ message ModelsApiConfiguration {
optional string hugging_face_api_key = 60;
optional string huawei_cloud_maas_api_key = 61;
optional string baseten_api_key = 62;
optional string ollama_api_key = 63;
// Plan mode configurations
optional ApiProvider plan_mode_api_provider = 100;
+1
View File
@@ -172,6 +172,7 @@ message ApiConfiguration {
optional string moonshot_api_key = 54;
optional string moonshot_api_line = 55;
optional string huawei_cloud_maas_api_key = 56;
optional string ollama_api_key = 57;
// Plan mode configurations
optional string plan_mode_api_provider = 100;
+3
View File
@@ -267,4 +267,7 @@ service UiService {
// Opens a URL in the default browser
rpc openUrl(StringRequest) returns (Empty);
// Opens the Cline walkthrough
rpc openWalkthrough(EmptyRequest) returns (Empty);
}
+41
View File
@@ -4,6 +4,8 @@ package host;
option java_package = "bot.cline.host.proto";
option java_multiple_files = true;
import "cline/common.proto";
// Provides methods for working with workspaces/projects.
service WorkspaceService {
// Returns a list of the top level directories of the workspace.
@@ -12,6 +14,8 @@ service WorkspaceService {
// 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);
}
message GetWorkspacePathsRequest {
@@ -34,3 +38,40 @@ message SaveOpenDocumentIfDirtyResponse {
// Returns true if the document was saved.
optional bool was_saved = 1;
}
message GetDiagnosticsRequest {
optional cline.Metadata metadata = 1;
}
message GetDiagnosticsResponse {
repeated FileDiagnostics file_diagnostics = 1;
}
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
View File
@@ -113,6 +113,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,
+2
View File
@@ -55,6 +55,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": {
/*
@@ -122,6 +123,7 @@ export class AnthropicHandler implements ApiHandler {
switch (modelId) {
case "claude-sonnet-4-20250514":
case "claude-opus-4-20250514":
case "claude-opus-4-1-20250805":
case "claude-3-7-sonnet-20250219":
case "claude-3-5-sonnet-20241022":
case "claude-3-5-haiku-20241022":
+14 -2
View File
@@ -1,5 +1,5 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { Message, Ollama } from "ollama"
import { Message, Ollama, Config } from "ollama"
import { ApiHandler } from "../"
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
import { convertToOllamaMessages } from "../transform/ollama-format"
@@ -8,6 +8,7 @@ import { withRetry } from "../retry"
interface OllamaHandlerOptions {
ollamaBaseUrl?: string
ollamaApiKey?: string
ollamaModelId?: string
ollamaApiOptionsCtxNum?: string
requestTimeoutMs?: number
@@ -24,7 +25,18 @@ export class OllamaHandler implements ApiHandler {
private ensureClient(): Ollama {
if (!this.client) {
try {
this.client = new Ollama({ host: this.options.ollamaBaseUrl || "http://localhost:11434" })
const clientOptions: Partial<Config> = {
host: this.options.ollamaBaseUrl || "http://localhost:11434",
}
// Add API key if provided (for Ollama cloud or authenticated instances)
if (this.options.ollamaApiKey) {
clientOptions.headers = {
Authorization: `Bearer ${this.options.ollamaApiKey}`,
}
}
this.client = new Ollama(clientOptions)
} catch (error) {
throw new Error(`Error creating Ollama client: ${error.message}`)
}
+4 -1
View File
@@ -74,7 +74,10 @@ export class RequestyHandler implements ApiHandler {
? { thinking: { type: "enabled", budget_tokens: thinkingBudget } }
: { thinking: { type: "disabled" } }
const thinkingArgs =
model.id.includes("claude-3-7-sonnet") || model.id.includes("claude-sonnet-4") || model.id.includes("claude-opus-4")
model.id.includes("claude-3-7-sonnet") ||
model.id.includes("claude-sonnet-4") ||
model.id.includes("claude-opus-4") ||
model.id.includes("claude-opus-4-1")
? thinking
: {}
+1
View File
@@ -86,6 +86,7 @@ export class VertexHandler implements ApiHandler {
switch (modelId) {
case "claude-sonnet-4@20250514":
case "claude-opus-4-1@20250805":
case "claude-opus-4@20250514":
case "claude-3-7-sonnet@20250219":
case "claude-3-5-sonnet-v2@20241022":
+3
View File
@@ -24,6 +24,7 @@ export async function createOpenRouterStream(
// handles direct model.id match logic
switch (model.id) {
case "anthropic/claude-sonnet-4":
case "anthropic/claude-opus-4.1":
case "anthropic/claude-opus-4":
case "anthropic/claude-3.7-sonnet":
case "anthropic/claude-3.7-sonnet:beta":
@@ -82,6 +83,7 @@ export async function createOpenRouterStream(
let maxTokens: number | undefined
switch (model.id) {
case "anthropic/claude-sonnet-4":
case "anthropic/claude-opus-4.1":
case "anthropic/claude-opus-4":
case "anthropic/claude-3.7-sonnet":
case "anthropic/claude-3.7-sonnet:beta":
@@ -117,6 +119,7 @@ export async function createOpenRouterStream(
let reasoning: { max_tokens: number } | undefined = undefined
switch (model.id) {
case "anthropic/claude-sonnet-4":
case "anthropic/claude-opus-4.1":
case "anthropic/claude-opus-4":
case "anthropic/claude-3.7-sonnet":
case "anthropic/claude-3.7-sonnet:beta":
@@ -114,6 +114,7 @@ export async function refreshGroqModels(controller: Controller, request: EmptyRe
telemetryService.captureProviderApiError({
taskId: controller.task?.taskId || "",
uuid: controller.task?.uuid || "",
errorMessage,
errorStatus: error.status,
model: "groq",
@@ -49,7 +49,6 @@ export async function refreshOpenRouterModels(
switch (rawModel.id) {
case "anthropic/claude-sonnet-4":
case "anthropic/claude-opus-4":
case "anthropic/claude-3-7-sonnet":
case "anthropic/claude-3-7-sonnet:beta":
case "anthropic/claude-3.7-sonnet":
@@ -62,6 +61,12 @@ export async function refreshOpenRouterModels(
modelInfo.cacheWritesPrice = 3.75
modelInfo.cacheReadsPrice = 0.3
break
case "anthropic/claude-opus-4.1":
case "anthropic/claude-opus-4":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 18.75
modelInfo.cacheReadsPrice = 1.5
break
case "anthropic/claude-3.5-sonnet-20240620":
case "anthropic/claude-3.5-sonnet-20240620:beta":
modelInfo.supportsPromptCache = true
+2 -7
View File
@@ -6,7 +6,7 @@ import { mentionRegexGlobal } from "@shared/context-mentions"
import fs from "fs/promises"
import { extractTextFromFile } from "@integrations/misc/extract-text"
import { isBinaryFile } from "isbinaryfile"
import { diagnosticsToProblemsString } from "@integrations/diagnostics"
import { getWorkspaceProblemsString } from "@/integrations/diagnostics"
import { getLatestTerminalOutput } from "@integrations/terminal/get-latest-output"
import { getCommitInfo } from "@utils/git"
import { getWorkingState } from "@utils/git"
@@ -225,12 +225,7 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
}
async function getWorkspaceProblems(): Promise<string> {
const diagnostics = vscode.languages.getDiagnostics()
const result = diagnosticsToProblemsString(diagnostics, [vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning])
if (!result) {
return "No errors or warnings detected."
}
return result
return await getWorkspaceProblemsString()
}
function isFileMention(mention: string): boolean {
+5
View File
@@ -200,6 +200,7 @@ export class CacheService {
openAiApiKey,
openAiHeaders,
ollamaBaseUrl,
ollamaApiKey,
ollamaApiOptionsCtxNum,
lmStudioBaseUrl,
anthropicBaseUrl,
@@ -399,6 +400,7 @@ export class CacheService {
awsSessionToken,
awsBedrockApiKey,
openAiApiKey,
ollamaApiKey,
geminiApiKey,
openAiNativeApiKey,
deepSeekApiKey,
@@ -598,6 +600,7 @@ export class CacheService {
openAiApiKey,
openAiHeaders,
ollamaBaseUrl,
ollamaApiKey,
ollamaApiOptionsCtxNum,
lmStudioBaseUrl,
anthropicBaseUrl,
@@ -802,6 +805,7 @@ export class CacheService {
awsSessionToken,
awsBedrockApiKey,
openAiApiKey,
ollamaApiKey,
geminiApiKey,
openAiNativeApiKey,
deepSeekApiKey,
@@ -845,6 +849,7 @@ export class CacheService {
awsSessionToken: this.secretsCache.get("awsSessionToken"),
awsBedrockApiKey: this.secretsCache.get("awsBedrockApiKey"),
openAiApiKey: this.secretsCache.get("openAiApiKey"),
ollamaApiKey: this.secretsCache.get("ollamaApiKey"),
geminiApiKey: this.secretsCache.get("geminiApiKey"),
openAiNativeApiKey: this.secretsCache.get("openAiNativeApiKey"),
deepSeekApiKey: this.secretsCache.get("deepSeekApiKey"),
+1
View File
@@ -7,6 +7,7 @@ export type SecretKey =
| "awsSessionToken"
| "awsBedrockApiKey"
| "openAiApiKey"
| "ollamaApiKey"
| "geminiApiKey"
| "openAiNativeApiKey"
| "deepSeekApiKey"
+1
View File
@@ -524,6 +524,7 @@ export async function migrateWelcomeViewCompleted(context: vscode.ExtensionConte
config.awsRegion,
config.vertexProjectId,
config.openAiApiKey,
config.ollamaApiKey,
config.planModeOllamaModelId,
config.planModeLmStudioModelId,
config.actModeOllamaModelId,
+4
View File
@@ -133,6 +133,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
openAiApiKey,
openAiHeaders,
ollamaBaseUrl,
ollamaApiKey,
ollamaApiOptionsCtxNum,
lmStudioBaseUrl,
anthropicBaseUrl,
@@ -214,6 +215,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getSecret(context, "openAiApiKey") as Promise<string | undefined>,
getGlobalState(context, "openAiHeaders") as Promise<Record<string, string> | undefined>,
getGlobalState(context, "ollamaBaseUrl") as Promise<string | undefined>,
getSecret(context, "ollamaApiKey") as Promise<string | undefined>,
getGlobalState(context, "ollamaApiOptionsCtxNum") as Promise<string | undefined>,
getGlobalState(context, "lmStudioBaseUrl") as Promise<string | undefined>,
getGlobalState(context, "anthropicBaseUrl") as Promise<string | undefined>,
@@ -468,6 +470,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
openAiApiKey,
openAiHeaders: openAiHeaders || {},
ollamaBaseUrl,
ollamaApiKey,
ollamaApiOptionsCtxNum,
lmStudioBaseUrl,
anthropicBaseUrl,
@@ -617,6 +620,7 @@ export async function resetGlobalState(controller: Controller) {
"awsSessionToken",
"awsBedrockApiKey",
"openAiApiKey",
"ollamaApiKey",
"geminiApiKey",
"openAiNativeApiKey",
"deepSeekApiKey",
+3 -2
View File
@@ -93,6 +93,7 @@ export class ToolExecutor {
private browserSettings: BrowserSettings,
private cwd: string,
private taskId: string,
private uuid: string,
private mode: Mode,
private strictPlanModeEnabled: boolean,
@@ -2342,7 +2343,7 @@ export class ToolExecutor {
await this.say("completion_result", result, undefined, undefined, false)
await this.saveCheckpoint(true)
await addNewChangesFlagToLastCompletionResultMessage()
telemetryService.captureTaskCompleted(this.taskId)
telemetryService.captureTaskCompleted(this.taskId, this.uuid)
} else {
// we already sent a command message, meaning the complete completion message has also been sent
await this.saveCheckpoint(true)
@@ -2367,7 +2368,7 @@ export class ToolExecutor {
await this.say("completion_result", result, undefined, undefined, false)
await this.saveCheckpoint(true)
await addNewChangesFlagToLastCompletionResultMessage()
telemetryService.captureTaskCompleted(this.taskId)
telemetryService.captureTaskCompleted(this.taskId, this.uuid)
}
// we already sent completion_result says, an empty string asks relinquishes control over button and field
+24 -11
View File
@@ -35,6 +35,7 @@ import pTimeout from "p-timeout"
import pWaitFor from "p-wait-for"
import * as path from "path"
import * as vscode from "vscode"
import { v4 as uuidv4 } from "uuid"
import { HostProvider } from "@/hosts/host-provider"
import { ClineErrorType } from "@/services/error/ClineError"
@@ -97,6 +98,7 @@ type UserContent = Array<Anthropic.ContentBlockParam>
export class Task {
// Core task variables
readonly taskId: string
readonly uuid: string
private taskIsFavorited?: boolean
private cwd: string
@@ -219,6 +221,7 @@ export class Task {
// Initialize taskId first
if (historyItem) {
this.taskId = historyItem.id
this.uuid = historyItem.uuid ?? uuidv4()
this.taskIsFavorited = historyItem.isFavorited
this.taskState.conversationHistoryDeletedRange = historyItem.conversationHistoryDeletedRange
if (historyItem.checkpointTrackerErrorMessage) {
@@ -226,6 +229,7 @@ export class Task {
}
} else if (task || images || files) {
this.taskId = Date.now().toString()
this.uuid = uuidv4()
} else {
throw new Error("Either historyItem or task/images must be provided")
}
@@ -233,6 +237,7 @@ export class Task {
this.messageStateHandler = new MessageStateHandler({
context,
taskId: this.taskId,
uuid: this.uuid,
taskState: this.taskState,
taskIsFavorited: this.taskIsFavorited,
updateTaskHistory: this.updateTaskHistory,
@@ -306,10 +311,10 @@ export class Task {
// initialize telemetry
if (historyItem) {
// Open task from history
telemetryService.captureTaskRestarted(this.taskId, currentProvider)
telemetryService.captureTaskRestarted(this.taskId, this.uuid, currentProvider)
} else {
// New task started
telemetryService.captureTaskCreated(this.taskId, currentProvider)
telemetryService.captureTaskCreated(this.taskId, this.uuid, currentProvider)
}
this.toolExecutor = new ToolExecutor(
@@ -330,6 +335,7 @@ export class Task {
this.browserSettings,
cwd,
this.taskId,
this.uuid,
this.mode,
strictPlanModeEnabled,
this.say.bind(this),
@@ -2228,7 +2234,7 @@ export class Task {
content: userContent,
})
telemetryService.captureConversationTurnEvent(this.taskId, providerId, modelId, "user")
telemetryService.captureConversationTurnEvent(this.taskId, this.uuid, providerId, modelId, "user")
// since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message
const lastApiReqIndex = findLastIndex(this.messageStateHandler.getClineMessages(), (m) => m.say === "api_req_started")
@@ -2293,13 +2299,20 @@ export class Task {
})
await this.messageStateHandler.saveClineMessagesAndUpdateHistory()
telemetryService.captureConversationTurnEvent(this.taskId, providerId, this.api.getModel().id, "assistant", {
tokensIn: inputTokens,
tokensOut: outputTokens,
cacheWriteTokens,
cacheReadTokens,
totalCost,
})
telemetryService.captureConversationTurnEvent(
this.taskId,
this.uuid,
providerId,
this.api.getModel().id,
"assistant",
{
tokensIn: inputTokens,
tokensOut: outputTokens,
cacheWriteTokens,
cacheReadTokens,
totalCost,
},
)
// signals to provider that it can retrieve the saved messages from disk, as abortTask can not be awaited on in nature
this.taskState.didFinishAbortingStream = true
@@ -2468,7 +2481,7 @@ export class Task {
// need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response
let didEndLoop = false
if (assistantMessage.length > 0) {
telemetryService.captureConversationTurnEvent(this.taskId, providerId, modelId, "assistant", {
telemetryService.captureConversationTurnEvent(this.taskId, this.uuid, providerId, modelId, "assistant", {
tokensIn: inputTokens,
tokensOut: outputTokens,
cacheWriteTokens,
+4
View File
@@ -17,6 +17,7 @@ import { getCwd, getDesktopDir } from "@/utils/path"
interface MessageStateHandlerParams {
context: vscode.ExtensionContext
taskId: string
uuid: string
taskIsFavorited?: boolean
updateTaskHistory: (historyItem: HistoryItem) => Promise<HistoryItem[]>
taskState: TaskState
@@ -32,11 +33,13 @@ export class MessageStateHandler {
private updateTaskHistory: (historyItem: HistoryItem) => Promise<HistoryItem[]>
private context: vscode.ExtensionContext
private taskId: string
private uuid: string
private taskState: TaskState
constructor(params: MessageStateHandlerParams) {
this.context = params.context
this.taskId = params.taskId
this.uuid = params.uuid
this.taskState = params.taskState
this.taskIsFavorited = params.taskIsFavorited ?? false
this.updateTaskHistory = params.updateTaskHistory
@@ -89,6 +92,7 @@ export class MessageStateHandler {
const cwd = await getCwd(getDesktopDir())
await this.updateTaskHistory({
id: this.taskId,
uuid: this.uuid,
ts: lastRelevantMessage.ts,
task: taskMessage.text ?? "",
tokensIn: apiMetrics.totalTokensIn,
+22 -2
View File
@@ -1,5 +1,6 @@
import { HostProvider } from "@/hosts/host-provider"
import { DiffViewProvider } from "@/integrations/editor/DiffViewProvider"
import { DiagnosticSeverity } from "@/shared/proto/host/workspace"
import { status } from "@grpc/grpc-js"
export class ExternalDiffViewProvider extends DiffViewProvider {
@@ -78,8 +79,27 @@ export class ExternalDiffViewProvider extends DiffViewProvider {
}
protected override async getNewDiagnosticProblems(): Promise<string> {
console.log(`Called ExternalDiffViewProvider.getNewDiagnosticProblems() stub`)
return ""
// Get diagnostics using the HostBridge workspace service
const response = await HostProvider.workspace.getDiagnostics({})
if (response.fileDiagnostics.length === 0) {
return ""
}
let result = ""
for (const fileDiagnostics of response.fileDiagnostics) {
const errors = fileDiagnostics.diagnostics.filter((d) => d.severity === DiagnosticSeverity.DIAGNOSTIC_ERROR)
if (errors.length > 0) {
result += `\n\n${fileDiagnostics.filePath}`
for (const diagnostic of errors) {
const line = (diagnostic.range?.start?.line || 0) + 1 // Proto lines are 0-indexed
const source = diagnostic.source ? `${diagnostic.source} ` : ""
result += `\n- [${source}Error] Line ${line}: ${diagnostic.message}`
}
}
}
return result.trim()
}
protected override async closeDiffView(): Promise<void> {
+1 -1
View File
@@ -3,7 +3,7 @@ import * as path from "path"
import * as vscode from "vscode"
import { DecorationController } from "@/hosts/vscode/DecorationController"
import { DiffViewProvider } from "@integrations/editor/DiffViewProvider"
import { diagnosticsToProblemsString, getNewDiagnostics } from "@/integrations/diagnostics"
import { diagnosticsToProblemsString, getNewDiagnostics } from "./diagnostics"
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
+109
View File
@@ -0,0 +1,109 @@
import * as vscode from "vscode"
import * as path from "path"
import deepEqual from "fast-deep-equal"
import { getCwd } from "@/utils/path"
export function getNewDiagnostics(
oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][],
newDiagnostics: [vscode.Uri, vscode.Diagnostic[]][],
): [vscode.Uri, vscode.Diagnostic[]][] {
const newProblems: [vscode.Uri, vscode.Diagnostic[]][] = []
const oldMap = new Map(oldDiagnostics)
for (const [uri, newDiags] of newDiagnostics) {
const oldDiags = oldMap.get(uri) || []
const newProblemsForUri = newDiags.filter((newDiag) => !oldDiags.some((oldDiag) => deepEqual(oldDiag, newDiag)))
if (newProblemsForUri.length > 0) {
newProblems.push([uri, newProblemsForUri])
}
}
return newProblems
}
// Usage:
// const oldDiagnostics = // ... your old diagnostics array
// const newDiagnostics = // ... your new diagnostics array
// const newProblems = getNewDiagnostics(oldDiagnostics, newDiagnostics);
// Example usage with mocks:
//
// // Mock old diagnostics
// const oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
// [vscode.Uri.file("/path/to/file1.ts"), [
// new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Old error in file1", vscode.DiagnosticSeverity.Error)
// ]],
// [vscode.Uri.file("/path/to/file2.ts"), [
// new vscode.Diagnostic(new vscode.Range(5, 5, 5, 15), "Old warning in file2", vscode.DiagnosticSeverity.Warning)
// ]]
// ];
//
// // Mock new diagnostics
// const newDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
// [vscode.Uri.file("/path/to/file1.ts"), [
// new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Old error in file1", vscode.DiagnosticSeverity.Error),
// new vscode.Diagnostic(new vscode.Range(2, 2, 2, 12), "New error in file1", vscode.DiagnosticSeverity.Error)
// ]],
// [vscode.Uri.file("/path/to/file2.ts"), [
// new vscode.Diagnostic(new vscode.Range(5, 5, 5, 15), "Old warning in file2", vscode.DiagnosticSeverity.Warning)
// ]],
// [vscode.Uri.file("/path/to/file3.ts"), [
// new vscode.Diagnostic(new vscode.Range(1, 1, 1, 11), "New error in file3", vscode.DiagnosticSeverity.Error)
// ]]
// ];
//
// const newProblems = getNewDiagnostics(oldDiagnostics, newDiagnostics);
//
// console.log("New problems:");
// for (const [uri, diagnostics] of newProblems) {
// console.log(`File: ${uri.fsPath}`);
// for (const diagnostic of diagnostics) {
// console.log(`- ${diagnostic.message} (${diagnostic.range.start.line}:${diagnostic.range.start.character})`);
// }
// }
//
// // Expected output:
// // New problems:
// // File: /path/to/file1.ts
// // - New error in file1 (2:2)
// // File: /path/to/file3.ts
// // - New error in file3 (1:1)
// will return empty string if no problems with the given severity are found
export async function diagnosticsToProblemsString(
diagnostics: [vscode.Uri, vscode.Diagnostic[]][],
severities: vscode.DiagnosticSeverity[],
): Promise<string> {
const cwd = await getCwd()
let result = ""
for (const [uri, fileDiagnostics] of diagnostics) {
const problems = fileDiagnostics.filter((d) => severities.includes(d.severity))
if (problems.length > 0) {
result += `\n\n${path.relative(cwd, uri.fsPath).toPosix()}`
for (const diagnostic of problems) {
let label: string
switch (diagnostic.severity) {
case vscode.DiagnosticSeverity.Error:
label = "Error"
break
case vscode.DiagnosticSeverity.Warning:
label = "Warning"
break
case vscode.DiagnosticSeverity.Information:
label = "Information"
break
case vscode.DiagnosticSeverity.Hint:
label = "Hint"
break
default:
label = "Diagnostic"
}
const line = diagnostic.range.start.line + 1 // VSCode lines are 0-indexed
const source = diagnostic.source ? `${diagnostic.source} ` : ""
result += `\n- [${source}${label}] Line ${line}: ${diagnostic.message}`
}
}
}
return result.trim()
}
@@ -0,0 +1,69 @@
import * as vscode from "vscode"
import {
GetDiagnosticsRequest,
GetDiagnosticsResponse,
FileDiagnostics,
Diagnostic,
DiagnosticRange,
DiagnosticPosition,
DiagnosticSeverity,
} from "@/shared/proto/host/workspace"
export async function getDiagnostics(request: GetDiagnosticsRequest): Promise<GetDiagnosticsResponse> {
// Get all diagnostics from VS Code
const vscodeAllDiagnostics = vscode.languages.getDiagnostics()
const fileDiagnostics: FileDiagnostics[] = []
for (const [uri, diagnostics] of vscodeAllDiagnostics) {
if (diagnostics.length > 0) {
const convertedDiagnostics: Diagnostic[] = diagnostics.map((vsDiagnostic) => {
// Convert VS Code severity to proto severity
let severity: DiagnosticSeverity
switch (vsDiagnostic.severity) {
case vscode.DiagnosticSeverity.Error:
severity = DiagnosticSeverity.DIAGNOSTIC_ERROR
break
case vscode.DiagnosticSeverity.Warning:
severity = DiagnosticSeverity.DIAGNOSTIC_WARNING
break
case vscode.DiagnosticSeverity.Information:
severity = DiagnosticSeverity.DIAGNOSTIC_INFORMATION
break
case vscode.DiagnosticSeverity.Hint:
severity = DiagnosticSeverity.DIAGNOSTIC_HINT
break
default:
severity = DiagnosticSeverity.DIAGNOSTIC_ERROR
}
return Diagnostic.create({
message: vsDiagnostic.message,
range: DiagnosticRange.create({
start: DiagnosticPosition.create({
line: vsDiagnostic.range.start.line,
character: vsDiagnostic.range.start.character,
}),
end: DiagnosticPosition.create({
line: vsDiagnostic.range.end.line,
character: vsDiagnostic.range.end.character,
}),
}),
severity: severity,
source: vsDiagnostic.source || undefined,
})
})
fileDiagnostics.push(
FileDiagnostics.create({
filePath: uri.fsPath,
diagnostics: convertedDiagnostics,
}),
)
}
}
return GetDiagnosticsResponse.create({
fileDiagnostics: fileDiagnostics,
})
}
+26 -82
View File
@@ -1,105 +1,49 @@
import * as vscode from "vscode"
import * as path from "path"
import deepEqual from "fast-deep-equal"
import { getCwd } from "@/utils/path"
import { HostProvider } from "@/hosts/host-provider"
import { GetDiagnosticsRequest, DiagnosticSeverity } from "@/shared/proto/host/workspace"
import { Metadata } from "@/shared/proto/cline/common"
export function getNewDiagnostics(
oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][],
newDiagnostics: [vscode.Uri, vscode.Diagnostic[]][],
): [vscode.Uri, vscode.Diagnostic[]][] {
const newProblems: [vscode.Uri, vscode.Diagnostic[]][] = []
const oldMap = new Map(oldDiagnostics)
/**
* Host-agnostic function to get workspace problems as a formatted string
* Used by @problems mention for cross-host compatibility
*/
export async function getWorkspaceProblemsString(): Promise<string> {
const response = await HostProvider.workspace.getDiagnostics(
GetDiagnosticsRequest.create({
metadata: Metadata.create({}),
}),
)
for (const [uri, newDiags] of newDiagnostics) {
const oldDiags = oldMap.get(uri) || []
const newProblemsForUri = newDiags.filter((newDiag) => !oldDiags.some((oldDiag) => deepEqual(oldDiag, newDiag)))
if (newProblemsForUri.length > 0) {
newProblems.push([uri, newProblemsForUri])
}
if (response.fileDiagnostics.length === 0) {
return "No errors or warnings detected."
}
return newProblems
}
// Usage:
// const oldDiagnostics = // ... your old diagnostics array
// const newDiagnostics = // ... your new diagnostics array
// const newProblems = getNewDiagnostics(oldDiagnostics, newDiagnostics);
// Example usage with mocks:
//
// // Mock old diagnostics
// const oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
// [vscode.Uri.file("/path/to/file1.ts"), [
// new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Old error in file1", vscode.DiagnosticSeverity.Error)
// ]],
// [vscode.Uri.file("/path/to/file2.ts"), [
// new vscode.Diagnostic(new vscode.Range(5, 5, 5, 15), "Old warning in file2", vscode.DiagnosticSeverity.Warning)
// ]]
// ];
//
// // Mock new diagnostics
// const newDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
// [vscode.Uri.file("/path/to/file1.ts"), [
// new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Old error in file1", vscode.DiagnosticSeverity.Error),
// new vscode.Diagnostic(new vscode.Range(2, 2, 2, 12), "New error in file1", vscode.DiagnosticSeverity.Error)
// ]],
// [vscode.Uri.file("/path/to/file2.ts"), [
// new vscode.Diagnostic(new vscode.Range(5, 5, 5, 15), "Old warning in file2", vscode.DiagnosticSeverity.Warning)
// ]],
// [vscode.Uri.file("/path/to/file3.ts"), [
// new vscode.Diagnostic(new vscode.Range(1, 1, 1, 11), "New error in file3", vscode.DiagnosticSeverity.Error)
// ]]
// ];
//
// const newProblems = getNewProblems(oldDiagnostics, newDiagnostics);
//
// console.log("New problems:");
// for (const [uri, diagnostics] of newProblems) {
// console.log(`File: ${uri.fsPath}`);
// for (const diagnostic of diagnostics) {
// console.log(`- ${diagnostic.message} (${diagnostic.range.start.line}:${diagnostic.range.start.character})`);
// }
// }
//
// // Expected output:
// // New problems:
// // File: /path/to/file1.ts
// // - New error in file1 (2:2)
// // File: /path/to/file3.ts
// // - New error in file3 (1:1)
// will return empty string if no problems with the given severity are found
export async function diagnosticsToProblemsString(
diagnostics: [vscode.Uri, vscode.Diagnostic[]][],
severities: vscode.DiagnosticSeverity[],
): Promise<string> {
const cwd = await getCwd()
let result = ""
for (const [uri, fileDiagnostics] of diagnostics) {
const problems = fileDiagnostics.filter((d) => severities.includes(d.severity))
for (const fileDiagnostics of response.fileDiagnostics) {
const problems = fileDiagnostics.diagnostics.filter(
(d) => d.severity === DiagnosticSeverity.DIAGNOSTIC_ERROR || d.severity === DiagnosticSeverity.DIAGNOSTIC_WARNING,
)
if (problems.length > 0) {
result += `\n\n${path.relative(cwd, uri.fsPath).toPosix()}`
result += `\n\n${fileDiagnostics.filePath}`
for (const diagnostic of problems) {
let label: string
switch (diagnostic.severity) {
case vscode.DiagnosticSeverity.Error:
case DiagnosticSeverity.DIAGNOSTIC_ERROR:
label = "Error"
break
case vscode.DiagnosticSeverity.Warning:
case DiagnosticSeverity.DIAGNOSTIC_WARNING:
label = "Warning"
break
case vscode.DiagnosticSeverity.Information:
case DiagnosticSeverity.DIAGNOSTIC_INFORMATION:
label = "Information"
break
case vscode.DiagnosticSeverity.Hint:
case DiagnosticSeverity.DIAGNOSTIC_HINT:
label = "Hint"
break
default:
label = "Diagnostic"
}
const line = diagnostic.range.start.line + 1 // VSCode lines are 0-indexed
const line = (diagnostic.range?.start?.line || 0) + 1 // Proto lines are 0-indexed
const source = diagnostic.source ? `${diagnostic.source} ` : ""
result += `\n- [${source}${label}] Line ${line}: ${diagnostic.message}`
}
@@ -181,10 +181,10 @@ export class TelemetryService {
* @param taskId Unique identifier for the new task
* @param apiProvider Optional API provider
*/
public captureTaskCreated(taskId: string, apiProvider?: string) {
public captureTaskCreated(taskId: string, uuid: string, apiProvider?: string) {
this.capture({
event: TelemetryService.EVENTS.TASK.CREATED,
properties: { taskId, apiProvider },
properties: { taskId, uuid, apiProvider },
})
}
@@ -193,10 +193,10 @@ export class TelemetryService {
* @param taskId Unique identifier for the new task
* @param apiProvider Optional API provider
*/
public captureTaskRestarted(taskId: string, apiProvider?: string) {
public captureTaskRestarted(taskId: string, uuid: string, apiProvider?: string) {
this.capture({
event: TelemetryService.EVENTS.TASK.RESTARTED,
properties: { taskId, apiProvider },
properties: { taskId, uuid, apiProvider },
})
}
@@ -204,10 +204,10 @@ export class TelemetryService {
* Records when cline calls the task completion_result tool signifying that cline is done with the task
* @param taskId Unique identifier for the task
*/
public captureTaskCompleted(taskId: string) {
public captureTaskCompleted(taskId: string, uuid: string) {
this.capture({
event: TelemetryService.EVENTS.TASK.COMPLETED,
properties: { taskId },
properties: { taskId, uuid },
})
}
@@ -221,6 +221,7 @@ export class TelemetryService {
*/
public captureConversationTurnEvent(
taskId: string,
uuid: string,
provider: string = "unknown",
model: string = "unknown",
source: "user" | "assistant",
@@ -233,13 +234,14 @@ export class TelemetryService {
} = {},
) {
// Ensure required parameters are provided
if (!taskId || !provider || !model || !source) {
if (!taskId || !uuid || !provider || !model || !source) {
console.warn("TelemetryService: Missing required parameters for message capture")
return
}
const properties: Record<string, unknown> = {
taskId,
uuid,
provider,
model,
source,
@@ -598,6 +600,7 @@ export class TelemetryService {
*/
public captureProviderApiError(args: {
taskId: string
uuid: string
model: string
errorMessage: string
errorStatus?: number | undefined
+1
View File
@@ -1,5 +1,6 @@
export type HistoryItem = {
id: string
uuid?: string // UUID for better tracking and metrics
ts: number
task: string
tokensIn: number
+11
View File
@@ -62,6 +62,7 @@ export interface ApiHandlerOptions {
openAiBaseUrl?: string
openAiApiKey?: string
ollamaBaseUrl?: string
ollamaApiKey?: string
ollamaApiOptionsCtxNum?: string
lmStudioBaseUrl?: string
geminiApiKey?: string
@@ -508,6 +509,16 @@ export const vertexModels = {
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
},
"claude-opus-4-1@20250805": {
maxTokens: 8192,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 15.0,
outputPrice: 75.0,
cacheWritesPrice: 18.75,
cacheReadsPrice: 1.5,
},
"claude-opus-4@20250514": {
maxTokens: 8192,
contextWindow: 200_000,
@@ -354,6 +354,7 @@ export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoA
openAiBaseUrl: config.openAiBaseUrl,
openAiApiKey: config.openAiApiKey,
ollamaBaseUrl: config.ollamaBaseUrl,
ollamaApiKey: config.ollamaApiKey,
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
lmStudioBaseUrl: config.lmStudioBaseUrl,
geminiApiKey: config.geminiApiKey,
@@ -485,6 +486,7 @@ export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguratio
openAiBaseUrl: protoConfig.openAiBaseUrl,
openAiApiKey: protoConfig.openAiApiKey,
ollamaBaseUrl: protoConfig.ollamaBaseUrl,
ollamaApiKey: protoConfig.ollamaApiKey,
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
lmStudioBaseUrl: protoConfig.lmStudioBaseUrl,
geminiApiKey: protoConfig.geminiApiKey,
@@ -34,6 +34,7 @@ export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfig
openaiBaseUrl: config.openAiBaseUrl,
openaiApiKey: config.openAiApiKey,
ollamaBaseUrl: config.ollamaBaseUrl,
ollamaApiKey: config.ollamaApiKey,
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
lmStudioBaseUrl: config.lmStudioBaseUrl,
geminiApiKey: config.geminiApiKey,
@@ -159,6 +160,7 @@ export function convertProtoApiConfigurationToApiConfiguration(protoConfig: Prot
openAiBaseUrl: protoConfig.openaiBaseUrl,
openAiApiKey: protoConfig.openaiApiKey,
ollamaBaseUrl: protoConfig.ollamaBaseUrl,
ollamaApiKey: protoConfig.ollamaApiKey,
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
lmStudioBaseUrl: protoConfig.lmStudioBaseUrl,
geminiApiKey: protoConfig.geminiApiKey,
+5 -1
View File
@@ -39,6 +39,7 @@ interface ChatViewProps {
// Use constants from the imported module
const MAX_IMAGES_AND_FILES_PER_MESSAGE = CHAT_CONSTANTS.MAX_IMAGES_AND_FILES_PER_MESSAGE
const QUICK_WINS_HISTORY_THRESHOLD = 3
const IS_STANDALONE = window?.__is_standalone__ ?? false
@@ -51,8 +52,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
telemetrySetting,
navigateToChat,
mode,
userInfo,
} = useExtensionState()
const shouldShowQuickWins = false // !taskHistory || taskHistory.length < QUICK_WINS_HISTORY_THRESHOLD
const isProdHostedApp = userInfo?.apiBaseUrl === "https://app.cline.bot"
const shouldShowQuickWins = isProdHostedApp && (!taskHistory || taskHistory.length < QUICK_WINS_HISTORY_THRESHOLD)
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages.slice(1))), [messages])
@@ -25,7 +25,7 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
<div className="overflow-y-auto flex flex-col pb-2.5">
{telemetrySetting === "unset" && <TelemetryBanner />}
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
<HomeHeader />
<HomeHeader shouldShowQuickWins={shouldShowQuickWins} />
{!shouldShowQuickWins && taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
</div>
<SuggestedTasks shouldShowQuickWins={shouldShowQuickWins} />
@@ -50,16 +50,16 @@ const featuredModels = [
description: "Recommended for agentic coding in Cline",
label: "Best",
},
{
id: "anthropic/claude-opus-4.1",
description: "Anthropic's newest model topping benchmarks",
label: "New",
},
{
id: "google/gemini-2.5-pro",
description: "Large 1M context window, great value",
label: "Trending",
},
{
id: "moonshotai/kimi-k2",
description: "Open source model topping coding benchmarks",
label: "New",
},
]
const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup, currentMode }) => {
@@ -211,6 +211,7 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup,
const showBudgetSlider = useMemo(() => {
return (
selectedModelId?.toLowerCase().includes("claude-sonnet-4") ||
selectedModelId?.toLowerCase().includes("claude-opus-4.1") ||
selectedModelId?.toLowerCase().includes("claude-opus-4") ||
selectedModelId?.toLowerCase().includes("claude-3-7-sonnet") ||
selectedModelId?.toLowerCase().includes("claude-3.7-sonnet") ||
@@ -14,6 +14,7 @@ export const SUPPORTED_ANTHROPIC_THINKING_MODELS = [
"claude-3-7-sonnet-20250219",
"claude-sonnet-4-20250514",
"claude-opus-4-20250514",
"claude-opus-4-1-20250805",
]
/**
@@ -314,11 +314,14 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
{(selectedModelId === "anthropic.claude-3-7-sonnet-20250219-v1:0" ||
selectedModelId === "anthropic.claude-sonnet-4-20250514-v1:0" ||
selectedModelId === "anthropic.claude-opus-4-1-20250805-v1:0" ||
selectedModelId === "anthropic.claude-opus-4-20250514-v1:0" ||
(modeFields.awsBedrockCustomSelected &&
modeFields.awsBedrockCustomModelBaseId === "anthropic.claude-3-7-sonnet-20250219-v1:0") ||
(modeFields.awsBedrockCustomSelected &&
modeFields.awsBedrockCustomModelBaseId === "anthropic.claude-sonnet-4-20250514-v1:0") ||
(modeFields.awsBedrockCustomSelected &&
modeFields.awsBedrockCustomModelBaseId === "anthropic.claude-opus-4-1-20250805-v1:0") ||
(modeFields.awsBedrockCustomSelected &&
modeFields.awsBedrockCustomModelBaseId === "anthropic.claude-opus-4-20250514-v1:0")) && (
<ThinkingBudgetSlider currentMode={currentMode} />
@@ -2,6 +2,7 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { useState, useCallback, useEffect } from "react"
import { useInterval } from "react-use"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { ApiKeyField } from "../common/ApiKeyField"
import { ModelsServiceClient } from "@/services/grpc-client"
import { StringRequest } from "@shared/proto/cline/common"
import OllamaModelPicker from "../OllamaModelPicker"
@@ -62,6 +63,16 @@ export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: Ollam
label="Use custom base URL"
/>
{apiConfiguration?.ollamaBaseUrl && (
<ApiKeyField
initialValue={apiConfiguration?.ollamaApiKey || ""}
onChange={(value) => handleFieldChange("ollamaApiKey", value)}
providerName="Ollama"
placeholder="Enter API Key (optional)..."
helpText="Optional API key for authenticated Ollama instances or cloud services. Leave empty for local installations."
/>
)}
{/* Model selection - use filterable picker */}
<label htmlFor="ollama-model-selection">
<span style={{ fontWeight: 500 }}>Model</span>
@@ -23,6 +23,7 @@ const SUPPORTED_THINKING_MODELS = [
"claude-3-7-sonnet@20250219",
"claude-sonnet-4@20250514",
"claude-opus-4@20250514",
"claude-opus-4-1@20250805",
"gemini-2.5-flash",
"gemini-2.5-pro",
"gemini-2.5-flash-lite-preview-06-17",
@@ -1,7 +1,21 @@
import ClineLogoVariable from "@/assets/ClineLogoVariable"
import HeroTooltip from "@/components/common/HeroTooltip"
import { UiServiceClient } from "@/services/grpc-client"
import { EmptyRequest } from "@shared/proto/cline/common"
interface HomeHeaderProps {
shouldShowQuickWins?: boolean
}
const HomeHeader = ({ shouldShowQuickWins = false }: HomeHeaderProps) => {
const handleTakeATour = async () => {
try {
await UiServiceClient.openWalkthrough(EmptyRequest.create())
} catch (error) {
console.error("Error opening walkthrough:", error)
}
}
const HomeHeader = () => {
return (
<div className="flex flex-col items-center mb-5">
<div className="my-5">
@@ -21,6 +35,16 @@ const HomeHeader = () => {
/>
</HeroTooltip>
</div>
{shouldShowQuickWins && (
<div className="mt-4">
<button
onClick={handleTakeATour}
className="flex items-center gap-2 px-4 py-2 rounded-full border border-[var(--vscode-panel-border)] bg-white/[0.02] hover:bg-[var(--vscode-list-hoverBackground)] transition-colors duration-150 ease-in-out text-[var(--vscode-editor-foreground)] text-sm font-medium cursor-pointer">
Take a Tour
<span className="codicon codicon-play scale-90"></span>
</button>
</div>
)}
</div>
)
}
@@ -7,7 +7,7 @@ interface QuickWinCardProps {
}
const renderIcon = (iconName?: string) => {
if (!iconName) return <span className="codicon codicon-rocket text-lg"></span>
if (!iconName) return <span className="codicon codicon-rocket !text-[28px] !leading-[1]"></span>
let iconClass = "codicon-rocket"
switch (iconName) {
@@ -23,21 +23,25 @@ const renderIcon = (iconName?: string) => {
default:
break
}
return <span className={`codicon ${iconClass} text-lg`}></span>
return <span className={`codicon ${iconClass} !text-[28px] !leading-[1]`}></span>
}
const QuickWinCard: React.FC<QuickWinCardProps> = ({ task, onExecute }) => {
return (
<div
className="flex items-center p-1 space-x-1.5 rounded-full cursor-pointer group transition-colors duration-150 ease-in-out bg-[var(--vscode-sideBar-background)] border border-[var(--vscode-panel-border)] hover:bg-[var(--vscode-list-hoverBackground)]"
className="flex items-center mb-2 py-0 px-5 space-x-3 rounded-full cursor-pointer group transition-colors duration-150 ease-in-out bg-white/[0.02] border border-[var(--vscode-panel-border)] hover:bg-[var(--vscode-list-hoverBackground)]"
onClick={() => onExecute()}>
<div className="flex-shrink-0 flex items-center justify-center w-5 h-5 text-[var(--vscode-icon-foreground)]">
<div className="flex-shrink-0 flex items-center justify-center w-6 h-6 text-[var(--vscode-icon-foreground)]">
{renderIcon(task.icon)}
</div>
<div className="flex-grow min-w-0">
<h3 className="text-xs font-medium truncate text-[var(--vscode-editor-foreground)]">{task.title}</h3>
<p className="text-xs truncate text-[var(--vscode-descriptionForeground)]">{task.description}</p>
<h3 className="text-sm font-medium truncate text-[var(--vscode-editor-foreground)] leading-tight mb-0 mt-0 pt-3">
{task.title}
</h3>
<p className="text-xs truncate text-[var(--vscode-descriptionForeground)] leading-tight mt-[1px]">
{task.description}
</p>
</div>
</div>
)
@@ -13,8 +13,8 @@ export const SuggestedTasks: React.FC<{ shouldShowQuickWins: boolean }> = ({ sho
return (
<div className="px-4 pt-1 pb-3 select-none">
{" "}
<h2 className="text-sm font-medium mb-2.5 text-center text-[var(--vscode-editor-foreground)]">
Quick <span className="text-[var(--vscode-terminal-ansiBrightCyan)]">[Wins]</span> with Cline
<h2 className="text-sm font-medium mb-2.5 text-center text-gray">
Quick <span className="text-white">[Wins]</span> with Cline
</h2>
<div className="flex flex-col space-y-1">
{" "}
@@ -12,7 +12,7 @@ export const quickWinTasks: QuickWinTask[] = [
{
id: "nextjs_notetaking_app",
title: "Build a Next.js App",
description: "Create a beautiful notetaking application with Next.js and Tailwind CSS.",
description: "Create a beautiful notetaking app with Next.js and Tailwind",
icon: "WebAppIcon",
actionCommand: "cline/createNextJsApp",
prompt: "Make a beautiful Next.js notetaking app, using Tailwind CSS for styling. Set up the basic structure and a simple UI for adding and viewing notes.",
@@ -21,10 +21,10 @@ export const quickWinTasks: QuickWinTask[] = [
{
id: "terminal_cli_tool",
title: "Craft a CLI Tool",
description: "Develop a powerful terminal CLI to automate a cool task.",
description: "Develop a powerful terminal CLI to automate a cool task",
icon: "TerminalIcon",
actionCommand: "cline/createCliTool",
prompt: "Make a terminal CLI tool using Node.js that fetches the current weather for a given city using a free weather API and displays it in a user-friendly format.",
prompt: "Make a terminal CLI tool using Node.js that organizes files in a directory by type, size, or date. It should have options to sort files into folders, show file statistics, find duplicates, and clean up empty directories. Include colorful output and progress indicators.",
buttonText: ">",
},
{