From 3b6610136122dd6d8db9acb6dcd9396fb7e777a4 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 16 Sep 2026 13:21:10 -0600 Subject: [PATCH] feat(TASK-044): surface the streaming response.incomplete event with its reason --- src/client/openai.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/client/openai.rs b/src/client/openai.rs index 3f1cad6..73a84fb 100644 --- a/src/client/openai.rs +++ b/src/client/openai.rs @@ -943,6 +943,12 @@ fn openai_responses_handle_event(data: &Value, handler: &mut SseHandler) -> Resu Some(message) => bail!("Response failed: {message}"), None => bail!("Response failed: {data}"), }, + Some("response.incomplete") => { + match data["response"]["incomplete_details"]["reason"].as_str() { + Some(reason) => bail!("The response was cut off: {reason}"), + None => bail!("The response was cut off: {data}"), + } + } Some("error") => match data["message"].as_str() { Some(message) => bail!("Stream error: {message}"), None => bail!("Stream error: {data}"), @@ -1378,6 +1384,28 @@ mod tests { assert!(err.contains("Rate limit reached"), "unexpected error: {err}"); } + #[test] + fn responses_stream_incomplete_event_bails_with_reason() { + let (sender, _receiver) = tokio::sync::mpsc::unbounded_channel(); + let abort_signal = crate::utils::create_abort_signal(); + let mut handler = SseHandler::new(sender, abort_signal); + let event = json!({ + "type": "response.incomplete", + "response": { + "incomplete_details": { "reason": "max_output_tokens" } + } + }); + + let err = openai_responses_handle_event(&event, &mut handler) + .unwrap_err() + .to_string(); + + assert!( + err.contains("The response was cut off: max_output_tokens"), + "unexpected error: {err}" + ); + } + fn openai_config(name: &str, auth: Option<&str>, oauth: Option) -> OpenAIConfig { OpenAIConfig { name: Some(name.into()),