1pub mod dispatcher;
4pub mod policy;
5pub mod simple;
6pub mod typed;
7
8pub use dispatcher::*;
9pub use policy::*;
10pub use simple::*;
11pub use typed::*;
12
13use std::sync::Arc;
14use std::time::Duration;
15
16pub mod media;
17
18use async_trait::async_trait;
19use tokio::sync::{broadcast, mpsc};
20use tokio::task::JoinHandle;
21use tokio_util::sync::CancellationToken;
22
23use crate::agent_session::InputEvent;
24use crate::error::ToolError;
25
26#[async_trait]
48pub trait ToolFunction: Send + Sync + 'static {
49 fn name(&self) -> &str;
51 fn description(&self) -> &str;
53 fn parameters(&self) -> Option<serde_json::Value>;
55 async fn call(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError>;
57
58 fn requires_confirmation(&self) -> bool {
64 false
65 }
66
67 fn confirmation_message(&self) -> Option<&str> {
69 None
70 }
71}
72
73#[async_trait]
77impl<T: ToolFunction + ?Sized> ToolFunction for Arc<T> {
78 fn name(&self) -> &str {
79 (**self).name()
80 }
81 fn description(&self) -> &str {
82 (**self).description()
83 }
84 fn parameters(&self) -> Option<serde_json::Value> {
85 (**self).parameters()
86 }
87 async fn call(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
88 (**self).call(args).await
89 }
90 fn requires_confirmation(&self) -> bool {
91 (**self).requires_confirmation()
92 }
93 fn confirmation_message(&self) -> Option<&str> {
94 (**self).confirmation_message()
95 }
96}
97
98#[async_trait]
100pub trait StreamingTool: Send + Sync + 'static {
101 fn name(&self) -> &str;
103 fn description(&self) -> &str;
105 fn parameters(&self) -> Option<serde_json::Value>;
107 async fn run(
109 &self,
110 args: serde_json::Value,
111 yield_tx: mpsc::Sender<serde_json::Value>,
112 ) -> Result<(), ToolError>;
113}
114
115#[async_trait]
117pub trait InputStreamingTool: Send + Sync + 'static {
118 fn name(&self) -> &str;
120 fn description(&self) -> &str;
122 fn parameters(&self) -> Option<serde_json::Value>;
124 async fn run(
126 &self,
127 args: serde_json::Value,
128 input_rx: broadcast::Receiver<InputEvent>,
129 yield_tx: mpsc::Sender<serde_json::Value>,
130 ) -> Result<(), ToolError>;
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub enum ToolClass {
136 Regular,
138 Streaming,
140 InputStream,
142}
143
144pub enum ToolKind {
146 Function(Arc<dyn ToolFunction>),
148 Streaming(Arc<dyn StreamingTool>),
150 InputStream(Arc<dyn InputStreamingTool>),
152}
153
154pub struct ActiveStreamingTool {
156 pub task: JoinHandle<()>,
158 pub cancel: CancellationToken,
160}
161
162pub(crate) const DEFAULT_TOOL_TIMEOUT: Duration = Duration::from_secs(30);
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use gemini_genai_rs::prelude::FunctionCall;
169 use serde_json::json;
170
171 struct MockTool;
172
173 #[async_trait]
174 impl ToolFunction for MockTool {
175 fn name(&self) -> &str {
176 "mock_tool"
177 }
178 fn description(&self) -> &str {
179 "A mock tool"
180 }
181 fn parameters(&self) -> Option<serde_json::Value> {
182 None
183 }
184 async fn call(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
185 Ok(json!({"result": "ok"}))
186 }
187 }
188
189 #[tokio::test]
190 async fn register_and_call_function_tool() {
191 let mut dispatcher = ToolDispatcher::new();
192 dispatcher.register_function(Arc::new(MockTool));
193 let result = dispatcher
194 .call_function("mock_tool", json!({}))
195 .await
196 .unwrap();
197 assert_eq!(result["result"], "ok");
198 }
199
200 #[tokio::test]
201 async fn call_unknown_tool_returns_error() {
202 let dispatcher = ToolDispatcher::new();
203 let result = dispatcher.call_function("nonexistent", json!({})).await;
204 assert!(result.is_err());
205 }
206
207 #[test]
208 fn to_tool_declarations() {
209 let mut dispatcher = ToolDispatcher::new();
210 dispatcher.register_function(Arc::new(MockTool));
211 let decls = dispatcher.to_tool_declarations();
212 assert_eq!(decls.len(), 1);
213 }
214
215 #[test]
216 fn classify_tool() {
217 let mut dispatcher = ToolDispatcher::new();
218 dispatcher.register_function(Arc::new(MockTool));
219 assert_eq!(dispatcher.classify("mock_tool"), Some(ToolClass::Regular));
220 assert_eq!(dispatcher.classify("nonexistent"), None);
221 }
222
223 #[test]
224 fn empty_dispatcher() {
225 let dispatcher = ToolDispatcher::new();
226 assert!(dispatcher.is_empty());
227 assert_eq!(dispatcher.len(), 0);
228 assert!(dispatcher.to_tool_declarations().is_empty());
229 }
230
231 #[test]
232 fn build_response_success() {
233 let call = FunctionCall {
234 name: "test".to_string(),
235 args: json!({}),
236 id: Some("call-1".to_string()),
237 };
238 let resp = ToolDispatcher::build_response(&call, Ok(json!({"ok": true})));
239 assert_eq!(resp.name, "test");
240 assert_eq!(resp.response["ok"], true);
241 }
242
243 #[test]
244 fn build_response_error() {
245 let call = FunctionCall {
246 name: "test".to_string(),
247 args: json!({}),
248 id: Some("call-1".to_string()),
249 };
250 let resp = ToolDispatcher::build_response(
251 &call,
252 Err(ToolError::ExecutionFailed("boom".to_string())),
253 );
254 assert!(resp.response["error"].as_str().unwrap().contains("boom"));
255 }
256
257 #[test]
258 fn tool_dispatcher_implements_tool_provider() {
259 use gemini_genai_rs::prelude::ToolProvider;
260 let mut dispatcher = ToolDispatcher::new();
261 dispatcher.register_function(Arc::new(MockTool));
262 let decls = dispatcher.declarations();
263 assert_eq!(decls.len(), 1);
264 }
265
266 #[tokio::test]
267 async fn simple_tool_closure() {
268 let tool = SimpleTool::new(
269 "add",
270 "Add two numbers",
271 Some(
272 json!({"type": "object", "properties": {"a": {"type": "number"}, "b": {"type": "number"}}}),
273 ),
274 |args| async move {
275 let a = args["a"].as_f64().unwrap_or(0.0);
276 let b = args["b"].as_f64().unwrap_or(0.0);
277 Ok(json!({"sum": a + b}))
278 },
279 );
280
281 let mut dispatcher = ToolDispatcher::new();
282 dispatcher.register_function(Arc::new(tool));
283 let result = dispatcher
284 .call_function("add", json!({"a": 3, "b": 4}))
285 .await
286 .unwrap();
287 assert_eq!(result["sum"], 7.0);
288 }
289
290 #[derive(serde::Deserialize, schemars::JsonSchema)]
293 struct WeatherArgs {
294 city: String,
296 #[serde(default = "default_units")]
298 units: String,
299 }
300
301 fn default_units() -> String {
302 "celsius".to_string()
303 }
304
305 #[test]
306 fn typed_tool_auto_generates_schema() {
307 let tool = TypedTool::new(
308 "get_weather",
309 "Get current weather for a city",
310 |_args: WeatherArgs| async move { Ok(json!({})) },
311 );
312
313 let params = tool.parameters().expect("should have parameters");
314
315 let props = ¶ms["properties"];
317 assert!(
318 props.get("city").is_some(),
319 "schema should contain 'city' property"
320 );
321 assert!(
322 props.get("units").is_some(),
323 "schema should contain 'units' property"
324 );
325
326 let required = params["required"]
328 .as_array()
329 .expect("should have required array");
330 let required_names: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
331 assert!(required_names.contains(&"city"), "city should be required");
332 }
333
334 #[tokio::test]
335 async fn typed_tool_deserializes_args() {
336 let tool = TypedTool::new(
337 "get_weather",
338 "Get current weather for a city",
339 |args: WeatherArgs| async move {
340 Ok(json!({
341 "temp": 22,
342 "city": args.city,
343 "units": args.units,
344 }))
345 },
346 );
347
348 let result = tool
349 .call(json!({"city": "London", "units": "fahrenheit"}))
350 .await
351 .unwrap();
352 assert_eq!(result["city"], "London");
353 assert_eq!(result["units"], "fahrenheit");
354 assert_eq!(result["temp"], 22);
355 }
356
357 #[tokio::test]
358 async fn typed_tool_invalid_args_returns_error() {
359 let tool = TypedTool::new(
360 "get_weather",
361 "Get current weather for a city",
362 |_args: WeatherArgs| async move { Ok(json!({})) },
363 );
364
365 let result = tool.call(json!({"units": "celsius"})).await;
367 assert!(result.is_err(), "should fail with missing required field");
368 let err = result.unwrap_err();
369 match &err {
370 ToolError::InvalidArgs(msg) => {
371 assert!(
372 msg.contains("city"),
373 "error message should mention the missing field: {msg}"
374 );
375 }
376 other => panic!("expected ToolError::InvalidArgs, got: {other:?}"),
377 }
378
379 let result = tool.call(json!({"city": 12345})).await;
381 assert!(result.is_err(), "should fail with wrong type");
382 }
383
384 #[tokio::test]
385 async fn typed_tool_registers_in_dispatcher() {
386 let tool = TypedTool::new(
387 "get_weather",
388 "Get current weather for a city",
389 |args: WeatherArgs| async move { Ok(json!({"city": args.city})) },
390 );
391
392 let mut dispatcher = ToolDispatcher::new();
393 dispatcher.register_function(Arc::new(tool));
394
395 assert_eq!(dispatcher.classify("get_weather"), Some(ToolClass::Regular));
396 assert_eq!(dispatcher.len(), 1);
397
398 let result = dispatcher
399 .call_function("get_weather", json!({"city": "Paris"}))
400 .await
401 .unwrap();
402 assert_eq!(result["city"], "Paris");
403
404 let decls = dispatcher.to_tool_declarations();
406 assert_eq!(decls.len(), 1);
407 }
408
409 struct SlowTool;
413
414 #[async_trait]
415 impl ToolFunction for SlowTool {
416 fn name(&self) -> &str {
417 "slow_tool"
418 }
419 fn description(&self) -> &str {
420 "A tool that never completes"
421 }
422 fn parameters(&self) -> Option<serde_json::Value> {
423 None
424 }
425 async fn call(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
426 tokio::time::sleep(Duration::from_secs(3600)).await;
428 Ok(json!({"result": "should never reach here"}))
429 }
430 }
431
432 #[tokio::test]
433 async fn tool_timeout_returns_error() {
434 let mut dispatcher = ToolDispatcher::new();
435 dispatcher.register_function(Arc::new(SlowTool));
436
437 let timeout = Duration::from_millis(50);
438 let result = dispatcher
439 .call_function_with_timeout("slow_tool", json!({}), timeout)
440 .await;
441
442 match result {
443 Err(ToolError::Timeout(d)) => assert_eq!(d, timeout),
444 other => panic!("expected ToolError::Timeout, got: {other:?}"),
445 }
446 }
447
448 #[tokio::test]
449 async fn tool_completes_before_timeout() {
450 let mut dispatcher = ToolDispatcher::new();
451 dispatcher.register_function(Arc::new(MockTool));
452
453 let result = dispatcher
454 .call_function_with_timeout("mock_tool", json!({}), Duration::from_secs(5))
455 .await
456 .unwrap();
457 assert_eq!(result["result"], "ok");
458 }
459
460 #[tokio::test]
461 async fn tool_cancelled_returns_error() {
462 let mut dispatcher = ToolDispatcher::new();
463 dispatcher.register_function(Arc::new(SlowTool));
464
465 let cancel = CancellationToken::new();
466 let cancel_clone = cancel.clone();
467
468 tokio::spawn(async move {
470 tokio::time::sleep(Duration::from_millis(50)).await;
471 cancel_clone.cancel();
472 });
473
474 let result = dispatcher
475 .call_function_with_cancel("slow_tool", json!({}), cancel)
476 .await;
477
478 match result {
479 Err(ToolError::Cancelled) => {} other => panic!("expected ToolError::Cancelled, got: {other:?}"),
481 }
482 }
483
484 #[test]
485 fn default_timeout_is_30s() {
486 let dispatcher = ToolDispatcher::new();
487 assert_eq!(dispatcher.default_timeout(), Duration::from_secs(30));
488 }
489
490 #[test]
491 fn with_timeout_overrides_default() {
492 let dispatcher = ToolDispatcher::new().with_timeout(Duration::from_secs(10));
493 assert_eq!(dispatcher.default_timeout(), Duration::from_secs(10));
494 }
495
496 #[tokio::test]
497 async fn call_function_uses_default_timeout() {
498 let mut dispatcher = ToolDispatcher::new().with_timeout(Duration::from_millis(50));
500 dispatcher.register_function(Arc::new(SlowTool));
501
502 let result = dispatcher.call_function("slow_tool", json!({})).await;
503
504 match result {
505 Err(ToolError::Timeout(d)) => assert_eq!(d, Duration::from_millis(50)),
506 other => panic!("expected ToolError::Timeout, got: {other:?}"),
507 }
508 }
509}