diff --git a/codex-rs/codex-client/src/error.rs b/codex-rs/codex-client/src/error.rs index 086b91a5052..fa2bfb4f797 100644 --- a/codex-rs/codex-client/src/error.rs +++ b/codex-rs/codex-client/src/error.rs @@ -7,6 +7,7 @@ pub enum TransportError { #[error("http {status}: {body:?}")] Http { status: StatusCode, + url: Option, headers: Option, body: Option, }, diff --git a/codex-rs/codex-client/src/transport.rs b/codex-rs/codex-client/src/transport.rs index 50e9f8fab77..a2e6c66f5dd 100644 --- a/codex-rs/codex-client/src/transport.rs +++ b/codex-rs/codex-client/src/transport.rs @@ -131,6 +131,7 @@ impl HttpTransport for ReqwestTransport { ); } + let url = req.url.clone(); let builder = self.build(req)?; let resp = builder.send().await.map_err(Self::map_error)?; let status = resp.status(); @@ -140,6 +141,7 @@ impl HttpTransport for ReqwestTransport { let body = String::from_utf8(bytes.to_vec()).ok(); return Err(TransportError::Http { status, + url: Some(url), headers: Some(headers), body, }); @@ -161,6 +163,7 @@ impl HttpTransport for ReqwestTransport { ); } + let url = req.url.clone(); let builder = self.build(req)?; let resp = builder.send().await.map_err(Self::map_error)?; let status = resp.status(); @@ -169,6 +172,7 @@ impl HttpTransport for ReqwestTransport { let body = resp.text().await.ok(); return Err(TransportError::Http { status, + url: Some(url), headers: Some(headers), body, }); diff --git a/codex-rs/core/src/api_bridge.rs b/codex-rs/core/src/api_bridge.rs index a19ff5abb23..19bd8d5ecbb 100644 --- a/codex-rs/core/src/api_bridge.rs +++ b/codex-rs/core/src/api_bridge.rs @@ -25,11 +25,13 @@ pub(crate) fn map_api_error(err: ApiError) -> CodexErr { ApiError::Api { status, message } => CodexErr::UnexpectedStatus(UnexpectedResponseError { status, body: message, + url: None, request_id: None, }), ApiError::Transport(transport) => match transport { TransportError::Http { status, + url, headers, body, } => { @@ -71,6 +73,7 @@ pub(crate) fn map_api_error(err: ApiError) -> CodexErr { CodexErr::UnexpectedStatus(UnexpectedResponseError { status, body: body_text, + url, request_id: extract_request_id(headers.as_ref()), }) } diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index e0ee3003058..bec015b4c5c 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -533,6 +533,7 @@ async fn handle_unauthorized( fn map_unauthorized_status(status: StatusCode) -> CodexErr { map_api_error(ApiError::Transport(TransportError::Http { status, + url: None, headers: None, body: None, })) diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index 6818f3c4ad0..3b490436e43 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -277,6 +277,7 @@ pub enum RefreshTokenFailedReason { pub struct UnexpectedResponseError { pub status: StatusCode, pub body: String, + pub url: Option, pub request_id: Option, } @@ -293,7 +294,11 @@ impl UnexpectedResponseError { return None; } - let mut message = format!("{CLOUDFLARE_BLOCKED_MESSAGE} (status {})", self.status); + let status = self.status; + let mut message = format!("{CLOUDFLARE_BLOCKED_MESSAGE} (status {status})"); + if let Some(url) = &self.url { + message.push_str(&format!(", url: {url}")); + } if let Some(id) = &self.request_id { message.push_str(&format!(", request id: {id}")); } @@ -307,16 +312,16 @@ impl std::fmt::Display for UnexpectedResponseError { if let Some(friendly) = self.friendly_message() { write!(f, "{friendly}") } else { - write!( - f, - "unexpected status {}: {}{}", - self.status, - self.body, - self.request_id - .as_ref() - .map(|id| format!(", request id: {id}")) - .unwrap_or_default() - ) + let status = self.status; + let body = &self.body; + let mut message = format!("unexpected status {status}: {body}"); + if let Some(url) = &self.url { + message.push_str(&format!(", url: {url}")); + } + if let Some(id) = &self.request_id { + message.push_str(&format!(", request id: {id}")); + } + write!(f, "{message}") } } } @@ -826,12 +831,16 @@ mod tests { status: StatusCode::FORBIDDEN, body: "Cloudflare error: Sorry, you have been blocked" .to_string(), + url: Some("http://example.com/blocked".to_string()), request_id: Some("ray-id".to_string()), }; let status = StatusCode::FORBIDDEN.to_string(); + let url = "http://example.com/blocked"; assert_eq!( err.to_string(), - format!("{CLOUDFLARE_BLOCKED_MESSAGE} (status {status}), request id: ray-id") + format!( + "{CLOUDFLARE_BLOCKED_MESSAGE} (status {status}), url: {url}, request id: ray-id" + ) ); } @@ -840,12 +849,14 @@ mod tests { let err = UnexpectedResponseError { status: StatusCode::FORBIDDEN, body: "plain text error".to_string(), + url: Some("http://example.com/plain".to_string()), request_id: None, }; let status = StatusCode::FORBIDDEN.to_string(); + let url = "http://example.com/plain"; assert_eq!( err.to_string(), - format!("unexpected status {status}: plain text error") + format!("unexpected status {status}: plain text error, url: {url}") ); }