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