Skip to content

Commit e9dee8d

Browse files
committed
refactor: share harness truncation result helpers
1 parent 9f30af5 commit e9dee8d

1 file changed

Lines changed: 91 additions & 80 deletions

File tree

packages/agent-core/src/harness/utils/truncate.ts

Lines changed: 91 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ export interface TruncationOptions {
4444
maxBytes?: number;
4545
}
4646

47+
interface ResolvedTruncationInput {
48+
lines: string[];
49+
totalLines: number;
50+
totalBytes: number;
51+
maxLines: number;
52+
maxBytes: number;
53+
}
54+
4755
interface RuntimeBuffer {
4856
byteLength(content: string, encoding: "utf8"): number;
4957
}
@@ -137,6 +145,50 @@ export function formatSize(bytes: number): string {
137145
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
138146
}
139147

148+
function resolveTruncationInput(
149+
content: string,
150+
options: TruncationOptions,
151+
): ResolvedTruncationInput {
152+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
153+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
154+
const totalBytes = utf8ByteLength(content);
155+
const lines = splitLinesForCounting(content);
156+
return {
157+
lines,
158+
totalLines: lines.length,
159+
totalBytes,
160+
maxLines,
161+
maxBytes,
162+
};
163+
}
164+
165+
function buildTruncationResult(
166+
input: ResolvedTruncationInput,
167+
params: {
168+
content: string;
169+
truncated: boolean;
170+
truncatedBy: TruncationResult["truncatedBy"];
171+
outputLines: number;
172+
outputBytes?: number;
173+
lastLinePartial?: boolean;
174+
firstLineExceedsLimit?: boolean;
175+
},
176+
): TruncationResult {
177+
return {
178+
content: params.content,
179+
truncated: params.truncated,
180+
truncatedBy: params.truncatedBy,
181+
totalLines: input.totalLines,
182+
totalBytes: input.totalBytes,
183+
outputLines: params.outputLines,
184+
outputBytes: params.outputBytes ?? utf8ByteLength(params.content),
185+
lastLinePartial: params.lastLinePartial ?? false,
186+
firstLineExceedsLimit: params.firstLineExceedsLimit ?? false,
187+
maxLines: input.maxLines,
188+
maxBytes: input.maxBytes,
189+
};
190+
}
191+
140192
/**
141193
* Truncate content from the head (keep first N lines/bytes).
142194
* Suitable for file reads where you want to see the beginning.
@@ -145,58 +197,39 @@ export function formatSize(bytes: number): string {
145197
* returns empty content with firstLineExceedsLimit=true.
146198
*/
147199
export function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {
148-
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
149-
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
150-
151-
const totalBytes = utf8ByteLength(content);
152-
const lines = splitLinesForCounting(content);
153-
const totalLines = lines.length;
200+
const input = resolveTruncationInput(content, options);
154201

155-
// Check if no truncation needed
156-
if (totalLines <= maxLines && totalBytes <= maxBytes) {
157-
return {
202+
if (input.totalLines <= input.maxLines && input.totalBytes <= input.maxBytes) {
203+
return buildTruncationResult(input, {
158204
content,
159205
truncated: false,
160206
truncatedBy: null,
161-
totalLines,
162-
totalBytes,
163-
outputLines: totalLines,
164-
outputBytes: totalBytes,
165-
lastLinePartial: false,
166-
firstLineExceedsLimit: false,
167-
maxLines,
168-
maxBytes,
169-
};
207+
outputLines: input.totalLines,
208+
outputBytes: input.totalBytes,
209+
});
170210
}
171211

172-
// Check if first line alone exceeds byte limit
173-
const firstLineBytes = utf8ByteLength(lines[0]);
174-
if (firstLineBytes > maxBytes) {
175-
return {
212+
const firstLineBytes = utf8ByteLength(input.lines[0]);
213+
if (firstLineBytes > input.maxBytes) {
214+
return buildTruncationResult(input, {
176215
content: "",
177216
truncated: true,
178217
truncatedBy: "bytes",
179-
totalLines,
180-
totalBytes,
181218
outputLines: 0,
182219
outputBytes: 0,
183-
lastLinePartial: false,
184220
firstLineExceedsLimit: true,
185-
maxLines,
186-
maxBytes,
187-
};
221+
});
188222
}
189223

190-
// Collect complete lines that fit
191224
const outputLinesArr: string[] = [];
192225
let outputBytesCount = 0;
193-
let truncatedBy: "lines" | "bytes" = totalLines > maxLines ? "lines" : "bytes";
226+
let truncatedBy: "lines" | "bytes" = input.totalLines > input.maxLines ? "lines" : "bytes";
194227

195-
for (let i = 0; i < lines.length && i < maxLines; i++) {
196-
const line = lines[i];
228+
for (let i = 0; i < input.lines.length && i < input.maxLines; i++) {
229+
const line = input.lines[i];
197230
const lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline
198231

199-
if (outputBytesCount + lineBytes > maxBytes) {
232+
if (outputBytesCount + lineBytes > input.maxBytes) {
200233
truncatedBy = "bytes";
201234
break;
202235
}
@@ -205,27 +238,22 @@ export function truncateHead(content: string, options: TruncationOptions = {}):
205238
outputBytesCount += lineBytes;
206239
}
207240

208-
// If we exited due to line limit
209-
if (totalLines > maxLines && outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
241+
if (
242+
input.totalLines > input.maxLines &&
243+
outputLinesArr.length >= input.maxLines &&
244+
outputBytesCount <= input.maxBytes
245+
) {
210246
truncatedBy = "lines";
211247
}
212248

213249
const outputContent = outputLinesArr.join("\n");
214-
const finalOutputBytes = utf8ByteLength(outputContent);
215250

216-
return {
251+
return buildTruncationResult(input, {
217252
content: outputContent,
218253
truncated: true,
219254
truncatedBy,
220-
totalLines,
221-
totalBytes,
222255
outputLines: outputLinesArr.length,
223-
outputBytes: finalOutputBytes,
224-
lastLinePartial: false,
225-
firstLineExceedsLimit: false,
226-
maxLines,
227-
maxBytes,
228-
};
256+
});
229257
}
230258

231259
/**
@@ -235,46 +263,33 @@ export function truncateHead(content: string, options: TruncationOptions = {}):
235263
* May return partial first line if the last line of original content exceeds byte limit.
236264
*/
237265
export function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {
238-
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
239-
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
240-
241-
const totalBytes = utf8ByteLength(content);
242-
const lines = splitLinesForCounting(content);
243-
const totalLines = lines.length;
266+
const input = resolveTruncationInput(content, options);
244267

245-
// Check if no truncation needed
246-
if (totalLines <= maxLines && totalBytes <= maxBytes) {
247-
return {
268+
if (input.totalLines <= input.maxLines && input.totalBytes <= input.maxBytes) {
269+
return buildTruncationResult(input, {
248270
content,
249271
truncated: false,
250272
truncatedBy: null,
251-
totalLines,
252-
totalBytes,
253-
outputLines: totalLines,
254-
outputBytes: totalBytes,
255-
lastLinePartial: false,
256-
firstLineExceedsLimit: false,
257-
maxLines,
258-
maxBytes,
259-
};
273+
outputLines: input.totalLines,
274+
outputBytes: input.totalBytes,
275+
});
260276
}
261277

262-
// Work backwards from the end
263278
const outputLinesArr: string[] = [];
264279
let outputBytesCount = 0;
265-
let truncatedBy: "lines" | "bytes" = totalLines > maxLines ? "lines" : "bytes";
280+
let truncatedBy: "lines" | "bytes" = input.totalLines > input.maxLines ? "lines" : "bytes";
266281
let lastLinePartial = false;
267282

268-
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
269-
const line = lines[i];
283+
for (let i = input.lines.length - 1; i >= 0 && outputLinesArr.length < input.maxLines; i--) {
284+
const line = input.lines[i];
270285
const lineBytes = utf8ByteLength(line) + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline
271286

272-
if (outputBytesCount + lineBytes > maxBytes) {
287+
if (outputBytesCount + lineBytes > input.maxBytes) {
273288
truncatedBy = "bytes";
274289
// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,
275290
// take the end of the line (partial)
276291
if (outputLinesArr.length === 0) {
277-
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
292+
const truncatedLine = truncateStringToBytesFromEnd(line, input.maxBytes);
278293
outputLinesArr.unshift(truncatedLine);
279294
outputBytesCount = utf8ByteLength(truncatedLine);
280295
lastLinePartial = true;
@@ -286,27 +301,23 @@ export function truncateTail(content: string, options: TruncationOptions = {}):
286301
outputBytesCount += lineBytes;
287302
}
288303

289-
// If we exited due to line limit
290-
if (totalLines > maxLines && outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
304+
if (
305+
input.totalLines > input.maxLines &&
306+
outputLinesArr.length >= input.maxLines &&
307+
outputBytesCount <= input.maxBytes
308+
) {
291309
truncatedBy = "lines";
292310
}
293311

294312
const outputContent = outputLinesArr.join("\n");
295-
const finalOutputBytes = utf8ByteLength(outputContent);
296313

297-
return {
314+
return buildTruncationResult(input, {
298315
content: outputContent,
299316
truncated: true,
300317
truncatedBy,
301-
totalLines,
302-
totalBytes,
303318
outputLines: outputLinesArr.length,
304-
outputBytes: finalOutputBytes,
305319
lastLinePartial,
306-
firstLineExceedsLimit: false,
307-
maxLines,
308-
maxBytes,
309-
};
320+
});
310321
}
311322

312323
/**

0 commit comments

Comments
 (0)