1use std::collections::VecDeque;
5use std::pin::Pin;
6
7use futures_core::Stream;
8
9use crate::http::MAX_RESPONSE_BYTES;
10use crate::{ChatResponse, LlmError, Result};
11
12const MAX_EVENT_BYTES: usize = 4 * 1024 * 1024;
14
15#[derive(Debug, Clone, PartialEq)]
17#[non_exhaustive]
18pub enum StreamEvent {
19 TextDelta(String),
21 ReasoningDelta(String),
23 ToolCallStart { id: String, name: String },
25 ToolCallDelta { id: String, arguments: String },
27 Done(ChatResponse),
29}
30
31pub type ChatStream = Pin<Box<dyn Stream<Item = Result<StreamEvent>> + Send>>;
33
34pub(crate) trait Accumulator: Send + 'static {
36 fn on_event(&mut self, event: &str, data: &str) -> Result<Vec<StreamEvent>>;
37 fn finish(&mut self) -> Result<ChatResponse>;
38}
39
40#[derive(Default)]
43pub(crate) struct SseParser {
44 buf: Vec<u8>,
45 scanned: usize,
47 pending_cr: bool,
49}
50
51impl SseParser {
52 pub(crate) fn push(&mut self, bytes: &[u8]) -> Result<Vec<(String, String)>> {
54 for &b in bytes {
55 match b {
56 b'\r' => {
57 self.buf.push(b'\n');
58 self.pending_cr = true;
59 }
60 b'\n' if self.pending_cr => self.pending_cr = false,
61 _ => {
62 self.buf.push(b);
63 self.pending_cr = false;
64 }
65 }
66 }
67
68 let mut events = Vec::new();
69 let mut consumed = 0;
70 let mut i = self.scanned.max(consumed + 1);
71 while i < self.buf.len() {
72 if self.buf[i] == b'\n' && self.buf[i - 1] == b'\n' {
73 if let Some(event) = parse_block(&String::from_utf8_lossy(&self.buf[consumed..=i]))
74 {
75 events.push(event);
76 }
77 consumed = i + 1;
78 i = consumed + 1;
79 } else {
80 i += 1;
81 }
82 }
83 self.buf.drain(..consumed);
85 self.scanned = self.buf.len().max(1);
86
87 if self.buf.len() > MAX_EVENT_BYTES {
88 return Err(LlmError::InvalidResponse(format!(
89 "stream event exceeds {MAX_EVENT_BYTES} bytes"
90 )));
91 }
92 Ok(events)
93 }
94
95 pub(crate) fn flush(&mut self) -> Option<(String, String)> {
97 self.scanned = 0;
98 let rest = String::from_utf8_lossy(&std::mem::take(&mut self.buf)).into_owned();
99 parse_block(&rest)
100 }
101}
102
103pub(crate) fn ended_early() -> LlmError {
105 LlmError::InvalidResponse(
106 "stream ended before the final event; the response may be incomplete".into(),
107 )
108}
109
110fn parse_block(block: &str) -> Option<(String, String)> {
111 let mut event = String::new();
112 let mut data: Vec<&str> = Vec::new();
113 for line in block.lines() {
114 if let Some(value) = line.strip_prefix("event:") {
115 event = value.trim().to_string();
116 } else if let Some(value) = line.strip_prefix("data:") {
117 data.push(value.strip_prefix(' ').unwrap_or(value));
118 }
119 }
121 (!data.is_empty()).then(|| (event, data.join("\n")))
122}
123
124struct State {
125 response: reqwest::Response,
126 parser: SseParser,
127 acc: Box<dyn Accumulator>,
128 queue: VecDeque<Result<StreamEvent>>,
129 read: usize,
130 finished: bool,
131}
132
133impl State {
134 fn handle(&mut self, events: Vec<(String, String)>) {
135 for (event, data) in events {
136 match self.acc.on_event(&event, &data) {
137 Ok(out) => self.queue.extend(out.into_iter().map(Ok)),
138 Err(e) => {
139 self.queue.push_back(Err(e));
140 self.finished = true;
141 return;
142 }
143 }
144 }
145 }
146
147 fn fail(&mut self, error: LlmError) {
148 self.queue.push_back(Err(error));
149 self.finished = true;
150 }
151}
152
153pub(crate) fn drive(response: reqwest::Response, acc: Box<dyn Accumulator>) -> ChatStream {
155 let state = State {
156 response,
157 parser: SseParser::default(),
158 acc,
159 queue: VecDeque::new(),
160 read: 0,
161 finished: false,
162 };
163 Box::pin(futures_util::stream::unfold(
164 state,
165 |mut state| async move {
166 loop {
167 if let Some(item) = state.queue.pop_front() {
168 return Some((item, state));
169 }
170 if state.finished {
171 return None;
172 }
173 match state.response.chunk().await {
174 Ok(Some(bytes)) => {
175 state.read += bytes.len();
176 if state.read > MAX_RESPONSE_BYTES {
177 state.fail(LlmError::InvalidResponse(format!(
178 "stream exceeds {MAX_RESPONSE_BYTES} bytes"
179 )));
180 continue;
181 }
182 match state.parser.push(&bytes) {
183 Ok(events) => state.handle(events),
184 Err(e) => state.fail(e),
185 }
186 }
187 Ok(None) => {
188 if let Some(last) = state.parser.flush() {
189 state.handle(vec![last]);
190 }
191 if !state.finished {
192 let done = state.acc.finish().map(StreamEvent::Done);
193 state.queue.push_back(done);
194 state.finished = true;
195 }
196 }
197 Err(e) => state.fail(LlmError::from_reqwest(e)),
198 }
199 }
200 },
201 ))
202}
203
204pub(crate) fn parse_data(data: &str) -> Result<serde_json::Value> {
206 serde_json::from_str(data).map_err(|source| LlmError::Deserialize {
207 source,
208 content: crate::error::truncate(data),
209 })
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn parses_events_split_across_chunks() {
218 let mut p = SseParser::default();
219 assert!(p.push(b"event: ping\ndata: {\"a\"").unwrap().is_empty());
220 let events = p
221 .push(b":1}\r\n\r\n: keep-alive\n\ndata: [DONE]\n\n")
222 .unwrap();
223 assert_eq!(
224 events,
225 vec![
226 ("ping".to_string(), "{\"a\":1}".to_string()),
227 (String::new(), "[DONE]".to_string())
228 ]
229 );
230 }
231
232 #[test]
233 fn multi_line_data_is_joined() {
234 let mut p = SseParser::default();
235 let events = p.push(b"data: line1\ndata: line2\n\n").unwrap();
236 assert_eq!(events[0].1, "line1\nline2");
237 }
238
239 #[test]
240 fn lone_cr_and_split_crlf_line_endings() {
241 let mut p = SseParser::default();
242 assert!(p.push(b"data: a\r").unwrap().is_empty());
243 let events = p.push(b"\n\r\ndata: b\r\r").unwrap();
244 assert_eq!(
245 events,
246 vec![
247 (String::new(), "a".to_string()),
248 (String::new(), "b".to_string())
249 ]
250 );
251 }
252
253 #[test]
254 fn many_events_in_one_chunk() {
255 let mut p = SseParser::default();
256 let chunk: Vec<u8> = (0..1000)
257 .flat_map(|i| format!("data: {i}\n\n").into_bytes())
258 .collect();
259 let events = p.push(&chunk).unwrap();
260 assert_eq!(events.len(), 1000);
261 assert_eq!(events[999].1, "999");
262 }
263
264 #[test]
265 fn oversized_event_is_rejected() {
266 let mut p = SseParser::default();
267 let big = vec![b'x'; MAX_EVENT_BYTES + 1];
268 assert!(p.push(&big).is_err());
269 }
270}