Compare commits

...

6 Commits

Author SHA1 Message Date
celestial-vault c6fefa133e merge conflicts 2025-06-17 18:40:04 -07:00
celestial-vault b66b695321 remove redundant line 2025-06-17 18:32:00 -07:00
celestial-vault 005576ba73 merge conflicts 2025-06-15 11:17:28 -07:00
celestial-vault 978b61df84 remove unused imports 2025-06-15 11:02:11 -07:00
celestial-vault f61bbe84eb move clineMessages state to MessageStateManager class 2025-06-14 23:11:48 -07:00
celestial-vault 5a34c78aa3 move apiConversationHistory to MessageStateHandler 2025-06-13 16:51:46 -07:00
4 changed files with 335 additions and 376 deletions
+1 -1
View File
@@ -57,7 +57,7 @@ export function withRetry(options: RetryOptions = {}) {
const handlerInstance = this as any
if (handlerInstance.options?.onRetryAttempt) {
try {
handlerInstance.options.onRetryAttempt(attempt + 1, maxRetries, delay, error)
await handlerInstance.options.onRetryAttempt(attempt + 1, maxRetries, delay, error)
} catch (e) {
console.error("Error in onRetryAttempt callback:", e)
}
+1 -1
View File
@@ -1007,7 +1007,7 @@ export class Controller {
uriScheme: vscode.env.uriScheme,
currentTaskItem: this.task?.taskId ? (taskHistory || []).find((item) => item.id === this.task?.taskId) : undefined,
checkpointTrackerErrorMessage: this.task?.checkpointTrackerErrorMessage,
clineMessages: this.task?.clineMessages || [],
clineMessages: this.task?.messageStateHandler.getClineMessages() || [],
taskHistory: (taskHistory || [])
.filter((item) => item.ts && item.task)
.sort((a, b) => b.ts - a.ts)
+212 -328
View File
File diff suppressed because it is too large Load Diff
+121 -46
View File
@@ -1,5 +1,5 @@
import { combineApiRequests } from "@/shared/combineApiRequests"
import { ensureTaskDirectoryExists, saveClineMessages } from "../storage/disk"
import { ensureTaskDirectoryExists, saveApiConversationHistory, saveClineMessages } from "../storage/disk"
import * as vscode from "vscode"
import { ClineMessage } from "@/shared/ExtensionMessage"
import { getApiMetrics } from "@/shared/getApiMetrics"
@@ -10,57 +10,132 @@ import os from "os"
import * as path from "path"
import CheckpointTracker from "@integrations/checkpoints/CheckpointTracker"
import { HistoryItem } from "@/shared/HistoryItem"
import Anthropic from "@anthropic-ai/sdk"
interface MessageStateHandlerParams {
context: vscode.ExtensionContext
taskId: string
conversationHistoryDeletedRange?: [number, number]
taskIsFavorited?: boolean
updateTaskHistory: (historyItem: HistoryItem) => Promise<HistoryItem[]>
}
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
// need to call getContext() from the task object when passing in context
export async function saveClineMessagesAndUpdateHistory(
context: vscode.ExtensionContext,
taskId: string,
clineMessages: ClineMessage[],
taskIsFavorited: boolean,
conversationHistoryDeletedRange: [number, number] | undefined,
checkpointTracker: CheckpointTracker | undefined,
updateTaskHistory: (historyItem: HistoryItem) => Promise<HistoryItem[]>,
) {
try {
await saveClineMessages(context, taskId, clineMessages)
export class MessageStateHandler {
private apiConversationHistory: Anthropic.MessageParam[] = []
private clineMessages: ClineMessage[] = []
private conversationHistoryDeletedRange: [number, number] | undefined
private taskIsFavorited: boolean
private checkpointTracker: CheckpointTracker | undefined
private updateTaskHistory: (historyItem: HistoryItem) => Promise<HistoryItem[]>
private context: vscode.ExtensionContext
private taskId: string
// combined as they are in ChatView
const apiMetrics = getApiMetrics(combineApiRequests(combineCommandSequences(clineMessages.slice(1))))
const taskMessage = clineMessages[0] // first message is always the task say
const lastRelevantMessage =
clineMessages[
findLastIndex(
clineMessages,
(message) => !(message.ask === "resume_task" || message.ask === "resume_completed_task"),
)
]
const taskDir = await ensureTaskDirectoryExists(context, taskId)
let taskDirSize = 0
constructor(params: MessageStateHandlerParams) {
this.context = params.context
this.taskId = params.taskId
this.conversationHistoryDeletedRange = params.conversationHistoryDeletedRange
this.taskIsFavorited = params.taskIsFavorited ?? false
this.updateTaskHistory = params.updateTaskHistory
}
setCheckpointTracker(tracker: CheckpointTracker | undefined) {
this.checkpointTracker = tracker
}
getApiConversationHistory(): Anthropic.MessageParam[] {
return this.apiConversationHistory
}
setApiConversationHistory(newHistory: Anthropic.MessageParam[]): void {
this.apiConversationHistory = newHistory
}
getClineMessages(): ClineMessage[] {
return this.clineMessages
}
setClineMessages(newMessages: ClineMessage[]) {
this.clineMessages = newMessages
}
async saveClineMessagesAndUpdateHistory(): Promise<void> {
try {
// getFolderSize.loose silently ignores errors
// returns # of bytes, size/1000/1000 = MB
taskDirSize = await getFolderSize.loose(taskDir)
await saveClineMessages(this.context, this.taskId, this.clineMessages)
// combined as they are in ChatView
const apiMetrics = getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1))))
const taskMessage = this.clineMessages[0] // first message is always the task say
const lastRelevantMessage =
this.clineMessages[
findLastIndex(
this.clineMessages,
(message) => !(message.ask === "resume_task" || message.ask === "resume_completed_task"),
)
]
const taskDir = await ensureTaskDirectoryExists(this.context, this.taskId)
let taskDirSize = 0
try {
// getFolderSize.loose silently ignores errors
// returns # of bytes, size/1000/1000 = MB
taskDirSize = await getFolderSize.loose(taskDir)
} catch (error) {
console.error("Failed to get task directory size:", taskDir, error)
}
await this.updateTaskHistory({
id: this.taskId,
ts: lastRelevantMessage.ts,
task: taskMessage.text ?? "",
tokensIn: apiMetrics.totalTokensIn,
tokensOut: apiMetrics.totalTokensOut,
cacheWrites: apiMetrics.totalCacheWrites,
cacheReads: apiMetrics.totalCacheReads,
totalCost: apiMetrics.totalCost,
size: taskDirSize,
shadowGitConfigWorkTree: await this.checkpointTracker?.getShadowGitConfigWorkTree(),
cwdOnTaskInitialization: cwd,
conversationHistoryDeletedRange: this.conversationHistoryDeletedRange,
isFavorited: this.taskIsFavorited,
})
} catch (error) {
console.error("Failed to get task directory size:", taskDir, error)
console.error("Failed to save cline messages:", error)
}
await updateTaskHistory({
id: taskId,
ts: lastRelevantMessage.ts,
task: taskMessage.text ?? "",
tokensIn: apiMetrics.totalTokensIn,
tokensOut: apiMetrics.totalTokensOut,
cacheWrites: apiMetrics.totalCacheWrites,
cacheReads: apiMetrics.totalCacheReads,
totalCost: apiMetrics.totalCost,
size: taskDirSize,
shadowGitConfigWorkTree: await checkpointTracker?.getShadowGitConfigWorkTree(),
cwdOnTaskInitialization: cwd,
conversationHistoryDeletedRange: conversationHistoryDeletedRange,
isFavorited: taskIsFavorited,
})
} catch (error) {
console.error("Failed to save cline messages:", error)
}
async addToApiConversationHistory(message: Anthropic.MessageParam) {
this.apiConversationHistory.push(message)
await saveApiConversationHistory(this.context, this.taskId, this.apiConversationHistory)
}
async overwriteApiConversationHistory(newHistory: Anthropic.MessageParam[]): Promise<void> {
this.apiConversationHistory = newHistory
await saveApiConversationHistory(this.context, this.taskId, this.apiConversationHistory)
}
async addToClineMessages(message: ClineMessage) {
// these values allow us to reconstruct the conversation history at the time this cline message was created
// it's important that apiConversationHistory is initialized before we add cline messages
message.conversationHistoryIndex = this.apiConversationHistory.length - 1 // NOTE: this is the index of the last added message which is the user message, and once the clinemessages have been presented we update the apiconversationhistory with the completed assistant message. This means when resetting to a message, we need to +1 this index to get the correct assistant message that this tool use corresponds to
message.conversationHistoryDeletedRange = this.conversationHistoryDeletedRange
this.clineMessages.push(message)
await this.saveClineMessagesAndUpdateHistory()
}
async overwriteClineMessages(newMessages: ClineMessage[]) {
this.clineMessages = newMessages
await this.saveClineMessagesAndUpdateHistory()
}
async updateClineMessage(index: number, updates: Partial<ClineMessage>): Promise<void> {
if (index < 0 || index >= this.clineMessages.length) {
throw new Error(`Invalid message index: ${index}`)
}
// Apply updates to the message
Object.assign(this.clineMessages[index], updates)
// Save changes and update history
await this.saveClineMessagesAndUpdateHistory()
}
}