1#[cfg(feature = "derive")]
154pub use aither_derive::tool;
155use alloc::borrow::Cow;
156use serde_json::Value;
157
158use crate::Result;
159use alloc::format;
160use alloc::string::{String, ToString};
161use alloc::vec::Vec;
162use alloc::{boxed::Box, collections::BTreeMap};
163use core::any::Any;
164use core::fmt::{Debug, Display};
165use core::{future::Future, pin::Pin};
166pub use mime::Mime;
167use schemars::{JsonSchema, Schema, schema_for};
168use serde::{Serialize, de::DeserializeOwned};
169
170#[derive(Debug, Clone, PartialEq, Eq)]
189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
190#[cfg_attr(feature = "serde", serde(tag = "kind", rename_all = "snake_case"))]
191pub enum ToolResult {
192 Done,
194
195 Text {
197 text: String,
199 },
200
201 Tsv {
203 text: String,
205 },
206
207 Json {
209 value: Value,
211 },
212
213 Binary {
215 mime: String,
217 content: Vec<u8>,
219 },
220
221 Error {
223 message: String,
225 },
226}
227
228impl ToolResult {
229 #[must_use]
231 pub fn text(s: impl Into<String>) -> Self {
232 Self::Text { text: s.into() }
233 }
234
235 #[must_use]
237 pub fn tsv(s: impl Into<String>) -> Self {
238 Self::Tsv { text: s.into() }
239 }
240
241 pub fn json<T: Serialize>(value: &T) -> Result<Self> {
247 Ok(Self::Json {
248 value: serde_json::to_value(value)?,
249 })
250 }
251
252 #[must_use]
254 pub const fn json_value(value: Value) -> Self {
255 Self::Json { value }
256 }
257
258 #[must_use]
260 pub fn image(data: Vec<u8>, media_type: &str) -> Self {
261 Self::Binary {
262 mime: parse_media_type_or_octet_stream(media_type),
263 content: data,
264 }
265 }
266
267 #[must_use]
269 pub fn binary(data: Vec<u8>) -> Self {
270 Self::Binary {
271 mime: mime::APPLICATION_OCTET_STREAM.essence_str().to_string(),
272 content: data,
273 }
274 }
275
276 #[must_use]
278 pub fn error(message: impl Into<String>) -> Self {
279 Self::Error {
280 message: message.into(),
281 }
282 }
283
284 #[must_use]
286 pub const fn is_done(&self) -> bool {
287 matches!(self, Self::Done)
288 }
289
290 #[must_use]
292 pub const fn is_error(&self) -> bool {
293 matches!(self, Self::Error { .. })
294 }
295
296 #[must_use]
298 pub fn as_text(&self) -> Option<&str> {
299 match self {
300 Self::Text { text } | Self::Tsv { text } => Some(text),
301 Self::Error { message } => Some(message),
302 Self::Done | Self::Json { .. } | Self::Binary { .. } => None,
303 }
304 }
305
306 #[must_use]
308 pub fn error_message(&self) -> Option<&str> {
309 match self {
310 Self::Error { message } => Some(message),
311 Self::Done
312 | Self::Text { .. }
313 | Self::Tsv { .. }
314 | Self::Json { .. }
315 | Self::Binary { .. } => None,
316 }
317 }
318
319 pub fn render_for_model(&self) -> Result<String> {
325 match self {
326 Self::Done => Ok(String::new()),
327 Self::Text { text } | Self::Tsv { text } => Ok(text.clone()),
328 Self::Json { value } => Ok(serde_json::to_string(value)?),
329 Self::Binary { mime, content } => {
330 let mut rendered = String::new();
331 rendered.push_str("[binary tool result: ");
332 rendered.push_str(mime);
333 rendered.push_str(", ");
334 rendered.push_str(content.len().to_string().as_str());
335 rendered.push_str(" bytes]");
336 Ok(rendered)
337 }
338 Self::Error { message } => Ok(message.clone()),
339 }
340 }
341
342 pub fn render_for_cli(&self) -> Result<String> {
348 match self {
349 Self::Done => Ok(String::new()),
350 Self::Text { text } | Self::Tsv { text } => Ok(text.clone()),
351 Self::Json { value } => Ok(serde_json::to_string_pretty(value)?),
352 Self::Binary { mime, content } => {
353 let mut rendered = String::new();
354 rendered.push_str("[binary tool result: ");
355 rendered.push_str(mime);
356 rendered.push_str(", ");
357 rendered.push_str(content.len().to_string().as_str());
358 rendered.push_str(" bytes]");
359 Ok(rendered)
360 }
361 Self::Error { message } => Ok(message.clone()),
362 }
363 }
364
365 #[must_use]
367 pub fn mime(&self) -> Option<Mime> {
368 match self {
369 Self::Binary { mime, .. } => mime.parse().ok(),
370 Self::Done
371 | Self::Text { .. }
372 | Self::Tsv { .. }
373 | Self::Json { .. }
374 | Self::Error { .. } => None,
375 }
376 }
377
378 #[must_use]
380 pub fn content(&self) -> Option<&[u8]> {
381 match self {
382 Self::Binary { content, .. } => Some(content),
383 Self::Done
384 | Self::Text { .. }
385 | Self::Tsv { .. }
386 | Self::Json { .. }
387 | Self::Error { .. } => None,
388 }
389 }
390}
391
392pub trait IntoToolResult {
398 fn into_tool_result(self) -> Result<ToolResult>;
404}
405
406impl IntoToolResult for ToolResult {
407 fn into_tool_result(self) -> Result<ToolResult> {
408 Ok(self)
409 }
410}
411
412impl IntoToolResult for () {
413 fn into_tool_result(self) -> Result<ToolResult> {
414 Ok(ToolResult::Done)
415 }
416}
417
418impl IntoToolResult for String {
419 fn into_tool_result(self) -> Result<ToolResult> {
420 Ok(ToolResult::text(self))
421 }
422}
423
424impl IntoToolResult for &str {
425 fn into_tool_result(self) -> Result<ToolResult> {
426 Ok(ToolResult::text(self))
427 }
428}
429
430impl IntoToolResult for Cow<'_, str> {
431 fn into_tool_result(self) -> Result<ToolResult> {
432 Ok(ToolResult::text(self.into_owned()))
433 }
434}
435
436impl IntoToolResult for Value {
437 fn into_tool_result(self) -> Result<ToolResult> {
438 Ok(ToolResult::json_value(self))
439 }
440}
441
442impl<T> IntoToolResult for Option<T>
443where
444 T: IntoToolResult,
445{
446 fn into_tool_result(self) -> Result<ToolResult> {
447 self.map_or_else(|| Ok(ToolResult::Done), IntoToolResult::into_tool_result)
448 }
449}
450
451impl<T, E> IntoToolResult for core::result::Result<T, E>
452where
453 T: Serialize,
454 E: Display,
455{
456 fn into_tool_result(self) -> Result<ToolResult> {
457 match self {
458 Ok(value) => serialize_success_value(&value),
459 Err(error) => Ok(ToolResult::error(error.to_string())),
460 }
461 }
462}
463
464fn parse_media_type_or_octet_stream(media_type: &str) -> String {
465 media_type
466 .parse::<Mime>()
467 .unwrap_or(mime::APPLICATION_OCTET_STREAM)
468 .essence_str()
469 .to_string()
470}
471
472fn serialize_success_value<T: Serialize>(value: &T) -> Result<ToolResult> {
473 let value = serde_json::to_value(value)?;
474 if let Some(tsv) = json_value_to_tsv(&value) {
475 return Ok(ToolResult::tsv(tsv));
476 }
477
478 match value {
479 Value::String(text) => Ok(ToolResult::text(text)),
480 other => Ok(ToolResult::json_value(other)),
481 }
482}
483
484#[must_use]
486pub fn json_value_to_tsv(value: &Value) -> Option<String> {
487 let rows = match value {
488 Value::Array(arr) if !arr.is_empty() => arr
489 .iter()
490 .map(|value| flatten_json_value(value, ""))
491 .collect::<Vec<_>>(),
492 Value::Object(_) => alloc::vec![flatten_json_value(value, "")],
493 Value::Array(_) | Value::String(_) | Value::Number(_) | Value::Bool(_) | Value::Null => {
494 return None;
495 }
496 };
497
498 if rows.is_empty() {
499 return None;
500 }
501
502 let mut columns: Vec<String> = Vec::new();
503 let mut seen: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
504 for row in &rows {
505 for (key, _) in row {
506 if seen.insert(key.clone()) {
507 columns.push(key.clone());
508 }
509 }
510 }
511
512 if columns.is_empty() {
513 return None;
514 }
515
516 let mut tsv = String::new();
517 for (index, column) in columns.iter().enumerate() {
518 if index > 0 {
519 tsv.push('\t');
520 }
521 tsv.push_str(&escape_tsv_field(column));
522 }
523 tsv.push('\n');
524
525 for row in &rows {
526 let row_map: alloc::collections::BTreeMap<&str, &str> = row
527 .iter()
528 .map(|(key, value)| (key.as_str(), value.as_str()))
529 .collect::<alloc::collections::BTreeMap<&str, &str>>();
530 for (index, column) in columns.iter().enumerate() {
531 if index > 0 {
532 tsv.push('\t');
533 }
534 if let Some(value) = row_map.get(column.as_str()) {
535 tsv.push_str(&escape_tsv_field(value));
536 }
537 }
538 tsv.push('\n');
539 }
540
541 Some(tsv)
542}
543
544fn flatten_json_value(value: &Value, prefix: &str) -> Vec<(String, String)> {
545 let mut flattened = Vec::new();
546 match value {
547 Value::Object(map) => {
548 for (key, child) in map {
549 let full_key = if prefix.is_empty() {
550 key.clone()
551 } else {
552 format!("{prefix}.{key}")
553 };
554 flattened.extend(flatten_json_value(child, &full_key));
555 }
556 }
557 Value::Array(_) => {
558 let serialized = serde_json::to_string(value).unwrap_or_default();
559 flattened.push((prefix.to_string(), serialized));
560 }
561 Value::String(text) => {
562 flattened.push((prefix.to_string(), text.clone()));
563 }
564 Value::Number(number) => {
565 flattened.push((prefix.to_string(), number.to_string()));
566 }
567 Value::Bool(boolean) => {
568 flattened.push((prefix.to_string(), boolean.to_string()));
569 }
570 Value::Null => {
571 flattened.push((prefix.to_string(), String::new()));
572 }
573 }
574 flattened
575}
576
577fn escape_tsv_field(value: &str) -> String {
578 value.replace(['\t', '\n', '\r'], " ")
579}
580
581pub trait Tool: Send + Sync {
621 fn name(&self) -> Cow<'static, str>;
623
624 fn description(&self) -> Cow<'static, str> {
634 description_from_schema::<Self::Arguments>().unwrap_or_default()
635 }
636
637 type Arguments: Send + JsonSchema + DeserializeOwned;
640
641 type Res: IntoToolResult + Send;
643
644 fn call(&self, arguments: Self::Arguments) -> impl Future<Output = Result<Self::Res>> + Send;
650}
651
652pub fn json<T: Serialize>(value: &T) -> Result<String> {
676 let value = serde_json::to_value(value)?;
677
678 Ok(value
679 .as_str()
680 .map_or_else(|| format!("{value:#}"), ToString::to_string))
681}
682
683trait ToolImpl: Send + Sync + Any {
684 fn call(&self, args: &str) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send + '_>>;
685
686 fn definition(&self) -> &ToolDefinition;
689
690 fn as_any(&self) -> &dyn Any;
695
696 fn as_any_mut(&mut self) -> &mut dyn Any;
698}
699
700struct DynToolImpl<F>
702where
703 F: Fn(&str) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send>> + Send + Sync,
704{
705 definition: ToolDefinition,
706 handler: F,
707}
708
709impl<F> ToolImpl for DynToolImpl<F>
710where
711 F: Fn(&str) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send>> + Send + Sync + 'static,
712{
713 fn call(&self, args: &str) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send + '_>> {
714 (self.handler)(args)
715 }
716
717 fn definition(&self) -> &ToolDefinition {
718 &self.definition
719 }
720
721 fn as_any(&self) -> &dyn Any {
722 self
723 }
724
725 fn as_any_mut(&mut self) -> &mut dyn Any {
726 self
727 }
728}
729
730fn schema_is_object(value: &Value) -> bool {
733 matches!(value.get("type").and_then(Value::as_str), Some("object"))
734 || value.get("properties").is_some()
735 || value.get("oneOf").is_some()
736 || value.get("anyOf").is_some()
737 || value.get("$defs").is_some()
738}
739
740fn is_object<T: JsonSchema>() -> bool {
741 schema_is_object(&schema_for!(T).to_value())
742}
743
744fn arguments_schema<T: JsonSchema>() -> Schema {
747 if is_object::<T>() {
748 schema_for!(T)
749 } else {
750 schema_for!(ToolArgument<T>)
751 }
752}
753
754fn description_from_schema<T: JsonSchema>() -> Option<Cow<'static, str>> {
756 schema_for!(T)
757 .to_value()
758 .get("description")
759 .and_then(Value::as_str)
760 .filter(|text| !text.trim().is_empty())
761 .map(|text| Cow::Owned(text.to_string()))
762}
763
764struct RegisteredTool<T: Tool> {
770 tool: T,
771 definition: ToolDefinition,
772 args_are_object: bool,
773}
774
775impl<T: Tool> RegisteredTool<T> {
776 fn new(tool: T) -> Self {
777 let definition = ToolDefinition::new(&tool);
778 let args_are_object = is_object::<T::Arguments>();
779 Self {
780 tool,
781 definition,
782 args_are_object,
783 }
784 }
785}
786
787impl<T: Tool + 'static> ToolImpl for RegisteredTool<T> {
788 fn call(&self, args: &str) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send + '_>> {
789 let result = if self.args_are_object {
790 serde_json::from_str::<T::Arguments>(args)
791 } else {
792 serde_json::from_str::<ToolArgument<T::Arguments>>(args).map(|wrapper| wrapper.value)
793 };
794
795 let Ok(arguments) = result else {
796 let name = self.definition.name().to_string();
799 let schema_str =
800 serde_json::to_string_pretty(&self.definition.arguments_openai_schema())
801 .unwrap_or_else(|_| "{}".to_string());
802 return Box::pin(async move {
803 Err(anyhow::Error::msg(format!(
804 "Invalid arguments for tool '{name}'. Expected schema:\n{schema_str}"
805 )))
806 });
807 };
808
809 Box::pin(async move { Tool::call(&self.tool, arguments).await?.into_tool_result() })
810 }
811
812 fn definition(&self) -> &ToolDefinition {
813 &self.definition
814 }
815
816 fn as_any(&self) -> &dyn Any {
817 &self.tool
818 }
819
820 fn as_any_mut(&mut self) -> &mut dyn Any {
821 &mut self.tool
822 }
823}
824
825#[derive(Debug, Clone, PartialEq, Eq)]
830pub struct InvalidSchema {
831 name: Cow<'static, str>,
833}
834
835impl InvalidSchema {
836 #[must_use]
838 pub fn name(&self) -> &str {
839 &self.name
840 }
841}
842
843impl Display for InvalidSchema {
844 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
845 write!(
846 f,
847 "tool '{}' has an argument schema that is neither an object nor a boolean",
848 self.name
849 )
850 }
851}
852
853impl core::error::Error for InvalidSchema {}
854
855#[derive(Debug, Clone, PartialEq, Eq)]
857pub enum RegisterError {
858 DuplicateName(Cow<'static, str>),
862
863 EmptyDescription(Cow<'static, str>),
868}
869
870impl Display for RegisterError {
871 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
872 match self {
873 Self::DuplicateName(name) => {
874 write!(f, "a tool named '{name}' is already registered")
875 }
876 Self::EmptyDescription(name) => write!(
877 f,
878 "tool '{name}' has an empty description; add a rustdoc comment to its \
879 Arguments type or implement Tool::description"
880 ),
881 }
882 }
883}
884
885impl core::error::Error for RegisterError {}
886
887pub struct Tools {
901 tools: BTreeMap<Cow<'static, str>, Box<dyn ToolImpl>>,
902}
903
904impl Debug for Tools {
905 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
906 f.debug_struct("Tools")
907 .field("tools", &self.tools.keys().collect::<Vec<_>>())
908 .finish()
909 }
910}
911
912#[derive(Debug, Clone, PartialEq)]
916#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
917pub struct ToolDefinition {
918 name: Cow<'static, str>,
920 description: Cow<'static, str>,
922 arguments: Schema,
924}
925
926impl ToolDefinition {
927 #[must_use]
932 pub fn new<T: Tool>(tool: &T) -> Self {
933 Self {
934 name: tool.name(),
935 description: tool.description(),
936 arguments: arguments_schema::<T::Arguments>(),
937 }
938 }
939
940 pub fn from_parts(
951 name: Cow<'static, str>,
952 description: Cow<'static, str>,
953 schema: Value,
954 ) -> core::result::Result<Self, InvalidSchema> {
955 let arguments: Schema = schema
956 .try_into()
957 .map_err(|_| InvalidSchema { name: name.clone() })?;
958
959 Ok(Self {
960 name,
961 description,
962 arguments,
963 })
964 }
965
966 #[must_use]
968 pub fn name(&self) -> &str {
969 &self.name
970 }
971
972 #[must_use]
974 pub fn description(&self) -> &str {
975 &self.description
976 }
977
978 #[must_use]
982 pub fn arguments_openai_schema(&self) -> serde_json::Value {
983 let mut inner = self.arguments.clone().to_value();
984 clean_schema(&mut inner);
985
986 inner
987 }
988}
989
990#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
991struct ToolArgument<T> {
992 value: T,
993}
994
995fn clean_schema(value: &mut Value) {
996 let defs = extract_defs(value);
998
999 resolve_and_clean(value, &defs);
1001
1002 if let Value::Object(map) = value {
1004 map.remove("description");
1007
1008 if map.contains_key("properties") && !map.contains_key("type") {
1010 map.insert("type".to_string(), Value::String("object".to_string()));
1011 }
1012 }
1013}
1014
1015fn extract_defs(value: &Value) -> serde_json::Map<String, Value> {
1017 if let Value::Object(map) = value
1018 && let Some(Value::Object(defs)) = map.get("$defs").or_else(|| map.get("definitions"))
1019 {
1020 return defs.clone();
1021 }
1022 serde_json::Map::new()
1023}
1024
1025#[allow(clippy::too_many_lines)]
1027fn resolve_and_clean(value: &mut Value, defs: &serde_json::Map<String, Value>) {
1028 resolve_and_clean_inner(value, defs, false);
1029}
1030
1031#[allow(clippy::too_many_lines)]
1033fn resolve_and_clean_inner(
1034 value: &mut Value,
1035 defs: &serde_json::Map<String, Value>,
1036 inside_properties: bool,
1037) {
1038 match value {
1039 Value::Object(map) => {
1040 if let Some(Value::String(ref_path)) = map.remove("$ref")
1042 && let Some(Value::Object(resolved_map)) = resolve_ref(&ref_path, defs)
1043 {
1044 let existing_description = map.remove("description");
1047 for (k, v) in resolved_map {
1048 map.entry(k).or_insert(v);
1049 }
1050 if let Some(desc) = existing_description {
1052 map.insert("description".to_string(), desc);
1053 }
1054 }
1055
1056 if let Some(const_val) = map.remove("const") {
1058 map.insert("enum".to_string(), Value::Array(alloc::vec![const_val]));
1059 }
1060
1061 if let Some(Value::Array(variants)) =
1063 map.remove("oneOf").or_else(|| map.remove("anyOf"))
1064 {
1065 let is_simple_enum = variants.iter().all(|v| {
1067 if let Value::Object(vm) = v {
1068 (vm.contains_key("const") || vm.contains_key("enum"))
1069 && !vm.contains_key("properties")
1070 } else {
1071 false
1072 }
1073 });
1074
1075 if is_simple_enum {
1076 let mut enum_values: alloc::vec::Vec<Value> = alloc::vec::Vec::new();
1078 let mut variant_type: Option<String> = None;
1079
1080 for variant in &variants {
1081 if let Value::Object(vm) = variant {
1082 if let Some(const_val) = vm.get("const")
1083 && !enum_values.contains(const_val)
1084 {
1085 enum_values.push(const_val.clone());
1086 }
1087 if let Some(Value::Array(arr)) = vm.get("enum") {
1088 for val in arr {
1089 if !enum_values.contains(val) {
1090 enum_values.push(val.clone());
1091 }
1092 }
1093 }
1094 if variant_type.is_none()
1095 && let Some(Value::String(t)) = vm.get("type")
1096 {
1097 variant_type = Some(t.clone());
1098 }
1099 }
1100 }
1101
1102 if !enum_values.is_empty() {
1103 map.insert("enum".to_string(), Value::Array(enum_values));
1104 if let Some(t) = variant_type {
1105 map.insert("type".to_string(), Value::String(t));
1106 }
1107 }
1108 } else {
1109 let mut all_properties = serde_json::Map::new();
1111
1112 for variant in variants {
1113 if let Value::Object(variant_map) = variant
1114 && let Some(Value::Object(props)) = variant_map.get("properties")
1115 {
1116 for (key, val) in props {
1117 let new_values: Option<alloc::vec::Vec<Value>> =
1119 if let Value::Object(val_obj) = val {
1120 if let Some(Value::Array(arr)) = val_obj.get("enum") {
1121 Some(arr.clone())
1122 } else {
1123 val_obj
1124 .get("const")
1125 .map(|const_val| alloc::vec![const_val.clone()])
1126 }
1127 } else {
1128 None
1129 };
1130
1131 if all_properties.contains_key(key) {
1132 if let Some(values) = new_values
1134 && let Some(Value::Object(existing_obj)) =
1135 all_properties.get_mut(key)
1136 && let Some(Value::Array(existing_enum)) =
1137 existing_obj.get_mut("enum")
1138 {
1139 for e in values {
1140 if !existing_enum.contains(&e) {
1141 existing_enum.push(e);
1142 }
1143 }
1144 }
1145 } else {
1146 let mut val_clone = val.clone();
1148 if let Value::Object(obj) = &mut val_clone
1149 && let Some(const_val) = obj.remove("const")
1150 {
1151 obj.insert(
1152 "enum".to_string(),
1153 Value::Array(alloc::vec![const_val]),
1154 );
1155 }
1156 all_properties.insert(key.clone(), val_clone);
1157 }
1158 }
1159 }
1160 }
1161
1162 if !all_properties.is_empty() {
1164 map.insert("type".to_string(), Value::String("object".to_string()));
1165 map.insert("properties".to_string(), Value::Object(all_properties));
1166 }
1167 }
1168 }
1169
1170 if !inside_properties {
1173 let allowed = [
1174 "type",
1175 "description",
1176 "properties",
1177 "required",
1178 "items",
1179 "enum",
1180 "nullable",
1181 ];
1182 map.retain(|k, _| allowed.contains(&k.as_str()));
1183 }
1184
1185 if let Some(Value::Array(types)) = map.get("type") {
1187 let non_null: Vec<&Value> = types
1189 .iter()
1190 .filter(|t| !matches!(t, Value::String(s) if s == "null"))
1191 .collect();
1192 if non_null.len() == 1 {
1193 map.insert("type".to_string(), non_null[0].clone());
1194 }
1195 }
1196
1197 for (key, v) in map.iter_mut() {
1199 let child_inside_props = key == "properties";
1201 resolve_and_clean_inner(v, defs, child_inside_props);
1202 }
1203 }
1204 Value::Array(arr) => {
1205 for v in arr {
1206 resolve_and_clean_inner(v, defs, false);
1207 }
1208 }
1209 _ => {}
1210 }
1211}
1212
1213fn resolve_ref(ref_path: &str, defs: &serde_json::Map<String, Value>) -> Option<Value> {
1215 let name = ref_path
1217 .strip_prefix("#/$defs/")
1218 .or_else(|| ref_path.strip_prefix("#/definitions/"))?;
1219
1220 defs.get(name).cloned()
1221}
1222
1223impl Default for Tools {
1224 fn default() -> Self {
1225 Self::new()
1226 }
1227}
1228
1229impl Tools {
1230 #[must_use]
1232 pub const fn new() -> Self {
1233 Self {
1234 tools: BTreeMap::new(),
1235 }
1236 }
1237
1238 #[must_use]
1242 pub fn get<T>(&self) -> Option<&T>
1243 where
1244 T: Tool + 'static,
1245 {
1246 self.tools
1247 .values()
1248 .find_map(|tool| tool.as_any().downcast_ref::<T>())
1249 }
1250
1251 #[must_use]
1255 pub fn get_mut<T>(&mut self) -> Option<&mut T>
1256 where
1257 T: Tool + 'static,
1258 {
1259 self.tools
1260 .values_mut()
1261 .find_map(|tool| tool.as_any_mut().downcast_mut::<T>())
1262 }
1263
1264 #[must_use]
1266 pub fn definitions(&self) -> Vec<ToolDefinition> {
1267 self.tools
1268 .values()
1269 .map(|tool| tool.definition().clone())
1270 .collect()
1271 }
1272
1273 pub fn register<T: Tool + 'static>(
1284 &mut self,
1285 tool: T,
1286 ) -> core::result::Result<(), RegisterError> {
1287 self.insert(Box::new(RegisteredTool::new(tool)))
1288 }
1289
1290 pub fn register_dyn<F>(
1299 &mut self,
1300 definition: ToolDefinition,
1301 handler: F,
1302 ) -> core::result::Result<(), RegisterError>
1303 where
1304 F: Fn(&str) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send>>
1305 + Send
1306 + Sync
1307 + 'static,
1308 {
1309 self.insert(Box::new(DynToolImpl {
1310 definition,
1311 handler,
1312 }))
1313 }
1314
1315 fn insert(&mut self, tool: Box<dyn ToolImpl>) -> core::result::Result<(), RegisterError> {
1316 let name = tool.definition().name.clone();
1317 if self.tools.contains_key(&name) {
1318 return Err(RegisterError::DuplicateName(name));
1319 }
1320 if tool.definition().description().trim().is_empty() {
1321 return Err(RegisterError::EmptyDescription(name));
1322 }
1323 self.tools.insert(name, tool);
1324 Ok(())
1325 }
1326
1327 pub fn unregister(&mut self, name: &str) {
1329 self.tools.remove(name);
1330 }
1331
1332 pub async fn call(&self, name: &str, args: &str) -> Result<ToolResult> {
1339 if let Some(tool) = self.tools.get(name) {
1340 tool.call(args).await
1341 } else {
1342 Err(anyhow::Error::msg(format!("Tool '{name}' not found")))
1343 }
1344 }
1345}
1346
1347#[cfg(test)]
1348mod tests {
1349 use super::*;
1350 use alloc::{format, string::ToString, vec};
1351 use schemars::JsonSchema;
1352 use serde::{Deserialize, Serialize};
1353
1354 #[derive(JsonSchema, Deserialize, Debug, PartialEq)]
1356 struct CalculatorArgs {
1357 operation: String,
1358 a: f64,
1359 b: f64,
1360 }
1361
1362 struct Calculator;
1363
1364 impl Tool for Calculator {
1365 fn name(&self) -> Cow<'static, str> {
1366 "calculator".into()
1367 }
1368 type Arguments = CalculatorArgs;
1369 type Res = ToolResult;
1370
1371 fn call(&self, args: Self::Arguments) -> impl Future<Output = Result<Self::Res>> + Send {
1372 core::future::ready(match args.operation.as_str() {
1373 "add" => Ok(ToolResult::text((args.a + args.b).to_string())),
1374 "subtract" => Ok(ToolResult::text((args.a - args.b).to_string())),
1375 "multiply" => Ok(ToolResult::text((args.a * args.b).to_string())),
1376 "divide" => {
1377 if args.b == 0.0 {
1378 Err(anyhow::Error::msg("Division by zero"))
1379 } else {
1380 Ok(ToolResult::text((args.a / args.b).to_string()))
1381 }
1382 }
1383 _ => Err(anyhow::Error::msg(format!(
1384 "Unknown operation: {}",
1385 args.operation
1386 ))),
1387 })
1388 }
1389 }
1390
1391 #[derive(JsonSchema, Deserialize)]
1393 struct GreetArgs {
1394 name: String,
1395 }
1396
1397 struct Greeter;
1398
1399 impl Tool for Greeter {
1400 fn name(&self) -> Cow<'static, str> {
1401 "greeter".into()
1402 }
1403 type Arguments = GreetArgs;
1404 type Res = ToolResult;
1405
1406 fn call(&self, args: Self::Arguments) -> impl Future<Output = Result<Self::Res>> + Send {
1407 core::future::ready(Ok(ToolResult::text(format!("Hello, {}!", args.name))))
1408 }
1409 }
1410
1411 #[test]
1412 fn from_parts_accepts_object_and_boolean_schemas() {
1413 for schema in [
1415 serde_json::json!({"type": "object"}),
1416 serde_json::json!(true),
1417 ] {
1418 assert!(
1419 ToolDefinition::from_parts("t".into(), "does a thing".into(), schema.clone())
1420 .is_ok(),
1421 "{schema} should be accepted"
1422 );
1423 }
1424 }
1425
1426 #[test]
1427 fn from_parts_rejects_non_schema_values() {
1428 for schema in [
1431 serde_json::json!("a string"),
1432 serde_json::json!([1, 2, 3]),
1433 serde_json::json!(7),
1434 serde_json::json!(null),
1435 ] {
1436 let result = ToolDefinition::from_parts("weird".into(), "d".into(), schema.clone());
1437 let Err(err) = result else {
1438 panic!("{schema} should be rejected");
1439 };
1440 assert_eq!(err.name(), "weird");
1441 }
1442 }
1443
1444 #[test]
1445 fn json_utility() {
1446 let value = serde_json::json!({
1447 "name": "test",
1448 "value": 42
1449 });
1450
1451 let json_str = json(&value).expect("a JSON value always serializes");
1452 assert!(json_str.contains("\"name\": \"test\""));
1453 assert!(json_str.contains("\"value\": 42"));
1454 }
1455
1456 #[test]
1457 fn tool_definition_creation() {
1458 let calculator = Calculator;
1459 let definition = ToolDefinition::new(&calculator);
1460
1461 assert_eq!(definition.name, "calculator");
1462 assert_eq!(
1463 definition.description,
1464 "Performs basic mathematical operations."
1465 );
1466 }
1469
1470 #[test]
1471 fn tools_creation() {
1472 let tools = Tools::new();
1473 assert_eq!(tools.definitions().len(), 0);
1474 }
1475
1476 #[test]
1477 fn tools_default() {
1478 let tools = Tools::default();
1479 assert_eq!(tools.definitions().len(), 0);
1480 }
1481
1482 #[tokio::test]
1483 async fn tools_register_and_call() {
1484 let mut tools = Tools::new();
1485 tools.register(Calculator).expect("calculator registers");
1486
1487 let definitions = tools.definitions();
1488 assert_eq!(definitions.len(), 1);
1489 assert_eq!(definitions[0].name, "calculator");
1490
1491 let result = tools
1492 .call("calculator", r#"{"operation": "add", "a": 5, "b": 3}"#)
1493 .await;
1494 assert!(result.is_ok());
1495 assert_eq!(result.unwrap().as_text(), Some("8"));
1496 }
1497
1498 #[tokio::test]
1499 async fn calculator_operations() {
1500 let mut tools = Tools::new();
1501 tools.register(Calculator).expect("calculator registers");
1502
1503 let result = tools
1505 .call("calculator", r#"{"operation": "add", "a": 10, "b": 5}"#)
1506 .await;
1507 assert_eq!(result.unwrap().as_text(), Some("15"));
1508
1509 let result = tools
1511 .call(
1512 "calculator",
1513 r#"{"operation": "subtract", "a": 10, "b": 3}"#,
1514 )
1515 .await;
1516 assert_eq!(result.unwrap().as_text(), Some("7"));
1517
1518 let result = tools
1520 .call("calculator", r#"{"operation": "multiply", "a": 4, "b": 3}"#)
1521 .await;
1522 assert_eq!(result.unwrap().as_text(), Some("12"));
1523
1524 let result = tools
1526 .call("calculator", r#"{"operation": "divide", "a": 15, "b": 3}"#)
1527 .await;
1528 assert_eq!(result.unwrap().as_text(), Some("5"));
1529 }
1530
1531 #[tokio::test]
1532 async fn calculator_division_by_zero() {
1533 let mut tools = Tools::new();
1534 tools.register(Calculator).expect("calculator registers");
1535
1536 let result = tools
1537 .call("calculator", r#"{"operation": "divide", "a": 10, "b": 0}"#)
1538 .await;
1539 assert!(result.is_err());
1540 assert!(result.unwrap_err().to_string().contains("Division by zero"));
1541 }
1542
1543 #[tokio::test]
1544 async fn calculator_unknown_operation() {
1545 let mut tools = Tools::new();
1546 tools.register(Calculator).expect("calculator registers");
1547
1548 let result = tools
1549 .call("calculator", r#"{"operation": "modulo", "a": 10, "b": 3}"#)
1550 .await;
1551 assert!(result.is_err());
1552 assert!(
1553 result
1554 .unwrap_err()
1555 .to_string()
1556 .contains("Unknown operation")
1557 );
1558 }
1559
1560 #[tokio::test]
1561 async fn multiple_tools() {
1562 let mut tools = Tools::new();
1563 tools.register(Calculator).expect("calculator registers");
1564 tools.register(Greeter).expect("greeter registers");
1565
1566 let definitions = tools.definitions();
1567 assert_eq!(definitions.len(), 2);
1568
1569 let calc_def = definitions.iter().find(|d| d.name == "calculator").unwrap();
1571 let greet_def = definitions.iter().find(|d| d.name == "greeter").unwrap();
1572
1573 assert_eq!(
1574 calc_def.description,
1575 "Performs basic mathematical operations."
1576 );
1577 assert_eq!(greet_def.description, "Greets a person by name.");
1578
1579 let calc_result = tools
1581 .call("calculator", r#"{"operation": "add", "a": 2, "b": 3}"#)
1582 .await;
1583 assert_eq!(calc_result.unwrap().as_text(), Some("5"));
1584
1585 let greet_result = tools.call("greeter", r#"{"name": "Alice"}"#).await;
1586 assert_eq!(greet_result.unwrap().as_text(), Some("Hello, Alice!"));
1587 }
1588
1589 #[derive(Debug, Serialize)]
1590 struct TableRow {
1591 name: &'static str,
1592 count: u32,
1593 }
1594
1595 #[derive(Debug, Serialize)]
1596 struct NestedTableRow {
1597 user: TableRow,
1598 ok: bool,
1599 }
1600
1601 #[derive(Debug)]
1602 struct ToolFailure(&'static str);
1603
1604 impl core::fmt::Display for ToolFailure {
1605 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1606 f.write_str(self.0)
1607 }
1608 }
1609
1610 impl core::error::Error for ToolFailure {}
1611
1612 #[test]
1613 fn into_tool_result_string_is_plain_text() {
1614 assert_eq!(
1615 String::from("hello").into_tool_result().unwrap(),
1616 ToolResult::text("hello")
1617 );
1618 }
1619
1620 #[test]
1621 fn into_tool_result_str_is_plain_text() {
1622 assert_eq!(
1623 "hello".into_tool_result().unwrap(),
1624 ToolResult::text("hello")
1625 );
1626 }
1627
1628 #[test]
1629 fn into_tool_result_option_none_is_done() {
1630 let result = Option::<String>::None.into_tool_result().unwrap();
1631 assert_eq!(result, ToolResult::Done);
1632 }
1633
1634 #[test]
1635 fn into_tool_result_option_some_delegates() {
1636 let result = Some("hello").into_tool_result().unwrap();
1637 assert_eq!(result, ToolResult::text("hello"));
1638 }
1639
1640 #[test]
1641 fn into_tool_result_result_ok_string_is_text() {
1642 let result = core::result::Result::<String, ToolFailure>::Ok(String::from("hello"))
1643 .into_tool_result()
1644 .unwrap();
1645 assert_eq!(result, ToolResult::text("hello"));
1646 }
1647
1648 #[test]
1649 fn into_tool_result_result_ok_object_is_tsv() {
1650 let result = core::result::Result::<TableRow, ToolFailure>::Ok(TableRow {
1651 name: "alpha",
1652 count: 3,
1653 })
1654 .into_tool_result()
1655 .unwrap();
1656
1657 assert_eq!(result, ToolResult::tsv("count\tname\n3\talpha\n"));
1658 }
1659
1660 #[test]
1661 fn into_tool_result_result_ok_array_of_objects_is_tsv() {
1662 let result = core::result::Result::<Vec<TableRow>, ToolFailure>::Ok(vec![
1663 TableRow {
1664 name: "alpha",
1665 count: 3,
1666 },
1667 TableRow {
1668 name: "beta",
1669 count: 5,
1670 },
1671 ])
1672 .into_tool_result()
1673 .unwrap();
1674
1675 assert_eq!(result, ToolResult::tsv("count\tname\n3\talpha\n5\tbeta\n"));
1676 }
1677
1678 #[test]
1679 fn into_tool_result_result_ok_scalar_is_json() {
1680 let result = core::result::Result::<bool, ToolFailure>::Ok(true)
1681 .into_tool_result()
1682 .unwrap();
1683 assert_eq!(result, ToolResult::json_value(Value::Bool(true)));
1684 }
1685
1686 #[test]
1687 fn into_tool_result_result_err_is_typed_error() {
1688 let result = core::result::Result::<TableRow, ToolFailure>::Err(ToolFailure("boom"))
1689 .into_tool_result()
1690 .unwrap();
1691 assert_eq!(result, ToolResult::error("boom"));
1692 assert!(result.is_error());
1693 assert_eq!(result.error_message(), Some("boom"));
1694 }
1695
1696 #[test]
1697 fn json_value_to_tsv_flattens_nested_objects() {
1698 let value = serde_json::to_value(NestedTableRow {
1699 user: TableRow {
1700 name: "alpha",
1701 count: 3,
1702 },
1703 ok: true,
1704 })
1705 .unwrap();
1706
1707 assert_eq!(
1708 json_value_to_tsv(&value),
1709 Some("ok\tuser.count\tuser.name\ntrue\t3\talpha\n".to_string())
1710 );
1711 }
1712
1713 #[tokio::test]
1714 async fn tool_not_found() {
1715 let tools = Tools::new();
1716
1717 let result = tools.call("nonexistent", "{}").await;
1718 assert!(result.is_err());
1719 assert!(
1720 result
1721 .unwrap_err()
1722 .to_string()
1723 .contains("Tool 'nonexistent' not found")
1724 );
1725 }
1726
1727 #[tokio::test]
1728 async fn invalid_json() {
1729 let mut tools = Tools::new();
1730 tools.register(Calculator).expect("calculator registers");
1731
1732 let result = tools.call("calculator", "invalid json").await;
1733 assert!(result.is_err());
1734 }
1735
1736 #[test]
1737 fn tools_unregister() {
1738 let mut tools = Tools::new();
1739 tools.register(Calculator).expect("calculator registers");
1740 tools.register(Greeter).expect("greeter registers");
1741
1742 assert_eq!(tools.definitions().len(), 2);
1743
1744 tools.unregister("calculator");
1745 assert_eq!(tools.definitions().len(), 1);
1746
1747 let remaining = &tools.definitions()[0];
1748 assert_eq!(remaining.name, "greeter");
1749
1750 tools.unregister("greeter");
1751 assert_eq!(tools.definitions().len(), 0);
1752 }
1753
1754 #[test]
1755 fn tools_debug() {
1756 let mut tools = Tools::new();
1757 tools.register(Calculator).expect("calculator registers");
1758 tools.register(Greeter).expect("greeter registers");
1759
1760 let debug_str = format!("{tools:?}");
1761 assert!(debug_str.contains("Tools"));
1762 assert!(debug_str.contains("calculator"));
1763 assert!(debug_str.contains("greeter"));
1764 }
1765
1766 #[test]
1767 fn tool_definition_debug() {
1768 let calculator = Calculator;
1769 let definition = ToolDefinition::new(&calculator);
1770 let debug_str = format!("{definition:?}");
1771
1772 assert!(debug_str.contains("ToolDefinition"));
1773 assert!(debug_str.contains("calculator"));
1774 assert!(debug_str.contains("Performs basic mathematical operations"));
1775 }
1776
1777 #[test]
1778 fn tool_definition_clone() {
1779 let calculator = Calculator;
1780 let original = ToolDefinition::new(&calculator);
1781 let cloned = original.clone();
1782
1783 assert_eq!(original.name, cloned.name);
1784 assert_eq!(original.description, cloned.description);
1785 }
1786
1787 #[test]
1788 fn schema_preserves_enum() {
1789 #[derive(JsonSchema, Deserialize)]
1790 #[serde(rename_all = "snake_case")]
1791 enum Status {
1792 Pending,
1793 InProgress,
1794 Completed,
1795 }
1796
1797 #[allow(dead_code)]
1798 #[derive(JsonSchema, Deserialize)]
1799 struct Item {
1800 status: Status,
1801 }
1802
1803 #[allow(dead_code)]
1804 #[derive(JsonSchema, Deserialize)]
1805 struct Args {
1806 items: Vec<Item>,
1807 }
1808
1809 struct TestTool;
1810
1811 impl Tool for TestTool {
1812 fn name(&self) -> Cow<'static, str> {
1813 "test".into()
1814 }
1815 type Arguments = Args;
1816 type Res = ToolResult;
1817
1818 fn call(
1819 &self,
1820 _args: Self::Arguments,
1821 ) -> impl Future<Output = Result<Self::Res>> + Send {
1822 core::future::ready(Ok(ToolResult::text("ok")))
1823 }
1824 }
1825
1826 let tool = TestTool;
1827 let def = ToolDefinition::new(&tool);
1828 let schema = def.arguments_openai_schema();
1829
1830 let schema_obj = schema.as_object().expect("schema should be object");
1832 let properties = schema_obj
1833 .get("properties")
1834 .expect("should have properties")
1835 .as_object()
1836 .unwrap();
1837 let items = properties
1838 .get("items")
1839 .expect("should have items")
1840 .as_object()
1841 .unwrap();
1842 let item_props = items
1843 .get("items")
1844 .expect("items should have items schema")
1845 .as_object()
1846 .unwrap();
1847 let item_properties = item_props
1848 .get("properties")
1849 .expect("item should have properties")
1850 .as_object()
1851 .unwrap();
1852 let status = item_properties
1853 .get("status")
1854 .expect("should have status")
1855 .as_object()
1856 .unwrap();
1857
1858 assert!(
1860 status.contains_key("enum"),
1861 "Status should have enum field. Full schema: {}",
1862 serde_json::to_string_pretty(&schema).unwrap()
1863 );
1864 }
1865
1866 #[test]
1867 fn schema_ref_resolution() {
1868 let raw_schema = serde_json::json!({
1870 "type": "object",
1871 "properties": {
1872 "status": {
1873 "$ref": "#/$defs/Status"
1874 }
1875 },
1876 "$defs": {
1877 "Status": {
1878 "type": "string",
1879 "enum": ["pending", "in_progress", "completed"]
1880 }
1881 }
1882 });
1883
1884 let mut schema = raw_schema;
1885 clean_schema(&mut schema);
1886
1887 let props = schema.get("properties").unwrap().as_object().unwrap();
1888 let status = props.get("status").unwrap().as_object().unwrap();
1889
1890 assert!(
1891 status.contains_key("enum"),
1892 "Status should have enum after ref resolution. Got: {}",
1893 serde_json::to_string_pretty(&schema).unwrap()
1894 );
1895 }
1896
1897 #[test]
1898 fn schema_nested_ref_in_array() {
1899 let raw_schema = serde_json::json!({
1901 "type": "object",
1902 "properties": {
1903 "todos": {
1904 "type": "array",
1905 "items": {
1906 "$ref": "#/$defs/TodoItem"
1907 }
1908 }
1909 },
1910 "$defs": {
1911 "TodoItem": {
1912 "type": "object",
1913 "properties": {
1914 "content": { "type": "string" },
1915 "status": { "$ref": "#/$defs/TodoStatus" }
1916 },
1917 "required": ["content", "status"]
1918 },
1919 "TodoStatus": {
1920 "type": "string",
1921 "enum": ["pending", "in_progress", "completed"]
1922 }
1923 }
1924 });
1925
1926 let mut schema = raw_schema;
1927 clean_schema(&mut schema);
1928
1929 let props = schema.get("properties").unwrap().as_object().unwrap();
1931 let todos = props.get("todos").unwrap().as_object().unwrap();
1932 let items = todos.get("items").unwrap().as_object().unwrap();
1933 let item_props = items.get("properties").unwrap().as_object().unwrap();
1934 let status = item_props.get("status").unwrap().as_object().unwrap();
1935
1936 assert!(
1937 status.contains_key("enum"),
1938 "Nested status should have enum. Full schema: {}",
1939 serde_json::to_string_pretty(&schema).unwrap()
1940 );
1941 }
1942}