use std::sync::Arc;
use caliban_provider::{ContentBlock, TextBlock, ToolResultBlock};
use tokio_util::sync::CancellationToken;
use tracing::instrument;
use super::StopCondition;
use crate::agent::Agent;
use crate::hooks::ToolCtx;
use crate::tool::{ToolContext, ToolError};
#[instrument(skip(agent, input, cancel), fields(tool = tool_name, id = tool_use_id))]
pub(crate) async fn dispatch_tool(
agent: &Agent,
turn_index: u32,
tool_use_id: &str,
tool_name: &str,
input: serde_json::Value,
cancel: &CancellationToken,
) -> std::result::Result<ToolResultBlock, StopCondition> {
if cancel.is_cancelled() {
return Err(StopCondition::Cancelled);
}
let tool_ctx = ToolCtx {
turn_index,
tool_use_id,
tool_name,
input: &input,
};
let invoke_result: std::result::Result<Vec<ContentBlock>, ToolError> =
match agent.tools.get(tool_name) {
None => Err(ToolError::invalid_input(format!(
"tool not found: {tool_name}"
))),
Some(tool) => {
let cx = ToolContext {
tool_use_id: tool_use_id.to_string(),
cancel: cancel.clone(),
hooks: Some(Arc::clone(&agent.hooks)),
turn_index,
};
tool.invoke(input.clone(), cx).await
}
};
if let Err(e) = agent.hooks.after_tool(&tool_ctx, &invoke_result).await {
tracing::warn!(tool = tool_name, error = %e, "after_tool hook error (non-fatal)");
}
match invoke_result {
Err(ToolError::Cancelled) => Err(StopCondition::Cancelled),
Err(e) => Ok(ToolResultBlock {
tool_use_id: tool_use_id.to_string(),
content: vec![ContentBlock::Text(TextBlock {
text: format!("Error: {e}"),
cache_control: None,
})],
is_error: true,
}),
Ok(content) => Ok(ToolResultBlock {
tool_use_id: tool_use_id.to_string(),
content,
is_error: false,
}),
}
}