Skip to main content

ferrin_core/stream_text/
result.rs

1//! The result of `stream_text`: the event stream and the completion handle.
2//!
3//! Derived from the Vercel AI SDK (Apache-2.0, Copyright 2023 Vercel, Inc.),
4//! translated from TypeScript to Rust and modified; see `NOTICE`.
5
6mod tee;
7
8use std::collections::VecDeque;
9use std::fmt;
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::Arc;
13use std::task::Context;
14use std::task::Poll;
15
16use ferrin_spec::BoxStream;
17use ferrin_spec::JsonValue;
18use ferrin_spec::PartId;
19use ferrin_spec::error::ProviderError;
20use ferrin_spec::error::TypeValidationError;
21use futures_core::Stream;
22use futures_util::FutureExt;
23use futures_util::StreamExt;
24use futures_util::stream;
25use serde::de::DeserializeOwned;
26use tokio::sync::oneshot;
27
28use super::StreamEvent;
29use crate::error::Error;
30use crate::generate_text::GenerateTextResult;
31use crate::output::ArrayElements;
32use crate::output::OutputHandler;
33use crate::output::PartialOutput;
34
35/// A boxed stream of [`StreamEvent`]s.
36pub type EventStream = BoxStream<'static, StreamEvent>;
37
38/// Drives the event pipeline and resolves with its final result.
39///
40/// Other event views remain readable while this future runs or after it
41/// completes. Unread events are buffered for those views; drop unused views
42/// to release their buffers. Dropping every view and this future cancels the
43/// owned pipeline. The final result is owned and does not require `O: Clone`.
44pub struct Completion<O> {
45    receiver: oneshot::Receiver<Result<GenerateTextResult<O>, Error>>,
46    driver: Option<EventStream>,
47}
48
49impl<O> Completion<O> {
50    pub(crate) fn new(receiver: oneshot::Receiver<Result<GenerateTextResult<O>, Error>>) -> Self {
51        Self {
52            receiver,
53            driver: None,
54        }
55    }
56
57    fn driving(mut self, driver: EventStream) -> Self {
58        self.driver = Some(driver);
59        self
60    }
61}
62
63impl<O> Future for Completion<O> {
64    type Output = Result<GenerateTextResult<O>, Error>;
65
66    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
67        // Bound synchronous draining so an already-buffered stream cannot
68        // monopolize the executor while other cursors wait for progress.
69        for _ in 0..64 {
70            match Pin::new(&mut self.receiver).poll(cx) {
71                Poll::Ready(Ok(result)) => {
72                    self.driver = None;
73                    return Poll::Ready(result);
74                }
75                Poll::Ready(Err(_)) => {
76                    self.driver = None;
77                    return Poll::Ready(Err(Error::Cancelled));
78                }
79                Poll::Pending => {}
80            }
81            let Some(driver) = self.driver.as_mut() else {
82                return Poll::Pending;
83            };
84            match driver.as_mut().poll_next(cx) {
85                Poll::Ready(Some(_)) => {}
86                Poll::Ready(None) => {
87                    self.driver = None;
88                }
89                Poll::Pending => return Poll::Pending,
90            }
91        }
92        cx.waker().wake_by_ref();
93        Poll::Pending
94    }
95}
96
97impl<O> fmt::Debug for Completion<O> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        f.write_str("Completion(..)")
100    }
101}
102
103/// Result of a streaming call with independent event views and a final result.
104///
105/// Reading a view or awaiting [`final_result`](Self::final_result) drives
106/// progress. Views created through [`full_stream`](Self::full_stream) keep
107/// their unread events until consumed or dropped; a slow view does not
108/// block another view. The final owner drop cancels the pipeline.
109pub struct StreamTextResult<O> {
110    pub(crate) call_id: String,
111    pub(crate) events: EventStream,
112    pub(crate) completion: Completion<O>,
113    pub(crate) output: Arc<dyn OutputHandler<O>>,
114}
115
116impl<O> fmt::Debug for StreamTextResult<O> {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.debug_struct("StreamTextResult")
119            .field("call_id", &self.call_id)
120            .finish_non_exhaustive()
121    }
122}
123
124impl<O> StreamTextResult<O> {
125    /// The call id.
126    #[must_use]
127    pub fn call_id(&self) -> &str {
128        &self.call_id
129    }
130
131    /// Splits the result into the event stream and the completion handle.
132    #[must_use]
133    pub fn split(self) -> (EventStream, Completion<O>) {
134        let (events, driver) = tee::tee(self.events);
135        (events, self.completion.driving(driver))
136    }
137
138    /// Creates an independent view starting at the current event cursor.
139    ///
140    /// Creating a view leaves the result's cursor in place, allowing later
141    /// views to read the same events. Lagging views retain unread events.
142    #[must_use]
143    pub fn full_stream(&mut self) -> EventStream {
144        let current = std::mem::replace(&mut self.events, Box::pin(stream::empty()));
145        let (view, retained) = tee::tee(current);
146        self.events = retained;
147        view
148    }
149
150    /// Returns a completion future that drives the pipeline without an event consumer.
151    #[must_use]
152    pub fn into_completion(self) -> Completion<O> {
153        self.completion.driving(self.events)
154    }
155
156    /// Drives the pipeline and returns its owned final result.
157    ///
158    /// # Errors
159    ///
160    /// Returns the error that ended the call. Retain the result to borrow its
161    /// text, usage, steps and output repeatedly after successful completion.
162    pub async fn final_result(self) -> Result<GenerateTextResult<O>, Error> {
163        self.into_completion().await
164    }
165
166    /// Creates an independent view yielding only text deltas.
167    ///
168    /// Inspect a full view or await the final result for terminal errors.
169    pub fn text_view(&mut self) -> impl Stream<Item = String> + Send + use<O> {
170        self.full_stream().filter_map(|event| async move {
171            match event {
172                StreamEvent::TextDelta { text, .. } => Some(text),
173                _ => None,
174            }
175        })
176    }
177
178    /// The result's current event cursor; consuming it advances future view starts.
179    pub fn events(&mut self) -> &mut EventStream {
180        &mut self.events
181    }
182
183    /// Drains every event and returns the final result.
184    ///
185    /// # Errors
186    ///
187    /// Returns the error that ended the call.
188    pub async fn consume(self) -> Result<GenerateTextResult<O>, Error> {
189        self.final_result().await
190    }
191}
192
193impl<O: Send + Sync + 'static> StreamTextResult<O> {
194    /// Returns cloneable final-result waiters that jointly drive the pipeline.
195    ///
196    /// The result and error are shared through `Arc`, so neither must be
197    /// cloneable. Each waiter resolves to the same allocation. Dropping all
198    /// waiters and event views cancels an unfinished call.
199    pub fn into_shared_completion(
200        self,
201    ) -> impl Future<Output = Arc<Result<GenerateTextResult<O>, Error>>> + Clone + Send {
202        self.into_completion().map(Arc::new).boxed().shared()
203    }
204}
205
206impl<O: Send + 'static> StreamTextResult<O> {
207    /// Consumes the result, yielding text deltas only. The final item is an
208    /// error when the call failed.
209    pub fn text_stream(self) -> impl Stream<Item = Result<String, Error>> + Send {
210        stream::unfold(Some((self.events, self.completion)), |state| async move {
211            let (mut events, completion) = state?;
212            loop {
213                match events.next().await {
214                    Some(StreamEvent::TextDelta { text, .. }) => {
215                        return Some((Ok(text), Some((events, completion))));
216                    }
217                    Some(_) => {}
218                    None => {
219                        return match completion.await {
220                            Ok(_) => None,
221                            Err(error) => Some((Err(error), None)),
222                        };
223                    }
224                }
225            }
226        })
227    }
228
229    /// Consumes the result, yielding the structured output as it grows.
230    ///
231    /// Only the first text part of the call (or retry attempt) is parsed; a value is published
232    /// whenever the repaired partial JSON changes. Errors are not reported
233    /// here: use [`consume`](Self::consume) or the completion for them.
234    pub fn partial_output_stream(self) -> impl Stream<Item = PartialOutput<O>> + Send {
235        partial_stream(self.events, self.output)
236    }
237
238    /// Creates an independent view of changing structured output.
239    ///
240    /// Like the consuming view, this parses the first text part per attempt;
241    /// terminal errors remain available through the final result.
242    pub fn partial_output_view(&mut self) -> impl Stream<Item = PartialOutput<O>> + Send + use<O> {
243        partial_stream(self.full_stream(), self.output.clone())
244    }
245}
246
247fn partial_stream<O: Send + 'static>(
248    events: EventStream,
249    handler: Arc<dyn OutputHandler<O>>,
250) -> impl Stream<Item = PartialOutput<O>> + Send {
251    let state = PartialState {
252        events,
253        handler,
254        first_text: None,
255        text: String::new(),
256        last: None,
257    };
258    stream::unfold(Some(state), |state| async move {
259        let mut state = state?;
260        loop {
261            let event = state.events.next().await?;
262            if let Some(output) = state.handle(&event) {
263                return Some((output, Some(state)));
264            }
265        }
266    })
267}
268
269struct PartialState<O> {
270    events: EventStream,
271    handler: Arc<dyn OutputHandler<O>>,
272    first_text: Option<PartId>,
273    text: String,
274    last: Option<JsonValue>,
275}
276
277impl<O: 'static> PartialState<O> {
278    fn reset(&mut self) {
279        self.first_text = None;
280        self.text.clear();
281        self.last = None;
282    }
283
284    fn handle(&mut self, event: &StreamEvent) -> Option<PartialOutput<O>> {
285        match event {
286            StreamEvent::RetryAttempt { .. } => {
287                self.reset();
288                None
289            }
290            StreamEvent::TextStart { id, .. } => {
291                if self.first_text.is_none() {
292                    self.first_text = Some(id.clone());
293                }
294                None
295            }
296            StreamEvent::TextDelta { id, text, .. } => {
297                if self.first_text.as_ref() != Some(id) || text.is_empty() {
298                    return None;
299                }
300                self.text.push_str(text);
301                let value = self.handler.parse_partial(&self.text)?;
302                if self.last.as_ref() == Some(&value) {
303                    return None;
304                }
305                self.last = Some(value.clone());
306                let typed = self.handler.typed_partial(&value);
307                Some(PartialOutput { value, typed })
308            }
309            _ => None,
310        }
311    }
312}
313
314impl<O> StreamTextResult<O>
315where
316    O: ArrayElements + IntoIterator<Item = <O as ArrayElements>::Element> + Send + 'static,
317    O::Element: DeserializeOwned + Send,
318{
319    /// Consumes the result, yielding each element of an array output as soon
320    /// as it is complete. The final item is an error when the call failed.
321    pub fn element_stream(self) -> impl Stream<Item = Result<O::Element, Error>> + Send {
322        elements(self.events, Some(self.completion), self.output)
323    }
324
325    /// Creates an independent view of completed array elements.
326    ///
327    /// Element decoding errors appear in this view; inspect the final result
328    /// for terminal provider or pipeline errors.
329    pub fn element_view(
330        &mut self,
331    ) -> impl Stream<Item = Result<O::Element, Error>> + Send + use<O> {
332        elements(self.full_stream(), None, self.output.clone())
333    }
334}
335
336fn elements<O>(
337    events: EventStream,
338    completion: Option<Completion<O>>,
339    handler: Arc<dyn OutputHandler<O>>,
340) -> impl Stream<Item = Result<O::Element, Error>> + Send
341where
342    O: ArrayElements + IntoIterator<Item = <O as ArrayElements>::Element> + Send + 'static,
343    O::Element: DeserializeOwned + Send,
344{
345    let state = ElementState {
346        events,
347        completion,
348        handler,
349        first_text: None,
350        text: String::new(),
351        published: 0,
352        pending: VecDeque::new(),
353        failed: false,
354    };
355    stream::unfold(Some(state), |state| async move {
356        let mut state = state?;
357        loop {
358            if let Some(item) = state.pending.pop_front() {
359                let next = if state.failed && state.pending.is_empty() {
360                    None
361                } else {
362                    Some(state)
363                };
364                return Some((item, next));
365            }
366            match state.events.next().await {
367                Some(event) => state.handle(&event),
368                None => {
369                    let completion = state.completion.take()?;
370                    return match completion.await {
371                        Ok(_) => None,
372                        Err(error) => Some((Err(error), None)),
373                    };
374                }
375            }
376        }
377    })
378}
379
380struct ElementState<O: ArrayElements> {
381    events: EventStream,
382    completion: Option<Completion<O>>,
383    handler: Arc<dyn OutputHandler<O>>,
384    first_text: Option<PartId>,
385    text: String,
386    published: usize,
387    pending: VecDeque<Result<O::Element, Error>>,
388    failed: bool,
389}
390
391impl<O> ElementState<O>
392where
393    O: ArrayElements + IntoIterator<Item = <O as ArrayElements>::Element> + 'static,
394    O::Element: DeserializeOwned,
395{
396    fn handle(&mut self, event: &StreamEvent) {
397        match event {
398            StreamEvent::RetryAttempt { .. } => {
399                self.first_text = None;
400                self.text.clear();
401                // Already-published array prefixes cannot be retracted. Keep
402                // the count, as in the reference element transform.
403            }
404            StreamEvent::TextStart { id, .. } => {
405                if self.first_text.is_none() {
406                    self.first_text = Some(id.clone());
407                }
408            }
409            StreamEvent::TextDelta { id, text, .. } => {
410                if self.first_text.as_ref() != Some(id) || text.is_empty() {
411                    return;
412                }
413                self.text.push_str(text);
414                let elements: Vec<Result<O::Element, Error>> =
415                    if let Some(typed) = self.handler.parse_typed_elements(&self.text) {
416                        typed.into_iter().map(Ok).collect()
417                    } else if let Some(raw) = self.handler.parse_elements(&self.text) {
418                        raw.into_iter()
419                            .map(|value| serde_json::from_value(value).map_err(Error::other))
420                            .collect()
421                    } else {
422                        return;
423                    };
424                for element in elements.into_iter().skip(self.published) {
425                    if let Some(max) = self.handler.max_elements()
426                        && self.published >= max
427                    {
428                        let value = self
429                            .handler
430                            .parse_partial(&self.text)
431                            .unwrap_or(JsonValue::Null);
432                        let error = TypeValidationError::new(
433                            value,
434                            std::io::Error::other(format!(
435                                "elements array must contain at most {max} items"
436                            )),
437                        );
438                        self.pending
439                            .push_back(Err(Error::from(ProviderError::from(error))));
440                        self.failed = true;
441                        break;
442                    }
443                    self.published += 1;
444                    self.pending.push_back(element);
445                }
446            }
447            _ => {}
448        }
449    }
450}