diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index 57f4775c..6a3f39b6 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -185,6 +185,8 @@ pub struct Config { /// All characters are inserted as they are received, and no buffering /// or placeholder replacement will occur for fast keypress bursts. pub disable_paste_burst: bool, + + pub use_experimental_reasoning_summary: bool, } impl Config { @@ -482,6 +484,8 @@ pub struct ConfigToml { pub experimental_use_exec_command_tool: Option, + pub use_experimental_reasoning_summary: Option, + /// The value for the `originator` header included with Responses API requests. pub responses_originator_header_internal_override: Option, @@ -807,6 +811,9 @@ impl Config { .unwrap_or(false), include_view_image_tool, disable_paste_burst: cfg.disable_paste_burst.unwrap_or(false), + use_experimental_reasoning_summary: cfg + .use_experimental_reasoning_summary + .unwrap_or(false), }; Ok(config) } @@ -1177,6 +1184,7 @@ disable_response_storage = true use_experimental_streamable_shell_tool: false, include_view_image_tool: true, disable_paste_burst: false, + use_experimental_reasoning_summary: false, }, o3_profile_config ); @@ -1235,6 +1243,7 @@ disable_response_storage = true use_experimental_streamable_shell_tool: false, include_view_image_tool: true, disable_paste_burst: false, + use_experimental_reasoning_summary: false, }; assert_eq!(expected_gpt3_profile_config, gpt3_profile_config); @@ -1308,6 +1317,7 @@ disable_response_storage = true use_experimental_streamable_shell_tool: false, include_view_image_tool: true, disable_paste_burst: false, + use_experimental_reasoning_summary: false, }; assert_eq!(expected_zdr_profile_config, zdr_profile_config); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 0e9d3dd6..f86658a8 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -191,10 +191,12 @@ impl ChatWidget { // At the end of a reasoning block, record transcript-only content. self.full_reasoning_buffer.push_str(&self.reasoning_buffer); if !self.full_reasoning_buffer.is_empty() { - self.add_to_history(history_cell::new_reasoning_block( + for cell in history_cell::new_reasoning_summary_block( self.full_reasoning_buffer.clone(), &self.config, - )); + ) { + self.add_boxed_history(cell); + } } self.reasoning_buffer.clear(); self.full_reasoning_buffer.clear(); @@ -900,16 +902,14 @@ impl ChatWidget { } fn add_to_history(&mut self, cell: impl HistoryCell + 'static) { + self.add_boxed_history(Box::new(cell)); + } + + fn add_boxed_history(&mut self, cell: Box) { if !cell.display_lines(u16::MAX).is_empty() { // Only break exec grouping if the cell renders visible lines. self.flush_active_exec_cell(); } - self.app_event_tx - .send(AppEvent::InsertHistoryCell(Box::new(cell))); - } - - fn add_boxed_history(&mut self, cell: Box) { - self.flush_active_exec_cell(); self.app_event_tx.send(AppEvent::InsertHistoryCell(cell)); } diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 1d81fade..dea72436 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -1247,6 +1247,44 @@ pub(crate) fn new_reasoning_block( TranscriptOnlyHistoryCell { lines } } +pub(crate) fn new_reasoning_summary_block( + full_reasoning_buffer: String, + config: &Config, +) -> Vec> { + if config.use_experimental_reasoning_summary { + // Experimental format is following: + // ** header ** + // + // reasoning summary + // + // So we need to strip header from reasoning summary + if let Some(open) = full_reasoning_buffer.find("**") { + let after_open = &full_reasoning_buffer[(open + 2)..]; + if let Some(close) = after_open.find("**") { + let after_close_idx = open + 2 + close + 2; + let header_buffer = full_reasoning_buffer[..after_close_idx].to_string(); + let summary_buffer = full_reasoning_buffer[after_close_idx..].to_string(); + + let mut header_lines: Vec> = Vec::new(); + header_lines.push(Line::from("Thinking".magenta().italic())); + append_markdown(&header_buffer, &mut header_lines, config); + + let mut summary_lines: Vec> = Vec::new(); + summary_lines.push(Line::from("Thinking".magenta().bold())); + append_markdown(&summary_buffer, &mut summary_lines, config); + + return vec![ + Box::new(TranscriptOnlyHistoryCell { + lines: header_lines, + }), + Box::new(AgentMessageCell::new(summary_lines, true)), + ]; + } + } + } + vec![Box::new(new_reasoning_block(full_reasoning_buffer, config))] +} + fn output_lines( output: Option<&CommandOutput>, only_err: bool,