Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions codex-rs/tui/src/chatwidget/rate_limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,19 @@ impl ChatWidget {
{
self.codex_rate_limit_reached_type = Some(rate_limit_reached_type);
}
let warnings = if is_codex_limit {

let has_workspace_credits = snapshot.credits.as_ref().is_some_and(|credits| {
credits.has_credits
&& (credits.unlimited
|| credits.balance.as_deref().is_some_and(|balance| {
balance
.trim()
.parse::<f64>()
.is_ok_and(|balance| balance > 0.0)
}))
});
let should_warn_about_rate_limit_usage = is_codex_limit && !has_workspace_credits;
let warnings = if should_warn_about_rate_limit_usage {
self.rate_limit_warnings.take_warnings(
snapshot
.secondary
Expand Down Expand Up @@ -245,12 +257,6 @@ impl ChatWidget {
.map(|w| f64::from(w.used_percent) >= RATE_LIMIT_SWITCH_PROMPT_THRESHOLD)
.unwrap_or(false));

let has_workspace_credits = snapshot
.credits
.as_ref()
.map(|credits| credits.has_credits)
.unwrap_or(false);

if high_usage
&& !has_workspace_credits
&& !self.rate_limit_switch_prompt_hidden()
Expand Down
76 changes: 76 additions & 0 deletions codex-rs/tui/src/chatwidget/tests/status_and_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,82 @@ async fn rate_limit_switch_prompt_skips_non_codex_limit() {
));
}

#[tokio::test]
async fn rate_limit_usage_warnings_show_when_workspace_credits_zero_balance() {
let (mut chat, mut rx, _) = make_chatwidget_manual(Some("gpt-5")).await;
chat.has_chatgpt_account = true;

let mut rate_limit_snapshot = snapshot(/*percent*/ 95.0);
rate_limit_snapshot.credits = Some(CreditsSnapshot {
has_credits: true,
unlimited: false,
balance: Some("0".to_string()),
});

chat.on_rate_limit_snapshot(Some(rate_limit_snapshot));

assert!(
!drain_insert_history(&mut rx).is_empty(),
"zero-balance workspace credits should not suppress proactive usage warnings"
);
assert!(matches!(
chat.rate_limit_switch_prompt,
RateLimitSwitchPromptState::Pending
));
}

#[tokio::test]
async fn rate_limit_usage_warnings_skip_when_workspace_credits_are_available() {
let (mut chat, mut rx, _) = make_chatwidget_manual(Some("gpt-5")).await;
chat.has_chatgpt_account = true;

let mut rate_limit_snapshot = snapshot(/*percent*/ 95.0);
rate_limit_snapshot.credits = Some(CreditsSnapshot {
has_credits: true,
unlimited: false,
balance: Some("25.00".to_string()),
});

chat.on_rate_limit_snapshot(Some(rate_limit_snapshot));

assert!(
drain_insert_history(&mut rx).is_empty(),
"workspace credits should suppress proactive usage warnings"
);
assert!(matches!(
chat.rate_limit_switch_prompt,
RateLimitSwitchPromptState::Idle
));
assert_eq!(
chat.rate_limit_warnings.primary_index, 0,
"suppressed warnings should not consume warning thresholds"
);
}

#[tokio::test]
async fn rate_limit_usage_warnings_skip_with_unlimited_workspace_credits() {
let (mut chat, mut rx, _) = make_chatwidget_manual(Some("gpt-5")).await;
chat.has_chatgpt_account = true;

let mut rate_limit_snapshot = snapshot(/*percent*/ 95.0);
rate_limit_snapshot.credits = Some(CreditsSnapshot {
has_credits: true,
unlimited: true,
balance: None,
});

chat.on_rate_limit_snapshot(Some(rate_limit_snapshot));

assert!(
drain_insert_history(&mut rx).is_empty(),
"unlimited workspace credits should suppress proactive usage warnings"
);
assert!(matches!(
chat.rate_limit_switch_prompt,
RateLimitSwitchPromptState::Idle
));
}

#[tokio::test]
async fn rate_limit_switch_prompt_shows_once_per_session() {
let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await;
Expand Down
Loading