From e80bdd4769336d5cc4fa04fdfd42f06b6ddeb5c9 Mon Sep 17 00:00:00 2001 From: starr-openai Date: Tue, 7 Apr 2026 13:45:10 -0700 Subject: [PATCH] Collapse /context assistant and developer rows Co-authored-by: Codex --- .../src/context_manager/context_breakdown.rs | 90 +++++++++++++++++-- codex-rs/tui/src/context_window.rs | 18 ++++ ...ders_collapsed_assistant_messages_row.snap | 16 ++++ 3 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 codex-rs/tui/src/snapshots/codex_tui__context_window__tests__renders_collapsed_assistant_messages_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 d7b42f4c66..9203b2cd68 100644 --- a/codex-rs/core/src/context_manager/context_breakdown.rs +++ b/codex-rs/core/src/context_manager/context_breakdown.rs @@ -210,8 +210,12 @@ impl BreakdownAccumulator { return; } let verbose = self.verbose; - if !verbose && label.starts_with("Tool call: ") { - label = "Tool calls".to_string(); + if !verbose { + if label.starts_with("Tool call: ") { + label = "Tool calls".to_string(); + } else if label.starts_with("Assistant message") { + label = "Assistant messages".to_string(); + } } let section = self .section_accumulator(section) @@ -366,7 +370,7 @@ fn classify_developer_text(text: &str) -> (ContextSectionKind, String) { if starts_with_tag(trimmed, COLLABORATION_MODE_OPEN_TAG) { return ( ContextSectionKind::BuiltIn, - "Collaboration mode instructions".to_string(), + "Developer instructions".to_string(), ); } if starts_with_tag(trimmed, "") { @@ -394,7 +398,7 @@ fn classify_user_text(text: &str, phase: Option<&MessagePhase>) -> (ContextSecti if ENVIRONMENT_CONTEXT_FRAGMENT.matches_text(text) { return ( ContextSectionKind::BuiltIn, - "Environment context".to_string(), + "Developer instructions".to_string(), ); } if USER_SHELL_COMMAND_FRAGMENT.matches_text(text) { @@ -668,7 +672,7 @@ mod tests { vec![ "AGENTS.md instructions for /repo".to_string(), "Base instructions".to_string(), - "Environment context".to_string(), + "Developer instructions".to_string(), "Skill instructions".to_string(), "Reasoning".to_string(), "Skill: notify (/tmp/skills/notify/SKILL.md)".to_string(), @@ -810,4 +814,80 @@ mod tests { conversation.tokens ); } + + #[test] + fn non_verbose_breakdown_merges_assistant_messages_into_one_row() { + let breakdown = build_context_window_breakdown( + &[ + ResponseItem::Message { + id: None, + role: "assistant".to_string(), + content: vec![ContentItem::OutputText { + text: "thinking".to_string(), + }], + end_turn: None, + phase: Some(MessagePhase::Commentary), + }, + ResponseItem::Message { + id: None, + role: "assistant".to_string(), + content: vec![ContentItem::OutputText { + text: "answer".to_string(), + }], + end_turn: Some(true), + phase: Some(MessagePhase::FinalAnswer), + }, + ], + &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!["Assistant messages".to_string()] + ); + } + + #[test] + fn collaboration_and_environment_context_are_grouped_under_developer_instructions() { + let breakdown = build_context_window_breakdown( + &[ + developer_text("default"), + user_text("\n /repo\n"), + ], + &BaseInstructions { + text: String::new(), + }, + /*model_context_window*/ None, + /*verbose*/ false, + ); + + let built_in = breakdown + .sections + .iter() + .find(|section| section.label == "Built-in") + .expect("built-in section"); + + assert_eq!( + built_in + .details + .iter() + .map(|detail| detail.label.clone()) + .collect::>(), + vec!["Developer instructions".to_string()] + ); + } } diff --git a/codex-rs/tui/src/context_window.rs b/codex-rs/tui/src/context_window.rs index b2cd7491ee..1f3e5a9eac 100644 --- a/codex-rs/tui/src/context_window.rs +++ b/codex-rs/tui/src/context_window.rs @@ -445,6 +445,24 @@ mod tests { assert_snapshot!(render_lines(&cell.display_lines(/*width*/ 80))); } + #[test] + fn renders_collapsed_assistant_messages_row() { + let cell = new_context_window_output(&ThreadContextWindowBreakdown { + model_context_window: Some(200_000), + total_tokens: 6_000, + sections: vec![ThreadContextWindowSection { + label: "Conversation".to_string(), + tokens: 6_000, + details: vec![ThreadContextWindowDetail { + label: "Assistant messages".to_string(), + tokens: 6_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_assistant_messages_row.snap b/codex-rs/tui/src/snapshots/codex_tui__context_window__tests__renders_collapsed_assistant_messages_row.snap new file mode 100644 index 0000000000..0c51665c3e --- /dev/null +++ b/codex-rs/tui/src/snapshots/codex_tui__context_window__tests__renders_collapsed_assistant_messages_row.snap @@ -0,0 +1,16 @@ +--- +source: tui/src/context_window.rs +assertion_line: 445 +expression: render_lines(&cell.display_lines(80)) +--- +/context + +Context map + ~6,000 tokens used of ~200,000 tokens (3.0%), ~194,000 tokens remaining. + window▕█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▏ + used ▕████████████████████████████████████████████▏ + + ■ Conversation 100.0% + + ■ Conversation ~6,000 tokens 100.0% of used + ██████████ Assistant messages (~6,000 tokens)