ironflow_engine/config/
mod.rs1mod agent;
8mod agent_role;
9mod approval;
10mod approvers;
11mod artifact;
12mod decision;
13pub mod delay;
14mod escalation;
15mod http;
16mod human_input;
17mod shell;
18mod workflow;
19
20pub use agent::{
21 AgentStep, AgentStepConfig, DEFAULT_RESUME_PROMPT, DEFAULT_UNTRUSTED_INSTRUCTION, Prompt, Tool,
22 ToolProfile,
23};
24pub use agent_role::{AgentRole, AgentTask};
25pub use approval::ApprovalConfig;
26pub use approvers::Approvers;
27pub use artifact::{ArtifactInput, ArtifactOutput, ArtifactRef};
28pub use decision::{DEFAULT_DECISION_MODEL, DecisionConfig, NoAnswers};
29pub use delay::DelayConfig;
30pub use escalation::{EscalationPolicy, NotificationTarget};
31pub use http::HttpConfig;
32pub use human_input::{HUMAN_INPUT_SCHEMA_KEY, HumanInputConfig};
33pub use ironflow_store::entities::{ApprovalRequirement, Assignee};
36pub use shell::ShellConfig;
37pub use workflow::{WorkflowOptions, WorkflowStepConfig};
38
39use ironflow_core::retry::RetryPolicy;
40use ironflow_store::entities::StepKind;
41use serde::{Deserialize, Serialize};
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(tag = "type", rename_all = "snake_case")]
59#[allow(clippy::large_enum_variant)]
62pub enum StepConfig {
63 Shell(ShellConfig),
65 Http(HttpConfig),
67 Agent(AgentStepConfig),
69 Workflow(WorkflowStepConfig),
71 Approval(ApprovalConfig),
73 Decision(DecisionConfig),
75 Delay(DelayConfig),
77}
78
79impl StepConfig {
80 pub fn allow_failure(&self) -> bool {
94 match self {
95 StepConfig::Shell(c) => c.allow_failure,
96 StepConfig::Http(c) => c.allow_failure,
97 StepConfig::Agent(c) => c.allow_failure,
98 StepConfig::Workflow(_)
99 | StepConfig::Approval(_)
100 | StepConfig::Decision(_)
101 | StepConfig::Delay(_) => false,
102 }
103 }
104
105 pub fn declared_outputs(&self) -> &[ArtifactOutput] {
119 match self {
120 StepConfig::Shell(c) => &c.outputs,
121 _ => &[],
122 }
123 }
124
125 pub fn retry(&self) -> Option<&RetryPolicy> {
140 match self {
141 StepConfig::Shell(c) => c.retry.as_ref(),
142 StepConfig::Http(c) => c.retry.as_ref(),
143 StepConfig::Agent(c) => c.retry.as_ref(),
144 StepConfig::Workflow(c) => c.retry.as_ref(),
145 StepConfig::Approval(_) | StepConfig::Decision(_) | StepConfig::Delay(_) => None,
146 }
147 }
148
149 pub fn kind(&self) -> StepKind {
161 match self {
162 StepConfig::Shell(_) => StepKind::Shell,
163 StepConfig::Http(_) => StepKind::Http,
164 StepConfig::Agent(_) => StepKind::Agent,
165 StepConfig::Workflow(_) => StepKind::Workflow,
166 StepConfig::Approval(_) => StepKind::Approval,
167 StepConfig::Decision(_) => StepKind::Decision,
168 StepConfig::Delay(_) => StepKind::Custom("delay".to_string()),
169 }
170 }
171}
172
173impl From<ShellConfig> for StepConfig {
174 fn from(c: ShellConfig) -> Self {
175 StepConfig::Shell(c)
176 }
177}
178
179impl From<HttpConfig> for StepConfig {
180 fn from(c: HttpConfig) -> Self {
181 StepConfig::Http(c)
182 }
183}
184
185impl From<AgentStepConfig> for StepConfig {
186 fn from(c: AgentStepConfig) -> Self {
187 StepConfig::Agent(c)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use ironflow_core::retry::RetryPolicy;
194
195 use super::*;
196
197 #[test]
198 fn retry_accessor_returns_policy_for_each_variant() {
199 let shell = StepConfig::Shell(ShellConfig::new("echo").retry_policy(RetryPolicy::new(2)));
200 assert_eq!(shell.retry().unwrap().max_retries(), 2);
201
202 let http = StepConfig::Http(HttpConfig::get("http://x").retry_policy(RetryPolicy::new(3)));
203 assert_eq!(http.retry().unwrap().max_retries(), 3);
204
205 let workflow = StepConfig::Workflow(
206 WorkflowStepConfig::new("build", serde_json::json!({}))
207 .retry_policy(RetryPolicy::new(4)),
208 );
209 assert_eq!(workflow.retry().unwrap().max_retries(), 4);
210
211 let agent = StepConfig::Agent(AgentStepConfig::new("test"));
212 assert!(agent.retry().is_none());
213
214 let approval = StepConfig::Approval(ApprovalConfig::new("ok?"));
215 assert!(approval.retry().is_none());
216 }
217
218 #[test]
219 fn serde_roundtrip() {
220 let configs = vec![
221 StepConfig::Shell(ShellConfig::new("echo test")),
222 StepConfig::Http(HttpConfig::get("http://example.com")),
223 StepConfig::Agent(AgentStepConfig::new("summarize")),
224 StepConfig::Workflow(WorkflowStepConfig::new("build", serde_json::json!({}))),
225 StepConfig::Approval(ApprovalConfig::new("Deploy to production?")),
226 StepConfig::Delay(DelayConfig::from_secs(60)),
227 ];
228
229 for config in configs {
230 let json = serde_json::to_string(&config).expect("serialize");
231 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
232 let json2 = serde_json::to_string(&back).expect("serialize2");
233 assert_eq!(json, json2);
234 }
235 }
236
237 #[test]
238 fn approval_with_escalation_roundtrips() {
239 let config = StepConfig::Approval(
240 ApprovalConfig::new("Deploy to production?")
241 .with_deadline_secs(3600)
242 .assigned_to(Assignee::group("release-managers"))
243 .on_timeout(EscalationPolicy::Chain(vec![
244 EscalationPolicy::Notify(vec![NotificationTarget::Webhook {
245 url: "https://example.com/sla".to_string(),
246 }]),
247 EscalationPolicy::AutoReject,
248 ])),
249 );
250
251 let json = serde_json::to_string(&config).expect("serialize");
252 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
253 let json2 = serde_json::to_string(&back).expect("serialize2");
254 assert_eq!(json, json2);
255
256 let StepConfig::Approval(approval) = back else {
257 panic!("expected an approval config");
258 };
259 assert_eq!(approval.effective_deadline_secs(), Some(3600));
260 assert_eq!(
261 approval.assignee(),
262 Some(&Assignee::group("release-managers"))
263 );
264 assert_eq!(approval.effective_policy().len(), 2);
265 }
266}