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 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
19 pub globals: BTreeMap<String, Value>,
20 pub terminal_node: String,
22 pub terminal_output: Option<Value>,
24 pub status: String,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub human_request: Option<Value>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub metadata: Option<RunMetadata>,
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub events: Option<Vec<WorkflowEvent>>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct RunMetadata {
40 pub total_elapsed_ms: u128,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub ttft_ms: Option<u128>,
45 pub total_input_tokens: u64,
47 pub total_output_tokens: u64,
49 pub total_tokens: u64,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub total_reasoning_tokens: Option<u64>,
54 pub tokens_per_second: f64,
56 pub step_details: Vec<StepTiming>,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub trace_id: Option<String>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct StepTiming {
66 pub node_id: String,
68 pub node_type: String,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub model: Option<String>,
73 pub elapsed_ms: u128,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub input_tokens: Option<u64>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub output_tokens: Option<u64>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub total_tokens: Option<u64>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub reasoning_tokens: Option<u64>,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub ttft_ms: Option<u128>,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct TokenTotals {
95 pub input_tokens: u64,
97 pub output_tokens: u64,
99 pub total_tokens: u64,
101 pub reasoning_tokens: Option<u64>,
103}
104
105impl TokenTotals {
106 pub fn add(&mut self, input: u64, output: u64, total: u64, reasoning: Option<u64>) {
108 self.input_tokens += input;
109 self.output_tokens += output;
110 self.total_tokens += total;
111 if let Some(r) = reasoning {
112 *self.reasoning_tokens.get_or_insert(0) += r;
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_token_totals_add() {
123 let mut t = TokenTotals::default();
124 t.add(10, 20, 30, Some(5));
125 t.add(5, 10, 15, None);
126 assert_eq!(t.input_tokens, 15);
127 assert_eq!(t.output_tokens, 30);
128 assert_eq!(t.total_tokens, 45);
129 assert_eq!(t.reasoning_tokens, Some(5));
130 }
131
132 #[test]
133 fn test_output_serialization() {
134 let output = WorkflowRunOutput {
135 workflow_id: "test".into(),
136 entry_node: "start".into(),
137 trace: vec!["start".into()],
138 outputs: BTreeMap::new(),
139 globals: BTreeMap::new(),
140 terminal_node: "start".into(),
141 terminal_output: Some(serde_json::json!("done")),
142 status: "completed".into(),
143 human_request: None,
144 metadata: None,
145 events: None,
146 };
147 let json = serde_json::to_string(&output).unwrap();
148 assert!(json.contains("test"));
149 }
150}