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 let mut text_deltas = adk_core::EventTextDeltas::default();
638 for event in events {
639 let event = text_deltas.push(event);
640 if let Some(content) = event.content() {
641 let text = content.parts.iter().filter_map(|p| p.text()).collect::<Vec<_>>().join("");
642
643 if !text.is_empty() {
644 messages.push(serde_json::json!({
645 "role": "assistant",
646 "content": text
647 }));
648 }
649 }
650 }
651
652 if !messages.is_empty() {
653 updates.insert("messages".to_string(), serde_json::json!(messages));
654 }
655
656 updates
657}
658
659#[async_trait]
660impl Node for AgentNode {
661 fn name(&self) -> &str {
662 &self.name
663 }
664
665 fn description(&self) -> &str {
666 self.agent.description()
667 }
668
669 fn capabilities(&self) -> adk_core::AgentCapabilities {
670 self.agent.capabilities()
671 }
672
673 async fn execute(&self, ctx: &NodeContext) -> Result<NodeOutput> {
674 use futures::StreamExt;
675
676 let content = (self.input_mapper)(&ctx.state);
678
679 let invocation_ctx = Arc::new(GraphInvocationContext::with_parent(
681 ctx.config.thread_id.clone(),
682 content,
683 self.agent.clone(),
684 ctx.config.parent_context.clone(),
685 ctx.run_config(),
686 ));
687
688 let stream = self.agent.run(invocation_ctx).await.map_err(|e| {
690 crate::error::GraphError::NodeExecutionFailed {
691 node: self.name.clone(),
692 message: e.to_string(),
693 }
694 })?;
695
696 let events: Vec<adk_core::Event> = stream.filter_map(|r| async { r.ok() }).collect().await;
697
698 if let Some(request) =
699 events.iter().find_map(|event| event.actions.tool_confirmation.clone())
700 {
701 return Ok(NodeOutput::new().with_interrupt(
702 crate::interrupt::GraphToolConfirmationPause::pending_interrupt(
703 self.name.clone(),
704 request,
705 ),
706 ));
707 }
708
709 let updates = (self.output_mapper)(&events);
711 let goto = self.goto_mapper.as_ref().and_then(|mapper| mapper(&updates));
712
713 let mut output = NodeOutput::new().with_updates(updates);
715 if let Some(targets) = goto {
716 output = output.with_goto(targets);
717 }
718 for event in &events {
719 if let Ok(json) = serde_json::to_value(event) {
720 output = output.with_event(StreamEvent::custom(&self.name, "agent_event", json));
721 }
722 }
723
724 Ok(output)
725 }
726
727 fn execute_stream<'a>(
728 &'a self,
729 ctx: &'a NodeContext,
730 ) -> Pin<Box<dyn futures::Stream<Item = Result<StreamEvent>> + Send + 'a>> {
731 use futures::StreamExt;
732 let name = self.name.clone();
733 let agent = self.agent.clone();
734 let input_mapper = &self.input_mapper;
735 let output_mapper = &self.output_mapper;
736 let goto_mapper = &self.goto_mapper;
737 let parent_context = ctx.config.parent_context.clone();
738 let thread_id = ctx.config.thread_id.clone();
739 let content = (input_mapper)(&ctx.state);
740
741 Box::pin(async_stream::stream! {
742 tracing::debug!("AgentNode::execute_stream called for {}", name);
743 let invocation_ctx = Arc::new(GraphInvocationContext::with_parent(
744 thread_id,
745 content,
746 agent.clone(),
747 parent_context,
748 ctx.run_config(),
749 ));
750
751 let stream = match agent.run(invocation_ctx).await {
752 Ok(s) => s,
753 Err(e) => {
754 yield Err(crate::error::GraphError::NodeExecutionFailed {
755 node: name.clone(),
756 message: e.to_string(),
757 });
758 return;
759 }
760 };
761
762 tokio::pin!(stream);
763 let mut all_events = Vec::new();
764 let mut text_deltas = adk_core::EventTextDeltas::default();
765
766 while let Some(result) = stream.next().await {
767 match result {
768 Ok(event) => {
769 if let Some(request) = event.actions.tool_confirmation.clone() {
770 yield Ok(StreamEvent::node_interrupt(
771 &name,
772 crate::interrupt::GraphToolConfirmationPause::KIND,
773 Some(crate::interrupt::GraphToolConfirmationPause::pending_data(
774 name.clone(),
775 request,
776 )),
777 ));
778 return;
779 }
780 if let Some(content) = text_deltas.push(&event).content() {
782 let text: String = content.parts.iter().filter_map(|p| p.text()).collect();
783 if !text.is_empty() {
784 yield Ok(StreamEvent::Message {
785 node: name.clone(),
786 content: text,
787 is_final: false,
788 });
789 }
790 }
791 all_events.push(event);
792 }
793 Err(e) => {
794 yield Err(crate::error::GraphError::NodeExecutionFailed {
795 node: name.clone(),
796 message: e.to_string(),
797 });
798 return;
799 }
800 }
801 }
802
803 for event in &all_events {
805 if let Ok(json) = serde_json::to_value(event) {
806 yield Ok(StreamEvent::custom(&name, "agent_event", json));
807 }
808 }
809
810 let updates = (output_mapper)(&all_events);
814 if let Some(targets) = goto_mapper.as_ref().and_then(|mapper| mapper(&updates)) {
816 yield Ok(StreamEvent::route_dispatched(&name, targets));
817 }
818 yield Ok(StreamEvent::Updates { node: name.clone(), updates });
819 })
820 }
821}
822
823struct GraphInvocationContext {
825 invocation_id: String,
826 user_content: adk_core::Content,
827 agent: Arc<dyn adk_core::Agent>,
828 session: Arc<GraphSession>,
829 run_config: adk_core::RunConfig,
830 ended: std::sync::atomic::AtomicBool,
831 parent: Option<Arc<dyn adk_core::InvocationContext>>,
837 user_id: String,
839 app_name: String,
840 branch: String,
841}
842
843const STANDALONE_USER_ID: &str = "graph_user";
845const STANDALONE_APP_NAME: &str = "graph_app";
847
848impl GraphInvocationContext {
849 fn with_parent(
850 session_id: String,
851 user_content: adk_core::Content,
852 agent: Arc<dyn adk_core::Agent>,
853 parent: Option<Arc<dyn adk_core::InvocationContext>>,
854 explicit_run_config: Option<adk_core::RunConfig>,
855 ) -> Self {
856 let invocation_id = uuid::Uuid::new_v4().to_string();
857 let session = Arc::new(GraphSession::new(session_id));
858 session.append_content(user_content.clone());
860
861 let (user_id, app_name, branch, inherited_run_config) = match parent.as_ref() {
864 Some(parent) => (
865 parent.user_id().to_string(),
866 parent.app_name().to_string(),
867 match parent.branch() {
868 "" => agent.name().to_string(),
869 existing => format!("{existing}.{}", agent.name()),
870 },
871 parent.run_config().clone(),
872 ),
873 None => (
874 STANDALONE_USER_ID.to_string(),
875 STANDALONE_APP_NAME.to_string(),
876 "main".to_string(),
877 adk_core::RunConfig::default(),
878 ),
879 };
880
881 Self {
882 invocation_id,
883 user_content,
884 agent,
885 session,
886 run_config: explicit_run_config.unwrap_or(inherited_run_config),
887 ended: std::sync::atomic::AtomicBool::new(false),
888 parent,
889 user_id,
890 app_name,
891 branch,
892 }
893 }
894}
895
896impl adk_core::ReadonlyContext for GraphInvocationContext {
898 fn invocation_id(&self) -> &str {
899 &self.invocation_id
900 }
901
902 fn agent_name(&self) -> &str {
903 self.agent.name()
904 }
905
906 fn user_id(&self) -> &str {
907 &self.user_id
908 }
909
910 fn app_name(&self) -> &str {
911 &self.app_name
912 }
913
914 fn session_id(&self) -> &str {
915 &self.session.id
916 }
917
918 fn branch(&self) -> &str {
919 &self.branch
920 }
921
922 fn user_content(&self) -> &adk_core::Content {
923 &self.user_content
924 }
925}
926
927#[async_trait]
929impl adk_core::CallbackContext for GraphInvocationContext {
930 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
931 self.parent.as_ref().and_then(|parent| parent.artifacts())
932 }
933
934 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
935 self.parent.as_ref().and_then(|parent| parent.shared_state())
936 }
937}
938
939#[async_trait]
941impl adk_core::InvocationContext for GraphInvocationContext {
942 fn agent(&self) -> Arc<dyn adk_core::Agent> {
943 self.agent.clone()
944 }
945
946 fn memory(&self) -> Option<Arc<dyn adk_core::Memory>> {
947 self.parent.as_ref().and_then(|parent| parent.memory())
948 }
949
950 fn session(&self) -> &dyn adk_core::Session {
951 self.session.as_ref()
952 }
953
954 fn run_config(&self) -> &adk_core::RunConfig {
955 &self.run_config
956 }
957
958 fn end_invocation(&self) {
959 self.ended.store(true, std::sync::atomic::Ordering::SeqCst);
960 if let Some(parent) = &self.parent {
961 parent.end_invocation();
962 }
963 }
964
965 fn ended(&self) -> bool {
966 self.ended.load(std::sync::atomic::Ordering::SeqCst)
967 || self.parent.as_ref().is_some_and(|parent| parent.ended())
968 }
969
970 fn is_cancelled(&self) -> bool {
971 self.parent.as_ref().is_some_and(|parent| parent.is_cancelled())
972 }
973
974 fn user_scopes(&self) -> Vec<String> {
975 self.parent.as_ref().map(|parent| parent.user_scopes()).unwrap_or_default()
976 }
977
978 fn request_metadata(&self) -> std::collections::HashMap<String, Value> {
979 self.parent.as_ref().map(|parent| parent.request_metadata()).unwrap_or_default()
980 }
981
982 async fn get_secret(&self, name: &str) -> adk_core::Result<Option<String>> {
983 match &self.parent {
984 Some(parent) => parent.get_secret(name).await,
985 None => Ok(None),
986 }
987 }
988
989 async fn get_secret_for(
990 &self,
991 request: &adk_core::SecretRequest,
992 ) -> adk_core::Result<Option<String>> {
993 match &self.parent {
994 Some(parent) => parent.get_secret_for(request).await,
995 None => Ok(None),
996 }
997 }
998}
999
1000struct GraphSession {
1002 id: String,
1003 state: GraphState,
1004 history: std::sync::RwLock<Vec<adk_core::Content>>,
1005}
1006
1007impl GraphSession {
1008 fn new(id: String) -> Self {
1009 Self { id, state: GraphState::new(), history: std::sync::RwLock::new(Vec::new()) }
1010 }
1011
1012 fn append_content(&self, content: adk_core::Content) {
1013 if let Ok(mut h) = self.history.write() {
1014 h.push(content);
1015 }
1016 }
1017}
1018
1019impl adk_core::Session for GraphSession {
1020 fn id(&self) -> &str {
1021 &self.id
1022 }
1023
1024 fn app_name(&self) -> &str {
1025 "graph_app"
1026 }
1027
1028 fn user_id(&self) -> &str {
1029 "graph_user"
1030 }
1031
1032 fn state(&self) -> &dyn adk_core::State {
1033 &self.state
1034 }
1035
1036 fn conversation_history(&self) -> Vec<adk_core::Content> {
1037 self.history.read().ok().map(|h| h.clone()).unwrap_or_default()
1038 }
1039
1040 fn append_to_history(&self, content: adk_core::Content) {
1041 self.append_content(content);
1042 }
1043}
1044
1045struct GraphState {
1047 data: std::sync::RwLock<std::collections::HashMap<String, serde_json::Value>>,
1048}
1049
1050impl GraphState {
1051 fn new() -> Self {
1052 Self { data: std::sync::RwLock::new(std::collections::HashMap::new()) }
1053 }
1054}
1055
1056impl adk_core::State for GraphState {
1057 fn get(&self, key: &str) -> Option<serde_json::Value> {
1058 self.data.read().ok()?.get(key).cloned()
1059 }
1060
1061 fn set(&mut self, key: String, value: serde_json::Value) {
1062 if let Err(msg) = adk_core::validate_state_key(&key) {
1063 tracing::warn!(key = %key, "rejecting invalid state key: {msg}");
1064 return;
1065 }
1066 if let Ok(mut data) = self.data.write() {
1067 data.insert(key, value);
1068 }
1069 }
1070
1071 fn all(&self) -> std::collections::HashMap<String, serde_json::Value> {
1072 self.data.read().ok().map(|d| d.clone()).unwrap_or_default()
1073 }
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078 use super::*;
1079
1080 #[tokio::test]
1081 async fn test_function_node() {
1082 let node = FunctionNode::new("test", |_ctx| async {
1083 Ok(NodeOutput::new().with_update("result", serde_json::json!("success")))
1084 });
1085
1086 assert_eq!(node.name(), "test");
1087
1088 let ctx = NodeContext::new(State::new(), ExecutionConfig::default(), 0);
1089 let output = node.execute(&ctx).await.unwrap();
1090
1091 assert_eq!(output.updates.get("result"), Some(&serde_json::json!("success")));
1092 }
1093
1094 #[tokio::test]
1095 async fn test_passthrough_node() {
1096 let node = PassthroughNode::new("pass");
1097 let ctx = NodeContext::new(State::new(), ExecutionConfig::default(), 0);
1098 let output = node.execute(&ctx).await.unwrap();
1099
1100 assert!(output.updates.is_empty());
1101 assert!(output.interrupt.is_none());
1102 }
1103
1104 #[test]
1105 fn default_output_mapper_does_not_repeat_a_complete_snapshot() {
1106 let mut partial = adk_core::Event::new("invocation");
1107 partial.llm_response.partial = true;
1108 partial.set_content(adk_core::Content::new("model").with_text("Hello"));
1109 let mut terminal = partial.clone();
1110 terminal.llm_response.partial = false;
1111 terminal.llm_response.provider_metadata =
1112 Some(serde_json::json!({"content_complete": true}));
1113 terminal.set_content(adk_core::Content::new("model").with_text("Hello world"));
1114
1115 let updates = default_output_mapper(&[partial, terminal]);
1116
1117 assert_eq!(
1118 updates.get("messages"),
1119 Some(&serde_json::json!([
1120 {"role": "assistant", "content": "Hello"},
1121 {"role": "assistant", "content": " world"},
1122 ]))
1123 );
1124 }
1125
1126 #[test]
1127 fn test_node_output_builder() {
1128 let output = NodeOutput::new().with_update("a", 1).with_update("b", "hello");
1129
1130 assert_eq!(output.updates.get("a"), Some(&serde_json::json!(1)));
1131 assert_eq!(output.updates.get("b"), Some(&serde_json::json!("hello")));
1132 }
1133}