Skip to main content

simple_agents_workflow/yaml_runner/
runner.rs

1use std::path::Path;
2
3use serde_json::{json, Value};
4use simple_agents_core::SimpleAgentsClient;
5
6use super::{
7    load_workflow_yaml_file,
8    run_workflow_yaml_with_client_and_custom_worker_and_events_and_options,
9    run_workflow_yaml_with_custom_worker_and_events_and_options, YamlWorkflow,
10    YamlWorkflowCustomWorkerExecutor, YamlWorkflowEventSink, YamlWorkflowLlmExecutor,
11    YamlWorkflowRunError, YamlWorkflowRunOptions, YamlWorkflowRunOutput,
12};
13
14#[derive(Clone, Copy)]
15enum WorkflowRunnerExecutor<'a> {
16    Llm(&'a dyn YamlWorkflowLlmExecutor),
17    Client(&'a SimpleAgentsClient),
18}
19
20#[derive(Clone, Copy)]
21enum WorkflowRunnerSource<'a> {
22    File(&'a Path),
23    Inline(&'a YamlWorkflow),
24}
25
26/// Builder-style runner for YAML workflow execution.
27///
28/// This is the preferred additive API for configuring workflow runs while keeping
29/// legacy `run_*` helpers as compatibility adapters.
30pub struct WorkflowRunner<'a> {
31    source: WorkflowRunnerSource<'a>,
32    workflow_input: Option<&'a Value>,
33    email_text: Option<&'a str>,
34    executor: Option<WorkflowRunnerExecutor<'a>>,
35    custom_worker: Option<&'a dyn YamlWorkflowCustomWorkerExecutor>,
36    event_sink: Option<&'a dyn YamlWorkflowEventSink>,
37    options: Option<&'a YamlWorkflowRunOptions>,
38}
39
40impl<'a> WorkflowRunner<'a> {
41    pub fn from_file(workflow_path: &'a Path) -> Self {
42        Self {
43            source: WorkflowRunnerSource::File(workflow_path),
44            workflow_input: None,
45            email_text: None,
46            executor: None,
47            custom_worker: None,
48            event_sink: None,
49            options: None,
50        }
51    }
52
53    pub fn from_workflow(workflow: &'a YamlWorkflow) -> Self {
54        Self {
55            source: WorkflowRunnerSource::Inline(workflow),
56            workflow_input: None,
57            email_text: None,
58            executor: None,
59            custom_worker: None,
60            event_sink: None,
61            options: None,
62        }
63    }
64
65    pub fn with_input(mut self, workflow_input: &'a Value) -> Self {
66        self.workflow_input = Some(workflow_input);
67        self
68    }
69
70    pub fn with_email_text(mut self, email_text: &'a str) -> Self {
71        self.email_text = Some(email_text);
72        self
73    }
74
75    pub fn with_executor(mut self, executor: &'a dyn YamlWorkflowLlmExecutor) -> Self {
76        self.executor = Some(WorkflowRunnerExecutor::Llm(executor));
77        self
78    }
79
80    pub fn with_client(mut self, client: &'a SimpleAgentsClient) -> Self {
81        self.executor = Some(WorkflowRunnerExecutor::Client(client));
82        self
83    }
84
85    pub fn with_custom_worker(
86        mut self,
87        custom_worker: Option<&'a dyn YamlWorkflowCustomWorkerExecutor>,
88    ) -> Self {
89        self.custom_worker = custom_worker;
90        self
91    }
92
93    pub fn with_event_sink(mut self, event_sink: Option<&'a dyn YamlWorkflowEventSink>) -> Self {
94        self.event_sink = event_sink;
95        self
96    }
97
98    pub fn with_options(mut self, options: &'a YamlWorkflowRunOptions) -> Self {
99        self.options = Some(options);
100        self
101    }
102
103    pub async fn run(self) -> Result<YamlWorkflowRunOutput, YamlWorkflowRunError> {
104        let fallback_input;
105        let workflow_input = if let Some(workflow_input) = self.workflow_input {
106            workflow_input
107        } else if let Some(email_text) = self.email_text {
108            fallback_input = json!({ "email_text": email_text });
109            &fallback_input
110        } else {
111            return Err(YamlWorkflowRunError::InvalidInput {
112                message: "workflow input is required; call with_input(...) or with_email_text(...)"
113                    .to_string(),
114            });
115        };
116
117        let default_options;
118        let options = if let Some(options) = self.options {
119            options
120        } else {
121            default_options = YamlWorkflowRunOptions::default();
122            &default_options
123        };
124
125        let executor = self
126            .executor
127            .ok_or_else(|| YamlWorkflowRunError::InvalidInput {
128                message:
129                    "workflow executor is required; call with_executor(...) or with_client(...)"
130                        .to_string(),
131            })?;
132
133        match (self.source, executor) {
134            (WorkflowRunnerSource::File(path), WorkflowRunnerExecutor::Llm(executor)) => {
135                let (_, workflow) = load_workflow_yaml_file(path)?;
136                run_workflow_yaml_with_custom_worker_and_events_and_options(
137                    &workflow,
138                    workflow_input,
139                    executor,
140                    self.custom_worker,
141                    self.event_sink,
142                    options,
143                )
144                .await
145            }
146            (WorkflowRunnerSource::File(path), WorkflowRunnerExecutor::Client(client)) => {
147                let (_, workflow) = load_workflow_yaml_file(path)?;
148                run_workflow_yaml_with_client_and_custom_worker_and_events_and_options(
149                    &workflow,
150                    workflow_input,
151                    client,
152                    self.custom_worker,
153                    self.event_sink,
154                    options,
155                )
156                .await
157            }
158            (WorkflowRunnerSource::Inline(workflow), WorkflowRunnerExecutor::Llm(executor)) => {
159                run_workflow_yaml_with_custom_worker_and_events_and_options(
160                    workflow,
161                    workflow_input,
162                    executor,
163                    self.custom_worker,
164                    self.event_sink,
165                    options,
166                )
167                .await
168            }
169            (WorkflowRunnerSource::Inline(workflow), WorkflowRunnerExecutor::Client(client)) => {
170                run_workflow_yaml_with_client_and_custom_worker_and_events_and_options(
171                    workflow,
172                    workflow_input,
173                    client,
174                    self.custom_worker,
175                    self.event_sink,
176                    options,
177                )
178                .await
179            }
180        }
181    }
182}