1pub mod agent;
2pub mod call;
3pub mod chat;
4pub mod cmd;
5pub mod gate;
6pub mod map;
7pub mod parallel;
8pub mod repeat;
9pub mod script;
10pub mod template_step;
11
12use std::sync::Arc;
13use std::time::Duration;
14
15use async_trait::async_trait;
16use serde::{Deserialize, Serialize};
17use tokio::sync::Mutex;
18
19use crate::config::StepConfig;
20use crate::engine::context::Context;
21use crate::error::StepError;
22use crate::sandbox::docker::DockerSandbox;
23use crate::workflow::schema::StepDef;
24
25pub type SharedSandbox = Option<Arc<Mutex<DockerSandbox>>>;
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum ParsedValue {
32 Text(String),
33 Json(serde_json::Value),
34 Integer(i64),
35 Lines(Vec<String>),
36 Boolean(bool),
37}
38
39#[async_trait]
41pub trait StepExecutor: Send + Sync {
42 async fn execute(
43 &self,
44 step_def: &StepDef,
45 config: &StepConfig,
46 context: &Context,
47 ) -> Result<StepOutput, StepError>;
48}
49
50#[async_trait]
52pub trait SandboxAwareExecutor: Send + Sync {
53 async fn execute_sandboxed(
54 &self,
55 step_def: &StepDef,
56 config: &StepConfig,
57 context: &Context,
58 sandbox: &SharedSandbox,
59 ) -> Result<StepOutput, StepError>;
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(tag = "type", rename_all = "snake_case")]
65pub enum StepOutput {
66 Cmd(CmdOutput),
67 Agent(AgentOutput),
68 Chat(ChatOutput),
69 Gate(GateOutput),
70 Scope(ScopeOutput),
71 Empty,
72}
73
74impl StepOutput {
75 pub fn text(&self) -> &str {
77 match self {
78 StepOutput::Cmd(o) => &o.stdout,
79 StepOutput::Agent(o) => &o.response,
80 StepOutput::Chat(o) => &o.response,
81 StepOutput::Gate(o) => o.message.as_deref().unwrap_or(""),
82 StepOutput::Scope(o) => o
83 .final_value
84 .as_ref()
85 .map(|v| v.text())
86 .unwrap_or(""),
87 StepOutput::Empty => "",
88 }
89 }
90
91 #[allow(dead_code)]
93 pub fn exit_code(&self) -> i32 {
94 match self {
95 StepOutput::Cmd(o) => o.exit_code,
96 _ => 0,
97 }
98 }
99
100 #[allow(dead_code)]
102 pub fn success(&self) -> bool {
103 match self {
104 StepOutput::Cmd(o) => o.exit_code == 0,
105 StepOutput::Gate(o) => o.passed,
106 _ => true,
107 }
108 }
109
110 #[allow(dead_code)]
112 pub fn lines(&self) -> Vec<&str> {
113 self.text()
114 .lines()
115 .filter(|l| !l.is_empty())
116 .collect()
117 }
118
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct CmdOutput {
123 pub stdout: String,
124 pub stderr: String,
125 pub exit_code: i32,
126 #[serde(
127 serialize_with = "serialize_duration",
128 deserialize_with = "deserialize_duration"
129 )]
130 pub duration: Duration,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct AgentOutput {
135 pub response: String,
136 pub session_id: Option<String>,
137 pub stats: AgentStats,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, Default)]
141pub struct AgentStats {
142 #[serde(
143 serialize_with = "serialize_duration",
144 deserialize_with = "deserialize_duration"
145 )]
146 pub duration: Duration,
147 pub input_tokens: u64,
148 pub output_tokens: u64,
149 pub cost_usd: f64,
150 pub turns: u32,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ChatOutput {
155 pub response: String,
156 pub model: String,
157 pub input_tokens: u64,
158 pub output_tokens: u64,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct GateOutput {
163 pub passed: bool,
164 pub message: Option<String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct ScopeOutput {
169 pub iterations: Vec<IterationOutput>,
170 pub final_value: Option<Box<StepOutput>>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct IterationOutput {
175 pub index: usize,
176 pub output: StepOutput,
177}
178
179fn serialize_duration<S>(d: &Duration, s: S) -> Result<S::Ok, S::Error>
180where
181 S: serde::Serializer,
182{
183 s.serialize_f64(d.as_secs_f64())
184}
185
186fn deserialize_duration<'de, D>(d: D) -> Result<Duration, D::Error>
187where
188 D: serde::Deserializer<'de>,
189{
190 let secs = f64::deserialize(d)?;
191 Ok(Duration::from_secs_f64(secs))
192}