From 82f1f79b3e5695cd127267286a0080b2e3770462 Mon Sep 17 00:00:00 2001
From: Quentin Machu
Date: Thu, 20 Feb 2025 11:42:59 +0800
Subject: [PATCH 01/87] fix: make AWS Bedrock authentication predictable
Anthropic's Bedrock SDK creates an AWS credential provider chain [1]
for each request it needs to sign. By doing so right before actually
having to sign the request, it can utilize any session created after
VSCode launched (i.e. outside of the process), and it can renew the
sessions every time necessary transparently for the user.
Cline, on the other hand, by transforming the provided AWS_PROFILE into
a key / secret / session, as part of client initialization, completely
short-circuits this, making it very difficult for users in companies where
sessions are short-lived. Furthermore, Cline would silently ignore the
provided AWS_PROFILE if there isn't a current/non-expired session at the time
of initialization, pass null keys to the Bedrock SDK, which would then
make use the default profile, which may not be configured or authorized
to use AWS Bedrock (as most AWS SSO hub accounts would). From the
perspective of the user, this would manifest itself as an "supported
country" error or unhelpful errors that are basically impossible to
debug without attaching a debugger to Cline.. and such developers may
end up reaching out to their DevOps/IT teams for help, which also could
turn into a waste of time.
This PR addresses the aforementioned issues by resolving the credentials on
every invocation.
1: https://github.com/anthropics/anthropic-sdk-typescript/blob/61b55599d50d9c93840e4736cb756cb3a62b0696/packages/bedrock-sdk/src/auth.ts#L19
---
.changeset/cold-shirts-deny.md | 5 ++
src/api/providers/bedrock.ts | 132 ++++++++++++++++++---------------
2 files changed, 76 insertions(+), 61 deletions(-)
create mode 100644 .changeset/cold-shirts-deny.md
diff --git a/.changeset/cold-shirts-deny.md b/.changeset/cold-shirts-deny.md
new file mode 100644
index 0000000000..13459ef8b6
--- /dev/null
+++ b/.changeset/cold-shirts-deny.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+fix: make AWS Bedrock authentication predictable
diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts
index 04de4af2b2..75a0c980b1 100644
--- a/src/api/providers/bedrock.ts
+++ b/src/api/providers/bedrock.ts
@@ -3,79 +3,25 @@ import { Anthropic } from "@anthropic-ai/sdk"
import { ApiHandler } from "../"
import { ApiHandlerOptions, bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "../../shared/api"
import { ApiStream } from "../transform/stream"
-import { fromIni } from "@aws-sdk/credential-providers"
+import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
export class AwsBedrockHandler implements ApiHandler {
private options: ApiHandlerOptions
- private client: AnthropicBedrock | any
- private initializationPromise: Promise
constructor(options: ApiHandlerOptions) {
this.options = options
- this.initializationPromise = this.initializeClient()
- }
-
- private async initializeClient() {
- let clientConfig: any = {
- awsRegion: this.options.awsRegion || "us-east-1",
- }
- try {
- if (this.options.awsUseProfile) {
- // Use profile-based credentials if enabled
- // Use named profile, defaulting to 'default' if not specified
- var credentials: any
- if (this.options.awsProfile) {
- credentials = await fromIni({
- profile: this.options.awsProfile,
- ignoreCache: true,
- })()
- } else {
- credentials = await fromIni({
- ignoreCache: true,
- })()
- }
- clientConfig.awsAccessKey = credentials.accessKeyId
- clientConfig.awsSecretKey = credentials.secretAccessKey
- clientConfig.awsSessionToken = credentials.sessionToken
- } else if (this.options.awsAccessKey && this.options.awsSecretKey) {
- // Use direct credentials if provided
- clientConfig.awsAccessKey = this.options.awsAccessKey
- clientConfig.awsSecretKey = this.options.awsSecretKey
- if (this.options.awsSessionToken) {
- clientConfig.awsSessionToken = this.options.awsSessionToken
- }
- }
- } catch (error) {
- console.error("Failed to initialize Bedrock client:", error)
- throw error
- } finally {
- this.client = new AnthropicBedrock(clientConfig)
- }
}
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
// cross region inference requires prefixing the model id with the region
- let modelId: string
- if (this.options.awsUseCrossRegionInference) {
- let regionPrefix = (this.options.awsRegion || "").slice(0, 3)
- switch (regionPrefix) {
- case "us-":
- modelId = `us.${this.getModel().id}`
- break
- case "eu-":
- modelId = `eu.${this.getModel().id}`
- break
- default:
- // cross region inference is not supported in this region, falling back to default model
- modelId = this.getModel().id
- break
- }
- } else {
- modelId = this.getModel().id
- }
+ let modelId = await this.getModelId()
- const stream = await this.client.messages.create({
+ // create anthropic client, using sessions created or renewed after this handler's
+ // initialization, and allowing for session renewal if necessary as well
+ let client = await this.getClient()
+
+ const stream = await client.messages.create({
model: modelId,
max_tokens: this.getModel().info.maxTokens || 8192,
temperature: 0,
@@ -142,4 +88,68 @@ export class AwsBedrockHandler implements ApiHandler {
info: bedrockModels[bedrockDefaultModelId],
}
}
+
+ private async getClient(): Promise {
+ // Create AWS credentials by executing a an AWS provider chain exactly as the
+ // Anthropic SDK does it, by wrapping the default chain into a temporary process
+ // environment.
+ const providerChain = fromNodeProviderChain()
+ const credentials = await AwsBedrockHandler.withTempEnv(
+ () => {
+ AwsBedrockHandler.setEnv("AWS_REGION", this.options.awsRegion)
+ AwsBedrockHandler.setEnv("AWS_ACCESS_KEY_ID", this.options.awsAccessKey)
+ AwsBedrockHandler.setEnv("AWS_SECRET_ACCESS_KEY", this.options.awsSecretKey)
+ AwsBedrockHandler.setEnv("AWS_SESSION_TOKEN", this.options.awsSessionToken)
+ AwsBedrockHandler.setEnv("AWS_PROFILE", this.options.awsProfile)
+ },
+ () => providerChain(),
+ )
+
+ // Return an AnthropicBedrock client with the resolved/assumed credentials.
+ //
+ // When AnthropicBedrock creates its AWS client, the chain will execute very
+ // fast as the access/secret keys will already be already provided, and have
+ // a higher precedence than the profiles.
+ return new AnthropicBedrock({
+ awsAccessKey: credentials.accessKeyId,
+ awsSecretKey: credentials.secretAccessKey,
+ awsSessionToken: credentials.sessionToken,
+ awsRegion: this.options.awsRegion || "us-east-1",
+ })
+ }
+
+ private async getModelId(): Promise {
+ if (this.options.awsUseCrossRegionInference) {
+ let regionPrefix = (this.options.awsRegion || "").slice(0, 3)
+ switch (regionPrefix) {
+ case "us-":
+ return `us.${this.getModel().id}`
+ case "eu-":
+ return `eu.${this.getModel().id}`
+ break
+ default:
+ // cross region inference is not supported in this region, falling back to default model
+ return this.getModel().id
+ break
+ }
+ }
+ return this.getModel().id
+ }
+
+ private static async withTempEnv(updateEnv: () => void, fn: () => Promise): Promise {
+ const previousEnv = { ...process.env }
+
+ try {
+ updateEnv()
+ return await fn()
+ } finally {
+ process.env = previousEnv
+ }
+ }
+
+ private static async setEnv(key: string, value: string | undefined) {
+ if (key !== "" && value !== undefined) {
+ process.env[key] = value
+ }
+ }
}
From f8e4bfdbeb14b13bc0a258c0e2fd48c20a59d868 Mon Sep 17 00:00:00 2001
From: Deepak Mangla
Date: Fri, 21 Feb 2025 13:35:33 +0530
Subject: [PATCH 02/87] Fix 'Contributing to cline' URL in docs. (#1891)
---
docs/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/README.md b/docs/README.md
index c2220d6d4d..5a57c38ba7 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -28,7 +28,7 @@ Welcome to the Cline documentation - your comprehensive guide to using and exten
- **Interested in contributing?** We welcome your input:
- Feel free to submit a pull request
- - [Contribution Guidelines](CONTRIBUTING.md)
+ - [Contribution Guidelines](../CONTRIBUTING.md)
## Additional Resources
From b0446f7bac57b7bf299c8b59b85597b2eb46f38d Mon Sep 17 00:00:00 2001
From: Dennis Bartlett
Date: Fri, 21 Feb 2025 02:21:00 -0600
Subject: [PATCH 03/87] Add IS_DEV and Hot Reloading to debug. (#1895)
* Add IS_DEV and Hot Reloading to debug.
* Changeset Added
* Clean logs
---
.changeset/yellow-paws-chew.md | 5 +++++
.vscode/launch.json | 6 +++++-
.vscode/tasks.json | 5 +++++
src/extension.ts | 17 +++++++++++++++++
webview-ui/scripts/build-react-no-split.js | 10 ++++++++++
.../src/components/settings/SettingsView.tsx | 2 +-
6 files changed, 43 insertions(+), 2 deletions(-)
create mode 100644 .changeset/yellow-paws-chew.md
diff --git a/.changeset/yellow-paws-chew.md b/.changeset/yellow-paws-chew.md
new file mode 100644
index 0000000000..b697783d76
--- /dev/null
+++ b/.changeset/yellow-paws-chew.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": minor
+---
+
+ADD IS_DEV and Hot Reloading to debug
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 90323404cc..c03c771a6d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,7 +11,11 @@
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
- "preLaunchTask": "${defaultBuildTask}"
+ "preLaunchTask": "${defaultBuildTask}",
+ "env": {
+ "IS_DEV": "true",
+ "DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
+ }
}
]
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index e1413836d1..bb1e2b8999 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -24,6 +24,11 @@
"presentation": {
"group": "watch",
"reveal": "never"
+ },
+ "options": {
+ "env": {
+ "IS_DEV": "true"
+ }
}
},
{
diff --git a/src/extension.ts b/src/extension.ts
index ed9cff31e9..50e545472c 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -7,6 +7,7 @@ import { Logger } from "./services/logging/Logger"
import { createClineAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
+import assert from "node:assert"
/*
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -190,3 +191,19 @@ export function activate(context: vscode.ExtensionContext) {
export function deactivate() {
Logger.log("Cline extension deactivated")
}
+
+// TODO: remove this in production
+// This is a workaround to reload the extension when the source code changes
+// since vscode doesn't support hot reload for extensions
+const { IS_DEV, DEV_WORKSPACE_FOLDER } = process.env
+
+if (IS_DEV) {
+ assert(DEV_WORKSPACE_FOLDER, "DEV_WORKSPACE_FOLDER must be set in development")
+ const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(DEV_WORKSPACE_FOLDER, "src/**/*"))
+
+ watcher.onDidChange(({ scheme, path }) => {
+ console.info(`${scheme} ${path} changed. Reloading VSCode...`)
+
+ vscode.commands.executeCommand("workbench.action.reloadWindow")
+ })
+}
diff --git a/webview-ui/scripts/build-react-no-split.js b/webview-ui/scripts/build-react-no-split.js
index 28f37108be..d393018c3c 100644
--- a/webview-ui/scripts/build-react-no-split.js
+++ b/webview-ui/scripts/build-react-no-split.js
@@ -12,6 +12,7 @@
const rewire = require("rewire")
const defaults = rewire("react-scripts/scripts/build.js")
const config = defaults.__get__("config")
+const webpack = require("webpack")
/* Modifying Webpack Configuration for 'shared' dir
This section uses Rewire to modify Create React App's webpack configuration without ejecting. Rewire allows us to inject and alter the internal build scripts of CRA at runtime. This allows us to maintain a flexible project structure that keeps shared code outside the webview-ui/src directory, while still adhering to CRA's security model that typically restricts imports to within src/.
@@ -119,6 +120,15 @@ config.output = {
filename: "static/js/[name].js",
}
+// Adjust build environment variables for dev/debug builds.
+config.plugins[4] = new webpack.DefinePlugin({
+ "process.env": {
+ ...config.plugins[4].definitions["process.env"],
+ NODE_ENV: JSON.stringify(process.env.IS_DEV ? "development" : "production"),
+ IS_DEV: JSON.stringify(process.env.IS_DEV),
+ },
+})
+
// Rename main.{hash}.css to main.css
config.plugins[5].options.filename = "static/css/[name].css"
config.plugins[5].options.moduleFilename = () => "static/css/main.css"
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index ad1e141fa5..994aab161f 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -5,7 +5,7 @@ import { validateApiConfiguration, validateModelId } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import ApiOptions from "./ApiOptions"
import SettingsButton from "../common/SettingsButton"
-const IS_DEV = false // FIXME: use flags when packaging
+const { IS_DEV } = process.env
type SettingsViewProps = {
onDone: () => void
From 25ea46aa8dcffb1606be5e81100bfa939081a0c6 Mon Sep 17 00:00:00 2001
From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Date: Sun, 23 Feb 2025 16:10:37 -0800
Subject: [PATCH 04/87] Revert "Add IS_DEV and Hot Reloading to debug. (#1895)"
(#1917)
This reverts commit b0446f7bac57b7bf299c8b59b85597b2eb46f38d.
---
.changeset/yellow-paws-chew.md | 5 -----
.vscode/launch.json | 6 +-----
.vscode/tasks.json | 5 -----
src/extension.ts | 17 -----------------
webview-ui/scripts/build-react-no-split.js | 10 ----------
.../src/components/settings/SettingsView.tsx | 2 +-
6 files changed, 2 insertions(+), 43 deletions(-)
delete mode 100644 .changeset/yellow-paws-chew.md
diff --git a/.changeset/yellow-paws-chew.md b/.changeset/yellow-paws-chew.md
deleted file mode 100644
index b697783d76..0000000000
--- a/.changeset/yellow-paws-chew.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"claude-dev": minor
----
-
-ADD IS_DEV and Hot Reloading to debug
diff --git a/.vscode/launch.json b/.vscode/launch.json
index c03c771a6d..90323404cc 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,11 +11,7 @@
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
- "preLaunchTask": "${defaultBuildTask}",
- "env": {
- "IS_DEV": "true",
- "DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
- }
+ "preLaunchTask": "${defaultBuildTask}"
}
]
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index bb1e2b8999..e1413836d1 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -24,11 +24,6 @@
"presentation": {
"group": "watch",
"reveal": "never"
- },
- "options": {
- "env": {
- "IS_DEV": "true"
- }
}
},
{
diff --git a/src/extension.ts b/src/extension.ts
index 50e545472c..ed9cff31e9 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -7,7 +7,6 @@ import { Logger } from "./services/logging/Logger"
import { createClineAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
-import assert from "node:assert"
/*
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -191,19 +190,3 @@ export function activate(context: vscode.ExtensionContext) {
export function deactivate() {
Logger.log("Cline extension deactivated")
}
-
-// TODO: remove this in production
-// This is a workaround to reload the extension when the source code changes
-// since vscode doesn't support hot reload for extensions
-const { IS_DEV, DEV_WORKSPACE_FOLDER } = process.env
-
-if (IS_DEV) {
- assert(DEV_WORKSPACE_FOLDER, "DEV_WORKSPACE_FOLDER must be set in development")
- const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(DEV_WORKSPACE_FOLDER, "src/**/*"))
-
- watcher.onDidChange(({ scheme, path }) => {
- console.info(`${scheme} ${path} changed. Reloading VSCode...`)
-
- vscode.commands.executeCommand("workbench.action.reloadWindow")
- })
-}
diff --git a/webview-ui/scripts/build-react-no-split.js b/webview-ui/scripts/build-react-no-split.js
index d393018c3c..28f37108be 100644
--- a/webview-ui/scripts/build-react-no-split.js
+++ b/webview-ui/scripts/build-react-no-split.js
@@ -12,7 +12,6 @@
const rewire = require("rewire")
const defaults = rewire("react-scripts/scripts/build.js")
const config = defaults.__get__("config")
-const webpack = require("webpack")
/* Modifying Webpack Configuration for 'shared' dir
This section uses Rewire to modify Create React App's webpack configuration without ejecting. Rewire allows us to inject and alter the internal build scripts of CRA at runtime. This allows us to maintain a flexible project structure that keeps shared code outside the webview-ui/src directory, while still adhering to CRA's security model that typically restricts imports to within src/.
@@ -120,15 +119,6 @@ config.output = {
filename: "static/js/[name].js",
}
-// Adjust build environment variables for dev/debug builds.
-config.plugins[4] = new webpack.DefinePlugin({
- "process.env": {
- ...config.plugins[4].definitions["process.env"],
- NODE_ENV: JSON.stringify(process.env.IS_DEV ? "development" : "production"),
- IS_DEV: JSON.stringify(process.env.IS_DEV),
- },
-})
-
// Rename main.{hash}.css to main.css
config.plugins[5].options.filename = "static/css/[name].css"
config.plugins[5].options.moduleFilename = () => "static/css/main.css"
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index 994aab161f..ad1e141fa5 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -5,7 +5,7 @@ import { validateApiConfiguration, validateModelId } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import ApiOptions from "./ApiOptions"
import SettingsButton from "../common/SettingsButton"
-const { IS_DEV } = process.env
+const IS_DEV = false // FIXME: use flags when packaging
type SettingsViewProps = {
onDone: () => void
From 1127e2a33c2fa04c52fdb512020bed0d047cdcfb Mon Sep 17 00:00:00 2001
From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Date: Mon, 24 Feb 2025 11:07:23 -0800
Subject: [PATCH 05/87] Add Claude 3.7 Sonnet
---
src/api/providers/anthropic.ts | 2 +
src/api/providers/openrouter.ts | 4 ++
src/core/webview/ClineProvider.ts | 2 +
src/shared/api.ts | 37 +++++++++++++++++--
webview-ui/src/components/chat/ChatView.tsx | 15 +++-----
.../src/components/settings/ApiOptions.tsx | 2 +-
.../settings/OpenRouterModelPicker.tsx | 4 +-
.../src/components/welcome/WelcomeView.tsx | 19 ++++------
8 files changed, 57 insertions(+), 28 deletions(-)
diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts
index 3aff867b7f..959916e9a8 100644
--- a/src/api/providers/anthropic.ts
+++ b/src/api/providers/anthropic.ts
@@ -24,6 +24,7 @@ export class AnthropicHandler implements ApiHandler {
const modelId = model.id
switch (modelId) {
// 'latest' alias does not support cache_control
+ case "claude-3-7-sonnet-20250219":
case "claude-3-5-sonnet-20241022":
case "claude-3-5-haiku-20241022":
case "claude-3-opus-20240229":
@@ -88,6 +89,7 @@ export class AnthropicHandler implements ApiHandler {
// https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
// https://github.com/anthropics/anthropic-sdk-typescript/commit/c920b77fc67bd839bfeb6716ceab9d7c9bbe7393
switch (modelId) {
+ case "claude-3-7-sonnet-20250219":
case "claude-3-5-sonnet-20241022":
case "claude-3-5-haiku-20241022":
case "claude-3-opus-20240229":
diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts
index 5f95639fdf..07db28fb81 100644
--- a/src/api/providers/openrouter.ts
+++ b/src/api/providers/openrouter.ts
@@ -38,6 +38,8 @@ export class OpenRouterHandler implements ApiHandler {
// prompt caching: https://openrouter.ai/docs/prompt-caching
// this is specifically for claude models (some models may 'support prompt caching' automatically without this)
switch (model.id) {
+ case "anthropic/claude-3-7-sonnet":
+ case "anthropic/claude-3-7-sonnet:beta":
case "anthropic/claude-3.5-sonnet":
case "anthropic/claude-3.5-sonnet:beta":
case "anthropic/claude-3.5-sonnet-20240620":
@@ -89,6 +91,8 @@ export class OpenRouterHandler implements ApiHandler {
// (models usually default to max tokens allowed)
let maxTokens: number | undefined
switch (model.id) {
+ case "anthropic/claude-3-7-sonnet":
+ case "anthropic/claude-3-7-sonnet:beta":
case "anthropic/claude-3.5-sonnet":
case "anthropic/claude-3.5-sonnet:beta":
case "anthropic/claude-3.5-sonnet-20240620":
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 65d5996bf9..87a2f83fbf 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -1407,6 +1407,8 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
}
switch (rawModel.id) {
+ case "anthropic/claude-3-7-sonnet":
+ case "anthropic/claude-3-7-sonnet:beta":
case "anthropic/claude-3.5-sonnet":
case "anthropic/claude-3.5-sonnet:beta":
// NOTE: this needs to be synced with api.ts/openrouter default model info
diff --git a/src/shared/api.ts b/src/shared/api.ts
index 444242433d..a5a96d4a7f 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -80,8 +80,19 @@ export interface ModelInfo {
// Anthropic
// https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02
export type AnthropicModelId = keyof typeof anthropicModels
-export const anthropicDefaultModelId: AnthropicModelId = "claude-3-5-sonnet-20241022"
+export const anthropicDefaultModelId: AnthropicModelId = "claude-3-7-sonnet-20250219"
export const anthropicModels = {
+ "claude-3-7-sonnet-20250219": {
+ maxTokens: 8192,
+ contextWindow: 200_000,
+ supportsImages: true,
+ supportsComputerUse: true,
+ supportsPromptCache: true,
+ inputPrice: 3.0,
+ outputPrice: 15.0,
+ cacheWritesPrice: 3.75,
+ cacheReadsPrice: 0.3,
+ },
"claude-3-5-sonnet-20241022": {
maxTokens: 8192,
contextWindow: 200_000,
@@ -130,6 +141,17 @@ export const anthropicModels = {
export type BedrockModelId = keyof typeof bedrockModels
export const bedrockDefaultModelId: BedrockModelId = "anthropic.claude-3-5-sonnet-20241022-v2:0"
export const bedrockModels = {
+ "anthropic.claude-3-7-sonnet-20250219-v1:0": {
+ maxTokens: 8192,
+ contextWindow: 200_000,
+ supportsImages: true,
+ supportsComputerUse: true,
+ supportsPromptCache: true,
+ inputPrice: 3.0,
+ outputPrice: 15.0,
+ cacheWritesPrice: 3.75,
+ cacheReadsPrice: 0.3,
+ },
"anthropic.claude-3-5-sonnet-20241022-v2:0": {
maxTokens: 8192,
contextWindow: 200_000,
@@ -183,7 +205,7 @@ export const bedrockModels = {
// OpenRouter
// https://openrouter.ai/models?order=newest&supported_parameters=tools
-export const openRouterDefaultModelId = "anthropic/claude-3.5-sonnet" // will always exist in openRouterModels
+export const openRouterDefaultModelId = "anthropic/claude-3-7-sonnet" // will always exist in openRouterModels
export const openRouterDefaultModelInfo: ModelInfo = {
maxTokens: 8192,
contextWindow: 200_000,
@@ -195,7 +217,7 @@ export const openRouterDefaultModelInfo: ModelInfo = {
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
description:
- "The new Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: New Sonnet scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal",
+ "Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. \n\nClaude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks.\n\nRead more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)",
}
// Vertex AI
@@ -203,6 +225,15 @@ export const openRouterDefaultModelInfo: ModelInfo = {
export type VertexModelId = keyof typeof vertexModels
export const vertexDefaultModelId: VertexModelId = "claude-3-5-sonnet-v2@20241022"
export const vertexModels = {
+ "claude-3-7-sonnet@20250219": {
+ maxTokens: 8192,
+ contextWindow: 200_000,
+ supportsImages: true,
+ supportsComputerUse: true,
+ supportsPromptCache: false,
+ inputPrice: 3.0,
+ outputPrice: 15.0,
+ },
"claude-3-5-sonnet-v2@20241022": {
maxTokens: 8192,
contextWindow: 200_000,
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index 8b4290cd2a..a78ca0c2fa 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -793,16 +793,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
What can I do for you?
- Thanks to{" "}
-
- Claude 3.5 Sonnet's agentic coding capabilities,
- {" "}
- I can handle complex software development tasks step-by-step. With tools that let me create & edit
- files, explore complex projects, use the browser, and execute terminal commands (after you grant
- permission), I can assist you in ways that go beyond code completion or tech support. I can even use
- MCP to create new tools and extend my own capabilities.
+ Thanks to Claude 3.7 Sonnet's agentic coding capabilities, I can handle complex software development
+ tasks step-by-step. With tools that let me create & edit files, explore complex projects, use the
+ browser, and execute terminal commands (after you grant permission), I can assist you in ways that go
+ beyond code completion or tech support. I can even use MCP to create new tools and extend my own
+ capabilities.
{taskHistory.length > 0 && }
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx
index 4f54cc0978..3d6037e275 100644
--- a/webview-ui/src/components/settings/ApiOptions.tsx
+++ b/webview-ui/src/components/settings/ApiOptions.tsx
@@ -938,7 +938,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
}}>
The VS Code Language Model API allows you to run models provided by other VS Code extensions
(including but not limited to GitHub Copilot). The easiest way to get started is to install the
- Copilot extension from the VS Marketplace and enabling Claude 3.5 Sonnet.
+ Copilot extension from the VS Marketplace and enabling Claude 3.7 Sonnet.
)}
diff --git a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
index 889a636ccb..685e420f07 100644
--- a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
+++ b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
@@ -228,8 +228,8 @@ const OpenRouterModelPicker: React.FC = ({ isPopup }
If you're unsure which model to choose, Cline works best with{" "}
handleModelChange("anthropic/claude-3.5-sonnet")}>
- anthropic/claude-3.5-sonnet.
+ onClick={() => handleModelChange("anthropic/claude-3-7-sonnet")}>
+ anthropic/claude-3-7-sonnet.
You can also try searching "free" for no-cost options currently available.
>
diff --git a/webview-ui/src/components/welcome/WelcomeView.tsx b/webview-ui/src/components/welcome/WelcomeView.tsx
index 330870f56a..a51ce44dc6 100644
--- a/webview-ui/src/components/welcome/WelcomeView.tsx
+++ b/webview-ui/src/components/welcome/WelcomeView.tsx
@@ -1,11 +1,11 @@
-import { VSCodeButton, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
-import { useEffect, useState, useCallback } from "react"
+import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
+import { useCallback, useEffect, useState } from "react"
+import { useEvent } from "react-use"
+import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { validateApiConfiguration } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import ApiOptions from "../settings/ApiOptions"
-import { useEvent } from "react-use"
-import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
const WelcomeView = () => {
const { apiConfiguration } = useExtensionState()
@@ -58,18 +58,13 @@ const WelcomeView = () => {
}}>
Hi, I'm Cline
- I can do all kinds of tasks thanks to the latest breakthroughs in{" "}
-
- Claude 3.5 Sonnet's agentic coding capabilities
- {" "}
- and access to tools that let me create & edit files, explore complex projects, use the browser, and execute
+ I can do all kinds of tasks thanks to breakthroughs in Claude 3.7 Sonnet's agentic coding capabilities and
+ access to tools that let me create & edit files, explore complex projects, use the browser, and execute
terminal commands (with your permission, of course). I can even use MCP to create new tools and extend my own
capabilities.
- To get started, this extension needs an API provider for Claude 3.5 Sonnet.
+ To get started, this extension needs an API provider for Claude 3.7 Sonnet.
Date: Mon, 24 Feb 2025 11:08:14 -0800
Subject: [PATCH 06/87] Prepare for release
---
CHANGELOG.md | 4 ++++
package.json | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd32320fd3..95a6622398 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## [3.4.6]
+
+- Add support for Claude 3.7 Sonnet
+
## [3.4.0]
- Introducing MCP Marketplace! You can now discover and install the best MCP servers right from within the extension, with new servers added regularly
diff --git a/package.json b/package.json
index 5fc196bbe2..32e12aa25a 100644
--- a/package.json
+++ b/package.json
@@ -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.4.5",
+ "version": "3.4.6",
"icon": "assets/icons/icon.png",
"galleryBanner": {
"color": "#617A91",
From 45b13dd775ad31e8a20b601a1d1ee518b28e76ac Mon Sep 17 00:00:00 2001
From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Date: Mon, 24 Feb 2025 11:14:32 -0800
Subject: [PATCH 07/87] Temporarily revert default openrouter model until API
is fixed
---
src/shared/api.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/shared/api.ts b/src/shared/api.ts
index a5a96d4a7f..a1a0871d2a 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -205,7 +205,7 @@ export const bedrockModels = {
// OpenRouter
// https://openrouter.ai/models?order=newest&supported_parameters=tools
-export const openRouterDefaultModelId = "anthropic/claude-3-7-sonnet" // will always exist in openRouterModels
+export const openRouterDefaultModelId = "anthropic/claude-3.5-sonnet" // will always exist in openRouterModels
export const openRouterDefaultModelInfo: ModelInfo = {
maxTokens: 8192,
contextWindow: 200_000,
@@ -217,7 +217,7 @@ export const openRouterDefaultModelInfo: ModelInfo = {
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
description:
- "Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. \n\nClaude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks.\n\nRead more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)",
+ "The new Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: New Sonnet scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal",
}
// Vertex AI
From 0f1240063c52b06ec0ac4d8af0eb029db03f8542 Mon Sep 17 00:00:00 2001
From: canvrno <46584286+canvrno@users.noreply.github.com>
Date: Mon, 24 Feb 2025 12:37:17 -0800
Subject: [PATCH 08/87] MCP Marketplace server installation prompt adjustment
(#1901)
* MCP server isntallation prompt adjustment
* changeset
---
.changeset/wicked-pears-tickle.md | 5 +++++
src/core/webview/ClineProvider.ts | 14 ++++++++++----
2 files changed, 15 insertions(+), 4 deletions(-)
create mode 100644 .changeset/wicked-pears-tickle.md
diff --git a/.changeset/wicked-pears-tickle.md b/.changeset/wicked-pears-tickle.md
new file mode 100644
index 0000000000..a2df4034b6
--- /dev/null
+++ b/.changeset/wicked-pears-tickle.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+Adjustment to MCP server installation prompt
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 87a2f83fbf..3eac593af2 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -32,6 +32,7 @@ import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings"
import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider"
import { searchCommits } from "../../utils/git"
import { ChatContent } from "../../shared/ChatContent"
+import { getShell } from "../../utils/shell"
/*
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -1245,10 +1246,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
mcpDownloadDetails: mcpDetails,
})
- // Create task with context from README
- const task = `Set up the MCP server from ${mcpDetails.githubUrl}.
-Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
-Once installed, demonstrate the server's capabilities by using one of its tools.
+ // Create task with context from README and added guidelines for MCP server installation
+ const task = `Set up the MCP server from ${mcpDetails.githubUrl} while adhering to these MCP server installation rules:
+- Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
+- Use commands aligned with the user's shell and operating system best practices. The user's shell is: ${getShell()}.
+- Create the directory for the new MCP server before starting installation.
+- Follow the MCP servers README exactly—only deviate if it clearly conflicts with the user's OS, in which case proceed thoughtfully.
+- Ensure any steps requiring the use of pip, npm, or any other package manager, are followed as required.
+- After running each command, read its output carefully and adjust subsequent steps as needed based on that information.
+- Once installed, demonstrate the server's capabilities by using one of its tools.
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
// Initialize task and show chat view
From 87670a37b89dcd39d9ece7e3fc59a3abfbece1d2 Mon Sep 17 00:00:00 2001
From: Evan <58194240+celestial-vault@users.noreply.github.com>
Date: Mon, 24 Feb 2025 13:53:29 -0800
Subject: [PATCH 09/87] Everyone Gets a Timeout (#1889)
* timeouts for individual servers
* changeset
* remove logger
* use const and descriptive function for time settings
---
.changeset/dull-planets-battle.md | 5 ++
src/core/prompts/system.ts | 5 +-
src/core/webview/ClineProvider.ts | 10 +++
src/services/mcp/McpHub.ts | 86 ++++++++++++++++-------
src/shared/WebviewMessage.ts | 2 +
src/shared/mcp.ts | 3 +
src/utils/time.ts | 3 +
webview-ui/src/components/mcp/McpView.tsx | 52 +++++++++++++-
8 files changed, 135 insertions(+), 31 deletions(-)
create mode 100644 .changeset/dull-planets-battle.md
create mode 100644 src/utils/time.ts
diff --git a/.changeset/dull-planets-battle.md b/.changeset/dull-planets-battle.md
new file mode 100644
index 0000000000..47393fa995
--- /dev/null
+++ b/.changeset/dull-planets-battle.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": minor
+---
+
+Added timeout configuration for individual MCP servers.
diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts
index 8d6d13766d..7c10163e92 100644
--- a/src/core/prompts/system.ts
+++ b/src/core/prompts/system.ts
@@ -759,8 +759,9 @@ IMPORTANT: Regardless of what else you see in the MCP settings file, you must de
"args": ["/path/to/weather-server/build/index.js"],
"env": {
"OPENWEATHER_API_KEY": "user-provided-api-key"
- }
- },
+ },
+ "timeout": 60, // 60 seconds should be set as the default timeout for all MCP servers
+ }
}
}
\`\`\`
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 3eac593af2..9987a34288 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -821,6 +821,16 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
break
}
+ case "updateMcpTimeout": {
+ try {
+ if (message.serverName && message.timeout) {
+ await this.mcpHub?.updateServerTimeout(message.serverName, message.timeout)
+ }
+ } catch (error) {
+ console.error(`Failed to update timeout for server ${message.serverName}:`, error)
+ }
+ break
+ }
case "openExtensionSettings": {
const settingsFilter = message.text || ""
await vscode.commands.executeCommand(
diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts
index 556d6d8c47..75332cd906 100644
--- a/src/services/mcp/McpHub.ts
+++ b/src/services/mcp/McpHub.ts
@@ -16,6 +16,7 @@ import * as vscode from "vscode"
import { z } from "zod"
import { ClineProvider, GlobalFileNames } from "../../core/webview/ClineProvider"
import {
+ DEFAULT_MCP_TIMEOUT_SECONDS,
McpMode,
McpResource,
McpResourceResponse,
@@ -26,7 +27,7 @@ import {
} from "../../shared/mcp"
import { fileExistsAtPath } from "../../utils/fs"
import { arePathsEqual } from "../../utils/path"
-
+import { secondsToMs } from "../../utils/time"
export type McpConnection = {
server: McpServer
client: Client
@@ -42,6 +43,7 @@ const StdioConfigSchema = z.object({
env: z.record(z.string()).optional(),
autoApprove: AutoApproveSchema.optional(),
disabled: z.boolean().optional(),
+ timeout: z.number().min(1).max(3600).optional().default(DEFAULT_MCP_TIMEOUT_SECONDS),
})
const McpSettingsSchema = z.object({
@@ -242,28 +244,6 @@ export class McpHub {
}
transport.start = async () => {} // No-op now, .connect() won't fail
- // // Set up notification handlers
- // client.setNotificationHandler(
- // // @ts-ignore-next-line
- // { method: "notifications/tools/list_changed" },
- // async () => {
- // console.log(`Tools changed for server: ${name}`)
- // connection.server.tools = await this.fetchTools(name)
- // await this.notifyWebviewOfServerChanges()
- // },
- // )
-
- // client.setNotificationHandler(
- // // @ts-ignore-next-line
- // { method: "notifications/resources/list_changed" },
- // async () => {
- // console.log(`Resources changed for server: ${name}`)
- // connection.server.resources = await this.fetchResources(name)
- // connection.server.resourceTemplates = await this.fetchResourceTemplates(name)
- // await this.notifyWebviewOfServerChanges()
- // },
- // )
-
// Connect
await client.connect(transport)
connection.server.status = "connected"
@@ -343,10 +323,6 @@ export class McpHub {
const connection = this.connections.find((conn) => conn.server.name === name)
if (connection) {
try {
- // connection.client.removeNotificationHandler("notifications/tools/list_changed")
- // connection.client.removeNotificationHandler("notifications/resources/list_changed")
- // connection.client.removeNotificationHandler("notifications/stderr")
- // connection.client.removeNotificationHandler("notifications/stderr")
await connection.transport.close()
await connection.client.close()
} catch (error) {
@@ -563,6 +539,7 @@ export class McpHub {
if (connection.server.disabled) {
throw new Error(`Server "${serverName}" is disabled`)
}
+
return await connection.client.request(
{
method: "resources/read",
@@ -586,6 +563,17 @@ export class McpHub {
throw new Error(`Server "${serverName}" is disabled and cannot be used`)
}
+ let timeout = secondsToMs(DEFAULT_MCP_TIMEOUT_SECONDS) // sdk expects ms
+
+ try {
+ const config = JSON.parse(connection.server.config)
+ const parsedConfig = StdioConfigSchema.parse(config)
+ timeout = secondsToMs(parsedConfig.timeout)
+ } catch (error) {
+ console.error(`Failed to parse timeout configuration for server ${serverName}: ${error}`)
+ // Continue with default timeout
+ }
+
return await connection.client.request(
{
method: "tools/call",
@@ -595,6 +583,9 @@ export class McpHub {
},
},
CallToolResultSchema,
+ {
+ timeout,
+ },
)
}
@@ -663,6 +654,47 @@ export class McpHub {
}
}
+ public async updateServerTimeout(serverName: string, timeout: number): Promise {
+ try {
+ // Validate timeout against schema
+ const setConfigResult = StdioConfigSchema.shape.timeout.safeParse(timeout)
+ if (!setConfigResult.success) {
+ throw new Error(`Invalid timeout value: ${timeout}. Must be between 1 and 3600 seconds.`)
+ }
+
+ const settingsPath = await this.getMcpSettingsFilePath()
+ const content = await fs.readFile(settingsPath, "utf-8")
+ const config = JSON.parse(content)
+
+ if (!config.mcpServers?.[serverName]) {
+ throw new Error(`Server "${serverName}" not found in settings`)
+ }
+
+ // Update the timeout in the config
+ config.mcpServers[serverName] = {
+ ...config.mcpServers[serverName],
+ timeout,
+ }
+
+ // Write updated config back to file
+ await fs.writeFile(settingsPath, JSON.stringify(config, null, 2))
+
+ // Update server connections to apply the new timeout
+ await this.updateServerConnections(config.mcpServers)
+
+ vscode.window.showInformationMessage(`Updated timeout to ${timeout} seconds`)
+ } catch (error) {
+ console.error("Failed to update server timeout:", error)
+ if (error instanceof Error) {
+ console.error("Error details:", error.message, error.stack)
+ }
+ vscode.window.showErrorMessage(
+ `Failed to update server timeout: ${error instanceof Error ? error.message : String(error)}`,
+ )
+ throw error
+ }
+ }
+
async dispose(): Promise {
this.removeAllFileWatchers()
for (const connection of this.connections) {
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index e42cb5860b..6fd9dfe085 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -50,6 +50,7 @@ export interface WebviewMessage {
| "searchCommits"
| "showMcpView"
| "fetchLatestMcpServersFromHub"
+ | "updateMcpTimeout"
// | "relaunchChromeDebugMode"
text?: string
disabled?: boolean
@@ -63,6 +64,7 @@ export interface WebviewMessage {
chatSettings?: ChatSettings
chatContent?: ChatContent
mcpId?: string
+ timeout?: number // For updateMcpTimeout
// For toggleToolAutoApprove
serverName?: string
diff --git a/src/shared/mcp.ts b/src/shared/mcp.ts
index f0a09afa2d..fb8816ac23 100644
--- a/src/shared/mcp.ts
+++ b/src/shared/mcp.ts
@@ -1,3 +1,5 @@
+export const DEFAULT_MCP_TIMEOUT_SECONDS = 60 // matches Anthropic's default timeout in their MCP SDK
+
export type McpMode = "full" | "server-use-only" | "off"
export type McpServer = {
@@ -9,6 +11,7 @@ export type McpServer = {
resources?: McpResource[]
resourceTemplates?: McpResourceTemplate[]
disabled?: boolean
+ timeout?: number
}
export type McpTool = {
diff --git a/src/utils/time.ts b/src/utils/time.ts
new file mode 100644
index 0000000000..316ea5a949
--- /dev/null
+++ b/src/utils/time.ts
@@ -0,0 +1,3 @@
+export function secondsToMs(seconds: number): number {
+ return seconds * 1000
+}
diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx
index f40dec9ddd..fee5322ef6 100644
--- a/webview-ui/src/components/mcp/McpView.tsx
+++ b/webview-ui/src/components/mcp/McpView.tsx
@@ -1,7 +1,15 @@
-import { VSCodeButton, VSCodeLink, VSCodePanels, VSCodePanelTab, VSCodePanelView } from "@vscode/webview-ui-toolkit/react"
+import {
+ VSCodeButton,
+ VSCodeLink,
+ VSCodePanels,
+ VSCodePanelTab,
+ VSCodePanelView,
+ VSCodeDropdown,
+ VSCodeOption,
+} from "@vscode/webview-ui-toolkit/react"
import { useEffect, useState } from "react"
import styled from "styled-components"
-import { McpServer } from "../../../../src/shared/mcp"
+import { DEFAULT_MCP_TIMEOUT_SECONDS, McpServer } from "../../../../src/shared/mcp"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { getMcpServerDisplayName } from "../../utils/mcp"
import { vscode } from "../../utils/vscode"
@@ -210,6 +218,36 @@ const ServerRow = ({ server }: { server: McpServer }) => {
}
}
+ const [timeout, setTimeout] = useState(() => {
+ try {
+ const config = JSON.parse(server.config)
+ return config.timeout?.toString() || DEFAULT_MCP_TIMEOUT_SECONDS.toString()
+ } catch {
+ return DEFAULT_MCP_TIMEOUT_SECONDS.toString()
+ }
+ })
+
+ const timeoutOptions = [
+ { value: "30", label: "30 seconds" },
+ { value: "60", label: "1 minute" },
+ { value: "300", label: "5 minutes" },
+ { value: "600", label: "10 minutes" },
+ { value: "1800", label: "30 minutes" },
+ { value: "3600", label: "1 hour" },
+ ]
+
+ const handleTimeoutChange = (e: any) => {
+ const select = e.target as HTMLSelectElement
+ const value = select.value
+ const num = parseInt(value)
+ setTimeout(value)
+ vscode.postMessage({
+ type: "updateMcpTimeout",
+ serverName: server.name,
+ timeout: num,
+ })
+ }
+
const handleRestart = () => {
vscode.postMessage({
type: "restartMcpServer",
@@ -410,6 +448,16 @@ const ServerRow = ({ server }: { server: McpServer }) => {
+
Date: Mon, 24 Feb 2025 18:14:58 -0800
Subject: [PATCH 13/87] Add new openrouter model IDs
---
src/api/providers/openrouter.ts | 4 ++++
src/core/webview/ClineProvider.ts | 2 ++
webview-ui/src/components/settings/OpenRouterModelPicker.tsx | 4 ++--
3 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts
index 07db28fb81..971f9fab73 100644
--- a/src/api/providers/openrouter.ts
+++ b/src/api/providers/openrouter.ts
@@ -38,6 +38,8 @@ export class OpenRouterHandler implements ApiHandler {
// prompt caching: https://openrouter.ai/docs/prompt-caching
// this is specifically for claude models (some models may 'support prompt caching' automatically without this)
switch (model.id) {
+ case "anthropic/claude-3.7-sonnet":
+ case "anthropic/claude-3.7-sonnet:beta":
case "anthropic/claude-3-7-sonnet":
case "anthropic/claude-3-7-sonnet:beta":
case "anthropic/claude-3.5-sonnet":
@@ -91,6 +93,8 @@ export class OpenRouterHandler implements ApiHandler {
// (models usually default to max tokens allowed)
let maxTokens: number | undefined
switch (model.id) {
+ case "anthropic/claude-3.7-sonnet":
+ case "anthropic/claude-3.7-sonnet:beta":
case "anthropic/claude-3-7-sonnet":
case "anthropic/claude-3-7-sonnet:beta":
case "anthropic/claude-3.5-sonnet":
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 3eac593af2..d135372c60 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -1415,6 +1415,8 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
switch (rawModel.id) {
case "anthropic/claude-3-7-sonnet":
case "anthropic/claude-3-7-sonnet:beta":
+ case "anthropic/claude-3.7-sonnet":
+ case "anthropic/claude-3.7-sonnet:beta":
case "anthropic/claude-3.5-sonnet":
case "anthropic/claude-3.5-sonnet:beta":
// NOTE: this needs to be synced with api.ts/openrouter default model info
diff --git a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
index 685e420f07..5f6020bbfa 100644
--- a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
+++ b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
@@ -228,8 +228,8 @@ const OpenRouterModelPicker: React.FC = ({ isPopup }
If you're unsure which model to choose, Cline works best with{" "}
handleModelChange("anthropic/claude-3-7-sonnet")}>
- anthropic/claude-3-7-sonnet.
+ onClick={() => handleModelChange("anthropic/claude-3.7-sonnet")}>
+ anthropic/claude-3.7-sonnet.
You can also try searching "free" for no-cost options currently available.
>
From 1c9da770a84e28aec1f1c18f15bf878a689079b0 Mon Sep 17 00:00:00 2001
From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Date: Mon, 24 Feb 2025 18:15:33 -0800
Subject: [PATCH 14/87] Revert "Temporarily revert default openrouter model
until API is fixed"
This reverts commit 45b13dd775ad31e8a20b601a1d1ee518b28e76ac.
---
src/shared/api.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/shared/api.ts b/src/shared/api.ts
index a1a0871d2a..a5a96d4a7f 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -205,7 +205,7 @@ export const bedrockModels = {
// OpenRouter
// https://openrouter.ai/models?order=newest&supported_parameters=tools
-export const openRouterDefaultModelId = "anthropic/claude-3.5-sonnet" // will always exist in openRouterModels
+export const openRouterDefaultModelId = "anthropic/claude-3-7-sonnet" // will always exist in openRouterModels
export const openRouterDefaultModelInfo: ModelInfo = {
maxTokens: 8192,
contextWindow: 200_000,
@@ -217,7 +217,7 @@ export const openRouterDefaultModelInfo: ModelInfo = {
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
description:
- "The new Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: New Sonnet scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal",
+ "Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. \n\nClaude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks.\n\nRead more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)",
}
// Vertex AI
From b4e67afcba0e9793ce2c5f889d2b94430b1f5969 Mon Sep 17 00:00:00 2001
From: Dennis Bartlett
Date: Mon, 24 Feb 2025 18:16:15 -0800
Subject: [PATCH 15/87] Reapply "Add IS_DEV and Hot Reloading to debug.
(#1895)" (#1917) (#1942)
* Reapply "Add IS_DEV and Hot Reloading to debug. (#1895)" (#1917)
This reverts commit 25ea46aa8dcffb1606be5e81100bfa939081a0c6.
* Update TODO to be more explicit. Update logic for checking IS_DEV
* Update TODO with even more explanation. (Now with 2x more explanation per explanation
---
.changeset/yellow-paws-chew.md | 5 +++++
.vscode/launch.json | 6 +++++-
.vscode/tasks.json | 5 +++++
src/extension.ts | 20 +++++++++++++++++++
webview-ui/scripts/build-react-no-split.js | 10 ++++++++++
.../src/components/settings/SettingsView.tsx | 2 +-
6 files changed, 46 insertions(+), 2 deletions(-)
create mode 100644 .changeset/yellow-paws-chew.md
diff --git a/.changeset/yellow-paws-chew.md b/.changeset/yellow-paws-chew.md
new file mode 100644
index 0000000000..b697783d76
--- /dev/null
+++ b/.changeset/yellow-paws-chew.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": minor
+---
+
+ADD IS_DEV and Hot Reloading to debug
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 90323404cc..c03c771a6d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,7 +11,11 @@
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
- "preLaunchTask": "${defaultBuildTask}"
+ "preLaunchTask": "${defaultBuildTask}",
+ "env": {
+ "IS_DEV": "true",
+ "DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
+ }
}
]
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index e1413836d1..bb1e2b8999 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -24,6 +24,11 @@
"presentation": {
"group": "watch",
"reveal": "never"
+ },
+ "options": {
+ "env": {
+ "IS_DEV": "true"
+ }
}
},
{
diff --git a/src/extension.ts b/src/extension.ts
index ed9cff31e9..991f415a00 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -7,6 +7,7 @@ import { Logger } from "./services/logging/Logger"
import { createClineAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
+import assert from "node:assert"
/*
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -190,3 +191,22 @@ export function activate(context: vscode.ExtensionContext) {
export function deactivate() {
Logger.log("Cline extension deactivated")
}
+
+// TODO: Find a solution for automatically removing DEV related content from production builds.
+// This type of code is fine in production to keep. We just will want to remove it from production builds
+// to bring down built asset sizes.
+//
+// This is a workaround to reload the extension when the source code changes
+// since vscode doesn't support hot reload for extensions
+const { IS_DEV, DEV_WORKSPACE_FOLDER } = process.env
+
+if (IS_DEV && IS_DEV !== "false") {
+ assert(DEV_WORKSPACE_FOLDER, "DEV_WORKSPACE_FOLDER must be set in development")
+ const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(DEV_WORKSPACE_FOLDER, "src/**/*"))
+
+ watcher.onDidChange(({ scheme, path }) => {
+ console.info(`${scheme} ${path} changed. Reloading VSCode...`)
+
+ vscode.commands.executeCommand("workbench.action.reloadWindow")
+ })
+}
diff --git a/webview-ui/scripts/build-react-no-split.js b/webview-ui/scripts/build-react-no-split.js
index 28f37108be..d393018c3c 100644
--- a/webview-ui/scripts/build-react-no-split.js
+++ b/webview-ui/scripts/build-react-no-split.js
@@ -12,6 +12,7 @@
const rewire = require("rewire")
const defaults = rewire("react-scripts/scripts/build.js")
const config = defaults.__get__("config")
+const webpack = require("webpack")
/* Modifying Webpack Configuration for 'shared' dir
This section uses Rewire to modify Create React App's webpack configuration without ejecting. Rewire allows us to inject and alter the internal build scripts of CRA at runtime. This allows us to maintain a flexible project structure that keeps shared code outside the webview-ui/src directory, while still adhering to CRA's security model that typically restricts imports to within src/.
@@ -119,6 +120,15 @@ config.output = {
filename: "static/js/[name].js",
}
+// Adjust build environment variables for dev/debug builds.
+config.plugins[4] = new webpack.DefinePlugin({
+ "process.env": {
+ ...config.plugins[4].definitions["process.env"],
+ NODE_ENV: JSON.stringify(process.env.IS_DEV ? "development" : "production"),
+ IS_DEV: JSON.stringify(process.env.IS_DEV),
+ },
+})
+
// Rename main.{hash}.css to main.css
config.plugins[5].options.filename = "static/css/[name].css"
config.plugins[5].options.moduleFilename = () => "static/css/main.css"
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index ad1e141fa5..994aab161f 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -5,7 +5,7 @@ import { validateApiConfiguration, validateModelId } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import ApiOptions from "./ApiOptions"
import SettingsButton from "../common/SettingsButton"
-const IS_DEV = false // FIXME: use flags when packaging
+const { IS_DEV } = process.env
type SettingsViewProps = {
onDone: () => void
From 408c0887ac04eb5e09b72c28e79f32ed198654f3 Mon Sep 17 00:00:00 2001
From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Date: Mon, 24 Feb 2025 18:17:35 -0800
Subject: [PATCH 16/87] Update default model ID
---
src/shared/api.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/shared/api.ts b/src/shared/api.ts
index a5a96d4a7f..e335732056 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -205,7 +205,7 @@ export const bedrockModels = {
// OpenRouter
// https://openrouter.ai/models?order=newest&supported_parameters=tools
-export const openRouterDefaultModelId = "anthropic/claude-3-7-sonnet" // will always exist in openRouterModels
+export const openRouterDefaultModelId = "anthropic/claude-3.7-sonnet" // will always exist in openRouterModels
export const openRouterDefaultModelInfo: ModelInfo = {
maxTokens: 8192,
contextWindow: 200_000,
From 4f6605ccdbbd49025d1716a9c7c6933fbae59afd Mon Sep 17 00:00:00 2001
From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Date: Mon, 24 Feb 2025 18:21:02 -0800
Subject: [PATCH 17/87] Update version
---
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 7651783195..4048df5cbd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
- "version": "3.4.3",
+ "version": "3.4.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
- "version": "3.4.3",
+ "version": "3.4.6",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
diff --git a/package.json b/package.json
index 32e12aa25a..523964ef32 100644
--- a/package.json
+++ b/package.json
@@ -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.4.6",
+ "version": "3.4.7",
"icon": "assets/icons/icon.png",
"galleryBanner": {
"color": "#617A91",
From 35a64a366655fbec0ed020d24e31f0eadfd567d9 Mon Sep 17 00:00:00 2001
From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Date: Mon, 24 Feb 2025 22:57:02 -0800
Subject: [PATCH 18/87] Modify MCP marketplace installation instructions
---
package.json | 2 +-
src/core/webview/ClineProvider.ts | 6 ++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 523964ef32..0951cf6c33 100644
--- a/package.json
+++ b/package.json
@@ -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.4.7",
+ "version": "3.4.8",
"icon": "assets/icons/icon.png",
"galleryBanner": {
"color": "#617A91",
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index d135372c60..7eb93c00c6 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -1249,11 +1249,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
// Create task with context from README and added guidelines for MCP server installation
const task = `Set up the MCP server from ${mcpDetails.githubUrl} while adhering to these MCP server installation rules:
- Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
-- Use commands aligned with the user's shell and operating system best practices. The user's shell is: ${getShell()}.
- Create the directory for the new MCP server before starting installation.
-- Follow the MCP servers README exactly—only deviate if it clearly conflicts with the user's OS, in which case proceed thoughtfully.
-- Ensure any steps requiring the use of pip, npm, or any other package manager, are followed as required.
-- After running each command, read its output carefully and adjust subsequent steps as needed based on that information.
+- Use commands aligned with the user's shell and operating system best practices.
+- The following README may contain instructions that conflict with the user's OS, in which case proceed thoughtfully.
- Once installed, demonstrate the server's capabilities by using one of its tools.
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
From 568932492b6dbd74380ae5d380c5b41aad100aa9 Mon Sep 17 00:00:00 2001
From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Date: Tue, 25 Feb 2025 15:23:38 -0800
Subject: [PATCH 19/87] Add optional opt-in telemetry to help fix bugs and
improve product
---
docs/PRIVACY.md | 23 +++++-
package-lock.json | 17 ++++-
package.json | 6 ++
src/core/webview/ClineProvider.ts | 50 ++++++++++---
src/extension.ts | 2 +
src/services/telemetry/TelemetryService.ts | 73 +++++++++++++++++++
src/shared/ExtensionMessage.ts | 1 +
src/shared/WebviewMessage.ts | 2 +
webview-ui/src/components/chat/ChatView.tsx | 5 +-
.../src/components/common/TelemetryBanner.tsx | 70 ++++++++++++++++++
.../src/context/ExtensionStateContext.tsx | 1 +
11 files changed, 233 insertions(+), 17 deletions(-)
create mode 100644 src/services/telemetry/TelemetryService.ts
create mode 100644 webview-ui/src/components/common/TelemetryBanner.tsx
diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md
index 548948b359..d293dc814a 100644
--- a/docs/PRIVACY.md
+++ b/docs/PRIVACY.md
@@ -42,8 +42,8 @@ Cline functions solely as a client-side VS Code extension that facilitates commu
1. **Local-Only Processing**:
- All operations happen on your local machine
- - No central servers or data collection
- - No telemetry or usage statistics gathered
+ - No central servers or data collection by default
+ - Anonymous telemetry and usage statistics are only collected if you explicitly opt in
- No account creation required
2. **API Key Security**:
@@ -73,7 +73,8 @@ When you request assistance:
- Error logs are processed locally
- No automatic error reporting to Cline
-- You control what information to include when reporting issues
+ - Optional anonymous telemetry and error reporting via PostHog if you opt in
+- You control what information to include when manually reporting issues
## Children's Privacy
@@ -90,6 +91,22 @@ We will post any changes to this policy on our GitHub repository. Significant ch
- You can inspect exactly what data is being sent to AI providers
- Enterprise users can implement additional access controls through VS Code
+## Telemetry & Usage Statistics
+
+If you choose to opt in to anonymous telemetry:
+
+- Basic usage statistics and error reports are collected via PostHog
+- A stable, anonymous identifier (VS Code's `machineId`) is used to understand unique usage patterns
+ - This identifier is not linked to any personal information
+ - It helps us understand how features are used across sessions
+ - It cannot be used to identify you personally
+- All data is anonymized and cannot be linked to individual users
+- No code content or sensitive information is ever included
+- You can opt out at any time through:
+ - VS Code Settings > Cline > Enable Telemetry
+ - VS Code Settings > Telemetry > Telemetry Level (setting this to anything other than "all" will disable Cline's telemetry)
+- Collected data helps us improve the extension's functionality and stability
+
## Contact Us
For privacy-related questions or concerns:
diff --git a/package-lock.json b/package-lock.json
index 4048df5cbd..41c8953530 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
- "version": "3.4.6",
+ "version": "3.4.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
- "version": "3.4.6",
+ "version": "3.4.8",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
@@ -40,6 +40,7 @@
"os-name": "^6.0.0",
"p-wait-for": "^5.0.2",
"pdf-parse": "^1.1.1",
+ "posthog-node": "^4.7.0",
"puppeteer-chromium-resolver": "^23.0.0",
"puppeteer-core": "^23.4.0",
"serialize-error": "^11.0.3",
@@ -11606,6 +11607,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/posthog-node": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.7.0.tgz",
+ "integrity": "sha512-RgdUKSW8MfMOkjUa8cYVqWndNjPePNuuxlGbrZC6z1WRBsVc6TdGl8caidmC10RW8mu/BOfmrGbP4cRTo2jARg==",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^1.7.4"
+ },
+ "engines": {
+ "node": ">=15.0.0"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
diff --git a/package.json b/package.json
index 0951cf6c33..97f6ba9ef8 100644
--- a/package.json
+++ b/package.json
@@ -186,6 +186,11 @@
"type": "boolean",
"default": true,
"description": "Controls whether the MCP Marketplace is enabled."
+ },
+ "cline.enableTelemetry": {
+ "type": "boolean",
+ "default": null,
+ "markdownDescription": "Allow anonymous usage and error reporting to help improve Cline. No code, prompts, or personal information is ever sent. See our [privacy policy](https://github.com/cline/cline/blob/main/docs/PRIVACY.md) for details."
}
}
}
@@ -268,6 +273,7 @@
"os-name": "^6.0.0",
"p-wait-for": "^5.0.2",
"pdf-parse": "^1.1.1",
+ "posthog-node": "^4.7.0",
"puppeteer-chromium-resolver": "^23.0.0",
"puppeteer-core": "^23.4.0",
"serialize-error": "^11.0.3",
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 7eb93c00c6..228a04230d 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -1,9 +1,9 @@
import { Anthropic } from "@anthropic-ai/sdk"
import axios from "axios"
-import fs from "fs/promises"
-import os from "os"
import crypto from "crypto"
import { execa } from "execa"
+import fs from "fs/promises"
+import os from "os"
import pWaitFor from "p-wait-for"
import * as path from "path"
import * as vscode from "vscode"
@@ -13,26 +13,25 @@ import { openFile, openImage } from "../../integrations/misc/open-file"
import { selectImages } from "../../integrations/misc/process-images"
import { getTheme } from "../../integrations/theme/getTheme"
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
-import { McpHub } from "../../services/mcp/McpHub"
-import { McpDownloadResponse, McpMarketplaceCatalog, McpMarketplaceItem, McpServer } from "../../shared/mcp"
import { FirebaseAuthManager, UserInfo } from "../../services/auth/FirebaseAuthManager"
+import { McpHub } from "../../services/mcp/McpHub"
import { ApiProvider, ModelInfo } from "../../shared/api"
import { findLast } from "../../shared/array"
+import { AutoApprovalSettings, DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings"
+import { BrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../shared/BrowserSettings"
+import { ChatContent } from "../../shared/ChatContent"
+import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings"
import { ExtensionMessage, ExtensionState, Platform } from "../../shared/ExtensionMessage"
import { HistoryItem } from "../../shared/HistoryItem"
+import { McpDownloadResponse, McpMarketplaceCatalog, McpServer } from "../../shared/mcp"
import { ClineCheckpointRestore, WebviewMessage } from "../../shared/WebviewMessage"
import { fileExistsAtPath } from "../../utils/fs"
+import { searchCommits } from "../../utils/git"
import { Cline } from "../Cline"
import { openMention } from "../mentions"
import { getNonce } from "./getNonce"
import { getUri } from "./getUri"
-import { AutoApprovalSettings, DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings"
-import { BrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../shared/BrowserSettings"
-import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings"
-import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider"
-import { searchCommits } from "../../utils/git"
-import { ChatContent } from "../../shared/ChatContent"
-import { getShell } from "../../utils/shell"
+import { telemetryService } from "../../services/telemetry/TelemetryService"
/*
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -280,6 +279,16 @@ export class ClineProvider implements vscode.WebviewViewProvider {
task,
images,
)
+
+ // New task started
+ if (telemetryService.isTelemetryEnabled()) {
+ telemetryService.capture({
+ event: "New task started",
+ properties: {
+ apiProvider: apiConfiguration.apiProvider,
+ },
+ })
+ }
}
async initClineWithHistoryItem(historyItem: HistoryItem) {
@@ -829,6 +838,21 @@ export class ClineProvider implements vscode.WebviewViewProvider {
)
break
}
+ // telemetry
+ case "openTelemetrySettings": {
+ await vscode.commands.executeCommand(
+ "workbench.action.openSettings",
+ "@ext:saoudrizwan.claude-dev cline.telemetryOptIn",
+ )
+ break
+ }
+ case "telemetryOptIn": {
+ if (message.bool !== undefined) {
+ await vscode.workspace.getConfiguration("cline").update("enableTelemetry", message.bool, true)
+ await this.postStateToWebview()
+ }
+ break
+ }
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
@@ -1594,6 +1618,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
userInfo,
authToken,
mcpMarketplaceEnabled,
+ telemetryOptIn,
} = await this.getState()
return {
@@ -1613,6 +1638,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
isLoggedIn: !!authToken,
userInfo,
mcpMarketplaceEnabled,
+ telemetryOptIn,
}
}
@@ -1791,6 +1817,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
.get("reasoningEffort", "medium")
const mcpMarketplaceEnabled = vscode.workspace.getConfiguration("cline").get("mcpMarketplace.enabled", true)
+ const telemetryOptIn = vscode.workspace.getConfiguration("cline").get("enableTelemetry", null)
return {
apiConfiguration: {
@@ -1847,6 +1874,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
previousModeModelId,
previousModeModelInfo,
mcpMarketplaceEnabled,
+ telemetryOptIn,
}
}
diff --git a/src/extension.ts b/src/extension.ts
index 991f415a00..e485cd352d 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -8,6 +8,7 @@ import { createClineAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
import assert from "node:assert"
+import { telemetryService } from "./services/telemetry/TelemetryService"
/*
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -189,6 +190,7 @@ export function activate(context: vscode.ExtensionContext) {
// This method is called when your extension is deactivated
export function deactivate() {
+ telemetryService.shutdown()
Logger.log("Cline extension deactivated")
}
diff --git a/src/services/telemetry/TelemetryService.ts b/src/services/telemetry/TelemetryService.ts
new file mode 100644
index 0000000000..1dea301c36
--- /dev/null
+++ b/src/services/telemetry/TelemetryService.ts
@@ -0,0 +1,73 @@
+import { PostHog } from "posthog-node"
+import * as vscode from "vscode"
+
+class PostHogClient {
+ private static instance: PostHogClient
+ private client: PostHog
+ private distinctId: string = vscode.env.machineId
+ private telemetryEnabled: boolean = false
+
+ private constructor() {
+ this.client = new PostHog("phc_qfOAGxZw2TL5O8p9KYd9ak3bPBFzfjC8fy5L6jNWY7K", {
+ host: "https://us.i.posthog.com",
+ enableExceptionAutocapture: false,
+ })
+
+ // Initialize telemetry state based on user settings
+ this.updateTelemetryState()
+
+ // Listen for settings changes
+ vscode.workspace.onDidChangeConfiguration((e) => {
+ if (e.affectsConfiguration("cline.enableTelemetry") || e.affectsConfiguration("telemetry.telemetryLevel")) {
+ this.updateTelemetryState()
+ }
+ })
+ }
+
+ private updateTelemetryState(): void {
+ // First check global telemetry level - telemetry should only be enabled when level is "all"
+ const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get("telemetryLevel", "all")
+ const globalTelemetryEnabled = telemetryLevel === "all"
+
+ // Only check Cline setting if global telemetry is enabled
+ if (globalTelemetryEnabled) {
+ const clineOptIn = vscode.workspace.getConfiguration("cline").get("enableTelemetry", null)
+ this.telemetryEnabled = clineOptIn === true
+ }
+
+ // Update PostHog client state based on telemetry preference
+ if (this.telemetryEnabled) {
+ this.client.optIn()
+ // console.log("Telemetry enabled")
+ } else {
+ this.client.optOut()
+ // console.log("Telemetry disabled")
+ }
+ }
+
+ public static getInstance(): PostHogClient {
+ if (!PostHogClient.instance) {
+ PostHogClient.instance = new PostHogClient()
+ }
+ return PostHogClient.instance
+ }
+
+ public capture(event: { event: string; properties?: any }): void {
+ // Only send events if telemetry is enabled
+ if (this.telemetryEnabled) {
+ this.client.capture({ distinctId: this.distinctId, event: event.event, properties: event.properties })
+ // console.log("Captured event", { distinctId: this.distinctId, event: event.event, properties: event.properties })
+ }
+ }
+
+ public isTelemetryEnabled(): boolean {
+ return this.telemetryEnabled
+ }
+
+ public async shutdown(): Promise {
+ await this.client.shutdown()
+ }
+}
+
+// Export a single instance
+export const telemetryService = PostHogClient.getInstance()
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index 136eb8ac0c..ecba81781d 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -81,6 +81,7 @@ export interface ExtensionState {
photoURL: string | null
}
mcpMarketplaceEnabled?: boolean
+ telemetryOptIn: boolean | null
}
export interface ClineMessage {
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index e42cb5860b..62f154c06e 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -50,6 +50,8 @@ export interface WebviewMessage {
| "searchCommits"
| "showMcpView"
| "fetchLatestMcpServersFromHub"
+ | "telemetryOptIn"
+ | "openTelemetrySettings"
// | "relaunchChromeDebugMode"
text?: string
disabled?: boolean
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index a78ca0c2fa..a6c7f2dbfa 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -26,6 +26,7 @@ import BrowserSessionRow from "./BrowserSessionRow"
import ChatRow from "./ChatRow"
import ChatTextArea from "./ChatTextArea"
import TaskHeader from "./TaskHeader"
+import TelemetryBanner from "../common/TelemetryBanner"
interface ChatViewProps {
isHidden: boolean
@@ -37,7 +38,7 @@ interface ChatViewProps {
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
- const { version, clineMessages: messages, taskHistory, apiConfiguration } = useExtensionState()
+ const { version, clineMessages: messages, taskHistory, apiConfiguration, telemetryOptIn } = useExtensionState()
//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)
@@ -789,6 +790,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
flexDirection: "column",
paddingBottom: "10px",
}}>
+ {telemetryOptIn === null && }
+
{showAnnouncement && }
+ Help improve Cline by sending anonymous usage data and error reports. No code, prompts, or personal
+ information is ever sent. See our{" "}
+
+ privacy policy
+ {" "}
+ for more details.
+
+
+
{IS_DEV && (
<>
Debug
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index 3ad7de6461..f3d0619183 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -9,6 +9,7 @@ import { convertTextMateToHljs } from "../utils/textMateToHljs"
import { vscode } from "../utils/vscode"
import { DEFAULT_BROWSER_SETTINGS } from "../../../src/shared/BrowserSettings"
import { DEFAULT_CHAT_SETTINGS } from "../../../src/shared/ChatSettings"
+import { TelemetrySetting } from "../../../src/shared/TelemetrySetting"
interface ExtensionStateContextType extends ExtensionState {
didHydrateState: boolean
@@ -21,6 +22,7 @@ interface ExtensionStateContextType extends ExtensionState {
filePaths: string[]
setApiConfiguration: (config: ApiConfiguration) => void
setCustomInstructions: (value?: string) => void
+ setTelemetrySetting: (value: TelemetrySetting) => void
setShowAnnouncement: (value: boolean) => void
}
@@ -39,7 +41,7 @@ export const ExtensionStateContextProvider: React.FC<{
chatSettings: DEFAULT_CHAT_SETTINGS,
isLoggedIn: false,
platform: DEFAULT_PLATFORM,
- telemetryOptIn: null,
+ telemetrySetting: "unset",
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)
@@ -158,6 +160,11 @@ export const ExtensionStateContextProvider: React.FC<{
...prevState,
customInstructions: value,
})),
+ setTelemetrySetting: (value) =>
+ setState((prevState) => ({
+ ...prevState,
+ telemetrySetting: value,
+ })),
setShowAnnouncement: (value) =>
setState((prevState) => ({
...prevState,
From 5e65ea04c9164a6d07f364c637dc36a7201544cf Mon Sep 17 00:00:00 2001
From: watany <76135106+watany-dev@users.noreply.github.com>
Date: Thu, 27 Feb 2025 11:42:39 +0900
Subject: [PATCH 24/87] fix: Anthropic's default model is 3.7 (#1971)
* fix: Anthropic's default model is 3.7
* changeset
---
.changeset/smooth-fans-flow.md | 5 +++++
src/shared/api.ts | 4 ++--
2 files changed, 7 insertions(+), 2 deletions(-)
create mode 100644 .changeset/smooth-fans-flow.md
diff --git a/.changeset/smooth-fans-flow.md b/.changeset/smooth-fans-flow.md
new file mode 100644
index 0000000000..e86157df3e
--- /dev/null
+++ b/.changeset/smooth-fans-flow.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+fix: Anthropic's default model is 3.7
diff --git a/src/shared/api.ts b/src/shared/api.ts
index e335732056..7624d115aa 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -139,7 +139,7 @@ export const anthropicModels = {
// AWS Bedrock
// https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html
export type BedrockModelId = keyof typeof bedrockModels
-export const bedrockDefaultModelId: BedrockModelId = "anthropic.claude-3-5-sonnet-20241022-v2:0"
+export const bedrockDefaultModelId: BedrockModelId = "anthropic.claude-3-7-sonnet-20250219-v1:0"
export const bedrockModels = {
"anthropic.claude-3-7-sonnet-20250219-v1:0": {
maxTokens: 8192,
@@ -223,7 +223,7 @@ export const openRouterDefaultModelInfo: ModelInfo = {
// Vertex AI
// https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude
export type VertexModelId = keyof typeof vertexModels
-export const vertexDefaultModelId: VertexModelId = "claude-3-5-sonnet-v2@20241022"
+export const vertexDefaultModelId: VertexModelId = "claude-3-7-sonnet@20250219"
export const vertexModels = {
"claude-3-7-sonnet@20250219": {
maxTokens: 8192,
From 7bc02926b474e8ba03d818ac7d0bc119deb213bc Mon Sep 17 00:00:00 2001
From: Minhao-Zhang <41777700+Minhao-Zhang@users.noreply.github.com>
Date: Thu, 27 Feb 2025 11:05:19 +0800
Subject: [PATCH 25/87] Fix Official DeepSeek-V3 cost (#1944)
* update the api price for deepseek (discount period is over)
* update the api price for deepseek (discount period is over)
---
src/shared/api.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/shared/api.ts b/src/shared/api.ts
index 7624d115aa..6517f37006 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -461,9 +461,9 @@ export const deepSeekModels = {
supportsImages: false,
supportsPromptCache: true, // supports context caching, but not in the way anthropic does it (deepseek reports input tokens and reads/writes in the same usage report) FIXME: we need to show users cache stats how deepseek does it
inputPrice: 0, // technically there is no input price, it's all either a cache hit or miss (ApiOptions will not show this)
- outputPrice: 0.28,
- cacheWritesPrice: 0.14,
- cacheReadsPrice: 0.014,
+ outputPrice: 1.1,
+ cacheWritesPrice: 0.27,
+ cacheReadsPrice: 0.07,
},
"deepseek-reasoner": {
maxTokens: 8_000,
From 9449f5dcd1c101a645a7332b68ebd99c7de607d9 Mon Sep 17 00:00:00 2001
From: FlavioInacta
Date: Thu, 27 Feb 2025 04:53:33 +0100
Subject: [PATCH 26/87] fix: Checking "Support Images" had no effect on first
click (#1922)
---
.changeset/rare-pigs-divide.md | 5 +++++
webview-ui/src/components/settings/ApiOptions.tsx | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
create mode 100644 .changeset/rare-pigs-divide.md
diff --git a/.changeset/rare-pigs-divide.md b/.changeset/rare-pigs-divide.md
new file mode 100644
index 0000000000..de8450782f
--- /dev/null
+++ b/.changeset/rare-pigs-divide.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+Fix checking "Support Images" setting had no effect on first click
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx
index 3d6037e275..4929d43876 100644
--- a/webview-ui/src/components/settings/ApiOptions.tsx
+++ b/webview-ui/src/components/settings/ApiOptions.tsx
@@ -721,7 +721,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
{modelConfigurationSelected && (
<>
{
const isChecked = e.target.checked === true
let modelInfo = apiConfiguration?.openAiModelInfo
From d58c947c417686d9b143f7b0926195f5a3e2e9bb Mon Sep 17 00:00:00 2001
From: Daniel Steigman <35793213+NightTrek@users.noreply.github.com>
Date: Wed, 26 Feb 2025 20:14:43 -0800
Subject: [PATCH 27/87] v0.1 of a cline rules file for the extension (#1847)
* v0.1 of the cline rules file for the extension
* added changeset
* improved the cline rules file
* added new version of the webview state definition
* added more in depth pass from gemini
---
.changeset/dull-kangaroos-poke.md | 5 +
.clinerules | 517 +++++++++++++++++++
docs/architecture/README.md | 43 ++
docs/architecture/extension-architecture.mmd | 41 ++
4 files changed, 606 insertions(+)
create mode 100644 .changeset/dull-kangaroos-poke.md
create mode 100644 .clinerules
create mode 100644 docs/architecture/README.md
create mode 100644 docs/architecture/extension-architecture.mmd
diff --git a/.changeset/dull-kangaroos-poke.md b/.changeset/dull-kangaroos-poke.md
new file mode 100644
index 0000000000..66d5ceb47f
--- /dev/null
+++ b/.changeset/dull-kangaroos-poke.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+Added a .clinerules file with details about our most common features
diff --git a/.clinerules b/.clinerules
new file mode 100644
index 0000000000..3bfef9026b
--- /dev/null
+++ b/.clinerules
@@ -0,0 +1,517 @@
+# Cline Extension Architecture & Development Guide
+
+## Project Overview
+
+Cline is a VSCode extension that provides AI assistance through a combination of a core extension backend and a React-based webview frontend. The extension is built with TypeScript and follows a modular architecture pattern.
+
+## Architecture Overview
+
+```mermaid
+graph TB
+ subgraph VSCode Extension Host
+ subgraph Core Extension
+ ExtensionEntry[Extension Entry src/extension.ts]
+ ClineProvider[ClineProvider src/core/webview/ClineProvider.ts]
+ ClineClass[Cline Class src/core/Cline.ts]
+ GlobalState[VSCode Global State]
+ SecretsStorage[VSCode Secrets Storage]
+ end
+
+ subgraph Webview UI
+ WebviewApp[React App webview-ui/src/App.tsx]
+ ExtStateContext[ExtensionStateContext webview-ui/src/context/ExtensionStateContext.tsx]
+ ReactComponents[React Components]
+ end
+
+ subgraph Storage
+ TaskStorage[Task Storage Per-Task Files & History]
+ CheckpointSystem[Git-based Checkpoints]
+ end
+ end
+
+ %% Core Extension Data Flow
+ ExtensionEntry --> ClineProvider
+ ClineProvider --> ClineClass
+ ClineClass --> GlobalState
+ ClineClass --> SecretsStorage
+ ClineClass --> TaskStorage
+ ClineClass --> CheckpointSystem
+
+ %% Webview Data Flow
+ WebviewApp --> ExtStateContext
+ ExtStateContext --> ReactComponents
+
+ %% Bidirectional Communication
+ ClineProvider <-->|postMessage| ExtStateContext
+
+ style GlobalState fill:#f9f,stroke:#333,stroke-width:2px
+ style SecretsStorage fill:#f9f,stroke:#333,stroke-width:2px
+ style ExtStateContext fill:#bbf,stroke:#333,stroke-width:2px
+ style ClineProvider fill:#bfb,stroke:#333,stroke-width:2px
+```
+
+## Definitions
+
+- core extension: Anything inside the src folder starting with the Cline.ts file
+- core extension state: Managed by the ClineProvider class in src/core/webview/ClineProvider.ts, which serves as the single source of truth for the extension's state. It manages multiple types of persistent storage (global state, workspace state, and secrets), handles state distribution to both the core extension and webview components, and coordinates state across multiple extension instances. This includes managing API configurations, task history, settings, and MCP configurations.
+- webview: Anything inside the webview-ui. All the react or view's seen by the user and user interaction compone
+- webview state: Managed by ExtensionStateContext in webview-ui/src/context/ExtensionStateContext.tsx, which provides React components with access to the extension's state through a context provider pattern. It maintains local state for UI components, handles real-time updates through message events, manages partial message updates, and provides methods for state modifications. The context includes extension version, messages, task history, theme, API configurations, MCP servers, marketplace catalog, and workspace file paths. It synchronizes with the core extension through VSCode's message passing system and provides type-safe access to state through a custom hook (useExtensionState).
+
+### Core Extension State
+
+The `ClineProvider` class manages multiple types of persistent storage:
+
+- **Global State:** Stored across all VSCode instances. Used for settings and data that should persist globally.
+- **Workspace State:** Specific to the current workspace. Used for task-specific data and settings.
+- **Secrets:** Secure storage for sensitive information like API keys.
+
+The `ClineProvider` handles the distribution of state to both the core extension and webview components. It also coordinates state across multiple extension instances, ensuring consistency.
+
+### Webview State
+
+The `ExtensionStateContext` in `webview-ui/src/context/ExtensionStateContext.tsx` provides React components with access to the extension's state. It uses a context provider pattern and maintains local state for UI components. The context includes:
+
+- Extension version
+- Messages
+- Task history
+- Theme
+- API configurations
+- MCP servers
+- Marketplace catalog
+- Workspace file paths
+
+It synchronizes with the core extension through VSCode's message passing system and provides type-safe access to the state via a custom hook (`useExtensionState`).
+
+## Core Extension (Cline.ts)
+
+The Cline class is the heart of the extension, managing task execution, state persistence, and tool coordination. Each task runs in its own instance of the Cline class, ensuring isolation and proper state management.
+
+### Task Execution Loop
+
+The core task execution loop follows this pattern:
+
+```typescript
+class Cline {
+ async initiateTaskLoop(userContent: UserContent, isNewTask: boolean) {
+ while (!this.abort) {
+ // 1. Make API request and stream response
+ const stream = this.attemptApiRequest()
+
+ // 2. Parse and present content blocks
+ for await (const chunk of stream) {
+ switch (chunk.type) {
+ case "text":
+ // Parse into content blocks
+ this.assistantMessageContent = parseAssistantMessage(chunk.text)
+ // Present blocks to user
+ await this.presentAssistantMessage()
+ break
+ }
+ }
+
+ // 3. Wait for tool execution to complete
+ await pWaitFor(() => this.userMessageContentReady)
+
+ // 4. Continue loop with tool result
+ const recDidEndLoop = await this.recursivelyMakeClineRequests(
+ this.userMessageContent
+ )
+ }
+ }
+}
+```
+
+### Message Streaming System
+
+The streaming system handles real-time updates and partial content:
+
+```typescript
+class Cline {
+ async presentAssistantMessage() {
+ // Handle streaming locks to prevent race conditions
+ if (this.presentAssistantMessageLocked) {
+ this.presentAssistantMessageHasPendingUpdates = true
+ return
+ }
+ this.presentAssistantMessageLocked = true
+
+ // Present current content block
+ const block = this.assistantMessageContent[this.currentStreamingContentIndex]
+
+ // Handle different types of content
+ switch (block.type) {
+ case "text":
+ await this.say("text", content, undefined, block.partial)
+ break
+ case "tool_use":
+ // Handle tool execution
+ break
+ }
+
+ // Move to next block if complete
+ if (!block.partial) {
+ this.currentStreamingContentIndex++
+ }
+ }
+}
+```
+
+### Tool Execution Flow
+
+Tools follow a strict execution pattern:
+
+```typescript
+class Cline {
+ async executeToolWithApproval(block: ToolBlock) {
+ // 1. Check auto-approval settings
+ if (this.shouldAutoApproveTool(block.name)) {
+ await this.say("tool", message)
+ this.consecutiveAutoApprovedRequestsCount++
+ } else {
+ // 2. Request user approval
+ const didApprove = await askApproval("tool", message)
+ if (!didApprove) {
+ this.didRejectTool = true
+ return
+ }
+ }
+
+ // 3. Execute tool
+ const result = await this.executeTool(block)
+
+ // 4. Save checkpoint
+ await this.saveCheckpoint()
+
+ // 5. Return result to API
+ return result
+ }
+}
+```
+
+### Error Handling & Recovery
+
+The system includes robust error handling:
+
+```typescript
+class Cline {
+ async handleError(action: string, error: Error) {
+ // 1. Check if task was abandoned
+ if (this.abandoned) return
+
+ // 2. Format error message
+ const errorString = `Error ${action}: ${error.message}`
+
+ // 3. Present error to user
+ await this.say("error", errorString)
+
+ // 4. Add error to tool results
+ pushToolResult(formatResponse.toolError(errorString))
+
+ // 5. Cleanup resources
+ await this.diffViewProvider.revertChanges()
+ await this.browserSession.closeBrowser()
+ }
+}
+```
+
+### API Request & Token Management
+
+The Cline class handles API requests with built-in retry, streaming, and token management:
+
+```typescript
+class Cline {
+ async *attemptApiRequest(previousApiReqIndex: number): ApiStream {
+ // 1. Wait for MCP servers to connect
+ await pWaitFor(() => this.providerRef.deref()?.mcpHub?.isConnecting !== true)
+
+ // 2. Manage context window
+ const previousRequest = this.clineMessages[previousApiReqIndex]
+ if (previousRequest?.text) {
+ const { tokensIn, tokensOut } = JSON.parse(previousRequest.text)
+ const totalTokens = (tokensIn || 0) + (tokensOut || 0)
+
+ // Truncate conversation if approaching context limit
+ if (totalTokens >= maxAllowedSize) {
+ this.conversationHistoryDeletedRange = getNextTruncationRange(
+ this.apiConversationHistory,
+ this.conversationHistoryDeletedRange,
+ totalTokens / 2 > maxAllowedSize ? "quarter" : "half"
+ )
+ }
+ }
+
+ // 3. Handle streaming with automatic retry
+ try {
+ this.isWaitingForFirstChunk = true
+ const firstChunk = await iterator.next()
+ yield firstChunk.value
+ this.isWaitingForFirstChunk = false
+
+ // Stream remaining chunks
+ yield* iterator
+ } catch (error) {
+ // 4. Error handling with retry
+ if (isOpenRouter && !this.didAutomaticallyRetryFailedApiRequest) {
+ await delay(1000)
+ this.didAutomaticallyRetryFailedApiRequest = true
+ yield* this.attemptApiRequest(previousApiReqIndex)
+ return
+ }
+
+ // 5. Ask user to retry if automatic retry failed
+ const { response } = await this.ask(
+ "api_req_failed",
+ this.formatErrorWithStatusCode(error)
+ )
+ if (response === "yesButtonClicked") {
+ await this.say("api_req_retried")
+ yield* this.attemptApiRequest(previousApiReqIndex)
+ return
+ }
+ }
+ }
+}
+```
+
+Key features:
+
+1. **Context Window Management**
+ - Tracks token usage across requests
+ - Automatically truncates conversation when needed
+ - Preserves important context while freeing space
+ - Handles different model context sizes
+
+2. **Streaming Architecture**
+ - Real-time chunk processing
+ - Partial content handling
+ - Race condition prevention
+ - Error recovery during streaming
+
+3. **Error Handling**
+ - Automatic retry for transient failures
+ - User-prompted retry for persistent issues
+ - Detailed error reporting
+ - State cleanup on failure
+
+4. **Token Tracking**
+ - Per-request token counting
+ - Cumulative usage tracking
+ - Cost calculation
+ - Cache hit monitoring
+
+### Task State & Resumption
+
+The Cline class provides robust task state management and resumption capabilities:
+
+```typescript
+class Cline {
+ async resumeTaskFromHistory() {
+ // 1. Load saved state
+ this.clineMessages = await this.getSavedClineMessages()
+ this.apiConversationHistory = await this.getSavedApiConversationHistory()
+
+ // 2. Handle interrupted tool executions
+ const lastMessage = this.apiConversationHistory[this.apiConversationHistory.length - 1]
+ if (lastMessage.role === "assistant") {
+ const toolUseBlocks = content.filter(block => block.type === "tool_use")
+ if (toolUseBlocks.length > 0) {
+ // Add interrupted tool responses
+ const toolResponses = toolUseBlocks.map(block => ({
+ type: "tool_result",
+ tool_use_id: block.id,
+ content: "Task was interrupted before this tool call could be completed."
+ }))
+ modifiedOldUserContent = [...toolResponses]
+ }
+ }
+
+ // 3. Notify about interruption
+ const agoText = this.getTimeAgoText(lastMessage?.ts)
+ newUserContent.push({
+ type: "text",
+ text: `[TASK RESUMPTION] This task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context.`
+ })
+
+ // 4. Resume task execution
+ await this.initiateTaskLoop(newUserContent, false)
+ }
+
+ private async saveTaskState() {
+ // Save conversation history
+ await this.saveApiConversationHistory()
+ await this.saveClineMessages()
+
+ // Create checkpoint
+ const commitHash = await this.checkpointTracker?.commit()
+
+ // Update task history
+ await this.providerRef.deref()?.updateTaskHistory({
+ id: this.taskId,
+ ts: lastMessage.ts,
+ task: taskMessage.text,
+ // ... other metadata
+ })
+ }
+}
+```
+
+Key aspects of task state management:
+
+1. **Task Persistence**
+ - Each task has a unique ID and dedicated storage directory
+ - Conversation history is saved after each message
+ - File changes are tracked through Git-based checkpoints
+ - Terminal output and browser state are preserved
+
+2. **State Recovery**
+ - Tasks can be resumed from any point
+ - Interrupted tool executions are handled gracefully
+ - File changes can be restored from checkpoints
+ - Context is preserved across VSCode sessions
+
+3. **Workspace Synchronization**
+ - File changes are tracked through Git
+ - Checkpoints are created after tool executions
+ - State can be restored to any checkpoint
+ - Changes can be compared between checkpoints
+
+4. **Error Recovery**
+ - Failed API requests can be retried
+ - Interrupted tool executions are marked
+ - Resources are cleaned up properly
+ - User is notified of state changes
+
+## Data Flow & State Management
+
+### Core Extension Role
+
+The core extension (ClineProvider) acts as the single source of truth for all persistent state. It:
+- Manages VSCode global state and secrets storage
+- Coordinates state updates between components
+- Ensures state consistency across webview reloads
+- Handles task-specific state persistence
+- Manages checkpoint creation and restoration
+
+### Terminal Management
+
+The Cline class manages terminal instances and command execution:
+
+```typescript
+class Cline {
+ async executeCommandTool(command: string): Promise<[boolean, ToolResponse]> {
+ // 1. Get or create terminal
+ const terminalInfo = await this.terminalManager.getOrCreateTerminal(cwd)
+ terminalInfo.terminal.show()
+
+ // 2. Execute command with output streaming
+ const process = this.terminalManager.runCommand(terminalInfo, command)
+
+ // 3. Handle real-time output
+ let result = ""
+ process.on("line", (line) => {
+ result += line + "\n"
+ if (!didContinue) {
+ sendCommandOutput(line)
+ } else {
+ this.say("command_output", line)
+ }
+ })
+
+ // 4. Wait for completion or user feedback
+ let completed = false
+ process.once("completed", () => {
+ completed = true
+ })
+
+ await process
+
+ // 5. Return result
+ if (completed) {
+ return [false, `Command executed.\n${result}`]
+ } else {
+ return [
+ false,
+ `Command is still running in the user's terminal.\n${result}\n\nYou will be updated on the terminal status and new output in the future.`
+ ]
+ }
+ }
+}
+```
+
+Key features:
+1. **Terminal Instance Management**
+ - Multiple terminal support
+ - Terminal state tracking (busy/inactive)
+ - Process cooldown monitoring
+ - Output history per terminal
+
+2. **Command Execution**
+ - Real-time output streaming
+ - User feedback handling
+ - Process state monitoring
+ - Error recovery
+
+### Browser Session Management
+
+The Cline class handles browser automation through Puppeteer:
+
+```typescript
+class Cline {
+ async executeBrowserAction(action: BrowserAction): Promise {
+ switch (action) {
+ case "launch":
+ // 1. Launch browser with fixed resolution
+ await this.browserSession.launchBrowser()
+ return await this.browserSession.navigateToUrl(url)
+
+ case "click":
+ // 2. Handle click actions with coordinates
+ return await this.browserSession.click(coordinate)
+
+ case "type":
+ // 3. Handle keyboard input
+ return await this.browserSession.type(text)
+
+ case "close":
+ // 4. Clean up resources
+ return await this.browserSession.closeBrowser()
+ }
+ }
+}
+```
+
+Key aspects:
+1. **Browser Control**
+ - Fixed 900x600 resolution window
+ - Single instance per task lifecycle
+ - Automatic cleanup on task completion
+ - Console log capture
+
+2. **Interaction Handling**
+ - Coordinate-based clicking
+ - Keyboard input simulation
+ - Screenshot capture
+ - Error recovery
+
+## Conclusion
+
+This guide provides a comprehensive overview of the Cline extension architecture, with special focus on state management, data persistence, and code organization. Following these patterns ensures robust feature implementation with proper state handling across the extension's components.
+
+Remember:
+- Always persist important state in the extension
+- The core extension exists in the src/ folder
+- Use proper typing for all state and messages
+- Handle errors and edge cases
+- Test state persistence across webview reloads
+- Follow the established patterns for consistency
+- Place new code in appropriate directories
+- Maintain clear separation of concerns
+- Install dependencies in correct package.json
+
+## Contributing
+
+Contributions to the Cline extension are welcome! Please follow these guidelines:
+
+When adding new tools or API providers, follow the existing patterns in the `src/integrations/` and `src/api/providers/` directories, respectively. Ensure that your code is well-documented and includes appropriate error handling.
+
+The `.clineignore` file allows users to specify files and directories that Cline should not access. When implementing new features, respect the `.clineignore` rules and ensure that your code does not attempt to read or modify ignored files.
diff --git a/docs/architecture/README.md b/docs/architecture/README.md
new file mode 100644
index 0000000000..e108c5dbc4
--- /dev/null
+++ b/docs/architecture/README.md
@@ -0,0 +1,43 @@
+# Cline Extension Architecture
+
+This directory contains architectural documentation for the Cline VSCode extension.
+
+## Extension Architecture Diagram
+
+The [extension-architecture.mmd](./extension-architecture.mmd) file contains a Mermaid diagram showing the high-level architecture of the Cline extension. The diagram illustrates:
+
+1. **Core Extension**
+ - Extension entry point and main classes
+ - State management through VSCode's global state and secrets storage
+ - Core business logic in the Cline class
+
+2. **Webview UI**
+ - React-based user interface
+ - State management through ExtensionStateContext
+ - Component hierarchy
+
+3. **Storage**
+ - Task-specific storage for history and state
+ - Git-based checkpoint system for file changes
+
+4. **Data Flow**
+ - Core extension data flow between components
+ - Webview UI data flow
+ - Bidirectional communication between core and webview
+
+## Viewing the Diagram
+
+To view the diagram:
+1. Install a Mermaid diagram viewer extension in VSCode
+2. Open extension-architecture.mmd
+3. Use the extension's preview feature to render the diagram
+
+You can also view the diagram on GitHub, which has built-in Mermaid rendering support.
+
+## Color Scheme
+
+The diagram uses a high-contrast color scheme for better visibility:
+- Pink (#ff0066): Global state and secrets storage components
+- Blue (#0066ff): Extension state context
+- Green (#00cc66): Cline provider
+- All components use white text for maximum readability
diff --git a/docs/architecture/extension-architecture.mmd b/docs/architecture/extension-architecture.mmd
new file mode 100644
index 0000000000..88d397c875
--- /dev/null
+++ b/docs/architecture/extension-architecture.mmd
@@ -0,0 +1,41 @@
+graph TB
+ subgraph VSCode Extension Host
+ subgraph Core Extension
+ ExtensionEntry[Extension Entry src/extension.ts]
+ ClineProvider[ClineProvider src/core/webview/ClineProvider.ts]
+ ClineClass[Cline Class src/core/Cline.ts]
+ GlobalState[VSCode Global State]
+ SecretsStorage[VSCode Secrets Storage]
+ end
+
+ subgraph Webview UI
+ WebviewApp[React App webview-ui/src/App.tsx]
+ ExtStateContext[ExtensionStateContext webview-ui/src/context/ExtensionStateContext.tsx]
+ ReactComponents[React Components]
+ end
+
+ subgraph Storage
+ TaskStorage[Task Storage Per-Task Files & History]
+ CheckpointSystem[Git-based Checkpoints]
+ end
+ end
+
+ %% Core Extension Data Flow
+ ExtensionEntry --> ClineProvider
+ ClineProvider --> ClineClass
+ ClineClass --> GlobalState
+ ClineClass --> SecretsStorage
+ ClineClass --> TaskStorage
+ ClineClass --> CheckpointSystem
+
+ %% Webview Data Flow
+ WebviewApp --> ExtStateContext
+ ExtStateContext --> ReactComponents
+
+ %% Bidirectional Communication
+ ClineProvider <-->|postMessage| ExtStateContext
+
+ style GlobalState fill:#ff0066,stroke:#333,stroke-width:2px,color:#ffffff
+ style SecretsStorage fill:#ff0066,stroke:#333,stroke-width:2px,color:#ffffff
+ style ExtStateContext fill:#0066ff,stroke:#333,stroke-width:2px,color:#ffffff
+ style ClineProvider fill:#00cc66,stroke:#333,stroke-width:2px,color:#ffffff
From a229b8ce9d8095254daed9684e689adaf7fe87f9 Mon Sep 17 00:00:00 2001
From: Doug Daniels
Date: Thu, 27 Feb 2025 00:28:02 -0600
Subject: [PATCH 28/87] feat(vertex): Add prompt caching support for Claude on
Vertex AI (#1885)
* feat(vertex): Add prompt caching support for Claude on Vertex AI
* Remove countTokens update claude 3.7
* claude-3-7-sonnet@20250219 support in Vertex AI as default model
---
.changeset/shiny-garlics-sip.md | 5 +
package-lock.json | 443 +++-----------------------------
package.json | 2 +-
src/api/providers/vertex.ts | 122 ++++++++-
src/shared/api.ts | 24 +-
5 files changed, 166 insertions(+), 430 deletions(-)
create mode 100644 .changeset/shiny-garlics-sip.md
diff --git a/.changeset/shiny-garlics-sip.md b/.changeset/shiny-garlics-sip.md
new file mode 100644
index 0000000000..38bc329fec
--- /dev/null
+++ b/.changeset/shiny-garlics-sip.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": minor
+---
+
+feat(vertex): Add prompt caching support for Claude on Vertex AI
diff --git a/package-lock.json b/package-lock.json
index 41c8953530..2bafe4a886 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
"@anthropic-ai/sdk": "^0.26.0",
- "@anthropic-ai/vertex-sdk": "^0.4.1",
+ "@anthropic-ai/vertex-sdk": "^0.6.4",
"@google/generative-ai": "^0.18.0",
"@mistralai/mistralai": "^1.5.0",
"@modelcontextprotocol/sdk": "^1.0.1",
@@ -118,14 +118,39 @@
}
},
"node_modules/@anthropic-ai/vertex-sdk": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.4.1.tgz",
- "integrity": "sha512-RT/2CWzqyAcJDZWxnNc1mXa7XiiHDaQ9aknfW4mIDw6zE+Zj/R2vCKpTb0dIwrmHYNOyKQNaD7Z1ynDt9oXFWA==",
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.6.4.tgz",
+ "integrity": "sha512-rMBlO2jF53TfMRmsQMm1bPO2JRUh4jYddjq/OJLj8DSAkfbCrNWhc0yhDed6oLYJg5s+VpDbvlPzMggqHhTfMw==",
+ "license": "MIT",
"dependencies": {
- "@anthropic-ai/sdk": ">=0.14 <1",
+ "@anthropic-ai/sdk": ">=0.35 <1",
"google-auth-library": "^9.4.2"
}
},
+ "node_modules/@anthropic-ai/vertex-sdk/node_modules/@anthropic-ai/sdk": {
+ "version": "0.36.3",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.36.3.tgz",
+ "integrity": "sha512-+c0mMLxL/17yFZ4P5+U6bTWiCSFZUKJddrv01ud2aFBWnTPLdRncYV76D3q1tqfnL7aCnhRtykFnoCFzvr4U3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^18.11.18",
+ "@types/node-fetch": "^2.6.4",
+ "abort-controller": "^3.0.0",
+ "agentkeepalive": "^4.2.1",
+ "form-data-encoder": "1.7.2",
+ "formdata-node": "^4.3.2",
+ "node-fetch": "^2.6.7"
+ }
+ },
+ "node_modules/@anthropic-ai/vertex-sdk/node_modules/@types/node": {
+ "version": "18.19.76",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz",
+ "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
"node_modules/@aws-crypto/crc32": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
@@ -2534,74 +2559,6 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
- "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
- "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
- "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
- "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
@@ -2619,346 +2576,6 @@
"node": ">=18"
}
},
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
- "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
- "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
- "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
- "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
- "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
- "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
- "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
- "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
- "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
- "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
- "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
- "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
- "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
- "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
- "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
- "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
- "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
- "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
- "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
- "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
diff --git a/package.json b/package.json
index 7d62bcb74d..74d72afe49 100644
--- a/package.json
+++ b/package.json
@@ -239,7 +239,7 @@
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
"@anthropic-ai/sdk": "^0.26.0",
- "@anthropic-ai/vertex-sdk": "^0.4.1",
+ "@anthropic-ai/vertex-sdk": "^0.6.4",
"@google/generative-ai": "^0.18.0",
"@mistralai/mistralai": "^1.5.0",
"@modelcontextprotocol/sdk": "^1.0.1",
diff --git a/src/api/providers/vertex.ts b/src/api/providers/vertex.ts
index 286562ed45..314095ac3f 100644
--- a/src/api/providers/vertex.ts
+++ b/src/api/providers/vertex.ts
@@ -21,14 +21,113 @@ export class VertexHandler implements ApiHandler {
@withRetry()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
- const stream = await this.client.messages.create({
- model: this.getModel().id,
- max_tokens: this.getModel().info.maxTokens || 8192,
- temperature: 0,
- system: systemPrompt,
- messages,
- stream: true,
- })
+ const model = this.getModel()
+ const modelId = model.id
+
+ let stream
+ switch (modelId) {
+ case "claude-3-7-sonnet@20250219":
+ case "claude-3-5-sonnet-v2@20241022":
+ case "claude-3-5-sonnet@20240620":
+ case "claude-3-5-haiku@20241022":
+ case "claude-3-opus@20240229":
+ case "claude-3-haiku@20240307": {
+ // Find indices of user messages for cache control
+ const userMsgIndices = messages.reduce(
+ (acc, msg, index) => (msg.role === "user" ? [...acc, index] : acc),
+ [] as number[],
+ )
+ const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
+ const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
+
+ stream = await this.client.beta.messages.create(
+ {
+ model: modelId,
+ max_tokens: model.info.maxTokens || 8192,
+ temperature: 0,
+ system: [
+ {
+ text: systemPrompt,
+ type: "text",
+ cache_control: { type: "ephemeral" },
+ },
+ ],
+ messages: messages.map((message, index) => {
+ if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
+ return {
+ ...message,
+ content:
+ typeof message.content === "string"
+ ? [
+ {
+ type: "text",
+ text: message.content,
+ cache_control: {
+ type: "ephemeral",
+ },
+ },
+ ]
+ : message.content.map((content, contentIndex) =>
+ contentIndex === message.content.length - 1
+ ? {
+ ...content,
+ cache_control: {
+ type: "ephemeral",
+ },
+ }
+ : content,
+ ),
+ }
+ }
+ return {
+ ...message,
+ content:
+ typeof message.content === "string"
+ ? [
+ {
+ type: "text",
+ text: message.content,
+ },
+ ]
+ : message.content,
+ }
+ }),
+ stream: true,
+ },
+ {
+ headers: {},
+ },
+ )
+ break
+ }
+ default: {
+ stream = await this.client.beta.messages.create({
+ model: modelId,
+ max_tokens: model.info.maxTokens || 8192,
+ temperature: 0,
+ system: [
+ {
+ text: systemPrompt,
+ type: "text",
+ },
+ ],
+ messages: messages.map((message) => ({
+ ...message,
+ content:
+ typeof message.content === "string"
+ ? [
+ {
+ type: "text",
+ text: message.content,
+ },
+ ]
+ : message.content,
+ })),
+ stream: true,
+ })
+ break
+ }
+ }
for await (const chunk of stream) {
switch (chunk.type) {
case "message_start":
@@ -37,6 +136,8 @@ export class VertexHandler implements ApiHandler {
type: "usage",
inputTokens: usage.input_tokens || 0,
outputTokens: usage.output_tokens || 0,
+ cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
+ cacheReadTokens: usage.cache_read_input_tokens || undefined,
}
break
case "message_delta":
@@ -46,7 +147,8 @@ export class VertexHandler implements ApiHandler {
outputTokens: chunk.usage.output_tokens || 0,
}
break
-
+ case "message_stop":
+ break
case "content_block_start":
switch (chunk.content_block.type) {
case "text":
@@ -73,6 +175,8 @@ export class VertexHandler implements ApiHandler {
break
}
break
+ case "content_block_stop":
+ break
}
}
}
diff --git a/src/shared/api.ts b/src/shared/api.ts
index 6517f37006..fc3d3a7a1f 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -219,9 +219,9 @@ export const openRouterDefaultModelInfo: ModelInfo = {
description:
"Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. \n\nClaude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks.\n\nRead more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)",
}
-
// Vertex AI
// https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude
+// https://cloud.google.com/vertex-ai/generative-ai/pricing#partner-models
export type VertexModelId = keyof typeof vertexModels
export const vertexDefaultModelId: VertexModelId = "claude-3-7-sonnet@20250219"
export const vertexModels = {
@@ -230,7 +230,7 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsComputerUse: true,
- supportsPromptCache: false,
+ supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
},
@@ -239,41 +239,51 @@ export const vertexModels = {
contextWindow: 200_000,
supportsImages: true,
supportsComputerUse: true,
- supportsPromptCache: false,
+ supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
+ cacheWritesPrice: 3.75,
+ cacheReadsPrice: 0.3,
},
"claude-3-5-sonnet@20240620": {
maxTokens: 8192,
contextWindow: 200_000,
supportsImages: true,
- supportsPromptCache: false,
+ supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
+ cacheWritesPrice: 3.75,
+ cacheReadsPrice: 0.3,
},
"claude-3-5-haiku@20241022": {
maxTokens: 8192,
contextWindow: 200_000,
supportsImages: false,
- supportsPromptCache: false,
+ supportsPromptCache: true,
inputPrice: 1.0,
outputPrice: 5.0,
+ cacheWritesPrice: 1.25,
+ cacheReadsPrice: 0.1,
},
"claude-3-opus@20240229": {
maxTokens: 4096,
contextWindow: 200_000,
supportsImages: true,
- supportsPromptCache: false,
+ supportsPromptCache: true,
inputPrice: 15.0,
outputPrice: 75.0,
+ cacheWritesPrice: 18.75,
+ cacheReadsPrice: 1.5,
},
"claude-3-haiku@20240307": {
maxTokens: 4096,
contextWindow: 200_000,
supportsImages: true,
- supportsPromptCache: false,
+ supportsPromptCache: true,
inputPrice: 0.25,
outputPrice: 1.25,
+ cacheWritesPrice: 0.3,
+ cacheReadsPrice: 0.03,
},
} as const satisfies Record
From 202e3a6d057474a256c0e021439e52e4215f4716 Mon Sep 17 00:00:00 2001
From: Andrei Edell
Date: Tue, 25 Feb 2025 10:39:14 -1000
Subject: [PATCH 29/87] Hugelung/rich mcp response (#1941)
* showing images after mcp responses
* images now open in a webview tab
* Open Graph link metadata display for MCP responses
* almost totally working rich mcp response display with images and embeds
* closer
* header for response display
* updated styling of mcp responses
* default to plain text if rich response is loading
* formatting fix
* added changeset output
* remove some old code
* add the dashed border back
* avoid XSS attacks by sanitizing the preview image urls and embeds
* remove incorrect vendor prefix css
* delete old version of open image implementation
* undo some comment removals and cleanups to make PR easier to read
---------
Co-authored-by: Andrei Edell
---
.changeset/old-dancers-smell.md | 5 +
package-lock.json | 22 +
package.json | 1 +
src/core/webview/ClineProvider.ts | 59 +++
src/integrations/misc/link-preview.ts | 107 ++++
src/shared/ExtensionMessage.ts | 12 +
src/shared/WebviewMessage.ts | 7 +
webview-ui/package-lock.json | 12 +
webview-ui/package.json | 2 +
webview-ui/src/components/chat/ChatRow.tsx | 290 +++++------
webview-ui/src/components/mcp/LinkPreview.tsx | 188 +++++++
.../src/components/mcp/McpResponseDisplay.tsx | 460 ++++++++++++++++++
12 files changed, 990 insertions(+), 175 deletions(-)
create mode 100644 .changeset/old-dancers-smell.md
create mode 100644 src/integrations/misc/link-preview.ts
create mode 100644 webview-ui/src/components/mcp/LinkPreview.tsx
create mode 100644 webview-ui/src/components/mcp/McpResponseDisplay.tsx
diff --git a/.changeset/old-dancers-smell.md b/.changeset/old-dancers-smell.md
new file mode 100644
index 0000000000..45c0af9d1c
--- /dev/null
+++ b/.changeset/old-dancers-smell.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": minor
+---
+
+Add rich MCP responses with images and link previews
diff --git a/package-lock.json b/package-lock.json
index 2bafe4a886..0dae4ee453 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -36,6 +36,7 @@
"isbinaryfile": "^5.0.2",
"mammoth": "^1.8.0",
"monaco-vscode-textmate-theme-converter": "^0.1.7",
+ "open-graph-scraper": "^6.9.0",
"openai": "^4.83.0",
"os-name": "^6.0.0",
"p-wait-for": "^5.0.2",
@@ -10665,6 +10666,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/open-graph-scraper": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/open-graph-scraper/-/open-graph-scraper-6.9.0.tgz",
+ "integrity": "sha512-1KoV5v6GT0/MqlryrVGQROhEAD4u8wC3VjYOxsnhj3mWeGJ6N6nF/rbrcZREFr+kiYm9I5LMrzdK9t9hBMbL2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "chardet": "^2.0.0",
+ "cheerio": "^1.0.0-rc.12",
+ "iconv-lite": "^0.6.3",
+ "undici": "^6.21.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/open-graph-scraper/node_modules/chardet": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.0.0.tgz",
+ "integrity": "sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ==",
+ "license": "MIT"
+ },
"node_modules/openai": {
"version": "4.83.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.83.0.tgz",
diff --git a/package.json b/package.json
index 74d72afe49..9fa64f199a 100644
--- a/package.json
+++ b/package.json
@@ -264,6 +264,7 @@
"isbinaryfile": "^5.0.2",
"mammoth": "^1.8.0",
"monaco-vscode-textmate-theme-converter": "^0.1.7",
+ "open-graph-scraper": "^6.9.0",
"openai": "^4.83.0",
"os-name": "^6.0.0",
"p-wait-for": "^5.0.2",
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index a042e92363..5c486c1521 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -10,6 +10,7 @@ import * as vscode from "vscode"
import { buildApiHandler } from "../../api"
import { downloadTask } from "../../integrations/misc/export-markdown"
import { openFile, openImage } from "../../integrations/misc/open-file"
+import { fetchOpenGraphData, isImageUrl } from "../../integrations/misc/link-preview"
import { selectImages } from "../../integrations/misc/process-images"
import { getTheme } from "../../integrations/theme/getTheme"
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
@@ -663,6 +664,17 @@ export class ClineProvider implements vscode.WebviewViewProvider {
case "openImage":
openImage(message.text!)
break
+ case "openInBrowser":
+ if (message.url) {
+ vscode.env.openExternal(vscode.Uri.parse(message.url))
+ }
+ break
+ case "fetchOpenGraphData":
+ this.fetchOpenGraphData(message.text!)
+ break
+ case "checkIsImageUrl":
+ this.checkIsImageUrl(message.text!)
+ break
case "openFile":
openFile(message.text!)
break
@@ -1955,6 +1967,53 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
return await this.context.secrets.get(key)
}
+ // Open Graph Data
+
+ async fetchOpenGraphData(url: string) {
+ try {
+ // Use the fetchOpenGraphData function from link-preview.ts
+ const ogData = await fetchOpenGraphData(url)
+
+ // Send the data back to the webview
+ await this.postMessageToWebview({
+ type: "openGraphData",
+ openGraphData: ogData,
+ url: url,
+ })
+ } catch (error) {
+ console.error(`Error fetching Open Graph data for ${url}:`, error)
+ // Send an error response
+ await this.postMessageToWebview({
+ type: "openGraphData",
+ error: `Failed to fetch Open Graph data: ${error}`,
+ url: url,
+ })
+ }
+ }
+
+ // Check if a URL is an image
+ async checkIsImageUrl(url: string) {
+ try {
+ // Check if the URL is an image
+ const isImage = await isImageUrl(url)
+
+ // Send the result back to the webview
+ await this.postMessageToWebview({
+ type: "isImageUrlResult",
+ isImage,
+ url,
+ })
+ } catch (error) {
+ console.error(`Error checking if URL is an image: ${url}`, error)
+ // Send an error response
+ await this.postMessageToWebview({
+ type: "isImageUrlResult",
+ isImage: false,
+ url,
+ })
+ }
+ }
+
// dev
async resetState() {
diff --git a/src/integrations/misc/link-preview.ts b/src/integrations/misc/link-preview.ts
new file mode 100644
index 0000000000..ad2bdea194
--- /dev/null
+++ b/src/integrations/misc/link-preview.ts
@@ -0,0 +1,107 @@
+import axios from "axios"
+import ogs from "open-graph-scraper"
+
+export interface OpenGraphData {
+ title?: string
+ description?: string
+ image?: string
+ url?: string
+ siteName?: string
+ type?: string
+}
+
+/**
+ * Fetches Open Graph metadata from a URL
+ * @param url The URL to fetch metadata from
+ * @returns Promise resolving to OpenGraphData
+ */
+export async function fetchOpenGraphData(url: string): Promise {
+ try {
+ const options = {
+ url: url,
+ timeout: 5000,
+ headers: {
+ "user-agent": "Mozilla/5.0 (compatible; VSCodeExtension/1.0; +https://cline.bot)",
+ },
+ onlyGetOpenGraphInfo: false, // Get all metadata, not just Open Graph
+ fetchOptions: {
+ redirect: "follow", // Follow redirects
+ } as any,
+ }
+
+ const { result } = await ogs(options)
+
+ // Use type assertion to avoid TypeScript errors
+ const data = result as any
+
+ // Handle image URLs
+ let imageUrl = data.ogImage?.[0]?.url || data.twitterImage?.[0]?.url
+
+ // If the image URL is relative, make it absolute
+ if (imageUrl && (imageUrl.startsWith("/") || imageUrl.startsWith("./"))) {
+ try {
+ // Extract the base URL and make the relative URL absolute
+ const urlObj = new URL(url)
+ const baseUrl = `${urlObj.protocol}//${urlObj.hostname}`
+ imageUrl = new URL(imageUrl, baseUrl).href
+ } catch (error) {
+ console.error(`Error converting relative URL to absolute: ${imageUrl}`, error)
+ }
+ }
+
+ return {
+ title: data.ogTitle || data.twitterTitle || data.dcTitle || data.title || new URL(url).hostname,
+ description:
+ data.ogDescription ||
+ data.twitterDescription ||
+ data.dcDescription ||
+ data.description ||
+ "No description available",
+ image: imageUrl,
+ url: data.ogUrl || url,
+ siteName: data.ogSiteName || new URL(url).hostname,
+ type: data.ogType,
+ }
+ } catch (error) {
+ console.error(`Error fetching Open Graph data for ${url}:`, error)
+ // Return basic information based on the URL
+ try {
+ const urlObj = new URL(url)
+ return {
+ title: urlObj.hostname,
+ description: url,
+ url: url,
+ siteName: urlObj.hostname,
+ }
+ } catch {
+ return {
+ title: url,
+ description: url,
+ url: url,
+ }
+ }
+ }
+}
+
+/**
+ * Checks if a URL is an image by making a HEAD request and checking the content type
+ * @param url The URL to check
+ * @returns Promise resolving to boolean indicating if the URL is an image
+ */
+export async function isImageUrl(url: string): Promise {
+ try {
+ const response = await axios.head(url, {
+ headers: {
+ "User-Agent": "Mozilla/5.0 (compatible; VSCodeExtension/1.0; +https://cline.bot)",
+ },
+ timeout: 3000,
+ })
+
+ const contentType = response.headers["content-type"]
+ return contentType && contentType.startsWith("image/")
+ } catch (error) {
+ console.error(`Error checking if URL is an image: ${url}`, error)
+ // If we can't determine, fall back to checking the file extension
+ return /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(url)
+ }
+}
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index 7ee199fffb..bcad63b788 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -31,6 +31,8 @@ export interface ExtensionMessage {
| "mcpMarketplaceCatalog"
| "mcpDownloadDetails"
| "commitSearchResults"
+ | "openGraphData"
+ | "isImageUrlResult"
text?: string
action?:
| "chatButtonClicked"
@@ -55,6 +57,16 @@ export interface ExtensionMessage {
error?: string
mcpDownloadDetails?: McpDownloadResponse
commits?: GitCommit[]
+ openGraphData?: {
+ title?: string
+ description?: string
+ image?: string
+ url?: string
+ siteName?: string
+ type?: string
+ }
+ url?: string
+ isImage?: boolean
}
export type Platform = "aix" | "darwin" | "freebsd" | "linux" | "openbsd" | "sunos" | "win32" | "unknown"
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index cd4e877604..873a7390f9 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -22,6 +22,7 @@ export interface WebviewMessage {
| "requestOllamaModels"
| "requestLmStudioModels"
| "openImage"
+ | "openInBrowser"
| "openFile"
| "openMention"
| "cancelTask"
@@ -52,6 +53,9 @@ export interface WebviewMessage {
| "fetchLatestMcpServersFromHub"
| "telemetrySetting"
| "openSettings"
+ | "updateMcpTimeout"
+ | "fetchOpenGraphData"
+ | "checkIsImageUrl"
// | "relaunchChromeDebugMode"
text?: string
disabled?: boolean
@@ -70,6 +74,9 @@ export interface WebviewMessage {
serverName?: string
toolName?: string
autoApprove?: boolean
+
+ // For openInBrowser
+ url?: string
}
export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json
index 1fa57354ba..81b6f05e6f 100644
--- a/webview-ui/package-lock.json
+++ b/webview-ui/package-lock.json
@@ -9,8 +9,10 @@
"version": "0.1.0",
"dependencies": {
"@floating-ui/react": "^0.27.4",
+ "@types/dompurify": "^3.0.5",
"@vscode/webview-ui-toolkit": "^1.4.0",
"debounce": "^2.1.1",
+ "dompurify": "^3.2.4",
"fast-deep-equal": "^3.1.3",
"fuse.js": "^7.0.0",
"fzf": "^0.5.2",
@@ -4979,6 +4981,15 @@
"@types/d3-selection": "*"
}
},
+ "node_modules/@types/dompurify": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
+ "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/trusted-types": "*"
+ }
+ },
"node_modules/@types/eslint": {
"version": "8.56.12",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
@@ -8981,6 +8992,7 @@
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
"integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
diff --git a/webview-ui/package.json b/webview-ui/package.json
index fd3743ad01..97df450bbe 100644
--- a/webview-ui/package.json
+++ b/webview-ui/package.json
@@ -4,8 +4,10 @@
"private": true,
"dependencies": {
"@floating-ui/react": "^0.27.4",
+ "@types/dompurify": "^3.0.5",
"@vscode/webview-ui-toolkit": "^1.4.0",
"debounce": "^2.1.1",
+ "dompurify": "^3.2.4",
"fast-deep-equal": "^3.1.3",
"fuse.js": "^7.0.0",
"fzf": "^0.5.2",
diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx
index f77019fc3f..88f82c1366 100644
--- a/webview-ui/src/components/chat/ChatRow.tsx
+++ b/webview-ui/src/components/chat/ChatRow.tsx
@@ -25,6 +25,7 @@ import McpResourceRow from "../mcp/McpResourceRow"
import McpToolRow from "../mcp/McpToolRow"
import { highlightMentions } from "./TaskHeader"
import { CheckmarkControl } from "../common/CheckmarkControl"
+import McpResponseDisplay from "../mcp/McpResponseDisplay"
const ChatRowContainer = styled.div`
padding: 10px 6px 10px 15px;
@@ -46,62 +47,87 @@ interface ChatRowProps {
interface ChatRowContentProps extends Omit {}
-const ChatRow = memo(
- (props: ChatRowProps) => {
- const { isLast, onHeightChange, message, lastModifiedMessage } = props
- // Store the previous height to compare with the current height
- // This allows us to detect changes without causing re-renders
- const prevHeightRef = useRef(0)
-
- // NOTE: for tools that are interrupted and not responded to (approved or rejected), there won't be a checkpoint hash
- let shouldShowCheckpoints =
- message.lastCheckpointHash != null &&
- (message.say === "tool" ||
- message.ask === "tool" ||
- message.say === "command" ||
- message.ask === "command" ||
- // message.say === "completion_result" ||
- // message.ask === "completion_result" ||
- message.say === "use_mcp_server" ||
- message.ask === "use_mcp_server")
-
- if (shouldShowCheckpoints && isLast) {
- shouldShowCheckpoints =
- lastModifiedMessage?.ask === "resume_completed_task" || lastModifiedMessage?.ask === "resume_task"
- }
-
- const [chatrow, { height }] = useSize(
-
-
- {shouldShowCheckpoints && }
- ,
- )
-
- useEffect(() => {
- // used for partials, command output, etc.
- // NOTE: it's important we don't distinguish between partial or complete here since our scroll effects in chatview need to handle height change during partial -> complete
- const isInitialRender = prevHeightRef.current === 0 // prevents scrolling when new element is added since we already scroll for that
- // height starts off at Infinity
- if (isLast && height !== 0 && height !== Infinity && height !== prevHeightRef.current) {
- if (!isInitialRender) {
- onHeightChange(height > prevHeightRef.current)
- }
- prevHeightRef.current = height
- }
- }, [height, isLast, onHeightChange, message])
-
- // we cannot return null as virtuoso does not support it, so we use a separate visibleMessages array to filter out messages that should not be rendered
- return chatrow
- },
- // memo does shallow comparison of props, so we need to do deep comparison of arrays/objects whose properties might change
- deepEqual,
+export const ProgressIndicator = () => (
+
+ )
+})
+
+const ChatRow = memo((props: ChatRowProps) => {
+ const { isLast, onHeightChange, message, lastModifiedMessage } = props
+ // Store the previous height to compare with the current height
+ // This allows us to detect changes without causing re-renders
+ const prevHeightRef = useRef(0)
+
+ // NOTE: for tools that are interrupted and not responded to (approved or rejected) there won't be a checkpoint hash
+ let shouldShowCheckpoints =
+ message.lastCheckpointHash != null &&
+ (message.say === "tool" ||
+ message.ask === "tool" ||
+ message.say === "command" ||
+ message.ask === "command" ||
+ // message.say === "completion_result" ||
+ // message.ask === "completion_result" ||
+ message.say === "use_mcp_server" ||
+ message.ask === "use_mcp_server")
+
+ if (shouldShowCheckpoints && isLast) {
+ shouldShowCheckpoints = lastModifiedMessage?.ask === "resume_completed_task" || lastModifiedMessage?.ask === "resume_task"
+ }
+
+ const [chatrow, { height }] = useSize(
+
+
+ {shouldShowCheckpoints && }
+ ,
+ )
+
+ useEffect(() => {
+ // used for partials command output etc.
+ // NOTE: it's important we don't distinguish between partial or complete here since our scroll effects in chatview need to handle height change during partial -> complete
+ const isInitialRender = prevHeightRef.current === 0 // prevents scrolling when new element is added since we already scroll for that
+ // height starts off at Infinity
+ if (isLast && height !== 0 && height !== Infinity && height !== prevHeightRef.current) {
+ if (!isInitialRender) {
+ onHeightChange(height > prevHeightRef.current)
+ }
+ prevHeightRef.current = height
+ }
+ }, [height, isLast, onHeightChange, message])
+
+ // we cannot return null as virtuoso does not support it so we use a separate visibleMessages array to filter out messages that should not be rendered
+ return chatrow
+},
+// memo does shallow comparison of props, so we need to do deep comparison of arrays/objects whose properties might change
+deepEqual)
+
export default ChatRow
export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessage, isLast }: ChatRowContentProps) => {
const { mcpServers, mcpMarketplaceCatalog } = useExtensionState()
-
const [seeNewChangesDisabled, setSeeNewChangesDisabled] = useState(false)
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
@@ -111,11 +137,13 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
}
return [undefined, undefined, undefined]
}, [message.text, message.say])
- // when resuming task, last wont be api_req_failed but a resume_task message, so api_req_started will show loading spinner. that's why we just remove the last api_req_started that failed without streaming anything
+
+ // when resuming task last won't be api_req_failed but a resume_task message so api_req_started will show loading spinner. that's why we just remove the last api_req_started that failed without streaming anything
const apiRequestFailedMessage =
isLast && lastModifiedMessage?.ask === "api_req_failed" // if request is retried then the latest message is a api_req_retried
? lastModifiedMessage?.text
: undefined
+
const isCommandExecuting =
isLast &&
(lastModifiedMessage?.ask === "command" || lastModifiedMessage?.say === "command") &&
@@ -367,12 +395,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
Cline wants to read this file:
-
-
- Uh-oh, this could be a problem on end. We've been alerted and
- will resolve this ASAP. You can also{" "}
-
- contact us
-
- .
-
-
- )} */}
>
)}
@@ -809,6 +799,8 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
)
case "api_req_finished":
return null // we should never see this message type
+ case "mcp_server_response":
+ return
case "text":
return (