mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 08:03:39 +00:00
Add proper truncation notices and comprehensive tests for read tool
**Improved output messages:** 1. File fits within limits: Just outputs content (no notices) 2. Lines get truncated: Shows "Some lines were truncated to 2000 characters for display" 3. File doesn't fit limit: Shows "N more lines not shown. Use offset=X to continue reading" 4. Offset beyond file: Shows "Error: Offset X is beyond end of file (N lines total)" 5. Both truncations: Combines both notices with ". " separator **Comprehensive test coverage:** - Files within limits (no notices) - Large files (line truncation) - Long lines (character truncation) - Offset parameter - Limit parameter - Offset + limit together - Invalid offset (out of bounds) - Combined truncations (both notices) All 17 tests passing ✓
This commit is contained in:
parent
c7a73d4f81
commit
2f0f0a913e
3 changed files with 491 additions and 15 deletions
File diff suppressed because one or more lines are too long
|
|
@ -125,23 +125,50 @@ export const readTool: AgentTool<typeof readSchema> = {
|
|||
const maxLines = limit || MAX_LINES;
|
||||
const endLine = Math.min(startLine + maxLines, lines.length);
|
||||
|
||||
// Get the relevant lines
|
||||
const selectedLines = lines.slice(startLine, endLine);
|
||||
// Check if offset is out of bounds
|
||||
if (startLine >= lines.length) {
|
||||
content = [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: Offset ${offset} is beyond end of file (${lines.length} lines total)`,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
// Get the relevant lines
|
||||
const selectedLines = lines.slice(startLine, endLine);
|
||||
|
||||
// Truncate long lines
|
||||
const formattedLines = selectedLines.map((line) => {
|
||||
return line.length > MAX_LINE_LENGTH ? line.slice(0, MAX_LINE_LENGTH) : line;
|
||||
});
|
||||
// Truncate long lines and track which were truncated
|
||||
let hadTruncatedLines = false;
|
||||
const formattedLines = selectedLines.map((line) => {
|
||||
if (line.length > MAX_LINE_LENGTH) {
|
||||
hadTruncatedLines = true;
|
||||
return line.slice(0, MAX_LINE_LENGTH);
|
||||
}
|
||||
return line;
|
||||
});
|
||||
|
||||
let outputText = formattedLines.join("\n");
|
||||
let outputText = formattedLines.join("\n");
|
||||
|
||||
// Add truncation notice if needed
|
||||
if (endLine < lines.length) {
|
||||
const remaining = lines.length - endLine;
|
||||
outputText += `\n\n... (${remaining} more lines not shown. Use offset=${endLine + 1} to continue reading)`;
|
||||
// Add notices
|
||||
const notices: string[] = [];
|
||||
|
||||
if (hadTruncatedLines) {
|
||||
notices.push(`Some lines were truncated to ${MAX_LINE_LENGTH} characters for display`);
|
||||
}
|
||||
|
||||
if (endLine < lines.length) {
|
||||
const remaining = lines.length - endLine;
|
||||
notices.push(
|
||||
`${remaining} more lines not shown. Use offset=${endLine + 1} to continue reading`,
|
||||
);
|
||||
}
|
||||
|
||||
if (notices.length > 0) {
|
||||
outputText += `\n\n... (${notices.join(". ")})`;
|
||||
}
|
||||
|
||||
content = [{ type: "text", text: outputText }];
|
||||
}
|
||||
|
||||
content = [{ type: "text", text: outputText }];
|
||||
}
|
||||
|
||||
// Check if aborted after reading
|
||||
|
|
|
|||
|
|
@ -32,14 +32,15 @@ describe("Coding Agent Tools", () => {
|
|||
});
|
||||
|
||||
describe("read tool", () => {
|
||||
it("should read file contents", async () => {
|
||||
it("should read file contents that fit within limits", async () => {
|
||||
const testFile = join(testDir, "test.txt");
|
||||
const content = "Hello, world!";
|
||||
const content = "Hello, world!\nLine 2\nLine 3";
|
||||
writeFileSync(testFile, content);
|
||||
|
||||
const result = await readTool.execute("test-call-1", { path: testFile });
|
||||
|
||||
expect(getTextOutput(result)).toBe(content);
|
||||
expect(getTextOutput(result)).not.toContain("more lines not shown");
|
||||
expect(result.details).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -51,6 +52,109 @@ describe("Coding Agent Tools", () => {
|
|||
expect(getTextOutput(result)).toContain("Error");
|
||||
expect(getTextOutput(result)).toContain("File not found");
|
||||
});
|
||||
|
||||
it("should truncate files exceeding line limit", async () => {
|
||||
const testFile = join(testDir, "large.txt");
|
||||
const lines = Array.from({ length: 2500 }, (_, i) => `Line ${i + 1}`);
|
||||
writeFileSync(testFile, lines.join("\n"));
|
||||
|
||||
const result = await readTool.execute("test-call-3", { path: testFile });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain("Line 1");
|
||||
expect(output).toContain("Line 2000");
|
||||
expect(output).not.toContain("Line 2001");
|
||||
expect(output).toContain("500 more lines not shown");
|
||||
expect(output).toContain("Use offset=2001 to continue reading");
|
||||
});
|
||||
|
||||
it("should truncate long lines and show notice", async () => {
|
||||
const testFile = join(testDir, "long-lines.txt");
|
||||
const longLine = "a".repeat(3000);
|
||||
const content = `Short line\n${longLine}\nAnother short line`;
|
||||
writeFileSync(testFile, content);
|
||||
|
||||
const result = await readTool.execute("test-call-4", { path: testFile });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain("Short line");
|
||||
expect(output).toContain("Another short line");
|
||||
expect(output).toContain("Some lines were truncated to 2000 characters");
|
||||
expect(output.split("\n")[1].length).toBe(2000);
|
||||
});
|
||||
|
||||
it("should handle offset parameter", async () => {
|
||||
const testFile = join(testDir, "offset-test.txt");
|
||||
const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`);
|
||||
writeFileSync(testFile, lines.join("\n"));
|
||||
|
||||
const result = await readTool.execute("test-call-5", { path: testFile, offset: 51 });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).not.toContain("Line 50");
|
||||
expect(output).toContain("Line 51");
|
||||
expect(output).toContain("Line 100");
|
||||
expect(output).not.toContain("more lines not shown");
|
||||
});
|
||||
|
||||
it("should handle limit parameter", async () => {
|
||||
const testFile = join(testDir, "limit-test.txt");
|
||||
const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`);
|
||||
writeFileSync(testFile, lines.join("\n"));
|
||||
|
||||
const result = await readTool.execute("test-call-6", { path: testFile, limit: 10 });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain("Line 1");
|
||||
expect(output).toContain("Line 10");
|
||||
expect(output).not.toContain("Line 11");
|
||||
expect(output).toContain("90 more lines not shown");
|
||||
expect(output).toContain("Use offset=11 to continue reading");
|
||||
});
|
||||
|
||||
it("should handle offset + limit together", async () => {
|
||||
const testFile = join(testDir, "offset-limit-test.txt");
|
||||
const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`);
|
||||
writeFileSync(testFile, lines.join("\n"));
|
||||
|
||||
const result = await readTool.execute("test-call-7", {
|
||||
path: testFile,
|
||||
offset: 41,
|
||||
limit: 20,
|
||||
});
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).not.toContain("Line 40");
|
||||
expect(output).toContain("Line 41");
|
||||
expect(output).toContain("Line 60");
|
||||
expect(output).not.toContain("Line 61");
|
||||
expect(output).toContain("40 more lines not shown");
|
||||
expect(output).toContain("Use offset=61 to continue reading");
|
||||
});
|
||||
|
||||
it("should show error when offset is beyond file length", async () => {
|
||||
const testFile = join(testDir, "short.txt");
|
||||
writeFileSync(testFile, "Line 1\nLine 2\nLine 3");
|
||||
|
||||
const result = await readTool.execute("test-call-8", { path: testFile, offset: 100 });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain("Error: Offset 100 is beyond end of file");
|
||||
expect(output).toContain("3 lines total");
|
||||
});
|
||||
|
||||
it("should show both truncation notices when applicable", async () => {
|
||||
const testFile = join(testDir, "both-truncations.txt");
|
||||
const longLine = "b".repeat(3000);
|
||||
const lines = Array.from({ length: 2500 }, (_, i) => (i === 500 ? longLine : `Line ${i + 1}`));
|
||||
writeFileSync(testFile, lines.join("\n"));
|
||||
|
||||
const result = await readTool.execute("test-call-9", { path: testFile });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain("Some lines were truncated to 2000 characters");
|
||||
expect(output).toContain("500 more lines not shown");
|
||||
});
|
||||
});
|
||||
|
||||
describe("write tool", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue