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:
Adam Perry @ OpenAI
2026-09-30 02:12:42 +00:00
committed by copyberry
parent fc81a7154b
commit 6ba4bf9e64
6 changed files with 434 additions and 26 deletions
@@ -14,6 +14,7 @@ use crate::sse::process_responses_event;
use crate::telemetry::WebsocketTelemetry;
use codex_client::TransportError;
use codex_http_client::HttpClientFactory;
use codex_http_client::RetryAfter;
use codex_websocket_client::WebSocketConnection;
use codex_websocket_client::WebSocketConnector;
use futures::FutureExt;
@@ -570,6 +571,7 @@ fn map_ws_error(err: WsError, url: &Url) -> ApiError {
WsError::Http(response) => {
let status = response.status();
let headers = response.headers().clone();
let retry_after = RetryAfter::from_headers(&headers);
let body = response
.body()
.as_ref()
@@ -579,7 +581,7 @@ fn map_ws_error(err: WsError, url: &Url) -> ApiError {
url: Some(url.to_string()),
headers: Some(headers),
body,
retry_after: None,
retry_after,
})
}
WsError::ConnectionClosed | WsError::AlreadyClosed => {