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
1 change: 1 addition & 0 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 28 additions & 7 deletions codex-rs/tui/src/app/event_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -975,18 +975,39 @@ impl App {
RateLimitRefreshOrigin::ResetPicker { request_id },
);
}
AppEvent::ConsumeRateLimitResetCredit {
idempotency_key,
AppEvent::OpenRateLimitResetConfirmation {
picker_request_id,
confirmation_gate,
credit_id,
reset_title,
reset_detail,
reset_description,
} => {
let request_id = self.chat_widget.show_rate_limit_reset_consuming_popup();
self.consume_rate_limit_reset_credit(
app_server,
request_id,
idempotency_key,
self.chat_widget.show_rate_limit_reset_confirmation(
picker_request_id,
confirmation_gate,
credit_id,
reset_title,
reset_detail,
reset_description,
);
}
AppEvent::ConsumeRateLimitResetCredit {
idempotency_key,
credit_id,
} => {
if let Some(request_id) = self
.chat_widget
.start_rate_limit_reset_consumption(&idempotency_key)
{
self.consume_rate_limit_reset_credit(
app_server,
request_id,
idempotency_key,
credit_id,
);
}
}
AppEvent::RateLimitResetCreditConsumed {
request_id,
idempotency_key,
Expand Down
12 changes: 12 additions & 0 deletions codex-rs/tui/src/app_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
//! quits without reaching into the app loop or coupling to shutdown/exit sequencing.

use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use codex_app_server_protocol::AddCreditsNudgeCreditType;
use codex_app_server_protocol::AddCreditsNudgeEmailStatus;
Expand Down Expand Up @@ -332,6 +334,16 @@ pub(crate) enum AppEvent {
/// Open the reset-credit flow selected from the `/usage` menu.
OpenRateLimitResetCredits,

/// Confirm the reset credit selected from the reset-credit picker.
OpenRateLimitResetConfirmation {
picker_request_id: u64,
confirmation_gate: Arc<AtomicBool>,
credit_id: Option<String>,
reset_title: String,
reset_detail: Option<String>,
reset_description: String,
},

/// Consume one reset credit using a stable idempotency key.
ConsumeRateLimitResetCredit {
idempotency_key: String,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,8 @@ pub(crate) struct ChatWidget {
completed_token_activity_output: Option<history_cell::CompositeHistoryCell>,
next_token_activity_request_id: u64,
pending_rate_limit_reset_request_id: Option<u64>,
pending_rate_limit_reset_idempotency_key: Option<String>,
rate_limit_reset_picker_request_id: Option<u64>,
pending_rate_limit_reset_hint_request_id: Option<u64>,
pending_usage_menu_rate_limit_request_id: Option<u64>,
pending_rate_limit_reset_hint: Option<PlainHistoryCell>,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/tui/src/chatwidget/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ impl ChatWidget {
completed_token_activity_output: None,
next_token_activity_request_id: 0,
pending_rate_limit_reset_request_id: None,
pending_rate_limit_reset_idempotency_key: None,
rate_limit_reset_picker_request_id: None,
pending_rate_limit_reset_hint_request_id: None,
pending_usage_menu_rate_limit_request_id: None,
pending_rate_limit_reset_hint: None,
Expand Down
86 changes: 17 additions & 69 deletions codex-rs/tui/src/chatwidget/reset_credits.rs
Original file line number Diff line number Diff line change
@@ -1,76 +1,19 @@
use crate::status::RateLimitSnapshotDisplay;
use chrono::DateTime;
use chrono::Local;
use chrono::Utc;
use codex_app_server_protocol::RateLimitResetCreditStatus;
use codex_app_server_protocol::RateLimitResetCreditsSummary;
use codex_app_server_protocol::RateLimitResetType;
use codex_protocol::account::PlanType;
use std::collections::BTreeMap;

use super::rate_limits::get_limits_duration;

pub(super) enum RateLimitResetScope {
Monthly,
WeeklyAndFiveHour,
Unknown,
}

impl RateLimitResetScope {
pub(super) fn picker_label(&self) -> &'static str {
match self {
Self::Monthly => "Full reset (Monthly)",
Self::WeeklyAndFiveHour => "Full reset (Weekly + 5h)",
Self::Unknown => "Full reset",
}
}

pub(super) fn usage_description(&self) -> &'static str {
match self {
Self::Monthly => "Reset your current monthly usage limit.",
Self::WeeklyAndFiveHour => "Reset your current 5-hour and weekly usage limits.",
Self::Unknown => "Reset your current usage limits.",
}
}
}

#[derive(Debug, Eq, PartialEq)]
pub(super) struct ResetCreditOption {
pub(super) credit_id: Option<String>,
pub(super) name: String,
pub(super) detail: Option<String>,
pub(super) description: String,
}

pub(super) fn rate_limit_reset_scope(
rate_limits: &BTreeMap<String, RateLimitSnapshotDisplay>,
plan_type: Option<PlanType>,
) -> RateLimitResetScope {
let window_labels = rate_limits
.iter()
.find(|(limit_id, _)| limit_id.eq_ignore_ascii_case("codex"))
.into_iter()
.flat_map(|(_, snapshot)| [snapshot.primary.as_ref(), snapshot.secondary.as_ref()])
.flatten()
.filter_map(|window| window.window_minutes.and_then(get_limits_duration))
.collect::<Vec<_>>();

if window_labels.iter().any(|label| label == "monthly")
|| matches!(plan_type, Some(PlanType::Free | PlanType::Go))
{
RateLimitResetScope::Monthly
} else if window_labels
.iter()
.any(|label| label == "5h" || label == "weekly")
{
RateLimitResetScope::WeeklyAndFiveHour
} else {
RateLimitResetScope::Unknown
}
}

pub(super) fn reset_credit_options(
summary: &RateLimitResetCreditsSummary,
scope: RateLimitResetScope,
) -> Vec<ResetCreditOption> {
let available_count = summary.available_count.max(0);
let detail_limit = usize::try_from(available_count).unwrap_or(usize::MAX);
Expand Down Expand Up @@ -100,28 +43,33 @@ pub(super) fn reset_credit_options(
.unwrap_or_else(|| "Expiration unavailable".to_string()),
None => "Does not expire".to_string(),
};
let reset_label = credit
let reset_title = credit
.title
.as_deref()
.filter(|title| !title.trim().is_empty())
.unwrap_or_else(|| match credit.reset_type {
RateLimitResetType::CodexRateLimits | RateLimitResetType::Unknown => {
scope.picker_label()
}
});
.map(str::trim)
.filter(|title| !title.is_empty())
.unwrap_or("Full reset");
let reset_description = credit
.description
.as_deref()
.map(str::trim)
.filter(|description| !description.is_empty())
.unwrap_or("Reset your current usage limits.");
ResetCreditOption {
credit_id: Some(credit.id.clone()),
name: reset_label.to_string(),
description: format!("{expiration}."),
name: reset_title.to_string(),
detail: Some(format!("{expiration}.")),
description: reset_description.to_string(),
}
})
.collect::<Vec<_>>();

if options.is_empty() {
options.push(ResetCreditOption {
credit_id: None,
name: "Use a reset".to_string(),
description: scope.usage_description().to_string(),
name: "Full reset".to_string(),
detail: None,
description: "Reset your current usage limits.".to_string(),
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: tui/src/chatwidget/tests/usage.rs
expression: "render_bottom_popup(&chat, 80)"
---
Use this reset?
Full reset (Weekly + 5 hr) · Expires 09:39 on 18 Jun 2026.

1. Yes, use reset Reset your weekly and 5-hour usage limits.
› 2. No, go back Choose a different reset.

Press enter to confirm or esc to go back
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ expression: "render_bottom_popup(&chat, 44)"
Usage limit resets
2 usage limit resets available.

› 1. Cancel
2. Full reset Expires 09:39 on 18 Jun
› 1. Full reset Expires 09:39 on 18 Jun
2026.
3. Full reset Does not expire.
2. Full reset Does not expire.
3. Cancel

Press enter to confirm or esc to go back
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ expression: "states.join(\"\\n---\\n\")"
Usage limit resets
2 usage limit resets available.

› 1. Cancel
2. Full reset (Weekly + 5 hr) Expires 09:39 on 18 Jun 2026.
3. Full reset (Weekly + 5 hr) Expires 08:59 on 27 Jun 2026.
› 1. Full reset (Weekly + 5 hr) Expires 09:39 on 18 Jun 2026.
2. Full reset (Weekly + 5 hr) Expires 08:59 on 27 Jun 2026.
3. Cancel

Press enter to confirm or esc to go back
---
Expand Down

This file was deleted.

Loading
Loading