use std::collections::HashSet;
use std::sync::Arc;
use crate::types::{AgentTool, ToolContext, ToolError, ToolResult};
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait ToolSource: crate::rt::MaybeSend + crate::rt::MaybeSync {
async fn tools(&self) -> Vec<Arc<dyn AgentTool>>;
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl<T: ToolSource + ?Sized> ToolSource for Arc<T> {
async fn tools(&self) -> Vec<Arc<dyn AgentTool>> {
(**self).tools().await
}
}
pub(crate) struct ArcTool(pub(crate) Arc<dyn AgentTool>);
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl AgentTool for ArcTool {
fn name(&self) -> &str {
self.0.name()
}
fn label(&self) -> &str {
self.0.label()
}
fn description(&self) -> &str {
self.0.description()
}
fn parameters_schema(&self) -> serde_json::Value {
self.0.parameters_schema()
}
async fn execute(
&self,
params: serde_json::Value,
ctx: ToolContext,
) -> Result<ToolResult, ToolError> {
self.0.execute(params, ctx).await
}
}
pub(crate) async fn collect(sources: &[Arc<dyn ToolSource>]) -> Vec<Arc<dyn AgentTool>> {
use futures::FutureExt;
let mut out = Vec::new();
for (index, source) in sources.iter().enumerate() {
let guarded = std::panic::AssertUnwindSafe(async move { (**source).tools().await });
match guarded.catch_unwind().await {
Ok(tools) => out.extend(tools),
Err(payload) => tracing::warn!(
source = index,
panic = %panic_message(payload.as_ref()),
"tool source panicked; it contributes no tools to this run"
),
}
}
out
}
fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
if let Some(s) = payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"<non-string panic payload>".to_string()
}
}
pub(crate) fn merge(tools: &mut Vec<Box<dyn AgentTool>>, sourced: Vec<Arc<dyn AgentTool>>) {
let static_names: HashSet<String> = tools.iter().map(|t| t.name().to_string()).collect();
let mut taken = static_names.clone();
let mut kept: Vec<Arc<dyn AgentTool>> = Vec::new();
for tool in sourced {
let name = tool.name().to_string();
if taken.contains(&name) {
if static_names.contains(&name) {
tracing::warn!(
tool = %name,
"a tool source offered a tool whose name a static tool already uses; \
the static tool is kept"
);
} else {
tracing::warn!(
tool = %name,
"two tool sources offered tools with the same name; \
the first one is kept"
);
}
continue;
}
taken.insert(name);
kept.push(tool);
}
kept.sort_by(|a, b| a.name().cmp(b.name()));
tools.extend(
kept.into_iter()
.map(|t| Box::new(ArcTool(t)) as Box<dyn AgentTool>),
);
}