Compare commits

...

12 Commits

Author SHA1 Message Date
Saoud Rizwan c12f021ca8 Revert "Cline.ts - Factor out overwriteApiConversationHistory [5/8] (#2361)"
This reverts commit 93d52288ea.
2025-03-21 13:19:06 -07:00
Evan 93d52288ea Cline.ts - Factor out overwriteApiConversationHistory [5/8] (#2361)
* factor out getSavedClineMessages

* changeset

* refactor addToApiConversationHistory

* changeset

* refactor overwriteApiConversationHistory

* changeset

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-03-21 13:16:24 -07:00
Evan 0e1ba91862 Cline.ts - Factor out addToApiConversationHistory [4/8] (#2360)
* factor out getSavedClineMessages

* changeset

* refactor addToApiConversationHistory

* changeset
2025-03-21 13:04:17 -07:00
Evan 809557540b Cline.ts - Factor out get saved cline messages [3/8] (#2358)
* factor out getSavedClineMessages

* changeset
2025-03-20 18:23:23 -07:00
celestial-vault 754ee766d1 delete extra changeset 2025-03-20 13:50:57 -07:00
celestial-vault d2e393e603 changeset 2025-03-20 13:48:45 -07:00
celestial-vault b0baefc3ba refactor out getSavedApiConversationHistory 2025-03-20 13:48:20 -07:00
celestial-vault 27571b4617 changeset 2025-03-20 13:41:26 -07:00
celestial-vault bf9164e1ae factor out saveApiConversationHistory 2025-03-20 13:40:52 -07:00
celestial-vault eea2c97835 changeset 2025-03-20 13:27:25 -07:00
celestial-vault cc67a77759 merge conflicts 2025-03-20 13:25:44 -07:00
celestial-vault 3d33834ce5 refactor out ensureTaskDirectoryExists 2025-03-20 13:21:58 -07:00
6 changed files with 115 additions and 67 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
refactor
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
refactor
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
refactor
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
refactor
+35 -67
View File
@@ -66,6 +66,12 @@ import { DEFAULT_LANGUAGE_SETTINGS, getLanguageKey, LanguageDisplay, LanguageKey
import { telemetryService } from "../services/telemetry/TelemetryService"
import pTimeout from "p-timeout"
import { GlobalFileNames } from "../global-constants"
import {
ensureTaskDirectoryExists,
getSavedApiConversationHistory,
getSavedClineMessages,
saveApiConversationHistory,
} from "./messages-io"
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
@@ -178,59 +184,11 @@ export class Cline {
// Storing task to disk for history
private async ensureTaskDirectoryExists(): Promise<string> {
const globalStoragePath = this.providerRef.deref()?.context.globalStorageUri.fsPath
if (!globalStoragePath) {
throw new Error("Global storage uri is invalid")
}
const taskDir = path.join(globalStoragePath, "tasks", this.taskId)
await fs.mkdir(taskDir, { recursive: true })
return taskDir
}
private async getSavedApiConversationHistory(): Promise<Anthropic.MessageParam[]> {
const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.apiConversationHistory)
const fileExists = await fileExistsAtPath(filePath)
if (fileExists) {
return JSON.parse(await fs.readFile(filePath, "utf8"))
}
return []
}
private async addToApiConversationHistory(message: Anthropic.MessageParam) {
this.apiConversationHistory.push(message)
await this.saveApiConversationHistory()
}
private async overwriteApiConversationHistory(newHistory: Anthropic.MessageParam[]) {
this.apiConversationHistory = newHistory
await this.saveApiConversationHistory()
}
private async saveApiConversationHistory() {
try {
const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.apiConversationHistory)
await fs.writeFile(filePath, JSON.stringify(this.apiConversationHistory))
} catch (error) {
// in the off chance this fails, we don't want to stop the task
console.error("Failed to save API conversation history:", error)
}
}
private async getSavedClineMessages(): Promise<ClineMessage[]> {
const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.uiMessages)
if (await fileExistsAtPath(filePath)) {
return JSON.parse(await fs.readFile(filePath, "utf8"))
} else {
// check old location
const oldPath = path.join(await this.ensureTaskDirectoryExists(), "claude_messages.json")
if (await fileExistsAtPath(oldPath)) {
const data = JSON.parse(await fs.readFile(oldPath, "utf8"))
await fs.unlink(oldPath) // remove old file
return data
}
}
return []
const globalStoragePath = this.providerRef.deref()?.context.globalStorageUri.fsPath
const taskId = this.taskId
await saveApiConversationHistory(globalStoragePath, taskId, this.apiConversationHistory)
}
private async addToClineMessages(message: ClineMessage) {
@@ -249,7 +207,9 @@ export class Cline {
private async saveClineMessages() {
try {
const taskDir = await this.ensureTaskDirectoryExists()
const globalStoragePath = this.providerRef.deref()?.context.globalStorageUri.fsPath
const taskId = this.taskId
const taskDir = await ensureTaskDirectoryExists(globalStoragePath, taskId)
const filePath = path.join(taskDir, GlobalFileNames.uiMessages)
await fs.writeFile(filePath, JSON.stringify(this.clineMessages))
// combined as they are in ChatView
@@ -840,8 +800,9 @@ export class Cline {
// if (!doesShadowGitExist) {
// this.checkpointTrackerErrorMessage = "Checkpoints are only available for new tasks"
// }
const modifiedClineMessages = await this.getSavedClineMessages()
const globalStoragePath = this.providerRef.deref()?.context.globalStorageUri.fsPath
const taskId = this.taskId
const modifiedClineMessages = await getSavedClineMessages(globalStoragePath, taskId)
// Remove any resume messages that may have been added before
const lastRelevantMessageIndex = findLastIndex(
@@ -866,11 +827,12 @@ export class Cline {
}
await this.overwriteClineMessages(modifiedClineMessages)
this.clineMessages = await this.getSavedClineMessages()
this.clineMessages = await getSavedClineMessages(globalStoragePath, taskId)
// Now present the cline messages to the user and ask if they want to resume (NOTE: we ran into a bug before where the apiconversationhistory wouldnt be initialized when opening a old task, and it was because we were waiting for resume)
// This is important in case the user deletes messages without resuming the task first
this.apiConversationHistory = await this.getSavedApiConversationHistory()
this.apiConversationHistory = await getSavedApiConversationHistory(globalStoragePath, taskId)
const lastClineMessage = this.clineMessages
.slice()
@@ -907,7 +869,10 @@ export class Cline {
// need to make sure that the api conversation history can be resumed by the api, even if it goes out of sync with cline messages
const existingApiConversationHistory: Anthropic.Messages.MessageParam[] = await this.getSavedApiConversationHistory()
const existingApiConversationHistory: Anthropic.Messages.MessageParam[] = await getSavedApiConversationHistory(
globalStoragePath,
taskId,
)
// if the last message is an assistant message, we need to check if there's tool use since every tool use has to have a tool response
// if there's no tool use and only a text block, then we can just add a user message
@@ -3158,10 +3123,13 @@ export class Cline {
// add environment details as its own text block, separate from tool results
userContent.push({ type: "text", text: environmentDetails })
await this.addToApiConversationHistory({
this.apiConversationHistory.push({
role: "user",
content: userContent,
})
const globalStoragePath = this.providerRef.deref()?.context.globalStorageUri.fsPath
const taskId = this.taskId
await saveApiConversationHistory(globalStoragePath, taskId, this.apiConversationHistory)
telemetryService.captureConversationTurnEvent(this.taskId, this.apiProvider, this.api.getModel().id, "user")
@@ -3220,7 +3188,8 @@ export class Cline {
}
// Let assistant know their response was interrupted for when task is resumed
await this.addToApiConversationHistory({
this.apiConversationHistory.push({
role: "assistant",
content: [
{
@@ -3235,6 +3204,7 @@ export class Cline {
},
],
})
await saveApiConversationHistory(globalStoragePath, taskId, this.apiConversationHistory)
// update api_req_started to have cancelled and cost, so that we can display the cost of the partial stream
updateApiReqMsg(cancelReason, streamingFailedMessage)
@@ -3386,10 +3356,11 @@ export class Cline {
if (assistantMessage.length > 0) {
telemetryService.captureConversationTurnEvent(this.taskId, this.apiProvider, this.api.getModel().id, "assistant")
await this.addToApiConversationHistory({
this.apiConversationHistory.push({
role: "assistant",
content: [{ type: "text", text: assistantMessage }],
})
await saveApiConversationHistory(globalStoragePath, taskId, this.apiConversationHistory)
// NOTE: this comment is here for future reference - this was a workaround for userMessageContent not getting set to true. It was due to it not recursively calling for partial blocks when didRejectTool, so it would get stuck waiting for a partial block to complete before it could continue.
// in case the content blocks finished
@@ -3421,15 +3392,12 @@ export class Cline {
"error",
"Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output.",
)
await this.addToApiConversationHistory({
this.apiConversationHistory.push({
role: "assistant",
content: [
{
type: "text",
text: "Failure: I did not provide a response.",
},
],
content: [{ type: "text", text: "Failure: I did not provide a response." }],
})
await saveApiConversationHistory(globalStoragePath, taskId, this.apiConversationHistory)
}
return didEndLoop // will always be false for now
+60
View File
@@ -0,0 +1,60 @@
import fs from "fs/promises"
import path from "path"
import { GlobalFileNames } from "../global-constants"
import Anthropic from "@anthropic-ai/sdk"
import { fileExistsAtPath } from "../utils/fs"
import { ClineMessage } from "../shared/ExtensionMessage"
export async function ensureTaskDirectoryExists(globalStoragePath: string | undefined, taskId: string): Promise<string> {
if (!globalStoragePath) {
throw new Error("Global storage uri is invalid")
}
const taskDir = path.join(globalStoragePath, "tasks", taskId)
await fs.mkdir(taskDir, { recursive: true })
return taskDir
}
export async function saveApiConversationHistory(
globalStoragePath: string | undefined,
taskId: string,
apiConversationHistory: Anthropic.MessageParam[],
) {
try {
const filePath = path.join(
await ensureTaskDirectoryExists(globalStoragePath, taskId),
GlobalFileNames.apiConversationHistory,
)
await fs.writeFile(filePath, JSON.stringify(apiConversationHistory))
} catch (error) {
// in the off chance this fails, we don't want to stop the task
console.error("Failed to save API conversation history:", error)
}
}
export async function getSavedApiConversationHistory(
globalStoragePath: string | undefined,
taskId: string,
): Promise<Anthropic.MessageParam[]> {
const filePath = path.join(await ensureTaskDirectoryExists(globalStoragePath, taskId), GlobalFileNames.apiConversationHistory)
const fileExists = await fileExistsAtPath(filePath)
if (fileExists) {
return JSON.parse(await fs.readFile(filePath, "utf8"))
}
return []
}
export async function getSavedClineMessages(globalStoragePath: string | undefined, taskId: string): Promise<ClineMessage[]> {
const filePath = path.join(await ensureTaskDirectoryExists(globalStoragePath, taskId), GlobalFileNames.uiMessages)
if (await fileExistsAtPath(filePath)) {
return JSON.parse(await fs.readFile(filePath, "utf8"))
} else {
// check old location
const oldPath = path.join(await ensureTaskDirectoryExists(globalStoragePath, taskId), "claude_messages.json")
if (await fileExistsAtPath(oldPath)) {
const data = JSON.parse(await fs.readFile(oldPath, "utf8"))
await fs.unlink(oldPath) // remove old file
return data
}
}
return []
}