Skip to main content

ferrin_core/
hooks.rs

1//! Lifecycle hooks: asynchronous callbacks awaited by the core.
2
3use std::fmt;
4use std::future::Future;
5use std::sync::Arc;
6
7use ferrin_spec::BoxFuture;
8
9use crate::generate_text::StepResult;
10use crate::stream_text::StreamEvent;
11use crate::telemetry::AbortEvent;
12use crate::telemetry::EndEvent;
13use crate::telemetry::ModelCallEndEvent;
14use crate::telemetry::ModelCallStartEvent;
15use crate::telemetry::StartEvent;
16use crate::telemetry::StepStartEvent;
17use crate::telemetry::ToolExecutionEndEvent;
18use crate::telemetry::ToolExecutionStartEvent;
19
20/// An asynchronous callback for event type `E`.
21///
22/// Implemented for every `Fn(Arc<E>) -> impl Future<Output = ()>` closure, so
23/// builders accept `|event| async move { ... }` directly. The core awaits the
24/// returned future before continuing.
25pub trait HookFn<E>: Send + Sync + 'static {
26    /// Handles one event.
27    fn call(&self, event: Arc<E>) -> BoxFuture<'static, ()>;
28}
29
30impl<E, F, Fut> HookFn<E> for F
31where
32    F: Fn(Arc<E>) -> Fut + Send + Sync + 'static,
33    Fut: Future<Output = ()> + Send + 'static,
34{
35    fn call(&self, event: Arc<E>) -> BoxFuture<'static, ()> {
36        Box::pin(self(event))
37    }
38}
39
40/// A list of hooks for one event type.
41pub type HookList<E> = Vec<Arc<dyn HookFn<E>>>;
42
43/// All lifecycle hooks of a call.
44#[derive(Clone, Default)]
45pub struct Hooks {
46    /// The call started.
47    pub on_start: HookList<StartEvent>,
48    /// A step started.
49    pub on_step_start: HookList<StepStartEvent>,
50    /// A model call is about to be made.
51    pub on_language_model_call_start: HookList<ModelCallStartEvent>,
52    /// A model call finished (tool calls parsed, not yet executed).
53    pub on_language_model_call_end: HookList<ModelCallEndEvent>,
54    /// A tool execution started.
55    pub on_tool_execution_start: HookList<ToolExecutionStartEvent>,
56    /// A tool execution finished.
57    pub on_tool_execution_end: HookList<ToolExecutionEndEvent>,
58    /// A step finished.
59    pub on_step_end: HookList<StepResult>,
60    /// The call finished.
61    pub on_end: HookList<EndEvent>,
62    /// Streaming: an event was emitted.
63    pub on_chunk: HookList<StreamEvent>,
64    /// Streaming: the call was aborted.
65    pub on_abort: HookList<AbortEvent>,
66}
67
68impl Hooks {
69    /// Appends the hooks of `other` after those of `self` (settings-level
70    /// hooks run before call-level hooks).
71    #[must_use]
72    pub fn merged(mut self, other: Hooks) -> Hooks {
73        self.on_start.extend(other.on_start);
74        self.on_step_start.extend(other.on_step_start);
75        self.on_language_model_call_start
76            .extend(other.on_language_model_call_start);
77        self.on_language_model_call_end
78            .extend(other.on_language_model_call_end);
79        self.on_tool_execution_start
80            .extend(other.on_tool_execution_start);
81        self.on_tool_execution_end
82            .extend(other.on_tool_execution_end);
83        self.on_step_end.extend(other.on_step_end);
84        self.on_end.extend(other.on_end);
85        self.on_chunk.extend(other.on_chunk);
86        self.on_abort.extend(other.on_abort);
87        self
88    }
89
90    /// Runs every hook of `list` in order with `event`.
91    pub async fn emit<E: 'static>(list: &[Arc<dyn HookFn<E>>], event: Arc<E>) {
92        for hook in list {
93            hook.call(Arc::clone(&event)).await;
94        }
95    }
96}
97
98impl fmt::Debug for Hooks {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.debug_struct("Hooks")
101            .field("on_start", &self.on_start.len())
102            .field("on_step_start", &self.on_step_start.len())
103            .field(
104                "on_language_model_call_start",
105                &self.on_language_model_call_start.len(),
106            )
107            .field(
108                "on_language_model_call_end",
109                &self.on_language_model_call_end.len(),
110            )
111            .field(
112                "on_tool_execution_start",
113                &self.on_tool_execution_start.len(),
114            )
115            .field("on_tool_execution_end", &self.on_tool_execution_end.len())
116            .field("on_step_end", &self.on_step_end.len())
117            .field("on_end", &self.on_end.len())
118            .field("on_chunk", &self.on_chunk.len())
119            .field("on_abort", &self.on_abort.len())
120            .finish()
121    }
122}