Skip to content

Commit 31a788e

Browse files
committed
fix: use multi-file read_file tool for synthetic messages
- Modified createSyntheticReadFileMessage to batch files in groups of 5 - Updated tests to verify single read_file call with multiple files - Added test for batching behavior when more than 5 files are mentioned - This improves efficiency by reducing the number of tool calls needed
1 parent c59edca commit 31a788e

File tree

2 files changed

+76
-8
lines changed

2 files changed

+76
-8
lines changed

src/core/task/__tests__/Task.spec.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,11 +1618,65 @@ describe("Cline", () => {
16181618
// Start the task
16191619
await (task as any).startTask("Compare @/src/index.ts with @/src/utils.ts")
16201620

1621-
// Verify synthetic assistant message has multiple read_file calls
1621+
// Verify synthetic assistant message has a single read_file call with multiple files
16221622
expect(apiMessages[1].role).toBe("assistant")
16231623
expect(apiMessages[1].content[0].text).toContain("<path>/src/index.ts</path>")
16241624
expect(apiMessages[1].content[0].text).toContain("<path>/src/utils.ts</path>")
1625-
expect(apiMessages[1].content[0].text).toMatch(/read_file.*read_file/s) // Multiple read_file blocks
1625+
// Should have exactly one read_file block containing both files
1626+
const readFileMatches = apiMessages[1].content[0].text.match(/<read_file>/g)
1627+
expect(readFileMatches).toHaveLength(1)
1628+
})
1629+
1630+
it("should batch files when more than 5 files are mentioned", async () => {
1631+
const task = new Task({
1632+
provider: mockProvider,
1633+
apiConfiguration: mockApiConfig,
1634+
task: "Analyze multiple files",
1635+
startTask: false,
1636+
})
1637+
1638+
// Mock the API conversation history to track messages
1639+
const apiMessages: any[] = []
1640+
vi.spyOn(task as any, "addToApiConversationHistory").mockImplementation(async (message) => {
1641+
apiMessages.push(message)
1642+
})
1643+
1644+
// Mock extractFileMentions to return 7 file mentions (more than 5)
1645+
const fileMentions = Array.from({ length: 7 }, (_, i) => ({
1646+
mention: `@file${i + 1}.ts`,
1647+
path: `file${i + 1}.ts`,
1648+
}))
1649+
vi.mocked(extractFileMentions).mockReturnValue(fileMentions)
1650+
vi.mocked(hasFileMentions).mockReturnValue(true)
1651+
1652+
// Mock processUserContentMentions
1653+
vi.mocked(processUserContentMentions).mockResolvedValue([
1654+
{
1655+
type: "text",
1656+
text: "Analyze multiple files with content",
1657+
},
1658+
])
1659+
1660+
// Start the task
1661+
await (task as any).startTask("Analyze multiple files")
1662+
1663+
// Verify synthetic assistant message has two read_file calls (5 files + 2 files)
1664+
expect(apiMessages[1].role).toBe("assistant")
1665+
const assistantText = apiMessages[1].content[0].text
1666+
1667+
// Should have exactly two read_file blocks
1668+
const readFileMatches = assistantText.match(/<read_file>/g)
1669+
expect(readFileMatches).toHaveLength(2)
1670+
1671+
// First batch should have 5 files
1672+
const firstBatch = assistantText.match(/<read_file>[\s\S]*?<\/read_file>/)[0]
1673+
const firstBatchFiles = firstBatch.match(/<path>file\d+\.ts<\/path>/g)
1674+
expect(firstBatchFiles).toHaveLength(5)
1675+
1676+
// Second batch should have 2 files
1677+
const secondBatch = assistantText.match(/<read_file>[\s\S]*?<\/read_file>/g)[1]
1678+
const secondBatchFiles = secondBatch.match(/<path>file\d+\.ts<\/path>/g)
1679+
expect(secondBatchFiles).toHaveLength(2)
16261680
})
16271681

16281682
it("should preserve task history without embedded file content", async () => {

src/core/task/synthetic-messages.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,28 @@ export function createSyntheticReadFileMessage(filePaths: string[]): string {
8686
return ""
8787
}
8888

89-
// Create read_file tool calls for each file
90-
const toolCalls = filePaths
91-
.map((path) => {
89+
// Group files into batches of 5 (the maximum allowed by read_file tool)
90+
const MAX_FILES_PER_CALL = 5
91+
const fileBatches: string[][] = []
92+
93+
for (let i = 0; i < filePaths.length; i += MAX_FILES_PER_CALL) {
94+
fileBatches.push(filePaths.slice(i, i + MAX_FILES_PER_CALL))
95+
}
96+
97+
// Create read_file tool calls - one per batch
98+
const toolCalls = fileBatches
99+
.map((batch) => {
100+
const fileElements = batch
101+
.map(
102+
(path) => ` <file>
103+
<path>${path}</path>
104+
</file>`,
105+
)
106+
.join("\n")
107+
92108
return `<read_file>
93109
<args>
94-
<file>
95-
<path>${path}</path>
96-
</file>
110+
${fileElements}
97111
</args>
98112
</read_file>`
99113
})

0 commit comments

Comments
 (0)