use std::sync::Arc;
use futures::future::join_all;
use crate::approval::ApprovalHandler;
use crate::error::{Error, ToolError};
use crate::provider::Provider;
use crate::text::request::{TextRequest, ToolChoice};
use crate::text::response::{Step, TextResponse};
use crate::tool::Tool;
use crate::value_objects::{
AssistantMessage, FinishReason, Message, ToolCall, ToolOutcome, ToolResult, ToolResultMessage,
};
pub trait StopCondition: Send + Sync {
fn should_stop(&self, steps: &[Step]) -> bool;
}
impl<F> StopCondition for F
where
F: Fn(&[Step]) -> bool + Send + Sync,
{
fn should_stop(&self, steps: &[Step]) -> bool {
self(steps)
}
}
pub async fn run_text(
provider: Arc<dyn Provider>,
mut request: TextRequest,
) -> Result<TextResponse, Error> {
let mut steps: Vec<Step> = Vec::new();
loop {
let step = provider.text_step(&request).await?;
let has_tool_calls =
step.finish_reason == FinishReason::ToolCalls && !step.tool_calls.is_empty();
let tool_calls = step.tool_calls.clone();
let text = step.text.clone();
steps.push(step);
let stop_condition_met = request
.stop_when
.iter()
.any(|condition| condition.should_stop(&steps));
if !has_tool_calls || steps.len() as u32 >= request.max_steps || stop_condition_met {
return Ok(TextResponse::from_steps(steps));
}
let results = execute_tools(
&request.tools,
&tool_calls,
request.approval_handler.as_ref(),
)
.await;
request.messages.push(Message::Assistant(AssistantMessage {
content: text,
tool_calls,
}));
request
.messages
.push(Message::ToolResult(ToolResultMessage {
tool_results: results,
}));
request.tool_choice = ToolChoice::Auto;
}
}
pub(crate) async fn execute_tools(
tools: &[Arc<dyn Tool>],
calls: &[ToolCall],
approval_handler: Option<&Arc<dyn ApprovalHandler>>,
) -> Vec<ToolResult> {
let mut concurrent_idx = Vec::new();
let mut sequential_idx = Vec::new();
for (i, call) in calls.iter().enumerate() {
match tools.iter().find(|t| t.name() == call.name) {
Some(tool) if tool.concurrent() => concurrent_idx.push(i),
_ => sequential_idx.push(i),
}
}
let mut results: Vec<Option<ToolResult>> = (0..calls.len()).map(|_| None).collect();
let concurrent_futures = concurrent_idx
.iter()
.map(|&i| execute_one(tools, &calls[i], approval_handler));
for (i, result) in concurrent_idx
.iter()
.zip(join_all(concurrent_futures).await)
{
results[*i] = Some(result);
}
for i in sequential_idx {
results[i] = Some(execute_one(tools, &calls[i], approval_handler).await);
}
results
.into_iter()
.map(|r| {
r.expect("every call index is populated by either the concurrent or sequential pass")
})
.collect()
}
async fn execute_one(
tools: &[Arc<dyn Tool>],
call: &ToolCall,
approval_handler: Option<&Arc<dyn ApprovalHandler>>,
) -> ToolResult {
let matches: Vec<&Arc<dyn Tool>> = tools.iter().filter(|t| t.name() == call.name).collect();
let outcome = match matches.as_slice() {
[] => ToolOutcome::Error(
ToolError::NotFound {
name: call.name.clone(),
}
.to_string(),
),
[tool] => {
let approved = if tool.needs_approval() {
match approval_handler {
Some(handler) => handler.approve(call).await,
None => false,
}
} else {
true
};
if approved {
match tool.call(call.arguments.clone()).await {
Ok(output) => ToolOutcome::Output(output),
Err(err) => ToolOutcome::Error(err.to_string()),
}
} else {
ToolOutcome::Error(
ToolError::ApprovalDenied {
name: call.name.clone(),
}
.to_string(),
)
}
}
_ => ToolOutcome::Error(
ToolError::MultipleFound {
name: call.name.clone(),
}
.to_string(),
),
};
ToolResult {
tool_call_id: call.id.clone(),
tool_name: call.name.clone(),
args: call.arguments.clone(),
result: outcome,
}
}