openai_compat/
streaming.rs1use std::collections::VecDeque;
5use std::pin::Pin;
6use std::task::{Context, Poll};
7
8use futures_core::Stream;
9use futures_util::StreamExt;
10use serde::de::DeserializeOwned;
11
12use crate::error::OpenAIError;
13
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
16pub struct ServerSentEvent {
17 pub event: Option<String>,
18 pub data: String,
19 pub id: Option<String>,
20 pub retry: Option<u64>,
21}
22
23#[derive(Debug, Default)]
31pub struct SseDecoder {
32 buf: Vec<u8>,
33 event: Option<String>,
34 data: Vec<String>,
35 id: Option<String>,
36 retry: Option<u64>,
37 bom_checked: bool,
38}
39
40const MAX_SSE_BUFFER: usize = 16 * 1024 * 1024;
43
44impl SseDecoder {
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn buffered_len(&self) -> usize {
51 self.buf.len()
52 }
53
54 pub fn feed(&mut self, chunk: &[u8]) -> Vec<ServerSentEvent> {
56 self.buf.extend_from_slice(chunk);
57
58 if !self.bom_checked && self.buf.len() >= 3 {
60 if self.buf.starts_with(&[0xEF, 0xBB, 0xBF]) {
61 self.buf.drain(..3);
62 }
63 self.bom_checked = true;
64 }
65
66 let mut events = Vec::new();
67
68 while let Some(pos) = self.buf.iter().position(|&b| b == b'\n' || b == b'\r') {
70 let term_len = if self.buf[pos] == b'\r' {
71 if pos + 1 == self.buf.len() {
72 break;
74 }
75 if self.buf[pos + 1] == b'\n' {
76 2
77 } else {
78 1
79 }
80 } else {
81 1
82 };
83
84 let line = String::from_utf8_lossy(&self.buf[..pos]).into_owned();
85 self.buf.drain(..pos + term_len);
86
87 if let Some(event) = self.process_line(&line) {
88 events.push(event);
89 }
90 }
91 events
92 }
93
94 pub fn flush(&mut self) -> Vec<ServerSentEvent> {
96 let mut events = Vec::new();
97 if self.buf.last() == Some(&b'\r') {
99 self.buf.pop();
100 }
101 if !self.buf.is_empty() {
102 let line = String::from_utf8_lossy(&self.buf).into_owned();
103 self.buf.clear();
104 if let Some(event) = self.process_line(&line) {
105 events.push(event);
106 }
107 }
108 if !self.data.is_empty() || self.event.is_some() {
110 events.push(self.dispatch());
111 }
112 events
113 }
114
115 fn process_line(&mut self, line: &str) -> Option<ServerSentEvent> {
116 if line.is_empty() {
117 if self.event.is_none() && self.data.is_empty() && self.retry.is_none() {
119 return None;
120 }
121 return Some(self.dispatch());
122 }
123 if line.starts_with(':') {
124 return None; }
126
127 let (field, value) = match line.split_once(':') {
128 Some((field, value)) => (field, value.strip_prefix(' ').unwrap_or(value)),
129 None => (line, ""),
130 };
131
132 match field {
133 "event" => self.event = Some(value.to_string()),
134 "data" => self.data.push(value.to_string()),
135 "id" => {
136 if !value.contains('\0') {
137 self.id = Some(value.to_string());
138 }
139 }
140 "retry" => {
141 if let Ok(retry) = value.parse() {
142 self.retry = Some(retry);
143 }
144 }
145 _ => {} }
147 None
148 }
149
150 fn dispatch(&mut self) -> ServerSentEvent {
151 ServerSentEvent {
152 event: self.event.take(),
153 data: std::mem::take(&mut self.data).join("\n"),
154 id: self.id.clone(),
155 retry: self.retry.take(),
156 }
157 }
158}
159
160pub struct EventStream<T> {
166 bytes: Pin<Box<dyn Stream<Item = Result<bytes::Bytes, reqwest::Error>> + Send>>,
167 decoder: SseDecoder,
168 pending: VecDeque<ServerSentEvent>,
169 done: bool,
170 bytes_exhausted: bool,
171 _marker: std::marker::PhantomData<fn() -> T>,
172}
173
174impl<T: DeserializeOwned> EventStream<T> {
175 pub(crate) fn new(response: reqwest::Response) -> Self {
176 Self {
177 bytes: Box::pin(response.bytes_stream()),
178 decoder: SseDecoder::new(),
179 pending: VecDeque::new(),
180 done: false,
181 bytes_exhausted: false,
182 _marker: std::marker::PhantomData,
183 }
184 }
185
186 fn item_from_event(event: ServerSentEvent) -> Option<Result<T, OpenAIError>> {
189 if event.data.starts_with("[DONE]") {
190 return None;
191 }
192 let value: serde_json::Value = match serde_json::from_str(&event.data) {
193 Ok(value) => value,
194 Err(err) => return Some(Err(OpenAIError::Stream(format!(
195 "invalid JSON in stream: {err}"
196 )))),
197 };
198 if let Some(error) = value.get("error").filter(|e| !e.is_null()) {
199 let message = error
200 .get("message")
201 .and_then(|m| m.as_str())
202 .filter(|m| !m.is_empty())
203 .unwrap_or("An error occurred during streaming");
204 return Some(Err(OpenAIError::Stream(message.to_string())));
205 }
206 match serde_json::from_value(value) {
207 Ok(item) => Some(Ok(item)),
208 Err(err) => Some(Err(OpenAIError::Json(err))),
209 }
210 }
211}
212
213impl<T: DeserializeOwned> Stream for EventStream<T> {
214 type Item = Result<T, OpenAIError>;
215
216 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
217 let this = self.get_mut();
218 loop {
219 if this.done {
220 return Poll::Ready(None);
221 }
222
223 if let Some(event) = this.pending.pop_front() {
224 if event.data.is_empty() {
226 continue;
227 }
228 match Self::item_from_event(event) {
229 Some(Ok(item)) => return Poll::Ready(Some(Ok(item))),
230 Some(Err(err)) => {
231 this.done = true;
232 return Poll::Ready(Some(Err(err)));
233 }
234 None => {
235 this.done = true;
236 return Poll::Ready(None);
237 }
238 }
239 }
240
241 if this.bytes_exhausted {
243 this.done = true;
244 return Poll::Ready(None);
245 }
246
247 match this.bytes.as_mut().poll_next(cx) {
248 Poll::Pending => return Poll::Pending,
249 Poll::Ready(Some(Ok(chunk))) => {
250 this.pending.extend(this.decoder.feed(&chunk));
251 if this.decoder.buffered_len() > MAX_SSE_BUFFER {
252 this.done = true;
253 return Poll::Ready(Some(Err(OpenAIError::Stream(format!(
254 "SSE line exceeded the {MAX_SSE_BUFFER} byte buffer limit"
255 )))));
256 }
257 }
258 Poll::Ready(Some(Err(err))) => {
259 this.done = true;
260 return Poll::Ready(Some(Err(if err.is_timeout() {
261 OpenAIError::Timeout
262 } else {
263 OpenAIError::Connection(err.to_string())
264 })));
265 }
266 Poll::Ready(None) => {
267 this.bytes_exhausted = true;
268 this.pending.extend(this.decoder.flush());
269 }
270 }
271 }
272 }
273}
274
275impl<T: DeserializeOwned + Send + 'static> EventStream<T> {
276 pub async fn collect_all(mut self) -> Result<Vec<T>, OpenAIError> {
278 let mut items = Vec::new();
279 while let Some(item) = self.next().await {
280 items.push(item?);
281 }
282 Ok(items)
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn decodes_simple_event() {
292 let mut decoder = SseDecoder::new();
293 let events = decoder.feed(b"data: {\"x\":1}\n\n");
294 assert_eq!(events.len(), 1);
295 assert_eq!(events[0].data, "{\"x\":1}");
296 assert_eq!(events[0].event, None);
297 }
298
299 #[test]
300 fn decodes_event_split_across_chunks() {
301 let mut decoder = SseDecoder::new();
302 assert!(decoder.feed(b"data: {\"x\"").is_empty());
303 assert!(decoder.feed(b":1}\n").is_empty());
304 let events = decoder.feed(b"\n");
305 assert_eq!(events.len(), 1);
306 assert_eq!(events[0].data, "{\"x\":1}");
307 }
308
309 #[test]
310 fn handles_crlf_and_cr_terminators() {
311 let mut decoder = SseDecoder::new();
312 let events = decoder.feed(b"data: a\r\n\r\ndata: b\r\rdata: c\n\n");
313 assert_eq!(
314 events.iter().map(|e| e.data.as_str()).collect::<Vec<_>>(),
315 vec!["a", "b", "c"]
316 );
317 }
318
319 #[test]
320 fn crlf_split_across_chunks() {
321 let mut decoder = SseDecoder::new();
322 assert!(decoder.feed(b"data: a\r").is_empty());
323 let events = decoder.feed(b"\n\r\n");
324 assert_eq!(events.len(), 1);
325 assert_eq!(events[0].data, "a");
326 }
327
328 #[test]
329 fn multiline_data_joined_with_newline() {
330 let mut decoder = SseDecoder::new();
331 let events = decoder.feed(b"data: line1\ndata: line2\n\n");
332 assert_eq!(events[0].data, "line1\nline2");
333 }
334
335 #[test]
336 fn parses_event_id_retry_and_comments() {
337 let mut decoder = SseDecoder::new();
338 let events =
339 decoder.feed(b": keep-alive\nevent: message\nid: 42\nretry: 100\ndata: hi\n\n");
340 assert_eq!(events.len(), 1);
341 assert_eq!(events[0].event.as_deref(), Some("message"));
342 assert_eq!(events[0].id.as_deref(), Some("42"));
343 assert_eq!(events[0].retry, Some(100));
344 assert_eq!(events[0].data, "hi");
345 }
346
347 #[test]
348 fn strips_single_leading_space_only() {
349 let mut decoder = SseDecoder::new();
350 let events = decoder.feed(b"data: padded\ndata:none\n\n");
351 assert_eq!(events[0].data, " padded\nnone");
352 }
353
354 #[test]
355 fn strips_leading_bom() {
356 let mut decoder = SseDecoder::new();
357 let events = decoder.feed(b"\xEF\xBB\xBFdata: first\n\n");
358 assert_eq!(events.len(), 1);
359 assert_eq!(events[0].data, "first");
360 let events = decoder.feed(b"data: \xEF\xBB\xBFx\n\n");
362 assert_eq!(events[0].data, "\u{FEFF}x");
363 }
364
365 #[test]
366 fn flush_emits_trailing_event() {
367 let mut decoder = SseDecoder::new();
368 assert!(decoder.feed(b"data: tail").is_empty());
369 let events = decoder.flush();
370 assert_eq!(events.len(), 1);
371 assert_eq!(events[0].data, "tail");
372 }
373}