1use std::sync::Arc;
4
5use agentyk_core::atoms;
6use agentyk_core::cancellation::CancellationToken;
7use agentyk_core::concurrency::join_all;
8use agentyk_core::driver::DeltaSink;
9use agentyk_core::error::{Error, Result};
10use agentyk_core::event::{EventData, EventListener, EventRequest, notify_event_listeners};
11use agentyk_core::id::{EventId, MessageId, SessionId, TurnId};
12use agentyk_core::message::Message;
13use agentyk_core::tool::{ToolContext, ToolOutput, ToolProgress, ToolProgressSink};
14use agentyk_core::turn::{TurnOutcome, TurnState};
15use async_trait::async_trait;
16use chrono::Utc;
17
18use crate::engine::{TurnEngine, TurnOperation};
19use crate::host::{TurnHost, TurnResult};
20
21struct ListenerProgressSink {
31 listeners: Vec<Arc<dyn EventListener>>,
32 session_id: SessionId,
33 turn_id: TurnId,
34 call_id: String,
35 name: String,
36}
37
38#[async_trait]
39impl ToolProgressSink for ListenerProgressSink {
40 async fn progress(&self, progress: ToolProgress) {
41 if self.listeners.is_empty() {
42 return;
43 }
44 let data = EventData::ToolProgress {
45 call_id: self.call_id.clone(),
46 name: self.name.clone(),
47 progress,
48 };
49 let event = EventRequest::with_turn(self.session_id, self.turn_id, data)
50 .into_ephemeral_event(EventId::new(), Utc::now());
51 notify_event_listeners(&self.listeners, &event).await;
52 }
53}
54
55struct RecordingDeltaSink<'h, 'a> {
56 host: &'h mut TurnHost<'a>,
57 turn_id: TurnId,
58 message_id: MessageId,
59 cancellation: CancellationToken,
60}
61
62#[async_trait]
63impl DeltaSink for RecordingDeltaSink<'_, '_> {
64 async fn delta(&mut self, delta: &str, accumulated: &str) -> Result<()> {
65 if self.cancellation.is_cancelled() {
66 return Err(Error::Cancelled);
67 }
68 self.host
69 .record(
70 self.turn_id,
71 vec![EventData::OutputMessageDelta {
72 message_id: self.message_id,
73 delta: delta.to_string(),
74 accumulated: accumulated.to_string(),
75 }],
76 )
77 .await
78 }
79}
80
81#[derive(Debug, Clone, Copy)]
90pub struct InProcessExecutor {
91 concurrent: bool,
92}
93
94impl Default for InProcessExecutor {
95 fn default() -> Self {
96 Self { concurrent: true }
97 }
98}
99
100impl InProcessExecutor {
101 pub fn new() -> Self {
103 Self::default()
104 }
105
106 pub fn sequential() -> Self {
114 Self { concurrent: false }
115 }
116}
117
118impl InProcessExecutor {
119 pub async fn run_turn(&self, host: &mut TurnHost<'_>, input: Message) -> Result<TurnResult> {
121 let engine = TurnEngine;
122 let started = engine.start(host, input).await;
123 let turn_id = started.state.turn_id;
124 host.record(turn_id, started.events).await?;
125 if let Some(rejection) = started.rejection {
126 let warnings = engine
127 .finish(
128 host,
129 turn_id,
130 serde_json::json!({
131 "success": false,
132 "error": rejection.reason,
133 }),
134 )
135 .await;
136 host.record(turn_id, warnings).await?;
137 return Err(Error::HookBlocked {
138 reason: rejection.reason,
139 user_message: rejection.user_message,
140 });
141 }
142 self.resume_turn(host, started.state).await
143 }
144
145 pub async fn resume_turn(
152 &self,
153 host: &mut TurnHost<'_>,
154 state: TurnState,
155 ) -> Result<TurnResult> {
156 let turn_id = state.turn_id;
157 let result = self.resume_turn_inner(host, state).await;
158 let data = match &result {
159 Ok(result) => serde_json::json!({
160 "success": true,
161 "response": result.response,
162 "tool_calls": result.tool_calls,
163 "iterations": result.iterations,
164 }),
165 Err(error) => serde_json::json!({
166 "success": false,
167 "error": error.to_string(),
168 }),
169 };
170 let warnings = TurnEngine.finish(host, turn_id, data).await;
171 host.record(turn_id, warnings).await?;
172 result
173 }
174
175 async fn resume_turn_inner(
176 &self,
177 host: &mut TurnHost<'_>,
178 mut state: TurnState,
179 ) -> Result<TurnResult> {
180 if state.session_id != host.session_id {
181 return Err(Error::Other(format!(
182 "turn belongs to {}, not session {}",
183 state.session_id, host.session_id
184 )));
185 }
186 let engine = TurnEngine;
187 let assembled = engine.assemble(host).await?;
188 let driver = host
189 .environment
190 .drivers
191 .get(&host.model.driver)
192 .ok_or_else(|| Error::UnknownDriver(host.model.driver.to_string()))?;
193 let turn_id = state.turn_id;
194
195 loop {
196 let step = engine.prepare(host, &assembled, &mut state).await?;
197 host.record(turn_id, step.events).await?;
198 match step.operation {
199 TurnOperation::InvokeModel { request } => {
200 let cancellation = host.cancellation.clone();
201 let message_id = state.current_message_id.unwrap_or_default();
202 let mut sink = RecordingDeltaSink {
203 host: &mut *host,
204 turn_id,
205 message_id,
206 cancellation,
207 };
208 match driver.complete_streaming(request, &mut sink).await {
209 Ok(response) => {
210 let events = engine.complete_model(&mut state, &response);
211 host.record(turn_id, events).await?;
212 }
213 Err(Error::Cancelled) => {
214 let events = state.on_cancel();
215 host.record(turn_id, events).await?;
216 return Err(Error::Cancelled);
217 }
218 Err(error) => {
219 let events = engine.fail_model(&mut state, &error);
220 host.record(turn_id, events).await?;
221 return Err(error);
222 }
223 }
224 }
225 TurnOperation::InvokeTools { calls } => {
226 let mut pending = Vec::new();
231 let mut outputs: Vec<Option<ToolOutput>> = Vec::with_capacity(calls.len());
232 for prepared in &calls {
233 match &prepared.denied {
234 Some(output) => outputs.push(Some(output.clone())),
235 None => {
236 outputs.push(None);
237 pending.push(prepared.call.clone());
238 }
239 }
240 }
241
242 if !pending.is_empty() {
243 let contexts: Vec<ToolContext> = pending
244 .iter()
245 .map(|call| {
246 ToolContext::new(host.session_id, turn_id)
247 .with_extensions(host.environment.extensions.clone())
248 .with_cancellation(host.cancellation.clone())
249 .with_progress(Arc::new(ListenerProgressSink {
250 listeners: host.environment.listeners.clone(),
251 session_id: host.session_id,
252 turn_id,
253 call_id: call.id.clone(),
254 name: call.name.clone(),
255 })
256 as Arc<dyn ToolProgressSink>)
257 })
258 .collect();
259
260 let cancellation = host.cancellation.clone();
265 let executed = match self.concurrent {
266 true => {
267 let futures = pending
268 .iter()
269 .zip(&contexts)
270 .map(|(call, context)| atoms::act(&assembled, call, context));
271 cancellation.run_until_cancelled(join_all(futures)).await
272 }
273 false => {
277 cancellation
278 .run_until_cancelled(async {
279 let mut results = Vec::with_capacity(pending.len());
280 for (call, context) in pending.iter().zip(&contexts) {
281 results
282 .push(atoms::act(&assembled, call, context).await);
283 }
284 results
285 })
286 .await
287 }
288 };
289 let Some(results) = executed else {
290 let events = state.on_cancel();
291 host.record(turn_id, events).await?;
292 return Err(Error::Cancelled);
293 };
294 let mut results = results.into_iter();
295 for slot in outputs.iter_mut() {
296 if slot.is_none() {
297 *slot = results.next();
298 }
299 }
300 }
301
302 for (prepared, output) in calls.into_iter().zip(outputs) {
306 let output = output.expect("every prepared call produced an output");
307 let events = engine
308 .complete_tool(
309 host,
310 &assembled,
311 &mut state,
312 &prepared.call,
313 output,
314 prepared.denied.is_some(),
315 )
316 .await;
317 host.record(turn_id, events).await?;
318 if let Some(step) = engine.guard(host, &mut state).await {
319 host.record(turn_id, step.events).await?;
320 if let TurnOperation::Finished(outcome) = step.operation {
321 return turn_result(host, state, outcome);
322 }
323 unreachable!("an engine guard only returns terminal operations");
324 }
325 }
326 }
327 TurnOperation::Finished(outcome) => {
328 return turn_result(host, state, outcome);
329 }
330 }
331 }
332 }
333}
334
335fn turn_result(host: &TurnHost<'_>, state: TurnState, outcome: TurnOutcome) -> Result<TurnResult> {
336 match outcome {
337 TurnOutcome::Success { response } => Ok(TurnResult::new(
338 state.turn_id,
339 response,
340 state.iterations,
341 state.tool_calls_executed,
342 state.usage,
343 )),
344 TurnOutcome::MaxIterations => Err(Error::MaxIterations(host.definition.max_iterations)),
345 TurnOutcome::Failed { error } => Err(Error::Other(error)),
346 TurnOutcome::Cancelled => Err(Error::Cancelled),
347 TurnOutcome::Sealed(reason) => Err(Error::Sealed(reason)),
348 }
349}