feat: support multiple rate limits (#11260)

Added multi-limit support end-to-end by carrying limit_name in
rate-limit snapshots and handling multiple buckets instead of only
codex.
Extended /usage client parsing to consume additional_rate_limits
Updated TUI /status and in-memory state to store/render per-limit
snapshots
Extended app-server rate-limit read response: kept rate_limits and added
rate_limits_by_name.
Adjusted usage-limit error messaging for non-default codex limit buckets
This commit is contained in:
xl-openai
2026-02-10 20:09:31 -08:00
committed by GitHub
Unverified
parent 641d5268fa
commit fdd0cd1de9
36 changed files with 1435 additions and 169 deletions
@@ -2206,6 +2206,8 @@ mod tests {
model_context_window: Some(4096),
};
let rate_limits = RateLimitSnapshot {
limit_id: Some("codex".to_string()),
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 42.5,
window_minutes: Some(15),
@@ -2258,6 +2260,8 @@ mod tests {
OutgoingMessage::AppServerNotification(
ServerNotification::AccountRateLimitsUpdated(payload),
) => {
assert_eq!(payload.rate_limits.limit_id.as_deref(), Some("codex"));
assert_eq!(payload.rate_limits.limit_name, None);
assert!(payload.rate_limits.primary.is_some());
assert!(payload.rate_limits.credits.is_some());
}
@@ -1425,9 +1425,15 @@ impl CodexMessageProcessor {
async fn get_account_rate_limits(&self, request_id: RequestId) {
match self.fetch_account_rate_limits().await {
Ok(rate_limits) => {
Ok((rate_limits, rate_limits_by_limit_id)) => {
let response = GetAccountRateLimitsResponse {
rate_limits: rate_limits.into(),
rate_limits_by_limit_id: Some(
rate_limits_by_limit_id
.into_iter()
.map(|(limit_id, snapshot)| (limit_id, snapshot.into()))
.collect(),
),
};
self.outgoing.send_response(request_id, response).await;
}
@@ -1437,7 +1443,15 @@ impl CodexMessageProcessor {
}
}
async fn fetch_account_rate_limits(&self) -> Result<CoreRateLimitSnapshot, JSONRPCErrorError> {
async fn fetch_account_rate_limits(
&self,
) -> Result<
(
CoreRateLimitSnapshot,
HashMap<String, CoreRateLimitSnapshot>,
),
JSONRPCErrorError,
> {
let Some(auth) = self.auth_manager.auth().await else {
return Err(JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
@@ -1461,14 +1475,41 @@ impl CodexMessageProcessor {
data: None,
})?;
client
.get_rate_limits()
let snapshots = client
.get_rate_limits_many()
.await
.map_err(|err| JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: format!("failed to fetch codex rate limits: {err}"),
data: None,
})?;
if snapshots.is_empty() {
return Err(JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: "failed to fetch codex rate limits: no snapshots returned".to_string(),
data: None,
});
}
let rate_limits_by_limit_id: HashMap<String, CoreRateLimitSnapshot> = snapshots
.iter()
.cloned()
.map(|snapshot| {
let limit_id = snapshot
.limit_id
.clone()
.unwrap_or_else(|| "codex".to_string());
(limit_id, snapshot)
})
.collect();
let primary = snapshots
.iter()
.find(|snapshot| snapshot.limit_id.as_deref() == Some("codex"))
.cloned()
.unwrap_or_else(|| snapshots[0].clone());
Ok((primary, rate_limits_by_limit_id))
}
async fn get_user_saved_config(&self, request_id: RequestId) {
+5 -1
View File
@@ -283,6 +283,8 @@ mod tests {
let notification =
ServerNotification::AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification {
rate_limits: RateLimitSnapshot {
limit_id: Some("codex".to_string()),
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 25,
window_duration_mins: Some(15),
@@ -299,7 +301,9 @@ mod tests {
json!({
"method": "account/rateLimits/updated",
"params": {
"rateLimits": {
"rateLimits": {
"limitId": "codex",
"limitName": null,
"primary": {
"usedPercent": 25,
"windowDurationMins": 15,