mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Compare commits
5 Commits
dh--git-in
...
tibo/rate-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
720c69bd03 | ||
|
|
f5af9cc8ba | ||
|
|
722826afff | ||
|
|
af3552d428 | ||
|
|
211b681499 |
@@ -53,6 +53,7 @@ use crate::state::TaskKind;
|
||||
use crate::token_data::PlanType;
|
||||
use crate::util::backoff;
|
||||
use chrono::DateTime;
|
||||
use chrono::TimeZone;
|
||||
use chrono::Utc;
|
||||
use codex_otel::otel_event_manager::OtelEventManager;
|
||||
use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig;
|
||||
@@ -76,6 +77,19 @@ struct Error {
|
||||
resets_at: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_reset_timestamp(value: Option<&str>) -> Option<DateTime<Utc>> {
|
||||
value.and_then(|raw| {
|
||||
raw.parse::<i64>()
|
||||
.ok()
|
||||
.and_then(|secs| Utc.timestamp_opt(secs, 0).single())
|
||||
.or_else(|| {
|
||||
DateTime::parse_from_rfc3339(raw)
|
||||
.map(|dt| dt.with_timezone(&Utc))
|
||||
.ok()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModelClient {
|
||||
config: Arc<Config>,
|
||||
@@ -423,11 +437,7 @@ impl ModelClient {
|
||||
let plan_type = error
|
||||
.plan_type
|
||||
.or_else(|| auth.as_ref().and_then(CodexAuth::get_plan_type));
|
||||
let resets_at = error
|
||||
.resets_at
|
||||
.as_deref()
|
||||
.and_then(|value| DateTime::parse_from_rfc3339(value).ok())
|
||||
.map(|dt| dt.with_timezone(&Utc));
|
||||
let resets_at = parse_reset_timestamp(error.resets_at.as_deref());
|
||||
let codex_err = CodexErr::UsageLimitReached(UsageLimitReachedError {
|
||||
plan_type,
|
||||
resets_at,
|
||||
@@ -639,7 +649,13 @@ fn parse_rate_limit_window(
|
||||
let resets_at = parse_header_str(headers, resets_header)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(std::string::ToString::to_string);
|
||||
.and_then(|value| {
|
||||
value.parse::<i64>().ok().or_else(|| {
|
||||
DateTime::parse_from_rfc3339(value)
|
||||
.map(|dt| dt.timestamp())
|
||||
.ok()
|
||||
})
|
||||
});
|
||||
|
||||
let has_data = used_percent != 0.0
|
||||
|| window_minutes.is_some_and(|minutes| minutes != 0)
|
||||
@@ -1430,6 +1446,10 @@ mod tests {
|
||||
let resp: ErrorResponse = serde_json::from_str(json).expect("should deserialize schema");
|
||||
|
||||
assert_matches!(resp.error.plan_type, Some(PlanType::Known(KnownPlan::Pro)));
|
||||
assert_eq!(
|
||||
resp.error.resets_at.as_deref(),
|
||||
Some("2024-01-01T00:00:00Z")
|
||||
);
|
||||
|
||||
let plan_json = serde_json::to_string(&resp.error.plan_type).expect("serialize plan_type");
|
||||
assert_eq!(plan_json, "\"pro\"");
|
||||
@@ -1443,6 +1463,10 @@ mod tests {
|
||||
let resp: ErrorResponse = serde_json::from_str(json).expect("should deserialize schema");
|
||||
|
||||
assert_matches!(resp.error.plan_type, Some(PlanType::Unknown(ref s)) if s == "vip");
|
||||
assert_eq!(
|
||||
resp.error.resets_at.as_deref(),
|
||||
Some("2024-01-01T00:01:00Z")
|
||||
);
|
||||
|
||||
let plan_json = serde_json::to_string(&resp.error.plan_type).expect("serialize plan_type");
|
||||
assert_eq!(plan_json, "\"vip\"");
|
||||
|
||||
@@ -425,12 +425,20 @@ mod tests {
|
||||
primary: Some(RateLimitWindow {
|
||||
used_percent: 50.0,
|
||||
window_minutes: Some(60),
|
||||
resets_at: Some("2024-01-01T01:00:00Z".to_string()),
|
||||
resets_at: Some(
|
||||
Utc.with_ymd_and_hms(2024, 1, 1, 1, 0, 0)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
),
|
||||
}),
|
||||
secondary: Some(RateLimitWindow {
|
||||
used_percent: 30.0,
|
||||
window_minutes: Some(120),
|
||||
resets_at: Some("2024-01-01T02:00:00Z".to_string()),
|
||||
resets_at: Some(
|
||||
Utc.with_ymd_and_hms(2024, 1, 1, 2, 0, 0)
|
||||
.unwrap()
|
||||
.timestamp(),
|
||||
),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,12 +818,12 @@ async fn token_count_includes_rate_limits_snapshot() {
|
||||
"primary": {
|
||||
"used_percent": 12.5,
|
||||
"window_minutes": 10,
|
||||
"resets_at": "2024-01-01T00:30:00Z"
|
||||
"resets_at": 1704069000
|
||||
},
|
||||
"secondary": {
|
||||
"used_percent": 40.0,
|
||||
"window_minutes": 60,
|
||||
"resets_at": "2024-01-01T02:00:00Z"
|
||||
"resets_at": 1704074400
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -865,12 +865,12 @@ async fn token_count_includes_rate_limits_snapshot() {
|
||||
"primary": {
|
||||
"used_percent": 12.5,
|
||||
"window_minutes": 10,
|
||||
"resets_at": "2024-01-01T00:30:00Z"
|
||||
"resets_at": 1704069000
|
||||
},
|
||||
"secondary": {
|
||||
"used_percent": 40.0,
|
||||
"window_minutes": 60,
|
||||
"resets_at": "2024-01-01T02:00:00Z"
|
||||
"resets_at": 1704074400
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -893,8 +893,8 @@ async fn token_count_includes_rate_limits_snapshot() {
|
||||
final_snapshot
|
||||
.primary
|
||||
.as_ref()
|
||||
.and_then(|window| window.resets_at.as_deref()),
|
||||
Some("2024-01-01T00:30:00Z")
|
||||
.and_then(|window| window.resets_at),
|
||||
Some(1704069000)
|
||||
);
|
||||
|
||||
wait_for_event(&codex, |msg| matches!(msg, EventMsg::TaskComplete(_))).await;
|
||||
|
||||
@@ -658,9 +658,9 @@ pub struct RateLimitWindow {
|
||||
/// Rolling window duration, in minutes.
|
||||
#[ts(type = "number | null")]
|
||||
pub window_minutes: Option<i64>,
|
||||
/// Timestamp (RFC3339) when the window resets.
|
||||
#[ts(type = "string | null")]
|
||||
pub resets_at: Option<String>,
|
||||
/// Timestamp (Unix seconds) when the window resets.
|
||||
#[ts(type = "number | null")]
|
||||
pub resets_at: Option<i64>,
|
||||
}
|
||||
|
||||
// Includes prompts, tools and space to call compact.
|
||||
|
||||
@@ -3,6 +3,8 @@ use crate::chatwidget::get_limits_duration;
|
||||
use super::helpers::format_reset_timestamp;
|
||||
use chrono::DateTime;
|
||||
use chrono::Local;
|
||||
use chrono::TimeZone;
|
||||
use chrono::Utc;
|
||||
use codex_core::protocol::RateLimitSnapshot;
|
||||
use codex_core::protocol::RateLimitWindow;
|
||||
|
||||
@@ -34,8 +36,7 @@ impl RateLimitWindowDisplay {
|
||||
fn from_window(window: &RateLimitWindow, captured_at: DateTime<Local>) -> Self {
|
||||
let resets_at = window
|
||||
.resets_at
|
||||
.as_deref()
|
||||
.and_then(|value| DateTime::parse_from_rfc3339(value).ok())
|
||||
.and_then(|value| Utc.timestamp_opt(value, 0).single())
|
||||
.map(|dt| dt.with_timezone(&Local))
|
||||
.map(|dt| format_reset_timestamp(dt, captured_at));
|
||||
|
||||
|
||||
@@ -62,10 +62,10 @@ fn sanitize_directory(lines: Vec<String>) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn reset_at_from(captured_at: &chrono::DateTime<chrono::Local>, seconds: i64) -> String {
|
||||
fn reset_at_from(captured_at: &chrono::DateTime<chrono::Local>, seconds: i64) -> i64 {
|
||||
(*captured_at + ChronoDuration::seconds(seconds))
|
||||
.with_timezone(&Utc)
|
||||
.to_rfc3339()
|
||||
.timestamp()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user