From c025309d3227c3c94c52bc6094ac4953946ce218 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 18 Nov 2025 11:19:38 -0800 Subject: [PATCH] tests --- codex-rs/app-server/src/models.rs | 4 +- codex-rs/common/src/model_presets.rs | 285 +++++++++--------- codex-rs/protocol/src/config_types.rs | 13 + codex-rs/tui/src/app.rs | 14 +- codex-rs/tui/src/chatwidget.rs | 28 +- ...ests__model_reasoning_selection_popup.snap | 12 +- codex-rs/tui/src/chatwidget/tests.rs | 10 +- codex-rs/tui/src/history_cell.rs | 10 +- 8 files changed, 180 insertions(+), 196 deletions(-) diff --git a/codex-rs/app-server/src/models.rs b/codex-rs/app-server/src/models.rs index d03795c2d4..d8ea3eaf98 100644 --- a/codex-rs/app-server/src/models.rs +++ b/codex-rs/app-server/src/models.rs @@ -19,7 +19,7 @@ fn model_from_preset(preset: ModelPreset) -> Model { display_name: preset.display_name.to_string(), description: preset.description.to_string(), supported_reasoning_efforts: reasoning_efforts_from_preset( - preset.supported_reasoning_efforts, + &preset.supported_reasoning_efforts, ), default_reasoning_effort: preset.default_reasoning_effort, is_default: preset.is_default, @@ -27,7 +27,7 @@ fn model_from_preset(preset: ModelPreset) -> Model { } fn reasoning_efforts_from_preset( - efforts: &'static [ReasoningEffortPreset], + efforts: &[ReasoningEffortPreset], ) -> Vec { efforts .iter() diff --git a/codex-rs/common/src/model_presets.rs b/codex-rs/common/src/model_presets.rs index ee71e8e254..ac4380e50f 100644 --- a/codex-rs/common/src/model_presets.rs +++ b/codex-rs/common/src/model_presets.rs @@ -11,6 +11,38 @@ pub struct ReasoningEffortPreset { pub effort: ReasoningEffort, /// Short human description shown next to the effort in UIs. pub description: &'static str, + /// Optional friendly label shown in featured pickers. + pub label: Option<&'static str>, +} + +impl ReasoningEffortPreset { + pub const fn new( + effort: ReasoningEffort, + description: &'static str, + label: Option<&'static str>, + ) -> Self { + Self { + effort, + description, + label, + } + } + + pub const fn with_label( + effort: ReasoningEffort, + description: &'static str, + label: &'static str, + ) -> Self { + Self { + effort, + description, + label: Some(label), + } + } + + pub fn label(&self) -> &'static str { + self.label.unwrap_or_else(|| self.effort.label()) + } } #[derive(Debug, Clone)] @@ -33,7 +65,7 @@ pub struct ModelPreset { /// Reasoning effort applied when none is explicitly chosen. pub default_reasoning_effort: ReasoningEffort, /// Supported reasoning effort options. - pub supported_reasoning_efforts: &'static [ReasoningEffortPreset], + pub supported_reasoning_efforts: Vec, /// Whether this is the default model for new users. pub is_default: bool, /// recommended upgrade model @@ -48,19 +80,18 @@ static PRESETS: Lazy> = Lazy::new(|| { display_name: "codex-auto", description: "Automatically chooses the best Codex model configuration for your task.", default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ - ReasoningEffortPreset { - effort: ReasoningEffort::Low, - description: "Works faster", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::Medium, - description: "Balances speed with intelligence", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::High, - description: "Works longer for harder tasks", - }, + supported_reasoning_efforts: vec![ + ReasoningEffortPreset::with_label(ReasoningEffort::Low, "Works faster", "Fast"), + ReasoningEffortPreset::with_label( + ReasoningEffort::Medium, + "Balances speed with intelligence", + "Balanced", + ), + ReasoningEffortPreset::with_label( + ReasoningEffort::High, + "Works longer for harder tasks", + "Thorough", + ), ], is_default: true, upgrade: None, @@ -71,19 +102,22 @@ static PRESETS: Lazy> = Lazy::new(|| { display_name: "gpt-5.1-codex", description: "Optimized for codex.", default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ - ReasoningEffortPreset { - effort: ReasoningEffort::Low, - description: "Fastest responses with limited reasoning", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", - }, + supported_reasoning_efforts: vec![ + ReasoningEffortPreset::new( + ReasoningEffort::Low, + "Fastest responses with limited reasoning", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::Medium, + "Dynamically adjusts reasoning based on the task", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + None, + ), ], is_default: false, upgrade: None, @@ -94,15 +128,17 @@ static PRESETS: Lazy> = Lazy::new(|| { display_name: "gpt-5.1-codex-mini", description: "Optimized for codex. Cheaper, faster, but less capable.", default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ - ReasoningEffortPreset { - effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", - }, + supported_reasoning_efforts: vec![ + ReasoningEffortPreset::new( + ReasoningEffort::Medium, + "Dynamically adjusts reasoning based on the task", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + None, + ), ], is_default: false, upgrade: None, @@ -113,19 +149,22 @@ static PRESETS: Lazy> = Lazy::new(|| { display_name: "gpt-5.1", description: "Broad world knowledge with strong general reasoning.", default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ - ReasoningEffortPreset { - effort: ReasoningEffort::Low, - description: "Balances speed with some reasoning; useful for straightforward queries and short explanations", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::Medium, - description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", - }, + supported_reasoning_efforts: vec![ + ReasoningEffortPreset::new( + ReasoningEffort::Low, + "Balances speed with some reasoning; useful for straightforward queries and short explanations", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::Medium, + "Provides a solid balance of reasoning depth and latency for general-purpose tasks", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + None, + ), ], is_default: false, upgrade: None, @@ -137,19 +176,22 @@ static PRESETS: Lazy> = Lazy::new(|| { display_name: "gpt-5-codex", description: "Optimized for codex.", default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ - ReasoningEffortPreset { - effort: ReasoningEffort::Low, - description: "Fastest responses with limited reasoning", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", - }, + supported_reasoning_efforts: vec![ + ReasoningEffortPreset::new( + ReasoningEffort::Low, + "Fastest responses with limited reasoning", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::Medium, + "Dynamically adjusts reasoning based on the task", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + None, + ), ], is_default: false, upgrade: Some(ModelUpgrade { @@ -163,15 +205,17 @@ static PRESETS: Lazy> = Lazy::new(|| { display_name: "gpt-5-codex-mini", description: "Optimized for codex. Cheaper, faster, but less capable.", default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ - ReasoningEffortPreset { - effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", - }, + supported_reasoning_efforts: vec![ + ReasoningEffortPreset::new( + ReasoningEffort::Medium, + "Dynamically adjusts reasoning based on the task", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + None, + ), ], is_default: false, upgrade: Some(ModelUpgrade { @@ -185,23 +229,27 @@ static PRESETS: Lazy> = Lazy::new(|| { display_name: "gpt-5", description: "Broad world knowledge with strong general reasoning.", default_reasoning_effort: ReasoningEffort::Medium, - supported_reasoning_efforts: &[ - ReasoningEffortPreset { - effort: ReasoningEffort::Minimal, - description: "Fastest responses with little reasoning", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::Low, - description: "Balances speed with some reasoning; useful for straightforward queries and short explanations", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::Medium, - description: "Provides a solid balance of reasoning depth and latency for general-purpose tasks", - }, - ReasoningEffortPreset { - effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", - }, + supported_reasoning_efforts: vec![ + ReasoningEffortPreset::new( + ReasoningEffort::Minimal, + "Fastest responses with little reasoning", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::Low, + "Balances speed with some reasoning; useful for straightforward queries and short explanations", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::Medium, + "Provides a solid balance of reasoning depth and latency for general-purpose tasks", + None, + ), + ReasoningEffortPreset::new( + ReasoningEffort::High, + "Maximizes reasoning depth for complex or ambiguous problems", + None, + ), ], is_default: false, upgrade: Some(ModelUpgrade { @@ -228,44 +276,15 @@ pub fn all_model_presets() -> &'static Vec { &PRESETS } -/// Label metadata for featured pickers (e.g., codex-auto variants). -#[derive(Debug, Clone, Copy)] -struct FeaturedEffortLabel { - model: &'static str, - effort: ReasoningEffort, - label: &'static str, +impl ModelPreset { + pub fn reasoning_effort_label(&self, effort: ReasoningEffort) -> &'static str { + self.supported_reasoning_efforts + .iter() + .find(|option| option.effort == effort) + .map(|option| option.label()) + .unwrap_or_else(|| effort.label()) + } } - -static FEATURED_EFFORT_LABELS: &[FeaturedEffortLabel] = &[ - FeaturedEffortLabel { - model: "codex-auto", - effort: ReasoningEffort::Low, - label: "Fast", - }, - FeaturedEffortLabel { - model: "codex-auto", - effort: ReasoningEffort::Medium, - label: "Balanced", - }, - FeaturedEffortLabel { - model: "codex-auto", - effort: ReasoningEffort::High, - label: "Thorough", - }, -]; - -/// Returns a friendly label for the given model/effort combination when available. -pub fn effort_label_for_model( - model: &str, - effort: Option, -) -> Option<&'static str> { - let effort = effort?; - FEATURED_EFFORT_LABELS - .iter() - .find(|entry| entry.model == model && entry.effort == effort) - .map(|entry| entry.label) -} - #[cfg(test)] mod tests { use super::*; @@ -275,20 +294,4 @@ mod tests { let default_models = PRESETS.iter().filter(|preset| preset.is_default).count(); assert!(default_models == 1); } - - #[test] - fn codex_auto_featured_options_define_labels() { - assert_eq!( - effort_label_for_model("codex-auto", Some(ReasoningEffort::Low)), - Some("Fast") - ); - assert_eq!( - effort_label_for_model("codex-auto", Some(ReasoningEffort::Medium)), - Some("Balanced") - ); - assert_eq!( - effort_label_for_model("codex-auto", Some(ReasoningEffort::High)), - Some("Thorough") - ); - } } diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 3881bde672..b58f5a0cb5 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -32,6 +32,19 @@ pub enum ReasoningEffort { High, } +impl ReasoningEffort { + /// Friendly label that can be shown in UIs. + pub fn label(self) -> &'static str { + match self { + ReasoningEffort::None => "None", + ReasoningEffort::Minimal => "Minimal", + ReasoningEffort::Low => "Fast", + ReasoningEffort::Medium => "Balanced", + ReasoningEffort::High => "Thorough", + } + } +} + /// A summary of the reasoning performed by the model. This can be useful for /// debugging and understanding the model's reasoning process. /// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 6c62d2d4e2..c898f408b4 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -19,7 +19,6 @@ use crate::update_action::UpdateAction; use codex_ansi_escape::ansi_escape_line; use codex_common::model_presets::ModelUpgrade; use codex_common::model_presets::all_model_presets; -use codex_common::model_presets::effort_label_for_model; use codex_core::AuthManager; use codex_core::ConversationManager; use codex_core::config::Config; @@ -95,13 +94,10 @@ fn should_show_model_migration_prompt( } fn format_model_change_target(model: &str, effort: Option) -> String { - if let Some(label) = effort_label_for_model(model, effort) { - format!("{model} ({label})") + if let Some(effort) = effort { + format!("{model} ({})", effort.label()) } else { - let suffix = effort - .map(|eff| format!(" with {eff} reasoning")) - .unwrap_or_else(|| " with default reasoning".to_string()); - format!("{model}{suffix}") + format!("{model} with default reasoning") } } @@ -1061,10 +1057,10 @@ mod tests { } #[test] - fn format_model_change_target_falls_back_to_reasoning_text() { + fn format_model_change_target_uses_effort_label() { let formatted = super::format_model_change_target("gpt-5.1-codex", Some(ReasoningEffortConfig::High)); - assert_eq!(formatted, "gpt-5.1-codex with high reasoning"); + assert_eq!(formatted, "gpt-5.1-codex (Thorough)"); } #[test] diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 2752bec001..e744aba645 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -122,7 +122,6 @@ use codex_common::approval_presets::ApprovalPreset; use codex_common::approval_presets::builtin_approval_presets; use codex_common::model_presets::ModelPreset; use codex_common::model_presets::builtin_model_presets; -use codex_common::model_presets::effort_label_for_model; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::ConversationManager; @@ -2111,11 +2110,8 @@ impl ChatWidget { }); })]; - let label = effort_label_for_model(&model_slug, Some(effort)) - .map(str::to_string) - .unwrap_or_else(|| Self::featured_option_label(effort)); + let mut name = effort.label().to_string(); let is_current_option = current_effort == Some(effort); - let mut name = label.to_string(); if effort == default_effort && !is_current_option { name.push_str(" (default)"); } @@ -2136,21 +2132,6 @@ impl ChatWidget { items } - fn featured_option_label(effort: ReasoningEffortConfig) -> String { - match effort { - ReasoningEffortConfig::Low => "Fast".to_string(), - ReasoningEffortConfig::Medium => "Balanced".to_string(), - ReasoningEffortConfig::High => "Thorough".to_string(), - _ => { - let mut text = effort.to_string(); - if let Some(first) = text.get_mut(0..1) { - first.make_ascii_uppercase(); - } - text - } - } - } - /// Open a popup to choose the reasoning effort (stage 2) for the given model. pub(crate) fn open_reasoning_popup( &mut self, @@ -2158,7 +2139,7 @@ impl ChatWidget { preferred_effort: Option, ) { let default_effort: ReasoningEffortConfig = preset.default_reasoning_effort; - let supported = preset.supported_reasoning_efforts; + let supported = &preset.supported_reasoning_efforts; struct EffortChoice { stored: Option, @@ -2211,10 +2192,7 @@ impl ChatWidget { let mut items: Vec = Vec::new(); for choice in choices.iter() { let effort = choice.display; - let mut effort_label = effort.to_string(); - if let Some(first) = effort_label.get_mut(0..1) { - first.make_ascii_uppercase(); - } + let mut effort_label = effort.label().to_string(); let is_current_choice = is_current_model && choice.stored == highlight_choice; if choice.stored == default_choice && !is_current_choice { effort_label.push_str(" (default)"); diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__model_reasoning_selection_popup.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__model_reasoning_selection_popup.snap index 060d1f82a9..9a9f78816b 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__model_reasoning_selection_popup.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__model_reasoning_selection_popup.snap @@ -4,11 +4,11 @@ expression: popup --- Select Reasoning Level for gpt-5.1-codex - 1. Low Fastest responses with limited reasoning - 2. Medium (default) Dynamically adjusts reasoning based on the task -› 3. High (current) Maximizes reasoning depth for complex or ambiguous - problems - ⚠ High reasoning effort can quickly consume Plus plan - rate limits. + 1. Fast Fastest responses with limited reasoning + 2. Balanced (default) Dynamically adjusts reasoning based on the task +› 3. Thorough (current) Maximizes reasoning depth for complex or ambiguous + problems + ⚠ High reasoning effort can quickly consume Plus plan + rate limits. Press enter to confirm or esc to go back diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index b4b540a542..8eb47ea1a4 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1573,17 +1573,17 @@ fn reasoning_popup_hides_default_label_when_option_is_current() { fn single_reasoning_option_skips_selection() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(); - static SINGLE_EFFORT: [ReasoningEffortPreset; 1] = [ReasoningEffortPreset { - effort: ReasoningEffortConfig::High, - description: "Maximizes reasoning depth for complex or ambiguous problems", - }]; let preset = ModelPreset { id: "model-with-single-reasoning", model: "model-with-single-reasoning", display_name: "model-with-single-reasoning", description: "", default_reasoning_effort: ReasoningEffortConfig::High, - supported_reasoning_efforts: &SINGLE_EFFORT, + supported_reasoning_efforts: vec![ReasoningEffortPreset::new( + ReasoningEffortConfig::High, + "Maximizes reasoning depth for complex or ambiguous problems", + None, + )], is_default: false, upgrade: None, }; diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index b026170f70..35465a3799 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -707,13 +707,7 @@ impl SessionHeaderHistoryCell { } fn reasoning_label(&self) -> Option<&'static str> { - self.reasoning_effort.map(|effort| match effort { - ReasoningEffortConfig::Minimal => "minimal", - ReasoningEffortConfig::Low => "low", - ReasoningEffortConfig::Medium => "medium", - ReasoningEffortConfig::High => "high", - ReasoningEffortConfig::None => "none", - }) + self.reasoning_effort.map(|effort| effort.label()) } } @@ -1824,7 +1818,7 @@ mod tests { .find(|line| line.contains("model:")) .expect("model line"); - assert!(model_line.contains("gpt-4o high")); + assert!(model_line.contains("gpt-4o Thorough")); assert!(model_line.contains("/model to change")); }