|
|
@@ -2,105 +2,201 @@ import { EventEmitter } from "events"
|
|
|
import * as vscode from "vscode"
|
|
|
|
|
|
import { ClineProvider } from "../core/webview/ClineProvider"
|
|
|
+import { openClineInNewTab } from "../activate/registerCommands"
|
|
|
|
|
|
-import { RooCodeAPI, RooCodeEvents, TokenUsage, RooCodeSettings } from "./roo-code"
|
|
|
-import { MessageHistory } from "./message-history"
|
|
|
+import { RooCodeSettings, RooCodeEvents, RooCodeEventName, ClineMessage } from "../schemas"
|
|
|
+import { IpcOrigin, IpcMessageType, TaskCommandName, TaskEvent } from "../schemas/ipc"
|
|
|
+import { RooCodeAPI } from "./interface"
|
|
|
+import { IpcServer } from "./ipc"
|
|
|
+import { outputChannelLog } from "./log"
|
|
|
|
|
|
export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
|
|
|
private readonly outputChannel: vscode.OutputChannel
|
|
|
- private readonly provider: ClineProvider
|
|
|
- private readonly history: MessageHistory
|
|
|
- private readonly tokenUsage: Record<string, TokenUsage>
|
|
|
-
|
|
|
- constructor(outputChannel: vscode.OutputChannel, provider: ClineProvider) {
|
|
|
+ private readonly sidebarProvider: ClineProvider
|
|
|
+ private tabProvider?: ClineProvider
|
|
|
+ private readonly context: vscode.ExtensionContext
|
|
|
+ private readonly ipc?: IpcServer
|
|
|
+ private readonly taskMap = new Map<string, ClineProvider>()
|
|
|
+ private readonly log: (...args: unknown[]) => void
|
|
|
+
|
|
|
+ constructor(
|
|
|
+ outputChannel: vscode.OutputChannel,
|
|
|
+ provider: ClineProvider,
|
|
|
+ socketPath?: string,
|
|
|
+ enableLogging = false,
|
|
|
+ ) {
|
|
|
super()
|
|
|
|
|
|
this.outputChannel = outputChannel
|
|
|
- this.provider = provider
|
|
|
- this.history = new MessageHistory()
|
|
|
- this.tokenUsage = {}
|
|
|
-
|
|
|
- this.provider.on("clineCreated", (cline) => {
|
|
|
- cline.on("message", (message) => this.emit("message", { taskId: cline.taskId, ...message }))
|
|
|
- cline.on("taskStarted", () => this.emit("taskStarted", cline.taskId))
|
|
|
- cline.on("taskPaused", () => this.emit("taskPaused", cline.taskId))
|
|
|
- cline.on("taskUnpaused", () => this.emit("taskUnpaused", cline.taskId))
|
|
|
- cline.on("taskAskResponded", () => this.emit("taskAskResponded", cline.taskId))
|
|
|
- cline.on("taskAborted", () => this.emit("taskAborted", cline.taskId))
|
|
|
- cline.on("taskSpawned", (childTaskId) => this.emit("taskSpawned", cline.taskId, childTaskId))
|
|
|
- cline.on("taskCompleted", (_, usage) => this.emit("taskCompleted", cline.taskId, usage))
|
|
|
- cline.on("taskTokenUsageUpdated", (_, usage) => this.emit("taskTokenUsageUpdated", cline.taskId, usage))
|
|
|
- this.emit("taskCreated", cline.taskId)
|
|
|
- })
|
|
|
+ this.sidebarProvider = provider
|
|
|
+ this.context = provider.context
|
|
|
+
|
|
|
+ this.log = enableLogging
|
|
|
+ ? (...args: unknown[]) => {
|
|
|
+ outputChannelLog(this.outputChannel, ...args)
|
|
|
+ console.log(args)
|
|
|
+ }
|
|
|
+ : () => {}
|
|
|
+
|
|
|
+ this.registerListeners(this.sidebarProvider)
|
|
|
+
|
|
|
+ if (socketPath) {
|
|
|
+ const ipc = (this.ipc = new IpcServer(socketPath, this.log))
|
|
|
+
|
|
|
+ ipc.listen()
|
|
|
+ this.log(`[API] ipc server started: socketPath=${socketPath}, pid=${process.pid}, ppid=${process.ppid}`)
|
|
|
+
|
|
|
+ ipc.on(IpcMessageType.TaskCommand, async (_clientId, { commandName, data }) => {
|
|
|
+ switch (commandName) {
|
|
|
+ case TaskCommandName.StartNewTask:
|
|
|
+ this.log(`[API] StartNewTask -> ${data.text}, ${JSON.stringify(data.configuration)}`)
|
|
|
+ await this.startNewTask(data)
|
|
|
+ break
|
|
|
+ case TaskCommandName.CancelTask:
|
|
|
+ this.log(`[API] CancelTask -> ${data}`)
|
|
|
+ await this.cancelTask(data)
|
|
|
+ break
|
|
|
+ case TaskCommandName.CloseTask:
|
|
|
+ this.log(`[API] CloseTask -> ${data}`)
|
|
|
+ await vscode.commands.executeCommand("workbench.action.files.saveFiles")
|
|
|
+ await vscode.commands.executeCommand("workbench.action.closeWindow")
|
|
|
+ break
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- this.on("message", ({ taskId, action, message }) => {
|
|
|
- if (action === "created") {
|
|
|
- this.history.add(taskId, message)
|
|
|
- } else if (action === "updated") {
|
|
|
- this.history.update(taskId, message)
|
|
|
+ public override emit<K extends keyof RooCodeEvents>(
|
|
|
+ eventName: K,
|
|
|
+ ...args: K extends keyof RooCodeEvents ? RooCodeEvents[K] : never
|
|
|
+ ) {
|
|
|
+ const data = { eventName: eventName as RooCodeEventName, payload: args } as TaskEvent
|
|
|
+ this.ipc?.broadcast({ type: IpcMessageType.TaskEvent, origin: IpcOrigin.Server, data })
|
|
|
+ return super.emit(eventName, ...args)
|
|
|
+ }
|
|
|
+
|
|
|
+ public async startNewTask({
|
|
|
+ configuration,
|
|
|
+ text,
|
|
|
+ images,
|
|
|
+ newTab,
|
|
|
+ }: {
|
|
|
+ configuration: RooCodeSettings
|
|
|
+ text?: string
|
|
|
+ images?: string[]
|
|
|
+ newTab?: boolean
|
|
|
+ }) {
|
|
|
+ let provider: ClineProvider
|
|
|
+
|
|
|
+ if (newTab) {
|
|
|
+ await vscode.commands.executeCommand("workbench.action.closeAllEditors")
|
|
|
+
|
|
|
+ if (!this.tabProvider) {
|
|
|
+ this.tabProvider = await openClineInNewTab({ context: this.context, outputChannel: this.outputChannel })
|
|
|
+ this.registerListeners(this.tabProvider)
|
|
|
}
|
|
|
- })
|
|
|
|
|
|
- this.on("taskTokenUsageUpdated", (taskId, usage) => (this.tokenUsage[taskId] = usage))
|
|
|
- }
|
|
|
+ provider = this.tabProvider
|
|
|
+ } else {
|
|
|
+ provider = this.sidebarProvider
|
|
|
+ }
|
|
|
+
|
|
|
+ if (configuration) {
|
|
|
+ await provider.setValues(configuration)
|
|
|
|
|
|
- public async startNewTask(text?: string, images?: string[]) {
|
|
|
- await this.provider.removeClineFromStack()
|
|
|
- await this.provider.postStateToWebview()
|
|
|
- await this.provider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
|
|
|
- await this.provider.postMessageToWebview({ type: "invoke", invoke: "newChat", text, images })
|
|
|
+ if (configuration.allowedCommands) {
|
|
|
+ await vscode.workspace
|
|
|
+ .getConfiguration("roo-cline")
|
|
|
+ .update("allowedCommands", configuration.allowedCommands, vscode.ConfigurationTarget.Global)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- const cline = await this.provider.initClineWithTask(text, images)
|
|
|
- return cline.taskId
|
|
|
+ await provider.removeClineFromStack()
|
|
|
+ await provider.postStateToWebview()
|
|
|
+ await provider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
|
|
|
+ await provider.postMessageToWebview({ type: "invoke", invoke: "newChat", text, images })
|
|
|
+
|
|
|
+ const { taskId } = await provider.initClineWithTask(text, images)
|
|
|
+ return taskId
|
|
|
}
|
|
|
|
|
|
public getCurrentTaskStack() {
|
|
|
- return this.provider.getCurrentTaskStack()
|
|
|
+ return this.sidebarProvider.getCurrentTaskStack()
|
|
|
}
|
|
|
|
|
|
public async clearCurrentTask(lastMessage?: string) {
|
|
|
- await this.provider.finishSubTask(lastMessage)
|
|
|
- await this.provider.postStateToWebview()
|
|
|
+ await this.sidebarProvider.finishSubTask(lastMessage)
|
|
|
+ await this.sidebarProvider.postStateToWebview()
|
|
|
}
|
|
|
|
|
|
public async cancelCurrentTask() {
|
|
|
- await this.provider.cancelTask()
|
|
|
+ await this.sidebarProvider.cancelTask()
|
|
|
+ }
|
|
|
+
|
|
|
+ public async cancelTask(taskId: string) {
|
|
|
+ const provider = this.taskMap.get(taskId)
|
|
|
+
|
|
|
+ if (provider) {
|
|
|
+ await provider.cancelTask()
|
|
|
+ this.taskMap.delete(taskId)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public async sendMessage(text?: string, images?: string[]) {
|
|
|
- await this.provider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images })
|
|
|
+ await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images })
|
|
|
}
|
|
|
|
|
|
public async pressPrimaryButton() {
|
|
|
- await this.provider.postMessageToWebview({ type: "invoke", invoke: "primaryButtonClick" })
|
|
|
+ await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "primaryButtonClick" })
|
|
|
}
|
|
|
|
|
|
public async pressSecondaryButton() {
|
|
|
- await this.provider.postMessageToWebview({ type: "invoke", invoke: "secondaryButtonClick" })
|
|
|
+ await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "secondaryButtonClick" })
|
|
|
}
|
|
|
|
|
|
public getConfiguration() {
|
|
|
- return this.provider.getValues()
|
|
|
+ return this.sidebarProvider.getValues()
|
|
|
}
|
|
|
|
|
|
public async setConfiguration(values: RooCodeSettings) {
|
|
|
- await this.provider.setValues(values)
|
|
|
- await this.provider.postStateToWebview()
|
|
|
+ await this.sidebarProvider.setValues(values)
|
|
|
+ await this.sidebarProvider.postStateToWebview()
|
|
|
}
|
|
|
|
|
|
public isReady() {
|
|
|
- return this.provider.viewLaunched
|
|
|
+ return this.sidebarProvider.viewLaunched
|
|
|
}
|
|
|
|
|
|
- public getMessages(taskId: string) {
|
|
|
- return this.history.getMessages(taskId)
|
|
|
- }
|
|
|
+ private registerListeners(provider: ClineProvider) {
|
|
|
+ provider.on("clineCreated", (cline) => {
|
|
|
+ cline.on("taskStarted", () => {
|
|
|
+ this.emit(RooCodeEventName.TaskStarted, cline.taskId)
|
|
|
+ this.taskMap.set(cline.taskId, provider)
|
|
|
+ })
|
|
|
|
|
|
- public getTokenUsage(taskId: string) {
|
|
|
- return this.tokenUsage[taskId]
|
|
|
- }
|
|
|
+ cline.on("message", (message) => this.emit(RooCodeEventName.Message, { taskId: cline.taskId, ...message }))
|
|
|
+
|
|
|
+ cline.on("taskTokenUsageUpdated", (_, usage) =>
|
|
|
+ this.emit(RooCodeEventName.TaskTokenUsageUpdated, cline.taskId, usage),
|
|
|
+ )
|
|
|
|
|
|
- public log(message: string) {
|
|
|
- this.outputChannel.appendLine(message)
|
|
|
+ cline.on("taskAskResponded", () => this.emit(RooCodeEventName.TaskAskResponded, cline.taskId))
|
|
|
+
|
|
|
+ cline.on("taskAborted", () => {
|
|
|
+ this.emit(RooCodeEventName.TaskAborted, cline.taskId)
|
|
|
+ this.taskMap.delete(cline.taskId)
|
|
|
+ })
|
|
|
+
|
|
|
+ cline.on("taskCompleted", (_, usage) => {
|
|
|
+ this.emit(RooCodeEventName.TaskCompleted, cline.taskId, usage)
|
|
|
+ this.taskMap.delete(cline.taskId)
|
|
|
+ })
|
|
|
+
|
|
|
+ cline.on("taskSpawned", (childTaskId) => this.emit(RooCodeEventName.TaskSpawned, cline.taskId, childTaskId))
|
|
|
+ cline.on("taskPaused", () => this.emit(RooCodeEventName.TaskPaused, cline.taskId))
|
|
|
+ cline.on("taskUnpaused", () => this.emit(RooCodeEventName.TaskUnpaused, cline.taskId))
|
|
|
+
|
|
|
+ this.emit(RooCodeEventName.TaskCreated, cline.taskId)
|
|
|
+ })
|
|
|
}
|
|
|
}
|