use super::AgentError;
use crate::types::{AgentAction, ToolInput};
use lc_core::tools::{BaseTool, ToolError};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Semaphore;
pub(crate) fn tool_error_observation(err: &AgentError) -> String {
format!("[Tool execution error: {err}]")
}
pub(crate) async fn run_tool_with_timeout(
tool: &Arc<dyn BaseTool>,
input: String,
timeout: Option<Duration>,
) -> Result<String, ToolError> {
let fut = tool.run(input);
match timeout {
Some(d) => match tokio::time::timeout(d, fut).await {
Ok(result) => result,
Err(_) => Err(ToolError::Timeout(d.as_secs())),
},
None => fut.await,
}
}
pub(crate) async fn execute_tool_for_stream(
tools: &[Arc<dyn BaseTool>],
action: &AgentAction,
timeout: Option<Duration>,
) -> Result<String, AgentError> {
let tool = tools
.iter()
.find(|t| t.name() == action.tool)
.ok_or_else(|| AgentError::ToolNotFound(action.tool.clone()))?;
let input_str = match &action.tool_input {
ToolInput::String { value: s } => s.clone(),
ToolInput::Object { value: v } => serde_json::to_string(v)
.map_err(|e| AgentError::Other(format!("Failed to serialize tool input: {}", e)))?,
};
run_tool_with_timeout(tool, input_str, timeout)
.await
.map_err(|e| match e {
ToolError::ControlAbort(msg) => AgentError::Other(format!("Tool call aborted: {msg}")),
other => AgentError::ToolExecutionError(other.to_string()),
})
}
pub(crate) async fn execute_tools_parallel_for_stream(
tools: &[Arc<dyn BaseTool>],
actions: &[AgentAction],
timeout: Option<Duration>,
max_concurrency: usize,
) -> Result<Vec<String>, AgentError> {
use futures_util::future::join_all;
let sem = Arc::new(Semaphore::new(max_concurrency));
let futures = actions.iter().map(|action| {
let sem = sem.clone();
async move {
let _permit = sem
.acquire_owned()
.await
.map_err(|e| AgentError::Other(format!("concurrency semaphore closed: {e}")))?;
execute_tool_for_stream(tools, action, timeout).await
}
});
let results = join_all(futures).await;
let mut observations = Vec::with_capacity(results.len());
for result in results {
match result {
Ok(output) => observations.push(output),
Err(e @ AgentError::ToolExecutionError(_)) => {
observations.push(tool_error_observation(&e))
}
Err(e) => return Err(e),
}
}
Ok(observations)
}