Skip to content

Commit a93c2e0

Browse files
committed
Refactor read-file tool to support XML input format and multiple line ranges
- Updated the `getReadFileDescription` function to reflect new XML structure for file reading requests. - Modified `readFileTool` to parse XML input, allowing multiple line ranges for each file. - Removed old parsing logic that handled line ranges as separate parameters. - Implemented validation for line ranges and ensured proper error handling for file access. - Adjusted approval messaging to accommodate new line range format. - Enhanced error handling to provide consistent feedback for file read errors.
1 parent f5ffc9a commit a93c2e0

File tree

6 files changed

+545
-654
lines changed

6 files changed

+545
-654
lines changed

src/core/__tests__/read-file-maxReadFileLine.test.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,11 @@ describe("read_file tool with maxReadFileLine setting", () => {
152152
;(addLineNumbers as jest.Mock).mockClear()
153153

154154
// Format args string based on params
155-
let argsContent = `:path:${params.path || testFilePath}`
156-
if (params.start_line) {
157-
argsContent += `\n:start_line:${params.start_line}`
158-
}
159-
if (params.end_line) {
160-
argsContent += `\n:end_line:${params.end_line}`
155+
let argsContent = `<file><path>${params.path || testFilePath}</path>`
156+
if (params.start_line && params.end_line) {
157+
argsContent += `<line_range>${params.start_line}-${params.end_line}</line_range>`
161158
}
159+
argsContent += `</file>`
162160

163161
const toolUse: ReadFileToolUse = {
164162
type: "tool_use",
@@ -327,7 +325,7 @@ describe("read_file tool with maxReadFileLine setting", () => {
327325

328326
// Verify XML structure
329327
expect(result).toBe(
330-
`<files>\n<file><path>${testFilePath}</path>\n<content lines="1-3">\n1 | Line 1\n2 | Line 2\n3 | Line 3</content>\n<list_code_definition_names>${sourceCodeDef.trim()}</list_code_definition_names>\n<notice>Showing only 3 of 5 total lines. Use start_line and end_line if you need to read more</notice>\n</file>\n</files>`,
328+
`<files>\n<file><path>${testFilePath}</path>\n<content lines="1-3">\n1 | Line 1\n2 | Line 2\n3 | Line 3</content>\n<list_code_definition_names>${sourceCodeDef.trim()}</list_code_definition_names>\n<notice>Showing only 3 of 5 total lines. Use line_range if you need to read more lines</notice>\n</file>\n</files>`,
331329
)
332330
})
333331

@@ -436,7 +434,7 @@ describe("read_file tool with maxReadFileLine setting", () => {
436434

437435
// Verify
438436
expect(result).toBe(
439-
`<files>\n<file><path>${testFilePath}</path>\n<content lines="1-${maxReadFileLine}">\n1 | Line 1\n2 | Line 2\n3 | Line 3</content>\n<list_code_definition_names>${sourceCodeDef.trim()}</list_code_definition_names>\n<notice>Showing only ${maxReadFileLine} of ${totalLines} total lines. Use start_line and end_line if you need to read more</notice>\n</file>\n</files>`,
437+
`<files>\n<file><path>${testFilePath}</path>\n<content lines="1-${maxReadFileLine}">\n1 | Line 1\n2 | Line 2\n3 | Line 3</content>\n<list_code_definition_names>${sourceCodeDef.trim()}</list_code_definition_names>\n<notice>Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines</notice>\n</file>\n</files>`,
440438
)
441439
})
442440

@@ -612,7 +610,7 @@ describe("read_file tool with maxReadFileLine setting", () => {
612610
expect(mockedReadLines).toHaveBeenCalled()
613611
expect(mockedExtractTextFromFile).not.toHaveBeenCalled()
614612
expect(result).toBe(
615-
`<files>\n<file><path>${testFilePath}</path>\n<content lines="1-${maxReadFileLine}">\n${numberedContent}\n</content>\n<notice>Showing only ${maxReadFileLine} of ${totalLines} total lines. Use start_line and end_line if you need to read more</notice>\n</file>\n</files>`,
613+
`<files>\n<file><path>${testFilePath}</path>\n<content lines="1-${maxReadFileLine}">\n${numberedContent}\n</content>\n<notice>Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines</notice>\n</file>\n</files>`,
616614
)
617615
})
618616

@@ -634,7 +632,7 @@ describe("read_file tool with maxReadFileLine setting", () => {
634632

635633
// Should handle long lines without issues
636634
expect(result).toBe(
637-
`<files>\n<file><path>${testFilePath}</path>\n<content lines="1-${maxReadFileLine}">\n${numberedLine}\n</content>\n<notice>Showing only ${maxReadFileLine} of 5 total lines. Use start_line and end_line if you need to read more</notice>\n</file>\n</files>`,
635+
`<files>\n<file><path>${testFilePath}</path>\n<content lines="1-${maxReadFileLine}">\n${numberedLine}\n</content>\n<notice>Showing only ${maxReadFileLine} of 5 total lines. Use line_range if you need to read more lines</notice>\n</file>\n</files>`,
638636
)
639637
})
640638
})

src/core/__tests__/read-file-tool.test.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ jest.mock("../ignore/RooIgnoreController", () => ({
2525
},
2626
}))
2727

28+
// Mocked functions with correct types
29+
const mockedCountFileLines = countFileLines as jest.MockedFunction<typeof countFileLines>
30+
const mockedReadLines = readLines as jest.MockedFunction<typeof readLines>
31+
const mockedExtractTextFromFile = extractTextFromFile as jest.MockedFunction<typeof extractTextFromFile>
32+
2833
describe("read_file tool functionality", () => {
2934
// Mock instances
3035
const mockCline = {
@@ -70,7 +75,7 @@ describe("read_file tool functionality", () => {
7075
type: "tool_use",
7176
name: "read_file",
7277
params: {
73-
args: `:path:src/app.ts\n:start_line:1\n:end_line:100`,
78+
args: `<file><path>src/app.ts</path><line_range>1-100</line_range></file>`,
7479
},
7580
partial: false,
7681
}
@@ -103,7 +108,7 @@ describe("read_file tool functionality", () => {
103108
type: "tool_use",
104109
name: "read_file",
105110
params: {
106-
args: `:path:src/app.ts\n:start_line:1\n:end_line:50\n======+++======\n:path:src/utils.ts`,
111+
args: `<file><path>src/app.ts</path><line_range>1-50</line_range></file><file><path>src/utils.ts</path></file>`,
107112
},
108113
partial: false,
109114
}
@@ -136,11 +141,15 @@ describe("read_file tool functionality", () => {
136141
})
137142

138143
it("should handle invalid line range parameters", async () => {
144+
// Setup
145+
mockedReadLines.mockRejectedValue(new Error("Invalid line range: invalid values"))
146+
mockedExtractTextFromFile.mockRejectedValue(new Error("Invalid line range: invalid values"))
147+
139148
const toolUse: ReadFileToolUse = {
140149
type: "tool_use",
141150
name: "read_file",
142151
params: {
143-
args: `:path:src/app.ts\n:start_line:abc\n:end_line:def`,
152+
args: `<file><path>src/app.ts</path><line_range>abc-def</line_range></file>`,
144153
},
145154
partial: false,
146155
}
@@ -159,15 +168,17 @@ describe("read_file tool functionality", () => {
159168
(param: string, value: string) => value,
160169
)
161170

162-
expect(result).toBe(`<files><error>Error reading files: Invalid start_line value</error></files>`)
171+
expect(result).toBe(
172+
`<files>\n<file><path>src/app.ts</path><error>Error reading file: Invalid line range: invalid values</error></file>\n</files>`,
173+
)
163174
})
164175

165176
it("should handle empty file entries in multiple file reads", async () => {
166177
const toolUse: ReadFileToolUse = {
167178
type: "tool_use",
168179
name: "read_file",
169180
params: {
170-
args: `:path:src/app.ts\n======+++======\n\n======+++======\n:path:src/utils.ts`,
181+
args: `<file><path>src/app.ts</path></file><file><path>src/utils.ts</path></file>`,
171182
},
172183
partial: false,
173184
}
@@ -198,7 +209,7 @@ describe("read_file tool functionality", () => {
198209
type: "tool_use",
199210
name: "read_file",
200211
params: {
201-
args: `:path:smallFile.txt`,
212+
args: `<file><path>smallFile.txt</path></file>`,
202213
},
203214
partial: false,
204215
}
@@ -231,7 +242,7 @@ describe("read_file tool functionality", () => {
231242
type: "tool_use",
232243
name: "read_file",
233244
params: {
234-
args: `:path:largeFile.txt`,
245+
args: `<file><path>largeFile.txt</path></file>`,
235246
},
236247
partial: false,
237248
}
@@ -261,7 +272,7 @@ describe("read_file tool functionality", () => {
261272
)
262273

263274
expect(result).toBe(
264-
`<files>\n<file><path>largeFile.txt</path>\n<content lines="1-500">\n1 | Test content</content>\n<notice>Showing only 500 of 5000 total lines. Use start_line and end_line if you need to read more</notice>\n</file>\n</files>`,
275+
`<files>\n<file><path>largeFile.txt</path>\n<content lines="1-500">\n1 | Test content</content>\n<notice>Showing only 500 of 5000 total lines. Use line_range if you need to read more lines</notice>\n</file>\n</files>`,
265276
)
266277
})
267278

@@ -270,7 +281,7 @@ describe("read_file tool functionality", () => {
270281
type: "tool_use",
271282
name: "read_file",
272283
params: {
273-
args: `:path:binary.pdf`,
284+
args: `<file><path>binary.pdf</path></file>`,
274285
},
275286
partial: false,
276287
}
@@ -305,7 +316,7 @@ describe("read_file tool functionality", () => {
305316
type: "tool_use",
306317
name: "read_file",
307318
params: {
308-
args: `:path:${filePath}\n:start_line:1\n:end_line:100`,
319+
args: `<file><path>${filePath}</path><line_range>1-100</line_range></file>`,
309320
},
310321
partial: false,
311322
}
@@ -367,19 +378,22 @@ describe("read_file tool functionality", () => {
367378
})
368379

369380
it("should handle file read errors", async () => {
381+
// Setup
382+
mockedCountFileLines.mockRejectedValue(new Error("File not found"))
383+
mockedReadLines.mockRejectedValue(new Error("File not found"))
384+
mockedExtractTextFromFile.mockRejectedValue(new Error("File not found"))
385+
370386
const toolUse: ReadFileToolUse = {
371387
type: "tool_use",
372388
name: "read_file",
373389
params: {
374-
args: `:path:nonexistent.txt`,
390+
args: `<file><path>nonexistent.txt</path></file>`,
375391
},
376392
partial: false,
377393
}
378394

379395
const { readFileTool } = require("../tools/readFileTool")
380396

381-
;(countFileLines as jest.Mock).mockRejectedValue(new Error("File not found"))
382-
383397
let result: string | undefined
384398
await readFileTool(
385399
mockCline,
@@ -392,15 +406,17 @@ describe("read_file tool functionality", () => {
392406
(param: string, value: string) => value,
393407
)
394408

395-
expect(result).toBe(`<files><error>Error reading files: File not found</error></files>`)
409+
expect(result).toBe(
410+
`<files>\n<file><path>nonexistent.txt</path><error>Error reading file: File not found</error></file>\n</files>`,
411+
)
396412
})
397413

398414
it("should handle line counting errors", async () => {
399415
const toolUse: ReadFileToolUse = {
400416
type: "tool_use",
401417
name: "read_file",
402418
params: {
403-
args: `:path:error.txt`,
419+
args: `<file><path>error.txt</path></file>`,
404420
},
405421
partial: false,
406422
}
@@ -431,7 +447,7 @@ describe("read_file tool functionality", () => {
431447
type: "tool_use",
432448
name: "read_file",
433449
params: {
434-
args: `:path:src/code.ts`,
450+
args: `<file><path>src/code.ts</path></file>`,
435451
},
436452
partial: false,
437453
}
@@ -465,7 +481,7 @@ describe("read_file tool functionality", () => {
465481
type: "tool_use",
466482
name: "read_file",
467483
params: {
468-
args: `:path:huge.log`,
484+
args: `<file><path>huge.log</path></file>`,
469485
},
470486
partial: false,
471487
}
@@ -497,7 +513,7 @@ describe("read_file tool functionality", () => {
497513
type: "tool_use",
498514
name: "read_file",
499515
params: {
500-
args: `:path:longlines.txt`,
516+
args: `<file><path>longlines.txt</path></file>`,
501517
},
502518
partial: false,
503519
}

0 commit comments

Comments
 (0)