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::{
30 AfterToolCallContext, AgentContext, AgentToolResult, BeforeToolCallContext, ToolExecutionMode,
31};
32
33use rpi_ai::types::{
34 AssistantMessage, AssistantMessageEvent, Content, StopReason, ToolCall, ToolCallType,
35 ToolResultMessage, ToolResultRole,
36};
37use rpi_ai::validate_tool_arguments;
38use std::sync::atomic::{AtomicBool, Ordering};
39use std::sync::Arc;
40
41pub type NewMessages = Vec<AgentMessage>;
44
45#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum LoopOutcome {
49 Completed,
50 Aborted,
51 Failed,
52}
53
54impl LoopOutcome {
55 fn from_stop(stop: StopReason) -> Self {
56 match stop {
57 StopReason::Aborted => LoopOutcome::Aborted,
58 StopReason::Error => LoopOutcome::Failed,
59 _ => LoopOutcome::Completed,
60 }
61 }
62}
63
64pub async fn run_agent_loop(
73 prompts: Vec<AgentMessage>,
74 context: AgentContext,
75 config: AgentLoopConfig,
76 emit: Arc<dyn AgentEmitter>,
77 stream_fn: StreamFn,
78) -> Result<NewMessages, AgentError> {
79 let mut new_messages: Vec<AgentMessage> = prompts.clone();
80 let mut current_context = AgentContext {
81 system_prompt: context.system_prompt.clone(),
82 messages: {
83 let mut v = context.messages.clone();
84 v.extend(prompts);
85 v
86 },
87 tools: context.tools.clone(),
88 };
89
90 emit_event(&emit, AgentEvent::AgentStart).await;
91 emit_event(&emit, AgentEvent::TurnStart).await;
92 for prompt in &new_messages {
93 emit_event(
94 &emit,
95 AgentEvent::MessageStart {
96 message: prompt.clone(),
97 },
98 )
99 .await;
100 emit_event(
101 &emit,
102 AgentEvent::MessageEnd {
103 message: prompt.clone(),
104 },
105 )
106 .await;
107 }
108
109 run_loop(
110 &mut current_context,
111 &mut new_messages,
112 &config,
113 &emit,
114 &stream_fn,
115 )
116 .await?;
117 Ok(new_messages)
118}
119
120pub async fn run_agent_loop_continue(
124 context: AgentContext,
125 config: AgentLoopConfig,
126 emit: Arc<dyn AgentEmitter>,
127 stream_fn: StreamFn,
128) -> Result<NewMessages, AgentError> {
129 if context.messages.is_empty() {
130 return Err(AgentError::State(
131 "cannot continue: no messages in context".into(),
132 ));
133 }
134 if context.messages.last().unwrap().is_assistant() {
135 return Err(AgentError::State(
136 "cannot continue from message role: assistant".into(),
137 ));
138 }
139
140 let mut new_messages: Vec<AgentMessage> = Vec::new();
141 let mut current_context = context;
142
143 emit_event(&emit, AgentEvent::AgentStart).await;
144 emit_event(&emit, AgentEvent::TurnStart).await;
145
146 run_loop(
147 &mut current_context,
148 &mut new_messages,
149 &config,
150 &emit,
151 &stream_fn,
152 )
153 .await?;
154 Ok(new_messages)
155}
156
157#[derive(Clone)]
163struct FinalizedToolCall {
164 tool_call: ToolCall,
165 result: AgentToolResult,
166 is_error: bool,
167}
168
169struct ExecutedToolBatch {
172 messages: Vec<ToolResultMessage>,
173 terminate: bool,
174}
175
176async fn run_loop(
177 current_context: &mut AgentContext,
178 new_messages: &mut Vec<AgentMessage>,
179 config: &AgentLoopConfig,
180 emit: &Arc<dyn AgentEmitter>,
181 stream_fn: &StreamFn,
182) -> Result<LoopOutcome, AgentError> {
183 let mut first_turn = true;
184 let mut pending_messages = drain_steering(config).await;
186
187 loop {
190 let mut has_more_tool_calls = true;
191
192 while has_more_tool_calls || !pending_messages.is_empty() {
194 if !first_turn {
195 emit_event(emit, AgentEvent::TurnStart).await;
196 } else {
197 first_turn = false;
198 }
199
200 if !pending_messages.is_empty() {
202 for message in pending_messages.drain(..) {
203 emit_event(
204 emit,
205 AgentEvent::MessageStart {
206 message: message.clone(),
207 },
208 )
209 .await;
210 emit_event(
211 emit,
212 AgentEvent::MessageEnd {
213 message: message.clone(),
214 },
215 )
216 .await;
217 current_context.messages.push(message.clone());
218 new_messages.push(message);
219 }
220 }
221
222 let message =
224 stream_assistant_response(current_context, config, emit, stream_fn).await?;
225 new_messages.push(AgentMessage::Assistant(Box::new(message.clone())));
226
227 if matches!(message.stop_reason, StopReason::Error | StopReason::Aborted) {
228 let am = AgentMessage::Assistant(Box::new(message.clone()));
229 emit_event(
230 emit,
231 AgentEvent::TurnEnd {
232 message: am,
233 tool_results: Vec::new(),
234 },
235 )
236 .await;
237 emit_event(
238 emit,
239 AgentEvent::AgentEnd {
240 messages: new_messages.clone(),
241 },
242 )
243 .await;
244 return Ok(LoopOutcome::from_stop(message.stop_reason));
245 }
246
247 let tool_calls: Vec<ToolCall> = message
249 .content
250 .iter()
251 .filter_map(|c| match c {
252 Content::ToolCall(tc) => Some(tc.clone()),
253 _ => None,
254 })
255 .collect();
256
257 let mut tool_results: Vec<ToolResultMessage> = Vec::new();
258 has_more_tool_calls = false;
259 if !tool_calls.is_empty() {
260 let batch = if matches!(message.stop_reason, StopReason::Length) {
261 fail_tool_calls_from_truncated_message(&tool_calls, emit).await?
263 } else {
264 execute_tool_calls(current_context, &message, &tool_calls, config, emit).await?
265 };
266 tool_results.extend(batch.messages);
267 has_more_tool_calls = !batch.terminate;
268
269 for result in &tool_results {
270 let am = AgentMessage::ToolResult(Box::new(result.clone()));
271 current_context.messages.push(am.clone());
272 new_messages.push(am);
273 }
274 }
275
276 if !tool_results.is_empty() {
277 if let Some(upd) = after_tool_results(
278 config,
279 &message,
280 &tool_results,
281 current_context,
282 new_messages,
283 )
284 .await
285 {
286 if let Some(ctx) = upd.context {
287 *current_context = ctx;
288 }
289 }
290 }
291
292 let am = AgentMessage::Assistant(Box::new(message.clone()));
293 emit_event(
294 emit,
295 AgentEvent::TurnEnd {
296 message: am,
297 tool_results: tool_results.clone(),
298 },
299 )
300 .await;
301
302 if let Some(upd) = prepare_next_turn(
306 config,
307 &message,
308 &tool_results,
309 current_context,
310 new_messages,
311 )
312 .await
313 {
314 if let Some(ctx) = upd.context {
315 *current_context = ctx;
316 }
317 }
318
319 if should_stop_after_turn(
320 config,
321 &message,
322 &tool_results,
323 current_context,
324 new_messages,
325 )
326 .await
327 {
328 emit_event(
329 emit,
330 AgentEvent::AgentEnd {
331 messages: new_messages.clone(),
332 },
333 )
334 .await;
335 return Ok(LoopOutcome::Completed);
336 }
337
338 if config.signal.is_cancelled() {
339 emit_event(
340 emit,
341 AgentEvent::AgentEnd {
342 messages: new_messages.clone(),
343 },
344 )
345 .await;
346 return Ok(LoopOutcome::Aborted);
347 }
348
349 pending_messages = drain_steering(config).await;
350 }
351
352 let follow_ups = drain_follow_up(config).await;
354 if !follow_ups.is_empty() {
355 pending_messages = follow_ups;
356 continue;
357 }
358 break;
359 }
360
361 emit_event(
362 emit,
363 AgentEvent::AgentEnd {
364 messages: new_messages.clone(),
365 },
366 )
367 .await;
368 Ok(LoopOutcome::Completed)
369}
370
371async fn stream_assistant_response(
379 context: &mut AgentContext,
380 config: &AgentLoopConfig,
381 emit: &Arc<dyn AgentEmitter>,
382 stream_fn: &StreamFn,
383) -> Result<AssistantMessage, AgentError> {
384 let messages = if let Some(transform) = &config.transform_context {
386 transform(context.messages.clone(), config.signal.clone()).await
387 } else {
388 context.messages.clone()
389 };
390
391 let llm_messages = (config.convert_to_llm)(messages).await;
393
394 let llm_context = rpi_ai::types::Context {
395 system_prompt: if context.system_prompt.is_empty() {
396 None
397 } else {
398 Some(context.system_prompt.clone())
399 },
400 messages: llm_messages,
401 tools: context.tools.iter().map(|t| t.schema().clone()).collect(),
402 };
403
404 let resolved_api_key = if let Some(get_key) = &config.get_api_key {
406 get_key(&config.model.provider)
407 .await
408 .or_else(|| config.api_key.clone())
409 } else {
410 config.api_key.clone()
411 };
412
413 let opts = config.to_stream_options(resolved_api_key);
414 let mut response = stream_fn(&config.model, &llm_context, &opts);
415
416 let mut added_partial = false;
417
418 while let Some(event) = response.next().await {
419 match &event {
420 AssistantMessageEvent::Start { partial } => {
421 let am = AgentMessage::Assistant(Box::new((**partial).clone()));
422 context.messages.push(am.clone());
423 added_partial = true;
424 emit_event(emit, AgentEvent::MessageStart { message: am }).await;
425 }
426 AssistantMessageEvent::TextStart { partial, .. }
427 | AssistantMessageEvent::TextDelta { partial, .. }
428 | AssistantMessageEvent::TextEnd { partial, .. }
429 | AssistantMessageEvent::ThinkingStart { partial, .. }
430 | AssistantMessageEvent::ThinkingDelta { partial, .. }
431 | AssistantMessageEvent::ThinkingEnd { partial, .. }
432 | AssistantMessageEvent::ToolCallStart { partial, .. }
433 | AssistantMessageEvent::ToolCallDelta { partial, .. }
434 | AssistantMessageEvent::ToolCallEnd { partial, .. } => {
435 if added_partial {
436 let am = AgentMessage::Assistant(Box::new((**partial).clone()));
437 if let Some(last) = context.messages.last_mut() {
438 *last = am.clone();
439 }
440 emit_event(
441 emit,
442 AgentEvent::MessageUpdate {
443 message: am,
444 assistant_message_event: event.clone(),
445 },
446 )
447 .await;
448 }
449 }
450 AssistantMessageEvent::Done { .. } | AssistantMessageEvent::Error { .. } => {
451 let final_message = response.result().await.map_err(|_| {
452 AgentError::Provider(
453 "assistant-message event stream ended without a terminal event".into(),
454 )
455 })?;
456 let am = AgentMessage::Assistant(Box::new(final_message.clone()));
457 if added_partial {
458 if let Some(last) = context.messages.last_mut() {
459 *last = am.clone();
460 }
461 } else {
462 context.messages.push(am.clone());
463 emit_event(
464 emit,
465 AgentEvent::MessageStart {
466 message: am.clone(),
467 },
468 )
469 .await;
470 }
471 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
472 return Ok(final_message);
473 }
474 }
475 }
476
477 let final_message = response.result().await.map_err(|_| {
480 AgentError::Provider("assistant-message event stream ended without a terminal event".into())
481 })?;
482 let am = AgentMessage::Assistant(Box::new(final_message.clone()));
483 if added_partial {
484 if let Some(last) = context.messages.last_mut() {
485 *last = am.clone();
486 }
487 } else {
488 context.messages.push(am.clone());
489 emit_event(
490 emit,
491 AgentEvent::MessageStart {
492 message: am.clone(),
493 },
494 )
495 .await;
496 }
497 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
498 Ok(final_message)
499}
500
501async fn fail_tool_calls_from_truncated_message(
510 tool_calls: &[ToolCall],
511 emit: &Arc<dyn AgentEmitter>,
512) -> Result<ExecutedToolBatch, AgentError> {
513 let mut messages: Vec<ToolResultMessage> = Vec::new();
514 for tool_call in tool_calls {
515 emit_event(
516 emit,
517 AgentEvent::ToolExecutionStart {
518 tool_call_id: tool_call.id.clone(),
519 tool_name: tool_call.name.clone(),
520 args: tool_call.arguments.clone(),
521 },
522 )
523 .await;
524 let reason = format!(
525 "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.",
526 tool_call.name
527 );
528 let result = create_error_tool_result(&reason);
529 emit_event(
530 emit,
531 AgentEvent::ToolExecutionEnd {
532 tool_call_id: tool_call.id.clone(),
533 tool_name: tool_call.name.clone(),
534 result: result.clone(),
535 is_error: true,
536 },
537 )
538 .await;
539 let trm = create_tool_result_message(tool_call, &result, true);
540 emit_tool_result_message(emit, &trm).await;
541 messages.push(trm);
542 }
543 Ok(ExecutedToolBatch {
544 messages,
545 terminate: false,
546 })
547}
548
549async fn execute_tool_calls(
557 current_context: &AgentContext,
558 assistant_message: &AssistantMessage,
559 tool_calls: &[ToolCall],
560 config: &AgentLoopConfig,
561 emit: &Arc<dyn AgentEmitter>,
562) -> Result<ExecutedToolBatch, AgentError> {
563 let has_sequential = tool_calls.iter().any(|tc| {
564 current_context
565 .tools
566 .iter()
567 .find(|t| t.schema().name == tc.name)
568 .map(|t| t.execution_mode() == ToolExecutionMode::Sequential)
569 .unwrap_or(false)
570 });
571 if config.tool_execution == ToolExecutionMode::Sequential || has_sequential {
572 execute_tool_calls_sequential(current_context, assistant_message, tool_calls, config, emit)
573 .await
574 } else {
575 execute_tool_calls_parallel(current_context, assistant_message, tool_calls, config, emit)
576 .await
577 }
578}
579
580async fn execute_tool_calls_sequential(
581 current_context: &AgentContext,
582 assistant_message: &AssistantMessage,
583 tool_calls: &[ToolCall],
584 config: &AgentLoopConfig,
585 emit: &Arc<dyn AgentEmitter>,
586) -> Result<ExecutedToolBatch, AgentError> {
587 let mut finalized_calls: Vec<FinalizedToolCall> = Vec::new();
588 let mut messages: Vec<ToolResultMessage> = Vec::new();
589
590 for tool_call in tool_calls {
591 emit_event(
592 emit,
593 AgentEvent::ToolExecutionStart {
594 tool_call_id: tool_call.id.clone(),
595 tool_name: tool_call.name.clone(),
596 args: tool_call.arguments.clone(),
597 },
598 )
599 .await;
600
601 let finalized =
602 run_one_tool_call(current_context, assistant_message, tool_call, config, emit).await?;
603
604 emit_tool_execution_end(emit, &finalized).await;
605 let trm =
606 create_tool_result_message(&finalized.tool_call, &finalized.result, finalized.is_error);
607 emit_tool_result_message(emit, &trm).await;
608 finalized_calls.push(finalized);
609 messages.push(trm);
610
611 if config.signal.is_cancelled() {
612 break;
613 }
614 }
615
616 Ok(ExecutedToolBatch {
617 messages,
618 terminate: should_terminate_tool_batch(&finalized_calls),
619 })
620}
621
622async fn execute_tool_calls_parallel(
633 current_context: &AgentContext,
634 assistant_message: &AssistantMessage,
635 tool_calls: &[ToolCall],
636 config: &AgentLoopConfig,
637 emit: &Arc<dyn AgentEmitter>,
638) -> Result<ExecutedToolBatch, AgentError> {
639 enum Entry {
641 Done(FinalizedToolCall),
642 Running(tokio::task::JoinHandle<FinalizedToolCall>),
643 }
644
645 let mut entries: Vec<Entry> = Vec::with_capacity(tool_calls.len());
646
647 for tool_call in tool_calls {
648 emit_event(
649 emit,
650 AgentEvent::ToolExecutionStart {
651 tool_call_id: tool_call.id.clone(),
652 tool_name: tool_call.name.clone(),
653 args: tool_call.arguments.clone(),
654 },
655 )
656 .await;
657
658 match prepare_tool_call(current_context, assistant_message, tool_call, config).await {
659 Prepared::Immediate { result, is_error } => {
660 let finalized = FinalizedToolCall {
661 tool_call: tool_call.clone(),
662 result,
663 is_error,
664 };
665 emit_tool_execution_end(emit, &finalized).await;
666 entries.push(Entry::Done(finalized));
667 }
668 Prepared::Ready { tool, args } => {
669 let tc = tool_call.clone();
673 let am = assistant_message.clone();
674 let ctx = current_context.clone();
675 let cfg = config.clone();
676 let emit2 = Arc::clone(emit);
677 let handle = tokio::spawn(async move {
678 let executed =
679 execute_prepared_tool_call(&tc, &tool, &args, &cfg, &emit2).await;
680 finalize_executed_tool_call(&ctx, &am, &tc, &args, executed, &cfg).await
681 });
682 entries.push(Entry::Running(handle));
683 }
684 }
685 if config.signal.is_cancelled() {
686 break;
687 }
688 }
689
690 let mut finalized_by_index: Vec<Option<FinalizedToolCall>> = vec![None; entries.len()];
693 let mut pending: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> = Vec::new();
694 for (i, e) in entries.into_iter().enumerate() {
695 match e {
696 Entry::Done(f) => {
697 finalized_by_index[i] = Some(f);
698 }
699 Entry::Running(h) => pending.push((i, h)),
700 }
701 }
702
703 while !pending.is_empty() {
704 if pending.len() == 1 {
705 let (i, h) = pending.remove(0);
707 let finalized = h.await.unwrap_or_else(|_| FinalizedToolCall {
708 tool_call: panicked_tool_call(),
709 result: create_error_tool_result("tool task panicked"),
710 is_error: true,
711 });
712 emit_tool_execution_end(emit, &finalized).await;
713 finalized_by_index[i] = Some(finalized);
714 break;
715 }
716
717 let mut resolved: Option<(usize, FinalizedToolCall)> = None;
721 let mut still_pending: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> =
722 Vec::with_capacity(pending.len());
723 for (i, h) in pending.drain(..) {
725 if resolved.is_none() && h.is_finished() {
726 let finalized = h.await.unwrap_or_else(|_| FinalizedToolCall {
727 tool_call: panicked_tool_call(),
728 result: create_error_tool_result("tool task panicked"),
729 is_error: true,
730 });
731 resolved = Some((i, finalized));
732 } else {
733 still_pending.push((i, h));
734 }
735 }
736 match resolved {
737 Some((i, finalized)) => {
738 emit_tool_execution_end(emit, &finalized).await;
739 finalized_by_index[i] = Some(finalized);
740 pending = still_pending;
741 }
742 None => {
743 pending = still_pending;
746 race_one_and_collect(emit, &mut pending, &mut finalized_by_index).await;
747 }
748 }
749 }
750
751 let mut messages: Vec<ToolResultMessage> = Vec::new();
754 let mut finalized_calls: Vec<FinalizedToolCall> = Vec::new();
755 for slot in finalized_by_index.into_iter() {
756 let finalized = slot.expect("every tool call finalized");
757 let trm =
758 create_tool_result_message(&finalized.tool_call, &finalized.result, finalized.is_error);
759 emit_tool_result_message(emit, &trm).await;
760 messages.push(trm);
761 finalized_calls.push(finalized);
762 }
763
764 Ok(ExecutedToolBatch {
765 messages,
766 terminate: should_terminate_tool_batch(&finalized_calls),
767 })
768}
769
770async fn race_one_and_collect(
779 emit: &Arc<dyn AgentEmitter>,
780 pending: &mut Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)>,
781 finalized_by_index: &mut [Option<FinalizedToolCall>],
782) {
783 let indexed: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> = std::mem::take(pending);
788 let mut boxed: Vec<
789 std::pin::Pin<Box<dyn std::future::Future<Output = (usize, FinalizedToolCall)> + Send>>,
790 > = Vec::with_capacity(indexed.len());
791 for (i, h) in indexed {
792 boxed.push(Box::pin(async move {
793 let f = h.await.unwrap_or_else(|_| FinalizedToolCall {
794 tool_call: panicked_tool_call(),
795 result: create_error_tool_result("tool task panicked"),
796 is_error: true,
797 });
798 (i, f)
799 }));
800 }
801
802 while !boxed.is_empty() {
803 let (outcome, _idx, rest) = futures::future::select_all(boxed).await;
805 boxed = rest;
806 let (i, finalized) = outcome;
807 emit_tool_execution_end(emit, &finalized).await;
808 finalized_by_index[i] = Some(finalized);
809 }
810}
811
812async fn run_one_tool_call(
814 current_context: &AgentContext,
815 assistant_message: &AssistantMessage,
816 tool_call: &ToolCall,
817 config: &AgentLoopConfig,
818 emit: &Arc<dyn AgentEmitter>,
819) -> Result<FinalizedToolCall, AgentError> {
820 match prepare_tool_call(current_context, assistant_message, tool_call, config).await {
821 Prepared::Immediate { result, is_error } => Ok(FinalizedToolCall {
822 tool_call: tool_call.clone(),
823 result,
824 is_error,
825 }),
826 Prepared::Ready { tool, args } => {
827 let executed = execute_prepared_tool_call(tool_call, &tool, &args, config, emit).await;
828 Ok(finalize_executed_tool_call(
829 current_context,
830 assistant_message,
831 tool_call,
832 &args,
833 executed,
834 config,
835 )
836 .await)
837 }
838 }
839}
840
841enum Prepared {
843 Immediate {
845 result: AgentToolResult,
846 is_error: bool,
847 },
848 Ready {
850 tool: Arc<dyn AgentTool>,
851 args: serde_json::Value,
852 },
853}
854
855async fn prepare_tool_call(
858 current_context: &AgentContext,
859 assistant_message: &AssistantMessage,
860 tool_call: &ToolCall,
861 config: &AgentLoopConfig,
862) -> Prepared {
863 let tool = current_context
864 .tools
865 .iter()
866 .find(|t| t.schema().name == tool_call.name)
867 .cloned();
868 let tool = match tool {
869 Some(t) => t,
870 None => {
871 return Prepared::Immediate {
872 result: create_error_tool_result(&format!("Tool {} not found", tool_call.name)),
873 is_error: true,
874 };
875 }
876 };
877
878 let prepared_args = match tool.prepare_arguments(tool_call.arguments.clone()) {
880 Ok(v) => v,
881 Err(e) => {
882 return Prepared::Immediate {
883 result: create_error_tool_result(&e.to_string()),
884 is_error: true,
885 };
886 }
887 };
888 let mut prepared_tool_call = tool_call.clone();
889 prepared_tool_call.arguments = prepared_args;
890
891 let validated_args = match validate_tool_arguments(tool.schema(), &prepared_tool_call) {
892 Ok(v) => v,
893 Err(e) => {
894 return Prepared::Immediate {
895 result: create_error_tool_result(&e.to_string()),
896 is_error: true,
897 };
898 }
899 };
900
901 let mut validated_args = validated_args;
908 if let Some(before) = &config.before_tool_call {
909 let ctx = BeforeToolCallContext {
910 assistant_message,
911 tool_call: &prepared_tool_call,
912 args: &validated_args,
913 context: current_context,
914 };
915 let before_result = before(ctx, config.signal.clone()).await;
916 if config.signal.is_cancelled() {
917 return Prepared::Immediate {
918 result: create_error_tool_result("Operation aborted"),
919 is_error: true,
920 };
921 }
922 if let Some(br) = before_result {
923 if let Some(replacement) = br.args {
924 validated_args = replacement;
925 }
926 if br.block {
927 let mut result = create_error_tool_result(
928 &br.reason
929 .unwrap_or_else(|| "Tool execution was blocked".to_string()),
930 );
931 if br.terminate {
932 result.terminate = true;
933 }
934 return Prepared::Immediate {
935 result,
936 is_error: true,
937 };
938 }
939 }
940 }
941
942 if config.signal.is_cancelled() {
943 return Prepared::Immediate {
944 result: create_error_tool_result("Operation aborted"),
945 is_error: true,
946 };
947 }
948
949 Prepared::Ready {
950 tool,
951 args: validated_args,
952 }
953}
954
955async fn execute_prepared_tool_call(
958 tool_call: &ToolCall,
959 tool: &Arc<dyn AgentTool>,
960 args: &serde_json::Value,
961 config: &AgentLoopConfig,
962 emit: &Arc<dyn AgentEmitter>,
963) -> ExecutedToolCallOutcome {
964 let accepting_updates = Arc::new(AtomicBool::new(true));
967 let tool_call_id = tool_call.id.clone();
968 let tool_name = tool_call.name.clone();
969 let args_clone = args.clone();
970 let emit_clone = Arc::clone(emit);
971
972 let on_update: Arc<dyn Fn(crate::types::ToolResultPartial) + Send + Sync> = {
973 let gate = Arc::clone(&accepting_updates);
974 Arc::new(move |partial: crate::types::ToolResultPartial| {
975 if !gate.load(Ordering::SeqCst) {
976 return;
977 }
978 let ev = AgentEvent::ToolExecutionUpdate {
981 tool_call_id: tool_call_id.clone(),
982 tool_name: tool_name.clone(),
983 args: args_clone.clone(),
984 partial_result: Arc::new(partial),
985 };
986 emit_clone.try_emit(ev);
991 })
992 };
993
994 let child_token = config.signal.child_token();
995 match tool
996 .execute(&tool_call.id, args.clone(), child_token, on_update)
997 .await
998 {
999 Ok(result) => {
1000 accepting_updates.store(false, Ordering::SeqCst);
1001 ExecutedToolCallOutcome {
1002 result,
1003 is_error: false,
1004 }
1005 }
1006 Err(e) => {
1007 accepting_updates.store(false, Ordering::SeqCst);
1008 ExecutedToolCallOutcome {
1009 result: create_error_tool_result(&e.to_string()),
1010 is_error: true,
1011 }
1012 }
1013 }
1014}
1015
1016struct ExecutedToolCallOutcome {
1019 result: AgentToolResult,
1020 is_error: bool,
1021}
1022
1023async fn finalize_executed_tool_call(
1025 current_context: &AgentContext,
1026 assistant_message: &AssistantMessage,
1027 tool_call: &ToolCall,
1028 args: &serde_json::Value,
1029 executed: ExecutedToolCallOutcome,
1030 config: &AgentLoopConfig,
1031) -> FinalizedToolCall {
1032 let mut result = executed.result;
1033 let mut is_error = executed.is_error;
1034
1035 if let Some(after) = &config.after_tool_call {
1036 let ctx = AfterToolCallContext {
1037 assistant_message,
1038 tool_call,
1039 args,
1040 result: &result,
1041 is_error,
1042 context: current_context,
1043 };
1044 match after(ctx, config.signal.clone()).await {
1045 Some(after_result) => {
1046 if let Some(c) = after_result.content {
1047 result.content = c;
1048 }
1049 if let Some(d) = after_result.details {
1050 result.details = d;
1051 }
1052 if let Some(u) = after_result.usage {
1053 result.usage = Some(u);
1054 }
1055 if let Some(t) = after_result.terminate {
1056 result.terminate = t;
1057 }
1058 if let Some(ie) = after_result.is_error {
1059 is_error = ie;
1060 }
1061 }
1062 None => {}
1063 }
1064 }
1065
1066 FinalizedToolCall {
1067 tool_call: tool_call.clone(),
1068 result,
1069 is_error,
1070 }
1071}
1072
1073fn should_terminate_tool_batch(finalized_calls: &[FinalizedToolCall]) -> bool {
1080 !finalized_calls.is_empty() && finalized_calls.iter().all(|f| f.result.terminate)
1081}
1082
1083fn create_error_tool_result(message: &str) -> AgentToolResult {
1086 AgentToolResult::error_text(message)
1087}
1088
1089fn create_tool_result_message(
1093 tool_call: &ToolCall,
1094 result: &AgentToolResult,
1095 is_error: bool,
1096) -> ToolResultMessage {
1097 ToolResultMessage {
1098 role: ToolResultRole,
1099 tool_call_id: tool_call.id.clone(),
1100 tool_name: tool_call.name.clone(),
1101 content: result.clone().into_content(),
1102 details: Some(result.details.clone()),
1103 usage: result.usage.clone(),
1104 added_tool_names: result.added_tool_names.clone(),
1105 is_error,
1106 timestamp: now_ms(),
1107 }
1108}
1109
1110async fn emit_tool_execution_end(emit: &Arc<dyn AgentEmitter>, finalized: &FinalizedToolCall) {
1112 emit_event(
1113 emit,
1114 AgentEvent::ToolExecutionEnd {
1115 tool_call_id: finalized.tool_call.id.clone(),
1116 tool_name: finalized.tool_call.name.clone(),
1117 result: finalized.result.clone(),
1118 is_error: finalized.is_error,
1119 },
1120 )
1121 .await;
1122}
1123
1124async fn emit_tool_result_message(emit: &Arc<dyn AgentEmitter>, trm: &ToolResultMessage) {
1127 let am = AgentMessage::ToolResult(Box::new(trm.clone()));
1128 emit_event(
1129 emit,
1130 AgentEvent::MessageStart {
1131 message: am.clone(),
1132 },
1133 )
1134 .await;
1135 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
1136}
1137
1138async fn drain_steering(config: &AgentLoopConfig) -> Vec<AgentMessage> {
1140 if let Some(hook) = &config.get_steering_messages {
1141 hook().await
1142 } else {
1143 Vec::new()
1144 }
1145}
1146
1147async fn drain_follow_up(config: &AgentLoopConfig) -> Vec<AgentMessage> {
1149 if let Some(hook) = &config.get_follow_up_messages {
1150 hook().await
1151 } else {
1152 Vec::new()
1153 }
1154}
1155
1156async fn prepare_next_turn(
1158 config: &AgentLoopConfig,
1159 message: &AssistantMessage,
1160 tool_results: &[ToolResultMessage],
1161 context: &AgentContext,
1162 new_messages: &[AgentMessage],
1163) -> Option<crate::types::AgentLoopTurnUpdate> {
1164 if let Some(hook) = &config.prepare_next_turn {
1165 let ctx = crate::types::ShouldStopAfterTurnContext {
1166 message,
1167 tool_results,
1168 context,
1169 new_messages,
1170 };
1171 hook(ctx).await
1172 } else {
1173 None
1174 }
1175}
1176
1177async fn after_tool_results(
1178 config: &AgentLoopConfig,
1179 message: &AssistantMessage,
1180 tool_results: &[ToolResultMessage],
1181 context: &AgentContext,
1182 new_messages: &[AgentMessage],
1183) -> Option<crate::types::AgentLoopTurnUpdate> {
1184 let hook = config.after_tool_results.as_ref()?;
1185 let ctx = crate::types::ShouldStopAfterTurnContext {
1186 message,
1187 tool_results,
1188 context,
1189 new_messages,
1190 };
1191 hook(ctx).await
1192}
1193
1194async fn should_stop_after_turn(
1196 config: &AgentLoopConfig,
1197 message: &AssistantMessage,
1198 tool_results: &[ToolResultMessage],
1199 context: &AgentContext,
1200 new_messages: &[AgentMessage],
1201) -> bool {
1202 if let Some(hook) = &config.should_stop_after_turn {
1203 let ctx = crate::types::ShouldStopAfterTurnContext {
1204 message,
1205 tool_results,
1206 context,
1207 new_messages,
1208 };
1209 hook(ctx).await
1210 } else {
1211 false
1212 }
1213}
1214
1215async fn emit_event(emit: &Arc<dyn AgentEmitter>, event: AgentEvent) {
1217 emit.emit(event).await;
1218}
1219
1220fn now_ms() -> i64 {
1223 use std::sync::atomic::{AtomicI64, Ordering};
1224 static T: AtomicI64 = AtomicI64::new(1);
1225 T.fetch_add(1, Ordering::Relaxed)
1226}
1227
1228fn panicked_tool_call() -> ToolCall {
1230 ToolCall {
1231 kind: ToolCallType,
1232 id: "<panic>".to_string(),
1233 name: "<panic>".to_string(),
1234 arguments: serde_json::Value::Null,
1235 thought_signature: None,
1236 namespace: None,
1237 }
1238}