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