Execute every tool-call block in the assistant's content. Returns the per-call results (in assistant content order) and `all_terminate = true` when every result hints early termination.
(
inner: &Arc<AgentInner>,
assistant: &PiAssistantMessage,
cancel: &CancellationToken,
)
| 329 | /// (in assistant content order) and `all_terminate = true` when every result hints early |
| 330 | /// termination. |
| 331 | async fn execute_tools( |
| 332 | inner: &Arc<AgentInner>, |
| 333 | assistant: &PiAssistantMessage, |
| 334 | cancel: &CancellationToken, |
| 335 | ) -> (Vec<ToolResultMessage>, bool) { |
| 336 | // Gather the tool calls + matched AgentTool implementations in assistant content order. |
| 337 | let tool_calls: Vec<&pie_ai::ToolCall> = assistant |
| 338 | .content |
| 339 | .iter() |
| 340 | .filter_map(|b| match b { |
| 341 | pie_ai::ContentBlock::ToolCall(tc) => Some(tc), |
| 342 | _ => None, |
| 343 | }) |
| 344 | .collect(); |
| 345 | if tool_calls.is_empty() { |
| 346 | return (Vec::new(), false); |
| 347 | } |
| 348 | let tools_snapshot = inner.state.lock().tools.clone(); |
| 349 | |
| 350 | // Decide per-call execution mode (parallel default unless any tool requests sequential). |
| 351 | let mode = inner.options.tool_execution; |
| 352 | let any_sequential = tool_calls.iter().any(|tc| { |
| 353 | let matched = tools_snapshot |
| 354 | .iter() |
| 355 | .find(|t| t.definition().name == tc.name); |
| 356 | matched |
| 357 | .and_then(|t| t.execution_mode()) |
| 358 | .map(|m| matches!(m, ToolExecutionMode::Sequential)) |
| 359 | .unwrap_or(false) |
| 360 | }); |
| 361 | let mode = if any_sequential { |
| 362 | ToolExecutionMode::Sequential |
| 363 | } else { |
| 364 | mode |
| 365 | }; |
| 366 | |
| 367 | // Pre-flight: run `before_tool_call` for every call. If a hook blocks, synthesize an error |
| 368 | // result and skip the actual execute. Returns Vec<Option<execute_input>> in call order. |
| 369 | let mut prepared: Vec<PreparedCall> = Vec::with_capacity(tool_calls.len()); |
| 370 | let agent_context = snapshot_context(inner); |
| 371 | for tc in &tool_calls { |
| 372 | let tool_id = tc.id.clone(); |
| 373 | let tool_name = tc.name.clone(); |
| 374 | let raw_args = serde_json::Value::Object(tc.arguments.clone()); |
| 375 | |
| 376 | // Resolve the tool BEFORE normalizing args so we can run its `prepare_arguments` |
| 377 | // compatibility shim. Unknown tools keep raw args (the dispatcher will produce a |
| 378 | // "no such tool" error result downstream). |
| 379 | let tool = tools_snapshot |
| 380 | .iter() |
| 381 | .find(|t| t.definition().name == tool_name) |
| 382 | .cloned(); |
| 383 | let args = match &tool { |
| 384 | Some(t) => t.prepare_arguments(raw_args), |
| 385 | None => raw_args, |
| 386 | }; |
| 387 | |
| 388 | emit( |
no test coverage detected