Skip to content

Commit 5013d10

Browse files
authored
[codex] suppress low usage remaining warnings when credits are available (#28593)
## Why The TUI computed proactive `Heads up, you have less than ...` warnings before considering workspace credits. As a result, users could see included-limit warnings even when they could continue using Codex with workspace credits. `has_credits` alone is not sufficient to determine whether finite credits are usable: a spend-control hard limit can cap the reported balance to zero while `has_credits` still reflects the workspace's raw balance. Unlimited credits are the opposite case: they are usable even though no numeric balance is reported. ## What changed - suppress proactive TUI rate-limit usage warnings and the lower-cost model nudge when usable workspace credits are available - treat credits as usable when `has_credits` is true and either `unlimited` is true or the parsed balance is positive - continue showing warnings when the usable balance is zero, including when a spend-control limit has capped otherwise available workspace credits - add regression coverage for zero-balance, positive-balance, and unlimited workspace-credit snapshots ## Validation - `just test -p codex-tui rate_limit_usage_warnings_`
1 parent a781761 commit 5013d10

2 files changed

Lines changed: 89 additions & 7 deletions

File tree

codex-rs/tui/src/chatwidget/rate_limits.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,19 @@ impl ChatWidget {
210210
{
211211
self.codex_rate_limit_reached_type = Some(rate_limit_reached_type);
212212
}
213-
let warnings = if is_codex_limit {
213+
214+
let has_workspace_credits = snapshot.credits.as_ref().is_some_and(|credits| {
215+
credits.has_credits
216+
&& (credits.unlimited
217+
|| credits.balance.as_deref().is_some_and(|balance| {
218+
balance
219+
.trim()
220+
.parse::<f64>()
221+
.is_ok_and(|balance| balance > 0.0)
222+
}))
223+
});
224+
let should_warn_about_rate_limit_usage = is_codex_limit && !has_workspace_credits;
225+
let warnings = if should_warn_about_rate_limit_usage {
214226
self.rate_limit_warnings.take_warnings(
215227
snapshot
216228
.secondary
@@ -245,12 +257,6 @@ impl ChatWidget {
245257
.map(|w| f64::from(w.used_percent) >= RATE_LIMIT_SWITCH_PROMPT_THRESHOLD)
246258
.unwrap_or(false));
247259

248-
let has_workspace_credits = snapshot
249-
.credits
250-
.as_ref()
251-
.map(|credits| credits.has_credits)
252-
.unwrap_or(false);
253-
254260
if high_usage
255261
&& !has_workspace_credits
256262
&& !self.rate_limit_switch_prompt_hidden()

codex-rs/tui/src/chatwidget/tests/status_and_layout.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,82 @@ async fn rate_limit_switch_prompt_skips_non_codex_limit() {
10091009
));
10101010
}
10111011

1012+
#[tokio::test]
1013+
async fn rate_limit_usage_warnings_show_when_workspace_credits_zero_balance() {
1014+
let (mut chat, mut rx, _) = make_chatwidget_manual(Some("gpt-5")).await;
1015+
chat.has_chatgpt_account = true;
1016+
1017+
let mut rate_limit_snapshot = snapshot(/*percent*/ 95.0);
1018+
rate_limit_snapshot.credits = Some(CreditsSnapshot {
1019+
has_credits: true,
1020+
unlimited: false,
1021+
balance: Some("0".to_string()),
1022+
});
1023+
1024+
chat.on_rate_limit_snapshot(Some(rate_limit_snapshot));
1025+
1026+
assert!(
1027+
!drain_insert_history(&mut rx).is_empty(),
1028+
"zero-balance workspace credits should not suppress proactive usage warnings"
1029+
);
1030+
assert!(matches!(
1031+
chat.rate_limit_switch_prompt,
1032+
RateLimitSwitchPromptState::Pending
1033+
));
1034+
}
1035+
1036+
#[tokio::test]
1037+
async fn rate_limit_usage_warnings_skip_when_workspace_credits_are_available() {
1038+
let (mut chat, mut rx, _) = make_chatwidget_manual(Some("gpt-5")).await;
1039+
chat.has_chatgpt_account = true;
1040+
1041+
let mut rate_limit_snapshot = snapshot(/*percent*/ 95.0);
1042+
rate_limit_snapshot.credits = Some(CreditsSnapshot {
1043+
has_credits: true,
1044+
unlimited: false,
1045+
balance: Some("25.00".to_string()),
1046+
});
1047+
1048+
chat.on_rate_limit_snapshot(Some(rate_limit_snapshot));
1049+
1050+
assert!(
1051+
drain_insert_history(&mut rx).is_empty(),
1052+
"workspace credits should suppress proactive usage warnings"
1053+
);
1054+
assert!(matches!(
1055+
chat.rate_limit_switch_prompt,
1056+
RateLimitSwitchPromptState::Idle
1057+
));
1058+
assert_eq!(
1059+
chat.rate_limit_warnings.primary_index, 0,
1060+
"suppressed warnings should not consume warning thresholds"
1061+
);
1062+
}
1063+
1064+
#[tokio::test]
1065+
async fn rate_limit_usage_warnings_skip_with_unlimited_workspace_credits() {
1066+
let (mut chat, mut rx, _) = make_chatwidget_manual(Some("gpt-5")).await;
1067+
chat.has_chatgpt_account = true;
1068+
1069+
let mut rate_limit_snapshot = snapshot(/*percent*/ 95.0);
1070+
rate_limit_snapshot.credits = Some(CreditsSnapshot {
1071+
has_credits: true,
1072+
unlimited: true,
1073+
balance: None,
1074+
});
1075+
1076+
chat.on_rate_limit_snapshot(Some(rate_limit_snapshot));
1077+
1078+
assert!(
1079+
drain_insert_history(&mut rx).is_empty(),
1080+
"unlimited workspace credits should suppress proactive usage warnings"
1081+
);
1082+
assert!(matches!(
1083+
chat.rate_limit_switch_prompt,
1084+
RateLimitSwitchPromptState::Idle
1085+
));
1086+
}
1087+
10121088
#[tokio::test]
10131089
async fn rate_limit_switch_prompt_shows_once_per_session() {
10141090
let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await;

0 commit comments

Comments
 (0)