Skip to main content

cruxx_script/
registry.rs

1/// Handler registry — maps string names to type-erased async step handlers.
2use std::collections::HashMap;
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use cruxx_core::prelude::{Agent, CruxCtx, CruxErr};
8use serde_json::Value;
9
10use crate::handler_output::HandlerOutput;
11use crate::metadata::HandlerMetadata;
12
13/// Type-erased async handler: Value in, HandlerOutput out.
14///
15/// Handlers that don't carry a confidence score return `HandlerOutput::new(value)`
16/// (equivalent to confidence `1.0`). Use [`HandlerRegistry::handler`] for handlers
17/// that emit confidence, or [`HandlerRegistry::handler_value`] for simple handlers
18/// that return a plain `Value` (auto-wrapped for backward compatibility).
19pub type BoxHandler = Arc<
20    dyn Fn(Value) -> Pin<Box<dyn Future<Output = Result<HandlerOutput, CruxErr>> + Send>>
21        + Send
22        + Sync,
23>;
24
25/// Type-erased agent runner: runs a registered agent with Value input, returns Value output.
26/// The function receives the input and returns a future that resolves to the output.
27/// Delegation is handled by the runner creating a child CruxCtx internally.
28pub type BoxAgentRunner = Arc<
29    dyn Fn(Value) -> Pin<Box<dyn Future<Output = Result<Value, CruxErr>> + Send>> + Send + Sync,
30>;
31
32/// Registry of named handlers and agents for pipeline execution.
33pub struct HandlerRegistry {
34    handlers: HashMap<String, BoxHandler>,
35    agents: HashMap<String, BoxAgentRunner>,
36    metadata: HashMap<String, HandlerMetadata>,
37}
38
39impl HandlerRegistry {
40    pub fn new() -> Self {
41        Self {
42            handlers: HashMap::new(),
43            agents: HashMap::new(),
44            metadata: HashMap::new(),
45        }
46    }
47
48    /// Register handler metadata for validation and introspection.
49    pub fn register_metadata(&mut self, meta: HandlerMetadata) {
50        self.metadata.insert(meta.name.clone(), meta);
51    }
52
53    /// Look up metadata for a registered handler by name.
54    pub fn get_metadata(&self, name: &str) -> Option<&HandlerMetadata> {
55        self.metadata.get(name)
56    }
57
58    /// Register a handler that returns [`HandlerOutput`] directly.
59    ///
60    /// Use this when the handler needs to control confidence. The returned
61    /// [`HandlerOutput`] may carry `confidence: Some(f32)` or `confidence: None`.
62    /// When confidence is `None`, `{{ steps.<name>.confidence }}` in templates
63    /// will return an `ExprError::NoConfidence` error — it does NOT silently
64    /// resolve to `1.0`. Only an explicit `Some(score)` is template-accessible.
65    ///
66    /// Prefer [`handler_value`](Self::handler_value) when confidence is irrelevant.
67    pub fn handler<F, Fut>(&mut self, name: impl Into<String>, f: F)
68    where
69        F: Fn(Value) -> Fut + Send + Sync + 'static,
70        Fut: Future<Output = Result<HandlerOutput, CruxErr>> + Send + 'static,
71    {
72        let name = name.into();
73        self.handlers
74            .insert(name, Arc::new(move |v| Box::pin(f(v))));
75    }
76
77    /// Register a handler that returns a plain [`Value`], without a confidence score.
78    ///
79    /// Convenience wrapper over [`handler`](Self::handler). The `Value` is auto-wrapped
80    /// via `HandlerOutput::from(value)`, which sets `confidence = None`. Accessing
81    /// `{{ steps.<name>.confidence }}` in templates will fail with
82    /// `ExprError::NoConfidence` — handler_value steps cannot be used as input to
83    /// `route_on_confidence`.
84    ///
85    /// Use this for handlers where confidence is not meaningful. Use
86    /// [`handler`](Self::handler) when the handler must emit a real confidence score.
87    pub fn handler_value<F, Fut>(&mut self, name: impl Into<String>, f: F)
88    where
89        F: Fn(Value) -> Fut + Send + Sync + 'static,
90        Fut: Future<Output = Result<Value, CruxErr>> + Send + 'static,
91    {
92        let name = name.into();
93        self.handlers.insert(
94            name,
95            Arc::new(move |v| {
96                let fut = f(v);
97                Box::pin(async move { fut.await.map(HandlerOutput::from) })
98                    as Pin<Box<dyn Future<Output = Result<HandlerOutput, CruxErr>> + Send>>
99            }),
100        );
101    }
102
103    /// Register a plain-value handler together with its [`HandlerMetadata`].
104    ///
105    /// Combines [`handler_value`](Self::handler_value) and
106    /// [`register_metadata`](Self::register_metadata) in one call.  The metadata
107    /// name is used as the handler name — the two must match.
108    pub fn handler_value_with_metadata<F, Fut>(&mut self, meta: HandlerMetadata, f: F)
109    where
110        F: Fn(Value) -> Fut + Send + Sync + 'static,
111        Fut: Future<Output = Result<Value, CruxErr>> + Send + 'static,
112    {
113        let name = meta.name.clone();
114        self.metadata.insert(name.clone(), meta);
115        self.handlers.insert(
116            name,
117            Arc::new(move |v| {
118                let fut = f(v);
119                Box::pin(async move { fut.await.map(HandlerOutput::from) })
120                    as Pin<Box<dyn Future<Output = Result<HandlerOutput, CruxErr>> + Send>>
121            }),
122        );
123    }
124
125    /// Register a cruxx Agent by name for delegation.
126    ///
127    /// The agent's `run` method will be called with a fresh CruxCtx.
128    pub fn agent<A>(&mut self, name: impl Into<String>)
129    where
130        A: Agent<Input = Value, Output = Value>,
131    {
132        let name_str = name.into();
133        let agent_name = name_str.clone();
134        self.agents.insert(
135            name_str,
136            Arc::new(move |input: Value| {
137                let n = agent_name.clone();
138                Box::pin(async move {
139                    let mut ctx = CruxCtx::new(&n);
140                    A::run(&mut ctx, input).await
141                }) as Pin<Box<dyn Future<Output = Result<Value, CruxErr>> + Send>>
142            }),
143        );
144    }
145
146    /// Register a named agent using a plain async closure.
147    ///
148    /// Unlike [`agent`](Self::agent), this does not require a concrete [`Agent`] impl —
149    /// any `async Fn(Value) -> Result<Value, CruxErr>` is accepted.  This is the
150    /// preferred way to register pipeline-level delegate targets programmatically.
151    pub fn agent_fn<F, Fut>(&mut self, name: impl Into<String>, f: F)
152    where
153        F: Fn(Value) -> Fut + Send + Sync + 'static,
154        Fut: Future<Output = Result<Value, CruxErr>> + Send + 'static,
155    {
156        let name = name.into();
157        self.agents.insert(name, Arc::new(move |v| Box::pin(f(v))));
158    }
159
160    /// Look up a handler by name.
161    pub fn get_handler(&self, name: &str) -> Option<&BoxHandler> {
162        self.handlers.get(name)
163    }
164
165    /// Look up an agent runner by name.
166    pub fn get_agent(&self, name: &str) -> Option<&BoxAgentRunner> {
167        self.agents.get(name)
168    }
169}
170
171impl Default for HandlerRegistry {
172    fn default() -> Self {
173        Self::new()
174    }
175}