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::{
12        Arc, Mutex,
13        atomic::{AtomicUsize, Ordering},
14    },
15    task::Poll,
16};
17
18use futures::StreamExt;
19
20use crate::{
21    completion::UnsupportedOption,
22    effect::{Answered, EffectId, EffectKind, HandlerDescriptor, HandlerKey},
23    error::{ErrorKind, ErrorReport},
24    streaming::{Item, Relayed, StreamEvent, StreamEvents},
25    tool::Touches,
26    wasm_compat::{WasmBoxedFuture, WasmCompatSend, WasmCompatSync},
27};
28
29#[cfg(test)]
30mod tests;
31
32/// Boxed handler reply future, `Send` on native targets and locally polled on WASM.
33pub type HandlerFuture<'a> = WasmBoxedFuture<'a, Reply>;
34
35/// A registered effect handler returning an outcome or an owned stream.
36///
37/// Provider and tool authors do not implement this directly: the adapters
38/// in [`crate::serve::adapters`] wrap models, tools and indexes
39/// (`Model`, `Tool`, `VectorStoreIndex`). A
40/// host implements it for out-of-tree kinds ([`EffectKind::Custom`], typed
41/// through [`crate::effect::CustomEffect`]) or for a replayer.
42///
43/// A handler returns an outcome or an owned stream. The driver adapts that
44/// reply to the requested delivery mode. Stream execution continues after
45/// this method returns; dropping the reply cancels that work.
46///
47/// The returned future must be `Send` natively (it runs inside the driver's
48/// task; the bound is the crate's `WasmCompatSend` marker, a no-op on
49/// browser wasm). `Self::Family` is what a typed key can be proven against
50/// (a typed registration); a handler with no one
51/// family names [`crate::effect::family::Dynamic`].
52///
53/// A handler holding an `Rc` or a `Cell` is refused natively:
54///
55/// ```compile_fail
56/// use std::rc::Rc;
57/// use rig_core::{serve::{Dispatch, Reply, Serve}, effect::{EffectKind, HandlerDescriptor, family}};
58///
59/// struct Local(Rc<u8>);
60/// impl Serve for Local {
61///     type Family = family::Dynamic;
62///     fn descriptor(&self) -> HandlerDescriptor { unimplemented!() }
63///     async fn serve(&self, _kind: EffectKind, _dispatch: Dispatch) -> Reply { unimplemented!() }
64/// }
65/// ```
66///
67/// ```compile_fail
68/// use std::cell::Cell;
69/// use rig_core::{serve::{Dispatch, Reply, Serve}, effect::{EffectKind, HandlerDescriptor, family}};
70///
71/// struct Local(Cell<u8>);
72/// impl Serve for Local {
73///     type Family = family::Dynamic;
74///     fn descriptor(&self) -> HandlerDescriptor { unimplemented!() }
75///     async fn serve(&self, _kind: EffectKind, _dispatch: Dispatch) -> Reply { unimplemented!() }
76/// }
77/// ```
78pub trait Serve: WasmCompatSend + WasmCompatSync {
79    /// The family this handler serves, or `Dynamic`.
80    type Family: crate::effect::Served;
81
82    /// What this handler is: the family-keyed description a typed view
83    /// checks at bind time and a scene serializes.
84    fn descriptor(&self) -> HandlerDescriptor;
85
86    /// The [descriptor](Self::descriptor)'s key. A handler whose
87    /// descriptor is costly to build, such as a tool's argument schema,
88    /// says it without building one.
89    fn key(&self) -> HandlerKey {
90        self.descriptor().key
91    }
92
93    /// Prepare an outcome or an owned stream for the driver to consume.
94    fn serve(
95        &self,
96        kind: EffectKind,
97        dispatch: Dispatch,
98    ) -> impl Future<Output = Reply> + WasmCompatSend + use<'_, Self>;
99
100    /// What dispatching `kind` may read and change, for a tool's handler
101    /// ([`Tool::footprint`](crate::tool::Tool::footprint)); `None` when it
102    /// cannot say, such as for arguments that do not parse.
103    fn footprint(&self, kind: &EffectKind) -> Option<Touches> {
104        let _ = kind;
105        None
106    }
107
108    /// The options of `kind` this handler would leave out when it serves
109    /// it, with nothing sent: for a model's handler, what its wire or model
110    /// cannot honour ([`DynModel::check`](crate::driver::DynModel::check)).
111    /// Empty when it leaves nothing out or cannot say, as by default.
112    fn check(&self, kind: &EffectKind) -> Vec<UnsupportedOption> {
113        let _ = kind;
114        Vec::new()
115    }
116}
117
118/// Object-safe handler interface implemented for every [`Serve`].
119pub(crate) trait Handler: WasmCompatSend + WasmCompatSync {
120    fn descriptor(&self) -> HandlerDescriptor;
121    fn handle(&self, kind: EffectKind, dispatch: Dispatch) -> HandlerFuture<'_>;
122    fn footprint(&self, kind: &EffectKind) -> Option<Touches>;
123    fn check(&self, kind: &EffectKind) -> Vec<UnsupportedOption>;
124}
125
126// A type that is not a `Serve` should be told to implement `Serve`, never
127// the crate-private `Handler` this blanket impl provides.
128#[diagnostic::do_not_recommend]
129impl<T: Serve> Handler for T {
130    fn descriptor(&self) -> HandlerDescriptor {
131        Serve::descriptor(self)
132    }
133
134    fn handle(&self, kind: EffectKind, dispatch: Dispatch) -> HandlerFuture<'_> {
135        let observer = dispatch.observer.clone();
136        let folded = dispatch.folded.clone();
137        let streaming = dispatch.is_stream();
138        let level = Level::enter(&dispatch.serving);
139        Box::pin(async move {
140            let reply = self.serve(kind, dispatch).await;
141            // A handler that serves the dispatch inside another one, such
142            // as a wrapper's inner handler, leaves the observer to the
143            // outermost: what the wrapper publishes once its inner handler
144            // answered is part of the record.
145            if !level.outermost() {
146                return reply.observed(streaming, None, None);
147            }
148            let seen = observer.and_then(|slot| lock(&slot).take());
149            reply.observed(streaming, seen, folded)
150        })
151    }
152
153    fn footprint(&self, kind: &EffectKind) -> Option<Touches> {
154        Serve::footprint(self, kind)
155    }
156
157    fn check(&self, kind: &EffectKind) -> Vec<UnsupportedOption> {
158        Serve::check(self, kind)
159    }
160}
161
162/// A shared handler is a handler: `Arc<H>` forwards, so one handler can be
163/// registered under several keys.
164impl<H: Serve + ?Sized> Serve for Arc<H> {
165    type Family = H::Family;
166
167    fn descriptor(&self) -> HandlerDescriptor {
168        (**self).descriptor()
169    }
170
171    async fn serve(&self, kind: EffectKind, dispatch: Dispatch) -> Reply {
172        (**self).serve(kind, dispatch).await
173    }
174
175    fn footprint(&self, kind: &EffectKind) -> Option<Touches> {
176        (**self).footprint(kind)
177    }
178
179    fn check(&self, kind: &EffectKind) -> Vec<UnsupportedOption> {
180        (**self).check(kind)
181    }
182}
183
184/// One handler serving a dispatch, counted in the dispatch's handlers that
185/// serve it one inside another until it answers or is dropped.
186struct Level(Arc<AtomicUsize>);
187
188impl Level {
189    fn enter(serving: &Arc<AtomicUsize>) -> Self {
190        serving.fetch_add(1, Ordering::AcqRel);
191        Self(serving.clone())
192    }
193
194    /// Whether no handler serves the dispatch around this one: the inner
195    /// ones answered or were dropped by now.
196    fn outermost(&self) -> bool {
197        self.0.load(Ordering::Acquire) == 1
198    }
199}
200
201impl Drop for Level {
202    fn drop(&mut self) {
203        self.0.fetch_sub(1, Ordering::AcqRel);
204    }
205}
206
207/// Shared, type-erased handler. Clones retain the same handler allocation.
208/// Implements `Send + Sync` on native targets, but not on WASM.
209#[derive(Clone)]
210pub struct ErasedHandler {
211    inner: ErasedInner,
212    /// The handler's [key](Serve::key), read once.
213    key: HandlerKey,
214}
215
216#[cfg(not(target_family = "wasm"))]
217type ErasedInner = Arc<dyn Handler + Send + Sync>;
218#[cfg(target_family = "wasm")]
219type ErasedInner = Arc<dyn Handler>;
220
221impl ErasedHandler {
222    /// Erase `handler`.
223    pub fn new(handler: impl Serve + 'static) -> Self {
224        Self {
225            key: handler.key(),
226            inner: Arc::new(handler),
227        }
228    }
229
230    /// A handler that serves nothing: it is `descriptor`, and every effect
231    /// it gets is answered [`ErrorKind::HandlerUnavailable`] with `why`. For
232    /// a model whose calls a plugin answers itself, such as from a batch,
233    /// so a call that reaches the handler by mistake fails plainly while
234    /// records still name the model.
235    ///
236    /// ```
237    /// use rig_core::completion::ModelRef;
238    /// use rig_core::effect::{FamilyDescriptor, HandlerDescriptor, model_key};
239    /// use rig_core::serve::ErasedHandler;
240    ///
241    /// let descriptor = HandlerDescriptor {
242    ///     key: model_key("batch/claude"),
243    ///     family: FamilyDescriptor::Completion {
244    ///         model: ModelRef::from("batch/claude"),
245    ///         capabilities: Default::default(),
246    ///     },
247    ///     layers: Vec::new(),
248    /// };
249    /// let handler = ErasedHandler::unavailable(descriptor, "answered from the batch API");
250    /// assert_eq!(handler.key(), &model_key("batch/claude"));
251    /// ```
252    pub fn unavailable(descriptor: HandlerDescriptor, why: impl Into<String>) -> Self {
253        Self::new(Unavailable {
254            descriptor,
255            why: why.into(),
256        })
257    }
258
259    /// Wrap this handler in a [`Layer`](super::Layer): `intercept` sees
260    /// every dispatch before this handler does and every answer after.
261    /// `handler.layered(a).layered(b)` calls `b.before` first and `a.after` first.
262    pub fn layered(self, intercept: impl super::Intercept) -> Self {
263        Self::new(super::Layer::new(self, intercept))
264    }
265
266    /// Rewrite this handler's completion streams: every item of each reply
267    /// passes through a fresh `make(published)` in order, and the items it
268    /// returns go on in its place. It sees the [`Relayed::Done`](crate::streaming::Relayed::Done)
269    /// too, so a rewrite of the events also rewrites the response the reply
270    /// ends with. A unary completion is rewritten as its events, so both
271    /// delivery modes answer alike; other outcomes and errors pass as they
272    /// are. `name` joins the descriptor's layers.
273    ///
274    /// `published` is the dispatch's
275    /// [`PublishedContext`](crate::tool::PublishedContext): a fact the
276    /// rewrite inserts there before it passes the `Done` on, such as that
277    /// it split reasoning out of this reply, comes back to the driver beside
278    /// the reply and is recorded with it.
279    ///
280    /// Unlike a [`Layer`](super::Layer), the rewrite is recorded: the
281    /// record of a dispatch is the reply the consumer receives, so a replay
282    /// of it does not rewrite twice.
283    pub fn map_stream<M, F>(self, name: impl Into<String>, make: M) -> Self
284    where
285        M: Fn(Arc<crate::tool::PublishedContext>) -> F + WasmCompatSend + WasmCompatSync + 'static,
286        F: FnMut(Relayed) -> Vec<Relayed> + WasmCompatSend + 'static,
287    {
288        Self::new(super::map::MapStream::new(self, name.into(), make))
289    }
290
291    /// What the erased handler is.
292    pub fn descriptor(&self) -> HandlerDescriptor {
293        self.inner.descriptor()
294    }
295
296    /// The key of its [descriptor](Self::descriptor), without building one.
297    pub fn key(&self) -> &HandlerKey {
298        &self.key
299    }
300
301    /// Serve one effect: the driver's call, straight to the boxed handler.
302    pub fn handle(&self, kind: EffectKind, dispatch: Dispatch) -> HandlerFuture<'_> {
303        self.inner.handle(kind, dispatch)
304    }
305
306    /// What dispatching `kind` to this handler may read and change
307    /// ([`Serve::footprint`]).
308    pub fn footprint(&self, kind: &EffectKind) -> Option<Touches> {
309        self.inner.footprint(kind)
310    }
311
312    /// The options of `kind` this handler would leave out ([`Serve::check`]).
313    pub fn check(&self, kind: &EffectKind) -> Vec<UnsupportedOption> {
314        self.inner.check(kind)
315    }
316
317    /// Whether two erased handlers are the same allocation.
318    pub fn ptr_eq(&self, other: &Self) -> bool {
319        Arc::ptr_eq(&self.inner, &other.inner)
320    }
321}
322
323impl std::fmt::Debug for ErasedHandler {
324    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325        f.debug_struct("ErasedHandler")
326            .field("key", &self.key)
327            .finish_non_exhaustive()
328    }
329}
330
331/// What [`ErasedHandler::unavailable`] erases: it answers every effect
332/// `HandlerUnavailable` with `why`.
333struct Unavailable {
334    descriptor: HandlerDescriptor,
335    why: String,
336}
337
338impl Serve for Unavailable {
339    type Family = crate::effect::family::Dynamic;
340
341    fn descriptor(&self) -> HandlerDescriptor {
342        self.descriptor.clone()
343    }
344
345    async fn serve(&self, _kind: EffectKind, _dispatch: Dispatch) -> Reply {
346        Reply::Outcome(Err(ErrorReport::new(
347            ErrorKind::HandlerUnavailable,
348            self.why.clone(),
349        )))
350    }
351}
352
353/// Forwards serving to the wrapped handler; re-erasure adds another boxed future.
354impl Serve for ErasedHandler {
355    type Family = crate::effect::family::Dynamic;
356
357    fn descriptor(&self) -> HandlerDescriptor {
358        self.inner.descriptor()
359    }
360
361    async fn serve(&self, kind: EffectKind, dispatch: Dispatch) -> Reply {
362        self.handle(kind, dispatch).await
363    }
364
365    fn footprint(&self, kind: &EffectKind) -> Option<Touches> {
366        self.inner.footprint(kind)
367    }
368
369    fn check(&self, kind: &EffectKind) -> Vec<UnsupportedOption> {
370        self.inner.check(kind)
371    }
372}
373
374/// What an observer of one dispatch sees: its outcome and, when wanted, each streamed item.
375pub trait Observe: Send + Sync {
376    /// Provider witness associated with this dispatch, independently of recording.
377    fn adapter_context(&self) -> Option<crate::observe::AdapterContext> {
378        None
379    }
380
381    /// The completed handler response, or the fold of a streaming handler's
382    /// events. A resolved response retains content that has no stream block,
383    /// such as images emitted as unknown events.
384    fn outcome(&mut self, outcome: Result<Answered, ErrorReport>);
385    /// Whether streamed items are wanted verbatim ([`Self::event`]).
386    fn keep_events(&self) -> bool;
387    /// One item pulled from the original handler stream. This is recording
388    /// evidence, not acknowledgement that a consumer received the item.
389    fn event(&mut self, item: &Item<StreamEvent>);
390    /// An error item in a kept stream.
391    fn stream_error(&mut self, _error: &ErrorReport) {}
392    /// Who a streamed reply is from, before its first item. Required, so a
393    /// recording observer cannot drop it: a replayed stream cut short needs
394    /// it to know its origin.
395    fn origin(&mut self, origin: &crate::message::Origin);
396    /// Observe one stream item together with its first folded outcome, if any.
397    /// Drivers that snapshot recording concurrently with cancellation can override
398    /// this operation to make the item and its answer one observation boundary.
399    fn stream_item(
400        &mut self,
401        item: &Result<Relayed, ErrorReport>,
402        outcome: Option<Result<Answered, ErrorReport>>,
403    ) {
404        if self.keep_events() {
405            match item {
406                Ok(Relayed::Origin(origin)) => self.origin(origin),
407                Ok(Relayed::Item(item)) => self.event(item),
408                Ok(Relayed::Done(_)) => {}
409                Err(error) => self.stream_error(error),
410            }
411        }
412        if let Some(outcome) = outcome {
413            self.outcome(outcome);
414        }
415    }
416    /// The dispatch was decided before any handler served it, by the layer
417    /// named: the record it opened is forgotten.
418    fn discard(&mut self, layer: &str);
419    /// The layer named serves `kind` in place of the effect that began
420    /// (same family): the record's request is what the innermost handler
421    /// served.
422    fn patch(&mut self, kind: &EffectKind);
423}
424
425/// What a unary consumer, the record, or a layer's verdict takes from a
426/// stream: its outcome, the response the stream ends with or the first
427/// error item. The origin folded the response; the tap only watches for it.
428#[derive(Default)]
429pub struct StreamTap {
430    /// The tap yielded its outcome.
431    finished: bool,
432}
433
434impl StreamTap {
435    /// A new stream tap.
436    pub fn new() -> Self {
437        Self::default()
438    }
439
440    /// Returns the stream's outcome once: the response it ends with, or the
441    /// first error item. Every later call returns `None`.
442    pub fn observe(
443        &mut self,
444        item: &Result<Relayed, ErrorReport>,
445    ) -> Option<Result<Answered, ErrorReport>> {
446        if self.finished {
447            return None;
448        }
449        let outcome = match item {
450            Ok(Relayed::Origin(_) | Relayed::Item(_)) => None,
451            Ok(Relayed::Done(response)) => Some(Ok(Answered::Completion(Arc::clone(response)))),
452            Err(report) => Some(Err(report.clone())),
453        };
454        self.finished = outcome.is_some();
455        outcome
456    }
457}
458
459/// The retryable report of a reply that ended before the provider ended it
460/// ([`ProviderError::Truncated`](crate::error::ProviderError::Truncated)).
461/// Retryability does not guarantee a subsequent attempt will succeed.
462pub fn stream_truncated() -> ErrorReport {
463    ErrorReport::from(&crate::error::ProviderError::Truncated)
464}
465
466/// An answer, or an owned stream whose execution belongs to the driver.
467pub enum Reply {
468    /// The completed unary answer or a setup error.
469    Outcome(Result<Answered, ErrorReport>),
470    /// Events, including any frames after the first terminal record.
471    Stream(StreamEvents),
472}
473
474impl Reply {
475    /// Fold a stream to its first outcome, or return the unary answer.
476    pub async fn into_outcome(self) -> Result<Answered, ErrorReport> {
477        self.folded_outcome(None).await
478    }
479
480    pub(crate) async fn folded_outcome(
481        self,
482        folded: Option<Folded>,
483    ) -> Result<Answered, ErrorReport> {
484        match self {
485            Self::Outcome(outcome) => outcome,
486            Self::Stream(mut stream) => {
487                let mut fold = StreamTap::new();
488                while let Some(item) = stream.next().await {
489                    let outcome = match &folded {
490                        Some(folded) => lock(folded).take(),
491                        None => fold.observe(&item),
492                    };
493                    if let Some(outcome) = outcome {
494                        return outcome;
495                    }
496                }
497                Err(stream_truncated())
498            }
499        }
500    }
501
502    /// Convert a completion into its events, then the response;
503    /// incompatible outcomes become errors.
504    pub fn into_stream(self) -> StreamEvents {
505        match self {
506            Self::Stream(stream) => stream,
507            Self::Outcome(outcome) => Box::pin(futures::stream::iter(match outcome {
508                Ok(Answered::Completion(response)) => {
509                    match crate::operation::completion::events_of(&response) {
510                        Ok(items) => std::iter::once(Ok(Relayed::Origin(response.origin.clone())))
511                            .chain(items.into_iter().map(|item| Ok(Relayed::Item(item))))
512                            .chain(std::iter::once(Ok(Relayed::Done(response))))
513                            .collect(),
514                        Err(error) => vec![Err(ErrorReport::from(&error))],
515                    }
516                }
517                Ok(other) => vec![Err(wrong_stream_answer(&other))],
518                Err(report) => vec![Err(report)],
519            })),
520        }
521    }
522
523    fn observed(self, streaming: bool, mut seen: Option<Observed>, folded: Option<Folded>) -> Self {
524        if !streaming && let Self::Outcome(outcome) = self {
525            if let Some(seen) = &mut seen {
526                seen.outcome(outcome.clone());
527            }
528            return Self::Outcome(outcome);
529        }
530        if seen.is_none() && folded.is_none() {
531            return if streaming {
532                Self::Stream(self.into_stream())
533            } else {
534                self
535            };
536        }
537        let mut original = match &self {
538            Self::Outcome(Ok(Answered::Completion(response))) => {
539                Some(Ok(Answered::Completion(response.clone())))
540            }
541            _ => None,
542        };
543        let mut stream = self.into_stream();
544        let mut fold = StreamTap::new();
545        let mut finished = false;
546        Self::Stream(Box::pin(futures::stream::poll_fn(move |cx| {
547            let item = match stream.as_mut().poll_next(cx) {
548                Poll::Pending => return Poll::Pending,
549                Poll::Ready(item) => item,
550            };
551            if let Some(item) = &item {
552                let mut outcome = if finished { None } else { fold.observe(item) };
553                finished |= outcome.is_some();
554                if let Some(seen) = &mut seen {
555                    // A unary answer made a stream is recorded as answered;
556                    // the fold's slot, when there is one, keeps the outcome.
557                    let recorded = match &outcome {
558                        None => None,
559                        Some(_) if matches!(item, Ok(Relayed::Done(_))) && original.is_some() => {
560                            original.take()
561                        }
562                        Some(_) if folded.is_none() => outcome.take(),
563                        Some(outcome) => Some(outcome.clone()),
564                    };
565                    if streaming {
566                        seen.item(item, recorded);
567                    } else if let Some(recorded) = recorded {
568                        seen.outcome(recorded);
569                    }
570                }
571                if let (Some(outcome), Some(folded)) = (outcome, &folded) {
572                    *lock(folded) = Some(outcome);
573                }
574            } else if !finished {
575                finished = true;
576                let outcome = Err(stream_truncated());
577                if let Some(seen) = &mut seen {
578                    seen.outcome(outcome.clone());
579                }
580                if let Some(folded) = &folded {
581                    *lock(folded) = Some(outcome);
582                }
583            }
584            Poll::Ready(item)
585        })))
586    }
587}
588
589// A layer and the recorder at its immediate inner boundary use the same fold.
590// Each outer boundary gets its own slot: a verdict can change that view.
591pub(crate) type Folded = Arc<Mutex<Option<Result<Answered, ErrorReport>>>>;
592
593fn lock<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
594    value
595        .lock()
596        .unwrap_or_else(std::sync::PoisonError::into_inner)
597}
598
599/// An effect's identity, requested delivery mode, and driver-provided scopes.
600/// Reply transport and cancellation remain owned by the driver.
601pub struct Dispatch {
602    adapter_context: Option<crate::observe::AdapterContext>,
603    adapter_context_explicit: bool,
604    id: EffectId,
605    streaming: bool,
606    scopes: Vec<Arc<dyn std::any::Any + Send + Sync>>,
607    observer: Option<Arc<Mutex<Option<Observed>>>>,
608    /// How many handlers serve it now, one inside another: only the
609    /// outermost tells the observer and the fold, once it answered.
610    serving: Arc<AtomicUsize>,
611    /// The layer whose verdict replaced the answer the consumer receives,
612    /// once one did. Shared down the layer chain; a driver reads it after
613    /// the reply ([`Dispatch::replaced_by`]).
614    replaced_by: Arc<Mutex<Option<String>>>,
615    folded: Option<Folded>,
616}
617
618/// A layer's handle for attributing its verdict once its inner handler
619/// answered: [`Dispatch::attribution`].
620#[derive(Clone)]
621pub(crate) struct Attribution(Arc<Mutex<Option<String>>>);
622
623impl Attribution {
624    /// The layer named replaced the answer the consumer receives. The
625    /// outermost layer to say so is the one the consumer's answer is from.
626    pub(crate) fn replaced(&self, layer: &str) {
627        *lock(&self.0) = Some(layer.to_owned());
628    }
629}
630
631impl Dispatch {
632    /// Construct the context for one effect.
633    pub fn new(id: EffectId, streaming: bool) -> Self {
634        Self {
635            id,
636            streaming,
637            adapter_context: None,
638            adapter_context_explicit: false,
639            scopes: Vec::new(),
640            observer: None,
641            serving: Arc::default(),
642            replaced_by: Arc::new(Mutex::new(None)),
643            folded: None,
644        }
645    }
646
647    /// The slot a layer's replacement verdict is named in: `None` until a
648    /// layer replaced the answer on its way out, then that layer's name.
649    /// The record keeps the handler's answer regardless; this says who the
650    /// consumer's answer is from. Shared with every inner dispatch, so a
651    /// driver reads it from the dispatch it built.
652    pub fn replaced_by(&self) -> Arc<Mutex<Option<String>>> {
653        self.replaced_by.clone()
654    }
655
656    /// The effect being served.
657    pub const fn id(&self) -> EffectId {
658        self.id
659    }
660
661    /// Whether the consumer requested streaming delivery.
662    pub const fn is_stream(&self) -> bool {
663        self.streaming
664    }
665
666    /// Attach a driver scope.
667    pub fn with_scope(mut self, scope: Arc<dyn std::any::Any + Send + Sync>) -> Self {
668        self.scopes.push(scope);
669        self
670    }
671
672    /// Find a scope by its concrete type.
673    pub fn scope<T: std::any::Any + Send + Sync>(&self) -> Option<Arc<T>> {
674        self.scopes
675            .iter()
676            .find_map(|scope| Arc::downcast::<T>(scope.clone()).ok())
677    }
678
679    /// Copy the scope handles for an inline or tool dispatch.
680    pub fn scopes(&self) -> Vec<Arc<dyn std::any::Any + Send + Sync>> {
681        self.scopes.clone()
682    }
683
684    /// Observe the original handler answer independently of layer verdicts.
685    /// A handler that serves the dispatch through an inner one, passing it
686    /// on, is observed once it answered, with what it published by then;
687    /// a [`Layer`](super::Layer) hands the observer to the handler it wraps.
688    pub fn with_observer(mut self, observer: Box<dyn Observe>) -> Self {
689        if !self.adapter_context_explicit {
690            self.adapter_context = observer.adapter_context();
691        }
692        self.observer = Some(Arc::new(Mutex::new(Some(Observed {
693            observer,
694            told: false,
695        }))));
696        self
697    }
698
699    /// This dispatch observed by `recorder` under its id, as a recording
700    /// driver installs it after [`begin`](super::Recorder::begin): its
701    /// outcome, stream, origin and layer decisions, and the tool output its
702    /// handler publishes into a [`PublishedContext`](crate::tool::PublishedContext)
703    /// scope. Add the scopes first.
704    pub fn recorded_by(self, recorder: Arc<dyn super::Recorder + Send + Sync>) -> Self {
705        let observer = super::recorder::RecordingObserver {
706            recorder,
707            id: self.id,
708            published: self.scope::<crate::tool::PublishedContext>(),
709        };
710        self.with_observer(Box::new(observer))
711    }
712
713    /// Supply provider context for this invocation independently of request data.
714    ///
715    /// Explicit context takes precedence over context supplied by an observer,
716    /// regardless of installation order, and survives forwarding through layers.
717    /// Reuse an operation context only for attempts of that same logical call.
718    pub fn with_adapter_context(mut self, context: crate::observe::AdapterContext) -> Self {
719        self.adapter_context = Some(context);
720        self.adapter_context_explicit = true;
721        self
722    }
723
724    /// Provider observation context forwarded across handler layers.
725    pub fn adapter_context(&self) -> Option<crate::observe::AdapterContext> {
726        self.adapter_context.clone()
727    }
728
729    pub(crate) fn patched(&mut self, kind: &EffectKind) {
730        if let Some(slot) = &self.observer
731            && let Some(seen) = lock(slot).as_mut()
732        {
733            seen.observer.patch(kind);
734        }
735    }
736
737    pub(crate) fn discard(&mut self, layer: &str) {
738        if let Some(slot) = &self.observer
739            && let Some(mut seen) = lock(slot).take()
740        {
741            seen.told = true;
742            seen.observer.discard(layer);
743        }
744    }
745
746    /// The handle a layer keeps to attribute its verdict once its inner
747    /// handler answered (the observer itself moves inward with
748    /// [`Self::inner`]).
749    pub(crate) fn attribution(&self) -> Attribution {
750        Attribution(self.replaced_by.clone())
751    }
752
753    pub(crate) fn inner(&mut self, folded: Option<Folded>) -> Self {
754        let observer = self.observer.as_ref().and_then(|slot| lock(slot).take());
755        Self {
756            id: self.id,
757            streaming: self.streaming,
758            adapter_context: self.adapter_context.clone(),
759            adapter_context_explicit: self.adapter_context_explicit,
760            scopes: self.scopes.clone(),
761            observer: observer.map(|seen| Arc::new(Mutex::new(Some(seen)))),
762            serving: Arc::default(),
763            replaced_by: self.replaced_by.clone(),
764            folded,
765        }
766    }
767}
768
769struct Observed {
770    observer: Box<dyn Observe>,
771    told: bool,
772}
773
774impl Observed {
775    fn outcome(&mut self, outcome: Result<Answered, ErrorReport>) {
776        if !self.told {
777            self.told = true;
778            self.observer.outcome(outcome);
779        }
780    }
781
782    fn item(
783        &mut self,
784        item: &Result<Relayed, ErrorReport>,
785        outcome: Option<Result<Answered, ErrorReport>>,
786    ) {
787        let outcome = outcome.filter(|_| !self.told);
788        self.told |= outcome.is_some();
789        self.observer.stream_item(item, outcome);
790    }
791}
792
793impl Drop for Observed {
794    fn drop(&mut self) {
795        self.outcome(Err(cancelled()));
796    }
797}
798
799fn wrong_stream_answer(other: &Answered) -> ErrorReport {
800    ErrorReport::new(
801        ErrorKind::Internal,
802        format!(
803            "a streaming dispatch was answered with a {} outcome",
804            other.family()
805        ),
806    )
807}
808
809/// The consumer stopped listening before an answer was observed.
810pub fn cancelled() -> ErrorReport {
811    ErrorReport::new(
812        ErrorKind::Cancelled,
813        "the consumer cancelled the dispatch before it was answered",
814    )
815    .with_retryable(false)
816}
817
818/// The receiver of an external answer or writer has been dropped.
819#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
820#[error("the dispatch's consumer is gone")]
821pub struct SinkClosed;
822
823/// Serve an effect inline, using the same reply conversions as a driver.
824pub async fn serve_inline(
825    handler: &ErasedHandler,
826    kind: EffectKind,
827) -> Result<Answered, ErrorReport> {
828    serve_inline_with(handler, kind, Vec::new()).await
829}
830
831/// Serve inline with driver scopes, including tool context and publication.
832pub async fn serve_inline_with(
833    handler: &ErasedHandler,
834    kind: EffectKind,
835    scopes: Vec<Arc<dyn std::any::Any + Send + Sync>>,
836) -> Result<Answered, ErrorReport> {
837    let mut dispatch = Dispatch::new(EffectId::from_raw(0), false);
838    for scope in scopes {
839        dispatch = dispatch.with_scope(scope);
840    }
841    handler.handle(kind, dispatch).await.into_outcome().await
842}