codetether-agent 4.5.7

A2A-native AI coding agent for the CodeTether ecosystem
Documentation
//! Tool-call replay for agent execution.
//!
//! This module applies provider-requested tool calls to the session and stores
//! the resulting tool history. When the LLM emits multiple calls in one turn
//! and **all** of them are in the read-only allowlist
//! ([`crate::tool::readonly::is_read_only`]), they are dispatched concurrently
//! via `join_all`; otherwise they run sequentially to preserve ordering
//! semantics for tools with side effects.
//!
//! # Examples
//!
//! ```ignore
//! agent.execute_tool_calls(&mut session, vec![]).await;
//! ```

use super::messages::PendingToolCall;
use crate::agent::{Agent, ToolUse};
use crate::provider::{ContentPart, Message, Role};
use crate::session::Session;
use crate::tool::ToolResult;
use crate::tool::readonly::is_read_only;

impl Agent {
    pub(super) async fn execute_tool_calls(
        &self,
        session: &mut Session,
        tool_calls: Vec<PendingToolCall>,
    ) {
        let results = if can_parallelize(&tool_calls) {
            self.execute_parallel(&tool_calls).await
        } else {
            self.execute_sequential(&tool_calls).await
        };
        record_results(session, tool_calls, results);
    }

    async fn execute_sequential(&self, calls: &[PendingToolCall]) -> Vec<ToolResult> {
        let mut out = Vec::with_capacity(calls.len());
        for (_, name, arguments) in calls {
            out.push(self.execute_tool(name, arguments).await);
        }
        out
    }

    async fn execute_parallel(&self, calls: &[PendingToolCall]) -> Vec<ToolResult> {
        let futures = calls
            .iter()
            .map(|(_, name, arguments)| self.execute_tool(name, arguments));
        futures::future::join_all(futures).await
    }
}

fn can_parallelize(calls: &[PendingToolCall]) -> bool {
    calls.len() > 1 && calls.iter().all(|(_, name, _)| is_read_only(name))
}

fn record_results(
    session: &mut Session,
    tool_calls: Vec<PendingToolCall>,
    results: Vec<ToolResult>,
) {
    for ((id, name, arguments), result) in tool_calls.into_iter().zip(results) {
        session.tool_uses.push(ToolUse {
            id: id.clone(),
            name: name.clone(),
            input: arguments.clone(),
            output: result.output.clone(),
            success: result.success,
        });
        session.add_message(Message {
            role: Role::Tool,
            content: vec![ContentPart::ToolResult {
                tool_call_id: id,
                content: result.output,
            }],
        });
    }
}