1use std::{collections::VecDeque, pin::Pin};
7
8use futures_util::{Stream, StreamExt};
9
10use crate::{ZaiError, ZaiResult, client::error::codes};
11
12pub(crate) type DecodedSseStream<T> = Pin<Box<dyn Stream<Item = ZaiResult<T>> + Send + 'static>>;
13
14#[derive(Debug, Default)]
19pub struct SseEventParser {
20 buf: Vec<u8>,
21 event_data: Vec<Vec<u8>>,
22}
23
24impl SseEventParser {
25 pub fn new() -> Self {
27 Self::default()
28 }
29
30 pub(crate) fn buffered_len(&self) -> usize {
32 let payload_bytes = self
33 .event_data
34 .iter()
35 .fold(0usize, |total, line| total.saturating_add(line.len()));
36 self.buf
37 .len()
38 .saturating_add(payload_bytes)
39 .saturating_add(self.event_data.len().saturating_sub(1))
40 }
41
42 pub fn push(&mut self, new_bytes: &[u8]) -> Vec<Vec<u8>> {
44 self.buf.extend_from_slice(new_bytes);
45 let mut events = Vec::with_capacity(4);
46
47 let mut consumed = 0;
51 while let Some(rel) = self.buf[consumed..].iter().position(|&b| b == b'\n') {
52 let newline = consumed + rel;
53 let end = if newline > consumed && self.buf[newline - 1] == b'\r' {
55 newline - 1
56 } else {
57 newline
58 };
59 let line = &self.buf[consumed..end];
60 consumed = newline + 1;
61
62 if line.is_empty() {
63 match self.event_data.len() {
68 0 => {},
69 1 => {
70 events.push(self.event_data.swap_remove(0));
71 },
72 _ => {
73 events.push(join_event_data(&self.event_data));
74 self.event_data.clear();
75 },
76 }
77 continue;
78 }
79
80 if line.starts_with(b":") {
81 continue;
82 }
83
84 if let Some(rest) = line.strip_prefix(b"data:") {
85 self.event_data.push(trim_one_leading_space(rest).to_vec());
86 }
87 }
88
89 self.buf.drain(..consumed);
90 events
91 }
92
93 pub fn finish(&mut self) -> Vec<Vec<u8>> {
109 match self.event_data.len() {
110 0 => Vec::new(),
111 1 => vec![self.event_data.swap_remove(0)],
112 _ => {
113 let event = join_event_data(&self.event_data);
114 self.event_data.clear();
115 vec![event]
116 },
117 }
118 }
119}
120
121struct RequiredDoneState {
122 raw: crate::client::transport::SseByteStream,
123 parser: SseEventParser,
124 pending: VecDeque<Vec<u8>>,
125 input_finished: bool,
126 terminated: bool,
127}
128
129pub(crate) fn decode_required_done_stream<T, F>(
135 raw: crate::client::transport::SseByteStream,
136 decode: F,
137) -> DecodedSseStream<T>
138where
139 T: Send + 'static,
140 F: Fn(&[u8]) -> ZaiResult<T> + Send + 'static,
141{
142 let payloads = required_done_payloads(raw);
143 let stream = futures_util::stream::unfold(
144 (payloads, decode, false),
145 |(mut payloads, decode, terminated)| async move {
146 if terminated {
147 return None;
148 }
149 let item = payloads.next().await?;
150 let item = item.and_then(|payload| decode(&payload));
151 let terminated = item.is_err();
152 Some((item, (payloads, decode, terminated)))
153 },
154 );
155 Box::pin(stream)
156}
157
158fn required_done_payloads(
159 raw: crate::client::transport::SseByteStream,
160) -> Pin<Box<dyn Stream<Item = ZaiResult<Vec<u8>>> + Send + 'static>> {
161 let state = RequiredDoneState {
162 raw,
163 parser: SseEventParser::new(),
164 pending: VecDeque::new(),
165 input_finished: false,
166 terminated: false,
167 };
168 let stream = futures_util::stream::unfold(state, |mut state| async move {
169 loop {
170 if state.terminated {
171 return None;
172 }
173
174 if let Some(payload) = state.pending.pop_front() {
175 if payload == b"[DONE]" {
176 return None;
177 }
178 if (payload.len() as u64) > crate::client::transport::limits::JSON_RESPONSE_MAX {
179 state.terminated = true;
180 return Some((Err(event_too_large()), state));
181 }
182 if let Some(error) = std::str::from_utf8(&payload)
183 .ok()
184 .and_then(crate::client::transport::decode::extract_error_envelope)
185 {
186 state.terminated = true;
187 return Some((Err(business_error(error)), state));
188 }
189 return Some((Ok(payload), state));
190 }
191
192 if state.input_finished {
193 state.pending.extend(state.parser.finish());
194 if state.pending.is_empty() {
195 state.terminated = true;
196 return Some((Err(ended_without_done()), state));
197 }
198 continue;
199 }
200
201 match state.raw.next().await {
202 Some(Ok(chunk)) => {
203 state.pending.extend(state.parser.push(&chunk));
204 if state.parser.buffered_len()
205 > crate::client::transport::limits::JSON_RESPONSE_MAX as usize
206 {
207 state.terminated = true;
208 return Some((Err(event_too_large()), state));
209 }
210 },
211 Some(Err(error)) => {
212 state.terminated = true;
213 return Some((Err(error), state));
214 },
215 None => state.input_finished = true,
216 }
217 }
218 });
219 Box::pin(stream)
220}
221
222fn event_too_large() -> ZaiError {
223 ZaiError::ApiError {
224 code: codes::SDK_VALIDATION,
225 message: format!(
226 "SSE event exceeded limit ({} bytes)",
227 crate::client::transport::limits::JSON_RESPONSE_MAX
228 ),
229 }
230}
231
232fn ended_without_done() -> ZaiError {
233 ZaiError::ApiError {
234 code: codes::SDK_IO,
235 message: "SSE stream ended before the required [DONE] event".to_string(),
236 }
237}
238
239fn business_error(error: crate::client::transport::decode::BusinessError) -> ZaiError {
240 let code = error
241 .code
242 .as_ref()
243 .and_then(crate::client::transport::parse_business_code)
244 .unwrap_or_default();
245 ZaiError::from_api_response(200, code, error.message)
246}
247
248fn trim_one_leading_space(bytes: &[u8]) -> &[u8] {
249 bytes.strip_prefix(b" ").unwrap_or(bytes)
250}
251
252fn join_event_data(lines: &[Vec<u8>]) -> Vec<u8> {
253 let total = lines.iter().map(std::vec::Vec::len).sum::<usize>() + lines.len().saturating_sub(1);
254 let mut event = Vec::with_capacity(total);
255 for (idx, line) in lines.iter().enumerate() {
256 if idx > 0 {
257 event.push(b'\n');
258 }
259 event.extend_from_slice(line);
260 }
261 event
262}
263
264pub fn extract_sse_data_lines(buf: &mut Vec<u8>, new_bytes: &[u8]) -> Vec<Vec<u8>> {
278 buf.extend_from_slice(new_bytes);
279 let mut results = Vec::new();
280
281 let Some(last_newline) = buf.iter().rposition(|&b| b == b'\n') else {
282 return results;
283 };
284
285 let completed = &buf[..=last_newline];
286 for line_with_nl in completed.split_inclusive(|&b| b == b'\n') {
287 let mut line = line_with_nl;
288 if let Some(line_without_nl) = line.strip_suffix(b"\n") {
289 line = line_without_nl;
290 }
291 if let Some(line_without_cr) = line.strip_suffix(b"\r") {
292 line = line_without_cr;
293 }
294 if line.is_empty() {
295 continue;
296 }
297 if let Some(rest) = line.strip_prefix(b"data:") {
298 results.push(trim_one_leading_space(rest).to_vec());
299 }
300 }
301
302 buf.drain(..=last_newline);
303
304 results
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use bytes::Bytes;
311
312 #[test]
313 fn test_single_complete_line() {
314 let mut buf = Vec::new();
315 let lines = extract_sse_data_lines(&mut buf, b"data: hello\n");
316 assert_eq!(lines.len(), 1);
317 assert_eq!(lines[0], b"hello");
318 }
319
320 #[test]
321 fn test_data_field_without_optional_space() {
322 let mut buf = Vec::new();
323 let lines = extract_sse_data_lines(&mut buf, b"data:hello\n");
324 assert_eq!(lines, vec![b"hello".to_vec()]);
325 }
326
327 #[test]
328 fn test_partial_then_complete() {
329 let mut buf = Vec::new();
330 let lines1 = extract_sse_data_lines(&mut buf, b"data: hel");
331 assert!(lines1.is_empty());
332
333 let lines2 = extract_sse_data_lines(&mut buf, b"lo\n");
334 assert_eq!(lines2.len(), 1);
335 assert_eq!(lines2[0], b"hello");
336 }
337
338 #[test]
339 fn test_crlf_line_endings() {
340 let mut buf = Vec::new();
341 let lines = extract_sse_data_lines(&mut buf, b"data: world\r\n");
342 assert_eq!(lines.len(), 1);
343 assert_eq!(lines[0], b"world");
344 }
345
346 #[test]
347 fn test_multiple_events_in_one_chunk() {
348 let mut buf = Vec::new();
349 let lines = extract_sse_data_lines(&mut buf, b"data: first\n\ndata: second\n");
350 assert_eq!(lines.len(), 2);
351 assert_eq!(lines[0], b"first");
352 assert_eq!(lines[1], b"second");
353 }
354
355 #[test]
356 fn test_done_marker() {
357 let mut buf = Vec::new();
358 let lines = extract_sse_data_lines(&mut buf, b"data: [DONE]\n");
359 assert_eq!(lines.len(), 1);
360 assert_eq!(lines[0], b"[DONE]");
361 }
362
363 #[test]
364 fn test_non_data_lines_skipped() {
365 let mut buf = Vec::new();
366 let lines = extract_sse_data_lines(&mut buf, b": comment\nid: 123\ndata: payload\n");
367 assert_eq!(lines.len(), 1);
368 assert_eq!(lines[0], b"payload");
369 }
370
371 #[test]
372 fn test_empty_lines_ignored() {
373 let mut buf = Vec::new();
374 let lines = extract_sse_data_lines(&mut buf, b"\n\n\ndata: hello\n\n");
375 assert_eq!(lines.len(), 1);
376 assert_eq!(lines[0], b"hello");
377 }
378
379 #[test]
380 fn event_parser_yields_complete_events() {
381 let mut parser = SseEventParser::new();
382 assert!(parser.push(b"data: hel").is_empty());
383 assert_eq!(parser.push(b"lo\r\n\r\n"), vec![b"hello".to_vec()]);
384 }
385
386 #[test]
387 fn event_parser_joins_multi_data_lines() {
388 let mut parser = SseEventParser::new();
389 let events = parser.push(b"data: {\"a\":\ndata: 1}\n\n");
390 assert_eq!(events, vec![b"{\"a\":\n1}".to_vec()]);
391 }
392
393 #[test]
394 fn event_parser_ignores_comments_and_non_data_fields() {
395 let mut parser = SseEventParser::new();
396 let events = parser.push(b": keepalive\nid: 1\ndata: payload\n\n");
397 assert_eq!(events, vec![b"payload".to_vec()]);
398 }
399
400 #[test]
401 fn event_parser_done_marker_split_across_chunks() {
402 let mut parser = SseEventParser::new();
406 assert!(parser.push(b"data: [DO").is_empty());
407 assert_eq!(parser.push(b"NE]\n\n"), vec![b"[DONE]".to_vec()]);
408 }
409
410 #[test]
411 fn event_parser_lone_cr_then_lf() {
412 let mut parser = SseEventParser::new();
415 assert!(parser.push(b"data: hi\r").is_empty());
416 assert_eq!(parser.push(b"\n\n"), vec![b"hi".to_vec()]);
417 }
418
419 #[test]
420 fn extract_sse_handles_crlf_split_across_chunks() {
421 let mut buf = Vec::new();
422 assert!(extract_sse_data_lines(&mut buf, b"data: hello\r").is_empty());
423 let lines = extract_sse_data_lines(&mut buf, b"\n");
424 assert_eq!(lines, vec![b"hello".to_vec()]);
425 }
426
427 #[test]
428 fn finish_flushes_trailing_event_without_blank_line() {
429 let mut parser = SseEventParser::new();
434 assert!(parser.push(b"data: hello\n").is_empty()); assert_eq!(parser.finish(), vec![b"hello".to_vec()]);
436 assert!(parser.finish().is_empty());
438 }
439
440 #[test]
441 fn finish_flushes_multi_data_trailing_event_joined() {
442 let mut parser = SseEventParser::new();
443 assert!(parser.push(b"data: {\"a\":\ndata: 1}\n").is_empty());
444 assert_eq!(parser.finish(), vec![b"{\"a\":\n1}".to_vec()]);
445 }
446
447 #[test]
448 fn finish_noop_when_event_already_dispatched() {
449 let mut parser = SseEventParser::new();
450 assert_eq!(parser.push(b"data: hello\n\n"), vec![b"hello".to_vec()]);
451 assert!(parser.finish().is_empty());
452 }
453
454 #[test]
455 fn finish_emits_trailing_done_marker_without_blank_line() {
456 let mut parser = SseEventParser::new();
459 assert!(parser.push(b"data: [DONE]\n").is_empty());
460 assert_eq!(parser.finish(), vec![b"[DONE]".to_vec()]);
461 }
462
463 #[tokio::test]
464 async fn required_done_stream_handles_transport_fragmentation() {
465 let raw: crate::client::transport::SseByteStream = Box::pin(futures_util::stream::iter([
466 Ok(Bytes::from_static(b"data: {\"value\":")),
467 Ok(Bytes::from_static(b"1}\n\ndata: [DO")),
468 Ok(Bytes::from_static(b"NE]\n\n")),
469 ]));
470 let mut stream = decode_required_done_stream(raw, |payload| {
471 serde_json::from_slice::<serde_json::Value>(payload).map_err(ZaiError::from)
472 });
473 assert_eq!(stream.next().await.unwrap().unwrap()["value"], 1);
474 assert!(stream.next().await.is_none());
475 }
476
477 #[tokio::test]
478 async fn required_done_stream_reports_missing_marker_once() {
479 let raw: crate::client::transport::SseByteStream =
480 Box::pin(futures_util::stream::iter([Ok(Bytes::from_static(
481 b"data: {\"value\":1}\n\n",
482 ))]));
483 let mut stream = decode_required_done_stream(raw, |payload| {
484 serde_json::from_slice::<serde_json::Value>(payload).map_err(ZaiError::from)
485 });
486 assert!(stream.next().await.unwrap().is_ok());
487 let error = stream.next().await.unwrap().unwrap_err();
488 assert_eq!(error.code(), Some(codes::SDK_IO));
489 assert!(error.message().contains("[DONE]"));
490 assert!(stream.next().await.is_none());
491 }
492
493 #[tokio::test]
494 async fn required_done_stream_terminates_after_decode_or_business_error() {
495 for body in [
496 b"data: not-json\n\ndata: {\"value\":1}\n\ndata: [DONE]\n\n".as_slice(),
497 b"data: {\"error\":{\"code\":1302,\"message\":\"limited\"}}\n\ndata: [DONE]\n\n"
498 .as_slice(),
499 ] {
500 let raw: crate::client::transport::SseByteStream = Box::pin(
501 futures_util::stream::iter([Ok(Bytes::copy_from_slice(body))]),
502 );
503 let mut stream = decode_required_done_stream(raw, |payload| {
504 serde_json::from_slice::<serde_json::Value>(payload).map_err(ZaiError::from)
505 });
506 assert!(stream.next().await.unwrap().is_err());
507 assert!(stream.next().await.is_none());
508 }
509 }
510}