Fix hardcoded truncation limits in tool output display

- Add maxLines and maxBytes fields to TruncationResult to track actual limits used
- Update tool-execution.ts to use actual limits from truncation result
- Add fallbacks to DEFAULT_MAX_* for backward compatibility with old sessions
- Fix outdated comments that said 30KB when default is 50KB
This commit is contained in:
Mario Zechner 2025-12-09 17:06:29 +01:00
parent 8226975080
commit ee9acdb49d
2 changed files with 36 additions and 9 deletions

View file

@ -3,7 +3,7 @@
* *
* Truncation is based on two independent limits - whichever is hit first wins: * Truncation is based on two independent limits - whichever is hit first wins:
* - Line limit (default: 2000 lines) * - Line limit (default: 2000 lines)
* - Byte limit (default: 30KB) * - Byte limit (default: 50KB)
* *
* Never returns partial lines (except bash tail truncation edge case). * Never returns partial lines (except bash tail truncation edge case).
*/ */
@ -31,12 +31,16 @@ export interface TruncationResult {
lastLinePartial: boolean; lastLinePartial: boolean;
/** Whether the first line exceeded the byte limit (for head truncation) */ /** Whether the first line exceeded the byte limit (for head truncation) */
firstLineExceedsLimit: boolean; firstLineExceedsLimit: boolean;
/** The max lines limit that was applied */
maxLines: number;
/** The max bytes limit that was applied */
maxBytes: number;
} }
export interface TruncationOptions { export interface TruncationOptions {
/** Maximum number of lines (default: 2000) */ /** Maximum number of lines (default: 2000) */
maxLines?: number; maxLines?: number;
/** Maximum number of bytes (default: 30KB) */ /** Maximum number of bytes (default: 50KB) */
maxBytes?: number; maxBytes?: number;
} }
@ -80,6 +84,8 @@ export function truncateHead(content: string, options: TruncationOptions = {}):
outputBytes: totalBytes, outputBytes: totalBytes,
lastLinePartial: false, lastLinePartial: false,
firstLineExceedsLimit: false, firstLineExceedsLimit: false,
maxLines,
maxBytes,
}; };
} }
@ -96,6 +102,8 @@ export function truncateHead(content: string, options: TruncationOptions = {}):
outputBytes: 0, outputBytes: 0,
lastLinePartial: false, lastLinePartial: false,
firstLineExceedsLimit: true, firstLineExceedsLimit: true,
maxLines,
maxBytes,
}; };
} }
@ -135,6 +143,8 @@ export function truncateHead(content: string, options: TruncationOptions = {}):
outputBytes: finalOutputBytes, outputBytes: finalOutputBytes,
lastLinePartial: false, lastLinePartial: false,
firstLineExceedsLimit: false, firstLineExceedsLimit: false,
maxLines,
maxBytes,
}; };
} }
@ -164,6 +174,8 @@ export function truncateTail(content: string, options: TruncationOptions = {}):
outputBytes: totalBytes, outputBytes: totalBytes,
lastLinePartial: false, lastLinePartial: false,
firstLineExceedsLimit: false, firstLineExceedsLimit: false,
maxLines,
maxBytes,
}; };
} }
@ -212,6 +224,8 @@ export function truncateTail(content: string, options: TruncationOptions = {}):
outputBytes: finalOutputBytes, outputBytes: finalOutputBytes,
lastLinePartial, lastLinePartial,
firstLineExceedsLimit: false, firstLineExceedsLimit: false,
maxLines,
maxBytes,
}; };
} }

View file

@ -1,6 +1,7 @@
import * as os from "node:os"; import * as os from "node:os";
import { Container, Spacer, Text } from "@mariozechner/pi-tui"; import { Container, Spacer, Text } from "@mariozechner/pi-tui";
import stripAnsi from "strip-ansi"; import stripAnsi from "strip-ansi";
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/tools/truncate.js";
import { theme } from "../theme/theme.js"; import { theme } from "../theme/theme.js";
/** /**
@ -140,7 +141,9 @@ export class ToolExecutionComponent extends Container {
if (truncation.truncatedBy === "lines") { if (truncation.truncatedBy === "lines") {
warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`); warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
} else { } else {
warnings.push(`Truncated: ${truncation.outputLines} lines shown (30KB limit)`); warnings.push(
`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,
);
} }
} }
text += "\n" + theme.fg("warning", `[${warnings.join(". ")}]`); text += "\n" + theme.fg("warning", `[${warnings.join(". ")}]`);
@ -178,16 +181,26 @@ export class ToolExecutionComponent extends Container {
const truncation = this.result.details?.truncation; const truncation = this.result.details?.truncation;
if (truncation?.truncated) { if (truncation?.truncated) {
if (truncation.firstLineExceedsLimit) { if (truncation.firstLineExceedsLimit) {
text += "\n" + theme.fg("warning", `[First line exceeds 30KB limit]`); text +=
"\n" +
theme.fg(
"warning",
`[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`,
);
} else if (truncation.truncatedBy === "lines") { } else if (truncation.truncatedBy === "lines") {
text += text +=
"\n" + "\n" +
theme.fg( theme.fg(
"warning", "warning",
`[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines]`, `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`,
); );
} else { } else {
text += "\n" + theme.fg("warning", `[Truncated: ${truncation.outputLines} lines shown (30KB limit)]`); text +=
"\n" +
theme.fg(
"warning",
`[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`,
);
} }
} }
} }
@ -277,7 +290,7 @@ export class ToolExecutionComponent extends Container {
warnings.push(`${entryLimit} entries limit`); warnings.push(`${entryLimit} entries limit`);
} }
if (truncation?.truncated) { if (truncation?.truncated) {
warnings.push("30KB limit"); warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
} }
text += "\n" + theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`); text += "\n" + theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`);
} }
@ -319,7 +332,7 @@ export class ToolExecutionComponent extends Container {
warnings.push(`${resultLimit} results limit`); warnings.push(`${resultLimit} results limit`);
} }
if (truncation?.truncated) { if (truncation?.truncated) {
warnings.push("30KB limit"); warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
} }
text += "\n" + theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`); text += "\n" + theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`);
} }
@ -366,7 +379,7 @@ export class ToolExecutionComponent extends Container {
warnings.push(`${matchLimit} matches limit`); warnings.push(`${matchLimit} matches limit`);
} }
if (truncation?.truncated) { if (truncation?.truncated) {
warnings.push("30KB limit"); warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
} }
if (linesTruncated) { if (linesTruncated) {
warnings.push("some lines truncated"); warnings.push("some lines truncated");