1use crate::errors::GraphError;
8use crate::state::{StateSchema, StateUpdate};
9use async_trait::async_trait;
10use std::future::Future;
11use std::marker::PhantomData;
12use std::pin::Pin;
13
14#[async_trait]
19pub trait GraphNode<S: StateSchema>: Send + Sync + 'static {
20 async fn execute(
29 &self,
30 state: &S,
31 config: Option<NodeConfig>,
32 ) -> Result<StateUpdate<S>, GraphError>;
33
34 fn name(&self) -> &str;
36}
37
38#[derive(Debug, Clone, Default)]
40pub struct NodeConfig {
41 pub recursion_limit: usize,
43
44 pub metadata: std::collections::HashMap<String, serde_json::Value>,
46
47 pub debug: bool,
49}
50
51pub type NodeResult<S> = Result<StateUpdate<S>, GraphError>;
53
54pub type AsyncNodeFn<S> =
56 Box<dyn Fn(&S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>> + Send + Sync>;
57
58pub trait AsyncFn<S: StateSchema>: Send + Sync {
60 fn call(&self, state: &S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>>;
61}
62
63impl<S: StateSchema, F, Fut> AsyncFn<S> for F
64where
65 F: Fn(&S) -> Fut + Send + Sync + 'static,
66 Fut: Future<Output = NodeResult<S>> + Send + 'static,
67{
68 fn call(&self, state: &S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>> {
69 Box::pin((self)(state))
70 }
71}
72
73pub struct AsyncNode<S: StateSchema, F: AsyncFn<S>> {
75 name: String,
76 func: F,
77 _marker: PhantomData<S>,
78}
79
80impl<S: StateSchema, F: AsyncFn<S>> AsyncNode<S, F> {
81 pub fn new(name: impl Into<String>, func: F) -> Self {
82 Self {
83 name: name.into(),
84 func,
85 _marker: PhantomData,
86 }
87 }
88}
89
90#[async_trait]
91impl<S: StateSchema, F: AsyncFn<S> + 'static> GraphNode<S> for AsyncNode<S, F> {
92 async fn execute(&self, state: &S, _config: Option<NodeConfig>) -> NodeResult<S> {
93 self.func.call(state).await
94 }
95
96 fn name(&self) -> &str {
97 &self.name
98 }
99}
100
101pub struct FunctionNode<S: StateSchema, F> {
105 name: String,
106 func: F,
107 _marker: PhantomData<S>,
108}
109
110impl<S: StateSchema, F> FunctionNode<S, F>
111where
112 F: Fn(&S) -> Pin<Box<dyn Future<Output = Result<StateUpdate<S>, GraphError>> + Send>>
113 + Send
114 + Sync,
115{
116 pub fn new(name: impl Into<String>, func: F) -> Self {
118 Self {
119 name: name.into(),
120 func,
121 _marker: PhantomData,
122 }
123 }
124}
125
126#[async_trait]
127impl<S: StateSchema, F: 'static> GraphNode<S> for FunctionNode<S, F>
128where
129 F: Fn(&S) -> Pin<Box<dyn Future<Output = Result<StateUpdate<S>, GraphError>> + Send>>
130 + Send
131 + Sync,
132{
133 async fn execute(
134 &self,
135 state: &S,
136 _config: Option<NodeConfig>,
137 ) -> Result<StateUpdate<S>, GraphError> {
138 (self.func)(state).await
139 }
140
141 fn name(&self) -> &str {
142 &self.name
143 }
144}
145
146pub struct SyncNode<S: StateSchema, F> {
150 name: String,
151 func: F,
152 _marker: PhantomData<S>,
153}
154
155impl<S: StateSchema, F> SyncNode<S, F>
156where
157 F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync,
158{
159 pub fn new(name: impl Into<String>, func: F) -> Self {
161 Self {
162 name: name.into(),
163 func,
164 _marker: PhantomData,
165 }
166 }
167}
168
169#[async_trait]
170impl<S: StateSchema, F: 'static> GraphNode<S> for SyncNode<S, F>
171where
172 F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync,
173{
174 async fn execute(
175 &self,
176 state: &S,
177 _config: Option<NodeConfig>,
178 ) -> Result<StateUpdate<S>, GraphError> {
179 (self.func)(state)
180 }
181
182 fn name(&self) -> &str {
183 &self.name
184 }
185}
186
187pub struct SentinelNode {
189 name: String,
190}
191
192impl SentinelNode {
193 pub fn start() -> Self {
194 Self {
195 name: crate::START.to_string(),
196 }
197 }
198
199 pub fn end() -> Self {
200 Self {
201 name: crate::END.to_string(),
202 }
203 }
204
205 pub fn custom(name: impl Into<String>) -> Self {
206 Self { name: name.into() }
207 }
208}
209
210#[async_trait]
211impl<S: StateSchema> GraphNode<S> for SentinelNode {
212 async fn execute(
213 &self,
214 _state: &S,
215 _config: Option<NodeConfig>,
216 ) -> Result<StateUpdate<S>, GraphError> {
217 Ok(StateUpdate::unchanged())
219 }
220
221 fn name(&self) -> &str {
222 &self.name
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use crate::state::AgentState;
230
231 #[tokio::test]
232 async fn test_sync_node() {
233 let node = SyncNode::new("test", |state: &AgentState| {
234 Ok(StateUpdate::full(AgentState::new(state.input.clone())))
235 });
236
237 let state = AgentState::new("Hello".to_string());
238 let result = node.execute(&state, None).await;
239 assert!(result.is_ok());
240 }
241
242 #[test]
243 fn test_sentinel_nodes() {
244 let start: SentinelNode = SentinelNode::start();
245 assert_eq!(GraphNode::<AgentState>::name(&start), crate::START);
246
247 let end: SentinelNode = SentinelNode::end();
248 assert_eq!(GraphNode::<AgentState>::name(&end), crate::END);
249 }
250}