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