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}