mirror of
https://github.com/openai/codex.git
synced 2026-11-04 00:24:43 +00:00
## Why Selecting a usage-limit reset from the picker immediately started redemption, leaving no opportunity to verify the choice. ## What changed - Open a separate confirmation popup before redeeming a selected reset, with `No, go back` selected by default. - Show backend-provided reset titles and descriptions in the picker and confirmation, with generic fallback copy when those details are absent. - Ignore duplicate or stale confirmation and redemption events, while retaining the idempotency key needed to retry a failed redemption. ## Testing - Added coverage for confirmation, cancellation, backend copy, duplicate and stale events, successful redemption, and retry behavior. GitOrigin-RevId: 828945f99f06e8c73941fd864e4aa9ac52729f98
78 lines
2.7 KiB
Rust
78 lines
2.7 KiB
Rust
use chrono::DateTime;
|
|
use chrono::Local;
|
|
use chrono::Utc;
|
|
use codex_app_server_protocol::RateLimitResetCreditStatus;
|
|
use codex_app_server_protocol::RateLimitResetCreditsSummary;
|
|
|
|
#[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 reset_credit_options(
|
|
summary: &RateLimitResetCreditsSummary,
|
|
) -> Vec<ResetCreditOption> {
|
|
let available_count = summary.available_count.max(0);
|
|
let detail_limit = usize::try_from(available_count).unwrap_or(usize::MAX);
|
|
let mut available_credits = summary
|
|
.credits
|
|
.as_deref()
|
|
.unwrap_or_default()
|
|
.iter()
|
|
.filter(|credit| credit.status == RateLimitResetCreditStatus::Available)
|
|
.collect::<Vec<_>>();
|
|
available_credits.sort_by_key(|credit| credit.expires_at.unwrap_or(i64::MAX));
|
|
|
|
let mut options = available_credits
|
|
.into_iter()
|
|
.take(detail_limit)
|
|
.map(|credit| {
|
|
let expiration = match credit.expires_at {
|
|
Some(expires_at) => DateTime::<Utc>::from_timestamp(expires_at, 0)
|
|
.map(|expires_at| {
|
|
format!(
|
|
"Expires {}",
|
|
expires_at
|
|
.with_timezone(&Local)
|
|
.format("%H:%M on %-d %b %Y")
|
|
)
|
|
})
|
|
.unwrap_or_else(|| "Expiration unavailable".to_string()),
|
|
None => "Does not expire".to_string(),
|
|
};
|
|
let reset_title = credit
|
|
.title
|
|
.as_deref()
|
|
.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_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: "Full reset".to_string(),
|
|
detail: None,
|
|
description: "Reset your current usage limits.".to_string(),
|
|
});
|
|
}
|
|
|
|
options
|
|
}
|