Skip to main content

ferrin_core/stream_text/
result.rs

1//! The result of `stream_text`: the event stream and the completion handle.
2
3use std::collections::VecDeque;
4use std::fmt;
5use std::future::Future;
6use std::pin::Pin;
7use std::sync::Arc;
8use std::task::Context;
9use std::task::Poll;
10
11use ferrin_spec::BoxStream;
12use ferrin_spec::PartId;
13use futures_core::Stream;
14use futures_util::StreamExt;
15use futures_util::stream;
16use serde::de::DeserializeOwned;
17use tokio::sync::oneshot;
18
19use super::StreamEvent;
20use crate::error::Error;
21use crate::generate_text::GenerateTextResult;
22use crate::output::ArrayElements;
23use crate::output::OutputHandler;
24use crate::output::PartialOutput;
25
26/// A boxed stream of [`StreamEvent`]s.
27pub type EventStream = BoxStream<'static, StreamEvent>;
28
29/// Resolves with the final result once the event stream has been drained.
30///
31/// Dropping the event stream before it ends cancels the call; the
32/// completion then resolves to [`Error::Cancelled`]. Awaiting the completion
33/// without consuming the events stalls: the pipeline does not buffer.
34pub struct Completion<O> {
35    receiver: oneshot::Receiver<Result<GenerateTextResult<O>, Error>>,
36}
37
38impl<O> Completion<O> {
39    pub(crate) fn new(receiver: oneshot::Receiver<Result<GenerateTextResult<O>, Error>>) -> Self {
40        Self { receiver }
41    }
42}
43
44impl<O> Future for Completion<O> {
45    type Output = Result<GenerateTextResult<O>, Error>;
46
47    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
48        match Pin::new(&mut self.receiver).poll(cx) {
49            Poll::Ready(Ok(result)) => Poll::Ready(result),
50            Poll::Ready(Err(_)) => Poll::Ready(Err(Error::Cancelled)),
51            Poll::Pending => Poll::Pending,
52        }
53    }
54}
55
56impl<O> fmt::Debug for Completion<O> {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        f.write_str("Completion(..)")
59    }
60}
61
62/// Result of a streaming call: one event stream plus a completion handle.
63///
64/// The stream must be consumed for the call to progress. Use
65/// [`split`](Self::split) to forward events in one task and await the
66/// completion in another, or one of the consuming views.
67pub struct StreamTextResult<O> {
68    pub(crate) call_id: String,
69    pub(crate) events: EventStream,
70    pub(crate) completion: Completion<O>,
71    pub(crate) output: Arc<dyn OutputHandler<O>>,
72}
73
74impl<O> fmt::Debug for StreamTextResult<O> {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        f.debug_struct("StreamTextResult")
77            .field("call_id", &self.call_id)
78            .finish_non_exhaustive()
79    }
80}
81
82impl<O> StreamTextResult<O> {
83    /// The call id.
84    #[must_use]
85    pub fn call_id(&self) -> &str {
86        &self.call_id
87    }
88
89    /// Splits the result into the event stream and the completion handle.
90    #[must_use]
91    pub fn split(self) -> (EventStream, Completion<O>) {
92        (self.events, self.completion)
93    }
94
95    /// The event stream.
96    pub fn events(&mut self) -> &mut EventStream {
97        &mut self.events
98    }
99
100    /// Drains every event and returns the final result.
101    ///
102    /// # Errors
103    ///
104    /// Returns the error that ended the call.
105    pub async fn consume(self) -> Result<GenerateTextResult<O>, Error> {
106        let (mut events, completion) = self.split();
107        while events.next().await.is_some() {}
108        completion.await
109    }
110}
111
112impl<O: Send + 'static> StreamTextResult<O> {
113    /// Consumes the result, yielding text deltas only. The final item is an
114    /// error when the call failed.
115    pub fn text_stream(self) -> impl Stream<Item = Result<String, Error>> + Send {
116        stream::unfold(Some((self.events, self.completion)), |state| async move {
117            let (mut events, completion) = state?;
118            loop {
119                match events.next().await {
120                    Some(StreamEvent::TextDelta { text, .. }) => {
121                        return Some((Ok(text), Some((events, completion))));
122                    }
123                    Some(_) => {}
124                    None => {
125                        return match completion.await {
126                            Ok(_) => None,
127                            Err(error) => Some((Err(error), None)),
128                        };
129                    }
130                }
131            }
132        })
133    }
134
135    /// Consumes the result, yielding the structured output as it grows.
136    ///
137    /// Only the first text part of each step is parsed; a value is published
138    /// whenever the repaired partial JSON changes. Errors are not reported
139    /// here: use [`consume`](Self::consume) or the completion for them.
140    pub fn partial_output_stream(self) -> impl Stream<Item = PartialOutput<O>> + Send {
141        let state = PartialState {
142            events: self.events,
143            handler: self.output,
144            first_text: None,
145            text: String::new(),
146            last: None,
147        };
148        stream::unfold(Some(state), |state| async move {
149            let mut state = state?;
150            loop {
151                let event = state.events.next().await?;
152                if let Some(output) = state.handle(&event) {
153                    return Some((output, Some(state)));
154                }
155            }
156        })
157    }
158}
159
160struct PartialState<O> {
161    events: EventStream,
162    handler: Arc<dyn OutputHandler<O>>,
163    first_text: Option<PartId>,
164    text: String,
165    last: Option<String>,
166}
167
168impl<O: 'static> PartialState<O> {
169    fn reset(&mut self) {
170        self.first_text = None;
171        self.text.clear();
172        self.last = None;
173    }
174
175    fn handle(&mut self, event: &StreamEvent) -> Option<PartialOutput<O>> {
176        match event {
177            StreamEvent::StartStep { .. } | StreamEvent::RetryAttempt { .. } => {
178                self.reset();
179                None
180            }
181            StreamEvent::TextStart { id, .. } => {
182                if self.first_text.is_none() {
183                    self.first_text = Some(id.clone());
184                }
185                None
186            }
187            StreamEvent::TextDelta { id, text, .. } => {
188                if self.first_text.as_ref() != Some(id) || text.is_empty() {
189                    return None;
190                }
191                self.text.push_str(text);
192                let value = self.handler.parse_partial(&self.text)?;
193                let serialized = value.to_string();
194                if self.last.as_deref() == Some(serialized.as_str()) {
195                    return None;
196                }
197                self.last = Some(serialized);
198                let typed = self.handler.typed_partial(&value);
199                Some(PartialOutput { value, typed })
200            }
201            _ => None,
202        }
203    }
204}
205
206impl<O> StreamTextResult<O>
207where
208    O: ArrayElements + Send + 'static,
209    O::Element: DeserializeOwned + Send,
210{
211    /// Consumes the result, yielding each element of an array output as soon
212    /// as it is complete. The final item is an error when the call failed.
213    pub fn element_stream(self) -> impl Stream<Item = Result<O::Element, Error>> + Send {
214        let state = ElementState {
215            events: self.events,
216            completion: Some(self.completion),
217            handler: self.output,
218            first_text: None,
219            text: String::new(),
220            published: 0,
221            pending: VecDeque::new(),
222        };
223        stream::unfold(Some(state), |state| async move {
224            let mut state = state?;
225            loop {
226                if let Some(item) = state.pending.pop_front() {
227                    return Some((item, Some(state)));
228                }
229                match state.events.next().await {
230                    Some(event) => state.handle(&event),
231                    None => {
232                        let completion = state.completion.take()?;
233                        return match completion.await {
234                            Ok(_) => None,
235                            Err(error) => Some((Err(error), None)),
236                        };
237                    }
238                }
239            }
240        })
241    }
242}
243
244struct ElementState<O: ArrayElements> {
245    events: EventStream,
246    completion: Option<Completion<O>>,
247    handler: Arc<dyn OutputHandler<O>>,
248    first_text: Option<PartId>,
249    text: String,
250    published: usize,
251    pending: VecDeque<Result<O::Element, Error>>,
252}
253
254impl<O> ElementState<O>
255where
256    O: ArrayElements + 'static,
257    O::Element: DeserializeOwned,
258{
259    fn handle(&mut self, event: &StreamEvent) {
260        match event {
261            StreamEvent::StartStep { .. } | StreamEvent::RetryAttempt { .. } => {
262                self.first_text = None;
263                self.text.clear();
264                self.published = 0;
265            }
266            StreamEvent::TextStart { id, .. } => {
267                if self.first_text.is_none() {
268                    self.first_text = Some(id.clone());
269                }
270            }
271            StreamEvent::TextDelta { id, text, .. } => {
272                if self.first_text.as_ref() != Some(id) || text.is_empty() {
273                    return;
274                }
275                self.text.push_str(text);
276                let Some(elements) = self.handler.parse_elements(&self.text) else {
277                    return;
278                };
279                for element in elements.into_iter().skip(self.published) {
280                    self.published += 1;
281                    self.pending
282                        .push_back(serde_json::from_value(element).map_err(Error::other));
283                }
284            }
285            _ => {}
286        }
287    }
288}