Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions codex-rs/core/src/tools/code_mode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,10 @@ fn build_freeform_tool_payload(
#[cfg(test)]
mod tests {
use super::build_nested_tool_payload;
use super::truncate_code_mode_result;
use crate::tools::context::ToolPayload;
use codex_code_mode::CodeModeToolKind;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_tools::ToolName;
use serde_json::json;

Expand Down Expand Up @@ -359,4 +361,23 @@ mod tests {
other => panic!("expected freeform payload, got {other:?}"),
}
}

#[test]
fn truncated_text_output_starts_with_warning() {
let items = vec![FunctionCallOutputContentItem::InputText {
text: "0123456789012345678901234567890123456789".to_string(),
}];

assert_eq!(
truncate_code_mode_result(items, Some(5)),
vec![FunctionCallOutputContentItem::InputText {
text: concat!(
"Warning: truncated output (original token count: 10)\n",
"Total output lines: 1\n\n",
"0123456789…5 tokens truncated…0123456789"
)
.to_string(),
}]
);
}
}
5 changes: 3 additions & 2 deletions codex-rs/core/tests/suite/code_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,7 @@ text(result.output);
/*index*/ 1
),
format!(
"Total output lines: 1\n\n{}…2500 tokens truncated…{}",
"Warning: truncated output (original token count: 22500)\nTotal output lines: 1\n\n{}…2500 tokens truncated…{}",
"A".repeat(40_000),
"A".repeat(40_000)
)
Expand Down Expand Up @@ -1176,7 +1176,7 @@ text(result.output);
&custom_tool_output_items(&second_mock.single_request(), "call-1"),
/*index*/ 1
),
"Total output lines: 1\n\n0123456789…5 tokens truncated…0123456789"
"Warning: truncated output (original token count: 10)\nTotal output lines: 1\n\n0123456789…5 tokens truncated…0123456789"
);

Ok(())
Expand Down Expand Up @@ -2389,6 +2389,7 @@ text("token one token two token three token four token five token six token seve
);
let expected_pattern = r#"(?sx)
\A
Warning:\ truncated\ output\ \(original\ token\ count:\ \d+\)\n
Total\ output\ lines:\ 1\n
\n
.*…\d+\ tokens\ truncated….*
Expand Down
8 changes: 4 additions & 4 deletions codex-rs/core/tests/suite/unified_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ fn parse_unified_exec_output(raw: &str) -> Result<ParsedUnifiedExecOutput> {
static OUTPUT_REGEX: OnceLock<Regex> = OnceLock::new();
let regex = OUTPUT_REGEX.get_or_init(|| {
Regex::new(concat!(
r#"(?s)^(?:Total output lines: \d+\n\n)?"#,
r#"(?s)^(?:Warning: truncated output \(original token count: \d+\)\n)?(?:Total output lines: \d+\n\n)?"#,
r#"(?:Chunk ID: (?P<chunk_id>[^\n]+)\n)?"#,
r#"Wall time: (?P<wall_time>-?\d+(?:\.\d+)?) seconds\n"#,
r#"(?:Process exited with code (?P<exit_code>-?\d+)\n)?"#,
Expand Down Expand Up @@ -1554,7 +1554,7 @@ async fn exec_command_clamps_model_requested_max_output_tokens_to_policy() -> Re
assert_eq!(output.original_token_count, Some(8_991));
let output_text = output.output.replace("\r\n", "\n");
assert_regex_match(
r"^Total output lines: 999\n\nEXEC-LINE-0001 x{20}\nEXEC-LINE-0002 x{20}\nEXEC-LINE-0003 x{13}…8941 tokens truncated…E-0997 x{20}\nEXEC-LINE-0998 x{20}\nEXEC-LINE-0999 x{20}\n$",
r"^Warning: truncated output \(original token count: 8991\)\nTotal output lines: 999\n\nEXEC-LINE-0001 x{20}\nEXEC-LINE-0002 x{20}\nEXEC-LINE-0003 x{13}…8941 tokens truncated…E-0997 x{20}\nEXEC-LINE-0998 x{20}\nEXEC-LINE-0999 x{20}\n$",
&output_text,
);

Expand Down Expand Up @@ -1645,7 +1645,7 @@ async fn write_stdin_clamps_model_requested_max_output_tokens_to_policy() -> Res
assert_eq!(stdin_output.original_token_count, Some(9_492));
let stdin_output_text = stdin_output.output.replace("\r\n", "\n");
assert_regex_match(
r"^Total output lines: 1000\n\ngo\nSTDIN-LINE-0001 y{20}\nSTDIN-LINE-0002 y{20}\nSTDIN-LINE-0003 yyyy…9442 tokens truncated…7 y{20}\nSTDIN-LINE-0998 y{20}\nSTDIN-LINE-0999 y{20}\n$",
r"^Warning: truncated output \(original token count: 9492\)\nTotal output lines: 1000\n\ngo\nSTDIN-LINE-0001 y{20}\nSTDIN-LINE-0002 y{20}\nSTDIN-LINE-0003 yyyy…9442 tokens truncated…7 y{20}\nSTDIN-LINE-0998 y{20}\nSTDIN-LINE-0999 y{20}\n$",
&stdin_output_text,
);

Expand Down Expand Up @@ -2960,7 +2960,7 @@ PY
let large_output = outputs.get(call_id).expect("missing large output summary");

let output_text = large_output.output.replace("\r\n", "\n");
let truncated_pattern = r"(?s)^Total output lines: \d+\n\n(token token \n){5,}.*…\d+ tokens truncated….*(token token \n){5,}$";
let truncated_pattern = r"(?s)^Warning: truncated output \(original token count: \d+\)\nTotal output lines: \d+\n\n(token token \n){5,}.*…\d+ tokens truncated….*(token token \n){5,}$";
assert_regex_match(truncated_pattern, &output_text);

let original_tokens = large_output
Expand Down
5 changes: 3 additions & 2 deletions codex-rs/core/tests/suite/user_shell_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,9 @@ async fn user_shell_command_output_is_truncated_in_history() -> anyhow::Result<(

let head = (1..=69).map(|i| format!("{i}\n")).collect::<String>();
let tail = (352..=400).map(|i| format!("{i}\n")).collect::<String>();
let truncated_body =
format!("Total output lines: 400\n\n{head}70…273 tokens truncated…351\n{tail}");
let truncated_body = format!(
"Warning: truncated output (original token count: 373)\nTotal output lines: 400\n\n{head}70…273 tokens truncated…351\n{tail}"
);
let escaped_command = escape(&command);
let escaped_truncated_body = escape(&truncated_body);
let expected_pattern = format!(
Expand Down
8 changes: 6 additions & 2 deletions codex-rs/utils/output-truncation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ pub fn formatted_truncate_text(content: &str, policy: TruncationPolicy) -> Strin
return content.to_string();
}

let original_token_count = approx_token_count(content);
let total_lines = content.lines().count();
let result = truncate_text(content, policy);
format!("Total output lines: {total_lines}\n\n{result}")
format!(
"Warning: truncated output (original token count: {original_token_count})\nTotal output lines: {total_lines}\n\n{result}"
Comment thread
aibrahim-oai marked this conversation as resolved.
Outdated
)
}

pub fn truncate_text(content: &str, policy: TruncationPolicy) -> String {
Expand Down Expand Up @@ -55,6 +58,7 @@ pub fn formatted_truncate_text_content_items_with_policy(
return (items.to_vec(), None);
}

let original_token_count = approx_token_count(&combined);
let mut out = vec![FunctionCallOutputContentItem::InputText {
text: formatted_truncate_text(&combined, policy),
}];
Expand All @@ -73,7 +77,7 @@ pub fn formatted_truncate_text_content_items_with_policy(
FunctionCallOutputContentItem::InputText { .. } => None,
}));

(out, Some(approx_token_count(&combined)))
(out, Some(original_token_count))
}

pub fn truncate_function_output_items_with_policy(
Expand Down
20 changes: 10 additions & 10 deletions codex-rs/utils/output-truncation/src/truncate_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn truncate_bytes_less_than_placeholder_returns_placeholder() {
let content = "example output";

assert_eq!(
"Total output lines: 1\n\n…13 chars truncated…t",
"Warning: truncated output (original token count: 4)\nTotal output lines: 1\n\n…13 chars truncated…t",
formatted_truncate_text(content, TruncationPolicy::Bytes(1)),
);
}
Expand All @@ -24,7 +24,7 @@ fn truncate_tokens_less_than_placeholder_returns_placeholder() {
let content = "example output";

assert_eq!(
"Total output lines: 1\n\nex…3 tokens truncated…ut",
"Warning: truncated output (original token count: 4)\nTotal output lines: 1\n\nex…3 tokens truncated…ut",
formatted_truncate_text(content, TruncationPolicy::Tokens(1)),
);
}
Expand Down Expand Up @@ -54,7 +54,7 @@ fn truncate_tokens_over_limit_returns_truncated() {
let content = "this is an example of a long output that should be truncated";

assert_eq!(
"Total output lines: 1\n\nthis is an…10 tokens truncated… truncated",
"Warning: truncated output (original token count: 15)\nTotal output lines: 1\n\nthis is an…10 tokens truncated… truncated",
formatted_truncate_text(content, TruncationPolicy::Tokens(5)),
);
}
Expand All @@ -64,7 +64,7 @@ fn truncate_bytes_over_limit_returns_truncated() {
let content = "this is an example of a long output that should be truncated";

assert_eq!(
"Total output lines: 1\n\nthis is an exam…30 chars truncated…ld be truncated",
"Warning: truncated output (original token count: 15)\nTotal output lines: 1\n\nthis is an exam…30 chars truncated…ld be truncated",
formatted_truncate_text(content, TruncationPolicy::Bytes(30)),
);
}
Expand All @@ -75,7 +75,7 @@ fn truncate_bytes_reports_original_line_count_when_truncated() {
"this is an example of a long output that should be truncated\nalso some other line";

assert_eq!(
"Total output lines: 2\n\nthis is an exam…51 chars truncated…some other line",
"Warning: truncated output (original token count: 21)\nTotal output lines: 2\n\nthis is an exam…51 chars truncated…some other line",
formatted_truncate_text(content, TruncationPolicy::Bytes(30)),
);
}
Expand All @@ -86,7 +86,7 @@ fn truncate_tokens_reports_original_line_count_when_truncated() {
"this is an example of a long output that should be truncated\nalso some other line";

assert_eq!(
"Total output lines: 2\n\nthis is an example o…11 tokens truncated…also some other line",
"Warning: truncated output (original token count: 21)\nTotal output lines: 2\n\nthis is an example o…11 tokens truncated…also some other line",
formatted_truncate_text(content, TruncationPolicy::Tokens(10)),
);
}
Expand Down Expand Up @@ -201,7 +201,7 @@ fn formatted_truncate_text_content_items_with_policy_preserves_empty_leading_tex
assert_eq!(
output,
vec![FunctionCallOutputContentItem::InputText {
text: "Total output lines: 1\n\n…3 chars truncated…".to_string(),
text: "Warning: truncated output (original token count: 1)\nTotal output lines: 1\n\n…3 chars truncated…".to_string(),
}]
);
assert_eq!(original_token_count, Some(1));
Expand Down Expand Up @@ -236,7 +236,7 @@ fn formatted_truncate_text_content_items_with_policy_merges_text_and_appends_ima
output,
vec![
FunctionCallOutputContentItem::InputText {
text: "Total output lines: 3\n\nabcd…6 chars truncated…ijkl".to_string(),
text: "Warning: truncated output (original token count: 4)\nTotal output lines: 3\n\nabcd…6 chars truncated…ijkl".to_string(),
},
FunctionCallOutputContentItem::InputImage {
image_url: "img:one".to_string(),
Expand Down Expand Up @@ -269,7 +269,7 @@ fn formatted_truncate_text_content_items_with_policy_preserves_encrypted_content
output,
vec![
FunctionCallOutputContentItem::InputText {
text: "Total output lines: 1\n\na…6 chars truncated…h".to_string(),
text: "Warning: truncated output (original token count: 2)\nTotal output lines: 1\n\na…6 chars truncated…h".to_string(),
},
FunctionCallOutputContentItem::EncryptedContent {
encrypted_content: "enc_opaque".to_string(),
Expand Down Expand Up @@ -322,7 +322,7 @@ fn formatted_truncate_text_content_items_with_policy_merges_all_text_for_token_b
assert_eq!(
output,
vec![FunctionCallOutputContentItem::InputText {
text: "Total output lines: 2\n\nabcd…3 tokens truncated…mnop".to_string(),
text: "Warning: truncated output (original token count: 5)\nTotal output lines: 2\n\nabcd…3 tokens truncated…mnop".to_string(),
}]
);
assert_eq!(original_token_count, Some(5));
Expand Down
Loading