1use std::collections::HashMap;
7use std::sync::Arc;
8
9use crate::registry::ToolRegistry;
10use crate::tool::SharedTool;
11
12pub trait ToolSource: Send + Sync {
17 fn name(&self) -> &str;
19
20 fn tools(&self) -> Vec<SharedTool>;
22}
23
24#[derive(Clone)]
26pub struct StaticToolSource {
27 name: String,
28 tools: Vec<SharedTool>,
29}
30
31impl std::fmt::Debug for StaticToolSource {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("StaticToolSource")
34 .field("name", &self.name)
35 .field("tools", &self.tools.len())
36 .finish()
37 }
38}
39
40impl StaticToolSource {
41 #[must_use]
43 pub fn new(name: impl Into<String>, tools: Vec<SharedTool>) -> Self {
44 Self {
45 name: name.into(),
46 tools,
47 }
48 }
49}
50
51impl ToolSource for StaticToolSource {
52 fn name(&self) -> &str {
53 &self.name
54 }
55
56 fn tools(&self) -> Vec<SharedTool> {
57 self.tools.clone()
58 }
59}
60
61#[must_use]
65pub fn merge_tool_sources<'a>(
66 sources: impl IntoIterator<Item = &'a dyn ToolSource>,
67) -> ToolRegistry {
68 let mut map: HashMap<String, SharedTool> = HashMap::new();
69 for source in sources {
70 for tool in source.tools() {
71 map.insert(tool.name().to_owned(), tool);
72 }
73 }
74 ToolRegistry::from_tools(map.into_values().collect())
75}
76
77#[must_use]
79pub fn merge_arc_sources(sources: &[Arc<dyn ToolSource>]) -> ToolRegistry {
80 let refs: Vec<&dyn ToolSource> = sources.iter().map(AsRef::as_ref).collect();
81 merge_tool_sources(refs)
82}
83
84#[cfg(test)]
85mod tests {
86 use async_trait::async_trait;
87 use serde_json::{Value, json};
88
89 use super::*;
90 use crate::calc::CalcTool;
91 use crate::tool::DynTool;
92
93 struct NamedTool {
94 n: &'static str,
95 }
96
97 #[async_trait]
98 impl DynTool for NamedTool {
99 fn name(&self) -> &str {
100 self.n
101 }
102 fn description(&self) -> &str {
103 "t"
104 }
105 fn parameters(&self) -> Value {
106 json!({"type":"object","properties":{}})
107 }
108 async fn call(
109 &self,
110 _ctx: crate::context::ToolCallContext,
111 _arguments: Value,
112 ) -> Result<crate::tool::ToolResult, crate::error::ToolError> {
113 Ok(crate::tool::ToolResult::text(self.n))
114 }
115 }
116
117 #[test]
118 fn last_source_wins_on_name() {
119 let a = StaticToolSource::new("a", vec![Arc::new(NamedTool { n: "dup" })]);
120 let b = StaticToolSource::new(
121 "b",
122 vec![Arc::new(NamedTool { n: "dup" }), Arc::new(CalcTool)],
123 );
124 let sources: [&dyn ToolSource; 2] = [&a, &b];
125 let reg = merge_tool_sources(sources);
126 assert_eq!(reg.len(), 2);
127 assert_eq!(reg.get("dup").expect("dup").name(), "dup");
128 assert!(reg.get(CalcTool.name()).is_some());
129 assert_eq!(reg.names().len(), 2);
130 }
131
132 #[test]
133 fn empty_merge() {
134 let reg = merge_tool_sources([]);
135 assert!(reg.is_empty());
136 }
137}