mirror of
https://github.com/cline/cline.git
synced 2026-09-06 20:41:02 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 257080b864 | |||
| 7c8bd0e7b2 | |||
| 59efda3e22 | |||
| 936923d334 | |||
| 0a57ec3b7d | |||
| 670f3a1d62 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
telemtrySetting protobus migration
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
clearAllTaskHistory protobus migration
|
||||
@@ -146,6 +146,7 @@
|
||||
"group": "Provider Configuration",
|
||||
"pages": [
|
||||
"provider-config/anthropic",
|
||||
"provider-config/claude-code",
|
||||
"provider-config/aws-bedrock-with-credentials-authentication",
|
||||
"provider-config/aws-bedrock-with-profile-authentication",
|
||||
"provider-config/gcp-vertex-ai",
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: "Claude Code"
|
||||
description: "Use your Claude Max subscription with Cline instead of paying per token. Learn how to set up and configure the Claude Code provider."
|
||||
---
|
||||
|
||||
**Website:** [https://docs.anthropic.com/en/docs/claude-code/setup](https://docs.anthropic.com/en/docs/claude-code/setup)
|
||||
|
||||
The Claude Code provider lets you use your existing Claude subscription with Cline. If you have Claude Max, this means you can use Claude in Cline without paying extra API costs.
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/claude-code-use-opus.gif"
|
||||
alt="Using the Claude Code provider in Cline with Opus model"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Setup
|
||||
|
||||
First, you'll need to install and authenticate Claude Code on your system:
|
||||
|
||||
1. **Install Claude Code**: Follow Anthropic's [official setup guide](https://docs.anthropic.com/en/docs/claude-code/setup) to install and authenticate the Claude CLI.
|
||||
|
||||
2. **Configure in Cline**:
|
||||
- Open Cline settings (⚙️ icon)
|
||||
- Select **Claude Code** from the **API Provider** dropdown
|
||||
- Set the path to your Claude CLI executable (usually just `claude` if it's in your PATH)
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/claude-code-setup.gif"
|
||||
alt="Setting up the Claude Code provider in Cline"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Finding your Claude Code path
|
||||
|
||||
If you're not sure where Claude Code is installed:
|
||||
|
||||
- **macOS / Linux**: Run `which claude` in your terminal
|
||||
- **Windows (Command Prompt)**: Run `where claude`
|
||||
- **Windows (PowerShell)**: Run `Get-Command claude`
|
||||
|
||||
## Supported Models
|
||||
|
||||
The Claude Code provider supports these models:
|
||||
|
||||
- `claude-sonnet-4-20250514` (Recommended)
|
||||
- `claude-opus-4-20250514`
|
||||
- `claude-3-7-sonnet-20250219`
|
||||
- `claude-3-5-sonnet-20241022`
|
||||
- `claude-3-5-haiku-20241022`
|
||||
|
||||
## How it works
|
||||
|
||||
When you use Claude Code with Cline, here's what happens behind the scenes:
|
||||
|
||||
Cline wraps the Claude Code CLI to handle your requests. Each time you send a message, Cline starts a new `claude` process, sends your conversation, and streams the response back. The AI reasoning comes from Claude Code, but all the actual file editing, terminal commands, and other tools are handled by Cline.
|
||||
|
||||
The main difference you'll notice is that responses don't stream character-by-character like other providers. Instead, Claude Code processes your full request before sending back the complete response.
|
||||
|
||||
## Limitations
|
||||
|
||||
There are a few things to keep in mind with Claude Code:
|
||||
|
||||
- Images in your messages get converted to text placeholders since Claude Code doesn't support image uploads through the CLI
|
||||
- Prompt caching isn't available with this provider
|
||||
- Responses don't stream in real-time like other providers
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you run into issues:
|
||||
|
||||
**Authentication problems**: Make sure you're logged into Claude Code with your subscription account. Run `claude auth status` to check.
|
||||
|
||||
**Path issues**: Double-check that the Claude CLI path in Cline's settings is correct. Try running `claude --version` in your terminal to verify it's working.
|
||||
|
||||
**Still having trouble?** We're actively improving this integration. Report issues on our [GitHub](https://github.com/cline/cline/issues) or ask for help in our [Discord](https://discord.gg/cline).
|
||||
|
||||
## Usage with subscriptions
|
||||
|
||||
If you have a Claude Max subscription, your usage in Cline shows up as $0.00 in the billing interface since you're not paying additional API costs. Your usage still counts against your subscription limits, but you won't see per-token charges.
|
||||
|
||||
For more details about using Claude Code with your subscription, check out Anthropic's documentation:
|
||||
|
||||
- [Claude Code Setup Guide](https://docs.anthropic.com/en/docs/claude-code/setup)
|
||||
- [Using Claude Code with Pro/Max Plans](https://support.anthropic.com/en/articles/11145838-using-claude-code-with-your-pro-or-max-plan)
|
||||
@@ -17,6 +17,7 @@ service StateService {
|
||||
rpc togglePlanActMode(TogglePlanActModeRequest) returns (Boolean);
|
||||
rpc updateAutoApprovalSettings(AutoApprovalSettingsRequest) returns (Empty);
|
||||
rpc updateSettings(UpdateSettingsRequest) returns (Empty);
|
||||
rpc updateTelemetrySetting(TelemetrySettingRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message State {
|
||||
@@ -88,6 +89,17 @@ message AutoApprovalSettingsRequest {
|
||||
repeated string favorites = 7;
|
||||
}
|
||||
|
||||
enum TelemetrySettingEnum {
|
||||
UNSET = 0;
|
||||
ENABLED = 1;
|
||||
DISABLED = 2;
|
||||
}
|
||||
|
||||
message TelemetrySettingRequest {
|
||||
Metadata metadata = 1;
|
||||
TelemetrySettingEnum setting = 2;
|
||||
}
|
||||
|
||||
// Message for updating settings
|
||||
message UpdateSettingsRequest {
|
||||
Metadata metadata = 1;
|
||||
|
||||
@@ -35,6 +35,8 @@ service TaskService {
|
||||
rpc taskCompletionViewChanges(Int64Request) returns (Empty);
|
||||
// Executes a quick win task with command and title
|
||||
rpc executeQuickWin(ExecuteQuickWinRequest) returns (Empty);
|
||||
// Deletes all task history
|
||||
rpc deleteAllTaskHistory(BooleanRequest) returns (DeleteAllTaskHistoryCount);
|
||||
}
|
||||
|
||||
// Request message for creating a new task
|
||||
@@ -116,3 +118,8 @@ message ExecuteQuickWinRequest {
|
||||
string command = 2;
|
||||
string title = 3;
|
||||
}
|
||||
|
||||
// Results returned when deleting all task history
|
||||
message DeleteAllTaskHistoryCount {
|
||||
int32 tasks_deleted = 1;
|
||||
}
|
||||
|
||||
@@ -265,4 +265,7 @@ service UiService {
|
||||
|
||||
// Returns the HTML for the webview index page. This is only used by external clients, not by the vscode webview.
|
||||
rpc getWebviewHtml(EmptyRequest) returns (String);
|
||||
|
||||
// Opens a URL in the default browser
|
||||
rpc openUrl(StringRequest) returns (Empty);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ import { sendAddToInputEvent } from "./ui/subscribeToAddToInput"
|
||||
import { sendAuthCallbackEvent } from "./account/subscribeToAuthCallback"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { sendRelinquishControlEvent } from "./ui/subscribeToRelinquishControl"
|
||||
import { handleTaskServiceRequest } from "./task"
|
||||
import { BooleanRequest } from "@shared/proto/common"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -216,35 +218,6 @@ export class Controller {
|
||||
await this.fetchMcpMarketplace(message.bool)
|
||||
break
|
||||
}
|
||||
|
||||
// telemetry
|
||||
case "telemetrySetting": {
|
||||
if (message.telemetrySetting) {
|
||||
await this.updateTelemetrySetting(message.telemetrySetting)
|
||||
}
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
}
|
||||
|
||||
case "clearAllTaskHistory": {
|
||||
const answer = await vscode.window.showWarningMessage(
|
||||
"What would you like to delete?",
|
||||
{ modal: true },
|
||||
"Delete All Except Favorites",
|
||||
"Delete Everything",
|
||||
"Cancel",
|
||||
)
|
||||
|
||||
if (answer === "Delete All Except Favorites") {
|
||||
await this.deleteNonFavoriteTaskHistory()
|
||||
await this.postStateToWebview()
|
||||
} else if (answer === "Delete Everything") {
|
||||
await this.deleteAllTaskHistory()
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
sendRelinquishControlEvent()
|
||||
break
|
||||
}
|
||||
case "grpc_request": {
|
||||
if (message.grpc_request) {
|
||||
await handleGrpcRequest(this, message.grpc_request)
|
||||
@@ -842,110 +815,6 @@ export class Controller {
|
||||
await downloadTask(historyItem.ts, apiConversationHistory)
|
||||
}
|
||||
|
||||
async deleteAllTaskHistory() {
|
||||
await this.clearTask()
|
||||
await updateGlobalState(this.context, "taskHistory", undefined)
|
||||
try {
|
||||
// Remove all contents of tasks directory
|
||||
const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks")
|
||||
if (await fileExistsAtPath(taskDirPath)) {
|
||||
await fs.rm(taskDirPath, { recursive: true, force: true })
|
||||
}
|
||||
// Remove checkpoints directory contents
|
||||
const checkpointsDirPath = path.join(this.context.globalStorageUri.fsPath, "checkpoints")
|
||||
if (await fileExistsAtPath(checkpointsDirPath)) {
|
||||
await fs.rm(checkpointsDirPath, { recursive: true, force: true })
|
||||
}
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Encountered error while deleting task history, there may be some files left behind. Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
// await this.postStateToWebview()
|
||||
}
|
||||
|
||||
async deleteNonFavoriteTaskHistory() {
|
||||
await this.clearTask()
|
||||
|
||||
const taskHistory = ((await getGlobalState(this.context, "taskHistory")) as HistoryItem[]) || []
|
||||
const favoritedTasks = taskHistory.filter((task) => task.isFavorited === true)
|
||||
|
||||
// If user has no favorited tasks, show a warning message
|
||||
if (favoritedTasks.length === 0) {
|
||||
vscode.window.showWarningMessage("No favorited tasks found. Please favorite tasks before using this option.")
|
||||
await this.postStateToWebview()
|
||||
return
|
||||
}
|
||||
|
||||
await updateGlobalState(this.context, "taskHistory", favoritedTasks)
|
||||
|
||||
// Delete non-favorited task directories
|
||||
try {
|
||||
const preserveTaskIds = favoritedTasks.map((task) => task.id)
|
||||
const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks")
|
||||
|
||||
if (await fileExistsAtPath(taskDirPath)) {
|
||||
const taskDirs = await fs.readdir(taskDirPath)
|
||||
for (const taskDir of taskDirs) {
|
||||
if (!preserveTaskIds.includes(taskDir)) {
|
||||
await fs.rm(path.join(taskDirPath, taskDir), { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Error deleting task history: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
|
||||
async deleteTaskWithId(id: string) {
|
||||
console.info("deleteTaskWithId: ", id)
|
||||
|
||||
try {
|
||||
if (id === this.task?.taskId) {
|
||||
await this.clearTask()
|
||||
console.debug("cleared task")
|
||||
}
|
||||
|
||||
const {
|
||||
taskDirPath,
|
||||
apiConversationHistoryFilePath,
|
||||
uiMessagesFilePath,
|
||||
contextHistoryFilePath,
|
||||
taskMetadataFilePath,
|
||||
} = await this.getTaskWithId(id)
|
||||
const legacyMessagesFilePath = path.join(taskDirPath, "claude_messages.json")
|
||||
const updatedTaskHistory = await this.deleteTaskFromState(id)
|
||||
|
||||
// Delete the task files
|
||||
for (const filePath of [
|
||||
apiConversationHistoryFilePath,
|
||||
uiMessagesFilePath,
|
||||
contextHistoryFilePath,
|
||||
taskMetadataFilePath,
|
||||
legacyMessagesFilePath,
|
||||
]) {
|
||||
const fileExists = await fileExistsAtPath(filePath)
|
||||
if (fileExists) {
|
||||
await fs.unlink(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
await fs.rmdir(taskDirPath) // succeeds if the dir is empty
|
||||
|
||||
if (updatedTaskHistory.length === 0) {
|
||||
await this.deleteAllTaskHistory()
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(`Error deleting task:`, error)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
|
||||
async deleteTaskFromState(id: string) {
|
||||
// Remove the task from history
|
||||
const taskHistory = ((await getGlobalState(this.context, "taskHistory")) as HistoryItem[] | undefined) || []
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import { TelemetrySettingRequest } from "../../../shared/proto/state"
|
||||
import { convertProtoTelemetrySettingToDomain } from "../../../shared/proto-conversions/state/telemetry-setting-conversion"
|
||||
|
||||
/**
|
||||
* Updates the telemetry setting
|
||||
* @param controller The controller instance
|
||||
* @param request The telemetry setting request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function updateTelemetrySetting(controller: Controller, request: TelemetrySettingRequest): Promise<Empty> {
|
||||
const telemetrySetting = convertProtoTelemetrySettingToDomain(request.setting)
|
||||
await controller.updateTelemetrySetting(telemetrySetting)
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { Controller } from ".."
|
||||
import { DeleteAllTaskHistoryCount } from "../../../shared/proto/task"
|
||||
import { BooleanRequest } from "../../../shared/proto/common"
|
||||
import { getGlobalState, updateGlobalState } from "../../storage/state"
|
||||
import { fileExistsAtPath } from "../../../utils/fs"
|
||||
import vscode from "vscode"
|
||||
|
||||
/**
|
||||
* Deletes all task history, with an option to preserve favorites
|
||||
* @param controller The controller instance
|
||||
* @param request Request with option to preserve favorites
|
||||
* @returns Results with count of deleted tasks
|
||||
*/
|
||||
export async function deleteAllTaskHistory(controller: Controller, request: BooleanRequest): Promise<DeleteAllTaskHistoryCount> {
|
||||
try {
|
||||
// Clear current task first
|
||||
await controller.clearTask()
|
||||
|
||||
// Get existing task history
|
||||
const taskHistory = ((await getGlobalState(controller.context, "taskHistory")) as any[]) || []
|
||||
const totalTasks = taskHistory.length
|
||||
|
||||
// If preserving favorites, filter out non-favorites
|
||||
if (request.value) {
|
||||
const favoritedTasks = taskHistory.filter((task) => task.isFavorited === true)
|
||||
|
||||
// If there are favorited tasks, update state
|
||||
if (favoritedTasks.length > 0) {
|
||||
await updateGlobalState(controller.context, "taskHistory", favoritedTasks)
|
||||
|
||||
// Delete non-favorited task directories
|
||||
const preserveTaskIds = favoritedTasks.map((task) => task.id)
|
||||
await cleanupTaskFiles(controller, preserveTaskIds)
|
||||
|
||||
// Update webview
|
||||
try {
|
||||
await controller.postStateToWebview()
|
||||
} catch (webviewErr) {
|
||||
console.error("Error posting to webview:", webviewErr)
|
||||
}
|
||||
|
||||
return DeleteAllTaskHistoryCount.create({
|
||||
tasksDeleted: totalTasks - favoritedTasks.length,
|
||||
})
|
||||
} else {
|
||||
return DeleteAllTaskHistoryCount.create({
|
||||
tasksDeleted: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Delete everything (not preserving favorites)
|
||||
await updateGlobalState(controller.context, "taskHistory", undefined)
|
||||
|
||||
try {
|
||||
// Remove all contents of tasks directory
|
||||
const taskDirPath = path.join(controller.context.globalStorageUri.fsPath, "tasks")
|
||||
if (await fileExistsAtPath(taskDirPath)) {
|
||||
await fs.rm(taskDirPath, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
// Remove checkpoints directory contents
|
||||
const checkpointsDirPath = path.join(controller.context.globalStorageUri.fsPath, "checkpoints")
|
||||
if (await fileExistsAtPath(checkpointsDirPath)) {
|
||||
await fs.rm(checkpointsDirPath, { recursive: true, force: true })
|
||||
}
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Encountered error while deleting task history, there may be some files left behind. Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
|
||||
// Update webview
|
||||
try {
|
||||
await controller.postStateToWebview()
|
||||
} catch (webviewErr) {
|
||||
console.error("Error posting to webview:", webviewErr)
|
||||
}
|
||||
|
||||
return DeleteAllTaskHistoryCount.create({
|
||||
tasksDeleted: totalTasks,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error in deleteAllTaskHistory:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to cleanup task files while preserving specified tasks
|
||||
*/
|
||||
async function cleanupTaskFiles(controller: Controller, preserveTaskIds: string[]) {
|
||||
const taskDirPath = path.join(controller.context.globalStorageUri.fsPath, "tasks")
|
||||
|
||||
try {
|
||||
if (await fileExistsAtPath(taskDirPath)) {
|
||||
const taskDirs = await fs.readdir(taskDirPath)
|
||||
console.debug(`[cleanupTaskFiles] Found ${taskDirs.length} task directories`)
|
||||
|
||||
// Delete only non-preserved task directories
|
||||
for (const dir of taskDirs) {
|
||||
if (!preserveTaskIds.includes(dir)) {
|
||||
await fs.rm(path.join(taskDirPath, dir), { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error cleaning up task files:", error)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { Controller } from ".."
|
||||
import { Empty, StringArrayRequest } from "../../../shared/proto/common"
|
||||
import { Empty, StringArrayRequest, BooleanRequest } from "../../../shared/proto/common"
|
||||
import { TaskMethodHandler } from "./index"
|
||||
import { fileExistsAtPath } from "../../../utils/fs"
|
||||
|
||||
/**
|
||||
* Deletes tasks with the specified IDs
|
||||
@@ -17,7 +20,72 @@ export const deleteTasksWithIds: TaskMethodHandler = async (
|
||||
throw new Error("Missing task IDs")
|
||||
}
|
||||
|
||||
await Promise.all(request.value.map((value) => controller.deleteTaskWithId(value)))
|
||||
for (const id of request.value) {
|
||||
await deleteTaskWithId(controller, id)
|
||||
}
|
||||
|
||||
return Empty.create()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single task with the specified ID
|
||||
* @param controller The controller instance
|
||||
* @param id The task ID to delete
|
||||
*/
|
||||
async function deleteTaskWithId(controller: Controller, id: string): Promise<void> {
|
||||
console.info("deleteTaskWithId: ", id)
|
||||
|
||||
try {
|
||||
// Clear current task if it matches the ID being deleted
|
||||
if (id === controller.task?.taskId) {
|
||||
await controller.clearTask()
|
||||
console.debug("cleared task")
|
||||
}
|
||||
|
||||
// Get task file paths
|
||||
const { taskDirPath, apiConversationHistoryFilePath, uiMessagesFilePath, contextHistoryFilePath, taskMetadataFilePath } =
|
||||
await controller.getTaskWithId(id)
|
||||
|
||||
// Remove task from state
|
||||
const updatedTaskHistory = await controller.deleteTaskFromState(id)
|
||||
|
||||
// Delete the task files
|
||||
for (const filePath of [
|
||||
apiConversationHistoryFilePath,
|
||||
uiMessagesFilePath,
|
||||
contextHistoryFilePath,
|
||||
taskMetadataFilePath,
|
||||
]) {
|
||||
const fileExists = await fileExistsAtPath(filePath)
|
||||
if (fileExists) {
|
||||
await fs.unlink(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty task directory
|
||||
try {
|
||||
await fs.rmdir(taskDirPath) // succeeds if the dir is empty
|
||||
} catch (error) {
|
||||
console.debug("Could not remove task directory (may not be empty):", error)
|
||||
}
|
||||
|
||||
// If no tasks remain, clean up everything
|
||||
if (updatedTaskHistory.length === 0) {
|
||||
const taskDirPath = path.join(controller.context.globalStorageUri.fsPath, "tasks")
|
||||
const checkpointsDirPath = path.join(controller.context.globalStorageUri.fsPath, "checkpoints")
|
||||
|
||||
if (await fileExistsAtPath(taskDirPath)) {
|
||||
await fs.rm(taskDirPath, { recursive: true, force: true })
|
||||
}
|
||||
if (await fileExistsAtPath(checkpointsDirPath)) {
|
||||
await fs.rm(checkpointsDirPath, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(`Error deleting task ${id}:`, error)
|
||||
throw error // Re-throw to let caller handle the error
|
||||
}
|
||||
|
||||
// Update webview state
|
||||
await controller.postStateToWebview()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Controller } from "../index"
|
||||
import type { StringRequest } from "../../../shared/proto/common"
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import { openUrlInBrowser } from "../../../utils/github-url-utils"
|
||||
|
||||
/**
|
||||
* Opens a URL in the default browser
|
||||
* @param controller The controller instance
|
||||
* @param request The URL to open
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function openUrl(controller: Controller, request: StringRequest): Promise<Empty> {
|
||||
try {
|
||||
await openUrlInBrowser(request.value)
|
||||
return Empty.create({})
|
||||
} catch (error) {
|
||||
console.error(`Failed to open URL: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ export interface WebviewMessage {
|
||||
| "fetchMcpMarketplace"
|
||||
| "searchCommits"
|
||||
| "telemetrySetting"
|
||||
| "clearAllTaskHistory"
|
||||
| "grpc_request"
|
||||
| "grpc_request_cancel"
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { TelemetrySettingEnum } from "../../proto/state"
|
||||
import { TelemetrySetting } from "../../TelemetrySetting"
|
||||
|
||||
/**
|
||||
* Converts a domain TelemetrySetting string to a proto TelemetrySettingEnum
|
||||
*/
|
||||
export function convertDomainTelemetrySettingToProto(setting: TelemetrySetting): TelemetrySettingEnum {
|
||||
switch (setting) {
|
||||
case "unset":
|
||||
return TelemetrySettingEnum.UNSET
|
||||
case "enabled":
|
||||
return TelemetrySettingEnum.ENABLED
|
||||
case "disabled":
|
||||
return TelemetrySettingEnum.DISABLED
|
||||
default:
|
||||
return TelemetrySettingEnum.UNSET
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a proto TelemetrySettingEnum to a domain TelemetrySetting string
|
||||
*/
|
||||
export function convertProtoTelemetrySettingToDomain(setting: TelemetrySettingEnum): TelemetrySetting {
|
||||
switch (setting) {
|
||||
case TelemetrySettingEnum.UNSET:
|
||||
return "unset"
|
||||
case TelemetrySettingEnum.ENABLED:
|
||||
return "enabled"
|
||||
case TelemetrySettingEnum.DISABLED:
|
||||
return "disabled"
|
||||
default:
|
||||
return "unset"
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import McpResponseDisplay from "@/components/mcp/chat-display/McpResponseDisplay
|
||||
import McpResourceRow from "@/components/mcp/configuration/tabs/installed/server-row/McpResourceRow"
|
||||
import McpToolRow from "@/components/mcp/configuration/tabs/installed/server-row/McpToolRow"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { FileServiceClient, TaskServiceClient } from "@/services/grpc-client"
|
||||
import { FileServiceClient, TaskServiceClient, UiServiceClient } from "@/services/grpc-client"
|
||||
import { findMatchingResourceOrTemplate, getMcpServerDisplayName } from "@/utils/mcp"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import {
|
||||
@@ -697,15 +697,13 @@ export const ChatRowContent = ({
|
||||
msUserSelect: "none",
|
||||
}}
|
||||
onClick={() => {
|
||||
// Attempt to open the URL in the default browser
|
||||
// Open the URL in the default browser using gRPC
|
||||
if (tool.path) {
|
||||
// Assuming 'openUrl' is a valid action the extension can handle.
|
||||
// If not, this might need adjustment based on how other external link openings are handled.
|
||||
vscode.postMessage({
|
||||
type: "action", // This should be a valid MessageType from WebviewMessage
|
||||
action: "openUrl", // This should be a valid WebviewAction from WebviewMessage
|
||||
url: tool.path,
|
||||
} as any) // Using 'as any' for now if 'openUrl' isn't strictly typed yet
|
||||
UiServiceClient.openUrl(StringRequest.create({ value: tool.path }))
|
||||
|
||||
.catch((err) => {
|
||||
console.error("Failed to open URL:", err)
|
||||
})
|
||||
}
|
||||
}}>
|
||||
<span
|
||||
|
||||
@@ -21,13 +21,6 @@ import {
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
function Demo() {
|
||||
// function handleHowdyClick() {
|
||||
// vscode.postMessage({
|
||||
// command: "hello",
|
||||
// text: "Hey there partner! 🤠",
|
||||
// })
|
||||
// }
|
||||
|
||||
const rowData = [
|
||||
{
|
||||
cell1: "Cell Data",
|
||||
|
||||
@@ -2,8 +2,8 @@ import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { StateServiceClient } from "@/services/grpc-client"
|
||||
import { TelemetrySettingEnum, TelemetrySettingRequest } from "@shared/proto/state"
|
||||
|
||||
const BannerContainer = styled.div`
|
||||
background-color: var(--vscode-banner-background);
|
||||
@@ -53,8 +53,16 @@ const TelemetryBanner = () => {
|
||||
navigateToSettings()
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
vscode.postMessage({ type: "telemetrySetting", telemetrySetting: "enabled" satisfies TelemetrySetting })
|
||||
const handleClose = async () => {
|
||||
try {
|
||||
await StateServiceClient.updateTelemetrySetting(
|
||||
TelemetrySettingRequest.create({
|
||||
setting: TelemetrySettingEnum.ENABLED,
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Error updating telemetry setting:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { TaskServiceClient } from "@/services/grpc-client"
|
||||
import { formatLargeNumber, formatSize } from "@/utils/format"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import { EmptyRequest, StringArrayRequest, StringRequest } from "@shared/proto/common"
|
||||
import { BooleanRequest, EmptyRequest, StringArrayRequest, StringRequest } from "@shared/proto/common"
|
||||
import { GetTaskHistoryRequest, TaskFavoriteRequest } from "@shared/proto/task"
|
||||
import { VSCodeButton, VSCodeCheckbox, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import Fuse, { FuseResult } from "fuse.js"
|
||||
@@ -727,7 +727,17 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
||||
disabled={deleteAllDisabled || taskHistory.length === 0}
|
||||
onClick={() => {
|
||||
setDeleteAllDisabled(true)
|
||||
vscode.postMessage({ type: "clearAllTaskHistory" })
|
||||
const confirmDelete = window.confirm("Are you sure you want to delete all task history?")
|
||||
if (confirmDelete) {
|
||||
const preserveFavorites = window.confirm(
|
||||
"Would you like to preserve favorited tasks?\n\nClick 'OK' to preserve favorites, or 'Cancel' to delete everything.",
|
||||
)
|
||||
TaskServiceClient.deleteAllTaskHistory(BooleanRequest.create({ value: preserveFavorites }))
|
||||
.catch((error) => console.error("Error deleting task history:", error))
|
||||
.finally(() => setDeleteAllDisabled(false))
|
||||
} else {
|
||||
setDeleteAllDisabled(false)
|
||||
}
|
||||
}}>
|
||||
Delete All History{totalTasksSize !== null ? ` (${formatSize(totalTasksSize)})` : ""}
|
||||
</DangerButton>
|
||||
|
||||
+16
-2
@@ -11,10 +11,12 @@ import {
|
||||
import { McpMarketplaceItem } from "@shared/mcp"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { McpServiceClient } from "@/services/grpc-client"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import McpMarketplaceCard from "./McpMarketplaceCard"
|
||||
import McpSubmitCard from "./McpSubmitCard"
|
||||
const McpMarketplaceView = () => {
|
||||
const { mcpServers, mcpMarketplaceCatalog } = useExtensionState()
|
||||
const { mcpServers, mcpMarketplaceCatalog, setMcpMarketplaceCatalog, mcpMarketplaceEnabled } = useExtensionState()
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
@@ -92,7 +94,19 @@ const McpMarketplaceView = () => {
|
||||
setIsLoading(true)
|
||||
}
|
||||
setError(null)
|
||||
vscode.postMessage({ type: "fetchMcpMarketplace", bool: forceRefresh })
|
||||
|
||||
if (mcpMarketplaceEnabled) {
|
||||
McpServiceClient.refreshMcpMarketplace(EmptyRequest.create({}))
|
||||
.then((response) => {
|
||||
setMcpMarketplaceCatalog(response)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error refreshing MCP marketplace:", error)
|
||||
setError("Failed to load marketplace data")
|
||||
setIsLoading(false)
|
||||
setIsRefreshing(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading || isRefreshing) {
|
||||
|
||||
@@ -1529,20 +1529,6 @@ const ApiOptions = ({
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
{/* Note: To fully implement this, you would need to add a handler in ClineProvider.ts */}
|
||||
{/* {apiConfiguration?.xaiApiKey && (
|
||||
<button
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "requestXAIModels",
|
||||
text: apiConfiguration?.xaiApiKey,
|
||||
})
|
||||
}}
|
||||
style={{ margin: "5px 0 0 0" }}
|
||||
className="vscode-button">
|
||||
Fetch Available Models
|
||||
</button>
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user