1use std::collections::HashMap;
2
3use atman_dsl::ast::{File, FlowDecl};
4
5use crate::error::RuntimeError;
6use crate::event::{Event, EventSink, FlowRunId, FlowStatus, TurnId};
7use crate::exec::exec_flow_with_siblings;
8use crate::provider::ProviderRegistry;
9use crate::session::Session;
10use crate::tool::{ToolCtx, ToolRegistry};
11use crate::value::Value;
12
13#[derive(Clone)]
14pub struct Executor {
15 pub tools: ToolRegistry,
16 pub providers: ProviderRegistry,
17 pub events: EventSink,
18 pub tool_ctx: ToolCtx,
19 pub safety: Option<crate::safety::SafetyConfig>,
20 pub source_dir: Option<std::path::PathBuf>,
22}
23
24impl Executor {
25 pub fn new() -> Self {
26 Self {
27 tools: ToolRegistry::new(),
28 providers: ProviderRegistry::new(),
29 events: EventSink::new(),
30 tool_ctx: ToolCtx::new(),
31 safety: None,
32 source_dir: None,
33 }
34 }
35
36 pub fn with_events(events: EventSink) -> Self {
37 Self {
38 tools: ToolRegistry::new(),
39 providers: ProviderRegistry::new(),
40 events,
41 tool_ctx: ToolCtx::new(),
42 safety: None,
43 source_dir: None,
44 }
45 }
46
47 pub fn with_safety(mut self, safety: crate::safety::SafetyConfig) -> Self {
48 self.safety = Some(safety);
49 self
50 }
51
52 pub async fn run(
53 &self,
54 file: &File,
55 flow_name: &str,
56 args: Vec<(String, Value)>,
57 ) -> Result<Value, RuntimeError> {
58 self.run_in_turn(file, flow_name, args, None, None).await
59 }
60
61 pub async fn run_in_turn(
62 &self,
63 file: &File,
64 flow_name: &str,
65 args: Vec<(String, Value)>,
66 turn_id: Option<TurnId>,
67 session: Option<std::sync::Arc<Session>>,
68 ) -> Result<Value, RuntimeError> {
69 self.run_in_turn_with_run_id(file, flow_name, args, turn_id, session, None)
70 .await
71 }
72
73 pub async fn run_in_turn_with_run_id(
74 &self,
75 file: &File,
76 flow_name: &str,
77 args: Vec<(String, Value)>,
78 turn_id: Option<TurnId>,
79 session: Option<std::sync::Arc<Session>>,
80 first_run_id: Option<FlowRunId>,
81 ) -> Result<Value, RuntimeError> {
82 let flows: HashMap<_, _> = file
83 .flows
84 .iter()
85 .map(|f| (f.name.name.clone(), f.clone()))
86 .collect();
87 let mut current = flow_name.to_string();
88 let mut current_args = args;
89 let mut next_run_id = first_run_id;
90 for _ in 0..5 {
91 let flow = flows
92 .get(¤t)
93 .ok_or_else(|| RuntimeError::UndefinedTool(format!("flow `{current}`")))?;
94 match self
95 .run_flow(
96 flow,
97 current_args,
98 &flows,
99 turn_id.clone(),
100 session.clone(),
101 next_run_id.take(),
102 )
103 .await
104 {
105 Err(RuntimeError::Redirect(target)) => {
106 current = target;
107 current_args = Vec::new();
108 continue;
109 }
110 other => return other,
111 }
112 }
113 Err(RuntimeError::ToolFailed(
114 "redirect chain exceeded max depth (5)".into(),
115 ))
116 }
117
118 async fn run_flow(
119 &self,
120 flow: &FlowDecl,
121 args: Vec<(String, Value)>,
122 flows: &HashMap<String, FlowDecl>,
123 turn_id: Option<TurnId>,
124 session: Option<std::sync::Arc<Session>>,
125 run_id: Option<FlowRunId>,
126 ) -> Result<Value, RuntimeError> {
127 let run_id = run_id.unwrap_or_else(FlowRunId::now);
128 let flow_cancel = session
129 .as_ref()
130 .map(|s| s.flow_cancel_token())
131 .unwrap_or_default();
132 let task_id = self.tool_ctx.task_registry.as_ref().map(|tr| {
133 tr.register(
134 crate::task_registry::TaskKind::Flow,
135 flow.name.name.clone(),
136 run_id.0.to_string(),
137 self.tool_ctx
138 .session_id
139 .clone()
140 .unwrap_or_else(|| "anon".into()),
141 flow_cancel.clone(),
142 )
143 });
144 self.events.emit(Event::FlowStart {
145 run_id: run_id.clone(),
146 flow_name: flow.name.name.clone(),
147 parent_run_id: None,
148 parent_node_id: None,
149 });
150 if let Some(sess) = session.as_ref() {
151 let _ = sess
152 .stream_tx()
153 .send(crate::stream::StreamFrame::FlowStart {
154 run_id: run_id.0.to_string(),
155 flow_name: flow.name.name.clone(),
156 parent_run_id: None,
157 parent_node_id: None,
158 });
159 }
160 let graph = crate::nodegraph::extract_graph(flow);
161 self.events.emit(Event::FlowGraph {
162 run_id: run_id.clone(),
163 graph: graph.clone(),
164 });
165 if let Some(sess) = session.as_ref() {
166 let _ = sess
167 .stream_tx()
168 .send(crate::stream::StreamFrame::FlowGraph {
169 run_id: run_id.0.to_string(),
170 graph,
171 });
172 }
173 let exec_fut = exec_flow_with_siblings(
174 flow,
175 args,
176 &self.tools,
177 &self.tool_ctx,
178 &self.providers,
179 flows,
180 Some(&self.events),
181 turn_id,
182 Some(run_id.clone()),
183 session.clone(),
184 flow_cancel.clone(),
185 self.safety.as_ref(),
186 self.source_dir.clone(),
187 );
188 let result = tokio::select! {
189 biased;
190 _ = flow_cancel.cancelled() => Err(RuntimeError::Cancelled("flow cancelled by user".into())),
191 r = exec_fut => r,
192 };
193 let status = match &result {
194 Ok(v) => {
195 if let Value::Err(e) = v
196 && matches!(e, RuntimeError::Cancelled(_))
197 {
198 FlowStatus::Cancelled
199 } else {
200 FlowStatus::Ok
201 }
202 }
203 Err(e) => {
204 if matches!(e, RuntimeError::Cancelled(_)) {
205 FlowStatus::Cancelled
206 } else {
207 FlowStatus::Errored {
208 message: e.to_string(),
209 }
210 }
211 }
212 };
213 let cancelled = matches!(status, FlowStatus::Cancelled);
214 if let (Some(tr), Some(tid)) = (self.tool_ctx.task_registry.as_ref(), &task_id) {
215 let ts = match &status {
216 FlowStatus::Ok => crate::task_registry::TaskStatus::Ok,
217 FlowStatus::Cancelled => crate::task_registry::TaskStatus::Killed,
218 FlowStatus::Errored { .. } => crate::task_registry::TaskStatus::Err,
219 };
220 tr.finish(tid, ts);
221 }
222 self.events.emit(Event::FlowEnd {
223 run_id: run_id.clone(),
224 flow_name: flow.name.name.clone(),
225 status: status.clone(),
226 });
227 if let Some(sess) = session.as_ref() {
228 let _ = sess.stream_tx().send(crate::stream::StreamFrame::FlowDone {
229 run_id: run_id.0.to_string(),
230 flow_name: flow.name.name.clone(),
231 ok: matches!(status, FlowStatus::Ok),
232 cancelled,
233 });
234 }
235 result
236 }
237}
238
239impl Default for Executor {
240 fn default() -> Self {
241 Self::new()
242 }
243}