1use serde::{Deserialize, Serialize, de::DeserializeOwned};
14use std::{any::Any, collections::BTreeMap, future::Future, marker::PhantomData, sync::Arc};
15
16use crate::{
17 BoxError, BoxFut, BoxPinFut, Function, Json, Resource, ToolInput, ToolOutput,
18 context::BaseContext,
19 model::FunctionDefinition,
20 registry::{collect_groups, select_by_names},
21 select_resources, validate_function_name,
22};
23
24pub trait Tool<C>: Send + Sync
29where
30 C: BaseContext + Send + Sync,
31{
32 type Args: DeserializeOwned + Send;
34
35 type Output: Serialize;
37
38 fn name(&self) -> String;
47
48 fn description(&self) -> String;
50
51 fn definition(&self) -> FunctionDefinition;
56
57 fn group(&self) -> Option<ToolGroupInfo> {
63 None
64 }
65
66 fn supported_resource_tags(&self) -> Vec<String> {
75 Vec::new()
76 }
77
78 fn select_resources(&self, resources: &mut Vec<Resource>) -> Vec<Resource> {
80 let supported_tags = self.supported_resource_tags();
81 select_resources(resources, &supported_tags)
82 }
83
84 fn init(&self, _ctx: C) -> impl Future<Output = Result<(), BoxError>> + Send {
88 std::future::ready(Ok(()))
89 }
90
91 fn call(
101 &self,
102 ctx: C,
103 args: Self::Args,
104 resources: Vec<Resource>,
105 ) -> impl Future<Output = Result<ToolOutput<Self::Output>, BoxError>> + Send;
106
107 fn call_raw(
109 &self,
110 ctx: C,
111 args: Json,
112 resources: Vec<Resource>,
113 ) -> impl Future<Output = Result<ToolOutput<Json>, BoxError>> + Send {
114 async move {
115 let args: Self::Args = serde_json::from_value(args)
116 .map_err(|err| format!("tool {}, invalid args: {}", self.name(), err))?;
117 let mut result = self
118 .call(ctx, args, resources)
119 .await
120 .map_err(|err| format!("tool {}, call failed: {}", self.name(), err))?;
121 let output = serde_json::to_value(&result.output)?;
122 if result.usage.requests == 0 {
123 result.usage.requests = 1;
124 }
125
126 Ok(ToolOutput {
127 output,
128 is_error: result.is_error,
129 artifacts: result.artifacts,
130 usage: result.usage,
131 tools_usage: result.tools_usage,
132 })
133 }
134 }
135}
136
137pub trait DynTool<C>: Send + Sync
142where
143 C: BaseContext + Send + Sync,
144{
145 fn as_any(&self) -> &(dyn Any + Send + Sync);
147
148 fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
150
151 fn name(&self) -> String;
153
154 fn definition(&self) -> FunctionDefinition;
156
157 fn group(&self) -> Option<ToolGroupInfo> {
159 None
160 }
161
162 fn supported_resource_tags(&self) -> Vec<String>;
164
165 fn init(&self, ctx: C) -> BoxPinFut<Result<(), BoxError>>;
167
168 fn call(
170 &self,
171 ctx: C,
172 args: Json,
173 resources: Vec<Resource>,
174 ) -> BoxPinFut<Result<ToolOutput<Json>, BoxError>>;
175}
176
177#[derive(Debug, Clone, Default, Serialize, Deserialize)]
187pub struct ToolGroupInfo {
188 pub id: String,
190 pub title: String,
192 pub description: String,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub instructions: Option<String>,
198}
199
200#[derive(Debug, Clone, Default, Serialize, Deserialize)]
214pub struct ToolGroup {
215 pub id: String,
217 pub title: String,
219 pub description: String,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub instructions: Option<String>,
225 pub members: Vec<String>,
227}
228
229impl ToolGroup {
230 pub fn from_info(info: ToolGroupInfo, members: Vec<String>) -> Self {
232 Self {
233 id: info.id,
234 title: info.title,
235 description: info.description,
236 instructions: info.instructions,
237 members,
238 }
239 }
240}
241
242pub trait ToolProvider<C>: Send + Sync
249where
250 C: BaseContext + Send + Sync,
251{
252 fn name(&self) -> String;
257
258 fn definitions(&self, names: Option<&[String]>) -> Vec<FunctionDefinition>;
265
266 fn groups(&self) -> Vec<ToolGroup> {
272 Vec::new()
273 }
274
275 fn contains_lowercase(&self, lowercase_name: &str) -> bool {
281 self.definitions(Some(&[lowercase_name.to_string()]))
282 .iter()
283 .any(|definition| definition.name.eq_ignore_ascii_case(lowercase_name))
284 }
285
286 fn supported_resource_tags(&self, _name: &str) -> Vec<String> {
288 Vec::new()
289 }
290
291 fn select_resources(&self, name: &str, resources: &mut Vec<Resource>) -> Vec<Resource> {
293 let supported_tags = self.supported_resource_tags(name);
294 select_resources(resources, &supported_tags)
295 }
296
297 fn init(&self, _ctx: C) -> BoxFut<'_, Result<(), BoxError>> {
299 Box::pin(async { Ok(()) })
300 }
301
302 fn refresh(&self) -> BoxFut<'_, Result<(), BoxError>> {
304 Box::pin(async { Ok(()) })
305 }
306
307 fn call(
309 &self,
310 ctx: C,
311 input: ToolInput<Json>,
312 ) -> BoxFut<'_, Result<ToolOutput<Json>, BoxError>>;
313}
314
315impl<C> dyn DynTool<C>
316where
317 C: BaseContext + Send + Sync + 'static,
318{
319 pub fn downcast_ref<T>(&self) -> Option<&T>
321 where
322 T: Tool<C> + 'static,
323 {
324 self.as_any().downcast_ref::<T>()
325 }
326
327 pub fn downcast<T>(self: Arc<Self>) -> Result<Arc<T>, Arc<Self>>
329 where
330 T: Tool<C> + 'static,
331 {
332 match self.clone().into_any().downcast::<T>() {
333 Ok(tool) => Ok(tool),
334 Err(_) => Err(self),
335 }
336 }
337}
338
339struct ToolWrapper<T, C>(Arc<T>, PhantomData<C>)
341where
342 T: Tool<C> + 'static,
343 C: BaseContext + Send + Sync + 'static;
344
345impl<T, C> DynTool<C> for ToolWrapper<T, C>
346where
347 T: Tool<C> + 'static,
348 C: BaseContext + Send + Sync + 'static,
349{
350 fn as_any(&self) -> &(dyn Any + Send + Sync) {
351 self.0.as_ref()
352 }
353
354 fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
355 self.0.clone()
356 }
357
358 fn name(&self) -> String {
359 self.0.name()
360 }
361
362 fn definition(&self) -> FunctionDefinition {
363 self.0.definition()
364 }
365
366 fn group(&self) -> Option<ToolGroupInfo> {
367 self.0.group()
368 }
369
370 fn supported_resource_tags(&self) -> Vec<String> {
371 self.0.supported_resource_tags()
372 }
373
374 fn init(&self, ctx: C) -> BoxPinFut<Result<(), BoxError>> {
375 let tool = self.0.clone();
376 Box::pin(async move { tool.init(ctx).await })
377 }
378
379 fn call(
380 &self,
381 ctx: C,
382 args: Json,
383 resources: Vec<Resource>,
384 ) -> BoxPinFut<Result<ToolOutput<Json>, BoxError>> {
385 let tool = self.0.clone();
386 Box::pin(async move { tool.call_raw(ctx, args, resources).await })
387 }
388}
389
390#[derive(Default)]
395pub struct ToolSet<C: BaseContext> {
396 set: BTreeMap<String, Arc<dyn DynTool<C>>>,
402}
403
404#[derive(Default)]
406pub struct ToolProviderSet<C: BaseContext> {
407 set: BTreeMap<String, Arc<dyn ToolProvider<C>>>,
412}
413
414impl<C> ToolProviderSet<C>
415where
416 C: BaseContext + Clone + Send + Sync + 'static,
417{
418 pub fn new() -> Self {
420 Self {
421 set: BTreeMap::new(),
422 }
423 }
424
425 pub fn contains_provider(&self, name: &str) -> bool {
427 self.set.contains_key(&name.to_ascii_lowercase())
428 }
429
430 pub fn add<T>(&mut self, provider: Arc<T>) -> Result<(), BoxError>
432 where
433 T: ToolProvider<C> + Send + Sync + 'static,
434 {
435 self.add_dyn(provider)
436 }
437
438 pub fn add_dyn(&mut self, provider: Arc<dyn ToolProvider<C>>) -> Result<(), BoxError> {
443 let name = provider.name().to_ascii_lowercase();
444 validate_function_name(&name)?;
445 if self.set.contains_key(&name) {
446 return Err(format!("tool provider {} already exists", name).into());
447 }
448
449 self.set.insert(name, provider);
450 Ok(())
451 }
452
453 pub fn iter(&self) -> impl Iterator<Item = (&str, &Arc<dyn ToolProvider<C>>)> {
455 self.set
456 .iter()
457 .map(|(name, provider)| (name.as_str(), provider))
458 }
459
460 pub fn contains_lowercase(&self, lowercase_name: &str) -> bool {
462 self.set
463 .values()
464 .any(|provider| provider.contains_lowercase(lowercase_name))
465 }
466
467 pub fn definitions(&self, names: Option<&[String]>) -> Vec<FunctionDefinition> {
474 match names {
475 Some([]) => Vec::new(),
476 _ => {
477 let mut definitions = BTreeMap::new();
478 for provider in self.set.values() {
479 for mut definition in provider.definitions(names) {
480 definition.name.make_ascii_lowercase();
481 definitions
482 .entry(definition.name.clone())
483 .or_insert(definition);
484 }
485 }
486 definitions.into_values().collect()
487 }
488 }
489 }
490
491 pub fn groups(&self) -> Vec<ToolGroup> {
493 self.set
494 .values()
495 .flat_map(|provider| provider.groups())
496 .collect()
497 }
498
499 pub fn functions(&self, names: Option<&[String]>) -> Vec<Function> {
501 self.definitions(names)
502 .into_iter()
503 .map(|definition| {
504 let supported_resource_tags = self
505 .set
506 .values()
507 .find(|provider| provider.contains_lowercase(&definition.name))
508 .map(|provider| provider.supported_resource_tags(&definition.name))
509 .unwrap_or_default();
510 Function {
511 definition,
512 supported_resource_tags,
513 }
514 })
515 .collect()
516 }
517
518 pub fn select_resources(&self, name: &str, resources: &mut Vec<Resource>) -> Vec<Resource> {
520 if resources.is_empty() {
521 return Vec::new();
522 }
523
524 let lowercase_name = name.to_ascii_lowercase();
525 self.set
526 .values()
527 .find(|provider| provider.contains_lowercase(&lowercase_name))
528 .map(|provider| provider.select_resources(&lowercase_name, resources))
529 .unwrap_or_default()
530 }
531
532 pub async fn init_all(&self, ctx: C) -> Result<(), BoxError> {
534 for provider in self.set.values() {
535 provider.init(ctx.clone()).await?;
536 }
537 Ok(())
538 }
539
540 pub async fn refresh_all(&self) -> Result<(), BoxError> {
542 for provider in self.set.values() {
543 provider.refresh().await?;
544 }
545 Ok(())
546 }
547
548 pub async fn call(
550 &self,
551 ctx: C,
552 mut input: ToolInput<Json>,
553 ) -> Result<ToolOutput<Json>, BoxError> {
554 input.name.make_ascii_lowercase();
555 let provider = self
556 .set
557 .values()
558 .find(|provider| provider.contains_lowercase(&input.name))
559 .ok_or_else(|| format!("tool {} not found", input.name))?;
560 provider.call(ctx, input).await
561 }
562}
563
564impl<C> IntoIterator for ToolProviderSet<C>
565where
566 C: BaseContext + Clone + Send + Sync + 'static,
567{
568 type Item = Arc<dyn ToolProvider<C>>;
569 type IntoIter = std::collections::btree_map::IntoValues<String, Arc<dyn ToolProvider<C>>>;
570
571 fn into_iter(self) -> Self::IntoIter {
573 self.set.into_values()
574 }
575}
576
577impl<C> ToolSet<C>
578where
579 C: BaseContext + Send + Sync + 'static,
580{
581 pub fn new() -> Self {
583 Self {
584 set: BTreeMap::new(),
585 }
586 }
587
588 pub fn contains(&self, name: &str) -> bool {
590 self.set.contains_key(&name.to_ascii_lowercase())
591 }
592
593 pub fn contains_lowercase(&self, lowercase_name: &str) -> bool {
595 self.set.contains_key(lowercase_name)
596 }
597
598 pub fn names(&self) -> Vec<String> {
600 self.set.keys().cloned().collect()
601 }
602
603 pub fn groups(&self) -> Vec<ToolGroup> {
610 collect_groups(self.set.iter().map(|(name, tool)| (name, tool.group())))
611 }
612
613 pub fn definition(&self, name: &str) -> Option<FunctionDefinition> {
615 self.set
616 .get(&name.to_ascii_lowercase())
617 .map(|tool| tool.definition())
618 }
619
620 pub fn definitions(&self, names: Option<&[String]>) -> Vec<FunctionDefinition> {
630 select_by_names(&self.set, names, |tool| tool.definition())
631 }
632
633 pub fn functions(&self, names: Option<&[String]>) -> Vec<Function> {
643 select_by_names(&self.set, names, |tool| Function {
644 definition: tool.definition(),
645 supported_resource_tags: tool.supported_resource_tags(),
646 })
647 }
648
649 pub fn select_resources(&self, name: &str, resources: &mut Vec<Resource>) -> Vec<Resource> {
651 if resources.is_empty() {
652 return Vec::new();
653 }
654
655 self.set
656 .get(&name.to_ascii_lowercase())
657 .map(|tool| {
658 let supported_tags = tool.supported_resource_tags();
659 select_resources(resources, &supported_tags)
660 })
661 .unwrap_or_default()
662 }
663
664 pub fn add<T>(&mut self, tool: Arc<T>) -> Result<(), BoxError>
669 where
670 T: Tool<C> + Send + Sync + 'static,
671 {
672 self.add_dyn(Arc::new(ToolWrapper(tool, PhantomData)))
673 }
674
675 pub fn add_dyn(&mut self, tool: Arc<dyn DynTool<C>>) -> Result<(), BoxError> {
680 let name = tool.name().to_ascii_lowercase();
681 validate_function_name(&name)?;
682 if self.set.contains_key(&name) {
683 return Err(format!("tool {} already exists", name).into());
684 }
685
686 self.set.insert(name, tool);
687 Ok(())
688 }
689
690 pub fn iter(&self) -> impl Iterator<Item = (&str, &Arc<dyn DynTool<C>>)> {
692 self.set.iter().map(|(name, tool)| (name.as_str(), tool))
693 }
694
695 pub fn get(&self, name: &str) -> Option<Arc<dyn DynTool<C>>> {
697 self.set.get(&name.to_ascii_lowercase()).cloned()
698 }
699
700 pub fn get_lowercase(&self, lowercase_name: &str) -> Option<Arc<dyn DynTool<C>>> {
702 self.set.get(lowercase_name).cloned()
703 }
704}
705
706impl<C> IntoIterator for ToolSet<C>
707where
708 C: BaseContext + Send + Sync + 'static,
709{
710 type Item = Arc<dyn DynTool<C>>;
711 type IntoIter = std::collections::btree_map::IntoValues<String, Arc<dyn DynTool<C>>>;
712
713 fn into_iter(self) -> Self::IntoIter {
715 self.set.into_values()
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722 use crate::test_support::{MockContext, resource};
723 use serde_json::json;
724
725 struct ExampleTool {
726 id: usize,
727 }
728
729 struct OtherTool;
730
731 #[derive(serde::Deserialize)]
732 struct EchoArgs {
733 value: String,
734 fail: bool,
735 }
736
737 struct TaggedTool;
738
739 struct InvalidTool;
740
741 impl Tool<MockContext> for ExampleTool {
742 type Args = ();
743 type Output = String;
744
745 fn name(&self) -> String {
746 "example_tool".to_string()
747 }
748
749 fn description(&self) -> String {
750 "Example tool used for downcast tests".to_string()
751 }
752
753 fn definition(&self) -> FunctionDefinition {
754 FunctionDefinition {
755 name: self.name(),
756 description: self.description(),
757 parameters: json!({
758 "type": "object",
759 "properties": {},
760 "required": [],
761 "additionalProperties": false
762 }),
763 strict: Some(true),
764 }
765 }
766
767 async fn call(
768 &self,
769 _ctx: MockContext,
770 _args: Self::Args,
771 _resources: Vec<Resource>,
772 ) -> Result<ToolOutput<Self::Output>, BoxError> {
773 Ok(ToolOutput::new(self.id.to_string()))
774 }
775 }
776
777 impl Tool<MockContext> for OtherTool {
778 type Args = ();
779 type Output = String;
780
781 fn name(&self) -> String {
782 "other_tool".to_string()
783 }
784
785 fn description(&self) -> String {
786 "Other tool used for downcast tests".to_string()
787 }
788
789 fn definition(&self) -> FunctionDefinition {
790 FunctionDefinition {
791 name: self.name(),
792 description: self.description(),
793 parameters: json!({
794 "type": "object",
795 "properties": {},
796 "required": [],
797 "additionalProperties": false
798 }),
799 strict: Some(true),
800 }
801 }
802
803 async fn call(
804 &self,
805 _ctx: MockContext,
806 _args: Self::Args,
807 _resources: Vec<Resource>,
808 ) -> Result<ToolOutput<Self::Output>, BoxError> {
809 Ok(ToolOutput::new("other".to_string()))
810 }
811 }
812
813 impl Tool<MockContext> for TaggedTool {
814 type Args = EchoArgs;
815 type Output = Json;
816
817 fn name(&self) -> String {
818 "tagged_tool".to_string()
819 }
820
821 fn description(&self) -> String {
822 "Tool that consumes text and code resources".to_string()
823 }
824
825 fn definition(&self) -> FunctionDefinition {
826 FunctionDefinition {
827 name: self.name(),
828 description: self.description(),
829 parameters: json!({
830 "type": "object",
831 "properties": {
832 "value": {"type": "string"},
833 "fail": {"type": "boolean"}
834 },
835 "required": ["value", "fail"],
836 "additionalProperties": false
837 }),
838 strict: Some(true),
839 }
840 }
841
842 fn supported_resource_tags(&self) -> Vec<String> {
843 vec!["text".to_string(), "code".to_string()]
844 }
845
846 async fn call(
847 &self,
848 _ctx: MockContext,
849 args: Self::Args,
850 resources: Vec<Resource>,
851 ) -> Result<ToolOutput<Self::Output>, BoxError> {
852 if args.fail {
853 return Err("forced failure".into());
854 }
855
856 let mut output = ToolOutput::new(json!({
857 "value": args.value,
858 "resources": resources.len(),
859 }));
860 output.is_error = Some(false);
861 Ok(output)
862 }
863 }
864
865 impl Tool<MockContext> for InvalidTool {
866 type Args = ();
867 type Output = String;
868
869 fn name(&self) -> String {
870 "bad.tool".to_string()
871 }
872
873 fn description(&self) -> String {
874 "Invalid function name".to_string()
875 }
876
877 fn definition(&self) -> FunctionDefinition {
878 FunctionDefinition {
879 name: self.name(),
880 description: self.description(),
881 parameters: json!({"type": "object"}),
882 strict: Some(true),
883 }
884 }
885
886 async fn call(
887 &self,
888 _ctx: MockContext,
889 _args: Self::Args,
890 _resources: Vec<Resource>,
891 ) -> Result<ToolOutput<Self::Output>, BoxError> {
892 Ok(ToolOutput::new(String::new()))
893 }
894 }
895
896 #[test]
897 fn dyn_tool_downcast_ref_returns_inner_tool() {
898 let tool = Arc::new(ExampleTool { id: 7 });
899 let mut tool_set = ToolSet::<MockContext>::new();
900 tool_set.add(tool).unwrap();
901
902 let dyn_tool = tool_set.get("example_tool").unwrap();
903 let concrete = dyn_tool.downcast_ref::<ExampleTool>().unwrap();
904
905 assert_eq!(concrete.id, 7);
906 assert!(dyn_tool.downcast_ref::<OtherTool>().is_none());
907 }
908
909 #[test]
910 fn dyn_tool_downcast_returns_original_arc() {
911 let tool = Arc::new(ExampleTool { id: 9 });
912 let mut tool_set = ToolSet::<MockContext>::new();
913 tool_set.add(tool.clone()).unwrap();
914
915 let dyn_tool = tool_set.get("example_tool").unwrap();
916 let concrete = match dyn_tool.downcast::<ExampleTool>() {
917 Ok(tool) => tool,
918 Err(_) => panic!("expected downcast to ExampleTool to succeed"),
919 };
920
921 assert_eq!(concrete.id, 9);
922 assert!(Arc::ptr_eq(&concrete, &tool));
923 }
924
925 #[test]
926 fn dyn_tool_downcast_mismatch_returns_original_arc() {
927 let tool = Arc::new(ExampleTool { id: 11 });
928 let mut tool_set = ToolSet::<MockContext>::new();
929 tool_set.add(tool).unwrap();
930
931 let dyn_tool = tool_set.get("example_tool").unwrap();
932 let original = dyn_tool.clone();
933 let err = match dyn_tool.downcast::<OtherTool>() {
934 Ok(_) => panic!("expected downcast to OtherTool to fail"),
935 Err(err) => err,
936 };
937
938 assert!(Arc::ptr_eq(&err, &original));
939 assert_eq!(err.name(), "example_tool");
940 }
941
942 #[test]
943 fn fixture_tools_cover_direct_methods() {
944 futures::executor::block_on(async {
945 let other = OtherTool;
946 assert_eq!(other.name(), "other_tool");
947 assert_eq!(other.description(), "Other tool used for downcast tests");
948 let definition = other.definition();
949 assert_eq!(definition.name, "other_tool");
950 assert_eq!(definition.description, "Other tool used for downcast tests");
951 assert_eq!(definition.parameters["type"], "object");
952 let output = other
953 .call(MockContext::default(), (), Vec::new())
954 .await
955 .unwrap();
956 assert_eq!(output.output, "other");
957
958 let invalid = InvalidTool;
959 assert_eq!(invalid.name(), "bad.tool");
960 assert_eq!(invalid.description(), "Invalid function name");
961 let definition = invalid.definition();
962 assert_eq!(definition.name, "bad.tool");
963 assert_eq!(definition.description, "Invalid function name");
964 assert_eq!(definition.parameters["type"], "object");
965 let output = invalid
966 .call(MockContext::default(), (), Vec::new())
967 .await
968 .unwrap();
969 assert!(output.output.is_empty());
970 });
971 }
972
973 #[test]
974 fn tool_default_methods_call_raw_and_dyn_wrapper_forward_calls() {
975 futures::executor::block_on(async {
976 let tool = Arc::new(ExampleTool { id: 42 });
977 let mut resources = vec![resource(1, &["text"])];
978
979 assert!(tool.supported_resource_tags().is_empty());
980 assert!(tool.select_resources(&mut resources).is_empty());
981 assert_eq!(resources.len(), 1);
982 tool.init(MockContext::default()).await.unwrap();
983
984 let raw = tool
985 .call_raw(MockContext::default(), Json::Null, Vec::new())
986 .await
987 .unwrap();
988 assert_eq!(raw.output, json!("42"));
989 assert_eq!(raw.usage.requests, 1);
990
991 let invalid = tool
992 .call_raw(MockContext::default(), json!({"bad": true}), Vec::new())
993 .await
994 .unwrap_err();
995 assert!(invalid.to_string().contains("invalid args"));
996
997 let mut tool_set = ToolSet::<MockContext>::new();
998 tool_set.add(tool).unwrap();
999 let dyn_tool = tool_set.get("EXAMPLE_TOOL").unwrap();
1000
1001 assert_eq!(dyn_tool.name(), "example_tool");
1002 assert_eq!(dyn_tool.definition().name, "example_tool");
1003 assert!(dyn_tool.supported_resource_tags().is_empty());
1004 dyn_tool.init(MockContext::default()).await.unwrap();
1005
1006 let output = dyn_tool
1007 .call(MockContext::default(), Json::Null, Vec::new())
1008 .await
1009 .unwrap();
1010 assert_eq!(output.output, json!("42"));
1011 assert_eq!(output.usage.requests, 1);
1012 });
1013 }
1014
1015 #[test]
1016 fn tool_set_registry_filters_resources_and_reports_errors() {
1017 futures::executor::block_on(async {
1018 let mut tool_set = ToolSet::<MockContext>::new();
1019 tool_set.add(Arc::new(ExampleTool { id: 1 })).unwrap();
1020 tool_set.add(Arc::new(TaggedTool)).unwrap();
1021
1022 assert!(tool_set.contains("EXAMPLE_TOOL"));
1023 assert!(tool_set.contains_lowercase("tagged_tool"));
1024 assert!(!tool_set.contains("missing_tool"));
1025 assert_eq!(
1026 tool_set.names(),
1027 vec!["example_tool".to_string(), "tagged_tool".to_string()]
1028 );
1029
1030 let definition = tool_set.definition("TAGGED_TOOL").unwrap();
1031 assert_eq!(definition.name, "tagged_tool");
1032 assert!(tool_set.definition("missing_tool").is_none());
1033
1034 let selected_names = vec!["TAGGED_TOOL".to_string(), "missing_tool".to_string()];
1035 let selected_definitions = tool_set.definitions(Some(&selected_names));
1036 assert_eq!(selected_definitions.len(), 1);
1037 assert_eq!(selected_definitions[0].name, "tagged_tool");
1038 assert_eq!(tool_set.definitions(None).len(), 2);
1039
1040 let duplicate_names = vec![
1042 "tagged_tool".to_string(),
1043 "TAGGED_TOOL".to_string(),
1044 "tagged_tool".to_string(),
1045 ];
1046 assert_eq!(tool_set.definitions(Some(&duplicate_names)).len(), 1);
1047 assert_eq!(tool_set.functions(Some(&duplicate_names)).len(), 1);
1048
1049 let selected_functions = tool_set.functions(Some(&selected_names));
1050 assert_eq!(selected_functions.len(), 1);
1051 assert_eq!(
1052 selected_functions[0].supported_resource_tags,
1053 vec!["text".to_string(), "code".to_string()]
1054 );
1055 assert_eq!(tool_set.functions(None).len(), 2);
1056
1057 let mut resources = vec![
1058 resource(1, &["image"]),
1059 resource(2, &["text"]),
1060 resource(3, &["code", "text"]),
1061 resource(4, &["audio"]),
1062 ];
1063 let selected = tool_set.select_resources("TAGGED_TOOL", &mut resources);
1064 assert_eq!(
1065 selected
1066 .iter()
1067 .map(|resource| resource._id)
1068 .collect::<Vec<_>>(),
1069 vec![2, 3]
1070 );
1071 assert_eq!(
1072 resources
1073 .iter()
1074 .map(|resource| resource._id)
1075 .collect::<Vec<_>>(),
1076 vec![1, 4]
1077 );
1078 assert!(
1079 tool_set
1080 .select_resources("missing_tool", &mut resources)
1081 .is_empty()
1082 );
1083
1084 let dyn_tool = tool_set.get_lowercase("tagged_tool").unwrap();
1085 let output = dyn_tool
1086 .call(
1087 MockContext::default(),
1088 json!({"value": "ok", "fail": false}),
1089 vec![resource(9, &["text"])],
1090 )
1091 .await
1092 .unwrap();
1093 assert_eq!(output.output["value"], "ok");
1094 assert_eq!(output.output["resources"], 1);
1095 assert_eq!(output.is_error, Some(false));
1096 assert_eq!(output.usage.requests, 1);
1097 assert!(tool_set.get("missing_tool").is_none());
1098 assert!(tool_set.get_lowercase("missing_tool").is_none());
1099
1100 let failed = dyn_tool
1101 .call(
1102 MockContext::default(),
1103 json!({"value": "bad", "fail": true}),
1104 Vec::new(),
1105 )
1106 .await
1107 .unwrap_err();
1108 assert!(failed.to_string().contains("call failed"));
1109
1110 let duplicate = tool_set.add(Arc::new(ExampleTool { id: 2 })).unwrap_err();
1111 assert!(duplicate.to_string().contains("already exists"));
1112
1113 let invalid = tool_set.add(Arc::new(InvalidTool)).unwrap_err();
1114 assert!(invalid.to_string().contains("invalid character"));
1115 });
1116 }
1117
1118 struct GroupedTool {
1119 name: &'static str,
1120 group: &'static str,
1121 }
1122
1123 impl Tool<MockContext> for GroupedTool {
1124 type Args = ();
1125 type Output = String;
1126
1127 fn name(&self) -> String {
1128 self.name.to_string()
1129 }
1130
1131 fn description(&self) -> String {
1132 "Grouped tool fixture".to_string()
1133 }
1134
1135 fn definition(&self) -> FunctionDefinition {
1136 FunctionDefinition {
1137 name: self.name(),
1138 description: self.description(),
1139 parameters: json!({
1140 "type": "object",
1141 "properties": {},
1142 "required": [],
1143 "additionalProperties": false
1144 }),
1145 strict: Some(true),
1146 }
1147 }
1148
1149 fn group(&self) -> Option<ToolGroupInfo> {
1150 Some(ToolGroupInfo {
1151 id: self.group.to_string(),
1152 title: format!("{} title", self.group),
1153 description: format!("{} description", self.group),
1154 instructions: Some(format!("{} instructions", self.group)),
1155 })
1156 }
1157
1158 async fn call(
1159 &self,
1160 _ctx: MockContext,
1161 _args: Self::Args,
1162 _resources: Vec<Resource>,
1163 ) -> Result<ToolOutput<Self::Output>, BoxError> {
1164 Ok(ToolOutput::new(String::new()))
1165 }
1166 }
1167
1168 #[test]
1169 fn tool_set_groups_aggregate_members_by_id() {
1170 let mut tool_set = ToolSet::<MockContext>::new();
1171 tool_set
1172 .add(Arc::new(GroupedTool {
1173 name: "fs_write",
1174 group: "fs",
1175 }))
1176 .unwrap();
1177 tool_set
1178 .add(Arc::new(GroupedTool {
1179 name: "fs_read",
1180 group: "fs",
1181 }))
1182 .unwrap();
1183 tool_set
1184 .add(Arc::new(GroupedTool {
1185 name: "mem_get",
1186 group: "memory",
1187 }))
1188 .unwrap();
1189 tool_set.add(Arc::new(ExampleTool { id: 1 })).unwrap();
1191
1192 let groups = tool_set.groups();
1193 assert_eq!(groups.len(), 2);
1194
1195 let fs = groups.iter().find(|group| group.id == "fs").unwrap();
1196 assert_eq!(
1198 fs.members,
1199 vec!["fs_read".to_string(), "fs_write".to_string()]
1200 );
1201 assert_eq!(fs.title, "fs title");
1202 assert_eq!(fs.instructions.as_deref(), Some("fs instructions"));
1203
1204 let memory = groups.iter().find(|group| group.id == "memory").unwrap();
1205 assert_eq!(memory.members, vec!["mem_get".to_string()]);
1206 }
1207}