From 2d587c17f1c43fb42f5368cae7cbcd8de63f3a5e Mon Sep 17 00:00:00 2001 From: dhruvgupta-oai Date: Mon, 23 Mar 2026 10:24:15 -0400 Subject: [PATCH] Differentiate spend-limit vs credit-limit copy --- codex-rs/core/src/error.rs | 13 +++++++++-- codex-rs/core/src/error_tests.rs | 39 ++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index 439bda3330..b84180d160 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -463,8 +463,17 @@ impl std::fmt::Display for UsageLimitReachedError { Some(PlanType::Unknown(plan)) if plan.eq_ignore_ascii_case("self_serve_business_usage_based") => { - "You've hit your usage limit. Contact your admin to add credits to continue." - .to_string() + match self + .rate_limits + .as_ref() + .and_then(|snapshot| snapshot.credits.as_ref()) + .map(|credits| credits.has_credits) + { + Some(true) => "You've hit your usage limit. Contact your admin to increase spend limits to continue." + .to_string(), + Some(false) | None => "You've hit your usage limit. Contact your admin to add credits to continue." + .to_string(), + } } Some(PlanType::Unknown(_)) | None => format!( "You've hit your usage limit.{}", diff --git a/codex-rs/core/src/error_tests.rs b/codex-rs/core/src/error_tests.rs index 22a8ec564a..f35b2dd5bd 100644 --- a/codex-rs/core/src/error_tests.rs +++ b/codex-rs/core/src/error_tests.rs @@ -4,6 +4,7 @@ use chrono::DateTime; use chrono::Duration as ChronoDuration; use chrono::TimeZone; use chrono::Utc; +use codex_protocol::protocol::CreditsSnapshot; use codex_protocol::protocol::RateLimitWindow; use pretty_assertions::assert_eq; use reqwest::Response; @@ -244,7 +245,7 @@ fn usage_limit_reached_error_formats_business_plan_without_reset() { } #[test] -fn usage_limit_reached_error_formats_self_serve_business_usage_plan() { +fn usage_limit_reached_error_formats_self_serve_business_usage_plan_with_spend_limits() { let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(); let resets_at = base + ChronoDuration::hours(1); with_now_override(base, move || { @@ -253,7 +254,41 @@ fn usage_limit_reached_error_formats_self_serve_business_usage_plan() { "self_serve_business_usage_based".to_string(), )), resets_at: Some(resets_at), - rate_limits: Some(Box::new(rate_limit_snapshot())), + rate_limits: Some(Box::new(RateLimitSnapshot { + credits: Some(CreditsSnapshot { + has_credits: true, + unlimited: false, + balance: Some("38".to_string()), + }), + ..rate_limit_snapshot() + })), + promo_message: None, + }; + assert_eq!( + err.to_string(), + "You've hit your usage limit. Contact your admin to increase spend limits to continue." + ); + }); +} + +#[test] +fn usage_limit_reached_error_formats_self_serve_business_usage_plan_without_credits() { + let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(); + let resets_at = base + ChronoDuration::hours(1); + with_now_override(base, move || { + let err = UsageLimitReachedError { + plan_type: Some(PlanType::Unknown( + "self_serve_business_usage_based".to_string(), + )), + resets_at: Some(resets_at), + rate_limits: Some(Box::new(RateLimitSnapshot { + credits: Some(CreditsSnapshot { + has_credits: false, + unlimited: false, + balance: Some("0".to_string()), + }), + ..rate_limit_snapshot() + })), promo_message: None, }; assert_eq!(