mirror of
https://github.com/openai/codex.git
synced 2026-09-10 20:26:47 +00:00
Distinguish HTTP quota errors from rate limits (#44492)
## Why HTTP 429 responses for exhausted quota, credit balances, and spending or usage limits were reported as retry-limit failures instead of usage-limit errors. ## What changed Parse the API error's `code` and map `insufficient_quota`, `credit_balance_exhausted`, `organization_spend_limit_exceeded`, `project_spend_limit_exceeded`, and `organization_usage_limit_exceeded` to `CodexErr::QuotaExceeded`. Also recognize `insufficient_quota` in the error's `type` field. ## Testing Add a regression test covering all recognized quota errors and verifying that `rate_limit_exceeded` and `slow_down` HTTP 429 errors retain their existing retry-limit mapping. GitOrigin-RevId: b075dba199e08d29563630f5ada846e3e7fd11ff
This commit is contained in:
@@ -159,6 +159,19 @@ pub fn map_api_error(err: ApiError) -> CodexErr {
|
||||
});
|
||||
} else if err.error.error_type.as_deref() == Some("usage_not_included") {
|
||||
return CodexErr::UsageNotIncluded;
|
||||
} else if err.error.error_type.as_deref() == Some("insufficient_quota")
|
||||
|| matches!(
|
||||
err.error.code.as_deref(),
|
||||
Some(
|
||||
"insufficient_quota"
|
||||
| "credit_balance_exhausted"
|
||||
| "organization_spend_limit_exceeded"
|
||||
| "project_spend_limit_exceeded"
|
||||
| "organization_usage_limit_exceeded"
|
||||
)
|
||||
)
|
||||
{
|
||||
return CodexErr::QuotaExceeded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +276,7 @@ struct UsageErrorResponse {
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UsageErrorBody {
|
||||
code: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
error_type: Option<String>,
|
||||
plan_type: Option<PlanType>,
|
||||
|
||||
@@ -322,6 +322,36 @@ fn map_api_error_keeps_unknown_400_errors_generic() {
|
||||
assert_eq!(message, &body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_api_error_distinguishes_http_quota_errors_from_rate_limits() {
|
||||
for error in [
|
||||
serde_json::json!({"type": "insufficient_quota"}),
|
||||
serde_json::json!({"code": "insufficient_quota"}),
|
||||
serde_json::json!({"code": "credit_balance_exhausted"}),
|
||||
serde_json::json!({"code": "organization_spend_limit_exceeded"}),
|
||||
serde_json::json!({"code": "project_spend_limit_exceeded"}),
|
||||
serde_json::json!({"code": "organization_usage_limit_exceeded"}),
|
||||
serde_json::json!({"type": "rate_limit_error", "code": "rate_limit_exceeded"}),
|
||||
serde_json::json!({"type": "rate_limit_error", "code": "slow_down"}),
|
||||
] {
|
||||
let expected = if error["type"] == "rate_limit_error" {
|
||||
CodexErrorInfo::ResponseTooManyFailedAttempts {
|
||||
http_status_code: Some(429),
|
||||
}
|
||||
} else {
|
||||
CodexErrorInfo::UsageLimitExceeded
|
||||
};
|
||||
let err = map_api_error(ApiError::Transport(TransportError::Http {
|
||||
status: http::StatusCode::TOO_MANY_REQUESTS,
|
||||
url: None,
|
||||
headers: None,
|
||||
body: Some(serde_json::json!({"error": error}).to_string()),
|
||||
}));
|
||||
|
||||
assert_eq!(err.to_codex_protocol_error(), expected, "{error}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_api_error_maps_usage_limit_limit_name_header() {
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
Reference in New Issue
Block a user