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_core::agent_role::{CostPeriod, MAX_ROLE_NAME_LEN, RoleCostCap};
36pub use ironflow_store::entities::{ApprovalRequirement, Assignee};
39pub use shell::ShellConfig;
40pub use workflow::{WorkflowOptions, WorkflowStepConfig};
41
42use ironflow_core::retry::RetryPolicy;
43use ironflow_store::entities::StepKind;
44use serde::{Deserialize, Serialize};
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(tag = "type", rename_all = "snake_case")]
62#[allow(clippy::large_enum_variant)]
65pub enum StepConfig {
66 Shell(ShellConfig),
68 Http(HttpConfig),
70 Agent(AgentStepConfig),
72 Workflow(WorkflowStepConfig),
74 Approval(ApprovalConfig),
76 Decision(DecisionConfig),
78 Delay(DelayConfig),
80}
81
82impl StepConfig {
83 pub fn allow_failure(&self) -> bool {
97 match self {
98 StepConfig::Shell(c) => c.allow_failure,
99 StepConfig::Http(c) => c.allow_failure,
100 StepConfig::Agent(c) => c.allow_failure,
101 StepConfig::Workflow(_)
102 | StepConfig::Approval(_)
103 | StepConfig::Decision(_)
104 | StepConfig::Delay(_) => false,
105 }
106 }
107
108 pub fn declared_outputs(&self) -> &[ArtifactOutput] {
122 match self {
123 StepConfig::Shell(c) => &c.outputs,
124 _ => &[],
125 }
126 }
127
128 pub fn retry(&self) -> Option<&RetryPolicy> {
143 match self {
144 StepConfig::Shell(c) => c.retry.as_ref(),
145 StepConfig::Http(c) => c.retry.as_ref(),
146 StepConfig::Agent(c) => c.retry.as_ref(),
147 StepConfig::Workflow(c) => c.retry.as_ref(),
148 StepConfig::Approval(_) | StepConfig::Decision(_) | StepConfig::Delay(_) => None,
149 }
150 }
151
152 pub fn kind(&self) -> StepKind {
164 match self {
165 StepConfig::Shell(_) => StepKind::Shell,
166 StepConfig::Http(_) => StepKind::Http,
167 StepConfig::Agent(_) => StepKind::Agent,
168 StepConfig::Workflow(_) => StepKind::Workflow,
169 StepConfig::Approval(_) => StepKind::Approval,
170 StepConfig::Decision(_) => StepKind::Decision,
171 StepConfig::Delay(_) => StepKind::Custom("delay".to_string()),
172 }
173 }
174}
175
176impl From<ShellConfig> for StepConfig {
177 fn from(c: ShellConfig) -> Self {
178 StepConfig::Shell(c)
179 }
180}
181
182impl From<HttpConfig> for StepConfig {
183 fn from(c: HttpConfig) -> Self {
184 StepConfig::Http(c)
185 }
186}
187
188impl From<AgentStepConfig> for StepConfig {
189 fn from(c: AgentStepConfig) -> Self {
190 StepConfig::Agent(c)
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use ironflow_core::retry::RetryPolicy;
197
198 use super::*;
199
200 #[test]
201 fn retry_accessor_returns_policy_for_each_variant() {
202 let shell = StepConfig::Shell(ShellConfig::new("echo").retry_policy(RetryPolicy::new(2)));
203 assert_eq!(shell.retry().unwrap().max_retries(), 2);
204
205 let http = StepConfig::Http(HttpConfig::get("http://x").retry_policy(RetryPolicy::new(3)));
206 assert_eq!(http.retry().unwrap().max_retries(), 3);
207
208 let workflow = StepConfig::Workflow(
209 WorkflowStepConfig::new("build", serde_json::json!({}))
210 .retry_policy(RetryPolicy::new(4)),
211 );
212 assert_eq!(workflow.retry().unwrap().max_retries(), 4);
213
214 let agent = StepConfig::Agent(AgentStepConfig::new("test"));
215 assert!(agent.retry().is_none());
216
217 let approval = StepConfig::Approval(ApprovalConfig::new("ok?"));
218 assert!(approval.retry().is_none());
219 }
220
221 #[test]
222 fn serde_roundtrip() {
223 let configs = vec![
224 StepConfig::Shell(ShellConfig::new("echo test")),
225 StepConfig::Http(HttpConfig::get("http://example.com")),
226 StepConfig::Agent(AgentStepConfig::new("summarize")),
227 StepConfig::Workflow(WorkflowStepConfig::new("build", serde_json::json!({}))),
228 StepConfig::Approval(ApprovalConfig::new("Deploy to production?")),
229 StepConfig::Delay(DelayConfig::from_secs(60)),
230 ];
231
232 for config in configs {
233 let json = serde_json::to_string(&config).expect("serialize");
234 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
235 let json2 = serde_json::to_string(&back).expect("serialize2");
236 assert_eq!(json, json2);
237 }
238 }
239
240 #[test]
241 fn approval_with_escalation_roundtrips() {
242 let config = StepConfig::Approval(
243 ApprovalConfig::new("Deploy to production?")
244 .with_deadline_secs(3600)
245 .assigned_to(Assignee::group("release-managers"))
246 .on_timeout(EscalationPolicy::Chain(vec![
247 EscalationPolicy::Notify(vec![NotificationTarget::Webhook {
248 url: "https://example.com/sla".to_string(),
249 }]),
250 EscalationPolicy::AutoReject,
251 ])),
252 );
253
254 let json = serde_json::to_string(&config).expect("serialize");
255 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
256 let json2 = serde_json::to_string(&back).expect("serialize2");
257 assert_eq!(json, json2);
258
259 let StepConfig::Approval(approval) = back else {
260 panic!("expected an approval config");
261 };
262 assert_eq!(approval.effective_deadline_secs(), Some(3600));
263 assert_eq!(
264 approval.assignee(),
265 Some(&Assignee::group("release-managers"))
266 );
267 assert_eq!(approval.effective_policy().len(), 2);
268 }
269}