simple_agents_workflow/yaml_runner/
output.rs1use super::events::WorkflowEvent;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct WorkflowRunOutput {
9 pub workflow_id: String,
11 pub entry_node: String,
13 pub trace: Vec<String>,
15 pub outputs: BTreeMap<String, Value>,
17 pub terminal_node: String,
19 pub terminal_output: Option<Value>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub metadata: Option<RunMetadata>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub events: Option<Vec<WorkflowEvent>>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct RunMetadata {
32 pub total_elapsed_ms: u128,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub ttft_ms: Option<u128>,
37 pub total_input_tokens: u64,
39 pub total_output_tokens: u64,
41 pub total_tokens: u64,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub total_reasoning_tokens: Option<u64>,
46 pub tokens_per_second: f64,
48 pub step_details: Vec<StepTiming>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub trace_id: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct StepTiming {
58 pub node_id: String,
60 pub node_type: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub model: Option<String>,
65 pub elapsed_ms: u128,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub input_tokens: Option<u64>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub output_tokens: Option<u64>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub total_tokens: Option<u64>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub reasoning_tokens: Option<u64>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub ttft_ms: Option<u128>,
82}
83
84#[derive(Debug, Clone, Default, Serialize, Deserialize)]
86pub struct TokenTotals {
87 pub input_tokens: u64,
89 pub output_tokens: u64,
91 pub total_tokens: u64,
93 pub reasoning_tokens: Option<u64>,
95}
96
97impl TokenTotals {
98 pub fn add(&mut self, input: u64, output: u64, total: u64, reasoning: Option<u64>) {
100 self.input_tokens += input;
101 self.output_tokens += output;
102 self.total_tokens += total;
103 if let Some(r) = reasoning {
104 *self.reasoning_tokens.get_or_insert(0) += r;
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_token_totals_add() {
115 let mut t = TokenTotals::default();
116 t.add(10, 20, 30, Some(5));
117 t.add(5, 10, 15, None);
118 assert_eq!(t.input_tokens, 15);
119 assert_eq!(t.output_tokens, 30);
120 assert_eq!(t.total_tokens, 45);
121 assert_eq!(t.reasoning_tokens, Some(5));
122 }
123
124 #[test]
125 fn test_output_serialization() {
126 let output = WorkflowRunOutput {
127 workflow_id: "test".into(),
128 entry_node: "start".into(),
129 trace: vec!["start".into()],
130 outputs: BTreeMap::new(),
131 terminal_node: "start".into(),
132 terminal_output: Some(serde_json::json!("done")),
133 metadata: None,
134 events: None,
135 };
136 let json = serde_json::to_string(&output).unwrap();
137 assert!(json.contains("test"));
138 }
139}