Skip to main content

rig_core/serve/
handler.rs

1//! Handler dispatch, owned replies, and delivery observation.
2//!
3//! ```
4//! use rig_core::{effect::EffectId, serve::Dispatch};
5//!
6//! let dispatch = Dispatch::new(EffectId::from_raw(1), false);
7//! assert!(!dispatch.is_stream());
8//! ```
9
10use std::{
11    sync::{Arc, Mutex},
12    task::Poll,
13};
14
15use futures::{StreamExt, channel::oneshot};
16
17use crate::{
18    effect::{EffectId, EffectKind, HandlerDescriptor, Outcome},
19    error::{ErrorKind, ErrorReport},
20    streaming::{Item, Relayed, StreamEvent, StreamEvents},
21    wasm_compat::{WasmBoxedFuture, WasmCompatSend, WasmCompatSync},
22};
23
24#[cfg(test)]
25mod tests;
26
27/// Boxed handler reply future, `Send` on native targets and locally polled on WASM.
28pub type HandlerFuture<'a> = WasmBoxedFuture<'a, Reply>;
29
30/// A registered effect handler returning an outcome or an owned stream.
31///
32/// Provider and tool authors do not implement this directly: the adapters
33/// in [`crate::serve::adapters`] wrap models, tools, memories and indexes
34/// (`Model`, `Tool`, `ConversationMemory`, `VectorStoreIndex`). A
35/// host implements it for out-of-tree kinds ([`EffectKind::Custom`], typed
36/// through [`crate::effect::CustomEffect`]) or for a replayer.
37///
38/// A handler returns an outcome or an owned stream. The driver adapts that
39/// reply to the requested delivery mode. Stream execution continues after
40/// this method returns; dropping the reply cancels that work.
41///
42/// The returned future must be `Send` natively (it runs inside the driver's
43/// task; the bound is the crate's `WasmCompatSend` marker, a no-op on
44/// browser wasm). `Self::Family` is what a typed key can be proven against
45/// (a typed registration on the bus); a handler with no one
46/// family names [`crate::effect::family::Dynamic`].
47///
48/// A handler holding an `Rc` or a `Cell` is refused natively:
49///
50/// ```compile_fail
51/// use std::rc::Rc;
52/// use rig_core::{serve::{Dispatch, Reply, Serve}, effect::{EffectKind, HandlerDescriptor, family}};
53///
54/// struct Local(Rc<u8>);
55/// impl Serve for Local {
56///     type Family = family::Dynamic;
57///     fn descriptor(&self) -> HandlerDescriptor { unimplemented!() }
58///     async fn serve(&self, _kind: EffectKind, _dispatch: Dispatch) -> Reply { unimplemented!() }
59/// }
60/// ```
61///
62/// ```compile_fail
63/// use std::cell::Cell;
64/// use rig_core::{serve::{Dispatch, Reply, Serve}, effect::{EffectKind, HandlerDescriptor, family}};
65///
66/// struct Local(Cell<u8>);
67/// impl Serve for Local {
68///     type Family = family::Dynamic;
69///     fn descriptor(&self) -> HandlerDescriptor { unimplemented!() }
70///     async fn serve(&self, _kind: EffectKind, _dispatch: Dispatch) -> Reply { unimplemented!() }
71/// }
72/// ```
73pub trait Serve: WasmCompatSend + WasmCompatSync {
74    /// The family this handler serves, or `Dynamic`.
75    type Family: crate::effect::Served;
76
77    /// What this handler is: the family-keyed description a typed view
78    /// checks at bind time and a scene serializes.
79    fn descriptor(&self) -> HandlerDescriptor;
80
81    /// Prepare an outcome or an owned stream for the driver to consume.
82    fn serve(
83        &self,
84        kind: EffectKind,
85        dispatch: Dispatch,
86    ) -> impl Future<Output = Reply> + WasmCompatSend + use<'_, Self>;
87}
88
89/// Object-safe handler interface implemented for every [`Serve`].
90pub(crate) trait Handler: WasmCompatSend + WasmCompatSync {
91    fn descriptor(&self) -> HandlerDescriptor;
92    fn handle(&self, kind: EffectKind, dispatch: Dispatch) -> HandlerFuture<'_>;
93}
94
95// A type that is not a `Serve` should be told to implement `Serve`, never
96// the crate-private `Handler` this blanket impl provides.
97#[diagnostic::do_not_recommend]
98impl<T: Serve> Handler for T {
99    fn descriptor(&self) -> HandlerDescriptor {
100        Serve::descriptor(self)
101    }
102
103    fn handle(&self, kind: EffectKind, dispatch: Dispatch) -> HandlerFuture<'_> {
104        let observer = dispatch.observer.clone();
105        let folded = dispatch.folded.clone();
106        let streaming = dispatch.is_stream();
107        Box::pin(async move {
108            let reply = self.serve(kind, dispatch).await;
109            let seen = observer.and_then(|slot| lock(&slot).take());
110            reply.observed(streaming, seen, folded)
111        })
112    }
113}
114
115/// A shared handler is a handler: `Arc<H>` forwards, so one handler can be
116/// registered under several keys.
117impl<H: Serve + ?Sized> Serve for Arc<H> {
118    type Family = H::Family;
119
120    fn descriptor(&self) -> HandlerDescriptor {
121        (**self).descriptor()
122    }
123
124    async fn serve(&self, kind: EffectKind, dispatch: Dispatch) -> Reply {
125        (**self).serve(kind, dispatch).await
126    }
127}
128
129/// Shared, type-erased handler. Clones retain the same handler allocation.
130/// Implements `Send + Sync` on native targets, but not on WASM.
131#[derive(Clone)]
132pub struct ErasedHandler(ErasedInner);
133
134#[cfg(not(target_family = "wasm"))]
135type ErasedInner = Arc<dyn Handler + Send + Sync>;
136#[cfg(target_family = "wasm")]
137type ErasedInner = Arc<dyn Handler>;
138
139impl ErasedHandler {
140    /// Erase `handler`.
141    pub fn new(handler: impl Serve + 'static) -> Self {
142        Self(Arc::new(handler))
143    }
144
145    /// Wrap this handler in a [`Layer`](super::Layer): `intercept` sees
146    /// every dispatch before this handler does and every answer after.
147    /// `handler.layered(a).layered(b)` calls `b.before` first and `a.after` first.
148    pub fn layered(self, intercept: impl super::Intercept) -> Self {
149        Self::new(super::Layer::new(self, intercept))
150    }
151
152    /// What the erased handler is.
153    pub fn descriptor(&self) -> HandlerDescriptor {
154        self.0.descriptor()
155    }
156
157    /// Serve one effect: the driver's call, straight to the boxed handler.
158    pub fn handle(&self, kind: EffectKind, dispatch: Dispatch) -> HandlerFuture<'_> {
159        self.0.handle(kind, dispatch)
160    }
161
162    /// Whether two erased handlers are the same allocation.
163    pub fn ptr_eq(&self, other: &Self) -> bool {
164        Arc::ptr_eq(&self.0, &other.0)
165    }
166}
167
168impl std::fmt::Debug for ErasedHandler {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        f.debug_struct("ErasedHandler")
171            .field("key", &self.0.descriptor().key)
172            .finish_non_exhaustive()
173    }
174}
175
176/// Forwards serving to the wrapped handler; re-erasure adds another boxed future.
177impl Serve for ErasedHandler {
178    type Family = crate::effect::family::Dynamic;
179
180    fn descriptor(&self) -> HandlerDescriptor {
181        self.0.descriptor()
182    }
183
184    async fn serve(&self, kind: EffectKind, dispatch: Dispatch) -> Reply {
185        self.handle(kind, dispatch).await
186    }
187}
188
189pub trait Observe: Send + Sync {
190    /// Provider witness associated with this dispatch, independently of recording.
191    fn adapter_context(&self) -> Option<crate::observe::AdapterContext> {
192        None
193    }
194
195    /// The completed handler response, or the fold of a streaming handler's
196    /// events. A resolved response retains content that has no stream block,
197    /// such as images emitted as unknown events.
198    fn outcome(&mut self, outcome: &Result<Outcome, ErrorReport>);
199    /// Whether streamed items are wanted verbatim ([`Self::event`]).
200    fn keep_events(&self) -> bool;
201    /// One item pulled from the original handler stream. This is recording
202    /// evidence, not acknowledgement that a consumer received the item.
203    fn event(&mut self, item: &Item<StreamEvent>);
204    /// An error item in a kept stream.
205    fn stream_error(&mut self, _error: &ErrorReport) {}
206    /// Who a streamed reply is from, before its first item. Required, so a
207    /// recording observer cannot drop it: a replayed stream cut short needs
208    /// it to know its origin.
209    fn origin(&mut self, origin: &crate::message::Origin);
210    /// Observe one stream item together with its first folded outcome, if any.
211    /// Drivers that snapshot recording concurrently with cancellation can override
212    /// this operation to make the item and its answer one observation boundary.
213    fn stream_item(
214        &mut self,
215        item: &Result<Relayed, ErrorReport>,
216        outcome: Option<&Result<Outcome, ErrorReport>>,
217    ) {
218        if self.keep_events() {
219            match item {
220                Ok(Relayed::Origin(origin)) => self.origin(origin),
221                Ok(Relayed::Item(item)) => self.event(item),
222                Ok(Relayed::Done(_)) => {}
223                Err(error) => self.stream_error(error),
224            }
225        }
226        if let Some(outcome) = outcome {
227            self.outcome(outcome);
228        }
229    }
230    /// The dispatch was decided before any handler served it, by the layer
231    /// named: the record it opened is forgotten.
232    fn discard(&mut self, layer: &str);
233    /// The layer named serves `kind` in place of the effect that began
234    /// (same family): the record's request is what the innermost handler
235    /// served.
236    fn patch(&mut self, kind: &EffectKind);
237}
238
239/// What a unary consumer, the record, or a layer's verdict takes from a
240/// stream: its outcome, the response the stream ends with or the first
241/// error item. The origin folded the response; the tap only watches for it.
242#[derive(Default)]
243pub struct StreamTap {
244    /// The tap yielded its outcome.
245    finished: bool,
246}
247
248impl StreamTap {
249    /// A new stream tap.
250    pub fn new() -> Self {
251        Self::default()
252    }
253
254    /// Returns the stream's outcome once: the response it ends with, or the
255    /// first error item. Every later call returns `None`.
256    pub fn observe(
257        &mut self,
258        item: &Result<Relayed, ErrorReport>,
259    ) -> Option<Result<Outcome, ErrorReport>> {
260        if self.finished {
261            return None;
262        }
263        let outcome = match item {
264            Ok(Relayed::Origin(_) | Relayed::Item(_)) => None,
265            Ok(Relayed::Done(response)) => Some(Ok(Outcome::Completion((**response).clone()))),
266            Err(report) => Some(Err(report.clone())),
267        };
268        self.finished = outcome.is_some();
269        outcome
270    }
271}
272
273/// The retryable report of a reply that ended before the provider ended it
274/// ([`ProviderError::Truncated`](crate::error::ProviderError::Truncated)).
275/// Retryability does not guarantee a subsequent attempt will succeed.
276pub fn stream_truncated() -> ErrorReport {
277    ErrorReport::from(&crate::error::ProviderError::Truncated)
278}
279
280/// An answer, or an owned stream whose execution belongs to the driver.
281pub enum Reply {
282    /// The completed unary answer or a setup error.
283    Outcome(Result<Outcome, ErrorReport>),
284    /// Events, including any frames after the first terminal record.
285    Stream(StreamEvents),
286}
287
288impl Reply {
289    /// Fold a stream to its first outcome, or return the unary answer.
290    pub async fn into_outcome(self) -> Result<Outcome, ErrorReport> {
291        self.folded_outcome(None).await
292    }
293
294    pub(crate) async fn folded_outcome(
295        self,
296        folded: Option<Folded>,
297    ) -> Result<Outcome, ErrorReport> {
298        match self {
299            Self::Outcome(outcome) => outcome,
300            Self::Stream(mut stream) => {
301                let mut fold = StreamTap::new();
302                while let Some(item) = stream.next().await {
303                    let outcome = match &folded {
304                        Some(folded) => lock(folded).take(),
305                        None => fold.observe(&item),
306                    };
307                    if let Some(outcome) = outcome {
308                        return outcome;
309                    }
310                }
311                Err(stream_truncated())
312            }
313        }
314    }
315
316    /// Convert a completion into its events, then the response;
317    /// incompatible outcomes become errors.
318    pub fn into_stream(self) -> StreamEvents {
319        match self {
320            Self::Stream(stream) => stream,
321            Self::Outcome(outcome) => Box::pin(futures::stream::iter(match outcome {
322                Ok(Outcome::Completion(response)) => {
323                    match crate::operation::completion::events_of(&response) {
324                        Ok(items) => std::iter::once(Ok(Relayed::Origin(response.origin.clone())))
325                            .chain(items.into_iter().map(|item| Ok(Relayed::Item(item))))
326                            .chain(std::iter::once(Ok(Relayed::Done(Box::new(response)))))
327                            .collect(),
328                        Err(error) => vec![Err(ErrorReport::from(&error))],
329                    }
330                }
331                Ok(other) => vec![Err(wrong_stream_answer(&other))],
332                Err(report) => vec![Err(report)],
333            })),
334        }
335    }
336
337    fn observed(self, streaming: bool, mut seen: Option<Observed>, folded: Option<Folded>) -> Self {
338        if !streaming && let Self::Outcome(outcome) = self {
339            if let Some(seen) = &mut seen {
340                seen.outcome(&outcome);
341            }
342            return Self::Outcome(outcome);
343        }
344        if seen.is_none() && folded.is_none() {
345            return if streaming {
346                Self::Stream(self.into_stream())
347            } else {
348                self
349            };
350        }
351        let original = match &self {
352            Self::Outcome(Ok(Outcome::Completion(response))) => {
353                Some(Ok(Outcome::Completion(response.clone())))
354            }
355            _ => None,
356        };
357        let mut stream = self.into_stream();
358        let mut fold = StreamTap::new();
359        let mut finished = false;
360        Self::Stream(Box::pin(futures::stream::poll_fn(move |cx| {
361            let item = match stream.as_mut().poll_next(cx) {
362                Poll::Pending => return Poll::Pending,
363                Poll::Ready(item) => item,
364            };
365            if let Some(item) = &item {
366                let outcome = if finished { None } else { fold.observe(item) };
367                if let Some(seen) = &mut seen {
368                    let recorded = outcome.as_ref().map(|outcome| {
369                        if matches!(item, Ok(Relayed::Done(_))) {
370                            original.as_ref().unwrap_or(outcome)
371                        } else {
372                            outcome
373                        }
374                    });
375                    if streaming {
376                        seen.item(item, recorded);
377                    } else if let Some(recorded) = recorded {
378                        seen.outcome(recorded);
379                    }
380                }
381                if let Some(outcome) = outcome {
382                    finished = true;
383                    if let Some(folded) = &folded {
384                        *lock(folded) = Some(outcome);
385                    }
386                }
387            } else if !finished {
388                finished = true;
389                let outcome = Err(stream_truncated());
390                if let Some(seen) = &mut seen {
391                    seen.outcome(&outcome);
392                }
393                if let Some(folded) = &folded {
394                    *lock(folded) = Some(outcome);
395                }
396            }
397            Poll::Ready(item)
398        })))
399    }
400}
401
402// A layer and the recorder at its immediate inner boundary use the same fold.
403// Each outer boundary gets its own slot: a verdict can change that view.
404pub(crate) type Folded = Arc<Mutex<Option<Result<Outcome, ErrorReport>>>>;
405
406fn lock<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
407    value
408        .lock()
409        .unwrap_or_else(std::sync::PoisonError::into_inner)
410}
411
412/// An effect's identity, requested delivery mode, and driver-provided scopes.
413/// Reply transport and cancellation remain owned by the driver.
414pub struct Dispatch {
415    adapter_context: Option<crate::observe::AdapterContext>,
416    adapter_context_explicit: bool,
417    id: EffectId,
418    streaming: bool,
419    scopes: Vec<Arc<dyn std::any::Any + Send + Sync>>,
420    observer: Option<Arc<Mutex<Option<Observed>>>>,
421    /// The layer whose verdict replaced the answer the consumer receives,
422    /// once one did. Shared down the layer chain; a driver reads it after
423    /// the reply ([`Dispatch::replaced_by`]).
424    replaced_by: Arc<Mutex<Option<String>>>,
425    folded: Option<Folded>,
426}
427
428/// A layer's handle for attributing its verdict once its inner handler
429/// answered: [`Dispatch::attribution`].
430#[derive(Clone)]
431pub(crate) struct Attribution(Arc<Mutex<Option<String>>>);
432
433impl Attribution {
434    /// The layer named replaced the answer the consumer receives. The
435    /// outermost layer to say so is the one the consumer's answer is from.
436    pub(crate) fn replaced(&self, layer: &str) {
437        *lock(&self.0) = Some(layer.to_owned());
438    }
439}
440
441impl Dispatch {
442    /// Construct the context for one effect.
443    pub fn new(id: EffectId, streaming: bool) -> Self {
444        Self {
445            id,
446            streaming,
447            adapter_context: None,
448            adapter_context_explicit: false,
449            scopes: Vec::new(),
450            observer: None,
451            replaced_by: Arc::new(Mutex::new(None)),
452            folded: None,
453        }
454    }
455
456    /// The slot a layer's replacement verdict is named in: `None` until a
457    /// layer replaced the answer on its way out, then that layer's name.
458    /// The record keeps the handler's answer regardless; this says who the
459    /// consumer's answer is from. Shared with every inner dispatch, so a
460    /// driver reads it from the dispatch it built.
461    pub fn replaced_by(&self) -> Arc<Mutex<Option<String>>> {
462        self.replaced_by.clone()
463    }
464
465    /// The effect being served.
466    pub const fn id(&self) -> EffectId {
467        self.id
468    }
469
470    /// Whether the consumer requested streaming delivery.
471    pub const fn is_stream(&self) -> bool {
472        self.streaming
473    }
474
475    /// Attach a driver scope.
476    pub fn with_scope(mut self, scope: Arc<dyn std::any::Any + Send + Sync>) -> Self {
477        self.scopes.push(scope);
478        self
479    }
480
481    /// Find a scope by its concrete type.
482    pub fn scope<T: std::any::Any + Send + Sync>(&self) -> Option<Arc<T>> {
483        self.scopes
484            .iter()
485            .find_map(|scope| Arc::downcast::<T>(scope.clone()).ok())
486    }
487
488    /// Copy the scope handles for an inline or tool dispatch.
489    pub fn scopes(&self) -> Vec<Arc<dyn std::any::Any + Send + Sync>> {
490        self.scopes.clone()
491    }
492
493    /// Observe the original handler answer independently of layer verdicts.
494    pub fn with_observer(mut self, observer: Box<dyn Observe>) -> Self {
495        if !self.adapter_context_explicit {
496            self.adapter_context = observer.adapter_context();
497        }
498        self.observer = Some(Arc::new(Mutex::new(Some(Observed {
499            observer,
500            told: false,
501        }))));
502        self
503    }
504
505    /// Supply provider context for this invocation independently of request data.
506    ///
507    /// Explicit context takes precedence over context supplied by an observer,
508    /// regardless of installation order, and survives forwarding through layers.
509    /// Reuse an operation context only for attempts of that same logical call.
510    pub fn with_adapter_context(mut self, context: crate::observe::AdapterContext) -> Self {
511        self.adapter_context = Some(context);
512        self.adapter_context_explicit = true;
513        self
514    }
515
516    /// Provider observation context forwarded across handler layers.
517    pub fn adapter_context(&self) -> Option<crate::observe::AdapterContext> {
518        self.adapter_context.clone()
519    }
520
521    pub(crate) fn patched(&mut self, kind: &EffectKind) {
522        if let Some(slot) = &self.observer
523            && let Some(seen) = lock(slot).as_mut()
524        {
525            seen.observer.patch(kind);
526        }
527    }
528
529    pub(crate) fn discard(&mut self, layer: &str) {
530        if let Some(slot) = &self.observer
531            && let Some(mut seen) = lock(slot).take()
532        {
533            seen.told = true;
534            seen.observer.discard(layer);
535        }
536    }
537
538    /// The handle a layer keeps to attribute its verdict once its inner
539    /// handler answered (the observer itself moves inward with
540    /// [`Self::inner`]).
541    pub(crate) fn attribution(&self) -> Attribution {
542        Attribution(self.replaced_by.clone())
543    }
544
545    pub(crate) fn inner(&mut self, folded: Option<Folded>) -> Self {
546        let observer = self.observer.as_ref().and_then(|slot| lock(slot).take());
547        Self {
548            id: self.id,
549            streaming: self.streaming,
550            adapter_context: self.adapter_context.clone(),
551            adapter_context_explicit: self.adapter_context_explicit,
552            scopes: self.scopes.clone(),
553            observer: observer.map(|seen| Arc::new(Mutex::new(Some(seen)))),
554            replaced_by: self.replaced_by.clone(),
555            folded,
556        }
557    }
558}
559
560struct Observed {
561    observer: Box<dyn Observe>,
562    told: bool,
563}
564
565impl Observed {
566    fn outcome(&mut self, outcome: &Result<Outcome, ErrorReport>) {
567        if !self.told {
568            self.told = true;
569            self.observer.outcome(outcome);
570        }
571    }
572
573    fn item(
574        &mut self,
575        item: &Result<Relayed, ErrorReport>,
576        outcome: Option<&Result<Outcome, ErrorReport>>,
577    ) {
578        let outcome = outcome.filter(|_| !self.told);
579        self.told |= outcome.is_some();
580        self.observer.stream_item(item, outcome);
581    }
582}
583
584impl Drop for Observed {
585    fn drop(&mut self) {
586        self.outcome(&Err(cancelled()));
587    }
588}
589
590fn wrong_stream_answer(other: &Outcome) -> ErrorReport {
591    ErrorReport::new(
592        ErrorKind::Internal,
593        format!(
594            "a streaming dispatch was answered with a {} outcome",
595            other.family()
596        ),
597    )
598}
599
600/// The consumer stopped listening before an answer was observed.
601pub fn cancelled() -> ErrorReport {
602    ErrorReport::new(
603        ErrorKind::Cancelled,
604        "the consumer cancelled the dispatch before it was answered",
605    )
606    .with_retryable(false)
607}
608
609/// The receiver of an external answer or writer has been dropped.
610#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
611#[error("the dispatch's consumer is gone")]
612pub struct SinkClosed;
613
614/// A single-use answer owned by an external responder.
615pub struct Resolver(oneshot::Sender<Result<Outcome, ErrorReport>>);
616
617/// Return a resolver and the answer future a handler must await.
618/// Dropping the resolver without answering reports the established
619/// unanswered-handler error; dropping the future closes the resolver.
620pub fn deferred() -> (
621    Resolver,
622    impl Future<Output = Result<Outcome, ErrorReport>> + Send + 'static,
623) {
624    let (sender, receiver) = oneshot::channel();
625    (Resolver(sender), async move {
626        receiver.await.unwrap_or_else(|_| {
627            Err(ErrorReport::new(
628                ErrorKind::Internal,
629                "the handler dropped its outcome sink without answering",
630            ))
631        })
632    })
633}
634
635impl Resolver {
636    /// Answer once. A late answer is discarded and reports closure.
637    pub fn resolve(self, outcome: Result<Outcome, ErrorReport>) -> Result<(), SinkClosed> {
638        self.0.send(outcome).map_err(|_| SinkClosed)
639    }
640
641    /// Whether the handler stopped waiting for this answer.
642    pub fn is_closed(&self) -> bool {
643        self.0.is_canceled()
644    }
645}
646
647/// Serve an effect inline, using the same reply conversions as a driver.
648pub async fn serve_inline(
649    handler: &ErasedHandler,
650    kind: EffectKind,
651) -> Result<Outcome, ErrorReport> {
652    serve_inline_with(handler, kind, Vec::new()).await
653}
654
655/// Serve inline with driver scopes, including tool context and publication.
656pub async fn serve_inline_with(
657    handler: &ErasedHandler,
658    kind: EffectKind,
659    scopes: Vec<Arc<dyn std::any::Any + Send + Sync>>,
660) -> Result<Outcome, ErrorReport> {
661    let mut dispatch = Dispatch::new(EffectId::from_raw(0), false);
662    for scope in scopes {
663        dispatch = dispatch.with_scope(scope);
664    }
665    handler.handle(kind, dispatch).await.into_outcome().await
666}