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}