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 run_config: Option<adk_core::RunConfig>,
104}
105
106impl NodeContext {
107 pub fn new(state: State, config: ExecutionConfig, step: usize) -> Self {
109 Self {
110 state,
111 config,
112 step,
113 progress_handle: None,
114 children: None,
115 parent_schema: None,
116 run_config: None,
117 }
118 }
119
120 pub fn parent_schema(&self) -> Option<std::sync::Arc<crate::state::StateSchema>> {
126 self.parent_schema.clone()
127 }
128
129 pub fn set_parent_schema(&mut self, schema: std::sync::Arc<crate::state::StateSchema>) {
131 self.parent_schema = Some(schema);
132 }
133
134 pub(crate) fn set_run_config(&mut self, run_config: adk_core::RunConfig) {
136 self.run_config = Some(run_config);
137 }
138
139 pub(crate) fn run_config(&self) -> Option<adk_core::RunConfig> {
140 self.run_config.clone()
141 }
142
143 pub(crate) fn child_invoker(&self) -> Option<std::sync::Arc<crate::child::ChildInvoker>> {
144 self.children.clone()
145 }
146
147 pub(crate) fn set_child_invoker(
149 &mut self,
150 invoker: std::sync::Arc<crate::child::ChildInvoker>,
151 ) {
152 self.children = Some(invoker);
153 }
154
155 pub async fn run_node(&self, child: &str, input: Value) -> Result<Value> {
172 self.run_node_with(child, input, crate::child::RunNodeOptions::default()).await
173 }
174
175 pub async fn run_node_with(
182 &self,
183 child: &str,
184 input: Value,
185 options: crate::child::RunNodeOptions,
186 ) -> Result<Value> {
187 let invoker = self.children.as_ref().ok_or_else(|| {
188 crate::error::GraphError::InvalidGraph(
189 "this node cannot invoke other nodes: no child invoker was attached".to_string(),
190 )
191 })?;
192 invoker.run(child, input, options, self).await
193 }
194
195 pub fn get(&self, key: &str) -> Option<&Value> {
197 self.state.get(key)
198 }
199
200 pub fn get_as<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
202 self.state.get(key).and_then(|v| serde_json::from_value(v.clone()).ok())
203 }
204
205 pub fn report_progress(&self) {
223 if let Some(handle) = &self.progress_handle {
224 handle.report_progress();
225 }
226 }
227
228 pub fn set_progress_handle(&mut self, handle: ProgressHandle) {
233 self.progress_handle = Some(handle);
234 }
235
236 pub fn progress_handle(&self) -> Option<&ProgressHandle> {
238 self.progress_handle.as_ref()
239 }
240}
241
242#[derive(Default)]
244pub struct NodeOutput {
245 pub updates: HashMap<String, Value>,
247 pub interrupt: Option<Interrupt>,
249 pub events: Vec<StreamEvent>,
251 pub goto: Option<Vec<String>>,
253 pub goto_parent: Option<Vec<String>>,
258}
259
260impl NodeOutput {
261 pub fn new() -> Self {
263 Self::default()
264 }
265
266 pub fn with_goto_parent<I, S>(mut self, targets: I) -> Self
312 where
313 I: IntoIterator<Item = S>,
314 S: Into<String>,
315 {
316 self.goto_parent = Some(targets.into_iter().map(Into::into).collect());
317 self
318 }
319
320 pub fn with_goto<I, S>(mut self, targets: I) -> Self
321 where
322 I: IntoIterator<Item = S>,
323 S: Into<String>,
324 {
325 self.goto = Some(targets.into_iter().map(Into::into).collect());
326 self
327 }
328
329 pub fn with_update(mut self, key: &str, value: impl Into<Value>) -> Self {
331 self.updates.insert(key.to_string(), value.into());
332 self
333 }
334
335 pub fn with_updates(mut self, updates: HashMap<String, Value>) -> Self {
337 self.updates.extend(updates);
338 self
339 }
340
341 pub fn with_interrupt(mut self, interrupt: Interrupt) -> Self {
343 self.interrupt = Some(interrupt);
344 self
345 }
346
347 pub fn with_event(mut self, event: StreamEvent) -> Self {
349 self.events.push(event);
350 self
351 }
352
353 pub fn interrupt(message: &str) -> Self {
355 Self::new().with_interrupt(crate::interrupt::interrupt(message))
356 }
357
358 pub fn interrupt_with_data(message: &str, data: Value) -> Self {
360 Self::new().with_interrupt(crate::interrupt::interrupt_with_data(message, data))
361 }
362}
363
364#[async_trait]
366pub trait Node: Send + Sync {
367 fn name(&self) -> &str;
369
370 fn description(&self) -> &str {
372 "Graph workflow node"
373 }
374
375 fn capabilities(&self) -> adk_core::AgentCapabilities {
377 adk_core::AgentCapabilities::default()
378 }
379
380 async fn execute(&self, ctx: &NodeContext) -> Result<NodeOutput>;
382
383 fn validate_against(&self, _parent: &crate::state::StateSchema) -> Result<()> {
401 Ok(())
402 }
403
404 fn validate(&self) -> Result<()> {
405 Ok(())
406 }
407
408 fn execute_stream<'a>(
415 &'a self,
416 ctx: &'a NodeContext,
417 ) -> Pin<Box<dyn futures::Stream<Item = Result<StreamEvent>> + Send + 'a>> {
418 let name = self.name().to_string();
419 Box::pin(async_stream::stream! {
420 match self.execute(ctx).await {
421 Ok(output) => {
422 for event in output.events {
423 yield Ok(event);
424 }
425 if let Some(targets) = output.goto {
429 yield Ok(StreamEvent::route_dispatched(&name, targets));
430 }
431 if let Some(interrupt) = output.interrupt {
432 let (message, data) = match interrupt {
433 crate::interrupt::Interrupt::Dynamic { message, data } => (message, data),
434 other => (other.to_string(), None),
435 };
436 yield Ok(StreamEvent::node_interrupt(&name, &message, data));
437 }
438 yield Ok(StreamEvent::Updates { node: name, updates: output.updates });
439 }
440 Err(e) => yield Err(e),
441 }
442 })
443 }
444}
445
446pub type BoxedNode = Box<dyn Node>;
448
449pub type AsyncNodeFn = Box<
451 dyn Fn(NodeContext) -> Pin<Box<dyn Future<Output = Result<NodeOutput>> + Send>> + Send + Sync,
452>;
453
454pub struct FunctionNode {
456 name: String,
457 func: AsyncNodeFn,
458}
459
460impl FunctionNode {
461 pub fn new<F, Fut>(name: &str, func: F) -> Self
463 where
464 F: Fn(NodeContext) -> Fut + Send + Sync + 'static,
465 Fut: Future<Output = Result<NodeOutput>> + Send + 'static,
466 {
467 Self { name: name.to_string(), func: Box::new(move |ctx| Box::pin(func(ctx))) }
468 }
469}
470
471#[async_trait]
472impl Node for FunctionNode {
473 fn name(&self) -> &str {
474 &self.name
475 }
476
477 async fn execute(&self, ctx: &NodeContext) -> Result<NodeOutput> {
478 let mut ctx_owned = NodeContext::new(ctx.state.clone(), ctx.config.clone(), ctx.step);
481 if let Some(handle) = ctx.progress_handle() {
482 ctx_owned.set_progress_handle(handle.clone());
483 }
484 if let Some(invoker) = ctx.child_invoker() {
485 ctx_owned.set_child_invoker(invoker);
486 }
487 if let Some(schema) = ctx.parent_schema() {
488 ctx_owned.set_parent_schema(schema);
489 }
490 if let Some(run_config) = ctx.run_config() {
491 ctx_owned.set_run_config(run_config);
492 }
493 (self.func)(ctx_owned).await
494 }
495}
496
497pub struct PassthroughNode {
499 name: String,
500}
501
502impl PassthroughNode {
503 pub fn new(name: &str) -> Self {
505 Self { name: name.to_string() }
506 }
507}
508
509#[async_trait]
510impl Node for PassthroughNode {
511 fn name(&self) -> &str {
512 &self.name
513 }
514
515 async fn execute(&self, _ctx: &NodeContext) -> Result<NodeOutput> {
516 Ok(NodeOutput::new())
517 }
518}
519
520pub type AgentInputMapper = Box<dyn Fn(&State) -> adk_core::Content + Send + Sync>;
522
523pub type AgentOutputMapper =
525 Box<dyn Fn(&[adk_core::Event]) -> HashMap<String, Value> + Send + Sync>;
526
527pub type AgentGotoMapper =
531 Box<dyn Fn(&HashMap<String, Value>) -> Option<Vec<String>> + Send + Sync>;
532
533pub struct AgentNode {
535 name: String,
536 #[allow(dead_code)]
537 agent: Arc<dyn adk_core::Agent>,
538 input_mapper: AgentInputMapper,
540 output_mapper: AgentOutputMapper,
542 goto_mapper: Option<AgentGotoMapper>,
544}
545
546impl AgentNode {
547 pub fn new(agent: Arc<dyn adk_core::Agent>) -> Self {
549 let name = agent.name().to_string();
550 Self {
551 name,
552 agent,
553 input_mapper: Box::new(default_input_mapper),
554 output_mapper: Box::new(default_output_mapper),
555 goto_mapper: None,
556 }
557 }
558
559 pub fn with_input_mapper<F>(mut self, mapper: F) -> Self
561 where
562 F: Fn(&State) -> adk_core::Content + Send + Sync + 'static,
563 {
564 self.input_mapper = Box::new(mapper);
565 self
566 }
567
568 pub fn with_output_mapper<F>(mut self, mapper: F) -> Self
570 where
571 F: Fn(&[adk_core::Event]) -> HashMap<String, Value> + Send + Sync + 'static,
572 {
573 self.output_mapper = Box::new(mapper);
574 self
575 }
576
577 pub fn with_goto_mapper<F>(mut self, mapper: F) -> Self
602 where
603 F: Fn(&HashMap<String, Value>) -> Option<Vec<String>> + Send + Sync + 'static,
604 {
605 self.goto_mapper = Some(Box::new(mapper));
606 self
607 }
608}
609
610fn default_input_mapper(state: &State) -> adk_core::Content {
612 if let Some(messages) = state.get("messages")
614 && let Some(arr) = messages.as_array()
615 && let Some(last) = arr.last()
616 && let Some(content) = last.get("content").and_then(|c| c.as_str())
617 {
618 return adk_core::Content::new("user").with_text(content);
619 }
620
621 if let Some(input) = state.get("input")
623 && let Some(text) = input.as_str()
624 {
625 return adk_core::Content::new("user").with_text(text);
626 }
627
628 adk_core::Content::new("user")
629}
630
631fn default_output_mapper(events: &[adk_core::Event]) -> HashMap<String, Value> {
633 let mut updates = HashMap::new();
634
635 let mut messages = Vec::new();
637 for event in events {
638 if let Some(content) = event.content() {
639 let text = content.parts.iter().filter_map(|p| p.text()).collect::<Vec<_>>().join("");
640
641 if !text.is_empty() {
642 messages.push(serde_json::json!({
643 "role": "assistant",
644 "content": text
645 }));
646 }
647 }
648 }
649
650 if !messages.is_empty() {
651 updates.insert("messages".to_string(), serde_json::json!(messages));
652 }
653
654 updates
655}
656
657#[async_trait]
658impl Node for AgentNode {
659 fn name(&self) -> &str {
660 &self.name
661 }
662
663 fn description(&self) -> &str {
664 self.agent.description()
665 }
666
667 fn capabilities(&self) -> adk_core::AgentCapabilities {
668 self.agent.capabilities()
669 }
670
671 async fn execute(&self, ctx: &NodeContext) -> Result<NodeOutput> {
672 use futures::StreamExt;
673
674 let content = (self.input_mapper)(&ctx.state);
676
677 let invocation_ctx = Arc::new(GraphInvocationContext::with_parent(
679 ctx.config.thread_id.clone(),
680 content,
681 self.agent.clone(),
682 ctx.config.parent_context.clone(),
683 ctx.run_config(),
684 ));
685
686 let stream = self.agent.run(invocation_ctx).await.map_err(|e| {
688 crate::error::GraphError::NodeExecutionFailed {
689 node: self.name.clone(),
690 message: e.to_string(),
691 }
692 })?;
693
694 let events: Vec<adk_core::Event> = stream.filter_map(|r| async { r.ok() }).collect().await;
695
696 if let Some(request) =
697 events.iter().find_map(|event| event.actions.tool_confirmation.clone())
698 {
699 return Ok(NodeOutput::new().with_interrupt(
700 crate::interrupt::GraphToolConfirmationPause::pending_interrupt(
701 self.name.clone(),
702 request,
703 ),
704 ));
705 }
706
707 let updates = (self.output_mapper)(&events);
709 let goto = self.goto_mapper.as_ref().and_then(|mapper| mapper(&updates));
710
711 let mut output = NodeOutput::new().with_updates(updates);
713 if let Some(targets) = goto {
714 output = output.with_goto(targets);
715 }
716 for event in &events {
717 if let Ok(json) = serde_json::to_value(event) {
718 output = output.with_event(StreamEvent::custom(&self.name, "agent_event", json));
719 }
720 }
721
722 Ok(output)
723 }
724
725 fn execute_stream<'a>(
726 &'a self,
727 ctx: &'a NodeContext,
728 ) -> Pin<Box<dyn futures::Stream<Item = Result<StreamEvent>> + Send + 'a>> {
729 use futures::StreamExt;
730 let name = self.name.clone();
731 let agent = self.agent.clone();
732 let input_mapper = &self.input_mapper;
733 let output_mapper = &self.output_mapper;
734 let goto_mapper = &self.goto_mapper;
735 let parent_context = ctx.config.parent_context.clone();
736 let thread_id = ctx.config.thread_id.clone();
737 let content = (input_mapper)(&ctx.state);
738
739 Box::pin(async_stream::stream! {
740 tracing::debug!("AgentNode::execute_stream called for {}", name);
741 let invocation_ctx = Arc::new(GraphInvocationContext::with_parent(
742 thread_id,
743 content,
744 agent.clone(),
745 parent_context,
746 ctx.run_config(),
747 ));
748
749 let stream = match agent.run(invocation_ctx).await {
750 Ok(s) => s,
751 Err(e) => {
752 yield Err(crate::error::GraphError::NodeExecutionFailed {
753 node: name.clone(),
754 message: e.to_string(),
755 });
756 return;
757 }
758 };
759
760 tokio::pin!(stream);
761 let mut all_events = Vec::new();
762
763 while let Some(result) = stream.next().await {
764 match result {
765 Ok(event) => {
766 if let Some(request) = event.actions.tool_confirmation.clone() {
767 yield Ok(StreamEvent::node_interrupt(
768 &name,
769 crate::interrupt::GraphToolConfirmationPause::KIND,
770 Some(crate::interrupt::GraphToolConfirmationPause::pending_data(
771 name.clone(),
772 request,
773 )),
774 ));
775 return;
776 }
777 if let Some(content) = event.content() {
779 let text: String = content.parts.iter().filter_map(|p| p.text()).collect();
780 if !text.is_empty() {
781 yield Ok(StreamEvent::Message {
782 node: name.clone(),
783 content: text,
784 is_final: false,
785 });
786 }
787 }
788 all_events.push(event);
789 }
790 Err(e) => {
791 yield Err(crate::error::GraphError::NodeExecutionFailed {
792 node: name.clone(),
793 message: e.to_string(),
794 });
795 return;
796 }
797 }
798 }
799
800 for event in &all_events {
802 if let Ok(json) = serde_json::to_value(event) {
803 yield Ok(StreamEvent::custom(&name, "agent_event", json));
804 }
805 }
806
807 let updates = (output_mapper)(&all_events);
811 if let Some(targets) = goto_mapper.as_ref().and_then(|mapper| mapper(&updates)) {
813 yield Ok(StreamEvent::route_dispatched(&name, targets));
814 }
815 yield Ok(StreamEvent::Updates { node: name.clone(), updates });
816 })
817 }
818}
819
820struct GraphInvocationContext {
822 invocation_id: String,
823 user_content: adk_core::Content,
824 agent: Arc<dyn adk_core::Agent>,
825 session: Arc<GraphSession>,
826 run_config: adk_core::RunConfig,
827 ended: std::sync::atomic::AtomicBool,
828 parent: Option<Arc<dyn adk_core::InvocationContext>>,
834 user_id: String,
836 app_name: String,
837 branch: String,
838}
839
840const STANDALONE_USER_ID: &str = "graph_user";
842const STANDALONE_APP_NAME: &str = "graph_app";
844
845impl GraphInvocationContext {
846 fn with_parent(
847 session_id: String,
848 user_content: adk_core::Content,
849 agent: Arc<dyn adk_core::Agent>,
850 parent: Option<Arc<dyn adk_core::InvocationContext>>,
851 explicit_run_config: Option<adk_core::RunConfig>,
852 ) -> Self {
853 let invocation_id = uuid::Uuid::new_v4().to_string();
854 let session = Arc::new(GraphSession::new(session_id));
855 session.append_content(user_content.clone());
857
858 let (user_id, app_name, branch, inherited_run_config) = match parent.as_ref() {
861 Some(parent) => (
862 parent.user_id().to_string(),
863 parent.app_name().to_string(),
864 match parent.branch() {
865 "" => agent.name().to_string(),
866 existing => format!("{existing}.{}", agent.name()),
867 },
868 parent.run_config().clone(),
869 ),
870 None => (
871 STANDALONE_USER_ID.to_string(),
872 STANDALONE_APP_NAME.to_string(),
873 "main".to_string(),
874 adk_core::RunConfig::default(),
875 ),
876 };
877
878 Self {
879 invocation_id,
880 user_content,
881 agent,
882 session,
883 run_config: explicit_run_config.unwrap_or(inherited_run_config),
884 ended: std::sync::atomic::AtomicBool::new(false),
885 parent,
886 user_id,
887 app_name,
888 branch,
889 }
890 }
891}
892
893impl adk_core::ReadonlyContext for GraphInvocationContext {
895 fn invocation_id(&self) -> &str {
896 &self.invocation_id
897 }
898
899 fn agent_name(&self) -> &str {
900 self.agent.name()
901 }
902
903 fn user_id(&self) -> &str {
904 &self.user_id
905 }
906
907 fn app_name(&self) -> &str {
908 &self.app_name
909 }
910
911 fn session_id(&self) -> &str {
912 &self.session.id
913 }
914
915 fn branch(&self) -> &str {
916 &self.branch
917 }
918
919 fn user_content(&self) -> &adk_core::Content {
920 &self.user_content
921 }
922}
923
924#[async_trait]
926impl adk_core::CallbackContext for GraphInvocationContext {
927 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
928 self.parent.as_ref().and_then(|parent| parent.artifacts())
929 }
930
931 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
932 self.parent.as_ref().and_then(|parent| parent.shared_state())
933 }
934}
935
936#[async_trait]
938impl adk_core::InvocationContext for GraphInvocationContext {
939 fn agent(&self) -> Arc<dyn adk_core::Agent> {
940 self.agent.clone()
941 }
942
943 fn memory(&self) -> Option<Arc<dyn adk_core::Memory>> {
944 self.parent.as_ref().and_then(|parent| parent.memory())
945 }
946
947 fn session(&self) -> &dyn adk_core::Session {
948 self.session.as_ref()
949 }
950
951 fn run_config(&self) -> &adk_core::RunConfig {
952 &self.run_config
953 }
954
955 fn end_invocation(&self) {
956 self.ended.store(true, std::sync::atomic::Ordering::SeqCst);
957 if let Some(parent) = &self.parent {
958 parent.end_invocation();
959 }
960 }
961
962 fn ended(&self) -> bool {
963 self.ended.load(std::sync::atomic::Ordering::SeqCst)
964 || self.parent.as_ref().is_some_and(|parent| parent.ended())
965 }
966
967 fn is_cancelled(&self) -> bool {
968 self.parent.as_ref().is_some_and(|parent| parent.is_cancelled())
969 }
970
971 fn user_scopes(&self) -> Vec<String> {
972 self.parent.as_ref().map(|parent| parent.user_scopes()).unwrap_or_default()
973 }
974
975 fn request_metadata(&self) -> std::collections::HashMap<String, Value> {
976 self.parent.as_ref().map(|parent| parent.request_metadata()).unwrap_or_default()
977 }
978
979 async fn get_secret(&self, name: &str) -> adk_core::Result<Option<String>> {
980 match &self.parent {
981 Some(parent) => parent.get_secret(name).await,
982 None => Ok(None),
983 }
984 }
985
986 async fn get_secret_for(
987 &self,
988 request: &adk_core::SecretRequest,
989 ) -> adk_core::Result<Option<String>> {
990 match &self.parent {
991 Some(parent) => parent.get_secret_for(request).await,
992 None => Ok(None),
993 }
994 }
995}
996
997struct GraphSession {
999 id: String,
1000 state: GraphState,
1001 history: std::sync::RwLock<Vec<adk_core::Content>>,
1002}
1003
1004impl GraphSession {
1005 fn new(id: String) -> Self {
1006 Self { id, state: GraphState::new(), history: std::sync::RwLock::new(Vec::new()) }
1007 }
1008
1009 fn append_content(&self, content: adk_core::Content) {
1010 if let Ok(mut h) = self.history.write() {
1011 h.push(content);
1012 }
1013 }
1014}
1015
1016impl adk_core::Session for GraphSession {
1017 fn id(&self) -> &str {
1018 &self.id
1019 }
1020
1021 fn app_name(&self) -> &str {
1022 "graph_app"
1023 }
1024
1025 fn user_id(&self) -> &str {
1026 "graph_user"
1027 }
1028
1029 fn state(&self) -> &dyn adk_core::State {
1030 &self.state
1031 }
1032
1033 fn conversation_history(&self) -> Vec<adk_core::Content> {
1034 self.history.read().ok().map(|h| h.clone()).unwrap_or_default()
1035 }
1036
1037 fn append_to_history(&self, content: adk_core::Content) {
1038 self.append_content(content);
1039 }
1040}
1041
1042struct GraphState {
1044 data: std::sync::RwLock<std::collections::HashMap<String, serde_json::Value>>,
1045}
1046
1047impl GraphState {
1048 fn new() -> Self {
1049 Self { data: std::sync::RwLock::new(std::collections::HashMap::new()) }
1050 }
1051}
1052
1053impl adk_core::State for GraphState {
1054 fn get(&self, key: &str) -> Option<serde_json::Value> {
1055 self.data.read().ok()?.get(key).cloned()
1056 }
1057
1058 fn set(&mut self, key: String, value: serde_json::Value) {
1059 if let Err(msg) = adk_core::validate_state_key(&key) {
1060 tracing::warn!(key = %key, "rejecting invalid state key: {msg}");
1061 return;
1062 }
1063 if let Ok(mut data) = self.data.write() {
1064 data.insert(key, value);
1065 }
1066 }
1067
1068 fn all(&self) -> std::collections::HashMap<String, serde_json::Value> {
1069 self.data.read().ok().map(|d| d.clone()).unwrap_or_default()
1070 }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::*;
1076
1077 #[tokio::test]
1078 async fn test_function_node() {
1079 let node = FunctionNode::new("test", |_ctx| async {
1080 Ok(NodeOutput::new().with_update("result", serde_json::json!("success")))
1081 });
1082
1083 assert_eq!(node.name(), "test");
1084
1085 let ctx = NodeContext::new(State::new(), ExecutionConfig::default(), 0);
1086 let output = node.execute(&ctx).await.unwrap();
1087
1088 assert_eq!(output.updates.get("result"), Some(&serde_json::json!("success")));
1089 }
1090
1091 #[tokio::test]
1092 async fn test_passthrough_node() {
1093 let node = PassthroughNode::new("pass");
1094 let ctx = NodeContext::new(State::new(), ExecutionConfig::default(), 0);
1095 let output = node.execute(&ctx).await.unwrap();
1096
1097 assert!(output.updates.is_empty());
1098 assert!(output.interrupt.is_none());
1099 }
1100
1101 #[test]
1102 fn test_node_output_builder() {
1103 let output = NodeOutput::new().with_update("a", 1).with_update("b", "hello");
1104
1105 assert_eq!(output.updates.get("a"), Some(&serde_json::json!(1)));
1106 assert_eq!(output.updates.get("b"), Some(&serde_json::json!("hello")));
1107 }
1108}