From acc2b63dfbe8e4783ef837828ebb32bcc3af1153 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Thu, 25 Sep 2025 11:10:40 -0700 Subject: [PATCH] Fix error message (#4204) Co-authored-by: Ahmed Ibrahim --- codex-rs/core/src/error.rs | 4 +-- codex-rs/tui/src/chatwidget.rs | 40 ++++++++++++++++++++++++++-- codex-rs/tui/src/chatwidget/tests.rs | 27 ++++++++++++++----- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index 1fe7e115..c5965dae 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -156,7 +156,7 @@ impl std::fmt::Display for UsageLimitReachedError { ) } Some(PlanType::Known(KnownPlan::Free)) => { - "To use Codex with your ChatGPT plan, upgrade to Plus: https://openai.com/chatgpt/pricing." + "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing)." .to_string() } Some(PlanType::Known(KnownPlan::Pro)) @@ -306,7 +306,7 @@ mod tests { }; assert_eq!( err.to_string(), - "To use Codex with your ChatGPT plan, upgrade to Plus: https://openai.com/chatgpt/pricing." + "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing)." ); } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 26d813a8..8bd15b53 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -132,7 +132,9 @@ impl RateLimitWarningState { fn take_warnings( &mut self, secondary_used_percent: Option, + secondary_window_minutes: Option, primary_used_percent: Option, + primary_window_minutes: Option, ) -> Vec { let reached_secondary_cap = matches!(secondary_used_percent, Some(percent) if percent == 100.0); @@ -152,8 +154,11 @@ impl RateLimitWarningState { self.secondary_index += 1; } if let Some(threshold) = highest_secondary { + let limit_label = secondary_window_minutes + .map(get_limits_duration) + .unwrap_or_else(|| "weekly".to_string()); warnings.push(format!( - "Heads up, you've used over {threshold:.0}% of your weekly limit. Run /status for a breakdown." + "Heads up, you've used over {threshold:.0}% of your {limit_label} limit. Run /status for a breakdown." )); } } @@ -167,8 +172,11 @@ impl RateLimitWarningState { self.primary_index += 1; } if let Some(threshold) = highest_primary { + let limit_label = primary_window_minutes + .map(get_limits_duration) + .unwrap_or_else(|| "5h".to_string()); warnings.push(format!( - "Heads up, you've used over {threshold:.0}% of your 5h limit. Run /status for a breakdown." + "Heads up, you've used over {threshold:.0}% of your {limit_label} limit. Run /status for a breakdown." )); } } @@ -177,6 +185,26 @@ impl RateLimitWarningState { } } +fn get_limits_duration(windows_minutes: u64) -> String { + const MINUTES_PER_HOUR: u64 = 60; + const MINUTES_PER_DAY: u64 = 24 * MINUTES_PER_HOUR; + const MINUTES_PER_WEEK: u64 = 7 * MINUTES_PER_DAY; + const MINUTES_PER_MONTH: u64 = 30 * MINUTES_PER_DAY; + const ROUNDING_BIAS_MINUTES: u64 = 3; + + if windows_minutes <= MINUTES_PER_DAY.saturating_add(ROUNDING_BIAS_MINUTES) { + let adjusted = windows_minutes.saturating_add(ROUNDING_BIAS_MINUTES); + let hours = std::cmp::max(1, adjusted / MINUTES_PER_HOUR); + format!("{hours}h") + } else if windows_minutes <= MINUTES_PER_WEEK.saturating_add(ROUNDING_BIAS_MINUTES) { + "weekly".to_string() + } else if windows_minutes <= MINUTES_PER_MONTH.saturating_add(ROUNDING_BIAS_MINUTES) { + "monthly".to_string() + } else { + "annual".to_string() + } +} + /// Common initialization parameters shared by all `ChatWidget` constructors. pub(crate) struct ChatWidgetInit { pub(crate) config: Config, @@ -377,7 +405,15 @@ impl ChatWidget { .secondary .as_ref() .map(|window| window.used_percent), + snapshot + .secondary + .as_ref() + .and_then(|window| window.window_minutes), snapshot.primary.as_ref().map(|window| window.used_percent), + snapshot + .primary + .as_ref() + .and_then(|window| window.window_minutes), ); let display = history_cell::rate_limit_snapshot_display(&snapshot, Local::now()); diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 18bc515b..53ad6097 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -384,12 +384,12 @@ fn rate_limit_warnings_emit_thresholds() { let mut state = RateLimitWarningState::default(); let mut warnings: Vec = Vec::new(); - warnings.extend(state.take_warnings(Some(10.0), Some(55.0))); - warnings.extend(state.take_warnings(Some(55.0), Some(10.0))); - warnings.extend(state.take_warnings(Some(10.0), Some(80.0))); - warnings.extend(state.take_warnings(Some(80.0), Some(10.0))); - warnings.extend(state.take_warnings(Some(10.0), Some(95.0))); - warnings.extend(state.take_warnings(Some(95.0), Some(10.0))); + warnings.extend(state.take_warnings(Some(10.0), Some(10079), Some(55.0), Some(299))); + warnings.extend(state.take_warnings(Some(55.0), Some(10081), Some(10.0), Some(299))); + warnings.extend(state.take_warnings(Some(10.0), Some(10081), Some(80.0), Some(299))); + warnings.extend(state.take_warnings(Some(80.0), Some(10081), Some(10.0), Some(299))); + warnings.extend(state.take_warnings(Some(10.0), Some(10081), Some(95.0), Some(299))); + warnings.extend(state.take_warnings(Some(95.0), Some(10079), Some(10.0), Some(299))); assert_eq!( warnings, @@ -411,6 +411,21 @@ fn rate_limit_warnings_emit_thresholds() { ); } +#[test] +fn test_rate_limit_warnings_monthly() { + let mut state = RateLimitWarningState::default(); + let mut warnings: Vec = Vec::new(); + + warnings.extend(state.take_warnings(Some(75.0), Some(43199), None, None)); + assert_eq!( + warnings, + vec![String::from( + "Heads up, you've used over 75% of your monthly limit. Run /status for a breakdown.", + ),], + "expected one warning per limit for the highest crossed threshold" + ); +} + // (removed experimental resize snapshot test) #[test]