Refactor ClineProvider

This commit is contained in:
Saoud Rizwan
2024-10-05 23:16:25 -04:00
parent f0315c1520
commit da3aa7a658
5 changed files with 23 additions and 26 deletions

View File

@@ -41,7 +41,7 @@ import { parseAssistantMessage } from "./prompts/parse-assistant-message"
import { formatResponse } from "./prompts/responses" import { formatResponse } from "./prompts/responses"
import { addCustomInstructions, SYSTEM_PROMPT } from "./prompts/system" import { addCustomInstructions, SYSTEM_PROMPT } from "./prompts/system"
import { truncateHalfConversation } from "./sliding-window" import { truncateHalfConversation } from "./sliding-window"
import { ClaudeDevProvider, GlobalFileNames } from "./webview/ClaudeDevProvider" import { ClineProvider, GlobalFileNames } from "./webview/ClaudeDevProvider"
const cwd = const cwd =
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
@@ -66,7 +66,7 @@ export class ClaudeDev {
private askResponseImages?: string[] private askResponseImages?: string[]
private lastMessageTs?: number private lastMessageTs?: number
private consecutiveMistakeCount: number = 0 private consecutiveMistakeCount: number = 0
private providerRef: WeakRef<ClaudeDevProvider> private providerRef: WeakRef<ClineProvider>
private abort: boolean = false private abort: boolean = false
didFinishAborting = false didFinishAborting = false
private diffViewProvider: DiffViewProvider private diffViewProvider: DiffViewProvider
@@ -82,7 +82,7 @@ export class ClaudeDev {
private didCompleteReadingStream = false private didCompleteReadingStream = false
constructor( constructor(
provider: ClaudeDevProvider, provider: ClineProvider,
apiConfiguration: ApiConfiguration, apiConfiguration: ApiConfiguration,
customInstructions?: string, customInstructions?: string,
alwaysAllowReadOnly?: boolean, alwaysAllowReadOnly?: boolean,

View File

@@ -61,10 +61,10 @@ export const GlobalFileNames = {
openRouterModels: "openrouter_models.json", openRouterModels: "openrouter_models.json",
} }
export class ClaudeDevProvider implements vscode.WebviewViewProvider { export class ClineProvider implements vscode.WebviewViewProvider {
public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension. public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
public static readonly tabPanelId = "claude-dev.TabPanelProvider" public static readonly tabPanelId = "claude-dev.TabPanelProvider"
private static activeInstances: Set<ClaudeDevProvider> = new Set() private static activeInstances: Set<ClineProvider> = new Set()
private disposables: vscode.Disposable[] = [] private disposables: vscode.Disposable[] = []
private view?: vscode.WebviewView | vscode.WebviewPanel private view?: vscode.WebviewView | vscode.WebviewPanel
private claudeDev?: ClaudeDev private claudeDev?: ClaudeDev
@@ -72,8 +72,8 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
private latestAnnouncementId = "sep-21-2024" // update to some unique identifier when we add a new announcement private latestAnnouncementId = "sep-21-2024" // update to some unique identifier when we add a new announcement
constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) { constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) {
this.outputChannel.appendLine("ClaudeDevProvider instantiated") this.outputChannel.appendLine("ClineProvider instantiated")
ClaudeDevProvider.activeInstances.add(this) ClineProvider.activeInstances.add(this)
this.workspaceTracker = new WorkspaceTracker(this) this.workspaceTracker = new WorkspaceTracker(this)
this.revertKodu() this.revertKodu()
} }
@@ -105,7 +105,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
- https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts - https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
*/ */
async dispose() { async dispose() {
this.outputChannel.appendLine("Disposing ClaudeDevProvider...") this.outputChannel.appendLine("Disposing ClineProvider...")
await this.clearTask() await this.clearTask()
this.outputChannel.appendLine("Cleared task") this.outputChannel.appendLine("Cleared task")
if (this.view && "dispose" in this.view) { if (this.view && "dispose" in this.view) {
@@ -121,10 +121,10 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
this.workspaceTracker?.dispose() this.workspaceTracker?.dispose()
this.workspaceTracker = undefined this.workspaceTracker = undefined
this.outputChannel.appendLine("Disposed all disposables") this.outputChannel.appendLine("Disposed all disposables")
ClaudeDevProvider.activeInstances.delete(this) ClineProvider.activeInstances.delete(this)
} }
public static getVisibleInstance(): ClaudeDevProvider | undefined { public static getVisibleInstance(): ClineProvider | undefined {
return findLast(Array.from(this.activeInstances), (instance) => instance.view?.visible === true) return findLast(Array.from(this.activeInstances), (instance) => instance.view?.visible === true)
} }
@@ -752,10 +752,10 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
/* /*
Now that we use retainContextWhenHidden, we don't have to store a cache of claude messages in the user's state, but we could to reduce memory footprint in long conversations. Now that we use retainContextWhenHidden, we don't have to store a cache of claude messages in the user's state, but we could to reduce memory footprint in long conversations.
- We have to be careful of what state is shared between ClaudeDevProvider instances since there could be multiple instances of the extension running at once. For example when we cached claude messages using the same key, two instances of the extension could end up using the same key and overwriting each other's messages. - We have to be careful of what state is shared between ClineProvider instances since there could be multiple instances of the extension running at once. For example when we cached claude messages using the same key, two instances of the extension could end up using the same key and overwriting each other's messages.
- Some state does need to be shared between the instances, i.e. the API key--however there doesn't seem to be a good way to notfy the other instances that the API key has changed. - Some state does need to be shared between the instances, i.e. the API key--however there doesn't seem to be a good way to notfy the other instances that the API key has changed.
We need to use a unique identifier for each ClaudeDevProvider instance's message cache since we could be running several instances of the extension outside of just the sidebar i.e. in editor panels. We need to use a unique identifier for each ClineProvider instance's message cache since we could be running several instances of the extension outside of just the sidebar i.e. in editor panels.
For now since we don't need to store task history, we'll just use an identifier unique to this provider instance (since there can be several provider instances open at once). For now since we don't need to store task history, we'll just use an identifier unique to this provider instance (since there can be several provider instances open at once).
However in the future when we implement task history, we'll need to use a unique identifier for each task. As well as manage a data structure that keeps track of task history with their associated identifiers and the task message itself, to present in a 'Task History' view. However in the future when we implement task history, we'll need to use a unique identifier for each task. As well as manage a data structure that keeps track of task history with their associated identifiers and the task message itself, to present in a 'Task History' view.

View File

@@ -1,11 +1,8 @@
import * as vscode from "vscode" import * as vscode from "vscode"
import { ClaudeDevProvider } from "../core/webview/ClaudeDevProvider" import { ClineProvider } from "../core/webview/ClaudeDevProvider"
import { ClaudeDevAPI } from "./claude-dev" import { ClaudeDevAPI } from "./claude-dev"
export function createClaudeDevAPI( export function createClaudeDevAPI(outputChannel: vscode.OutputChannel, sidebarProvider: ClineProvider): ClaudeDevAPI {
outputChannel: vscode.OutputChannel,
sidebarProvider: ClaudeDevProvider
): ClaudeDevAPI {
const api: ClaudeDevAPI = { const api: ClaudeDevAPI = {
setCustomInstructions: async (value: string) => { setCustomInstructions: async (value: string) => {
await sidebarProvider.updateCustomInstructions(value) await sidebarProvider.updateCustomInstructions(value)

View File

@@ -1,7 +1,7 @@
// The module 'vscode' contains the VS Code extensibility API // The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below // Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode" import * as vscode from "vscode"
import { ClaudeDevProvider } from "./core/webview/ClaudeDevProvider" import { ClineProvider } from "./core/webview/ClaudeDevProvider"
import delay from "delay" import delay from "delay"
import { createClaudeDevAPI } from "./exports" import { createClaudeDevAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix import "./utils/path" // necessary to have access to String.prototype.toPosix
@@ -39,10 +39,10 @@ export function activate(context: vscode.ExtensionContext) {
// }) // })
// context.subscriptions.push(disposable) // context.subscriptions.push(disposable)
const sidebarProvider = new ClaudeDevProvider(context, outputChannel) const sidebarProvider = new ClineProvider(context, outputChannel)
context.subscriptions.push( context.subscriptions.push(
vscode.window.registerWebviewViewProvider(ClaudeDevProvider.sideBarId, sidebarProvider, { vscode.window.registerWebviewViewProvider(ClineProvider.sideBarId, sidebarProvider, {
webviewOptions: { retainContextWhenHidden: true }, webviewOptions: { retainContextWhenHidden: true },
}) })
) )
@@ -60,7 +60,7 @@ export function activate(context: vscode.ExtensionContext) {
outputChannel.appendLine("Opening Claude Dev in new tab") outputChannel.appendLine("Opening Claude Dev in new tab")
// (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event) // (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event)
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
const tabProvider = new ClaudeDevProvider(context, outputChannel) const tabProvider = new ClineProvider(context, outputChannel)
//const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined //const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0)) const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
@@ -71,7 +71,7 @@ export function activate(context: vscode.ExtensionContext) {
} }
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
const panel = vscode.window.createWebviewPanel(ClaudeDevProvider.tabPanelId, "Claude Dev", targetCol, { const panel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Claude Dev", targetCol, {
enableScripts: true, enableScripts: true,
retainContextWhenHidden: true, retainContextWhenHidden: true,
localResourceRoots: [context.extensionUri], localResourceRoots: [context.extensionUri],
@@ -126,7 +126,7 @@ export function activate(context: vscode.ExtensionContext) {
const handleUri = async (uri: vscode.Uri) => { const handleUri = async (uri: vscode.Uri) => {
const path = uri.path const path = uri.path
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B")) const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
const visibleProvider = ClaudeDevProvider.getVisibleInstance() const visibleProvider = ClineProvider.getVisibleInstance()
if (!visibleProvider) { if (!visibleProvider) {
return return
} }

View File

@@ -1,17 +1,17 @@
import * as vscode from "vscode" import * as vscode from "vscode"
import * as path from "path" import * as path from "path"
import { listFiles } from "../../services/glob/list-files" import { listFiles } from "../../services/glob/list-files"
import { ClaudeDevProvider } from "../../core/webview/ClaudeDevProvider" import { ClineProvider } from "../../core/webview/ClaudeDevProvider"
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
// Note: this is not a drop-in replacement for listFiles at the start of tasks, since that will be done for Desktops when there is no workspace selected // Note: this is not a drop-in replacement for listFiles at the start of tasks, since that will be done for Desktops when there is no workspace selected
class WorkspaceTracker { class WorkspaceTracker {
private providerRef: WeakRef<ClaudeDevProvider> private providerRef: WeakRef<ClineProvider>
private disposables: vscode.Disposable[] = [] private disposables: vscode.Disposable[] = []
private filePaths: Set<string> = new Set() private filePaths: Set<string> = new Set()
constructor(provider: ClaudeDevProvider) { constructor(provider: ClineProvider) {
this.providerRef = new WeakRef(provider) this.providerRef = new WeakRef(provider)
this.registerListeners() this.registerListeners()
} }