Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import delay from "delay"
import pWaitFor from "p-wait-for"
import { serializeError } from "serialize-error"
import { Package } from "../../shared/package"
import { isNativeProtocol } from "@roo-code/types"
import { getCurrentToolProtocol, formatToolInvocation } from "../tools/helpers/toolResultFormatting"

import {
type TaskLike,
Expand Down Expand Up @@ -1383,19 +1385,21 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
let existingApiConversationHistory: ApiMessage[] = 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
// Now also protocol-aware: format according to current protocol setting
const protocol = getCurrentToolProtocol()
const useNative = isNativeProtocol(protocol)

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<string, string>)
.map(([key, value]) => `<${key}>\n${value}\n</${key}>`)
.join("\n")
// Format tool invocation based on protocol
const params = block.input as Record<string, any>
const formattedText = formatToolInvocation(block.name, params, protocol)

return {
type: "text",
text: `<${block.name}>\n${inputAsXml}\n</${block.name}>`,
text: formattedText,
} as Anthropic.Messages.TextBlockParam
} else if (block.type === "tool_result") {
// Convert block.content to text block array, removing images
Expand Down
90 changes: 77 additions & 13 deletions src/core/tools/ReadFileTool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from "path"
import { isBinaryFile } from "isbinaryfile"
import type { FileEntry, LineRange } from "@roo-code/types"
import { isNativeProtocol } from "@roo-code/types"

import { Task } from "../task/Task"
import { ClineSayTool } from "../../shared/ExtensionMessage"
Expand All @@ -14,6 +15,7 @@ import { readLines } from "../../integrations/misc/read-lines"
import { extractTextFromFile, addLineNumbers, getSupportedBinaryFormats } from "../../integrations/misc/extract-text"
import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter"
import { parseXml } from "../../utils/xml"
import { getCurrentToolProtocol } from "./helpers/toolResultFormatting"
import {
DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
Expand All @@ -35,6 +37,7 @@ interface FileResult {
notice?: string
lineRanges?: LineRange[]
xmlContent?: string
nativeContent?: string
imageDataUrl?: string
feedbackText?: string
feedbackImages?: any[]
Expand Down Expand Up @@ -105,12 +108,15 @@ export class ReadFileTool extends BaseTool<"read_file"> {
async execute(params: { files: FileEntry[] }, task: Task, callbacks: ToolCallbacks): Promise<void> {
const { handleError, pushToolResult } = callbacks
const fileEntries = params.files
const protocol = getCurrentToolProtocol()
const useNative = isNativeProtocol(protocol)

if (!fileEntries || fileEntries.length === 0) {
task.consecutiveMistakeCount++
task.recordToolError("read_file")
const errorMsg = await task.sayAndCreateMissingParamError("read_file", "args (containing valid file paths)")
pushToolResult(`<files><error>${errorMsg}</error></files>`)
const errorResult = useNative ? `Error: ${errorMsg}` : `<files><error>${errorMsg}</error></files>`
pushToolResult(errorResult)
return
}

Expand Down Expand Up @@ -146,6 +152,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
status: "blocked",
error: errorMsg,
xmlContent: `<file><path>${relPath}</path><error>Error reading file: ${errorMsg}</error></file>`,
nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`,
})
await handleError(`reading file ${relPath}`, new Error(errorMsg))
hasRangeError = true
Expand All @@ -157,6 +164,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
status: "blocked",
error: errorMsg,
xmlContent: `<file><path>${relPath}</path><error>Error reading file: ${errorMsg}</error></file>`,
nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`,
})
await handleError(`reading file ${relPath}`, new Error(errorMsg))
hasRangeError = true
Expand All @@ -175,6 +183,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
status: "blocked",
error: errorMsg,
xmlContent: `<file><path>${relPath}</path><error>${errorMsg}</error></file>`,
nativeContent: `File: ${relPath}\nError: ${errorMsg}`,
})
continue
}
Expand Down Expand Up @@ -228,6 +237,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
updateFileResult(fileResult.path, {
status: "denied",
xmlContent: `<file><path>${fileResult.path}</path><status>Denied by user</status></file>`,
nativeContent: `File: ${fileResult.path}\nStatus: Denied by user`,
feedbackText: text,
feedbackImages: images,
})
Expand All @@ -248,6 +258,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
updateFileResult(fileResult.path, {
status: "denied",
xmlContent: `<file><path>${fileResult.path}</path><status>Denied by user</status></file>`,
nativeContent: `File: ${fileResult.path}\nStatus: Denied by user`,
})
}
})
Expand All @@ -260,6 +271,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
updateFileResult(fileResult.path, {
status: "denied",
xmlContent: `<file><path>${fileResult.path}</path><status>Denied by user</status></file>`,
nativeContent: `File: ${fileResult.path}\nStatus: Denied by user`,
})
})
}
Expand Down Expand Up @@ -299,6 +311,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
updateFileResult(relPath, {
status: "denied",
xmlContent: `<file><path>${relPath}</path><status>Denied by user</status></file>`,
nativeContent: `File: ${relPath}\nStatus: Denied by user`,
feedbackText: text,
feedbackImages: images,
})
Expand Down Expand Up @@ -343,6 +356,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
updateFileResult(relPath, {
xmlContent: `<file><path>${relPath}</path>\n<notice>${validationResult.notice}</notice>\n</file>`,
nativeContent: `File: ${relPath}\nNote: ${validationResult.notice}`,
})
continue
}
Expand All @@ -353,6 +367,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {

updateFileResult(relPath, {
xmlContent: `<file><path>${relPath}</path>\n<notice>${imageResult.notice}</notice>\n</file>`,
nativeContent: `File: ${relPath}\nNote: ${imageResult.notice}`,
imageDataUrl: imageResult.dataUrl,
})
continue
Expand All @@ -362,6 +377,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
status: "error",
error: `Error reading image file: ${errorMsg}`,
xmlContent: `<file><path>${relPath}</path><error>Error reading image file: ${errorMsg}</error></file>`,
nativeContent: `File: ${relPath}\nError: Error reading image file: ${errorMsg}`,
})
await handleError(
`reading image file ${relPath}`,
Expand All @@ -378,23 +394,29 @@ export class ReadFileTool extends BaseTool<"read_file"> {
updateFileResult(relPath, {
notice: `Binary file format: ${fileFormat}`,
xmlContent: `<file><path>${relPath}</path>\n<binary_file format="${fileFormat}">Binary file - content not displayed</binary_file>\n</file>`,
nativeContent: `File: ${relPath}\nBinary file (${fileFormat}) - content not displayed`,
})
continue
}
}

if (fileResult.lineRanges && fileResult.lineRanges.length > 0) {
const rangeResults: string[] = []
const nativeRangeResults: string[] = []

for (const range of fileResult.lineRanges) {
const content = addLineNumbers(
await readLines(fullPath, range.end - 1, range.start - 1),
range.start,
)
const lineRangeAttr = ` lines="${range.start}-${range.end}"`
rangeResults.push(`<content${lineRangeAttr}>\n${content}</content>`)
nativeRangeResults.push(`Lines ${range.start}-${range.end}:\n${content}`)
}

updateFileResult(relPath, {
xmlContent: `<file><path>${relPath}</path>\n${rangeResults.join("\n")}\n</file>`,
nativeContent: `File: ${relPath}\n${nativeRangeResults.join("\n\n")}`,
})
continue
}
Expand All @@ -406,9 +428,10 @@ export class ReadFileTool extends BaseTool<"read_file"> {
task.rooIgnoreController,
)
if (defResult) {
let xmlInfo = `<notice>Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines</notice>\n`
const notice = `Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines`
updateFileResult(relPath, {
xmlContent: `<file><path>${relPath}</path>\n<list_code_definition_names>${defResult}</list_code_definition_names>\n${xmlInfo}</file>`,
xmlContent: `<file><path>${relPath}</path>\n<list_code_definition_names>${defResult}</list_code_definition_names>\n<notice>${notice}</notice>\n</file>`,
nativeContent: `File: ${relPath}\nCode Definitions:\n${defResult}\n\nNote: ${notice}`,
})
}
} catch (error) {
Expand All @@ -427,6 +450,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
const content = addLineNumbers(await readLines(fullPath, maxReadFileLine - 1, 0))
const lineRangeAttr = ` lines="1-${maxReadFileLine}"`
let xmlInfo = `<content${lineRangeAttr}>\n${content}</content>\n`
let nativeInfo = `Lines 1-${maxReadFileLine}:\n${content}\n`

try {
const defResult = await parseSourceCodeDefinitionsForFile(
Expand All @@ -436,10 +460,16 @@ export class ReadFileTool extends BaseTool<"read_file"> {
if (defResult) {
const truncatedDefs = truncateDefinitionsToLineLimit(defResult, maxReadFileLine)
xmlInfo += `<list_code_definition_names>${truncatedDefs}</list_code_definition_names>\n`
nativeInfo += `\nCode Definitions:\n${truncatedDefs}\n`
}
xmlInfo += `<notice>Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines</notice>\n`

const notice = `Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines`
xmlInfo += `<notice>${notice}</notice>\n`
nativeInfo += `\nNote: ${notice}`

updateFileResult(relPath, {
xmlContent: `<file><path>${relPath}</path>\n${xmlInfo}</file>`,
nativeContent: `File: ${relPath}\n${nativeInfo}`,
})
} catch (error) {
if (error instanceof Error && error.message.startsWith("Unsupported language:")) {
Expand All @@ -462,6 +492,8 @@ export class ReadFileTool extends BaseTool<"read_file"> {
let content = await extractTextFromFile(fullPath)
let xmlInfo = ""

let nativeInfo = ""

if (budgetResult.shouldTruncate && budgetResult.maxChars !== undefined) {
const truncateResult = truncateFileContent(
content,
Expand All @@ -479,31 +511,52 @@ export class ReadFileTool extends BaseTool<"read_file"> {
xmlInfo =
content.length > 0 ? `<content${lineRangeAttr}>\n${content}</content>\n` : `<content/>`
xmlInfo += `<notice>${truncateResult.notice}</notice>\n`

nativeInfo =
content.length > 0
? `Lines 1-${displayedLines}:\n${content}\n\nNote: ${truncateResult.notice}`
: `Note: ${truncateResult.notice}`
} else {
const lineRangeAttr = ` lines="1-${totalLines}"`
xmlInfo = totalLines > 0 ? `<content${lineRangeAttr}>\n${content}</content>\n` : `<content/>`

if (totalLines === 0) {
xmlInfo += `<notice>File is empty</notice>\n`
nativeInfo = "Note: File is empty"
} else {
nativeInfo = `Lines 1-${totalLines}:\n${content}`
}
}

await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)

updateFileResult(relPath, { xmlContent: `<file><path>${relPath}</path>\n${xmlInfo}</file>` })
updateFileResult(relPath, {
xmlContent: `<file><path>${relPath}</path>\n${xmlInfo}</file>`,
nativeContent: `File: ${relPath}\n${nativeInfo}`,
})
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
updateFileResult(relPath, {
status: "error",
error: `Error reading file: ${errorMsg}`,
xmlContent: `<file><path>${relPath}</path><error>Error reading file: ${errorMsg}</error></file>`,
nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`,
})
await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg))
}
}

const xmlResults = fileResults.filter((result) => result.xmlContent).map((result) => result.xmlContent)
const filesXml = `<files>\n${xmlResults.join("\n")}\n</files>`
// Build final result based on protocol
let finalResult: string
if (useNative) {
const nativeResults = fileResults
.filter((result) => result.nativeContent)
.map((result) => result.nativeContent)
finalResult = nativeResults.join("\n\n---\n\n")
} else {
const xmlResults = fileResults.filter((result) => result.xmlContent).map((result) => result.xmlContent)
finalResult = `<files>\n${xmlResults.join("\n")}\n</files>`
}

const fileImageUrls = fileResults
.filter((result) => result.imageDataUrl)
Expand Down Expand Up @@ -537,26 +590,26 @@ export class ReadFileTool extends BaseTool<"read_file"> {

if (statusMessage || imagesToInclude.length > 0) {
const result = formatResponse.toolResult(
statusMessage || filesXml,
statusMessage || finalResult,
imagesToInclude.length > 0 ? imagesToInclude : undefined,
)

if (typeof result === "string") {
if (statusMessage) {
pushToolResult(`${result}\n${filesXml}`)
pushToolResult(`${result}\n${finalResult}`)
} else {
pushToolResult(result)
}
} else {
if (statusMessage) {
const textBlock = { type: "text" as const, text: filesXml }
const textBlock = { type: "text" as const, text: finalResult }
pushToolResult([...result, textBlock])
} else {
pushToolResult(result)
}
}
} else {
pushToolResult(filesXml)
pushToolResult(finalResult)
}
} catch (error) {
const relPath = fileEntries[0]?.path || "unknown"
Expand All @@ -567,14 +620,25 @@ export class ReadFileTool extends BaseTool<"read_file"> {
status: "error",
error: `Error reading file: ${errorMsg}`,
xmlContent: `<file><path>${relPath}</path><error>Error reading file: ${errorMsg}</error></file>`,
nativeContent: `File: ${relPath}\nError: Error reading file: ${errorMsg}`,
})
}

await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg))

const xmlResults = fileResults.filter((result) => result.xmlContent).map((result) => result.xmlContent)
// Build final error result based on protocol
let errorResult: string
if (useNative) {
const nativeResults = fileResults
.filter((result) => result.nativeContent)
.map((result) => result.nativeContent)
errorResult = nativeResults.join("\n\n---\n\n")
} else {
const xmlResults = fileResults.filter((result) => result.xmlContent).map((result) => result.xmlContent)
errorResult = `<files>\n${xmlResults.join("\n")}\n</files>`
}

pushToolResult(`<files>\n${xmlResults.join("\n")}\n</files>`)
pushToolResult(errorResult)
}
}

Expand Down
Loading
Loading