Download codex-rs/codex-api/src/rate_limits.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 12.6 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/codex-api/src/rate_limits.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/codex-api/src/rate_limits.rs
-
curl -L -o rate_limits.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/codex-api/src/rate_limits.rs
12.6 kB
| use codex_protocol::account::PlanType; | |
| use codex_protocol::protocol::CreditsSnapshot; | |
| use codex_protocol::protocol::RateLimitReachedType; | |
| use codex_protocol::protocol::RateLimitSnapshot; | |
| use codex_protocol::protocol::RateLimitWindow; | |
| use http::HeaderMap; | |
| use serde::Deserialize; | |
| use std::collections::BTreeSet; | |
| use std::fmt::Display; | |
| pub struct RateLimitError { | |
| pub message: String, | |
| } | |
| impl Display for RateLimitError { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "{}", self.message) | |
| } | |
| } | |
| /// Parses the default Codex rate-limit header family into a `RateLimitSnapshot`. | |
| pub fn parse_default_rate_limit(headers: &HeaderMap) -> Option<RateLimitSnapshot> { | |
| parse_rate_limit_for_limit(headers, /*limit_id*/ None) | |
| } | |
| /// Parses all known rate-limit header families into update records keyed by limit id. | |
| pub fn parse_all_rate_limits(headers: &HeaderMap) -> Vec<RateLimitSnapshot> { | |
| let mut snapshots = Vec::new(); | |
| if let Some(snapshot) = parse_default_rate_limit(headers) { | |
| snapshots.push(snapshot); | |
| } | |
| let mut limit_ids: BTreeSet<String> = BTreeSet::new(); | |
| for name in headers.keys() { | |
| let header_name = name.as_str().to_ascii_lowercase(); | |
| if let Some(limit_id) = header_name_to_limit_id(&header_name) | |
| && limit_id != "codex" | |
| { | |
| limit_ids.insert(limit_id); | |
| } | |
| } | |
| snapshots.extend(limit_ids.into_iter().filter_map(|limit_id| { | |
| let snapshot = parse_rate_limit_for_limit(headers, Some(limit_id.as_str()))?; | |
| has_rate_limit_data(&snapshot).then_some(snapshot) | |
| })); | |
| snapshots | |
| } | |
| /// Parses rate-limit headers for the provided limit id. | |
| /// | |
| /// `limit_id` should match the server-provided metered limit id (e.g. `codex`, | |
| /// `codex_other`). When omitted, this defaults to the legacy `codex` header family. | |
| pub fn parse_rate_limit_for_limit( | |
| headers: &HeaderMap, | |
| limit_id: Option<&str>, | |
| ) -> Option<RateLimitSnapshot> { | |
| let normalized_limit = limit_id | |
| .map(str::trim) | |
| .filter(|name| !name.is_empty()) | |
| .unwrap_or("codex") | |
| .to_ascii_lowercase() | |
| .replace('_', "-"); | |
| let prefix = format!("x-{normalized_limit}"); | |
| let primary = parse_rate_limit_window( | |
| headers, | |
| &format!("{prefix}-primary-used-percent"), | |
| &format!("{prefix}-primary-window-minutes"), | |
| &format!("{prefix}-primary-reset-at"), | |
| ); | |
| let secondary = parse_rate_limit_window( | |
| headers, | |
| &format!("{prefix}-secondary-used-percent"), | |
| &format!("{prefix}-secondary-window-minutes"), | |
| &format!("{prefix}-secondary-reset-at"), | |
| ); | |
| let normalized_limit_id = normalize_limit_id(normalized_limit); | |
| let credits = parse_credits_snapshot(headers); | |
| let limit_name_header = format!("{prefix}-limit-name"); | |
| let parsed_limit_name = parse_header_str(headers, &limit_name_header) | |
| .map(str::trim) | |
| .filter(|name| !name.is_empty()) | |
| .map(std::string::ToString::to_string); | |
| Some(RateLimitSnapshot { | |
| limit_id: Some(normalized_limit_id), | |
| limit_name: parsed_limit_name, | |
| normal_model_slug: None, | |
| primary, | |
| secondary, | |
| credits, | |
| individual_limit: None, | |
| spend_control_reached: None, | |
| plan_type: None, | |
| rate_limit_reached_type: None, | |
| }) | |
| } | |
| struct RateLimitEventWindow { | |
| used_percent: f64, | |
| window_minutes: Option<i64>, | |
| reset_at: Option<i64>, | |
| } | |
| struct RateLimitEventDetails { | |
| primary: Option<RateLimitEventWindow>, | |
| secondary: Option<RateLimitEventWindow>, | |
| } | |
| struct RateLimitEventCredits { | |
| has_credits: bool, | |
| unlimited: bool, | |
| balance: Option<String>, | |
| } | |
| struct RateLimitEvent { | |
| kind: String, | |
| plan_type: Option<PlanType>, | |
| rate_limits: Option<RateLimitEventDetails>, | |
| credits: Option<RateLimitEventCredits>, | |
| metered_limit_name: Option<String>, | |
| limit_name: Option<String>, | |
| } | |
| pub fn parse_rate_limit_event(payload: &str) -> Option<RateLimitSnapshot> { | |
| let event: RateLimitEvent = serde_json::from_str(payload).ok()?; | |
| if event.kind != "codex.rate_limits" { | |
| return None; | |
| } | |
| let (primary, secondary) = if let Some(details) = event.rate_limits.as_ref() { | |
| ( | |
| map_event_window(details.primary.as_ref()), | |
| map_event_window(details.secondary.as_ref()), | |
| ) | |
| } else { | |
| (None, None) | |
| }; | |
| let credits = event.credits.map(|credits| CreditsSnapshot { | |
| has_credits: credits.has_credits, | |
| unlimited: credits.unlimited, | |
| balance: credits.balance, | |
| }); | |
| let limit_id = event | |
| .metered_limit_name | |
| .or(event.limit_name) | |
| .map(normalize_limit_id); | |
| Some(RateLimitSnapshot { | |
| limit_id: Some(limit_id.unwrap_or_else(|| "codex".to_string())), | |
| limit_name: None, | |
| normal_model_slug: None, | |
| primary, | |
| secondary, | |
| credits, | |
| individual_limit: None, | |
| spend_control_reached: None, | |
| plan_type: event.plan_type, | |
| rate_limit_reached_type: None, | |
| }) | |
| } | |
| fn map_event_window(window: Option<&RateLimitEventWindow>) -> Option<RateLimitWindow> { | |
| let window = window?; | |
| Some(RateLimitWindow { | |
| used_percent: window.used_percent, | |
| window_minutes: window.window_minutes, | |
| resets_at: window.reset_at, | |
| }) | |
| } | |
| /// Parses the bespoke Codex rate-limit headers into a `RateLimitSnapshot`. | |
| pub fn parse_promo_message(headers: &HeaderMap) -> Option<String> { | |
| parse_header_str(headers, "x-codex-promo-message") | |
| .map(str::trim) | |
| .filter(|value| !value.is_empty()) | |
| .map(std::string::ToString::to_string) | |
| } | |
| pub(crate) fn parse_rate_limit_reached_type(headers: &HeaderMap) -> Option<RateLimitReachedType> { | |
| parse_header_str(headers, "x-codex-rate-limit-reached-type")? | |
| .trim() | |
| .parse() | |
| .ok() | |
| } | |
| fn parse_rate_limit_window( | |
| headers: &HeaderMap, | |
| used_percent_header: &str, | |
| window_minutes_header: &str, | |
| resets_at_header: &str, | |
| ) -> Option<RateLimitWindow> { | |
| let used_percent: Option<f64> = parse_header_f64(headers, used_percent_header); | |
| used_percent.and_then(|used_percent| { | |
| let window_minutes = parse_header_i64(headers, window_minutes_header); | |
| let resets_at = parse_header_i64(headers, resets_at_header); | |
| let has_data = used_percent != 0.0 | |
| || window_minutes.is_some_and(|minutes| minutes != 0) | |
| || resets_at.is_some(); | |
| has_data.then_some(RateLimitWindow { | |
| used_percent, | |
| window_minutes, | |
| resets_at, | |
| }) | |
| }) | |
| } | |
| fn parse_credits_snapshot(headers: &HeaderMap) -> Option<CreditsSnapshot> { | |
| let has_credits = parse_header_bool(headers, "x-codex-credits-has-credits")?; | |
| let unlimited = parse_header_bool(headers, "x-codex-credits-unlimited")?; | |
| let balance = parse_header_str(headers, "x-codex-credits-balance") | |
| .map(str::trim) | |
| .filter(|value| !value.is_empty()) | |
| .map(std::string::ToString::to_string); | |
| Some(CreditsSnapshot { | |
| has_credits, | |
| unlimited, | |
| balance, | |
| }) | |
| } | |
| fn parse_header_f64(headers: &HeaderMap, name: &str) -> Option<f64> { | |
| parse_header_str(headers, name)? | |
| .parse::<f64>() | |
| .ok() | |
| .filter(|v| v.is_finite()) | |
| } | |
| fn parse_header_i64(headers: &HeaderMap, name: &str) -> Option<i64> { | |
| parse_header_str(headers, name)?.parse::<i64>().ok() | |
| } | |
| fn parse_header_bool(headers: &HeaderMap, name: &str) -> Option<bool> { | |
| let raw = parse_header_str(headers, name)?; | |
| if raw.eq_ignore_ascii_case("true") || raw == "1" { | |
| Some(true) | |
| } else if raw.eq_ignore_ascii_case("false") || raw == "0" { | |
| Some(false) | |
| } else { | |
| None | |
| } | |
| } | |
| fn parse_header_str<'a>(headers: &'a HeaderMap, name: &str) -> Option<&'a str> { | |
| headers.get(name)?.to_str().ok() | |
| } | |
| fn has_rate_limit_data(snapshot: &RateLimitSnapshot) -> bool { | |
| snapshot.primary.is_some() || snapshot.secondary.is_some() || snapshot.credits.is_some() | |
| } | |
| fn header_name_to_limit_id(header_name: &str) -> Option<String> { | |
| let suffix = "-primary-used-percent"; | |
| let prefix = header_name.strip_suffix(suffix)?; | |
| let limit = prefix.strip_prefix("x-")?; | |
| Some(normalize_limit_id(limit.to_string())) | |
| } | |
| fn normalize_limit_id(name: impl Into<String>) -> String { | |
| name.into().trim().to_ascii_lowercase().replace('-', "_") | |
| } | |
| mod tests { | |
| use super::*; | |
| use http::HeaderValue; | |
| use pretty_assertions::assert_eq; | |
| fn parse_rate_limit_for_limit_defaults_to_codex_headers() { | |
| let mut headers = HeaderMap::new(); | |
| headers.insert( | |
| "x-codex-primary-used-percent", | |
| HeaderValue::from_static("12.5"), | |
| ); | |
| headers.insert( | |
| "x-codex-primary-window-minutes", | |
| HeaderValue::from_static("60"), | |
| ); | |
| headers.insert( | |
| "x-codex-primary-reset-at", | |
| HeaderValue::from_static("1704069000"), | |
| ); | |
| let snapshot = parse_rate_limit_for_limit(&headers, /*limit_id*/ None).expect("snapshot"); | |
| assert_eq!(snapshot.limit_id.as_deref(), Some("codex")); | |
| assert_eq!(snapshot.limit_name, None); | |
| let primary = snapshot.primary.expect("primary"); | |
| assert_eq!(primary.used_percent, 12.5); | |
| assert_eq!(primary.window_minutes, Some(60)); | |
| assert_eq!(primary.resets_at, Some(1704069000)); | |
| } | |
| fn parse_rate_limit_for_limit_reads_secondary_headers() { | |
| let mut headers = HeaderMap::new(); | |
| headers.insert( | |
| "x-codex-secondary-primary-used-percent", | |
| HeaderValue::from_static("80"), | |
| ); | |
| headers.insert( | |
| "x-codex-secondary-primary-window-minutes", | |
| HeaderValue::from_static("1440"), | |
| ); | |
| headers.insert( | |
| "x-codex-secondary-primary-reset-at", | |
| HeaderValue::from_static("1704074400"), | |
| ); | |
| let snapshot = | |
| parse_rate_limit_for_limit(&headers, Some("codex_secondary")).expect("snapshot"); | |
| assert_eq!(snapshot.limit_id.as_deref(), Some("codex_secondary")); | |
| assert_eq!(snapshot.limit_name, None); | |
| let primary = snapshot.primary.expect("primary"); | |
| assert_eq!(primary.used_percent, 80.0); | |
| assert_eq!(primary.window_minutes, Some(1440)); | |
| assert_eq!(primary.resets_at, Some(1704074400)); | |
| assert_eq!(snapshot.secondary, None); | |
| } | |
| fn parse_rate_limit_for_limit_prefers_limit_name_header() { | |
| let mut headers = HeaderMap::new(); | |
| headers.insert( | |
| "x-codex-bengalfox-primary-used-percent", | |
| HeaderValue::from_static("80"), | |
| ); | |
| headers.insert( | |
| "x-codex-bengalfox-limit-name", | |
| HeaderValue::from_static("gpt-5.2-codex-sonic"), | |
| ); | |
| let snapshot = | |
| parse_rate_limit_for_limit(&headers, Some("codex_bengalfox")).expect("snapshot"); | |
| assert_eq!(snapshot.limit_id.as_deref(), Some("codex_bengalfox")); | |
| assert_eq!(snapshot.limit_name.as_deref(), Some("gpt-5.2-codex-sonic")); | |
| } | |
| fn parse_all_rate_limits_reads_all_limit_families() { | |
| let mut headers = HeaderMap::new(); | |
| headers.insert( | |
| "x-codex-primary-used-percent", | |
| HeaderValue::from_static("12.5"), | |
| ); | |
| headers.insert( | |
| "x-codex-secondary-primary-used-percent", | |
| HeaderValue::from_static("80"), | |
| ); | |
| let updates = parse_all_rate_limits(&headers); | |
| assert_eq!(updates.len(), 2); | |
| assert_eq!(updates[0].limit_id.as_deref(), Some("codex")); | |
| assert_eq!(updates[1].limit_id.as_deref(), Some("codex_secondary")); | |
| assert_eq!(updates[0].limit_name, None); | |
| assert_eq!(updates[1].limit_name, None); | |
| } | |
| fn parse_all_rate_limits_includes_default_codex_snapshot() { | |
| let headers = HeaderMap::new(); | |
| let updates = parse_all_rate_limits(&headers); | |
| assert_eq!(updates.len(), 1); | |
| assert_eq!(updates[0].limit_id.as_deref(), Some("codex")); | |
| assert_eq!(updates[0].limit_name, None); | |
| assert_eq!(updates[0].primary, None); | |
| assert_eq!(updates[0].secondary, None); | |
| assert_eq!(updates[0].credits, None); | |
| } | |
| } | |