codex_cli/rate_limits/
render.rs1use 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}