Skip to content

Commit 55bc00d

Browse files
bahirulautofix-ci[bot]amitksingh1490
authored andcommitted
feat: openai/codex mark transport sse parse errors as retryable (#2566)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Amit Singh <amitksingh1490@gmail.com>
1 parent 43b23ca commit 55bc00d

1 file changed

Lines changed: 48 additions & 1 deletion

File tree

crates/forge_repo/src/provider/openai_responses/repository.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ impl<T: HttpInfra> OpenAIResponsesProvider<T> {
249249
Err(e) => Some(Err(e)),
250250
}
251251
}
252-
Err(e) => Some(Err(anyhow::anyhow!("SSE parse error: {}", e))),
252+
Err(e) => Some(Err(into_sse_parse_error(e))),
253253
}
254254
});
255255

@@ -259,6 +259,20 @@ impl<T: HttpInfra> OpenAIResponsesProvider<T> {
259259
}
260260
}
261261

262+
fn into_sse_parse_error<E>(error: eventsource_stream::EventStreamError<E>) -> anyhow::Error
263+
where
264+
E: std::fmt::Debug + std::fmt::Display + Send + Sync + 'static,
265+
{
266+
let is_retryable = matches!(&error, eventsource_stream::EventStreamError::Transport(_));
267+
let error = anyhow::anyhow!("SSE parse error: {}", error);
268+
269+
if is_retryable {
270+
forge_domain::Error::Retryable(error).into()
271+
} else {
272+
error
273+
}
274+
}
275+
262276
/// Derives an API base URL suitable for OpenAI Responses API from a configured
263277
/// endpoint URL.
264278
///
@@ -382,6 +396,12 @@ mod tests {
382396
use super::*;
383397
use crate::provider::mock_server::MockServer;
384398

399+
fn is_retryable(error: &anyhow::Error) -> bool {
400+
error
401+
.downcast_ref::<forge_domain::Error>()
402+
.is_some_and(|error| matches!(error, forge_domain::Error::Retryable(_)))
403+
}
404+
385405
fn make_credential(provider_id: ProviderId, key: &str) -> Option<forge_domain::AuthCredential> {
386406
Some(forge_domain::AuthCredential {
387407
id: provider_id,
@@ -803,6 +823,33 @@ mod tests {
803823
assert_eq!(headers[1].1, "value");
804824
}
805825

826+
#[test]
827+
fn test_into_sse_parse_error_marks_transport_errors_retryable() {
828+
let error = into_sse_parse_error(eventsource_stream::EventStreamError::Transport(
829+
anyhow::anyhow!("error decoding response body"),
830+
));
831+
832+
assert!(is_retryable(&error));
833+
assert_eq!(
834+
error.to_string(),
835+
"SSE parse error: Transport error: error decoding response body"
836+
);
837+
}
838+
839+
#[test]
840+
fn test_into_sse_parse_error_keeps_utf8_errors_non_retryable() {
841+
let error =
842+
into_sse_parse_error(eventsource_stream::EventStreamError::<anyhow::Error>::Utf8(
843+
String::from_utf8(vec![0xFF]).unwrap_err(),
844+
));
845+
846+
assert!(!is_retryable(&error));
847+
assert_eq!(
848+
error.to_string(),
849+
"SSE parse error: UTF8 error: invalid utf-8 sequence of 1 bytes from index 0"
850+
);
851+
}
852+
806853
#[test]
807854
fn test_get_headers_without_credential() {
808855
let provider = Provider {

0 commit comments

Comments
 (0)