Skip to main content

crimson_crab/
streaming.rs

1//! Server-sent-events (SSE) streaming for `POST /v1/messages` with
2//! `"stream": true`.
3//!
4//! [`MessageStream`] wraps the raw byte stream returned by the API, decodes the
5//! SSE records with a hand-rolled parser (no external eventsource dependency),
6//! and yields typed [`StreamEvent`]s. As events flow past it, the stream also
7//! *accumulates* them into a [`Message`] identical in shape to the one the
8//! non-streaming endpoint would have returned — retrievable with
9//! [`MessageStream::final_message`] while draining, or in one shot with
10//! [`MessageStream::collect_final`].
11//!
12//! Forward compatibility is preserved throughout: unknown event types and
13//! unknown delta types deserialize into `Unknown` catch-alls (retaining the raw
14//! JSON) rather than erroring, and an in-stream `error` event is surfaced to the
15//! caller as an `Err` item that terminates the stream.
16//!
17//! # Examples
18//!
19//! ```no_run
20//! use crimson_crab::api::MessagesRequest;
21//! use crimson_crab::streaming::{ContentDelta, StreamEvent};
22//! use crimson_crab::types::MessageParam;
23//! use futures_util::StreamExt;
24//!
25//! # async fn demo(client: &crimson_crab::Client) -> crimson_crab::Result<()> {
26//! let request = MessagesRequest::builder()
27//!     .model("claude-opus-4-8")
28//!     .max_tokens(1024)
29//!     .messages(vec![MessageParam::user("Hello")])
30//!     .build()?;
31//!
32//! let mut stream = client.messages().stream(&request).await?;
33//! while let Some(event) = stream.next().await {
34//!     if let StreamEvent::ContentBlockDelta {
35//!         delta: ContentDelta::TextDelta { text },
36//!         ..
37//!     } = event?
38//!     {
39//!         print!("{text}");
40//!     }
41//! }
42//! # Ok(())
43//! # }
44//! ```
45
46use std::collections::HashMap;
47use std::pin::Pin;
48use std::task::{Context, Poll};
49
50use bytes::Bytes;
51use futures_core::Stream;
52use futures_util::StreamExt;
53use serde::de::{self, Deserializer};
54use serde::ser::SerializeMap;
55use serde::{Deserialize, Serialize, Serializer};
56
57use crate::error::{self, Error, Result};
58use crate::types::{ContentBlock, Message, StopDetails, StopReason, Usage};
59
60// ---------------------------------------------------------------------------
61// Stream event types.
62// ---------------------------------------------------------------------------
63
64/// A single decoded SSE event from a streaming `POST /v1/messages` response.
65///
66/// The variants mirror the Anthropic stream event sequence
67/// (`message_start` → `content_block_start`/`content_block_delta`/
68/// `content_block_stop` (repeated) → `message_delta` → `message_stop`), plus
69/// `ping` heartbeats and an in-stream `error`. Any event type the SDK does not
70/// model deserializes to [`StreamEvent::Unknown`], preserving the raw JSON.
71///
72/// [`MessageStream`] intercepts [`StreamEvent::Error`] and surfaces it as an
73/// `Err` item instead of yielding it, so consumers of the stream never observe
74/// this variant directly; it exists so the event model is complete and
75/// round-trips.
76///
77/// # Examples
78///
79/// ```
80/// use crimson_crab::streaming::StreamEvent;
81///
82/// let event: StreamEvent = serde_json::from_str(
83///     r#"{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hi"}}"#,
84/// )
85/// .unwrap();
86/// assert!(matches!(event, StreamEvent::ContentBlockDelta { index: 0, .. }));
87///
88/// // An unknown event type is preserved rather than rejected.
89/// let novel: StreamEvent = serde_json::from_str(r#"{"type":"brand_new","x":1}"#).unwrap();
90/// assert!(matches!(novel, StreamEvent::Unknown(_)));
91/// ```
92#[derive(Clone, Debug, PartialEq)]
93#[non_exhaustive]
94pub enum StreamEvent {
95    /// The opening event; carries the base [`Message`] (empty `content`).
96    MessageStart {
97        /// The base message that subsequent events are accumulated into.
98        message: Message,
99    },
100    /// A content block begins at `index`.
101    ContentBlockStart {
102        /// The position of the block within the message `content` array.
103        index: usize,
104        /// The initial (usually empty) block.
105        content_block: ContentBlock,
106    },
107    /// An incremental update to the content block at `index`.
108    ContentBlockDelta {
109        /// The position of the block being updated.
110        index: usize,
111        /// The delta to apply.
112        delta: ContentDelta,
113    },
114    /// The content block at `index` is complete.
115    ContentBlockStop {
116        /// The position of the block that finished.
117        index: usize,
118    },
119    /// Top-level message updates (final `stop_reason`, cumulative `usage`).
120    MessageDelta {
121        /// The stop metadata for the message.
122        delta: MessageDeltaBody,
123        /// Cumulative usage totals, if present.
124        usage: Option<UsageDelta>,
125    },
126    /// The final event; the message is complete.
127    MessageStop,
128    /// A keep-alive heartbeat with no payload.
129    Ping,
130    /// An in-stream error; terminates the message.
131    Error {
132        /// The error body reported by the server.
133        error: StreamErrorBody,
134    },
135    /// Forward-compatible catch-all preserving the raw JSON of an unknown event.
136    Unknown(serde_json::Value),
137}
138
139/// An incremental update to a content block, carried by
140/// [`StreamEvent::ContentBlockDelta`].
141///
142/// # Examples
143///
144/// ```
145/// use crimson_crab::streaming::ContentDelta;
146///
147/// let delta: ContentDelta =
148///     serde_json::from_str(r#"{"type":"text_delta","text":"Hello"}"#).unwrap();
149/// assert_eq!(delta, ContentDelta::TextDelta { text: "Hello".to_string() });
150///
151/// let unknown: ContentDelta =
152///     serde_json::from_str(r#"{"type":"future_delta","x":1}"#).unwrap();
153/// assert!(matches!(unknown, ContentDelta::Unknown(_)));
154/// ```
155#[derive(Clone, Debug, PartialEq)]
156#[non_exhaustive]
157pub enum ContentDelta {
158    /// Text to append to a `text` block.
159    TextDelta {
160        /// The text fragment.
161        text: String,
162    },
163    /// A fragment of a `tool_use` block's JSON `input`; concatenate all
164    /// fragments and parse the result (an empty string means `{}`).
165    InputJsonDelta {
166        /// The partial JSON fragment.
167        partial_json: String,
168    },
169    /// Reasoning text to append to a `thinking` block.
170    ThinkingDelta {
171        /// The thinking fragment.
172        thinking: String,
173    },
174    /// The signature for a `thinking` block (arrives before its stop event).
175    SignatureDelta {
176        /// The opaque signature fragment.
177        signature: String,
178    },
179    /// A citation to append to a `text` block.
180    CitationsDelta {
181        /// The raw citation object.
182        citation: serde_json::Value,
183    },
184    /// Forward-compatible catch-all preserving the raw JSON of an unknown delta.
185    Unknown(serde_json::Value),
186}
187
188/// The `delta` object carried by a [`StreamEvent::MessageDelta`] event.
189///
190/// It conveys the final `stop_reason`/`stop_sequence` (and, on a refusal, the
191/// `stop_details`) once the model has finished; these are merged into the
192/// accumulated [`Message`].
193///
194/// # Examples
195///
196/// ```
197/// use crimson_crab::streaming::MessageDeltaBody;
198/// use crimson_crab::types::StopReason;
199///
200/// let body: MessageDeltaBody = serde_json::from_value(serde_json::json!({
201///     "stop_reason": "end_turn",
202///     "stop_sequence": null
203/// }))
204/// .unwrap();
205/// assert_eq!(body.stop_reason, Some(StopReason::EndTurn));
206/// ```
207#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
208pub struct MessageDeltaBody {
209    /// Why generation stopped, if known at this point.
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub stop_reason: Option<StopReason>,
212    /// The matched stop sequence, if any.
213    #[serde(default, skip_serializing_if = "Option::is_none")]
214    pub stop_sequence: Option<String>,
215    /// Refusal detail, present only on a refusal stop.
216    #[serde(default, skip_serializing_if = "Option::is_none")]
217    pub stop_details: Option<StopDetails>,
218    /// Forward-compatible catch-all for any other fields.
219    #[serde(flatten)]
220    pub extra: serde_json::Map<String, serde_json::Value>,
221}
222
223/// The `usage` object carried by a [`StreamEvent::MessageDelta`] event.
224///
225/// Each present field is a **cumulative total** that *replaces* (not adds to)
226/// the corresponding field on the accumulated message's [`Usage`].
227///
228/// # Examples
229///
230/// ```
231/// use crimson_crab::streaming::UsageDelta;
232///
233/// let usage: UsageDelta =
234///     serde_json::from_value(serde_json::json!({"output_tokens": 12})).unwrap();
235/// assert_eq!(usage.output_tokens, Some(12));
236/// ```
237#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
238pub struct UsageDelta {
239    /// Cumulative input tokens, if updated.
240    #[serde(default, skip_serializing_if = "Option::is_none")]
241    pub input_tokens: Option<u64>,
242    /// Cumulative output tokens, if updated.
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub output_tokens: Option<u64>,
245    /// Cumulative cache-creation input tokens, if updated.
246    #[serde(default, skip_serializing_if = "Option::is_none")]
247    pub cache_creation_input_tokens: Option<u64>,
248    /// Cumulative cache-read input tokens, if updated.
249    #[serde(default, skip_serializing_if = "Option::is_none")]
250    pub cache_read_input_tokens: Option<u64>,
251    /// Forward-compatible catch-all for any other usage fields.
252    #[serde(flatten)]
253    pub extra: serde_json::Map<String, serde_json::Value>,
254}
255
256/// The `error` object carried by an in-stream [`StreamEvent::Error`] event.
257///
258/// # Examples
259///
260/// ```
261/// use crimson_crab::streaming::StreamErrorBody;
262///
263/// let body: StreamErrorBody = serde_json::from_value(serde_json::json!({
264///     "type": "overloaded_error",
265///     "message": "Overloaded"
266/// }))
267/// .unwrap();
268/// assert_eq!(body.error_type, "overloaded_error");
269/// ```
270#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
271pub struct StreamErrorBody {
272    /// The machine-readable error type, e.g. `"overloaded_error"`.
273    #[serde(rename = "type")]
274    pub error_type: String,
275    /// A human-readable description of the error.
276    pub message: String,
277    /// Forward-compatible catch-all for any other fields.
278    #[serde(flatten)]
279    pub extra: serde_json::Map<String, serde_json::Value>,
280}
281
282// ---------------------------------------------------------------------------
283// Serde for the two hand-written enums.
284// ---------------------------------------------------------------------------
285
286impl Serialize for ContentDelta {
287    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
288    where
289        S: Serializer,
290    {
291        match self {
292            ContentDelta::TextDelta { text } => {
293                let mut map = serializer.serialize_map(Some(2))?;
294                map.serialize_entry("type", "text_delta")?;
295                map.serialize_entry("text", text)?;
296                map.end()
297            }
298            ContentDelta::InputJsonDelta { partial_json } => {
299                let mut map = serializer.serialize_map(Some(2))?;
300                map.serialize_entry("type", "input_json_delta")?;
301                map.serialize_entry("partial_json", partial_json)?;
302                map.end()
303            }
304            ContentDelta::ThinkingDelta { thinking } => {
305                let mut map = serializer.serialize_map(Some(2))?;
306                map.serialize_entry("type", "thinking_delta")?;
307                map.serialize_entry("thinking", thinking)?;
308                map.end()
309            }
310            ContentDelta::SignatureDelta { signature } => {
311                let mut map = serializer.serialize_map(Some(2))?;
312                map.serialize_entry("type", "signature_delta")?;
313                map.serialize_entry("signature", signature)?;
314                map.end()
315            }
316            ContentDelta::CitationsDelta { citation } => {
317                let mut map = serializer.serialize_map(Some(2))?;
318                map.serialize_entry("type", "citations_delta")?;
319                map.serialize_entry("citation", citation)?;
320                map.end()
321            }
322            ContentDelta::Unknown(value) => value.serialize(serializer),
323        }
324    }
325}
326
327impl<'de> Deserialize<'de> for ContentDelta {
328    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
329    where
330        D: Deserializer<'de>,
331    {
332        let value = serde_json::Value::deserialize(deserializer)?;
333        let tag = value
334            .get("type")
335            .and_then(|t| t.as_str())
336            .map(str::to_owned);
337        let delta = match tag.as_deref() {
338            Some("text_delta") => {
339                #[derive(Deserialize)]
340                struct R {
341                    #[serde(default)]
342                    text: String,
343                }
344                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
345                ContentDelta::TextDelta { text: r.text }
346            }
347            Some("input_json_delta") => {
348                #[derive(Deserialize)]
349                struct R {
350                    #[serde(default)]
351                    partial_json: String,
352                }
353                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
354                ContentDelta::InputJsonDelta {
355                    partial_json: r.partial_json,
356                }
357            }
358            Some("thinking_delta") => {
359                #[derive(Deserialize)]
360                struct R {
361                    #[serde(default)]
362                    thinking: String,
363                }
364                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
365                ContentDelta::ThinkingDelta {
366                    thinking: r.thinking,
367                }
368            }
369            Some("signature_delta") => {
370                #[derive(Deserialize)]
371                struct R {
372                    #[serde(default)]
373                    signature: String,
374                }
375                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
376                ContentDelta::SignatureDelta {
377                    signature: r.signature,
378                }
379            }
380            Some("citations_delta") => {
381                #[derive(Deserialize)]
382                struct R {
383                    #[serde(default)]
384                    citation: serde_json::Value,
385                }
386                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
387                ContentDelta::CitationsDelta {
388                    citation: r.citation,
389                }
390            }
391            _ => ContentDelta::Unknown(value),
392        };
393        Ok(delta)
394    }
395}
396
397impl Serialize for StreamEvent {
398    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
399    where
400        S: Serializer,
401    {
402        match self {
403            StreamEvent::MessageStart { message } => {
404                let mut map = serializer.serialize_map(Some(2))?;
405                map.serialize_entry("type", "message_start")?;
406                map.serialize_entry("message", message)?;
407                map.end()
408            }
409            StreamEvent::ContentBlockStart {
410                index,
411                content_block,
412            } => {
413                let mut map = serializer.serialize_map(Some(3))?;
414                map.serialize_entry("type", "content_block_start")?;
415                map.serialize_entry("index", index)?;
416                map.serialize_entry("content_block", content_block)?;
417                map.end()
418            }
419            StreamEvent::ContentBlockDelta { index, delta } => {
420                let mut map = serializer.serialize_map(Some(3))?;
421                map.serialize_entry("type", "content_block_delta")?;
422                map.serialize_entry("index", index)?;
423                map.serialize_entry("delta", delta)?;
424                map.end()
425            }
426            StreamEvent::ContentBlockStop { index } => {
427                let mut map = serializer.serialize_map(Some(2))?;
428                map.serialize_entry("type", "content_block_stop")?;
429                map.serialize_entry("index", index)?;
430                map.end()
431            }
432            StreamEvent::MessageDelta { delta, usage } => {
433                let len = if usage.is_some() { 3 } else { 2 };
434                let mut map = serializer.serialize_map(Some(len))?;
435                map.serialize_entry("type", "message_delta")?;
436                map.serialize_entry("delta", delta)?;
437                if let Some(usage) = usage {
438                    map.serialize_entry("usage", usage)?;
439                }
440                map.end()
441            }
442            StreamEvent::MessageStop => {
443                let mut map = serializer.serialize_map(Some(1))?;
444                map.serialize_entry("type", "message_stop")?;
445                map.end()
446            }
447            StreamEvent::Ping => {
448                let mut map = serializer.serialize_map(Some(1))?;
449                map.serialize_entry("type", "ping")?;
450                map.end()
451            }
452            StreamEvent::Error { error } => {
453                let mut map = serializer.serialize_map(Some(2))?;
454                map.serialize_entry("type", "error")?;
455                map.serialize_entry("error", error)?;
456                map.end()
457            }
458            StreamEvent::Unknown(value) => value.serialize(serializer),
459        }
460    }
461}
462
463impl<'de> Deserialize<'de> for StreamEvent {
464    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
465    where
466        D: Deserializer<'de>,
467    {
468        let value = serde_json::Value::deserialize(deserializer)?;
469        let tag = value
470            .get("type")
471            .and_then(|t| t.as_str())
472            .map(str::to_owned);
473        let event = match tag.as_deref() {
474            Some("message_start") => {
475                #[derive(Deserialize)]
476                struct R {
477                    message: Message,
478                }
479                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
480                StreamEvent::MessageStart { message: r.message }
481            }
482            Some("content_block_start") => {
483                #[derive(Deserialize)]
484                struct R {
485                    index: usize,
486                    content_block: ContentBlock,
487                }
488                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
489                StreamEvent::ContentBlockStart {
490                    index: r.index,
491                    content_block: r.content_block,
492                }
493            }
494            Some("content_block_delta") => {
495                #[derive(Deserialize)]
496                struct R {
497                    index: usize,
498                    delta: ContentDelta,
499                }
500                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
501                StreamEvent::ContentBlockDelta {
502                    index: r.index,
503                    delta: r.delta,
504                }
505            }
506            Some("content_block_stop") => {
507                #[derive(Deserialize)]
508                struct R {
509                    index: usize,
510                }
511                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
512                StreamEvent::ContentBlockStop { index: r.index }
513            }
514            Some("message_delta") => {
515                #[derive(Deserialize)]
516                struct R {
517                    delta: MessageDeltaBody,
518                    #[serde(default)]
519                    usage: Option<UsageDelta>,
520                }
521                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
522                StreamEvent::MessageDelta {
523                    delta: r.delta,
524                    usage: r.usage,
525                }
526            }
527            Some("message_stop") => StreamEvent::MessageStop,
528            Some("ping") => StreamEvent::Ping,
529            Some("error") => {
530                #[derive(Deserialize)]
531                struct R {
532                    error: StreamErrorBody,
533                }
534                let r: R = serde_json::from_value(value).map_err(de::Error::custom)?;
535                StreamEvent::Error { error: r.error }
536            }
537            _ => StreamEvent::Unknown(value),
538        };
539        Ok(event)
540    }
541}
542
543// ---------------------------------------------------------------------------
544// SSE record decoder.
545// ---------------------------------------------------------------------------
546
547/// Splits an `SSE` line into its field name and value (`"data: x"` →
548/// `("data", "x")`), stripping a single optional space after the colon. A line
549/// with no colon is a field name with an empty value.
550fn parse_field(line: &str) -> (&str, &str) {
551    match line.find(':') {
552        Some(idx) => {
553            let field = &line[..idx];
554            let value = &line[idx + 1..];
555            let value = value.strip_prefix(' ').unwrap_or(value);
556            (field, value)
557        }
558        None => (line, ""),
559    }
560}
561
562/// An incremental SSE record decoder over an arbitrarily-chunked byte stream.
563///
564/// Bytes are buffered; complete newline-terminated lines are consumed one at a
565/// time (tolerating both `\n` and `\r\n`). `data:` lines accumulate (joined with
566/// `\n` for multi-line data), comment lines (`:` prefix) and other fields are
567/// ignored, and a blank line dispatches the accumulated event's `data` payload.
568#[derive(Default)]
569struct SseDecoder {
570    buffer: Vec<u8>,
571    data: String,
572    saw_data: bool,
573}
574
575impl SseDecoder {
576    fn push(&mut self, bytes: &[u8]) {
577        self.buffer.extend_from_slice(bytes);
578    }
579
580    /// Returns the next complete event's `data` payload, or `None` if more bytes
581    /// are required. Records without a `data` field are skipped.
582    fn next_event_data(&mut self) -> Option<String> {
583        loop {
584            let newline = self.buffer.iter().position(|&byte| byte == b'\n')?;
585            let line_bytes: Vec<u8> = self.buffer.drain(..=newline).collect();
586            let mut end = line_bytes.len() - 1; // exclude the trailing '\n'
587            if end > 0 && line_bytes[end - 1] == b'\r' {
588                end -= 1; // tolerate CRLF line endings
589            }
590            let line = String::from_utf8_lossy(&line_bytes[..end]);
591
592            if line.is_empty() {
593                if self.saw_data {
594                    self.saw_data = false;
595                    return Some(std::mem::take(&mut self.data));
596                }
597                self.data.clear();
598                continue;
599            }
600            if line.starts_with(':') {
601                continue; // comment / heartbeat line
602            }
603            let (field, value) = parse_field(line.as_ref());
604            if field == "data" {
605                if self.saw_data {
606                    self.data.push('\n');
607                }
608                self.data.push_str(value);
609                self.saw_data = true;
610            }
611            // `event:`, `id:`, `retry:`, and unknown fields are ignored; the
612            // event's `type` is read from the JSON `data` payload instead.
613        }
614    }
615
616    /// Flushes a trailing event that the stream ended without terminating with a
617    /// blank line.
618    fn flush(&mut self) -> Option<String> {
619        if !self.buffer.is_empty() {
620            let line_bytes = std::mem::take(&mut self.buffer);
621            let mut end = line_bytes.len();
622            if end > 0 && line_bytes[end - 1] == b'\n' {
623                end -= 1;
624            }
625            if end > 0 && line_bytes[end - 1] == b'\r' {
626                end -= 1;
627            }
628            let line = String::from_utf8_lossy(&line_bytes[..end]);
629            if !line.is_empty() && !line.starts_with(':') {
630                let (field, value) = parse_field(line.as_ref());
631                if field == "data" {
632                    if self.saw_data {
633                        self.data.push('\n');
634                    }
635                    self.data.push_str(value);
636                    self.saw_data = true;
637                }
638            }
639        }
640        if self.saw_data {
641            self.saw_data = false;
642            Some(std::mem::take(&mut self.data))
643        } else {
644            None
645        }
646    }
647}
648
649// ---------------------------------------------------------------------------
650// Accumulation.
651// ---------------------------------------------------------------------------
652
653/// An upper bound on the number of accumulated content blocks.
654///
655/// Blocks normally arrive contiguously from index 0, so any index at or beyond
656/// this bound indicates a malformed or hostile stream. The cap prevents a single
657/// `content_block_start` event with an enormous `index` (a few dozen bytes on the
658/// wire) from driving [`set_block`] to allocate placeholder blocks up to that
659/// index and exhausting memory.
660const MAX_STREAM_CONTENT_BLOCKS: usize = 1 << 16;
661
662/// Places `block` at `index` in `content`, extending with placeholder blocks if
663/// a gap appears (blocks normally arrive contiguously from index 0).
664///
665/// An `index` at or beyond [`MAX_STREAM_CONTENT_BLOCKS`] is ignored rather than
666/// gap-filled, so an untrusted byte source cannot force unbounded allocation.
667fn set_block(content: &mut Vec<ContentBlock>, index: usize, block: ContentBlock) {
668    if index < content.len() {
669        content[index] = block;
670    } else if index < MAX_STREAM_CONTENT_BLOCKS {
671        while content.len() < index {
672            content.push(ContentBlock::Unknown(serde_json::Value::Null));
673        }
674        content.push(block);
675    }
676    // else: an absurd index — drop the block to avoid unbounded allocation.
677}
678
679/// Merges a [`UsageDelta`] into a [`Usage`], replacing each present field with
680/// the delta's cumulative total.
681fn merge_usage(target: &mut Usage, delta: &UsageDelta) {
682    if let Some(value) = delta.input_tokens {
683        target.input_tokens = value;
684    }
685    if let Some(value) = delta.output_tokens {
686        target.output_tokens = value;
687    }
688    if let Some(value) = delta.cache_creation_input_tokens {
689        target.cache_creation_input_tokens = Some(value);
690    }
691    if let Some(value) = delta.cache_read_input_tokens {
692        target.cache_read_input_tokens = Some(value);
693    }
694    for (key, value) in &delta.extra {
695        target.extra.insert(key.clone(), value.clone());
696    }
697}
698
699/// Assembles a [`Message`] from the stream events as they pass by.
700#[derive(Default)]
701struct Accumulator {
702    message: Option<Message>,
703    /// Per-index buffers for `tool_use` blocks' `input_json_delta` fragments.
704    json_buffers: HashMap<usize, String>,
705}
706
707impl Accumulator {
708    fn apply(&mut self, event: &StreamEvent) {
709        match event {
710            StreamEvent::MessageStart { message } => {
711                self.message = Some(message.clone());
712                self.json_buffers.clear();
713            }
714            StreamEvent::ContentBlockStart {
715                index,
716                content_block,
717            } => {
718                if let Some(message) = self.message.as_mut() {
719                    set_block(&mut message.content, *index, content_block.clone());
720                }
721                if matches!(
722                    content_block,
723                    ContentBlock::ToolUse(_) | ContentBlock::ServerToolUse(_)
724                ) {
725                    self.json_buffers.insert(*index, String::new());
726                }
727            }
728            StreamEvent::ContentBlockDelta { index, delta } => {
729                self.apply_delta(*index, delta);
730            }
731            StreamEvent::ContentBlockStop { index } => {
732                self.finish_block(*index);
733            }
734            StreamEvent::MessageDelta { delta, usage } => {
735                if let Some(message) = self.message.as_mut() {
736                    if delta.stop_reason.is_some() {
737                        message.stop_reason = delta.stop_reason.clone();
738                    }
739                    if delta.stop_sequence.is_some() {
740                        message.stop_sequence = delta.stop_sequence.clone();
741                    }
742                    if delta.stop_details.is_some() {
743                        message.stop_details = delta.stop_details.clone();
744                    }
745                    if let Some(usage) = usage {
746                        merge_usage(&mut message.usage, usage);
747                    }
748                }
749            }
750            StreamEvent::MessageStop
751            | StreamEvent::Ping
752            | StreamEvent::Error { .. }
753            | StreamEvent::Unknown(_) => {}
754        }
755    }
756
757    fn apply_delta(&mut self, index: usize, delta: &ContentDelta) {
758        if let ContentDelta::InputJsonDelta { partial_json } = delta {
759            self.json_buffers
760                .entry(index)
761                .or_default()
762                .push_str(partial_json);
763            return;
764        }
765        let Some(message) = self.message.as_mut() else {
766            return;
767        };
768        let Some(block) = message.content.get_mut(index) else {
769            return;
770        };
771        match delta {
772            ContentDelta::TextDelta { text } => {
773                if let ContentBlock::Text(inner) = block {
774                    inner.text.push_str(text);
775                }
776            }
777            ContentDelta::ThinkingDelta { thinking } => {
778                if let ContentBlock::Thinking(inner) = block {
779                    inner.thinking.push_str(thinking);
780                }
781            }
782            ContentDelta::SignatureDelta { signature } => {
783                if let ContentBlock::Thinking(inner) = block {
784                    inner.signature.push_str(signature);
785                }
786            }
787            ContentDelta::CitationsDelta { citation } => {
788                if let ContentBlock::Text(inner) = block {
789                    inner
790                        .citations
791                        .get_or_insert_with(Vec::new)
792                        .push(citation.clone());
793                }
794            }
795            ContentDelta::InputJsonDelta { .. } | ContentDelta::Unknown(_) => {}
796        }
797    }
798
799    fn finish_block(&mut self, index: usize) {
800        let Some(json) = self.json_buffers.remove(&index) else {
801            return;
802        };
803        let parsed = if json.trim().is_empty() {
804            // An empty input buffer means an empty tool input object.
805            serde_json::Value::Object(serde_json::Map::new())
806        } else {
807            // The concatenated fragments should parse as the tool_use `input`
808            // object. If they do not (a truncated or corrupted stream), preserve
809            // the raw accumulated bytes as a JSON string rather than silently
810            // discarding them as `null`, so the loss is detectable by the caller.
811            match serde_json::from_str(&json) {
812                Ok(value) => value,
813                Err(_) => serde_json::Value::String(json),
814            }
815        };
816        if let Some(message) = self.message.as_mut() {
817            if let Some(ContentBlock::ToolUse(inner) | ContentBlock::ServerToolUse(inner)) =
818                message.content.get_mut(index)
819            {
820                inner.input = parsed;
821            }
822        }
823    }
824}
825
826// ---------------------------------------------------------------------------
827// MessageStream.
828// ---------------------------------------------------------------------------
829
830/// The boxed byte stream a [`MessageStream`] reads from.
831/// `Send` on native targets, nothing on wasm (reqwest's wasm streams are not
832/// `Send`; single-threaded wasm doesn't need the bound).
833#[cfg(not(target_arch = "wasm32"))]
834pub trait MaybeSend: Send {}
835#[cfg(not(target_arch = "wasm32"))]
836impl<T: Send> MaybeSend for T {}
837/// `Send` on native targets, nothing on wasm.
838#[cfg(target_arch = "wasm32")]
839pub trait MaybeSend {}
840#[cfg(target_arch = "wasm32")]
841impl<T> MaybeSend for T {}
842
843#[cfg(not(target_arch = "wasm32"))]
844type ByteStream = Pin<Box<dyn Stream<Item = Result<Bytes>> + Send>>;
845#[cfg(target_arch = "wasm32")]
846type ByteStream = Pin<Box<dyn Stream<Item = Result<Bytes>>>>;
847
848/// A [`Stream`] of [`StreamEvent`]s decoded from a streaming `POST /v1/messages`
849/// response, which simultaneously accumulates a final [`Message`].
850///
851/// Drive it with your async runtime via [`futures_util::StreamExt`]. Each item
852/// is a `Result<StreamEvent>`: a decode failure or an in-stream `error` event
853/// yields `Err` and ends the stream. After the stream is exhausted (or at any
854/// point), [`final_message`](MessageStream::final_message) returns the message
855/// assembled so far; [`collect_final`](MessageStream::collect_final) drains the
856/// stream and returns the complete [`Message`].
857///
858/// # Examples
859///
860/// ```no_run
861/// use crimson_crab::api::MessagesRequest;
862/// use crimson_crab::types::MessageParam;
863///
864/// # async fn demo(client: &crimson_crab::Client) -> crimson_crab::Result<()> {
865/// let request = MessagesRequest::builder()
866///     .model("claude-opus-4-8")
867///     .max_tokens(1024)
868///     .messages(vec![MessageParam::user("Hi")])
869///     .build()?;
870/// let message = client.messages().stream(&request).await?.collect_final().await?;
871/// println!("{}", message.text());
872/// # Ok(())
873/// # }
874/// ```
875pub struct MessageStream {
876    source: ByteStream,
877    decoder: SseDecoder,
878    accumulator: Accumulator,
879    source_done: bool,
880    done: bool,
881}
882
883impl MessageStream {
884    /// Wraps a raw streaming HTTP response.
885    pub(crate) fn new(response: reqwest::Response) -> Self {
886        let source = response
887            .bytes_stream()
888            .map(|chunk| chunk.map_err(Error::from));
889        Self::from_pinned(Box::pin(source))
890    }
891
892    /// Builds a [`MessageStream`] from an arbitrary byte stream.
893    ///
894    /// This low-level constructor exists for custom transports and testing; most
895    /// callers obtain a stream via
896    /// [`Messages::stream`](crate::api::messages::Messages::stream).
897    ///
898    /// # Examples
899    ///
900    /// ```
901    /// use bytes::Bytes;
902    /// use crimson_crab::streaming::MessageStream;
903    /// use futures_util::stream;
904    ///
905    /// let chunks: Vec<crimson_crab::Result<Bytes>> =
906    ///     vec![Ok(Bytes::from_static(b"event: ping\ndata: {\"type\":\"ping\"}\n\n"))];
907    /// let stream = MessageStream::from_byte_stream(stream::iter(chunks));
908    /// assert!(stream.final_message().is_none());
909    /// ```
910    #[doc(hidden)]
911    pub fn from_byte_stream<S>(stream: S) -> Self
912    where
913        S: Stream<Item = Result<Bytes>> + MaybeSend + 'static,
914    {
915        Self::from_pinned(Box::pin(stream))
916    }
917
918    fn from_pinned(source: ByteStream) -> Self {
919        Self {
920            source,
921            decoder: SseDecoder::default(),
922            accumulator: Accumulator::default(),
923            source_done: false,
924            done: false,
925        }
926    }
927
928    /// Returns the message accumulated so far, or `None` before the
929    /// `message_start` event has been seen.
930    ///
931    /// # Examples
932    ///
933    /// ```
934    /// use bytes::Bytes;
935    /// use crimson_crab::streaming::MessageStream;
936    /// use futures_util::stream;
937    ///
938    /// let stream =
939    ///     MessageStream::from_byte_stream(stream::iter(Vec::<crimson_crab::Result<Bytes>>::new()));
940    /// assert!(stream.final_message().is_none());
941    /// ```
942    pub fn final_message(&self) -> Option<&Message> {
943        self.accumulator.message.as_ref()
944    }
945
946    /// Drains the stream to completion and returns the accumulated [`Message`].
947    ///
948    /// Returns the first error encountered (a decode failure, an in-stream
949    /// `error` event, or a transport error). Returns [`Error::Stream`] if the
950    /// stream ended without a `message_start` event.
951    ///
952    /// # Examples
953    ///
954    /// ```no_run
955    /// use crimson_crab::api::MessagesRequest;
956    /// use crimson_crab::types::MessageParam;
957    ///
958    /// # async fn demo(client: &crimson_crab::Client) -> crimson_crab::Result<()> {
959    /// let request = MessagesRequest::builder()
960    ///     .model("claude-opus-4-8")
961    ///     .max_tokens(1024)
962    ///     .messages(vec![MessageParam::user("Hi")])
963    ///     .build()?;
964    /// let message = client.messages().stream(&request).await?.collect_final().await?;
965    /// let _ = message.stop_reason;
966    /// # Ok(())
967    /// # }
968    /// ```
969    pub async fn collect_final(mut self) -> Result<Message> {
970        while let Some(item) = self.next().await {
971            item?;
972        }
973        self.accumulator
974            .message
975            .ok_or_else(|| Error::Stream("stream ended without a message_start event".to_string()))
976    }
977
978    /// Decodes one event `data` payload, accumulating it and returning the item
979    /// to yield (or `None` to skip an empty payload).
980    fn decode(&mut self, data: &str) -> Option<Result<StreamEvent>> {
981        if data.trim().is_empty() {
982            return None;
983        }
984        match serde_json::from_str::<StreamEvent>(data) {
985            Ok(StreamEvent::Error { error }) => {
986                self.done = true;
987                Some(Err(error::from_error_body(error.error_type, error.message)))
988            }
989            Ok(event) => {
990                self.accumulator.apply(&event);
991                Some(Ok(event))
992            }
993            Err(source) => {
994                self.done = true;
995                Some(Err(Error::from(source)))
996            }
997        }
998    }
999}
1000
1001impl Stream for MessageStream {
1002    type Item = Result<StreamEvent>;
1003
1004    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1005        let this = self.get_mut();
1006        if this.done {
1007            return Poll::Ready(None);
1008        }
1009        loop {
1010            if let Some(data) = this.decoder.next_event_data() {
1011                if let Some(item) = this.decode(&data) {
1012                    return Poll::Ready(Some(item));
1013                }
1014                continue;
1015            }
1016
1017            if this.source_done {
1018                if let Some(data) = this.decoder.flush() {
1019                    if let Some(item) = this.decode(&data) {
1020                        return Poll::Ready(Some(item));
1021                    }
1022                }
1023                this.done = true;
1024                return Poll::Ready(None);
1025            }
1026
1027            match this.source.as_mut().poll_next(cx) {
1028                Poll::Ready(Some(Ok(bytes))) => {
1029                    this.decoder.push(&bytes);
1030                    continue;
1031                }
1032                Poll::Ready(Some(Err(err))) => {
1033                    this.done = true;
1034                    return Poll::Ready(Some(Err(err)));
1035                }
1036                Poll::Ready(None) => {
1037                    this.source_done = true;
1038                    continue;
1039                }
1040                Poll::Pending => return Poll::Pending,
1041            }
1042        }
1043    }
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048    use super::*;
1049
1050    #[test]
1051    fn sse_decoder_tolerates_crlf_and_comments() {
1052        let mut decoder = SseDecoder::default();
1053        decoder.push(b": heartbeat comment\r\n");
1054        decoder.push(b"event: ping\r\ndata: {\"type\":\"ping\"}\r\n\r\n");
1055        let data = decoder.next_event_data().expect("one record");
1056        assert_eq!(data, "{\"type\":\"ping\"}");
1057        assert!(decoder.next_event_data().is_none());
1058    }
1059
1060    #[test]
1061    fn sse_decoder_joins_multiline_data() {
1062        let mut decoder = SseDecoder::default();
1063        decoder.push(b"data: line one\ndata: line two\n\n");
1064        let data = decoder.next_event_data().expect("one record");
1065        assert_eq!(data, "line one\nline two");
1066    }
1067
1068    #[test]
1069    fn content_delta_round_trips() {
1070        for raw in [
1071            r#"{"type":"text_delta","text":"Hi"}"#,
1072            r#"{"type":"input_json_delta","partial_json":"{\"a\":1}"}"#,
1073            r#"{"type":"thinking_delta","thinking":"hmm"}"#,
1074            r#"{"type":"signature_delta","signature":"sig"}"#,
1075        ] {
1076            let delta: ContentDelta = serde_json::from_str(raw).expect("parse");
1077            let out = serde_json::to_string(&delta).expect("serialize");
1078            let reparsed: ContentDelta = serde_json::from_str(&out).expect("reparse");
1079            assert_eq!(delta, reparsed);
1080        }
1081    }
1082
1083    #[test]
1084    fn empty_input_json_becomes_empty_object() {
1085        let mut acc = Accumulator::default();
1086        let message_start: StreamEvent = serde_json::from_str(
1087            r#"{"type":"message_start","message":{"id":"m1","type":"message","role":"assistant","model":"claude-opus-4-8","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":1,"output_tokens":1}}}"#,
1088        )
1089        .expect("parse message_start");
1090        acc.apply(&message_start);
1091        let start: StreamEvent = serde_json::from_str(
1092            r#"{"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"t1","name":"noop","input":{}}}"#,
1093        )
1094        .expect("parse start");
1095        acc.apply(&start);
1096        // No input_json_delta events at all -> empty buffer parses to `{}`.
1097        let stop: StreamEvent =
1098            serde_json::from_str(r#"{"type":"content_block_stop","index":0}"#).expect("parse stop");
1099        acc.apply(&stop);
1100        let message = acc.message.expect("no message");
1101        match &message.content[0] {
1102            ContentBlock::ToolUse(inner) => {
1103                assert_eq!(inner.input, serde_json::json!({}));
1104            }
1105            other => panic!("expected tool_use, got {other:?}"),
1106        }
1107    }
1108}