mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Keep response streams alive through connection failures (#37485)
## What changed - Classify HTTP connection failures separately from other network errors without exposing request URLs. - For sampling requests, retry connection failures with exponential delays from 5 to 60 seconds and show a `Reconnecting... waiting for network` stream error. - Preserve the normal stream retry budget while waiting for the provider to become reachable. Keep the existing bounded retry behavior for other retryable errors. ## Testing - Verify connection errors are classified without leaking URL contents. - Verify a turn recovers after its provider becomes reachable and still applies the configured retry limit to a subsequent incomplete stream. GitOrigin-RevId: 646553290c865a1332abd30c4a64ed9266bbfc6f
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
//! Errors returned by the shared Codex HTTP transport.
|
||||
|
||||
use crate::client::HttpError;
|
||||
use http::HeaderMap;
|
||||
use http::StatusCode;
|
||||
use thiserror::Error;
|
||||
@@ -17,6 +18,8 @@ pub enum TransportError {
|
||||
RetryLimit,
|
||||
#[error("timeout")]
|
||||
Timeout,
|
||||
#[error("connection failed: {0}")]
|
||||
Connection(#[source] HttpError),
|
||||
#[error("network error: {0}")]
|
||||
Network(String),
|
||||
#[error("request build error: {0}")]
|
||||
|
||||
@@ -78,7 +78,9 @@ impl ReqwestTransport {
|
||||
}
|
||||
|
||||
fn map_error(err: reqwest::Error) -> TransportError {
|
||||
if err.is_timeout() {
|
||||
if err.is_connect() {
|
||||
TransportError::Connection(err.without_url())
|
||||
} else if err.is_timeout() {
|
||||
TransportError::Timeout
|
||||
} else {
|
||||
TransportError::Network(err.to_string())
|
||||
|
||||
@@ -28,6 +28,28 @@ async fn disabled_request_logging_suppresses_transport_url_and_body() {
|
||||
assert!(!logs.contains("body-secret"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connection_failures_are_classified_without_exposing_request_urls() {
|
||||
let unavailable_server =
|
||||
std::net::TcpListener::bind(("127.0.0.1", 0)).expect("server port should bind");
|
||||
let server_addr = unavailable_server
|
||||
.local_addr()
|
||||
.expect("server listener should have an address");
|
||||
drop(unavailable_server);
|
||||
let transport = ReqwestTransport::from_http_client(HttpClient::new(test_reqwest_client()));
|
||||
let request = Request::new(
|
||||
Method::POST,
|
||||
format!("http://{server_addr}/responses?token=url-secret"),
|
||||
);
|
||||
|
||||
let error = match transport.stream(request).await {
|
||||
Err(TransportError::Connection(error)) => error,
|
||||
Err(error) => panic!("expected a connection failure, got {error}"),
|
||||
Ok(_) => panic!("an unavailable server should not return a response"),
|
||||
};
|
||||
assert!(!error.to_string().contains("url-secret"));
|
||||
}
|
||||
|
||||
fn test_reqwest_client() -> reqwest::Client {
|
||||
reqwest::Client::builder()
|
||||
.no_proxy()
|
||||
|
||||
Reference in New Issue
Block a user