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