From 1c5a7cd1ca2f10a0b54f84a3eb987c96aa27be67 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 18 Aug 2025 13:09:13 -0700 Subject: [PATCH] introduce variable reasoning effort --- codex-rs/core/src/client.rs | 1 + codex-rs/protocol/src/config_types.rs | 2 - codex-rs/tui/src/app.rs | 2 +- codex-rs/tui/src/bottom_pane/choice_popup.rs | 93 +++++++++++++++++++ codex-rs/tui/src/bottom_pane/command_popup.rs | 2 +- codex-rs/tui/src/slash_command.rs | 7 +- 6 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 codex-rs/tui/src/bottom_pane/choice_popup.rs diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 86a711e436..17f586943a 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -153,6 +153,7 @@ impl ModelClient { self.effort, self.summary, ); + trace!("reasoning: {:?}", reasoning); // Request encrypted COT if we are not storing responses, // otherwise reasoning items will be referenced by ID diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 7b205e662f..777d38e0ab 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -13,8 +13,6 @@ pub enum ReasoningEffort { #[default] Medium, High, - /// Option to disable reasoning. - None, } /// A summary of the reasoning performed by the model. This can be useful for diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 95c80cb3c7..6c4a7bd17f 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -416,7 +416,7 @@ impl App<'_> { widget.add_status_output(); } } - SlashCommand::Effort => { + SlashCommand::ReasoningEffort => { if self.config.model_family.supports_reasoning_summaries { if let AppState::Chat { widget } = &mut self.app_state { widget.open_reasoning_effort_popup(); diff --git a/codex-rs/tui/src/bottom_pane/choice_popup.rs b/codex-rs/tui/src/bottom_pane/choice_popup.rs new file mode 100644 index 0000000000..b18e33de6b --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/choice_popup.rs @@ -0,0 +1,93 @@ +use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig; +use ratatui::buffer::Buffer; +use ratatui::layout::Rect; +use ratatui::widgets::WidgetRef; + +use super::popup_consts::MAX_POPUP_ROWS; +use super::scroll_state::ScrollState; +use super::selection_popup_common::GenericDisplayRow; +use super::selection_popup_common::render_rows; +use strum::IntoEnumIterator; + +/// Payload associated with a selected item in a generic choice popup. +pub(crate) enum ChoicePayload { + ReasoningEffort(ReasoningEffortConfig), +} + +pub(crate) struct ChoiceItem { + pub name: String, + pub is_current: bool, + pub description: Option, + pub payload: ChoicePayload, +} + +/// A simple reusable choice popup that displays a fixed list of items and +/// allows the user to select one using Up/Down/Enter. +pub(crate) struct ChoicePopup { + items: Vec, + state: ScrollState, +} + +impl ChoicePopup { + pub(crate) fn new_reasoning_effort(current: ReasoningEffortConfig) -> Self { + let items: Vec = ReasoningEffortConfig::iter() + .map(|v| ChoiceItem { + name: v.to_string(), + is_current: v == current, + description: None, + payload: ChoicePayload::ReasoningEffort(v), + }) + .collect(); + + let mut state = ScrollState::new(); + // Default selection to the current value when present + if let Some((idx, _)) = items.iter().enumerate().find(|(_, it)| it.is_current) { + state.selected_idx = Some(idx); + } + + Self { items, state } + } + + pub(crate) fn move_up(&mut self) { + let len = self.items.len(); + self.state.move_up_wrap(len); + self.state.ensure_visible(len, len.min(MAX_POPUP_ROWS)); + } + + pub(crate) fn move_down(&mut self) { + let len = self.items.len(); + self.state.move_down_wrap(len); + self.state.ensure_visible(len, len.min(MAX_POPUP_ROWS)); + } + + pub(crate) fn selected_payload(&self) -> Option<&ChoicePayload> { + self.state + .selected_idx + .and_then(|idx| self.items.get(idx)) + .map(|it| &it.payload) + } + + pub(crate) fn calculate_required_height(&self) -> u16 { + self.items.len().clamp(1, MAX_POPUP_ROWS) as u16 + } +} + +impl WidgetRef for &ChoicePopup { + fn render_ref(&self, area: Rect, buf: &mut Buffer) { + let rows_all: Vec = if self.items.is_empty() { + Vec::new() + } else { + self.items + .iter() + .map(|item| GenericDisplayRow { + name: item.name.clone(), + match_indices: None, + is_current: item.is_current, + description: item.description.clone(), + }) + .collect() + }; + + render_rows(area, buf, &rows_all, &self.state, MAX_POPUP_ROWS); + } +} diff --git a/codex-rs/tui/src/bottom_pane/command_popup.rs b/codex-rs/tui/src/bottom_pane/command_popup.rs index 4ebe4c4c65..5450922b6a 100644 --- a/codex-rs/tui/src/bottom_pane/command_popup.rs +++ b/codex-rs/tui/src/bottom_pane/command_popup.rs @@ -30,7 +30,7 @@ impl CommandPopup { pub(crate) fn filter_for_capabilities(&mut self, show_reasoning_commands: bool) { if !show_reasoning_commands { self.all_commands - .retain(|(_, c)| !matches!(c, SlashCommand::Effort)); + .retain(|(_, c)| !matches!(c, SlashCommand::ReasoningEffort)); } } diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index fdfef7b0fe..6615a9bc78 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -17,9 +17,8 @@ pub enum SlashCommand { Compact, Diff, Mention, + ReasoningEffort, Status, - /// Choose model reasoning effort (only shown when supported by model family). - Effort, Logout, Quit, #[cfg(debug_assertions)] @@ -34,10 +33,12 @@ impl SlashCommand { SlashCommand::Init => "create an AGENTS.md file with instructions for Codex", SlashCommand::Compact => "summarize conversation to prevent hitting the context limit", SlashCommand::Quit => "exit Codex", + SlashCommand::ReasoningEffort => { + "choose model reasoning effort (low/medium/high/minimal)" + } SlashCommand::Diff => "show git diff (including untracked files)", SlashCommand::Mention => "mention a file", SlashCommand::Status => "show current session configuration and token usage", - SlashCommand::Effort => "choose model reasoning effort (low/medium/high/minimal/none)", SlashCommand::Logout => "log out of Codex", #[cfg(debug_assertions)] SlashCommand::TestApproval => "test approval request",