ironflow_engine/executor/mod.rs
1//! Step executor — reconstructs operations from configs and runs them.
2//!
3//! Each step type (shell, HTTP, agent) has its own executor implementing
4//! the [`StepExecutor`] trait. The [`execute_step_config`] function dispatches
5//! to the appropriate executor based on the [`StepConfig`] variant.
6
7mod agent;
8mod http;
9mod shell;
10
11use std::future::Future;
12use std::sync::Arc;
13
14use rust_decimal::Decimal;
15use serde_json::Value;
16use uuid::Uuid;
17
18use ironflow_core::provider::AgentProvider;
19
20use crate::config::StepConfig;
21use crate::error::EngineError;
22
23pub use agent::AgentExecutor;
24pub use http::HttpExecutor;
25pub use shell::ShellExecutor;
26
27/// Result of executing a single step.
28#[derive(Debug, Clone)]
29pub struct StepOutput {
30 /// Serialized output (stdout for shell, body for http, value for agent).
31 pub output: Value,
32 /// Wall-clock duration in milliseconds.
33 pub duration_ms: u64,
34 /// Cost in USD (agent steps only).
35 pub cost_usd: Decimal,
36 /// Input token count (agent steps only).
37 pub input_tokens: Option<u64>,
38 /// Output token count (agent steps only).
39 pub output_tokens: Option<u64>,
40}
41
42/// Result of a single step within a [`parallel`](crate::context::WorkflowContext::parallel) batch.
43#[derive(Debug, Clone)]
44pub struct ParallelStepResult {
45 /// The step name (same as provided to `parallel()`).
46 pub name: String,
47 /// The step execution output.
48 pub output: StepOutput,
49 /// The step ID in the store (for dependency tracking).
50 pub step_id: Uuid,
51}
52
53/// Trait for step executors.
54///
55/// Each step type implements this trait to execute its specific operation
56/// and return a [`StepOutput`].
57pub trait StepExecutor: Send + Sync {
58 /// Execute the step and return structured output.
59 ///
60 /// # Errors
61 ///
62 /// Returns [`EngineError`] if the operation fails.
63 fn execute(
64 &self,
65 provider: &Arc<dyn AgentProvider>,
66 ) -> impl Future<Output = Result<StepOutput, EngineError>> + Send;
67}
68
69/// Execute a [`StepConfig`] and return structured output.
70///
71/// # Errors
72///
73/// Returns [`EngineError::Operation`] if the operation fails.
74///
75/// # Examples
76///
77/// ```no_run
78/// use ironflow_engine::config::{StepConfig, ShellConfig};
79/// use ironflow_engine::executor::execute_step_config;
80/// use ironflow_core::provider::AgentProvider;
81/// use ironflow_core::providers::claude::ClaudeCodeProvider;
82/// use std::sync::Arc;
83///
84/// # async fn example() -> Result<(), ironflow_engine::error::EngineError> {
85/// let provider: Arc<dyn AgentProvider> = Arc::new(ClaudeCodeProvider::new());
86/// let config = StepConfig::Shell(ShellConfig::new("echo hello"));
87/// let output = execute_step_config(&config, &provider).await?;
88/// # Ok(())
89/// # }
90/// ```
91pub async fn execute_step_config(
92 config: &StepConfig,
93 provider: &Arc<dyn AgentProvider>,
94) -> Result<StepOutput, EngineError> {
95 match config {
96 StepConfig::Shell(cfg) => ShellExecutor::new(cfg).execute(provider).await,
97 StepConfig::Http(cfg) => HttpExecutor::new(cfg).execute(provider).await,
98 StepConfig::Agent(cfg) => AgentExecutor::new(cfg).execute(provider).await,
99 StepConfig::Workflow(_) => Err(EngineError::StepConfig(
100 "workflow steps are executed by WorkflowContext, not the executor".to_string(),
101 )),
102 }
103}