gemini_adk_fluent_rs/compose/
tools.rs1use std::future::Future;
6use std::pin::Pin;
7use std::sync::Arc;
8
9use gemini_adk_rs::text::TextAgent;
10use gemini_adk_rs::tool::{SimpleTool, ToolFunction};
11use gemini_genai_rs::prelude::Tool;
12
13#[derive(Clone)]
15pub struct ToolComposite {
16 pub entries: Vec<ToolCompositeEntry>,
18}
19
20#[derive(Clone)]
22pub enum ToolCompositeEntry {
23 Function(Arc<dyn ToolFunction>),
25 BuiltIn(Tool),
27 Agent {
29 name: String,
31 description: String,
33 agent: Arc<dyn TextAgent>,
35 },
36 Mcp {
38 params: String,
40 },
41 A2a {
43 url: String,
45 skill: String,
47 },
48 Mock {
50 name: String,
52 description: String,
54 response: serde_json::Value,
56 },
57 OpenApi {
59 name: String,
61 spec_url: String,
63 },
64 Search {
66 name: String,
68 description: String,
70 },
71 Schema {
73 name: String,
75 schema: serde_json::Value,
77 },
78 Transform {
80 inner: Box<ToolCompositeEntry>,
82 transformer: Arc<
84 dyn Fn(serde_json::Value) -> Pin<Box<dyn Future<Output = serde_json::Value> + Send>>
85 + Send
86 + Sync,
87 >,
88 },
89}
90
91impl ToolComposite {
92 pub fn from_function(f: Arc<dyn ToolFunction>) -> Self {
94 Self {
95 entries: vec![ToolCompositeEntry::Function(f)],
96 }
97 }
98
99 pub fn from_built_in(tool: Tool) -> Self {
101 Self {
102 entries: vec![ToolCompositeEntry::BuiltIn(tool)],
103 }
104 }
105
106 pub fn len(&self) -> usize {
108 self.entries.len()
109 }
110
111 pub fn is_empty(&self) -> bool {
113 self.entries.is_empty()
114 }
115}
116
117impl std::ops::BitOr for ToolComposite {
119 type Output = ToolComposite;
120
121 fn bitor(mut self, rhs: ToolComposite) -> Self::Output {
122 self.entries.extend(rhs.entries);
123 self
124 }
125}
126
127pub struct T;
129
130impl T {
131 pub fn function(f: Arc<dyn ToolFunction>) -> ToolComposite {
133 ToolComposite::from_function(f)
134 }
135
136 pub fn google_search() -> ToolComposite {
138 ToolComposite::from_built_in(Tool::google_search())
139 }
140
141 pub fn url_context() -> ToolComposite {
143 ToolComposite::from_built_in(Tool::url_context())
144 }
145
146 pub fn code_execution() -> ToolComposite {
148 ToolComposite::from_built_in(Tool::code_execution())
149 }
150
151 pub fn simple<F, Fut>(
153 name: impl Into<String>,
154 description: impl Into<String>,
155 f: F,
156 ) -> ToolComposite
157 where
158 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
159 Fut: Future<Output = Result<serde_json::Value, gemini_adk_rs::ToolError>> + Send + 'static,
160 {
161 let tool = SimpleTool::new(name, description, None, f);
162 ToolComposite::from_function(Arc::new(tool))
163 }
164
165 pub fn fn_tool<F, Fut>(
169 name: impl Into<String>,
170 description: impl Into<String>,
171 f: F,
172 ) -> ToolComposite
173 where
174 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
175 Fut: Future<Output = Result<serde_json::Value, gemini_adk_rs::ToolError>> + Send + 'static,
176 {
177 Self::simple(name, description, f)
178 }
179
180 pub fn confirm(tool: ToolComposite, _message: &str) -> ToolComposite {
182 tool
184 }
185
186 pub fn timeout(tool: ToolComposite, _duration: std::time::Duration) -> ToolComposite {
188 tool
190 }
191
192 pub fn cached(tool: ToolComposite) -> ToolComposite {
194 tool
196 }
197
198 pub fn toolset(tools: Vec<Arc<dyn ToolFunction>>) -> ToolComposite {
200 ToolComposite {
201 entries: tools
202 .into_iter()
203 .map(ToolCompositeEntry::Function)
204 .collect(),
205 }
206 }
207
208 pub fn agent(
213 name: impl Into<String>,
214 description: impl Into<String>,
215 agent: impl TextAgent + 'static,
216 ) -> ToolComposite {
217 ToolComposite {
218 entries: vec![ToolCompositeEntry::Agent {
219 name: name.into(),
220 description: description.into(),
221 agent: Arc::new(agent),
222 }],
223 }
224 }
225
226 pub fn mcp(params: impl Into<String>) -> ToolComposite {
231 ToolComposite {
232 entries: vec![ToolCompositeEntry::Mcp {
233 params: params.into(),
234 }],
235 }
236 }
237
238 pub fn a2a(url: impl Into<String>, skill: impl Into<String>) -> ToolComposite {
242 ToolComposite {
243 entries: vec![ToolCompositeEntry::A2a {
244 url: url.into(),
245 skill: skill.into(),
246 }],
247 }
248 }
249
250 pub fn mock(
254 name: impl Into<String>,
255 description: impl Into<String>,
256 response: serde_json::Value,
257 ) -> ToolComposite {
258 ToolComposite {
259 entries: vec![ToolCompositeEntry::Mock {
260 name: name.into(),
261 description: description.into(),
262 response,
263 }],
264 }
265 }
266
267 pub fn openapi(name: impl Into<String>, spec_url: impl Into<String>) -> ToolComposite {
272 ToolComposite {
273 entries: vec![ToolCompositeEntry::OpenApi {
274 name: name.into(),
275 spec_url: spec_url.into(),
276 }],
277 }
278 }
279
280 pub fn search(name: impl Into<String>, description: impl Into<String>) -> ToolComposite {
284 ToolComposite {
285 entries: vec![ToolCompositeEntry::Search {
286 name: name.into(),
287 description: description.into(),
288 }],
289 }
290 }
291
292 pub fn schema(name: impl Into<String>, schema: serde_json::Value) -> ToolComposite {
296 ToolComposite {
297 entries: vec![ToolCompositeEntry::Schema {
298 name: name.into(),
299 schema,
300 }],
301 }
302 }
303
304 pub fn transform<F, Fut>(tool: ToolComposite, f: F) -> ToolComposite
309 where
310 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
311 Fut: Future<Output = serde_json::Value> + Send + 'static,
312 {
313 let f: Arc<
314 dyn Fn(serde_json::Value) -> Pin<Box<dyn Future<Output = serde_json::Value> + Send>>
315 + Send
316 + Sync,
317 > = Arc::new(
318 move |v: serde_json::Value| -> Pin<Box<dyn Future<Output = serde_json::Value> + Send>> {
319 Box::pin(f(v))
320 },
321 );
322 ToolComposite {
323 entries: tool
324 .entries
325 .into_iter()
326 .map(|entry| ToolCompositeEntry::Transform {
327 inner: Box::new(entry),
328 transformer: Arc::clone(&f),
329 })
330 .collect(),
331 }
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn google_search_creates_composite() {
341 let t = T::google_search();
342 assert_eq!(t.len(), 1);
343 }
344
345 #[test]
346 fn url_context_creates_composite() {
347 let t = T::url_context();
348 assert_eq!(t.len(), 1);
349 }
350
351 #[test]
352 fn code_execution_creates_composite() {
353 let t = T::code_execution();
354 assert_eq!(t.len(), 1);
355 }
356
357 #[test]
358 fn compose_with_bitor() {
359 let t = T::google_search() | T::url_context() | T::code_execution();
360 assert_eq!(t.len(), 3);
361 }
362
363 #[test]
364 fn simple_creates_tool() {
365 let t = T::simple("greet", "Greets the user", |_args| async {
366 Ok(serde_json::json!({"message": "hello"}))
367 });
368 assert_eq!(t.len(), 1);
369 match &t.entries[0] {
370 ToolCompositeEntry::Function(f) => assert_eq!(f.name(), "greet"),
371 _ => panic!("expected Function entry"),
372 }
373 }
374
375 #[test]
376 fn toolset_combines_functions() {
377 let tool_a: Arc<dyn ToolFunction> =
378 Arc::new(SimpleTool::new("a", "tool a", None, |_| async {
379 Ok(serde_json::json!(null))
380 }));
381 let tool_b: Arc<dyn ToolFunction> =
382 Arc::new(SimpleTool::new("b", "tool b", None, |_| async {
383 Ok(serde_json::json!(null))
384 }));
385 let t = T::toolset(vec![tool_a, tool_b]);
386 assert_eq!(t.len(), 2);
387 }
388}