mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Honor server retry advice across Responses retries and fallback (#49441)
## Why Responses overload and exhausted-request errors could terminate despite server retry advice, and WebSocket-to-HTTP fallback could issue a request before the advised deadline. ## What changed - Allow `ServerOverloaded` and `RetryLimit` errors to retry when server advice is present, within the configured retry budgets. - Preserve `Retry-After` headers from rejected WebSocket upgrades and wait for the server deadline before falling back to HTTP. - Keep quota, usage-limit, and policy failures terminal even when they include retry advice. ## Testing Add coverage for HTTP overload and rate-limit recovery, WebSocket upgrade rejection and fallback timing, retry deadline preservation through error mapping, and retry limits for sampling and manual and automatic compaction. GitOrigin-RevId: 72d30a9f078652664754dfa971fd7dc7a78f20e8
This commit is contained in:
committed by
copyberry
parent
fc81a7154b
commit
6ba4bf9e64
@@ -74,6 +74,56 @@ async fn map_api_error_preserves_retry_delay() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mapping a new error category preserves elapsed advice without making hard failures retryable.
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn http_retry_deadline_survives_mapping_and_respects_terminal_errors() {
|
||||
use std::time::Duration;
|
||||
|
||||
let advice = RetryAfter::from_delay(Duration::from_secs(10)).expect("retry advice");
|
||||
tokio::time::advance(Duration::from_secs(4)).await;
|
||||
for (status, code, retryable) in [
|
||||
(503, "server_is_overloaded", true),
|
||||
(429, "rate_limit_exceeded", true),
|
||||
(429, "insufficient_quota", false),
|
||||
(429, "usage_limit_reached", false),
|
||||
(400, "cyber_policy", false),
|
||||
] {
|
||||
let error = map_api_error(ApiError::Transport(TransportError::Http {
|
||||
status: http::StatusCode::from_u16(status).unwrap(),
|
||||
url: None,
|
||||
headers: None,
|
||||
body: Some(serde_json::json!({"error": {"type": code, "code": code, "message": "sensitive upstream detail"}}).to_string()),
|
||||
retry_after: Some(advice),
|
||||
}));
|
||||
assert_eq!(
|
||||
(error.retry_after(), error.server_retry_delay()),
|
||||
(Some(advice), Some(Duration::from_secs(6))),
|
||||
"{code}",
|
||||
);
|
||||
assert_eq!(
|
||||
error.retry_delay(/*retry_count*/ 1),
|
||||
retryable.then_some(Duration::from_secs(6)),
|
||||
"{code}",
|
||||
);
|
||||
if status == 429 && retryable {
|
||||
assert_eq!(
|
||||
(
|
||||
error.to_codex_protocol_error(),
|
||||
error.http_status_code_value(),
|
||||
error.to_string()
|
||||
),
|
||||
(
|
||||
CodexErrorInfo::ResponseTooManyFailedAttempts {
|
||||
http_status_code: Some(429),
|
||||
},
|
||||
Some(429),
|
||||
"exceeded retry limit, last status: 429 Too Many Requests".to_string(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_api_error_distinguishes_capacity_from_slow_down() {
|
||||
for (code, expected, retryable) in [
|
||||
|
||||
Reference in New Issue
Block a user