fix(api): surface provider error in SSE stream frames (companion to ff416ff)

Same fix as ff416ff but for the streaming path. Some backends embed an
error JSON object in an SSE data: frame:

  data: {"error":{"message":"context too long","code":400}}

parse_sse_frame() was attempting to deserialize this as ChatCompletionChunk
and failing with 'missing field' / 'invalid type', hiding the actual
backend error message.

Fix: check for an 'error' key before full chunk deserialization, same as
the non-streaming path in ff416ff. Symmetric pair:
- ff416ff: non-streaming path (response body)
- this:    streaming path (SSE data: frame)

115 api + 159 CLI tests pass. Fmt clean.
This commit is contained in:
YeonGyu-Kim
2026-04-09 23:03:33 +09:00
parent ff416ff3e7
commit ed42f8f298

View File

@@ -1084,6 +1084,35 @@ fn parse_sse_frame(
if payload == "[DONE]" { if payload == "[DONE]" {
return Ok(None); return Ok(None);
} }
// Some backends embed an error object in a data: frame instead of using an
// HTTP error status. Surface the error message directly rather than letting
// ChatCompletionChunk deserialization fail with a cryptic 'missing field' error.
if let Ok(raw) = serde_json::from_str::<serde_json::Value>(&payload) {
if let Some(err_obj) = raw.get("error") {
let msg = err_obj
.get("message")
.and_then(|m| m.as_str())
.unwrap_or("provider returned an error in stream")
.to_string();
let code = err_obj
.get("code")
.and_then(|c| c.as_u64())
.map(|c| c as u16);
let status = reqwest::StatusCode::from_u16(code.unwrap_or(400))
.unwrap_or(reqwest::StatusCode::BAD_REQUEST);
return Err(ApiError::Api {
status,
error_type: err_obj
.get("type")
.and_then(|t| t.as_str())
.map(str::to_owned),
message: Some(msg),
request_id: None,
body: payload.to_string(),
retryable: false,
});
}
}
serde_json::from_str::<ChatCompletionChunk>(&payload) serde_json::from_str::<ChatCompletionChunk>(&payload)
.map(Some) .map(Some)
.map_err(|error| ApiError::json_deserialize(provider, model, &payload, error)) .map_err(|error| ApiError::json_deserialize(provider, model, &payload, error))