diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 743b63a..cbe061a 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -10,7 +10,7 @@ import * as vscode from "vscode" import { ApiHandler, buildApiHandler } from "../api" import { ApiStream } from "../api/transform/stream" import { DiffViewProvider } from "../integrations/editor/DiffViewProvider" -import { formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" +import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" import { extractTextFromFile } from "../integrations/misc/extract-text" import { TerminalManager } from "../integrations/terminal/TerminalManager" import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" @@ -460,15 +460,50 @@ 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 + let existingApiConversationHistory: Anthropic.Messages.MessageParam[] = + await this.getSavedApiConversationHistory() + + // v2.0 xml tags refactor caveat: since we don't use tools anymore, we need to replace all tool use blocks with a text block since the API disallows conversations with tool uses and no tool schema + const conversationWithoutToolBlocks = existingApiConversationHistory.map((message) => { + if (Array.isArray(message.content)) { + const newContent = message.content.map((block) => { + if (block.type === "tool_use") { + // it's important we convert to the new tool schema format so the model doesn't get confused about how to invoke tools + const inputAsXml = Object.entries(block.input as Record) + .map(([key, value]) => `<${key}>\n${value}\n`) + .join("\n") + return { + type: "text", + text: `<${block.name}>\n${inputAsXml}\n`, + } as Anthropic.Messages.TextBlockParam + } else if (block.type === "tool_result") { + // Convert block.content to text block array, removing images + const contentAsTextBlocks = Array.isArray(block.content) + ? block.content.filter((item) => item.type === "text") + : [{ type: "text", text: block.content }] + const textContent = contentAsTextBlocks.map((item) => item.text).join("\n\n") + const toolName = findToolName(block.tool_use_id, existingApiConversationHistory) + return { + type: "text", + text: `[${toolName} Result]\n\n${textContent}`, + } as Anthropic.Messages.TextBlockParam + } + return block + }) + return { ...message, content: newContent } + } + return message + }) + existingApiConversationHistory = conversationWithoutToolBlocks + + // FIXME: remove tool use blocks altogether + // 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 // (note this isn't relevant anymore since we use custom tool prompts instead of tool use blocks, but this is here for legacy purposes in case users resume old tasks) // if the last message is a user message, we can need to get the assistant message before it to see if it made tool calls, and if so, fill in the remaining tool responses with 'interrupted' - const existingApiConversationHistory: Anthropic.Messages.MessageParam[] = - await this.getSavedApiConversationHistory() - 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[] // need to remove the last user message to replace with new modified user message if (existingApiConversationHistory.length > 0) { diff --git a/src/integrations/misc/export-markdown.ts b/src/integrations/misc/export-markdown.ts index c665a97..97c5c85 100644 --- a/src/integrations/misc/export-markdown.ts +++ b/src/integrations/misc/export-markdown.ts @@ -82,7 +82,7 @@ export function formatContentBlockToMarkdown( } } -function findToolName(toolCallId: string, messages: Anthropic.MessageParam[]): string { +export function findToolName(toolCallId: string, messages: Anthropic.MessageParam[]): string { for (const message of messages) { if (Array.isArray(message.content)) { for (const block of message.content) {