From b25960f64d28d69721873e490ebabd778ca22856 Mon Sep 17 00:00:00 2001 From: starr-openai Date: Tue, 7 Apr 2026 13:34:06 -0700 Subject: [PATCH] Collapse /context tool calls row Co-authored-by: Codex --- .../src/context_manager/context_breakdown.rs | 55 ++++++++++++++++++- codex-rs/tui/src/context_window.rs | 18 ++++++ ...sts__renders_collapsed_tool_calls_row.snap | 16 ++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 codex-rs/tui/src/snapshots/codex_tui__context_window__tests__renders_collapsed_tool_calls_row.snap diff --git a/codex-rs/core/src/context_manager/context_breakdown.rs b/codex-rs/core/src/context_manager/context_breakdown.rs index 6ffcfa8e96..d7b42f4c66 100644 --- a/codex-rs/core/src/context_manager/context_breakdown.rs +++ b/codex-rs/core/src/context_manager/context_breakdown.rs @@ -205,11 +205,14 @@ impl BreakdownAccumulator { } } - fn add_detail(&mut self, section: ContextSectionKind, label: String, tokens: i64) { + fn add_detail(&mut self, section: ContextSectionKind, mut label: String, tokens: i64) { if tokens <= 0 { return; } let verbose = self.verbose; + if !verbose && label.starts_with("Tool call: ") { + label = "Tool calls".to_string(); + } let section = self .section_accumulator(section) .section @@ -757,4 +760,54 @@ mod tests { .sum::() ); } + + #[test] + fn non_verbose_breakdown_merges_tool_calls_into_one_row() { + let breakdown = build_context_window_breakdown( + &[ + ResponseItem::FunctionCall { + id: None, + call_id: "call-1".to_string(), + name: "read_file".to_string(), + namespace: None, + arguments: "{}".to_string(), + }, + ResponseItem::FunctionCall { + id: None, + call_id: "call-2".to_string(), + name: "list_dir".to_string(), + namespace: None, + arguments: "{}".to_string(), + }, + ], + &BaseInstructions { + text: String::new(), + }, + /*model_context_window*/ None, + /*verbose*/ false, + ); + + let conversation = breakdown + .sections + .iter() + .find(|section| section.label == "Conversation") + .expect("conversation section"); + + assert_eq!( + conversation + .details + .iter() + .map(|detail| detail.label.clone()) + .collect::>(), + vec!["Tool calls".to_string()] + ); + assert_eq!( + conversation + .details + .iter() + .map(|detail| detail.tokens) + .sum::(), + conversation.tokens + ); + } } diff --git a/codex-rs/tui/src/context_window.rs b/codex-rs/tui/src/context_window.rs index 1628d078ef..b2cd7491ee 100644 --- a/codex-rs/tui/src/context_window.rs +++ b/codex-rs/tui/src/context_window.rs @@ -427,6 +427,24 @@ mod tests { assert_snapshot!(render_lines(&cell.display_lines(/*width*/ 80))); } + #[test] + fn renders_collapsed_tool_calls_row() { + let cell = new_context_window_output(&ThreadContextWindowBreakdown { + model_context_window: Some(200_000), + total_tokens: 8_000, + sections: vec![ThreadContextWindowSection { + label: "Conversation".to_string(), + tokens: 8_000, + details: vec![ThreadContextWindowDetail { + label: "Tool calls".to_string(), + tokens: 8_000, + }], + }], + }); + + assert_snapshot!(render_lines(&cell.display_lines(/*width*/ 80))); + } + #[test] fn allocates_bar_width_by_largest_remainder() { assert_eq!(allocate_token_widths(&[5, 3, 2], 10, 7), vec![4, 2, 1]); diff --git a/codex-rs/tui/src/snapshots/codex_tui__context_window__tests__renders_collapsed_tool_calls_row.snap b/codex-rs/tui/src/snapshots/codex_tui__context_window__tests__renders_collapsed_tool_calls_row.snap new file mode 100644 index 0000000000..26fbffbb14 --- /dev/null +++ b/codex-rs/tui/src/snapshots/codex_tui__context_window__tests__renders_collapsed_tool_calls_row.snap @@ -0,0 +1,16 @@ +--- +source: tui/src/context_window.rs +assertion_line: 427 +expression: render_lines(&cell.display_lines(80)) +--- +/context + +Context map + ~8,000 tokens used of ~200,000 tokens (4.0%), ~192,000 tokens remaining. + window▕█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▏ + used ▕████████████████████████████████████████████▏ + + ■ Conversation 100.0% + + ■ Conversation ~8,000 tokens 100.0% of used + ██████████ Tool calls (~8,000 tokens)