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 spawned: false,
150 });
151 if let Some(sess) = session.as_ref() {
152 let _ = sess
153 .stream_tx()
154 .send(crate::stream::StreamFrame::FlowStart {
155 run_id: run_id.0.to_string(),
156 flow_name: flow.name.name.clone(),
157 parent_run_id: None,
158 parent_node_id: None,
159 });
160 }
161 let graph = crate::nodegraph::extract_graph(flow);
162 self.events.emit(Event::FlowGraph {
163 run_id: run_id.clone(),
164 graph: graph.clone(),
165 });
166 if let Some(sess) = session.as_ref() {
167 let _ = sess
168 .stream_tx()
169 .send(crate::stream::StreamFrame::FlowGraph {
170 run_id: run_id.0.to_string(),
171 graph,
172 });
173 }
174 let mut tool_ctx = self.tool_ctx.clone();
177 if let Some(sess) = session.as_ref() {
178 tool_ctx.stream_tx = Some(sess.stream_tx());
179 tool_ctx.session_messages_handle = Some(sess.messages_handle());
180 let root_entry = sess.flow_registry.create_entry(
184 "root".to_string(),
185 sess.goal().unwrap_or_else(|| flow.name.name.clone()),
186 String::new(),
187 run_id.clone(),
188 );
189 tool_ctx.agent_entry = Some(std::sync::Arc::clone(&root_entry));
190 sess.set_current_root("root".to_string());
191 }
192 let exec_fut = exec_flow_with_siblings(
193 flow,
194 args,
195 &self.tools,
196 &tool_ctx,
197 &self.providers,
198 flows,
199 Some(&self.events),
200 turn_id,
201 Some(run_id.clone()),
202 session.clone(),
203 flow_cancel.clone(),
204 self.safety.as_ref(),
205 self.source_dir.clone(),
206 );
207 let result = tokio::select! {
208 biased;
209 _ = flow_cancel.cancelled() => Err(RuntimeError::Cancelled("flow cancelled by user".into())),
210 r = exec_fut => r,
211 };
212 let status = match &result {
213 Ok(v) => {
214 if let Value::Err(e) = v
215 && matches!(e, RuntimeError::Cancelled(_))
216 {
217 FlowStatus::Cancelled
218 } else {
219 FlowStatus::Ok
220 }
221 }
222 Err(e) => {
223 if matches!(e, RuntimeError::Cancelled(_)) {
224 FlowStatus::Cancelled
225 } else {
226 FlowStatus::Errored {
227 message: e.to_string(),
228 }
229 }
230 }
231 };
232 let cancelled = matches!(status, FlowStatus::Cancelled);
233 if let (Some(tr), Some(tid)) = (self.tool_ctx.task_registry.as_ref(), &task_id) {
234 let ts = match &status {
235 FlowStatus::Ok => crate::task_registry::TaskStatus::Ok,
236 FlowStatus::Cancelled => crate::task_registry::TaskStatus::Killed,
237 FlowStatus::Errored { .. } => crate::task_registry::TaskStatus::Err,
238 };
239 tr.finish(tid, ts);
240 }
241 self.events.emit(Event::FlowEnd {
242 run_id: run_id.clone(),
243 flow_name: flow.name.name.clone(),
244 status: status.clone(),
245 });
246 if let Some(sess) = session.as_ref() {
247 let _ = sess.stream_tx().send(crate::stream::StreamFrame::FlowDone {
248 run_id: run_id.0.to_string(),
249 flow_name: flow.name.name.clone(),
250 ok: matches!(status, FlowStatus::Ok),
251 cancelled,
252 });
253 }
254 result
255 }
256}
257
258impl Default for Executor {
259 fn default() -> Self {
260 Self::new()
261 }
262}