mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Warn clearly when code mode output is truncated (#28467)
## Summary - make `formatted_truncate_text` prepend `Warning: truncated output (original token count: N)` above the existing `Total output lines` header - update direct formatter, unified-exec, user-shell, and code-mode expectations - add core unit coverage that runs in Bazel without requiring the skipped V8-backed code-mode integration suite ## Validation - `cargo test -p codex-utils-output-truncation -- --nocapture` (17 passed) - `cargo test -p codex-core --lib truncated_text_output_starts_with_warning -- --nocapture` - `cargo test -p codex-core --test all clamps_model_requested_max_output_tokens_to_policy -- --nocapture` (2 passed) - `cargo test -p codex-core --test all unified_exec_formats_large_output_summary -- --nocapture` - `cargo test -p codex-core --test all user_shell_command_output_is_truncated_in_history -- --nocapture` - Bazel CI exercises the shared formatter and downstream integration expectations
This commit is contained in:
committed by
GitHub
Unverified
parent
3ded846488
commit
952656356a
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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(())
|
||||
@@ -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….*
|
||||
|
||||
@@ -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)?"#,
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
}
|
||||
|
||||
pub fn truncate_text(content: &str, policy: TruncationPolicy) -> String {
|
||||
@@ -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),
|
||||
}];
|
||||
@@ -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(
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
@@ -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));
|
||||
@@ -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(),
|
||||
@@ -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(),
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user