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 None,
116 )
117 .await?;
118 Ok(new_messages)
119}
120
121pub async fn run_agent_loop_continue(
125 context: AgentContext,
126 config: AgentLoopConfig,
127 emit: Arc<dyn AgentEmitter>,
128 stream_fn: StreamFn,
129) -> Result<NewMessages, AgentError> {
130 if context.messages.is_empty() {
131 return Err(AgentError::State(
132 "cannot continue: no messages in context".into(),
133 ));
134 }
135 if context.messages.last().unwrap().is_assistant() {
136 return Err(AgentError::State(
137 "cannot continue from message role: assistant".into(),
138 ));
139 }
140
141 let mut new_messages: Vec<AgentMessage> = Vec::new();
142 let mut current_context = context;
143
144 emit_event(&emit, AgentEvent::AgentStart).await;
145 emit_event(&emit, AgentEvent::TurnStart).await;
146
147 run_loop(
148 &mut current_context,
149 &mut new_messages,
150 &config,
151 &emit,
152 &stream_fn,
153 None,
154 )
155 .await?;
156 Ok(new_messages)
157}
158
159pub async fn run_agent_loop_from_assistant(
171 context: AgentContext,
172 config: AgentLoopConfig,
173 emit: Arc<dyn AgentEmitter>,
174 stream_fn: StreamFn,
175) -> Result<NewMessages, AgentError> {
176 let mut current_context = context;
177 let Some(AgentMessage::Assistant(assistant)) = current_context.messages.last().cloned() else {
178 return Err(AgentError::State(
179 "cannot continue from an assistant whose tools already ran: the last message is not an assistant message"
180 .into(),
181 ));
182 };
183 let mut new_messages: Vec<AgentMessage> = Vec::new();
184
185 emit_event(&emit, AgentEvent::AgentStart).await;
186 emit_event(&emit, AgentEvent::TurnStart).await;
188
189 run_loop(
190 &mut current_context,
191 &mut new_messages,
192 &config,
193 &emit,
194 &stream_fn,
195 Some(*assistant),
196 )
197 .await?;
198 Ok(new_messages)
199}
200
201#[derive(Clone)]
207struct FinalizedToolCall {
208 tool_call: ToolCall,
209 result: AgentToolResult,
210 is_error: bool,
211}
212
213struct ExecutedToolBatch {
216 messages: Vec<ToolResultMessage>,
217 terminate: bool,
218}
219
220async fn run_loop(
221 current_context: &mut AgentContext,
222 new_messages: &mut Vec<AgentMessage>,
223 config: &AgentLoopConfig,
224 emit: &Arc<dyn AgentEmitter>,
225 stream_fn: &StreamFn,
226 pending_assistant: Option<AssistantMessage>,
232) -> Result<LoopOutcome, AgentError> {
233 let mut pending_assistant = pending_assistant;
234 let mut first_turn = true;
235 let mut pending_messages = drain_steering(config).await;
237
238 loop {
241 let mut has_more_tool_calls = true;
242
243 while has_more_tool_calls || !pending_messages.is_empty() {
245 if !first_turn {
246 emit_event(emit, AgentEvent::TurnStart).await;
247 } else {
248 first_turn = false;
249 }
250
251 if !pending_messages.is_empty() {
253 for message in pending_messages.drain(..) {
254 emit_event(
255 emit,
256 AgentEvent::MessageStart {
257 message: message.clone(),
258 },
259 )
260 .await;
261 emit_event(
262 emit,
263 AgentEvent::MessageEnd {
264 message: message.clone(),
265 },
266 )
267 .await;
268 current_context.messages.push(message.clone());
269 new_messages.push(message);
270 }
271 }
272
273 let message = match pending_assistant.take() {
276 Some(message) => message,
277 None => {
278 let message =
279 stream_assistant_response(current_context, config, emit, stream_fn).await?;
280 new_messages.push(AgentMessage::Assistant(Box::new(message.clone())));
281 message
282 }
283 };
284
285 if matches!(message.stop_reason, StopReason::Error | StopReason::Aborted) {
286 let am = AgentMessage::Assistant(Box::new(message.clone()));
287 emit_event(
288 emit,
289 AgentEvent::TurnEnd {
290 message: am,
291 tool_results: Vec::new(),
292 },
293 )
294 .await;
295 emit_event(
296 emit,
297 AgentEvent::AgentEnd {
298 messages: new_messages.clone(),
299 },
300 )
301 .await;
302 return Ok(LoopOutcome::from_stop(message.stop_reason));
303 }
304
305 let tool_calls: Vec<ToolCall> = message
307 .content
308 .iter()
309 .filter_map(|c| match c {
310 Content::ToolCall(tc) => Some(tc.clone()),
311 _ => None,
312 })
313 .collect();
314
315 let mut tool_results: Vec<ToolResultMessage> = Vec::new();
316 has_more_tool_calls = false;
317 if !tool_calls.is_empty() {
318 let batch = if matches!(message.stop_reason, StopReason::Length) {
319 fail_tool_calls_from_truncated_message(&tool_calls, emit).await?
321 } else {
322 execute_tool_calls(current_context, &message, &tool_calls, config, emit).await?
323 };
324 tool_results.extend(batch.messages);
325 has_more_tool_calls = !batch.terminate;
326
327 for result in &tool_results {
328 let am = AgentMessage::ToolResult(Box::new(result.clone()));
329 current_context.messages.push(am.clone());
330 new_messages.push(am);
331 }
332 }
333
334 if !tool_results.is_empty() {
335 if let Some(upd) = after_tool_results(
336 config,
337 &message,
338 &tool_results,
339 current_context,
340 new_messages,
341 )
342 .await
343 {
344 if let Some(ctx) = upd.context {
345 *current_context = ctx;
346 }
347 }
348 }
349
350 let am = AgentMessage::Assistant(Box::new(message.clone()));
351 emit_event(
352 emit,
353 AgentEvent::TurnEnd {
354 message: am,
355 tool_results: tool_results.clone(),
356 },
357 )
358 .await;
359
360 if let Some(upd) = prepare_next_turn(
364 config,
365 &message,
366 &tool_results,
367 current_context,
368 new_messages,
369 )
370 .await
371 {
372 if let Some(ctx) = upd.context {
373 *current_context = ctx;
374 }
375 }
376
377 if should_stop_after_turn(
378 config,
379 &message,
380 &tool_results,
381 current_context,
382 new_messages,
383 )
384 .await
385 {
386 emit_event(
387 emit,
388 AgentEvent::AgentEnd {
389 messages: new_messages.clone(),
390 },
391 )
392 .await;
393 return Ok(LoopOutcome::Completed);
394 }
395
396 pending_messages = drain_steering(config).await;
403 if config.signal.is_cancelled() && pending_messages.is_empty() {
404 emit_event(
405 emit,
406 AgentEvent::AgentEnd {
407 messages: new_messages.clone(),
408 },
409 )
410 .await;
411 return Ok(LoopOutcome::Aborted);
412 }
413 }
414
415 let follow_ups = drain_follow_up(config).await;
417 if !follow_ups.is_empty() {
418 pending_messages = follow_ups;
419 continue;
420 }
421 break;
422 }
423
424 emit_event(
425 emit,
426 AgentEvent::AgentEnd {
427 messages: new_messages.clone(),
428 },
429 )
430 .await;
431 Ok(LoopOutcome::Completed)
432}
433
434async fn stream_assistant_response(
442 context: &mut AgentContext,
443 config: &AgentLoopConfig,
444 emit: &Arc<dyn AgentEmitter>,
445 stream_fn: &StreamFn,
446) -> Result<AssistantMessage, AgentError> {
447 let messages = if let Some(transform) = &config.transform_context {
449 transform(context.messages.clone(), config.signal.clone()).await
450 } else {
451 context.messages.clone()
452 };
453
454 let llm_messages = (config.convert_to_llm)(messages).await;
456
457 let llm_context = rpi_ai::types::Context {
458 system_prompt: if context.system_prompt.is_empty() {
459 None
460 } else {
461 Some(context.system_prompt.clone())
462 },
463 messages: llm_messages,
464 tools: context.tools.iter().map(|t| t.schema().clone()).collect(),
465 };
466
467 let resolved_api_key = if let Some(get_key) = &config.get_api_key {
469 get_key(&config.model.provider)
470 .await
471 .or_else(|| config.api_key.clone())
472 } else {
473 config.api_key.clone()
474 };
475
476 let opts = config.to_stream_options(resolved_api_key);
477 let mut response = stream_fn(&config.model, &llm_context, &opts);
478
479 let mut added_partial = false;
480
481 while let Some(event) = response.next().await {
482 match &event {
483 AssistantMessageEvent::Start { partial } => {
484 let am = AgentMessage::Assistant(Box::new((**partial).clone()));
485 context.messages.push(am.clone());
486 added_partial = true;
487 emit_event(emit, AgentEvent::MessageStart { message: am }).await;
488 }
489 AssistantMessageEvent::TextStart { partial, .. }
490 | AssistantMessageEvent::TextDelta { partial, .. }
491 | AssistantMessageEvent::TextEnd { partial, .. }
492 | AssistantMessageEvent::ThinkingStart { partial, .. }
493 | AssistantMessageEvent::ThinkingDelta { partial, .. }
494 | AssistantMessageEvent::ThinkingEnd { partial, .. }
495 | AssistantMessageEvent::ToolCallStart { partial, .. }
496 | AssistantMessageEvent::ToolCallDelta { partial, .. }
497 | AssistantMessageEvent::ToolCallEnd { partial, .. } => {
498 if added_partial {
499 emit_event(
506 emit,
507 AgentEvent::MessageUpdate {
508 message: Arc::clone(partial),
512 assistant_message_event: event.clone(),
513 },
514 )
515 .await;
516 }
517 }
518 AssistantMessageEvent::Done { .. } | AssistantMessageEvent::Error { .. } => {
519 let final_message = response.result().await.map_err(|_| {
520 AgentError::Provider(
521 "assistant-message event stream ended without a terminal event".into(),
522 )
523 })?;
524 let am = AgentMessage::Assistant(Box::new(final_message.clone()));
525 if added_partial {
526 if let Some(last) = context.messages.last_mut() {
527 *last = am.clone();
528 }
529 } else {
530 context.messages.push(am.clone());
531 emit_event(
532 emit,
533 AgentEvent::MessageStart {
534 message: am.clone(),
535 },
536 )
537 .await;
538 }
539 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
540 return Ok(final_message);
541 }
542 }
543 }
544
545 let final_message = response.result().await.map_err(|_| {
548 AgentError::Provider("assistant-message event stream ended without a terminal event".into())
549 })?;
550 let am = AgentMessage::Assistant(Box::new(final_message.clone()));
551 if added_partial {
552 if let Some(last) = context.messages.last_mut() {
553 *last = am.clone();
554 }
555 } else {
556 context.messages.push(am.clone());
557 emit_event(
558 emit,
559 AgentEvent::MessageStart {
560 message: am.clone(),
561 },
562 )
563 .await;
564 }
565 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
566 Ok(final_message)
567}
568
569async fn fail_tool_calls_from_truncated_message(
578 tool_calls: &[ToolCall],
579 emit: &Arc<dyn AgentEmitter>,
580) -> Result<ExecutedToolBatch, AgentError> {
581 let mut messages: Vec<ToolResultMessage> = Vec::new();
582 for tool_call in tool_calls {
583 emit_event(
584 emit,
585 AgentEvent::ToolExecutionStart {
586 tool_call_id: tool_call.id.clone(),
587 tool_name: tool_call.name.clone(),
588 args: tool_call.arguments.clone(),
589 },
590 )
591 .await;
592 let reason = format!(
593 "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.",
594 tool_call.name
595 );
596 let result = create_error_tool_result(&reason);
597 emit_event(
598 emit,
599 AgentEvent::ToolExecutionEnd {
600 tool_call_id: tool_call.id.clone(),
601 tool_name: tool_call.name.clone(),
602 result: result.clone(),
603 is_error: true,
604 },
605 )
606 .await;
607 let trm = create_tool_result_message(tool_call, &result, true);
608 emit_tool_result_message(emit, &trm).await;
609 messages.push(trm);
610 }
611 Ok(ExecutedToolBatch {
612 messages,
613 terminate: false,
614 })
615}
616
617async fn execute_tool_calls(
625 current_context: &AgentContext,
626 assistant_message: &AssistantMessage,
627 tool_calls: &[ToolCall],
628 config: &AgentLoopConfig,
629 emit: &Arc<dyn AgentEmitter>,
630) -> Result<ExecutedToolBatch, AgentError> {
631 let has_sequential = tool_calls.iter().any(|tc| {
632 current_context
633 .tools
634 .iter()
635 .find(|t| t.schema().name == tc.name)
636 .map(|t| t.execution_mode() == ToolExecutionMode::Sequential)
637 .unwrap_or(false)
638 });
639 if config.tool_execution == ToolExecutionMode::Sequential || has_sequential {
640 execute_tool_calls_sequential(current_context, assistant_message, tool_calls, config, emit)
641 .await
642 } else {
643 execute_tool_calls_parallel(current_context, assistant_message, tool_calls, config, emit)
644 .await
645 }
646}
647
648async fn execute_tool_calls_sequential(
649 current_context: &AgentContext,
650 assistant_message: &AssistantMessage,
651 tool_calls: &[ToolCall],
652 config: &AgentLoopConfig,
653 emit: &Arc<dyn AgentEmitter>,
654) -> Result<ExecutedToolBatch, AgentError> {
655 let mut finalized_calls: Vec<FinalizedToolCall> = Vec::new();
656 let mut messages: Vec<ToolResultMessage> = Vec::new();
657
658 for tool_call in tool_calls {
659 emit_event(
660 emit,
661 AgentEvent::ToolExecutionStart {
662 tool_call_id: tool_call.id.clone(),
663 tool_name: tool_call.name.clone(),
664 args: tool_call.arguments.clone(),
665 },
666 )
667 .await;
668
669 let finalized =
670 run_one_tool_call(current_context, assistant_message, tool_call, config, emit).await?;
671
672 emit_tool_execution_end(emit, &finalized).await;
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 finalized_calls.push(finalized);
677 messages.push(trm);
678
679 if config.signal.is_cancelled() {
680 break;
681 }
682 }
683
684 Ok(ExecutedToolBatch {
685 messages,
686 terminate: should_terminate_tool_batch(&finalized_calls),
687 })
688}
689
690async fn execute_tool_calls_parallel(
701 current_context: &AgentContext,
702 assistant_message: &AssistantMessage,
703 tool_calls: &[ToolCall],
704 config: &AgentLoopConfig,
705 emit: &Arc<dyn AgentEmitter>,
706) -> Result<ExecutedToolBatch, AgentError> {
707 enum Entry {
709 Done(FinalizedToolCall),
710 Running(tokio::task::JoinHandle<FinalizedToolCall>),
711 }
712
713 let mut entries: Vec<Entry> = Vec::with_capacity(tool_calls.len());
714
715 for tool_call in tool_calls {
716 emit_event(
717 emit,
718 AgentEvent::ToolExecutionStart {
719 tool_call_id: tool_call.id.clone(),
720 tool_name: tool_call.name.clone(),
721 args: tool_call.arguments.clone(),
722 },
723 )
724 .await;
725
726 match prepare_tool_call(current_context, assistant_message, tool_call, config).await {
727 Prepared::Immediate { result, is_error } => {
728 let finalized = FinalizedToolCall {
729 tool_call: tool_call.clone(),
730 result,
731 is_error,
732 };
733 emit_tool_execution_end(emit, &finalized).await;
734 entries.push(Entry::Done(finalized));
735 }
736 Prepared::Ready { tool, args } => {
737 let tc = tool_call.clone();
741 let am = assistant_message.clone();
742 let ctx = current_context.clone();
743 let cfg = config.clone();
744 let emit2 = Arc::clone(emit);
745 let handle = tokio::spawn(async move {
746 let executed =
747 execute_prepared_tool_call(&tc, &tool, &args, &cfg, &emit2).await;
748 finalize_executed_tool_call(&ctx, &am, &tc, &args, executed, &cfg).await
749 });
750 entries.push(Entry::Running(handle));
751 }
752 }
753 if config.signal.is_cancelled() {
754 break;
755 }
756 }
757
758 let mut finalized_by_index: Vec<Option<FinalizedToolCall>> = vec![None; entries.len()];
761 let mut pending: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> = Vec::new();
762 for (i, e) in entries.into_iter().enumerate() {
763 match e {
764 Entry::Done(f) => {
765 finalized_by_index[i] = Some(f);
766 }
767 Entry::Running(h) => pending.push((i, h)),
768 }
769 }
770
771 while !pending.is_empty() {
772 if pending.len() == 1 {
773 let (i, h) = pending.remove(0);
775 let finalized = h.await.unwrap_or_else(|_| FinalizedToolCall {
776 tool_call: panicked_tool_call(),
777 result: create_error_tool_result("tool task panicked"),
778 is_error: true,
779 });
780 emit_tool_execution_end(emit, &finalized).await;
781 finalized_by_index[i] = Some(finalized);
782 break;
783 }
784
785 let mut resolved: Option<(usize, FinalizedToolCall)> = None;
789 let mut still_pending: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> =
790 Vec::with_capacity(pending.len());
791 for (i, h) in pending.drain(..) {
793 if resolved.is_none() && h.is_finished() {
794 let finalized = h.await.unwrap_or_else(|_| FinalizedToolCall {
795 tool_call: panicked_tool_call(),
796 result: create_error_tool_result("tool task panicked"),
797 is_error: true,
798 });
799 resolved = Some((i, finalized));
800 } else {
801 still_pending.push((i, h));
802 }
803 }
804 match resolved {
805 Some((i, finalized)) => {
806 emit_tool_execution_end(emit, &finalized).await;
807 finalized_by_index[i] = Some(finalized);
808 pending = still_pending;
809 }
810 None => {
811 pending = still_pending;
814 race_one_and_collect(emit, &mut pending, &mut finalized_by_index).await;
815 }
816 }
817 }
818
819 let mut messages: Vec<ToolResultMessage> = Vec::new();
822 let mut finalized_calls: Vec<FinalizedToolCall> = Vec::new();
823 for slot in finalized_by_index.into_iter() {
824 let finalized = slot.expect("every tool call finalized");
825 let trm =
826 create_tool_result_message(&finalized.tool_call, &finalized.result, finalized.is_error);
827 emit_tool_result_message(emit, &trm).await;
828 messages.push(trm);
829 finalized_calls.push(finalized);
830 }
831
832 Ok(ExecutedToolBatch {
833 messages,
834 terminate: should_terminate_tool_batch(&finalized_calls),
835 })
836}
837
838async fn race_one_and_collect(
847 emit: &Arc<dyn AgentEmitter>,
848 pending: &mut Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)>,
849 finalized_by_index: &mut [Option<FinalizedToolCall>],
850) {
851 let indexed: Vec<(usize, tokio::task::JoinHandle<FinalizedToolCall>)> = std::mem::take(pending);
856 let mut boxed: Vec<
857 std::pin::Pin<Box<dyn std::future::Future<Output = (usize, FinalizedToolCall)> + Send>>,
858 > = Vec::with_capacity(indexed.len());
859 for (i, h) in indexed {
860 boxed.push(Box::pin(async move {
861 let f = h.await.unwrap_or_else(|_| FinalizedToolCall {
862 tool_call: panicked_tool_call(),
863 result: create_error_tool_result("tool task panicked"),
864 is_error: true,
865 });
866 (i, f)
867 }));
868 }
869
870 while !boxed.is_empty() {
871 let (outcome, _idx, rest) = futures::future::select_all(boxed).await;
873 boxed = rest;
874 let (i, finalized) = outcome;
875 emit_tool_execution_end(emit, &finalized).await;
876 finalized_by_index[i] = Some(finalized);
877 }
878}
879
880async fn run_one_tool_call(
882 current_context: &AgentContext,
883 assistant_message: &AssistantMessage,
884 tool_call: &ToolCall,
885 config: &AgentLoopConfig,
886 emit: &Arc<dyn AgentEmitter>,
887) -> Result<FinalizedToolCall, AgentError> {
888 match prepare_tool_call(current_context, assistant_message, tool_call, config).await {
889 Prepared::Immediate { result, is_error } => Ok(FinalizedToolCall {
890 tool_call: tool_call.clone(),
891 result,
892 is_error,
893 }),
894 Prepared::Ready { tool, args } => {
895 let executed = execute_prepared_tool_call(tool_call, &tool, &args, config, emit).await;
896 Ok(finalize_executed_tool_call(
897 current_context,
898 assistant_message,
899 tool_call,
900 &args,
901 executed,
902 config,
903 )
904 .await)
905 }
906 }
907}
908
909enum Prepared {
911 Immediate {
913 result: AgentToolResult,
914 is_error: bool,
915 },
916 Ready {
918 tool: Arc<dyn AgentTool>,
919 args: serde_json::Value,
920 },
921}
922
923async fn prepare_tool_call(
926 current_context: &AgentContext,
927 assistant_message: &AssistantMessage,
928 tool_call: &ToolCall,
929 config: &AgentLoopConfig,
930) -> Prepared {
931 let tool = current_context
932 .tools
933 .iter()
934 .find(|t| t.schema().name == tool_call.name)
935 .cloned();
936 let tool = match tool {
937 Some(t) => t,
938 None => {
939 return Prepared::Immediate {
940 result: create_error_tool_result(&format!("Tool {} not found", tool_call.name)),
941 is_error: true,
942 };
943 }
944 };
945
946 let prepared_args = match tool.prepare_arguments(tool_call.arguments.clone()) {
948 Ok(v) => v,
949 Err(e) => {
950 return Prepared::Immediate {
951 result: create_error_tool_result(&e.to_string()),
952 is_error: true,
953 };
954 }
955 };
956 let mut prepared_tool_call = tool_call.clone();
957 prepared_tool_call.arguments = prepared_args;
958
959 let validated_args = match validate_tool_arguments(tool.schema(), &prepared_tool_call) {
960 Ok(v) => v,
961 Err(e) => {
962 return Prepared::Immediate {
963 result: create_error_tool_result(&e.to_string()),
964 is_error: true,
965 };
966 }
967 };
968
969 let mut validated_args = validated_args;
976 if let Some(before) = &config.before_tool_call {
977 let ctx = BeforeToolCallContext {
978 assistant_message,
979 tool_call: &prepared_tool_call,
980 args: &validated_args,
981 context: current_context,
982 };
983 let before_result = before(ctx, config.signal.clone()).await;
984 if config.signal.is_cancelled() {
985 return Prepared::Immediate {
986 result: create_error_tool_result("Operation aborted"),
987 is_error: true,
988 };
989 }
990 if let Some(br) = before_result {
991 if let Some(replacement) = br.args {
992 validated_args = replacement;
993 }
994 if br.block {
995 let mut result = create_error_tool_result(
996 &br.reason
997 .unwrap_or_else(|| "Tool execution was blocked".to_string()),
998 );
999 if br.terminate {
1000 result.terminate = true;
1001 }
1002 return Prepared::Immediate {
1003 result,
1004 is_error: true,
1005 };
1006 }
1007 }
1008 }
1009
1010 if config.signal.is_cancelled() {
1011 return Prepared::Immediate {
1012 result: create_error_tool_result("Operation aborted"),
1013 is_error: true,
1014 };
1015 }
1016
1017 Prepared::Ready {
1018 tool,
1019 args: validated_args,
1020 }
1021}
1022
1023async fn execute_prepared_tool_call(
1026 tool_call: &ToolCall,
1027 tool: &Arc<dyn AgentTool>,
1028 args: &serde_json::Value,
1029 config: &AgentLoopConfig,
1030 emit: &Arc<dyn AgentEmitter>,
1031) -> ExecutedToolCallOutcome {
1032 let accepting_updates = Arc::new(AtomicBool::new(true));
1035 let tool_call_id = tool_call.id.clone();
1036 let tool_name = tool_call.name.clone();
1037 let args_clone = args.clone();
1038 let emit_clone = Arc::clone(emit);
1039
1040 let on_update: Arc<dyn Fn(crate::types::ToolResultPartial) + Send + Sync> = {
1041 let gate = Arc::clone(&accepting_updates);
1042 Arc::new(move |partial: crate::types::ToolResultPartial| {
1043 if !gate.load(Ordering::SeqCst) {
1044 return;
1045 }
1046 let ev = AgentEvent::ToolExecutionUpdate {
1049 tool_call_id: tool_call_id.clone(),
1050 tool_name: tool_name.clone(),
1051 args: args_clone.clone(),
1052 partial_result: Arc::new(partial),
1053 };
1054 emit_clone.try_emit(ev);
1059 })
1060 };
1061
1062 let child_token = config.signal.child_token();
1063 match tool
1064 .execute(&tool_call.id, args.clone(), child_token, on_update)
1065 .await
1066 {
1067 Ok(result) => {
1068 accepting_updates.store(false, Ordering::SeqCst);
1069 ExecutedToolCallOutcome {
1070 result,
1071 is_error: false,
1072 }
1073 }
1074 Err(e) => {
1075 accepting_updates.store(false, Ordering::SeqCst);
1076 ExecutedToolCallOutcome {
1077 result: create_error_tool_result(&e.to_string()),
1078 is_error: true,
1079 }
1080 }
1081 }
1082}
1083
1084struct ExecutedToolCallOutcome {
1087 result: AgentToolResult,
1088 is_error: bool,
1089}
1090
1091async fn finalize_executed_tool_call(
1093 current_context: &AgentContext,
1094 assistant_message: &AssistantMessage,
1095 tool_call: &ToolCall,
1096 args: &serde_json::Value,
1097 executed: ExecutedToolCallOutcome,
1098 config: &AgentLoopConfig,
1099) -> FinalizedToolCall {
1100 let mut result = executed.result;
1101 let mut is_error = executed.is_error;
1102
1103 if let Some(after) = &config.after_tool_call {
1104 let ctx = AfterToolCallContext {
1105 assistant_message,
1106 tool_call,
1107 args,
1108 result: &result,
1109 is_error,
1110 context: current_context,
1111 };
1112 match after(ctx, config.signal.clone()).await {
1113 Some(after_result) => {
1114 if let Some(c) = after_result.content {
1115 result.content = c;
1116 }
1117 if let Some(d) = after_result.details {
1118 result.details = d;
1119 }
1120 if let Some(u) = after_result.usage {
1121 result.usage = Some(u);
1122 }
1123 if let Some(t) = after_result.terminate {
1124 result.terminate = t;
1125 }
1126 if let Some(ie) = after_result.is_error {
1127 is_error = ie;
1128 }
1129 }
1130 None => {}
1131 }
1132 }
1133
1134 FinalizedToolCall {
1135 tool_call: tool_call.clone(),
1136 result,
1137 is_error,
1138 }
1139}
1140
1141fn should_terminate_tool_batch(finalized_calls: &[FinalizedToolCall]) -> bool {
1148 !finalized_calls.is_empty() && finalized_calls.iter().all(|f| f.result.terminate)
1149}
1150
1151fn create_error_tool_result(message: &str) -> AgentToolResult {
1154 AgentToolResult::error_text(message)
1155}
1156
1157fn create_tool_result_message(
1161 tool_call: &ToolCall,
1162 result: &AgentToolResult,
1163 is_error: bool,
1164) -> ToolResultMessage {
1165 ToolResultMessage {
1166 role: ToolResultRole,
1167 tool_call_id: tool_call.id.clone(),
1168 tool_name: tool_call.name.clone(),
1169 content: result.clone().into_content(),
1170 details: Some(result.details.clone()),
1171 usage: result.usage.clone(),
1172 added_tool_names: result.added_tool_names.clone(),
1173 is_error,
1174 timestamp: crate::clock::now_ms(),
1175 }
1176}
1177
1178async fn emit_tool_execution_end(emit: &Arc<dyn AgentEmitter>, finalized: &FinalizedToolCall) {
1180 emit_event(
1181 emit,
1182 AgentEvent::ToolExecutionEnd {
1183 tool_call_id: finalized.tool_call.id.clone(),
1184 tool_name: finalized.tool_call.name.clone(),
1185 result: finalized.result.clone(),
1186 is_error: finalized.is_error,
1187 },
1188 )
1189 .await;
1190}
1191
1192async fn emit_tool_result_message(emit: &Arc<dyn AgentEmitter>, trm: &ToolResultMessage) {
1195 let am = AgentMessage::ToolResult(Box::new(trm.clone()));
1196 emit_event(
1197 emit,
1198 AgentEvent::MessageStart {
1199 message: am.clone(),
1200 },
1201 )
1202 .await;
1203 emit_event(emit, AgentEvent::MessageEnd { message: am }).await;
1204}
1205
1206async fn drain_steering(config: &AgentLoopConfig) -> Vec<AgentMessage> {
1208 if let Some(hook) = &config.get_steering_messages {
1209 hook().await
1210 } else {
1211 Vec::new()
1212 }
1213}
1214
1215async fn drain_follow_up(config: &AgentLoopConfig) -> Vec<AgentMessage> {
1217 if let Some(hook) = &config.get_follow_up_messages {
1218 hook().await
1219 } else {
1220 Vec::new()
1221 }
1222}
1223
1224async fn prepare_next_turn(
1226 config: &AgentLoopConfig,
1227 message: &AssistantMessage,
1228 tool_results: &[ToolResultMessage],
1229 context: &AgentContext,
1230 new_messages: &[AgentMessage],
1231) -> Option<crate::types::AgentLoopTurnUpdate> {
1232 if let Some(hook) = &config.prepare_next_turn {
1233 let ctx = crate::types::ShouldStopAfterTurnContext {
1234 message,
1235 tool_results,
1236 context,
1237 new_messages,
1238 };
1239 hook(ctx).await
1240 } else {
1241 None
1242 }
1243}
1244
1245async fn after_tool_results(
1246 config: &AgentLoopConfig,
1247 message: &AssistantMessage,
1248 tool_results: &[ToolResultMessage],
1249 context: &AgentContext,
1250 new_messages: &[AgentMessage],
1251) -> Option<crate::types::AgentLoopTurnUpdate> {
1252 let hook = config.after_tool_results.as_ref()?;
1253 let ctx = crate::types::ShouldStopAfterTurnContext {
1254 message,
1255 tool_results,
1256 context,
1257 new_messages,
1258 };
1259 hook(ctx).await
1260}
1261
1262async fn should_stop_after_turn(
1264 config: &AgentLoopConfig,
1265 message: &AssistantMessage,
1266 tool_results: &[ToolResultMessage],
1267 context: &AgentContext,
1268 new_messages: &[AgentMessage],
1269) -> bool {
1270 if let Some(hook) = &config.should_stop_after_turn {
1271 let ctx = crate::types::ShouldStopAfterTurnContext {
1272 message,
1273 tool_results,
1274 context,
1275 new_messages,
1276 };
1277 hook(ctx).await
1278 } else {
1279 false
1280 }
1281}
1282
1283async fn emit_event(emit: &Arc<dyn AgentEmitter>, event: AgentEvent) {
1285 emit.emit(event).await;
1286}
1287
1288fn panicked_tool_call() -> ToolCall {
1290 ToolCall {
1291 kind: ToolCallType,
1292 id: "<panic>".to_string(),
1293 name: "<panic>".to_string(),
1294 arguments: serde_json::Value::Null,
1295 thought_signature: None,
1296 namespace: None,
1297 }
1298}