Skip to main content

atman_runtime/
executor.rs

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    /// When set, relative `@` paths are resolved against this directory.
21    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(&current)
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}