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}
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(&current)
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}