mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Save latest assistant response before tool use; fix task resumption conversation reconstruction
This commit is contained in:
@@ -507,16 +507,12 @@ export class ClaudeDev {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { response, text, images } = await this.ask(askType) // calls poststatetowebview
|
const { response, text, images } = await this.ask(askType) // calls poststatetowebview
|
||||||
|
let responseText: string | undefined
|
||||||
let newUserContent: UserContent = []
|
let responseImages: string[] | undefined
|
||||||
if (response === "messageResponse") {
|
if (response === "messageResponse") {
|
||||||
await this.say("user_feedback", text, images)
|
await this.say("user_feedback", text, images)
|
||||||
if (images && images.length > 0) {
|
responseText = text
|
||||||
newUserContent.push(...this.formatImagesIntoBlocks(images))
|
responseImages = images
|
||||||
}
|
|
||||||
if (text) {
|
|
||||||
newUserContent.push({ type: "text", text })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// need to make sure that the api conversation history can be resumed by the api, even if it goes out of sync with claude messages
|
// need to make sure that the api conversation history can be resumed by the api, even if it goes out of sync with claude messages
|
||||||
@@ -529,8 +525,8 @@ export class ClaudeDev {
|
|||||||
const existingApiConversationHistory: Anthropic.Messages.MessageParam[] =
|
const existingApiConversationHistory: Anthropic.Messages.MessageParam[] =
|
||||||
await this.getSavedApiConversationHistory()
|
await this.getSavedApiConversationHistory()
|
||||||
|
|
||||||
let modifiedOldUserContent: UserContent
|
let modifiedOldUserContent: UserContent // either the last message if its user message, or the user message before the last (assistant) message
|
||||||
let modifiedApiConversationHistory: Anthropic.Messages.MessageParam[]
|
let modifiedApiConversationHistory: Anthropic.Messages.MessageParam[] // need to remove the last user message to replace with new modified user message
|
||||||
if (existingApiConversationHistory.length > 0) {
|
if (existingApiConversationHistory.length > 0) {
|
||||||
const lastMessage = existingApiConversationHistory[existingApiConversationHistory.length - 1]
|
const lastMessage = existingApiConversationHistory[existingApiConversationHistory.length - 1]
|
||||||
|
|
||||||
@@ -556,7 +552,7 @@ export class ClaudeDev {
|
|||||||
modifiedOldUserContent = []
|
modifiedOldUserContent = []
|
||||||
}
|
}
|
||||||
} else if (lastMessage.role === "user") {
|
} else if (lastMessage.role === "user") {
|
||||||
const previousAssistantMessage =
|
const previousAssistantMessage: Anthropic.Messages.MessageParam | undefined =
|
||||||
existingApiConversationHistory[existingApiConversationHistory.length - 2]
|
existingApiConversationHistory[existingApiConversationHistory.length - 2]
|
||||||
|
|
||||||
const existingUserContent: UserContent = Array.isArray(lastMessage.content)
|
const existingUserContent: UserContent = Array.isArray(lastMessage.content)
|
||||||
@@ -586,7 +582,7 @@ export class ClaudeDev {
|
|||||||
content: "Task was interrupted before this tool call could be completed.",
|
content: "Task was interrupted before this tool call could be completed.",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
modifiedApiConversationHistory = existingApiConversationHistory.slice(0, -1)
|
modifiedApiConversationHistory = existingApiConversationHistory.slice(0, -1) // removes the last user message
|
||||||
modifiedOldUserContent = [...existingUserContent, ...missingToolResponses]
|
modifiedOldUserContent = [...existingUserContent, ...missingToolResponses]
|
||||||
} else {
|
} else {
|
||||||
modifiedApiConversationHistory = existingApiConversationHistory.slice(0, -1)
|
modifiedApiConversationHistory = existingApiConversationHistory.slice(0, -1)
|
||||||
@@ -603,10 +599,8 @@ export class ClaudeDev {
|
|||||||
throw new Error("Unexpected: No existing API conversation history")
|
throw new Error("Unexpected: No existing API conversation history")
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we have newUserContent which is user's current message, and the modifiedOldUserContent which is the old message with tool responses filled in
|
let newUserContent: UserContent = [...modifiedOldUserContent]
|
||||||
// we need to combine them while ensuring there is only one text block
|
|
||||||
const modifiedOldUserContentText = modifiedOldUserContent.find((block) => block.type === "text")?.text
|
|
||||||
const newUserContentText = newUserContent.find((block) => block.type === "text")?.text
|
|
||||||
const agoText = (() => {
|
const agoText = (() => {
|
||||||
const timestamp = lastClaudeMessage?.ts ?? Date.now()
|
const timestamp = lastClaudeMessage?.ts ?? Date.now()
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
@@ -627,22 +621,21 @@ export class ClaudeDev {
|
|||||||
return "just now"
|
return "just now"
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const combinedText =
|
newUserContent.push({
|
||||||
`Task resumption: This autonomous coding task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now ${cwd}. If the task has not been completed, retry the last step before interruption and proceed with completing the task.` +
|
type: "text",
|
||||||
(modifiedOldUserContentText
|
text:
|
||||||
? `\n\nLast recorded user input before interruption:\n<previous_message>\n${modifiedOldUserContentText}\n</previous_message>`
|
`Task resumption: This autonomous coding task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now '${cwd}'. If the task has not been completed, retry the last step before interruption and proceed with completing the task.` +
|
||||||
: "") +
|
(responseText
|
||||||
(newUserContentText
|
? `\n\nNew instructions for task continuation:\n<user_message>\n${responseText}\n</user_message>`
|
||||||
? `\n\nNew instructions for task continuation:\n<user_message>\n${newUserContentText}\n</user_message>`
|
: ""),
|
||||||
: "")
|
})
|
||||||
|
|
||||||
const newUserContentImages = newUserContent.filter((block) => block.type === "image")
|
if (responseImages && responseImages.length > 0) {
|
||||||
const combinedModifiedOldUserContentWithNewUserContent: UserContent = (
|
newUserContent.push(...this.formatImagesIntoBlocks(responseImages))
|
||||||
modifiedOldUserContent.filter((block) => block.type !== "text") as UserContent
|
}
|
||||||
).concat([{ type: "text", text: combinedText }, ...newUserContentImages])
|
|
||||||
|
|
||||||
await this.overwriteApiConversationHistory(modifiedApiConversationHistory)
|
await this.overwriteApiConversationHistory(modifiedApiConversationHistory)
|
||||||
await this.initiateTaskLoop(combinedModifiedOldUserContentWithNewUserContent)
|
await this.initiateTaskLoop(newUserContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initiateTaskLoop(userContent: UserContent): Promise<void> {
|
private async initiateTaskLoop(userContent: UserContent): Promise<void> {
|
||||||
@@ -1649,17 +1642,34 @@ ${this.customInstructions.trim()}
|
|||||||
|
|
||||||
// A response always returns text content blocks (it's just that before we were iterating over the completion_attempt response before we could append text response, resulting in bug)
|
// A response always returns text content blocks (it's just that before we were iterating over the completion_attempt response before we could append text response, resulting in bug)
|
||||||
for (const contentBlock of response.content) {
|
for (const contentBlock of response.content) {
|
||||||
|
// type can only be text or tool_use
|
||||||
if (contentBlock.type === "text") {
|
if (contentBlock.type === "text") {
|
||||||
assistantResponses.push(contentBlock)
|
assistantResponses.push(contentBlock)
|
||||||
await this.say("text", contentBlock.text)
|
await this.say("text", contentBlock.text)
|
||||||
|
} else if (contentBlock.type === "tool_use") {
|
||||||
|
assistantResponses.push(contentBlock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response
|
||||||
|
if (assistantResponses.length > 0) {
|
||||||
|
await this.addToApiConversationHistory({ role: "assistant", content: assistantResponses })
|
||||||
|
} else {
|
||||||
|
// this should never happen! it there's no assistant_responses, that means we got no text or tool_use content blocks from API which we should assume is an error
|
||||||
|
await this.say(
|
||||||
|
"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({
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: "Failure: I did not provide a response." }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let toolResults: Anthropic.ToolResultBlockParam[] = []
|
let toolResults: Anthropic.ToolResultBlockParam[] = []
|
||||||
let attemptCompletionBlock: Anthropic.Messages.ToolUseBlock | undefined
|
let attemptCompletionBlock: Anthropic.Messages.ToolUseBlock | undefined
|
||||||
for (const contentBlock of response.content) {
|
for (const contentBlock of response.content) {
|
||||||
if (contentBlock.type === "tool_use") {
|
if (contentBlock.type === "tool_use") {
|
||||||
assistantResponses.push(contentBlock)
|
|
||||||
const toolName = contentBlock.name as ToolName
|
const toolName = contentBlock.name as ToolName
|
||||||
const toolInput = contentBlock.input
|
const toolInput = contentBlock.input
|
||||||
const toolUseId = contentBlock.id
|
const toolUseId = contentBlock.id
|
||||||
@@ -1677,17 +1687,6 @@ ${this.customInstructions.trim()}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assistantResponses.length > 0) {
|
|
||||||
await this.addToApiConversationHistory({ role: "assistant", content: assistantResponses })
|
|
||||||
} else {
|
|
||||||
// this should never happen! it there's no assistant_responses, that means we got no text or tool_use content blocks from API which we should assume is an error
|
|
||||||
await this.say("error", "Unexpected Error: No assistant messages were found in the API response")
|
|
||||||
await this.addToApiConversationHistory({
|
|
||||||
role: "assistant",
|
|
||||||
content: [{ type: "text", text: "Failure: I did not have a response to provide." }],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let didEndLoop = false
|
let didEndLoop = false
|
||||||
|
|
||||||
// attempt_completion is always done last, since there might have been other tools that needed to be called first before the job is finished
|
// attempt_completion is always done last, since there might have been other tools that needed to be called first before the job is finished
|
||||||
|
|||||||
Reference in New Issue
Block a user