1use super::error::ExecutionError;
38use super::execution_state::{ExecutionState, StepState};
39use super::ids::{CallableType, ExecutionId, ParentLink, StepId, StepSource, StepType, TenantId};
40use serde::{Deserialize, Serialize};
41use std::collections::HashMap;
42use std::time::Instant;
43
44#[derive(Debug)]
46pub struct Execution {
47 pub id: ExecutionId,
49 pub tenant_id: Option<TenantId>,
52 pub state: ExecutionState,
54 pub parent: Option<ParentLink>,
56 pub steps: HashMap<StepId, Step>,
58 pub step_order: Vec<StepId>,
60 pub schema_version: Option<String>,
62 pub started_at: Option<Instant>,
64 pub ended_at: Option<Instant>,
66 pub output: Option<String>,
68 pub error: Option<ExecutionError>,
70}
71
72impl Execution {
73 pub fn new() -> Self {
75 Self {
76 id: ExecutionId::new(),
77 tenant_id: None,
78 state: ExecutionState::Created,
79 parent: None,
80 steps: HashMap::new(),
81 step_order: Vec::new(),
82 schema_version: None,
83 started_at: None,
84 ended_at: None,
85 output: None,
86 error: None,
87 }
88 }
89
90 pub fn with_id(id: ExecutionId) -> Self {
92 Self {
93 id,
94 tenant_id: None,
95 state: ExecutionState::Created,
96 parent: None,
97 steps: HashMap::new(),
98 step_order: Vec::new(),
99 schema_version: None,
100 started_at: None,
101 ended_at: None,
102 output: None,
103 error: None,
104 }
105 }
106
107 pub fn with_tenant(tenant_id: TenantId) -> Self {
109 Self {
110 id: ExecutionId::new(),
111 tenant_id: Some(tenant_id),
112 state: ExecutionState::Created,
113 parent: None,
114 steps: HashMap::new(),
115 step_order: Vec::new(),
116 schema_version: None,
117 started_at: None,
118 ended_at: None,
119 output: None,
120 error: None,
121 }
122 }
123
124 pub fn child(&self) -> Self {
127 let mut child = Self::new();
128 child.parent = Some(ParentLink::execution(self.id.clone()));
129 child.tenant_id = self.tenant_id.clone(); child
131 }
132
133 pub fn with_schema_version(mut self, version: impl Into<String>) -> Self {
135 self.schema_version = Some(version.into());
136 self
137 }
138
139 pub fn get_step(&self, id: &StepId) -> Option<&Step> {
141 self.steps.get(id)
142 }
143
144 pub fn get_step_mut(&mut self, id: &StepId) -> Option<&mut Step> {
146 self.steps.get_mut(id)
147 }
148
149 pub fn add_step(&mut self, step: Step) {
151 self.step_order.push(step.id.clone());
152 self.steps.insert(step.id.clone(), step);
153 }
154
155 pub fn duration_ms(&self) -> Option<u64> {
157 match (self.started_at, self.ended_at) {
158 (Some(start), Some(end)) => Some(end.duration_since(start).as_millis() as u64),
159 (Some(start), None) => Some(start.elapsed().as_millis() as u64),
160 _ => None,
161 }
162 }
163
164 pub fn is_terminal(&self) -> bool {
166 self.state.is_terminal()
167 }
168}
169
170impl Default for Execution {
171 fn default() -> Self {
172 Self::new()
173 }
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct Step {
182 #[serde(rename = "stepId")]
184 pub id: StepId,
185 #[serde(rename = "parentStepId")]
187 pub parent_step_id: Option<StepId>,
188 pub step_type: StepType,
190 pub name: String,
192 pub state: StepState,
194 pub input: Option<String>,
196 pub output: Option<String>,
198 pub error: Option<ExecutionError>,
200 pub duration_ms: Option<u64>,
202 pub started_at: Option<i64>,
204 pub ended_at: Option<i64>,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub source: Option<StepSource>,
209 #[serde(skip_serializing_if = "Option::is_none")]
212 pub callable_id: Option<String>,
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub callable_type: Option<CallableType>,
216}
217
218impl Step {
219 pub fn new(step_type: StepType, name: impl Into<String>) -> Self {
221 Self {
222 id: StepId::new(),
223 parent_step_id: None,
224 step_type,
225 name: name.into(),
226 state: StepState::Pending,
227 input: None,
228 output: None,
229 error: None,
230 duration_ms: None,
231 started_at: None,
232 ended_at: None,
233 source: None,
234 callable_id: None,
235 callable_type: None,
236 }
237 }
238
239 pub fn nested(parent_id: &StepId, step_type: StepType, name: impl Into<String>) -> Self {
241 let mut step = Self::new(step_type, name);
242 step.parent_step_id = Some(parent_id.clone());
243 step
244 }
245
246 pub fn with_input(mut self, input: impl Into<String>) -> Self {
248 self.input = Some(input.into());
249 self
250 }
251
252 pub fn with_source(mut self, source: StepSource) -> Self {
254 self.source = Some(source);
255 self
256 }
257
258 pub fn with_callable(
263 mut self,
264 callable_id: impl Into<String>,
265 callable_type: CallableType,
266 ) -> Self {
267 self.callable_id = Some(callable_id.into());
268 self.callable_type = Some(callable_type);
269 self
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::super::execution_state::WaitReason;
276 use super::super::ids::StepSourceType;
277 use super::*;
278
279 #[test]
284 fn test_execution_new() {
285 let exec = Execution::new();
286 assert!(exec.id.as_str().starts_with("exec_"));
287 assert_eq!(exec.state, ExecutionState::Created);
288 assert!(exec.parent.is_none());
289 assert!(exec.steps.is_empty());
290 assert!(exec.step_order.is_empty());
291 assert!(exec.schema_version.is_none());
292 assert!(exec.started_at.is_none());
293 assert!(exec.ended_at.is_none());
294 assert!(exec.output.is_none());
295 assert!(exec.error.is_none());
296 }
297
298 #[test]
299 fn test_execution_with_id() {
300 let id = ExecutionId::from_string("exec_custom_id");
301 let exec = Execution::with_id(id.clone());
302 assert_eq!(exec.id.as_str(), "exec_custom_id");
303 assert_eq!(exec.state, ExecutionState::Created);
304 }
305
306 #[test]
307 fn test_execution_child() {
308 let parent = Execution::new();
309 let child = parent.child();
310
311 assert!(child.parent.is_some());
312 let parent_link = child.parent.unwrap();
313 assert_eq!(parent_link.parent_id, parent.id.as_str());
314 }
315
316 #[test]
317 fn test_execution_with_schema_version() {
318 let exec = Execution::new().with_schema_version("v1.0.0");
319 assert_eq!(exec.schema_version, Some("v1.0.0".to_string()));
320 }
321
322 #[test]
323 fn test_execution_with_schema_version_owned_string() {
324 let exec = Execution::new().with_schema_version(String::from("v2.0.0"));
325 assert_eq!(exec.schema_version, Some("v2.0.0".to_string()));
326 }
327
328 #[test]
329 fn test_execution_add_step() {
330 let mut exec = Execution::new();
331 let step = Step::new(StepType::LlmNode, "test_step");
332 let step_id = step.id.clone();
333
334 exec.add_step(step);
335
336 assert_eq!(exec.steps.len(), 1);
337 assert_eq!(exec.step_order.len(), 1);
338 assert!(exec.steps.contains_key(&step_id));
339 }
340
341 #[test]
342 fn test_execution_get_step() {
343 let mut exec = Execution::new();
344 let step = Step::new(StepType::ToolNode, "get_step_test");
345 let step_id = step.id.clone();
346 exec.add_step(step);
347
348 let retrieved = exec.get_step(&step_id);
349 assert!(retrieved.is_some());
350 assert_eq!(retrieved.unwrap().name, "get_step_test");
351 }
352
353 #[test]
354 fn test_execution_get_step_not_found() {
355 let exec = Execution::new();
356 let nonexistent_id = StepId::from_string("step_nonexistent");
357 assert!(exec.get_step(&nonexistent_id).is_none());
358 }
359
360 #[test]
361 fn test_execution_get_step_mut() {
362 let mut exec = Execution::new();
363 let step = Step::new(StepType::FunctionNode, "mutable_step");
364 let step_id = step.id.clone();
365 exec.add_step(step);
366
367 let step_mut = exec.get_step_mut(&step_id);
368 assert!(step_mut.is_some());
369
370 step_mut.unwrap().output = Some("modified".to_string());
371
372 let step = exec.get_step(&step_id).unwrap();
373 assert_eq!(step.output, Some("modified".to_string()));
374 }
375
376 #[test]
377 fn test_execution_step_order_preserved() {
378 let mut exec = Execution::new();
379 let step1 = Step::new(StepType::LlmNode, "step1");
380 let step2 = Step::new(StepType::ToolNode, "step2");
381 let step3 = Step::new(StepType::FunctionNode, "step3");
382
383 let id1 = step1.id.clone();
384 let id2 = step2.id.clone();
385 let id3 = step3.id.clone();
386
387 exec.add_step(step1);
388 exec.add_step(step2);
389 exec.add_step(step3);
390
391 assert_eq!(exec.step_order[0], id1);
392 assert_eq!(exec.step_order[1], id2);
393 assert_eq!(exec.step_order[2], id3);
394 }
395
396 #[test]
397 fn test_execution_duration_ms_not_started() {
398 let exec = Execution::new();
399 assert!(exec.duration_ms().is_none());
400 }
401
402 #[test]
403 fn test_execution_duration_ms_started_not_ended() {
404 let mut exec = Execution::new();
405 exec.started_at = Some(Instant::now());
406 std::thread::sleep(std::time::Duration::from_millis(10));
407
408 let duration = exec.duration_ms();
409 assert!(duration.is_some());
410 assert!(duration.unwrap() >= 10);
411 }
412
413 #[test]
414 fn test_execution_duration_ms_completed() {
415 let mut exec = Execution::new();
416 let start = Instant::now();
417 std::thread::sleep(std::time::Duration::from_millis(20));
418 let end = Instant::now();
419
420 exec.started_at = Some(start);
421 exec.ended_at = Some(end);
422
423 let duration = exec.duration_ms();
424 assert!(duration.is_some());
425 assert!(duration.unwrap() >= 20);
426 }
427
428 #[test]
429 fn test_execution_is_terminal_created() {
430 let exec = Execution::new();
431 assert!(!exec.is_terminal());
432 }
433
434 #[test]
435 fn test_execution_is_terminal_running() {
436 let mut exec = Execution::new();
437 exec.state = ExecutionState::Running;
438 assert!(!exec.is_terminal());
439 }
440
441 #[test]
442 fn test_execution_is_terminal_completed() {
443 let mut exec = Execution::new();
444 exec.state = ExecutionState::Completed;
445 assert!(exec.is_terminal());
446 }
447
448 #[test]
449 fn test_execution_is_terminal_failed() {
450 let mut exec = Execution::new();
451 exec.state = ExecutionState::Failed;
452 assert!(exec.is_terminal());
453 }
454
455 #[test]
456 fn test_execution_is_terminal_cancelled() {
457 let mut exec = Execution::new();
458 exec.state = ExecutionState::Cancelled;
459 assert!(exec.is_terminal());
460 }
461
462 #[test]
463 fn test_execution_is_terminal_paused() {
464 let mut exec = Execution::new();
465 exec.state = ExecutionState::Paused;
466 assert!(!exec.is_terminal());
467 }
468
469 #[test]
470 fn test_execution_is_terminal_waiting() {
471 let mut exec = Execution::new();
472 exec.state = ExecutionState::Waiting(WaitReason::Approval);
473 assert!(!exec.is_terminal());
474 }
475
476 #[test]
477 fn test_execution_default() {
478 let exec: Execution = Default::default();
479 assert!(exec.id.as_str().starts_with("exec_"));
480 assert_eq!(exec.state, ExecutionState::Created);
481 }
482
483 #[test]
488 fn test_step_new() {
489 let step = Step::new(StepType::LlmNode, "test_step");
490 assert!(step.id.as_str().starts_with("step_"));
491 assert_eq!(step.step_type, StepType::LlmNode);
492 assert_eq!(step.name, "test_step");
493 assert_eq!(step.state, StepState::Pending);
494 assert!(step.parent_step_id.is_none());
495 assert!(step.input.is_none());
496 assert!(step.output.is_none());
497 assert!(step.error.is_none());
498 assert!(step.duration_ms.is_none());
499 }
500
501 #[test]
502 fn test_step_new_all_types() {
503 let types = vec![
504 StepType::LlmNode,
505 StepType::GraphNode,
506 StepType::ToolNode,
507 StepType::FunctionNode,
508 StepType::RouterNode,
509 StepType::BranchNode,
510 StepType::LoopNode,
511 ];
512
513 for step_type in types {
514 let step = Step::new(step_type.clone(), "test");
515 assert_eq!(step.step_type, step_type);
516 }
517 }
518
519 #[test]
520 fn test_step_nested() {
521 let parent_id = StepId::from_string("step_parent");
522 let step = Step::nested(&parent_id, StepType::ToolNode, "nested_step");
523
524 assert!(step.parent_step_id.is_some());
525 assert_eq!(step.parent_step_id.unwrap().as_str(), "step_parent");
526 assert_eq!(step.step_type, StepType::ToolNode);
527 assert_eq!(step.name, "nested_step");
528 }
529
530 #[test]
531 fn test_step_with_input() {
532 let step = Step::new(StepType::LlmNode, "input_step").with_input("Hello, AI!");
533
534 assert!(step.input.is_some());
535 assert_eq!(step.input.unwrap(), "Hello, AI!");
536 }
537
538 #[test]
539 fn test_step_with_input_owned_string() {
540 let step =
541 Step::new(StepType::LlmNode, "input_step").with_input(String::from("Owned input"));
542
543 assert!(step.input.is_some());
544 assert_eq!(step.input.unwrap(), "Owned input");
545 }
546
547 #[test]
548 fn test_step_clone() {
549 let step = Step::new(StepType::FunctionNode, "cloneable").with_input("input data");
550 let cloned = step.clone();
551
552 assert_eq!(step.id.as_str(), cloned.id.as_str());
553 assert_eq!(step.name, cloned.name);
554 assert_eq!(step.input, cloned.input);
555 }
556
557 #[test]
558 fn test_step_serde() {
559 let step = Step::new(StepType::GraphNode, "serializable").with_input("input");
560
561 let json = serde_json::to_string(&step).unwrap();
562 let parsed: Step = serde_json::from_str(&json).unwrap();
563
564 assert_eq!(step.name, parsed.name);
565 assert_eq!(step.step_type, parsed.step_type);
566 assert_eq!(step.input, parsed.input);
567 }
568
569 #[test]
570 fn test_step_serde_field_names() {
571 let step = Step::new(StepType::LlmNode, "test_step").with_input("test input");
573
574 let json = serde_json::to_string(&step).unwrap();
575
576 assert!(json.contains("\"stepId\""), "Should have stepId field");
578 assert!(json.contains("\"stepType\""), "Should have stepType field");
579 assert!(json.contains("\"state\""), "Should have state field");
580 assert!(
581 json.contains("\"durationMs\""),
582 "Should have durationMs field"
583 );
584 assert!(
585 json.contains("\"startedAt\""),
586 "Should have startedAt field"
587 );
588 assert!(json.contains("\"endedAt\""), "Should have endedAt field");
589
590 assert!(
592 !json.contains("\"step_id\""),
593 "Should NOT have step_id field"
594 );
595 assert!(
596 !json.contains("\"step_type\""),
597 "Should NOT have step_type field"
598 );
599 assert!(
600 !json.contains("\"duration_ms\""),
601 "Should NOT have duration_ms field"
602 );
603 assert!(
604 !json.contains("\"started_at\""),
605 "Should NOT have started_at field"
606 );
607 assert!(
608 !json.contains("\"ended_at\""),
609 "Should NOT have ended_at field"
610 );
611 }
612
613 #[test]
614 fn test_step_timestamps() {
615 let mut step = Step::new(StepType::ToolNode, "timestamped");
616 let now = chrono::Utc::now().timestamp_millis();
617
618 step.started_at = Some(now);
619 step.ended_at = Some(now + 1000);
620 step.duration_ms = Some(1000);
621
622 assert_eq!(step.started_at, Some(now));
623 assert_eq!(step.ended_at, Some(now + 1000));
624 assert_eq!(step.duration_ms, Some(1000));
625 }
626
627 #[test]
628 fn test_step_state_modifications() {
629 let mut step = Step::new(StepType::LlmNode, "state_step");
630
631 assert_eq!(step.state, StepState::Pending);
632
633 step.state = StepState::Running;
634 assert_eq!(step.state, StepState::Running);
635
636 step.state = StepState::Completed;
637 step.output = Some("Result".to_string());
638 assert_eq!(step.state, StepState::Completed);
639 assert_eq!(step.output, Some("Result".to_string()));
640 }
641
642 #[test]
643 fn test_step_error_handling() {
644 let mut step = Step::new(StepType::ToolNode, "error_step");
645
646 step.state = StepState::Failed;
647 step.error = Some(ExecutionError::kernel_internal("Test error"));
648
649 assert!(step.error.is_some());
650 }
651
652 #[test]
657 fn test_execution_with_multiple_steps() {
658 let mut exec = Execution::new();
659 exec.state = ExecutionState::Running;
660 exec.started_at = Some(Instant::now());
661
662 for i in 0..5 {
664 let step = Step::new(StepType::FunctionNode, format!("step_{}", i))
665 .with_input(format!("input_{}", i));
666 exec.add_step(step);
667 }
668
669 assert_eq!(exec.steps.len(), 5);
670 assert_eq!(exec.step_order.len(), 5);
671
672 for step_id in &exec.step_order {
674 assert!(exec.get_step(step_id).is_some());
675 }
676 }
677
678 #[test]
679 fn test_nested_execution_structure() {
680 let root = Execution::new();
681 let child1 = root.child();
682 let child2 = root.child();
683
684 assert!(child1.parent.is_some());
686 assert!(child2.parent.is_some());
687 assert_eq!(
688 child1.parent.as_ref().unwrap().parent_id,
689 child2.parent.as_ref().unwrap().parent_id
690 );
691
692 assert_ne!(child1.id.as_str(), child2.id.as_str());
694 }
695
696 #[test]
697 fn test_nested_steps_structure() {
698 let parent_step = Step::new(StepType::GraphNode, "parent");
699 let parent_id = parent_step.id.clone();
700
701 let child1 = Step::nested(&parent_id, StepType::LlmNode, "child1");
702 let child2 = Step::nested(&parent_id, StepType::ToolNode, "child2");
703
704 assert_eq!(
705 child1.parent_step_id.as_ref().unwrap().as_str(),
706 parent_id.as_str()
707 );
708 assert_eq!(
709 child2.parent_step_id.as_ref().unwrap().as_str(),
710 parent_id.as_str()
711 );
712 }
713
714 #[test]
719 fn test_step_with_callable() {
720 let step = Step::new(StepType::GraphNode, "Research Agent")
721 .with_callable("research-agent-v2", CallableType::Agent);
722
723 assert!(step.callable_id.is_some());
724 assert_eq!(step.callable_id.unwrap(), "research-agent-v2");
725 assert!(step.callable_type.is_some());
726 assert_eq!(step.callable_type.unwrap(), CallableType::Agent);
727 }
728
729 #[test]
730 fn test_step_callable_serde() {
731 let step = Step::new(StepType::GraphNode, "Chat Handler")
732 .with_callable("chat-handler", CallableType::Chat);
733
734 let json = serde_json::to_string(&step).unwrap();
735 let parsed: Step = serde_json::from_str(&json).unwrap();
736
737 assert_eq!(parsed.callable_id, Some("chat-handler".to_string()));
738 assert_eq!(parsed.callable_type, Some(CallableType::Chat));
739
740 assert!(
742 json.contains("\"callableId\""),
743 "Should have callableId field"
744 );
745 assert!(
746 json.contains("\"callableType\""),
747 "Should have callableType field"
748 );
749 }
750
751 #[test]
752 fn test_step_callable_none_not_serialized() {
753 let step = Step::new(StepType::LlmNode, "No Callable Info");
754
755 let json = serde_json::to_string(&step).unwrap();
756
757 assert!(
759 !json.contains("callableId"),
760 "Should NOT serialize None callableId"
761 );
762 assert!(
763 !json.contains("callableType"),
764 "Should NOT serialize None callableType"
765 );
766 }
767
768 #[test]
769 fn test_callable_type_display() {
770 assert_eq!(format!("{}", CallableType::Completion), "completion");
771 assert_eq!(format!("{}", CallableType::Chat), "chat");
772 assert_eq!(format!("{}", CallableType::Agent), "agent");
773 assert_eq!(format!("{}", CallableType::Workflow), "workflow");
774 assert_eq!(format!("{}", CallableType::Background), "background");
775 assert_eq!(format!("{}", CallableType::Composite), "composite");
776 assert_eq!(format!("{}", CallableType::Tool), "tool");
777 assert_eq!(format!("{}", CallableType::Custom), "custom");
778 }
779
780 #[test]
781 fn test_callable_type_default() {
782 let default_type = CallableType::default();
783 assert_eq!(default_type, CallableType::Agent);
784 }
785
786 #[test]
787 fn test_callable_type_serde_all_variants() {
788 let variants = vec![
789 CallableType::Completion,
790 CallableType::Chat,
791 CallableType::Agent,
792 CallableType::Workflow,
793 CallableType::Background,
794 CallableType::Composite,
795 CallableType::Tool,
796 CallableType::Custom,
797 ];
798
799 for variant in variants {
800 let json = serde_json::to_string(&variant).unwrap();
801 let parsed: CallableType = serde_json::from_str(&json).unwrap();
802 assert_eq!(parsed, variant);
803 }
804 }
805
806 #[test]
807 fn test_step_chained_builders() {
808 let _parent_id = StepId::from_string("step_parent");
810 let step = Step::new(StepType::GraphNode, "Full Step")
811 .with_input("User request")
812 .with_callable("research-agent", CallableType::Agent)
813 .with_source(StepSource {
814 source_type: StepSourceType::Discovered,
815 triggered_by: Some("step_123".to_string()),
816 reason: Some("LLM suggested sub-task".to_string()),
817 depth: Some(1),
818 spawn_mode: None,
819 });
820
821 assert_eq!(step.name, "Full Step");
822 assert_eq!(step.input, Some("User request".to_string()));
823 assert_eq!(step.callable_id, Some("research-agent".to_string()));
824 assert_eq!(step.callable_type, Some(CallableType::Agent));
825 assert!(step.source.is_some());
826 assert_eq!(
827 step.source.as_ref().unwrap().source_type,
828 StepSourceType::Discovered
829 );
830 }
831}