Skip to main content

codex_cli/rate_limits/
render.rs

1use chrono::{Local, TimeZone};
2use serde_json::Value;
3
4pub struct UsageData {
5    pub primary: Window,
6    pub secondary: Window,
7}
8
9pub struct Window {
10    pub limit_window_seconds: i64,
11    pub used_percent: f64,
12    pub reset_at: i64,
13}
14
15pub struct RenderValues {
16    pub primary_label: String,
17    pub secondary_label: String,
18    pub primary_remaining: i64,
19    pub secondary_remaining: i64,
20    pub primary_reset_epoch: i64,
21    pub secondary_reset_epoch: i64,
22}
23
24pub struct WeeklyValues {
25    pub weekly_remaining: i64,
26    pub weekly_reset_epoch: i64,
27    pub non_weekly_label: String,
28    pub non_weekly_remaining: i64,
29    pub non_weekly_reset_epoch: Option<i64>,
30}
31
32pub fn parse_usage(json: &Value) -> Option<UsageData> {
33    let rate_limit = json.get("rate_limit")?;
34    let primary = parse_window(rate_limit.get("primary_window")?)?;
35    let secondary = parse_window(rate_limit.get("secondary_window")?)?;
36    Some(UsageData { primary, secondary })
37}
38
39fn parse_window(value: &Value) -> Option<Window> {
40    let limit_window_seconds = value.get("limit_window_seconds")?.as_i64()?;
41    let used_percent = value
42        .get("used_percent")
43        .and_then(|v| v.as_f64())
44        .unwrap_or(0.0);
45    let reset_at = value.get("reset_at")?.as_i64()?;
46    Some(Window {
47        limit_window_seconds,
48        used_percent,
49        reset_at,
50    })
51}
52
53pub fn render_values(data: &UsageData) -> RenderValues {
54    let primary_label = format_window_seconds(data.primary.limit_window_seconds)
55        .unwrap_or_else(|| "Primary".to_string());
56    let secondary_label = format_window_seconds(data.secondary.limit_window_seconds)
57        .unwrap_or_else(|| "Secondary".to_string());
58
59    let primary_remaining = remaining_percent(data.primary.used_percent);
60    let secondary_remaining = remaining_percent(data.secondary.used_percent);
61
62    RenderValues {
63        primary_label,
64        secondary_label,
65        primary_remaining,
66        secondary_remaining,
67        primary_reset_epoch: data.primary.reset_at,
68        secondary_reset_epoch: data.secondary.reset_at,
69    }
70}
71
72pub fn weekly_values(values: &RenderValues) -> WeeklyValues {
73    let (
74        weekly_remaining,
75        weekly_reset_epoch,
76        non_weekly_label,
77        non_weekly_remaining,
78        non_weekly_reset_epoch,
79    ) = if values.primary_label == "Weekly" {
80        (
81            values.primary_remaining,
82            values.primary_reset_epoch,
83            values.secondary_label.clone(),
84            values.secondary_remaining,
85            Some(values.secondary_reset_epoch),
86        )
87    } else {
88        (
89            values.secondary_remaining,
90            values.secondary_reset_epoch,
91            values.primary_label.clone(),
92            values.primary_remaining,
93            Some(values.primary_reset_epoch),
94        )
95    };
96
97    WeeklyValues {
98        weekly_remaining,
99        weekly_reset_epoch,
100        non_weekly_label,
101        non_weekly_remaining,
102        non_weekly_reset_epoch,
103    }
104}
105
106pub fn format_window_seconds(raw: i64) -> Option<String> {
107    if raw <= 0 {
108        return None;
109    }
110    if raw % 604_800 == 0 {
111        let weeks = raw / 604_800;
112        if weeks == 1 {
113            return Some("Weekly".to_string());
114        }
115        return Some(format!("{weeks}w"));
116    }
117    if raw % 86_400 == 0 {
118        return Some(format!("{}d", raw / 86_400));
119    }
120    if raw % 3_600 == 0 {
121        return Some(format!("{}h", raw / 3_600));
122    }
123    if raw % 60 == 0 {
124        return Some(format!("{}m", raw / 60));
125    }
126    Some(format!("{raw}s"))
127}
128
129pub fn format_epoch_local_datetime(epoch: i64) -> Option<String> {
130    let dt = Local.timestamp_opt(epoch, 0).single()?;
131    Some(dt.format("%m-%d %H:%M").to_string())
132}
133
134pub fn format_epoch_local_datetime_with_offset(epoch: i64) -> Option<String> {
135    let dt = Local.timestamp_opt(epoch, 0).single()?;
136    Some(dt.format("%m-%d %H:%M %:z").to_string())
137}
138
139pub fn format_epoch_local(epoch: i64, fmt: &str) -> Option<String> {
140    let dt = Local.timestamp_opt(epoch, 0).single()?;
141    Some(dt.format(fmt).to_string())
142}
143
144pub fn format_until_epoch_compact(target_epoch: i64, now_epoch: i64) -> Option<String> {
145    if target_epoch <= 0 || now_epoch <= 0 {
146        return None;
147    }
148    let remaining = target_epoch - now_epoch;
149    if remaining <= 0 {
150        return Some(format!("{:>2}h {:>2}m", 0, 0));
151    }
152
153    if remaining >= 86_400 {
154        let days = remaining / 86_400;
155        let hours = (remaining % 86_400) / 3_600;
156        return Some(format!("{:>2}d {:>2}h", days, hours));
157    }
158
159    let hours = remaining / 3_600;
160    let minutes = (remaining % 3_600) / 60;
161    Some(format!("{:>2}h {:>2}m", hours, minutes))
162}
163
164fn remaining_percent(used_percent: f64) -> i64 {
165    let remaining = 100.0 - used_percent;
166    remaining.round() as i64
167}