@@ -12,8 +12,8 @@ use codex_protocol::models::ReasoningItemReasoningSummary;
1212use pretty_assertions:: assert_eq;
1313use regex_lite:: Regex ;
1414
15- const EXEC_FORMAT_MAX_LINES : usize = 256 ;
1615const EXEC_FORMAT_MAX_BYTES : usize = 10_000 ;
16+ const EXEC_FORMAT_MAX_TOKENS : usize = 2_500 ;
1717
1818fn assistant_msg ( text : & str ) -> ResponseItem {
1919 ResponseItem :: Message {
@@ -56,6 +56,10 @@ fn reasoning_msg(text: &str) -> ResponseItem {
5656 }
5757}
5858
59+ fn truncate_exec_output ( content : & str ) -> String {
60+ truncate:: truncate_text ( content, TruncationPolicy :: Tokens ( EXEC_FORMAT_MAX_TOKENS ) )
61+ }
62+
5963#[ test]
6064fn filters_non_api_messages ( ) {
6165 let mut h = ContextManager :: default ( ) ;
@@ -228,7 +232,7 @@ fn normalization_retains_local_shell_outputs() {
228232 ResponseItem :: FunctionCallOutput {
229233 call_id: "shell-1" . to_string( ) ,
230234 output: FunctionCallOutputPayload {
231- content: "ok " . to_string( ) ,
235+ content: "Total output lines: 1 \n \n ok " . to_string( ) ,
232236 ..Default :: default ( )
233237 } ,
234238 } ,
@@ -330,8 +334,8 @@ fn record_items_respects_custom_token_limit() {
330334 assert ! ( stored. content. contains( "tokens truncated" ) ) ;
331335}
332336
333- fn assert_truncated_message_matches ( message : & str , line : & str , total_lines : usize ) {
334- let pattern = truncated_message_pattern ( line, total_lines ) ;
337+ fn assert_truncated_message_matches ( message : & str , line : & str , expected_removed : usize ) {
338+ let pattern = truncated_message_pattern ( line) ;
335339 let regex = Regex :: new ( & pattern) . unwrap_or_else ( |err| {
336340 panic ! ( "failed to compile regex {pattern}: {err}" ) ;
337341 } ) ;
@@ -347,51 +351,37 @@ fn assert_truncated_message_matches(message: &str, line: &str, total_lines: usiz
347351 "body exceeds byte limit: {} bytes" ,
348352 body. len( )
349353 ) ;
354+ let removed: usize = captures
355+ . name ( "removed" )
356+ . expect ( "missing removed capture" )
357+ . as_str ( )
358+ . parse ( )
359+ . unwrap_or_else ( |err| panic ! ( "invalid removed tokens: {err}" ) ) ;
360+ assert_eq ! ( removed, expected_removed, "mismatched removed token count" ) ;
350361}
351362
352- fn truncated_message_pattern ( line : & str , total_lines : usize ) -> String {
353- let head_lines = EXEC_FORMAT_MAX_LINES / 2 ;
354- let tail_lines = EXEC_FORMAT_MAX_LINES - head_lines;
355- let head_take = head_lines. min ( total_lines) ;
356- let tail_take = tail_lines. min ( total_lines. saturating_sub ( head_take) ) ;
357- let omitted = total_lines. saturating_sub ( head_take + tail_take) ;
363+ fn truncated_message_pattern ( line : & str ) -> String {
358364 let escaped_line = regex_lite:: escape ( line) ;
359- if omitted == 0 {
360- return format ! (
361- r"(?s)^Total output lines: {total_lines}\n\n(?P<body>{escaped_line}.*\n\[\.{{3}} removed \d+ bytes to fit {EXEC_FORMAT_MAX_BYTES} byte limit \.{{3}}]\n\n.*)$" ,
362- ) ;
363- }
364- format ! (
365- r"(?s)^Total output lines: {total_lines}\n\n(?P<body>{escaped_line}.*\n\[\.{{3}} omitted {omitted} of {total_lines} lines \.{{3}}]\n\n.*)$" ,
366- )
365+ format ! ( r"(?s)^(?P<body>{escaped_line}.*?)(?:\r?)?…(?P<removed>\d+) tokens truncated…(?:.*)?$" )
367366}
368367
369368#[ test]
370369fn format_exec_output_truncates_large_error ( ) {
371370 let line = "very long execution error line that should trigger truncation\n " ;
372371 let large_error = line. repeat ( 2_500 ) ; // way beyond both byte and line limits
373372
374- let truncated = truncate :: truncate_with_line_bytes_budget ( & large_error, EXEC_FORMAT_MAX_BYTES ) ;
373+ let truncated = truncate_exec_output ( & large_error) ;
375374
376- let total_lines = large_error. lines ( ) . count ( ) ;
377- assert_truncated_message_matches ( & truncated, line, total_lines) ;
375+ assert_truncated_message_matches ( & truncated, line, 36250 ) ;
378376 assert_ne ! ( truncated, large_error) ;
379377}
380378
381379#[ test]
382380fn format_exec_output_marks_byte_truncation_without_omitted_lines ( ) {
383- let long_line = "a" . repeat ( EXEC_FORMAT_MAX_BYTES + 50 ) ;
384- let truncated = truncate:: truncate_with_line_bytes_budget ( & long_line, EXEC_FORMAT_MAX_BYTES ) ;
385-
381+ let long_line = "a" . repeat ( EXEC_FORMAT_MAX_BYTES + 10000 ) ;
382+ let truncated = truncate_exec_output ( & long_line) ;
386383 assert_ne ! ( truncated, long_line) ;
387- let removed_bytes = long_line. len ( ) . saturating_sub ( EXEC_FORMAT_MAX_BYTES ) ;
388- let marker_line = format ! (
389- "[... removed {removed_bytes} bytes to fit {EXEC_FORMAT_MAX_BYTES} byte limit ...]"
390- ) ;
391- assert ! (
392- truncated. contains( & marker_line) ,
393- "missing byte truncation marker: {truncated}"
394- ) ;
384+ assert_truncated_message_matches ( & truncated, "a" , 2500 ) ;
395385 assert ! (
396386 !truncated. contains( "omitted" ) ,
397387 "line omission marker should not appear when no lines were dropped: {truncated}"
@@ -401,34 +391,25 @@ fn format_exec_output_marks_byte_truncation_without_omitted_lines() {
401391#[ test]
402392fn format_exec_output_returns_original_when_within_limits ( ) {
403393 let content = "example output\n " . repeat ( 10 ) ;
404-
405- assert_eq ! (
406- truncate:: truncate_with_line_bytes_budget( & content, EXEC_FORMAT_MAX_BYTES ) ,
407- content
408- ) ;
394+ assert_eq ! ( truncate_exec_output( & content) , content) ;
409395}
410396
411397#[ test]
412398fn format_exec_output_reports_omitted_lines_and_keeps_head_and_tail ( ) {
413- let total_lines = EXEC_FORMAT_MAX_LINES + 100 ;
399+ let total_lines = 2_000 ;
400+ let filler = "x" . repeat ( 64 ) ;
414401 let content: String = ( 0 ..total_lines)
415- . map ( |idx| format ! ( "line-{idx}\n " ) )
402+ . map ( |idx| format ! ( "line-{idx}-{filler} \n " ) )
416403 . collect ( ) ;
417404
418- let truncated = truncate:: truncate_with_line_bytes_budget ( & content, EXEC_FORMAT_MAX_BYTES ) ;
419- let omitted = total_lines - EXEC_FORMAT_MAX_LINES ;
420- let expected_marker = format ! ( "[... omitted {omitted} of {total_lines} lines ...]" ) ;
421-
422- assert ! (
423- truncated. contains( & expected_marker) ,
424- "missing omitted marker: {truncated}"
425- ) ;
405+ let truncated = truncate_exec_output ( & content) ;
406+ assert_truncated_message_matches ( & truncated, "line-0-" , 34_723 ) ;
426407 assert ! (
427- truncated. contains( "line-0\n " ) ,
408+ truncated. contains( "line-0- " ) ,
428409 "expected head line to remain: {truncated}"
429410 ) ;
430411
431- let last_line = format ! ( "line-{}\n " , total_lines - 1 ) ;
412+ let last_line = format ! ( "line-{}- " , total_lines - 1 ) ;
432413 assert ! (
433414 truncated. contains( & last_line) ,
434415 "expected tail line to remain: {truncated}"
@@ -437,22 +418,15 @@ fn format_exec_output_reports_omitted_lines_and_keeps_head_and_tail() {
437418
438419#[ test]
439420fn format_exec_output_prefers_line_marker_when_both_limits_exceeded ( ) {
440- let total_lines = EXEC_FORMAT_MAX_LINES + 42 ;
421+ let total_lines = 300 ;
441422 let long_line = "x" . repeat ( 256 ) ;
442423 let content: String = ( 0 ..total_lines)
443424 . map ( |idx| format ! ( "line-{idx}-{long_line}\n " ) )
444425 . collect ( ) ;
445426
446- let truncated = truncate :: truncate_with_line_bytes_budget ( & content, EXEC_FORMAT_MAX_BYTES ) ;
427+ let truncated = truncate_exec_output ( & content) ;
447428
448- assert ! (
449- truncated. contains( "[... omitted 42 of 298 lines ...]" ) ,
450- "expected omitted marker when line count exceeds limit: {truncated}"
451- ) ;
452- assert ! (
453- !truncated. contains( "byte limit" ) ,
454- "line omission marker should take precedence over byte marker: {truncated}"
455- ) ;
429+ assert_truncated_message_matches ( & truncated, "line-0-" , 17_423 ) ;
456430}
457431
458432//TODO(aibrahim): run CI in release mode.
0 commit comments