1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! Transport-safe decoding of an SSE response body into one wire protocol's events.
//!
//! The framing, the `[DONE]` sentinel and the error classification are identical for both
//! protocols, so they live once in `parse_events`; the two public entry points differ only in
//! the payload type they ask it to deserialize.
use crate;
use crate::;
use ;
use ;
use DeserializeOwned;
use Pin;
/// Parses a raw HTTP response body as a Server-Sent Events (SSE) stream.
///
/// Transforms an HTTP streaming response into a stream of parsed [`OpenAIChunk`] objects.
/// This function handles the SSE protocol details, extracting JSON data from the SSE format.
///
/// # SSE Format
///
/// Server-Sent Events is a standard protocol for server-to-client streaming. The format is:
///
/// ```text
/// data: {"id":"msg_123","object":"chat.completion.chunk",...}
///
/// data: {"id":"msg_123","object":"chat.completion.chunk",...}
///
/// data: [DONE]
///
/// ```
///
/// Key characteristics:
/// - Each message starts with `data: `
/// - Messages are separated by double newlines (`\n\n`)
/// - The stream ends with `data: [DONE]`
/// - Everything after `data: ` (until newline) is the payload
///
/// # Arguments
///
/// * `body` - The raw HTTP response from the API request
///
/// # Returns
///
/// A pinned, boxed stream that yields `Result<OpenAIChunk>` for each successfully parsed event.
/// The stream is `Send` to allow use across thread boundaries.
///
/// # Error Handling
///
/// Each stream item can be an error:
/// - **HTTP errors**: Network issues, connection drops (wrapped as [`Error::Http`])
/// - **Parse errors**: Invalid JSON in the SSE data field (wrapped as [`Error::Stream`])
/// - **Protocol errors**: Invalid UTF-8 or malformed SSE fields (wrapped as [`Error::Stream`])
///
/// Errors are yielded as stream items rather than panicking the parser. Consumers should handle
/// them explicitly.
///
/// # Example Flow
///
/// ```text
/// Raw HTTP bytes: b"data: {\"id\":\"123\"}\n\ndata: [DONE]\n\n"
/// ↓
/// bytes_stream() yields arbitrary transport chunks
/// ↓
/// Eventsource buffers chunks into complete SSE events
/// ↓
/// Skip "[DONE]" and parse event data into OpenAIChunk
/// ↓
/// Stream<Result<OpenAIChunk>>
/// ```
///
/// # Protocol Notes
///
/// - **`[DONE]` sentinel**: OpenAI's SSE streams end with `data: [DONE]`. This is not valid
/// JSON, so we skip it rather than attempting to parse.
///
/// - **Chunk boundaries**: HTTP streaming can split data at arbitrary byte positions. The
/// eventsource decoder buffers partial events and emits every complete event, including
/// multiple events received in a single transport chunk.
///
/// - **UTF-8 handling**: Split multi-byte characters are buffered until complete. Invalid
/// UTF-8 is reported as a stream error instead of being replaced with lossy characters.
///
/// # Usage
///
/// ```rust,ignore
/// let response = client.post(url).send().await?;
/// let mut stream = parse_sse_stream(response);
///
/// while let Some(result) = stream.next().await {
/// match result {
/// Ok(chunk) => process_chunk(chunk),
/// Err(e) => eprintln!("Stream error: {}", e),
/// }
/// }
/// ```
/// Parses an SSE response body as a stream of [`AnthropicEvent`]s.
///
/// The Anthropic sibling of [`parse_sse_stream`]. Anthropic labels each frame with an
/// `event:` line as well as its `data:` payload, but the payload repeats the type in its own
/// `type` field, so only the payload is parsed and the two can never disagree about what an
/// event is.
///
/// Anthropic does not terminate with `data: [DONE]` — `message_stop` ends the response and
/// the connection closes — but the sentinel is skipped here as it is for OpenAI, because
/// some compatible third-party endpoints send both.
/// Decodes an SSE body into `T`, one item per complete event.
///
/// The shared half of both public parsers: the SSE framing, the `[DONE]` sentinel and the
/// error classification are protocol-independent, and only the payload type differs. Written
/// once so a fix to the transport handling cannot land in one protocol and miss the other.