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 pub fn exit_code(&self) -> i32 {
93 match self {
94 StepOutput::Cmd(o) => o.exit_code,
95 _ => 0,
96 }
97 }
98
99 pub fn success(&self) -> bool {
101 match self {
102 StepOutput::Cmd(o) => o.exit_code == 0,
103 StepOutput::Gate(o) => o.passed,
104 _ => true,
105 }
106 }
107
108 pub fn lines(&self) -> Vec<&str> {
110 self.text()
111 .lines()
112 .filter(|l| !l.is_empty())
113 .collect()
114 }
115
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct CmdOutput {
120 pub stdout: String,
121 pub stderr: String,
122 pub exit_code: i32,
123 #[serde(
124 serialize_with = "serialize_duration",
125 deserialize_with = "deserialize_duration"
126 )]
127 pub duration: Duration,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct AgentOutput {
132 pub response: String,
133 pub session_id: Option<String>,
134 pub stats: AgentStats,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, Default)]
138pub struct AgentStats {
139 #[serde(
140 serialize_with = "serialize_duration",
141 deserialize_with = "deserialize_duration"
142 )]
143 pub duration: Duration,
144 pub input_tokens: u64,
145 pub output_tokens: u64,
146 pub cost_usd: f64,
147 pub turns: u32,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ChatOutput {
152 pub response: String,
153 pub model: String,
154 pub input_tokens: u64,
155 pub output_tokens: u64,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct GateOutput {
160 pub passed: bool,
161 pub message: Option<String>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct ScopeOutput {
166 pub iterations: Vec<IterationOutput>,
167 pub final_value: Option<Box<StepOutput>>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct IterationOutput {
172 pub index: usize,
173 pub output: StepOutput,
174}
175
176fn serialize_duration<S>(d: &Duration, s: S) -> Result<S::Ok, S::Error>
177where
178 S: serde::Serializer,
179{
180 s.serialize_f64(d.as_secs_f64())
181}
182
183fn deserialize_duration<'de, D>(d: D) -> Result<Duration, D::Error>
184where
185 D: serde::Deserializer<'de>,
186{
187 let secs = f64::deserialize(d)?;
188 Ok(Duration::from_secs_f64(secs))
189}