Skip to main content

audacity_sdk/
stream.rs

1//! Streaming output handle — wraps the SSE byte stream and exposes
2//! `recv().await` (aws EventReceiver parity).
3
4use futures_util::StreamExt;
5use tokio::sync::mpsc;
6
7use crate::mapping::{flush_metadata, process_chunk, StreamState};
8use crate::sse::{SseParser, SseToken};
9use crate::types::ConverseStreamOutput;
10use crate::Error;
11
12/// Handle returned by `converse_stream().send().await`.
13///
14/// Mirrors the aws-sdk-rust `EventReceiver` pattern:
15/// ```rust,ignore
16/// while let Some(event) = output.stream.recv().await? { … }
17/// ```
18pub struct ConverseStreamOutputHandle {
19    pub stream: StreamReceiver,
20}
21
22/// The receiving end of the stream channel.
23pub struct StreamReceiver {
24    rx: mpsc::Receiver<Result<ConverseStreamOutput, Error>>,
25}
26
27impl StreamReceiver {
28    /// Receive the next stream event.
29    /// Returns `Ok(None)` when the stream has ended normally.
30    /// Returns `Err(e)` on stream errors.
31    pub async fn recv(&mut self) -> Result<Option<ConverseStreamOutput>, Error> {
32        match self.rx.recv().await {
33            Some(Ok(event)) => Ok(Some(event)),
34            Some(Err(e)) => Err(e),
35            None => Ok(None),
36        }
37    }
38}
39
40/// Spawn a background task that drives the SSE byte stream and sends parsed
41/// events through the channel.
42pub(crate) fn spawn_stream_driver(
43    response: reqwest::Response,
44    start_time: std::time::Instant,
45) -> ConverseStreamOutputHandle {
46    let (tx, rx) = mpsc::channel::<Result<ConverseStreamOutput, Error>>(64);
47
48    tokio::spawn(async move {
49        let mut byte_stream = response.bytes_stream();
50        let mut parser = SseParser::new();
51        let mut state = StreamState::new();
52        state.start_time = start_time;
53
54        let mut done = false; // saw the [DONE] sentinel
55        let mut aborted = false; // already surfaced an error / receiver gone
56        let mut eof = false; // byte stream ended
57
58        'read: while !done && !eof {
59            match byte_stream.next().await {
60                Some(Ok(chunk)) => {
61                    parser.push(&chunk);
62                    if parser.overflowed() {
63                        let _ = tx
64                            .send(Err(Error::model_stream_error(
65                                "SSE line exceeds the 32 MiB limit",
66                            )))
67                            .await;
68                        aborted = true;
69                        break 'read;
70                    }
71                }
72                // Transport failure after the first SSE byte → spec §1/§4:
73                // ModelStreamErrorException, never a raw I/O error, never a retry.
74                Some(Err(e)) => {
75                    let _ = tx
76                        .send(Err(Error::model_stream_error(format!(
77                            "stream read error: {e}"
78                        ))))
79                        .await;
80                    aborted = true;
81                    break 'read;
82                }
83                None => {
84                    // Connection closed: flush a possible partial trailing line
85                    // and run its tokens through the same handling below.
86                    parser.flush();
87                    eof = true;
88                }
89            }
90
91            for token in parser.drain() {
92                match token {
93                    SseToken::Done => {
94                        done = true;
95                        break;
96                    }
97                    SseToken::Data(json_str) => match process_chunk(&mut state, &json_str) {
98                        Ok(events) => {
99                            for ev in events {
100                                if tx.send(Ok(ev)).await.is_err() {
101                                    aborted = true;
102                                    break 'read;
103                                }
104                            }
105                        }
106                        Err(e) => {
107                            let _ = tx.send(Err(e)).await;
108                            aborted = true;
109                            break 'read;
110                        }
111                    },
112                }
113            }
114        }
115
116        // Terminal rule: [DONE] seen → emit pending metadata as the final
117        // event; connection dropped without [DONE] → ModelStreamError.
118        if done {
119            if let Some(meta) = flush_metadata(&state) {
120                let _ = tx.send(Ok(meta)).await;
121            }
122        } else if !aborted {
123            let _ = tx
124                .send(Err(Error::model_stream_error(
125                    "connection closed before [DONE]",
126                )))
127                .await;
128        }
129    });
130
131    ConverseStreamOutputHandle {
132        stream: StreamReceiver { rx },
133    }
134}