1use crate::error::Result;
6use crate::interrupt::Interrupt;
7use crate::state::State;
8use crate::stream::StreamEvent;
9use crate::timeout::ProgressHandle;
10use async_trait::async_trait;
11use serde_json::Value;
12use std::collections::HashMap;
13use std::future::Future;
14use std::pin::Pin;
15use std::sync::Arc;
16
17#[derive(Clone)]
19pub struct ExecutionConfig {
20 pub thread_id: String,
22 pub resume_from: Option<String>,
24 pub recursion_limit: usize,
26 pub metadata: HashMap<String, Value>,
28 pub parent_context: Option<Arc<dyn adk_core::InvocationContext>>,
37}
38
39impl ExecutionConfig {
40 pub fn new(thread_id: &str) -> Self {
42 Self {
43 thread_id: thread_id.to_string(),
44 resume_from: None,
45 recursion_limit: 50,
46 metadata: HashMap::new(),
47 parent_context: None,
48 }
49 }
50
51 #[must_use]
57 pub fn with_parent_context(mut self, parent: Arc<dyn adk_core::InvocationContext>) -> Self {
58 self.parent_context = Some(parent);
59 self
60 }
61
62 pub fn with_recursion_limit(mut self, limit: usize) -> Self {
64 self.recursion_limit = limit;
65 self
66 }
67
68 pub fn with_resume_from(mut self, checkpoint_id: &str) -> Self {
70 self.resume_from = Some(checkpoint_id.to_string());
71 self
72 }
73
74 pub fn with_metadata(mut self, key: &str, value: Value) -> Self {
76 self.metadata.insert(key.to_string(), value);
77 self
78 }
79}
80
81impl Default for ExecutionConfig {
82 fn default() -> Self {
83 Self::new(&uuid::Uuid::new_v4().to_string())
84 }
85}
86
87pub struct NodeContext {
89 pub state: State,
91 pub config: ExecutionConfig,
93 pub step: usize,
95 progress_handle: Option<ProgressHandle>,
98 children: Option<std::sync::Arc<crate::child::ChildInvoker>>,
100 parent_schema: Option<std::sync::Arc<crate::state::StateSchema>>,
102}
103
104impl NodeContext {
105 pub fn new(state: State, config: ExecutionConfig, step: usize) -> Self {
107 Self { state, config, step, progress_handle: None, children: None, parent_schema: None }
108 }
109
110 pub fn parent_schema(&self) -> Option<std::sync::Arc<crate::state::StateSchema>> {
116 self.parent_schema.clone()
117 }
118
119 pub fn set_parent_schema(&mut self, schema: std::sync::Arc<crate::state::StateSchema>) {
121 self.parent_schema = Some(schema);
122 }
123
124 pub(crate) fn child_invoker(&self) -> Option<std::sync::Arc<crate::child::ChildInvoker>> {
125 self.children.clone()
126 }
127
128 pub(crate) fn set_child_invoker(
130 &mut self,
131 invoker: std::sync::Arc<crate::child::ChildInvoker>,
132 ) {
133 self.children = Some(invoker);
134 }
135
136 pub async fn run_node(&self, child: &str, input: Value) -> Result<Value> {
153 self.run_node_with(child, input, crate::child::RunNodeOptions::default()).await
154 }
155
156 pub async fn run_node_with(
163 &self,
164 child: &str,
165 input: Value,
166 options: crate::child::RunNodeOptions,
167 ) -> Result<Value> {
168 let invoker = self.children.as_ref().ok_or_else(|| {
169 crate::error::GraphError::InvalidGraph(
170 "this node cannot invoke other nodes: no child invoker was attached".to_string(),
171 )
172 })?;
173 invoker.run(child, input, options, self).await
174 }
175
176 pub fn get(&self, key: &str) -> Option<&Value> {
178 self.state.get(key)
179 }
180
181 pub fn get_as<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
183 self.state.get(key).and_then(|v| serde_json::from_value(v.clone()).ok())
184 }
185
186 pub fn report_progress(&self) {
204 if let Some(handle) = &self.progress_handle {
205 handle.report_progress();
206 }
207 }
208
209 pub fn set_progress_handle(&mut self, handle: ProgressHandle) {
214 self.progress_handle = Some(handle);
215 }
216
217 pub fn progress_handle(&self) -> Option<&ProgressHandle> {
219 self.progress_handle.as_ref()
220 }
221}
222
223#[derive(Default)]
225pub struct NodeOutput {
226 pub updates: HashMap<String, Value>,
228 pub interrupt: Option<Interrupt>,
230 pub events: Vec<StreamEvent>,
232 pub goto: Option<Vec<String>>,
234 pub goto_parent: Option<Vec<String>>,
239}
240
241impl NodeOutput {
242 pub fn new() -> Self {
244 Self::default()
245 }
246
247 pub fn with_goto_parent<I, S>(mut self, targets: I) -> Self
293 where
294 I: IntoIterator<Item = S>,
295 S: Into<String>,
296 {
297 self.goto_parent = Some(targets.into_iter().map(Into::into).collect());
298 self
299 }
300
301 pub fn with_goto<I, S>(mut self, targets: I) -> Self
302 where
303 I: IntoIterator<Item = S>,
304 S: Into<String>,
305 {
306 self.goto = Some(targets.into_iter().map(Into::into).collect());
307 self
308 }
309
310 pub fn with_update(mut self, key: &str, value: impl Into<Value>) -> Self {
312 self.updates.insert(key.to_string(), value.into());
313 self
314 }
315
316 pub fn with_updates(mut self, updates: HashMap<String, Value>) -> Self {
318 self.updates.extend(updates);
319 self
320 }
321
322 pub fn with_interrupt(mut self, interrupt: Interrupt) -> Self {
324 self.interrupt = Some(interrupt);
325 self
326 }
327
328 pub fn with_event(mut self, event: StreamEvent) -> Self {
330 self.events.push(event);
331 self
332 }
333
334 pub fn interrupt(message: &str) -> Self {
336 Self::new().with_interrupt(crate::interrupt::interrupt(message))
337 }
338
339 pub fn interrupt_with_data(message: &str, data: Value) -> Self {
341 Self::new().with_interrupt(crate::interrupt::interrupt_with_data(message, data))
342 }
343}
344
345#[async_trait]
347pub trait Node: Send + Sync {
348 fn name(&self) -> &str;
350
351 async fn execute(&self, ctx: &NodeContext) -> Result<NodeOutput>;
353
354 fn validate_against(&self, _parent: &crate::state::StateSchema) -> Result<()> {
372 Ok(())
373 }
374
375 fn validate(&self) -> Result<()> {
376 Ok(())
377 }
378
379 fn execute_stream<'a>(
386 &'a self,
387 ctx: &'a NodeContext,
388 ) -> Pin<Box<dyn futures::Stream<Item = Result<StreamEvent>> + Send + 'a>> {
389 let name = self.name().to_string();
390 Box::pin(async_stream::stream! {
391 match self.execute(ctx).await {
392 Ok(output) => {
393 for event in output.events {
394 yield Ok(event);
395 }
396 if let Some(targets) = output.goto {
400 yield Ok(StreamEvent::route_dispatched(&name, targets));
401 }
402 if let Some(interrupt) = output.interrupt {
403 let (message, data) = match interrupt {
404 crate::interrupt::Interrupt::Dynamic { message, data } => (message, data),
405 other => (other.to_string(), None),
406 };
407 yield Ok(StreamEvent::node_interrupt(&name, &message, data));
408 }
409 yield Ok(StreamEvent::Updates { node: name, updates: output.updates });
410 }
411 Err(e) => yield Err(e),
412 }
413 })
414 }
415}
416
417pub type BoxedNode = Box<dyn Node>;
419
420pub type AsyncNodeFn = Box<
422 dyn Fn(NodeContext) -> Pin<Box<dyn Future<Output = Result<NodeOutput>> + Send>> + Send + Sync,
423>;
424
425pub struct FunctionNode {
427 name: String,
428 func: AsyncNodeFn,
429}
430
431impl FunctionNode {
432 pub fn new<F, Fut>(name: &str, func: F) -> Self
434 where
435 F: Fn(NodeContext) -> Fut + Send + Sync + 'static,
436 Fut: Future<Output = Result<NodeOutput>> + Send + 'static,
437 {
438 Self { name: name.to_string(), func: Box::new(move |ctx| Box::pin(func(ctx))) }
439 }
440}
441
442#[async_trait]
443impl Node for FunctionNode {
444 fn name(&self) -> &str {
445 &self.name
446 }
447
448 async fn execute(&self, ctx: &NodeContext) -> Result<NodeOutput> {
449 let mut ctx_owned = NodeContext::new(ctx.state.clone(), ctx.config.clone(), ctx.step);
452 if let Some(handle) = ctx.progress_handle() {
453 ctx_owned.set_progress_handle(handle.clone());
454 }
455 if let Some(invoker) = ctx.child_invoker() {
456 ctx_owned.set_child_invoker(invoker);
457 }
458 if let Some(schema) = ctx.parent_schema() {
459 ctx_owned.set_parent_schema(schema);
460 }
461 (self.func)(ctx_owned).await
462 }
463}
464
465pub struct PassthroughNode {
467 name: String,
468}
469
470impl PassthroughNode {
471 pub fn new(name: &str) -> Self {
473 Self { name: name.to_string() }
474 }
475}
476
477#[async_trait]
478impl Node for PassthroughNode {
479 fn name(&self) -> &str {
480 &self.name
481 }
482
483 async fn execute(&self, _ctx: &NodeContext) -> Result<NodeOutput> {
484 Ok(NodeOutput::new())
485 }
486}
487
488pub type AgentInputMapper = Box<dyn Fn(&State) -> adk_core::Content + Send + Sync>;
490
491pub type AgentOutputMapper =
493 Box<dyn Fn(&[adk_core::Event]) -> HashMap<String, Value> + Send + Sync>;
494
495pub type AgentGotoMapper =
499 Box<dyn Fn(&HashMap<String, Value>) -> Option<Vec<String>> + Send + Sync>;
500
501pub struct AgentNode {
503 name: String,
504 #[allow(dead_code)]
505 agent: Arc<dyn adk_core::Agent>,
506 input_mapper: AgentInputMapper,
508 output_mapper: AgentOutputMapper,
510 goto_mapper: Option<AgentGotoMapper>,
512}
513
514impl AgentNode {
515 pub fn new(agent: Arc<dyn adk_core::Agent>) -> Self {
517 let name = agent.name().to_string();
518 Self {
519 name,
520 agent,
521 input_mapper: Box::new(default_input_mapper),
522 output_mapper: Box::new(default_output_mapper),
523 goto_mapper: None,
524 }
525 }
526
527 pub fn with_input_mapper<F>(mut self, mapper: F) -> Self
529 where
530 F: Fn(&State) -> adk_core::Content + Send + Sync + 'static,
531 {
532 self.input_mapper = Box::new(mapper);
533 self
534 }
535
536 pub fn with_output_mapper<F>(mut self, mapper: F) -> Self
538 where
539 F: Fn(&[adk_core::Event]) -> HashMap<String, Value> + Send + Sync + 'static,
540 {
541 self.output_mapper = Box::new(mapper);
542 self
543 }
544
545 pub fn with_goto_mapper<F>(mut self, mapper: F) -> Self
570 where
571 F: Fn(&HashMap<String, Value>) -> Option<Vec<String>> + Send + Sync + 'static,
572 {
573 self.goto_mapper = Some(Box::new(mapper));
574 self
575 }
576}
577
578fn default_input_mapper(state: &State) -> adk_core::Content {
580 if let Some(messages) = state.get("messages")
582 && let Some(arr) = messages.as_array()
583 && let Some(last) = arr.last()
584 && let Some(content) = last.get("content").and_then(|c| c.as_str())
585 {
586 return adk_core::Content::new("user").with_text(content);
587 }
588
589 if let Some(input) = state.get("input")
591 && let Some(text) = input.as_str()
592 {
593 return adk_core::Content::new("user").with_text(text);
594 }
595
596 adk_core::Content::new("user")
597}
598
599fn default_output_mapper(events: &[adk_core::Event]) -> HashMap<String, Value> {
601 let mut updates = HashMap::new();
602
603 let mut messages = Vec::new();
605 for event in events {
606 if let Some(content) = event.content() {
607 let text = content.parts.iter().filter_map(|p| p.text()).collect::<Vec<_>>().join("");
608
609 if !text.is_empty() {
610 messages.push(serde_json::json!({
611 "role": "assistant",
612 "content": text
613 }));
614 }
615 }
616 }
617
618 if !messages.is_empty() {
619 updates.insert("messages".to_string(), serde_json::json!(messages));
620 }
621
622 updates
623}
624
625#[async_trait]
626impl Node for AgentNode {
627 fn name(&self) -> &str {
628 &self.name
629 }
630
631 async fn execute(&self, ctx: &NodeContext) -> Result<NodeOutput> {
632 use futures::StreamExt;
633
634 let content = (self.input_mapper)(&ctx.state);
636
637 let invocation_ctx = Arc::new(GraphInvocationContext::with_parent(
639 ctx.config.thread_id.clone(),
640 content,
641 self.agent.clone(),
642 ctx.config.parent_context.clone(),
643 ));
644
645 let stream = self.agent.run(invocation_ctx).await.map_err(|e| {
647 crate::error::GraphError::NodeExecutionFailed {
648 node: self.name.clone(),
649 message: e.to_string(),
650 }
651 })?;
652
653 let events: Vec<adk_core::Event> = stream.filter_map(|r| async { r.ok() }).collect().await;
654
655 let updates = (self.output_mapper)(&events);
657 let goto = self.goto_mapper.as_ref().and_then(|mapper| mapper(&updates));
658
659 let mut output = NodeOutput::new().with_updates(updates);
661 if let Some(targets) = goto {
662 output = output.with_goto(targets);
663 }
664 for event in &events {
665 if let Ok(json) = serde_json::to_value(event) {
666 output = output.with_event(StreamEvent::custom(&self.name, "agent_event", json));
667 }
668 }
669
670 Ok(output)
671 }
672
673 fn execute_stream<'a>(
674 &'a self,
675 ctx: &'a NodeContext,
676 ) -> Pin<Box<dyn futures::Stream<Item = Result<StreamEvent>> + Send + 'a>> {
677 use futures::StreamExt;
678 let name = self.name.clone();
679 let agent = self.agent.clone();
680 let input_mapper = &self.input_mapper;
681 let output_mapper = &self.output_mapper;
682 let goto_mapper = &self.goto_mapper;
683 let parent_context = ctx.config.parent_context.clone();
684 let thread_id = ctx.config.thread_id.clone();
685 let content = (input_mapper)(&ctx.state);
686
687 Box::pin(async_stream::stream! {
688 tracing::debug!("AgentNode::execute_stream called for {}", name);
689 let invocation_ctx = Arc::new(GraphInvocationContext::with_parent(
690 thread_id,
691 content,
692 agent.clone(),
693 parent_context,
694 ));
695
696 let stream = match agent.run(invocation_ctx).await {
697 Ok(s) => s,
698 Err(e) => {
699 yield Err(crate::error::GraphError::NodeExecutionFailed {
700 node: name.clone(),
701 message: e.to_string(),
702 });
703 return;
704 }
705 };
706
707 tokio::pin!(stream);
708 let mut all_events = Vec::new();
709
710 while let Some(result) = stream.next().await {
711 match result {
712 Ok(event) => {
713 if let Some(content) = event.content() {
715 let text: String = content.parts.iter().filter_map(|p| p.text()).collect();
716 if !text.is_empty() {
717 yield Ok(StreamEvent::Message {
718 node: name.clone(),
719 content: text,
720 is_final: false,
721 });
722 }
723 }
724 all_events.push(event);
725 }
726 Err(e) => {
727 yield Err(crate::error::GraphError::NodeExecutionFailed {
728 node: name.clone(),
729 message: e.to_string(),
730 });
731 return;
732 }
733 }
734 }
735
736 for event in &all_events {
738 if let Ok(json) = serde_json::to_value(event) {
739 yield Ok(StreamEvent::custom(&name, "agent_event", json));
740 }
741 }
742
743 let updates = (output_mapper)(&all_events);
747 if let Some(targets) = goto_mapper.as_ref().and_then(|mapper| mapper(&updates)) {
749 yield Ok(StreamEvent::route_dispatched(&name, targets));
750 }
751 yield Ok(StreamEvent::Updates { node: name.clone(), updates });
752 })
753 }
754}
755
756struct GraphInvocationContext {
758 invocation_id: String,
759 user_content: adk_core::Content,
760 agent: Arc<dyn adk_core::Agent>,
761 session: Arc<GraphSession>,
762 run_config: adk_core::RunConfig,
763 ended: std::sync::atomic::AtomicBool,
764 parent: Option<Arc<dyn adk_core::InvocationContext>>,
770 user_id: String,
772 app_name: String,
773 branch: String,
774}
775
776const STANDALONE_USER_ID: &str = "graph_user";
778const STANDALONE_APP_NAME: &str = "graph_app";
780
781impl GraphInvocationContext {
782 fn with_parent(
783 session_id: String,
784 user_content: adk_core::Content,
785 agent: Arc<dyn adk_core::Agent>,
786 parent: Option<Arc<dyn adk_core::InvocationContext>>,
787 ) -> Self {
788 let invocation_id = uuid::Uuid::new_v4().to_string();
789 let session = Arc::new(GraphSession::new(session_id));
790 session.append_content(user_content.clone());
792
793 let (user_id, app_name, branch, run_config) = match parent.as_ref() {
796 Some(parent) => (
797 parent.user_id().to_string(),
798 parent.app_name().to_string(),
799 match parent.branch() {
800 "" => agent.name().to_string(),
801 existing => format!("{existing}.{}", agent.name()),
802 },
803 parent.run_config().clone(),
804 ),
805 None => (
806 STANDALONE_USER_ID.to_string(),
807 STANDALONE_APP_NAME.to_string(),
808 "main".to_string(),
809 adk_core::RunConfig::default(),
810 ),
811 };
812
813 Self {
814 invocation_id,
815 user_content,
816 agent,
817 session,
818 run_config,
819 ended: std::sync::atomic::AtomicBool::new(false),
820 parent,
821 user_id,
822 app_name,
823 branch,
824 }
825 }
826}
827
828impl adk_core::ReadonlyContext for GraphInvocationContext {
830 fn invocation_id(&self) -> &str {
831 &self.invocation_id
832 }
833
834 fn agent_name(&self) -> &str {
835 self.agent.name()
836 }
837
838 fn user_id(&self) -> &str {
839 &self.user_id
840 }
841
842 fn app_name(&self) -> &str {
843 &self.app_name
844 }
845
846 fn session_id(&self) -> &str {
847 &self.session.id
848 }
849
850 fn branch(&self) -> &str {
851 &self.branch
852 }
853
854 fn user_content(&self) -> &adk_core::Content {
855 &self.user_content
856 }
857}
858
859#[async_trait]
861impl adk_core::CallbackContext for GraphInvocationContext {
862 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
863 self.parent.as_ref().and_then(|parent| parent.artifacts())
864 }
865
866 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
867 self.parent.as_ref().and_then(|parent| parent.shared_state())
868 }
869}
870
871#[async_trait]
873impl adk_core::InvocationContext for GraphInvocationContext {
874 fn agent(&self) -> Arc<dyn adk_core::Agent> {
875 self.agent.clone()
876 }
877
878 fn memory(&self) -> Option<Arc<dyn adk_core::Memory>> {
879 self.parent.as_ref().and_then(|parent| parent.memory())
880 }
881
882 fn session(&self) -> &dyn adk_core::Session {
883 self.session.as_ref()
884 }
885
886 fn run_config(&self) -> &adk_core::RunConfig {
887 &self.run_config
888 }
889
890 fn end_invocation(&self) {
891 self.ended.store(true, std::sync::atomic::Ordering::SeqCst);
892 if let Some(parent) = &self.parent {
893 parent.end_invocation();
894 }
895 }
896
897 fn ended(&self) -> bool {
898 self.ended.load(std::sync::atomic::Ordering::SeqCst)
899 || self.parent.as_ref().is_some_and(|parent| parent.ended())
900 }
901
902 fn is_cancelled(&self) -> bool {
903 self.parent.as_ref().is_some_and(|parent| parent.is_cancelled())
904 }
905
906 fn user_scopes(&self) -> Vec<String> {
907 self.parent.as_ref().map(|parent| parent.user_scopes()).unwrap_or_default()
908 }
909
910 fn request_metadata(&self) -> std::collections::HashMap<String, Value> {
911 self.parent.as_ref().map(|parent| parent.request_metadata()).unwrap_or_default()
912 }
913
914 async fn get_secret(&self, name: &str) -> adk_core::Result<Option<String>> {
915 match &self.parent {
916 Some(parent) => parent.get_secret(name).await,
917 None => Ok(None),
918 }
919 }
920
921 async fn get_secret_for(
922 &self,
923 request: &adk_core::SecretRequest,
924 ) -> adk_core::Result<Option<String>> {
925 match &self.parent {
926 Some(parent) => parent.get_secret_for(request).await,
927 None => Ok(None),
928 }
929 }
930}
931
932struct GraphSession {
934 id: String,
935 state: GraphState,
936 history: std::sync::RwLock<Vec<adk_core::Content>>,
937}
938
939impl GraphSession {
940 fn new(id: String) -> Self {
941 Self { id, state: GraphState::new(), history: std::sync::RwLock::new(Vec::new()) }
942 }
943
944 fn append_content(&self, content: adk_core::Content) {
945 if let Ok(mut h) = self.history.write() {
946 h.push(content);
947 }
948 }
949}
950
951impl adk_core::Session for GraphSession {
952 fn id(&self) -> &str {
953 &self.id
954 }
955
956 fn app_name(&self) -> &str {
957 "graph_app"
958 }
959
960 fn user_id(&self) -> &str {
961 "graph_user"
962 }
963
964 fn state(&self) -> &dyn adk_core::State {
965 &self.state
966 }
967
968 fn conversation_history(&self) -> Vec<adk_core::Content> {
969 self.history.read().ok().map(|h| h.clone()).unwrap_or_default()
970 }
971
972 fn append_to_history(&self, content: adk_core::Content) {
973 self.append_content(content);
974 }
975}
976
977struct GraphState {
979 data: std::sync::RwLock<std::collections::HashMap<String, serde_json::Value>>,
980}
981
982impl GraphState {
983 fn new() -> Self {
984 Self { data: std::sync::RwLock::new(std::collections::HashMap::new()) }
985 }
986}
987
988impl adk_core::State for GraphState {
989 fn get(&self, key: &str) -> Option<serde_json::Value> {
990 self.data.read().ok()?.get(key).cloned()
991 }
992
993 fn set(&mut self, key: String, value: serde_json::Value) {
994 if let Err(msg) = adk_core::validate_state_key(&key) {
995 tracing::warn!(key = %key, "rejecting invalid state key: {msg}");
996 return;
997 }
998 if let Ok(mut data) = self.data.write() {
999 data.insert(key, value);
1000 }
1001 }
1002
1003 fn all(&self) -> std::collections::HashMap<String, serde_json::Value> {
1004 self.data.read().ok().map(|d| d.clone()).unwrap_or_default()
1005 }
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010 use super::*;
1011
1012 #[tokio::test]
1013 async fn test_function_node() {
1014 let node = FunctionNode::new("test", |_ctx| async {
1015 Ok(NodeOutput::new().with_update("result", serde_json::json!("success")))
1016 });
1017
1018 assert_eq!(node.name(), "test");
1019
1020 let ctx = NodeContext::new(State::new(), ExecutionConfig::default(), 0);
1021 let output = node.execute(&ctx).await.unwrap();
1022
1023 assert_eq!(output.updates.get("result"), Some(&serde_json::json!("success")));
1024 }
1025
1026 #[tokio::test]
1027 async fn test_passthrough_node() {
1028 let node = PassthroughNode::new("pass");
1029 let ctx = NodeContext::new(State::new(), ExecutionConfig::default(), 0);
1030 let output = node.execute(&ctx).await.unwrap();
1031
1032 assert!(output.updates.is_empty());
1033 assert!(output.interrupt.is_none());
1034 }
1035
1036 #[test]
1037 fn test_node_output_builder() {
1038 let output = NodeOutput::new().with_update("a", 1).with_update("b", "hello");
1039
1040 assert_eq!(output.updates.get("a"), Some(&serde_json::json!(1)));
1041 assert_eq!(output.updates.get("b"), Some(&serde_json::json!("hello")));
1042 }
1043}