bug(core): UI reporting for truncated read_file. (#5155)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
joshualitt 2025-07-31 09:31:14 -07:00 committed by GitHub
parent 65be9cab47
commit ae86c7ba05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 79 additions and 3 deletions

View File

@ -222,7 +222,7 @@ describe('ReadFileTool', () => {
'Line 7',
'Line 8',
].join('\n'),
returnDisplay: '(truncated)',
returnDisplay: 'Read lines 6-8 of 20 from paginated.txt',
});
});

View File

@ -420,7 +420,7 @@ describe('fileUtils', () => {
expect(result.llmContent).toContain(
'[File content truncated: showing lines 6-10 of 20 total lines. Use offset/limit parameters to view more.]',
);
expect(result.returnDisplay).toBe('(truncated)');
expect(result.returnDisplay).toBe('Read lines 6-10 of 20 from test.txt');
expect(result.isTruncated).toBe(true);
expect(result.originalLineCount).toBe(20);
expect(result.linesShown).toEqual([6, 10]);
@ -465,9 +465,72 @@ describe('fileUtils', () => {
expect(result.llmContent).toContain(
'[File content partially truncated: some lines exceeded maximum length of 2000 characters.]',
);
expect(result.returnDisplay).toBe(
'Read all 3 lines from test.txt (some lines were shortened)',
);
expect(result.isTruncated).toBe(true);
});
it('should truncate when line count exceeds the limit', async () => {
const lines = Array.from({ length: 11 }, (_, i) => `Line ${i + 1}`);
actualNodeFs.writeFileSync(testTextFilePath, lines.join('\n'));
// Read 5 lines, but there are 11 total
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
0,
5,
);
expect(result.isTruncated).toBe(true);
expect(result.returnDisplay).toBe('Read lines 1-5 of 11 from test.txt');
});
it('should truncate when a line length exceeds the character limit', async () => {
const longLine = 'b'.repeat(2500);
const lines = Array.from({ length: 10 }, (_, i) => `Line ${i + 1}`);
lines.push(longLine); // Total 11 lines
actualNodeFs.writeFileSync(testTextFilePath, lines.join('\n'));
// Read all 11 lines, including the long one
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
0,
11,
);
expect(result.isTruncated).toBe(true);
expect(result.returnDisplay).toBe(
'Read all 11 lines from test.txt (some lines were shortened)',
);
});
it('should truncate both line count and line length when both exceed limits', async () => {
const linesWithLongInMiddle = Array.from(
{ length: 20 },
(_, i) => `Line ${i + 1}`,
);
linesWithLongInMiddle[4] = 'c'.repeat(2500);
actualNodeFs.writeFileSync(
testTextFilePath,
linesWithLongInMiddle.join('\n'),
);
// Read 10 lines out of 20, including the long line
const result = await processSingleFileContent(
testTextFilePath,
tempRootDir,
0,
10,
);
expect(result.isTruncated).toBe(true);
expect(result.returnDisplay).toBe(
'Read lines 1-10 of 20 from test.txt (some lines were shortened)',
);
});
it('should return an error if the file size exceeds 20MB', async () => {
// Create a file just over 20MB
const twentyOneMB = 21 * 1024 * 1024;

View File

@ -310,9 +310,22 @@ export async function processSingleFileContent(
}
llmTextContent += formattedLines.join('\n');
// By default, return nothing to streamline the common case of a successful read_file.
let returnDisplay = '';
if (contentRangeTruncated) {
returnDisplay = `Read lines ${
actualStartLine + 1
}-${endLine} of ${originalLineCount} from ${relativePathForDisplay}`;
if (linesWereTruncatedInLength) {
returnDisplay += ' (some lines were shortened)';
}
} else if (linesWereTruncatedInLength) {
returnDisplay = `Read all ${originalLineCount} lines from ${relativePathForDisplay} (some lines were shortened)`;
}
return {
llmContent: llmTextContent,
returnDisplay: isTruncated ? '(truncated)' : '',
returnDisplay,
isTruncated,
originalLineCount,
linesShown: [actualStartLine + 1, endLine],