1use crate::agent_tool::AgentTool;
24use crate::error::AgentError;
25use crate::events::{AgentEmitter, AgentEvent};
26use crate::hooks::AgentLoopConfig;
27use crate::message::AgentMessage;
28use crate::stream_fn::StreamFn;
29use crate::types::{AfterToolCallContext, AgentContext, AgentToolResult, BeforeToolCallContext, ToolExecutionMode};
30
31use rpi_ai::types::{
32 AssistantMessage, AssistantMessageEvent, Content, StopReason, ToolCall,
33 ToolCallType, ToolResultMessage, ToolResultRole,
34};
35use rpi_ai::validate_tool_arguments;
36use std::sync::atomic::{AtomicBool, Ordering};
37use std::sync::Arc;
38
39pub type NewMessages = Vec<AgentMessage>;
42
43#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum LoopOutcome {
47 Completed,
48 Aborted,
49 Failed,
50}
51
52impl LoopOutcome {
53 fn from_stop(stop: StopReason) -> Self {
54 match stop {
55 StopReason::Aborted => LoopOutcome::Aborted,
56 StopReason::Error => LoopOutcome::Failed,
57 _ => LoopOutcome::Completed,
58 }
59 }
60}
61
62pub async fn run_agent_loop(
71 prompts: Vec<AgentMessage>,
72 context: AgentContext,
73 config: AgentLoopConfig,
74 emit: Arc<dyn AgentEmitter>,
75 stream_fn: StreamFn,
76) -> Result<NewMessages, AgentError> {
77 let mut new_messages: Vec<AgentMessage> = prompts.clone();
78 let mut current_context = AgentContext {
79 system_prompt: context.system_prompt.clone(),
80 messages: {
81 let mut v = context.messages.clone();
82 v.extend(prompts);
83 v
84 },
85 tools: context.tools.clone(),
86 };
87
88 emit_event(&emit, AgentEvent::AgentStart).await;
89 emit_event(&emit, AgentEvent::TurnStart).await;
90 for prompt in &new_messages {
91 emit_event(&emit, AgentEvent::MessageStart { message: prompt.clone() }).await;
92 emit_event(&emit, AgentEvent::MessageEnd { message: prompt.clone() }).await;
93 }
94
95 run_loop(
96 &mut current_context,
97 &mut new_messages,
98 &config,
99 &emit,
100 &stream_fn,
101 )
102 .await?;
103 Ok(new_messages)
104}
105
106pub async fn run_agent_loop_continue(
110 context: AgentContext,
111 config: AgentLoopConfig,
112 emit: Arc<dyn AgentEmitter>,
113 stream_fn: StreamFn,
114) -> Result<NewMessages, AgentError> {
115 if context.messages.is_empty() {
116 return Err(AgentError::State("cannot continue: no messages in context".into()));
117 }
118 if context.messages.last().unwrap().is_assistant() {
119 return Err(AgentError::State(
120 "cannot continue from message role: assistant".into(),
121 ));
122 }
123
124 let mut new_messages: Vec<AgentMessage> = Vec::new();
125 let mut current_context = context;
126
127 emit_event(&emit, AgentEvent::AgentStart).await;
128 emit_event(&emit, AgentEvent::TurnStart).await;
129
130 run_loop(
131 &mut current_context,
132 &mut new_messages,
133 &config,
134 &emit,
135 &stream_fn,
136 )
137 .await?;
138 Ok(new_messages)
139}
140
141#[derive(Clone)]
147struct FinalizedToolCall {
148 tool_call: ToolCall,
149 result: AgentToolResult,
150 is_error: bool,
151}
152
153struct ExecutedToolBatch {
156 messages: Vec<ToolResultMessage>,
157 terminate: bool,
158}
159
160async fn run_loop(
161 current_context: &mut AgentContext,
162 new_messages: &mut Vec<AgentMessage>,
163 config: &AgentLoopConfig,
164 emit: &Arc<dyn AgentEmitter>,
165 stream_fn: &StreamFn,
166) -> Result<LoopOutcome, AgentError> {
167 let mut first_turn = true;
168 let mut pending_messages = drain_steering(config).await;
170
171 loop {
174 let mut has_more_tool_calls = true;
175
176 while has_more_tool_calls || !pending_messages.is_empty() {
178 if !first_turn {
179 emit_event(emit, AgentEvent::TurnStart).await;
180 } else {
181 first_turn = false;
182 }
183
184 if !pending_messages.is_empty() {
186 for message in pending_messages.drain(..) {
187 emit_event(emit, AgentEvent::MessageStart { message: message.clone() }).await;
188 emit_event(emit, AgentEvent::MessageEnd { message: message.clone() }).await;
189 current_context.messages.push(message.clone());
190 new_messages.push(message);
191 }
192 }
193
194 let message =
196 stream_assistant_response(current_context, config, emit, stream_fn).await?;
197 new_messages.push(AgentMessage::Assistant(Box::new(message.clone())));
198
199 if matches!(message.stop_reason, StopReason::Error | StopReason::Aborted) {
200 let am = AgentMessage::Assistant(Box::new(message.clone()));
201 emit_event(emit, AgentEvent::TurnEnd { message: am, tool_results: Vec::new() }).await;
202 emit_event(emit, AgentEvent::AgentEnd { messages: new_messages.clone() }).await;
203 return Ok(LoopOutcome::from_stop(message.stop_reason));
204 }
205
206 let tool_calls: Vec<ToolCall> = message
208 .content
209 .iter()
210 .filter_map(|c| match c {
211 Content::ToolCall(tc) => Some(tc.clone()),
212 _ => None,
213 })
214 .collect();
215
216 let mut tool_results: Vec<ToolResultMessage> = Vec::new();
217 has_more_tool_calls = false;
218 if !tool_calls.is_empty() {
219 let batch = if matches!(message.stop_reason, StopReason::Length) {
220 fail_tool_calls_from_truncated_message(&tool_calls, emit).await?
222 } else {
223 execute_tool_calls(current_context, &message, &tool_calls, config, emit).await?
224 };
225 tool_results.extend(batch.messages);
226 has_more_tool_calls = !batch.terminate;
227
228 for result in &tool_results {
229 let am = AgentMessage::ToolResult(Box::new(result.clone()));
230 current_context.messages.push(am.clone());
231 new_messages.push(am);
232 }
233 }
234
235 let am = AgentMessage::Assistant(Box::new(message.clone()));
236 emit_event(
237 emit,
238 AgentEvent::TurnEnd {
239 message: am,
240 tool_results: tool_results.clone(),
241 },
242 )
243 .await;
244
245 if let Some(upd) =
249 prepare_next_turn(config, &message, &tool_results, current_context, new_messages).await
250 {
251 if let Some(ctx) = upd.context {
252 *current_context = ctx;
253 }
254 }
255
256 if should_stop_after_turn(config, &message, &tool_results, current_context, new_messages).await {
257 emit_event(emit, AgentEvent::AgentEnd { messages: new_messages.clone() }).await;
258 return Ok(LoopOutcome::Completed);
259 }
260
261 if config.signal.is_cancelled() {
262 emit_event(emit, AgentEvent::AgentEnd { messages: new_messages.clone() }).await;
263 return Ok(LoopOutcome::Aborted);
264 }
265
266 pending_messages = drain_steering(config).await;
267 }
268
269 let follow_ups = drain_follow_up(config).await;
271 if !follow_ups.is_empty() {
272 pending_messages = follow_ups;
273 continue;
274 }
275 break;
276 }
277
278 emit_event(emit, AgentEvent::AgentEnd { messages: new_messages.clone() }).await;
279 Ok(LoopOutcome::Completed)
280}
281
282async fn stream_assistant_response(
290 context: &mut AgentContext,
291 config: &AgentLoopConfig,
292 emit: &Arc<dyn AgentEmitter>,
293 stream_fn: &StreamFn,
294) -> Result<AssistantMessage, AgentError> {
295 let messages = if let Some(transform) = &config.transform_context {
297 transform(context.messages.clone(), config.signal.clone()).await
298 } else {
299 context.messages.clone()
300 };
301
302 let llm_messages = (config.convert_to_llm)(messages).await;
304
305 let llm_context = rpi_ai::types::Context {
306 system_prompt: if context.system_prompt.is_empty() {
307 None
308 } else {
309 Some(context.system_prompt.clone())
310 },
311 messages: llm_messages,
312 tools: context.tools.iter().map(|t| t.schema().clone()).collect(),
313 };
314
315 let resolved_api_key = if let Some(get_key) = &config.get_api_key {
317 get_key(&config.model.provider)
318 .await
319 .or_else(|| config.api_key.clone())
320 } else {
321 config.api_key.clone()
322 };
323
324 let opts = config.to_stream_options(resolved_api_key);
325 let mut response = stream_fn(&config.model, &llm_context, &opts);
326
327 let mut added_partial = false;
328
329 while let Some(event) = response.next().await {
330 match &event {
331 AssistantMessageEvent::Start { partial } => {
332 let am = AgentMessage::Assistant(Box::new((**partial).clone()));
333 context.messages.push(am.clone());
334 added_partial = true;
335 emit_event(emit, AgentEvent::MessageStart { message: am }).await;
336 }
337 AssistantMessageEvent::TextStart { partial, .. }
338 | AssistantMessageEvent::TextDelta { partial, .. }
339 | AssistantMessageEvent::TextEnd { partial, .. }
340 | AssistantMessageEvent::ThinkingStart { partial, .. }
341 | AssistantMessageEvent::ThinkingDelta { partial, .. }
342 | AssistantMessageEvent::ThinkingEnd { partial, .. }
343 | AssistantMessageEvent::ToolCallStart { partial, .. }
344 | AssistantMessageEvent::ToolCallDelta { partial, .. }
345 | AssistantMessageEvent::ToolCallEnd { partial, .. } => {
346 if added_partial {
347 let am = AgentMessage::Assistant(Box::new((**partial).clone()));
348 if let Some(last) = context.messages.last_mut() {
349 *last = am.clone();
350 }
351 emit_event(
352 emit,
353 AgentEvent::MessageUpdate {
354 message: am,
355 assistant_message_event: event.clone(),
356 },
357 )
358 .await;
359 }
360 }
361 AssistantMessageEvent::Done { .. } | AssistantMessageEvent::Error { .. } => {
362 let final_message = response.result().await.map_err(|_| {
363 AgentError::Provider(
364 "assistant-message event stream ended without a terminal event".into(),
365 )
366 })?;
367 let am = AgentMessage::Assistant(Box::new(final_message.clone()));
368 if added_partial {
369 if let Some(last) = context.messages.last_mut() {
370 *last = am.clone();
371 }
372 } else {
373 context.messages.push(am.clone());
374 emit_event(emit, AgentEvent::MessageStart { message: am.clone() }).await;
375 }
376 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
377 return Ok(final_message);
378 }
379 }
380 }
381
382 let final_message = response.result().await.map_err(|_| {
385 AgentError::Provider("assistant-message event stream ended without a terminal event".into())
386 })?;
387 let am = AgentMessage::Assistant(Box::new(final_message.clone()));
388 if added_partial {
389 if let Some(last) = context.messages.last_mut() {
390 *last = am.clone();
391 }
392 } else {
393 context.messages.push(am.clone());
394 emit_event(emit, AgentEvent::MessageStart { message: am.clone() }).await;
395 }
396 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
397 Ok(final_message)
398}
399
400async fn fail_tool_calls_from_truncated_message(
409 tool_calls: &[ToolCall],
410 emit: &Arc<dyn AgentEmitter>,
411) -> Result<ExecutedToolBatch, AgentError> {
412 let mut messages: Vec<ToolResultMessage> = Vec::new();
413 for tool_call in tool_calls {
414 emit_event(
415 emit,
416 AgentEvent::ToolExecutionStart {
417 tool_call_id: tool_call.id.clone(),
418 tool_name: tool_call.name.clone(),
419 args: tool_call.arguments.clone(),
420 },
421 )
422 .await;
423 let reason = format!(
424 "Tool call {:?} was not executed: the response hit the output token limit, so its arguments may be truncated. Re-issue the tool call with complete arguments.",
425 tool_call.name
426 );
427 let result = create_error_tool_result(&reason);
428 emit_event(
429 emit,
430 AgentEvent::ToolExecutionEnd {
431 tool_call_id: tool_call.id.clone(),
432 tool_name: tool_call.name.clone(),
433 result: result.clone(),
434 is_error: true,
435 },
436 )
437 .await;
438 let trm = create_tool_result_message(tool_call, &result, true);
439 emit_tool_result_message(emit, &trm).await;
440 messages.push(trm);
441 }
442 Ok(ExecutedToolBatch {
443 messages,
444 terminate: false,
445 })
446}
447
448async fn execute_tool_calls(
456 current_context: &AgentContext,
457 assistant_message: &AssistantMessage,
458 tool_calls: &[ToolCall],
459 config: &AgentLoopConfig,
460 emit: &Arc<dyn AgentEmitter>,
461) -> Result<ExecutedToolBatch, AgentError> {
462 let has_sequential = tool_calls.iter().any(|tc| {
463 current_context
464 .tools
465 .iter()
466 .find(|t| t.schema().name == tc.name)
467 .map(|t| t.execution_mode() == ToolExecutionMode::Sequential)
468 .unwrap_or(false)
469 });
470 if config.tool_execution == ToolExecutionMode::Sequential || has_sequential {
471 execute_tool_calls_sequential(
472 current_context,
473 assistant_message,
474 tool_calls,
475 config,
476 emit,
477 )
478 .await
479 } else {
480 execute_tool_calls_parallel(
481 current_context,
482 assistant_message,
483 tool_calls,
484 config,
485 emit,
486 )
487 .await
488 }
489}
490
491async fn execute_tool_calls_sequential(
492 current_context: &AgentContext,
493 assistant_message: &AssistantMessage,
494 tool_calls: &[ToolCall],
495 config: &AgentLoopConfig,
496 emit: &Arc<dyn AgentEmitter>,
497) -> Result<ExecutedToolBatch, AgentError> {
498 let mut finalized_calls: Vec<FinalizedToolCall> = Vec::new();
499 let mut messages: Vec<ToolResultMessage> = Vec::new();
500
501 for tool_call in tool_calls {
502 emit_event(
503 emit,
504 AgentEvent::ToolExecutionStart {
505 tool_call_id: tool_call.id.clone(),
506 tool_name: tool_call.name.clone(),
507 args: tool_call.arguments.clone(),
508 },
509 )
510 .await;
511
512 let finalized = run_one_tool_call(
513 current_context,
514 assistant_message,
515 tool_call,
516 config,
517 emit,
518 )
519 .await?;
520
521 emit_tool_execution_end(emit, &finalized).await;
522 let trm = create_tool_result_message(&finalized.tool_call, &finalized.result, finalized.is_error);
523 emit_tool_result_message(emit, &trm).await;
524 finalized_calls.push(finalized);
525 messages.push(trm);
526
527 if config.signal.is_cancelled() {
528 break;
529 }
530 }
531
532 Ok(ExecutedToolBatch {
533 messages,
534 terminate: should_terminate_tool_batch(&finalized_calls),
535 })
536}
537
538async fn execute_tool_calls_parallel(
549 current_context: &AgentContext,
550 assistant_message: &AssistantMessage,
551 tool_calls: &[ToolCall],
552 config: &AgentLoopConfig,
553 emit: &Arc<dyn AgentEmitter>,
554) -> Result<ExecutedToolBatch, AgentError> {
555 enum Entry {
557 Done(FinalizedToolCall),
558 Running(tokio::task::JoinHandle<FinalizedToolCall>),
559 }
560
561 let mut entries: Vec<Entry> = Vec::with_capacity(tool_calls.len());
562
563 for tool_call in tool_calls {
564 emit_event(
565 emit,
566 AgentEvent::ToolExecutionStart {
567 tool_call_id: tool_call.id.clone(),
568 tool_name: tool_call.name.clone(),
569 args: tool_call.arguments.clone(),
570 },
571 )
572 .await;
573
574 match prepare_tool_call(current_context, assistant_message, tool_call, config).await {
575 Prepared::Immediate { result, is_error } => {
576 let finalized = FinalizedToolCall {
577 tool_call: tool_call.clone(),
578 result,
579 is_error,
580 };
581 emit_tool_execution_end(emit, &finalized).await;
582 entries.push(Entry::Done(finalized));
583 }
584 Prepared::Ready { tool, args } => {
585 let tc = tool_call.clone();
589 let am = assistant_message.clone();
590 let ctx = current_context.clone();
591 let cfg = config.clone();
592 let emit2 = Arc::clone(emit);
593 let handle = tokio::spawn(async move {
594 let executed =
595 execute_prepared_tool_call(&tc, &tool, &args, &cfg, &emit2).await;
596 finalize_executed_tool_call(&ctx, &am, &tc, &args, executed, &cfg).await
597 });
598 entries.push(Entry::Running(handle));
599 }
600 }
601 if config.signal.is_cancelled() {
602 break;
603 }
604 }
605
606 let mut finalized_by_index: Vec<Option<FinalizedToolCall>> = vec![None; entries.len()];
609 let mut pending: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> = Vec::new();
610 for (i, e) in entries.into_iter().enumerate() {
611 match e {
612 Entry::Done(f) => {
613 finalized_by_index[i] = Some(f);
614 }
615 Entry::Running(h) => pending.push((i, h)),
616 }
617 }
618
619 while !pending.is_empty() {
620 if pending.len() == 1 {
621 let (i, h) = pending.remove(0);
623 let finalized = h.await.unwrap_or_else(|_| FinalizedToolCall {
624 tool_call: panicked_tool_call(),
625 result: create_error_tool_result("tool task panicked"),
626 is_error: true,
627 });
628 emit_tool_execution_end(emit, &finalized).await;
629 finalized_by_index[i] = Some(finalized);
630 break;
631 }
632
633 let mut resolved: Option<(usize, FinalizedToolCall)> = None;
637 let mut still_pending: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> =
638 Vec::with_capacity(pending.len());
639 for (i, h) in pending.drain(..) {
641 if resolved.is_none() && h.is_finished() {
642 let finalized = h.await.unwrap_or_else(|_| FinalizedToolCall {
643 tool_call: panicked_tool_call(),
644 result: create_error_tool_result("tool task panicked"),
645 is_error: true,
646 });
647 resolved = Some((i, finalized));
648 } else {
649 still_pending.push((i, h));
650 }
651 }
652 match resolved {
653 Some((i, finalized)) => {
654 emit_tool_execution_end(emit, &finalized).await;
655 finalized_by_index[i] = Some(finalized);
656 pending = still_pending;
657 }
658 None => {
659 pending = still_pending;
662 race_one_and_collect(emit, &mut pending, &mut finalized_by_index).await;
663 }
664 }
665 }
666
667 let mut messages: Vec<ToolResultMessage> = Vec::new();
670 let mut finalized_calls: Vec<FinalizedToolCall> = Vec::new();
671 for slot in finalized_by_index.into_iter() {
672 let finalized = slot.expect("every tool call finalized");
673 let trm =
674 create_tool_result_message(&finalized.tool_call, &finalized.result, finalized.is_error);
675 emit_tool_result_message(emit, &trm).await;
676 messages.push(trm);
677 finalized_calls.push(finalized);
678 }
679
680 Ok(ExecutedToolBatch {
681 messages,
682 terminate: should_terminate_tool_batch(&finalized_calls),
683 })
684}
685
686async fn race_one_and_collect(
695 emit: &Arc<dyn AgentEmitter>,
696 pending: &mut Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)>,
697 finalized_by_index: &mut [Option<FinalizedToolCall>],
698) {
699 let indexed: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> =
704 std::mem::take(pending);
705 let mut boxed: Vec<
706 std::pin::Pin<Box<dyn std::future::Future<Output = (usize, FinalizedToolCall)> + Send>>,
707 > = Vec::with_capacity(indexed.len());
708 for (i, h) in indexed {
709 boxed.push(Box::pin(async move {
710 let f = h.await.unwrap_or_else(|_| FinalizedToolCall {
711 tool_call: panicked_tool_call(),
712 result: create_error_tool_result("tool task panicked"),
713 is_error: true,
714 });
715 (i, f)
716 }));
717 }
718
719 while !boxed.is_empty() {
720 let (outcome, _idx, rest) = futures::future::select_all(boxed).await;
722 boxed = rest;
723 let (i, finalized) = outcome;
724 emit_tool_execution_end(emit, &finalized).await;
725 finalized_by_index[i] = Some(finalized);
726 }
727}
728
729async fn run_one_tool_call(
731 current_context: &AgentContext,
732 assistant_message: &AssistantMessage,
733 tool_call: &ToolCall,
734 config: &AgentLoopConfig,
735 emit: &Arc<dyn AgentEmitter>,
736) -> Result<FinalizedToolCall, AgentError> {
737 match prepare_tool_call(current_context, assistant_message, tool_call, config).await {
738 Prepared::Immediate { result, is_error } => Ok(FinalizedToolCall {
739 tool_call: tool_call.clone(),
740 result,
741 is_error,
742 }),
743 Prepared::Ready { tool, args } => {
744 let executed =
745 execute_prepared_tool_call(tool_call, &tool, &args, config, emit).await;
746 Ok(finalize_executed_tool_call(
747 current_context,
748 assistant_message,
749 tool_call,
750 &args,
751 executed,
752 config,
753 )
754 .await)
755 }
756 }
757}
758
759enum Prepared {
761 Immediate {
763 result: AgentToolResult,
764 is_error: bool,
765 },
766 Ready {
768 tool: Arc<dyn AgentTool>,
769 args: serde_json::Value,
770 },
771}
772
773async fn prepare_tool_call(
776 current_context: &AgentContext,
777 assistant_message: &AssistantMessage,
778 tool_call: &ToolCall,
779 config: &AgentLoopConfig,
780) -> Prepared {
781 let tool = current_context.tools.iter().find(|t| t.schema().name == tool_call.name).cloned();
782 let tool = match tool {
783 Some(t) => t,
784 None => {
785 return Prepared::Immediate {
786 result: create_error_tool_result(&format!("Tool {} not found", tool_call.name)),
787 is_error: true,
788 };
789 }
790 };
791
792 let prepared_args = match tool.prepare_arguments(tool_call.arguments.clone()) {
794 Ok(v) => v,
795 Err(e) => {
796 return Prepared::Immediate {
797 result: create_error_tool_result(&e.to_string()),
798 is_error: true,
799 };
800 }
801 };
802 let mut prepared_tool_call = tool_call.clone();
803 prepared_tool_call.arguments = prepared_args;
804
805 let validated_args = match validate_tool_arguments(tool.schema(), &prepared_tool_call) {
806 Ok(v) => v,
807 Err(e) => {
808 return Prepared::Immediate {
809 result: create_error_tool_result(&e.to_string()),
810 is_error: true,
811 };
812 }
813 };
814
815 let mut validated_args = validated_args;
822 if let Some(before) = &config.before_tool_call {
823 let ctx = BeforeToolCallContext {
824 assistant_message,
825 tool_call: &prepared_tool_call,
826 args: &validated_args,
827 context: current_context,
828 };
829 let before_result = before(ctx, config.signal.clone()).await;
830 if config.signal.is_cancelled() {
831 return Prepared::Immediate {
832 result: create_error_tool_result("Operation aborted"),
833 is_error: true,
834 };
835 }
836 if let Some(br) = before_result {
837 if let Some(replacement) = br.args {
838 validated_args = replacement;
839 }
840 if br.block {
841 let mut result =
842 create_error_tool_result(&br.reason.unwrap_or_else(|| "Tool execution was blocked".to_string()));
843 if br.terminate {
844 result.terminate = true;
845 }
846 return Prepared::Immediate {
847 result,
848 is_error: true,
849 };
850 }
851 }
852 }
853
854 if config.signal.is_cancelled() {
855 return Prepared::Immediate {
856 result: create_error_tool_result("Operation aborted"),
857 is_error: true,
858 };
859 }
860
861 Prepared::Ready {
862 tool,
863 args: validated_args,
864 }
865}
866
867async fn execute_prepared_tool_call(
870 tool_call: &ToolCall,
871 tool: &Arc<dyn AgentTool>,
872 args: &serde_json::Value,
873 config: &AgentLoopConfig,
874 emit: &Arc<dyn AgentEmitter>,
875) -> ExecutedToolCallOutcome {
876 let accepting_updates = Arc::new(AtomicBool::new(true));
879 let tool_call_id = tool_call.id.clone();
880 let tool_name = tool_call.name.clone();
881 let args_clone = args.clone();
882 let emit_clone = Arc::clone(emit);
883
884 let on_update: Arc<dyn Fn(crate::types::ToolResultPartial) + Send + Sync> = {
885 let gate = Arc::clone(&accepting_updates);
886 Arc::new(move |partial: crate::types::ToolResultPartial| {
887 if !gate.load(Ordering::SeqCst) {
888 return;
889 }
890 let ev = AgentEvent::ToolExecutionUpdate {
893 tool_call_id: tool_call_id.clone(),
894 tool_name: tool_name.clone(),
895 args: args_clone.clone(),
896 partial_result: Arc::new(partial),
897 };
898 emit_clone.try_emit(ev);
903 })
904 };
905
906 let child_token = config.signal.child_token();
907 match tool
908 .execute(
909 &tool_call.id,
910 args.clone(),
911 child_token,
912 on_update,
913 )
914 .await
915 {
916 Ok(result) => {
917 accepting_updates.store(false, Ordering::SeqCst);
918 ExecutedToolCallOutcome {
919 result,
920 is_error: false,
921 }
922 }
923 Err(e) => {
924 accepting_updates.store(false, Ordering::SeqCst);
925 ExecutedToolCallOutcome {
926 result: create_error_tool_result(&e.to_string()),
927 is_error: true,
928 }
929 }
930 }
931}
932
933struct ExecutedToolCallOutcome {
936 result: AgentToolResult,
937 is_error: bool,
938}
939
940async fn finalize_executed_tool_call(
942 current_context: &AgentContext,
943 assistant_message: &AssistantMessage,
944 tool_call: &ToolCall,
945 args: &serde_json::Value,
946 executed: ExecutedToolCallOutcome,
947 config: &AgentLoopConfig,
948) -> FinalizedToolCall {
949 let mut result = executed.result;
950 let mut is_error = executed.is_error;
951
952 if let Some(after) = &config.after_tool_call {
953 let ctx = AfterToolCallContext {
954 assistant_message,
955 tool_call,
956 args,
957 result: &result,
958 is_error,
959 context: current_context,
960 };
961 match after(ctx, config.signal.clone()).await {
962 Some(after_result) => {
963 if let Some(c) = after_result.content {
964 result.content = c;
965 }
966 if let Some(d) = after_result.details {
967 result.details = d;
968 }
969 if let Some(u) = after_result.usage {
970 result.usage = Some(u);
971 }
972 if let Some(t) = after_result.terminate {
973 result.terminate = t;
974 }
975 if let Some(ie) = after_result.is_error {
976 is_error = ie;
977 }
978 }
979 None => {}
980 }
981 }
982
983 FinalizedToolCall {
984 tool_call: tool_call.clone(),
985 result,
986 is_error,
987 }
988}
989
990fn should_terminate_tool_batch(finalized_calls: &[FinalizedToolCall]) -> bool {
997 !finalized_calls.is_empty()
998 && finalized_calls.iter().all(|f| f.result.terminate)
999}
1000
1001fn create_error_tool_result(message: &str) -> AgentToolResult {
1004 AgentToolResult::error_text(message)
1005}
1006
1007fn create_tool_result_message(
1011 tool_call: &ToolCall,
1012 result: &AgentToolResult,
1013 is_error: bool,
1014) -> ToolResultMessage {
1015 ToolResultMessage {
1016 role: ToolResultRole,
1017 tool_call_id: tool_call.id.clone(),
1018 tool_name: tool_call.name.clone(),
1019 content: result.clone().into_content(),
1020 details: Some(result.details.clone()),
1021 usage: result.usage.clone(),
1022 added_tool_names: result.added_tool_names.clone(),
1023 is_error,
1024 timestamp: now_ms(),
1025 }
1026}
1027
1028async fn emit_tool_execution_end(
1030 emit: &Arc<dyn AgentEmitter>,
1031 finalized: &FinalizedToolCall,
1032) {
1033 emit_event(
1034 emit,
1035 AgentEvent::ToolExecutionEnd {
1036 tool_call_id: finalized.tool_call.id.clone(),
1037 tool_name: finalized.tool_call.name.clone(),
1038 result: finalized.result.clone(),
1039 is_error: finalized.is_error,
1040 },
1041 )
1042 .await;
1043}
1044
1045async fn emit_tool_result_message(emit: &Arc<dyn AgentEmitter>, trm: &ToolResultMessage) {
1048 let am = AgentMessage::ToolResult(Box::new(trm.clone()));
1049 emit_event(emit, AgentEvent::MessageStart { message: am.clone() }).await;
1050 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
1051}
1052
1053async fn drain_steering(config: &AgentLoopConfig) -> Vec<AgentMessage> {
1055 if let Some(hook) = &config.get_steering_messages {
1056 hook().await
1057 } else {
1058 Vec::new()
1059 }
1060}
1061
1062async fn drain_follow_up(config: &AgentLoopConfig) -> Vec<AgentMessage> {
1064 if let Some(hook) = &config.get_follow_up_messages {
1065 hook().await
1066 } else {
1067 Vec::new()
1068 }
1069}
1070
1071async fn prepare_next_turn(
1073 config: &AgentLoopConfig,
1074 message: &AssistantMessage,
1075 tool_results: &[ToolResultMessage],
1076 context: &AgentContext,
1077 new_messages: &[AgentMessage],
1078) -> Option<crate::types::AgentLoopTurnUpdate> {
1079 if let Some(hook) = &config.prepare_next_turn {
1080 let ctx = crate::types::ShouldStopAfterTurnContext {
1081 message,
1082 tool_results,
1083 context,
1084 new_messages,
1085 };
1086 hook(ctx).await
1087 } else {
1088 None
1089 }
1090}
1091
1092async fn should_stop_after_turn(
1094 config: &AgentLoopConfig,
1095 message: &AssistantMessage,
1096 tool_results: &[ToolResultMessage],
1097 context: &AgentContext,
1098 new_messages: &[AgentMessage],
1099) -> bool {
1100 if let Some(hook) = &config.should_stop_after_turn {
1101 let ctx = crate::types::ShouldStopAfterTurnContext {
1102 message,
1103 tool_results,
1104 context,
1105 new_messages,
1106 };
1107 hook(ctx).await
1108 } else {
1109 false
1110 }
1111}
1112
1113async fn emit_event(emit: &Arc<dyn AgentEmitter>, event: AgentEvent) {
1115 emit.emit(event).await;
1116}
1117
1118fn now_ms() -> i64 {
1121 use std::sync::atomic::{AtomicI64, Ordering};
1122 static T: AtomicI64 = AtomicI64::new(1);
1123 T.fetch_add(1, Ordering::Relaxed)
1124}
1125
1126fn panicked_tool_call() -> ToolCall {
1128 ToolCall {
1129 kind: ToolCallType,
1130 id: "<panic>".to_string(),
1131 name: "<panic>".to_string(),
1132 arguments: serde_json::Value::Null,
1133 thought_signature: None,
1134 namespace: None,
1135 }
1136}