1#[cfg(feature = "derive")]
208pub use aither_derive::tool;
209use alloc::borrow::Cow;
210use serde_json::Value;
211
212use crate::Result;
213use alloc::format;
214use alloc::string::{String, ToString};
215use alloc::vec::Vec;
216use alloc::{boxed::Box, collections::BTreeMap};
217use core::any::Any;
218use core::fmt::{Debug, Display};
219use core::{future::Future, pin::Pin};
220pub use mime::Mime;
221use schemars::{JsonSchema, Schema, schema_for};
222use serde::de::DeserializeOwned;
223
224mod command;
225mod context;
226
227pub use command::{
228 CommandOutcome, CommandResult, CommandStream, Interruption, InterruptionCause, StoredFile,
229 render_interruptions,
230};
231pub use context::{Progress, ProgressError, ProgressSink, ToolContext};
232
233#[derive(Debug, Clone, PartialEq, Eq)]
252#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
253#[cfg_attr(feature = "serde", serde(tag = "kind", rename_all = "snake_case"))]
254pub enum ToolResult {
255 Done,
257
258 Text {
260 text: String,
262 },
263
264 Tsv {
266 text: String,
268 },
269
270 Binary {
272 mime: String,
274 content: Vec<u8>,
276 },
277
278 Error {
280 message: String,
282 },
283
284 Command(Box<CommandResult>),
290
291 Parts {
298 parts: Vec<ToolResultPart>,
300 },
301}
302
303#[derive(Debug, Clone, PartialEq, Eq)]
308#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
309#[cfg_attr(feature = "serde", serde(tag = "kind", rename_all = "snake_case"))]
310pub enum ToolResultPart {
311 Text {
313 text: String,
315 },
316
317 Tsv {
319 text: String,
321 },
322
323 Binary {
325 mime: String,
327 content: Vec<u8>,
329 },
330}
331
332impl ToolResult {
333 #[must_use]
335 pub fn text(s: impl Into<String>) -> Self {
336 Self::Text { text: s.into() }
337 }
338
339 #[must_use]
341 pub fn tsv(s: impl Into<String>) -> Self {
342 Self::Tsv { text: s.into() }
343 }
344
345 #[must_use]
347 pub fn image(data: Vec<u8>, media_type: &str) -> Self {
348 Self::Binary {
349 mime: parse_media_type_or_octet_stream(media_type),
350 content: data,
351 }
352 }
353
354 #[must_use]
356 pub fn binary(data: Vec<u8>) -> Self {
357 Self::Binary {
358 mime: mime::APPLICATION_OCTET_STREAM.essence_str().to_string(),
359 content: data,
360 }
361 }
362
363 #[must_use]
365 pub fn error(message: impl Into<String>) -> Self {
366 Self::Error {
367 message: message.into(),
368 }
369 }
370
371 #[must_use]
377 pub fn parts(parts: Vec<ToolResultPart>) -> Self {
378 match <[ToolResultPart; 1]>::try_from(parts) {
379 Ok([part]) => part.into_result(),
380 Err(parts) if parts.is_empty() => Self::Done,
381 Err(parts) => Self::Parts { parts },
382 }
383 }
384
385 #[must_use]
387 pub const fn is_done(&self) -> bool {
388 matches!(self, Self::Done)
389 }
390
391 #[must_use]
393 pub const fn is_error(&self) -> bool {
394 matches!(self, Self::Error { .. })
395 }
396
397 #[must_use]
399 pub fn as_text(&self) -> Option<&str> {
400 match self {
401 Self::Text { text } | Self::Tsv { text } => Some(text),
402 Self::Error { message } => Some(message),
403 Self::Done | Self::Binary { .. } | Self::Command(_) | Self::Parts { .. } => None,
404 }
405 }
406
407 #[must_use]
409 pub fn command(&self) -> Option<&CommandResult> {
410 match self {
411 Self::Command(command) => Some(command.as_ref()),
412 _ => None,
413 }
414 }
415
416 #[must_use]
418 pub fn error_message(&self) -> Option<&str> {
419 match self {
420 Self::Error { message } => Some(message),
421 Self::Done
422 | Self::Text { .. }
423 | Self::Tsv { .. }
424 | Self::Binary { .. }
425 | Self::Command(_)
426 | Self::Parts { .. } => None,
427 }
428 }
429
430 #[must_use]
432 pub fn render_for_model(&self) -> String {
433 match self {
434 Self::Done => String::new(),
435 Self::Text { text } | Self::Tsv { text } => text.clone(),
436 Self::Command(command) => command.render_for_model(),
437 Self::Binary { mime, content } => render_binary_placeholder(mime, content),
438 Self::Error { message } => message.clone(),
439 Self::Parts { parts } => join_part_renders(parts),
440 }
441 }
442
443 #[must_use]
448 pub fn mime(&self) -> Option<Mime> {
449 match self {
450 Self::Binary { mime, .. } => mime.parse().ok(),
451 Self::Parts { parts } => match parts.as_slice() {
452 [ToolResultPart::Binary { mime, .. }] => mime.parse().ok(),
453 _ => None,
454 },
455 Self::Done
456 | Self::Text { .. }
457 | Self::Tsv { .. }
458 | Self::Command(_)
459 | Self::Error { .. } => None,
460 }
461 }
462
463 #[must_use]
467 pub fn content(&self) -> Option<&[u8]> {
468 match self {
469 Self::Binary { content, .. } => Some(content),
470 Self::Parts { parts } => match parts.as_slice() {
471 [ToolResultPart::Binary { content, .. }] => Some(content),
472 _ => None,
473 },
474 Self::Done
475 | Self::Text { .. }
476 | Self::Tsv { .. }
477 | Self::Command(_)
478 | Self::Error { .. } => None,
479 }
480 }
481}
482
483impl ToolResultPart {
484 #[must_use]
486 pub fn text(s: impl Into<String>) -> Self {
487 Self::Text { text: s.into() }
488 }
489
490 #[must_use]
492 pub fn tsv(s: impl Into<String>) -> Self {
493 Self::Tsv { text: s.into() }
494 }
495
496 #[must_use]
498 pub fn image(data: Vec<u8>, media_type: &str) -> Self {
499 Self::Binary {
500 mime: parse_media_type_or_octet_stream(media_type),
501 content: data,
502 }
503 }
504
505 #[must_use]
507 pub fn binary(data: Vec<u8>) -> Self {
508 Self::Binary {
509 mime: mime::APPLICATION_OCTET_STREAM.essence_str().to_string(),
510 content: data,
511 }
512 }
513
514 #[must_use]
516 pub fn as_text(&self) -> Option<&str> {
517 match self {
518 Self::Text { text } | Self::Tsv { text } => Some(text),
519 Self::Binary { .. } => None,
520 }
521 }
522
523 #[must_use]
525 pub fn render_for_model(&self) -> String {
526 match self {
527 Self::Text { text } | Self::Tsv { text } => text.clone(),
528 Self::Binary { mime, content } => render_binary_placeholder(mime, content),
529 }
530 }
531
532 #[must_use]
534 pub fn mime(&self) -> Option<Mime> {
535 match self {
536 Self::Binary { mime, .. } => mime.parse().ok(),
537 Self::Text { .. } | Self::Tsv { .. } => None,
538 }
539 }
540
541 #[must_use]
543 pub fn content(&self) -> Option<&[u8]> {
544 match self {
545 Self::Binary { content, .. } => Some(content),
546 Self::Text { .. } | Self::Tsv { .. } => None,
547 }
548 }
549
550 #[must_use]
552 pub fn into_result(self) -> ToolResult {
553 match self {
554 Self::Text { text } | Self::Tsv { text } => ToolResult::Text { text },
555 Self::Binary { mime, content } => ToolResult::Binary { mime, content },
556 }
557 }
558}
559
560fn render_binary_placeholder(mime: &str, content: &[u8]) -> String {
561 let mut rendered = String::new();
562 rendered.push_str("[binary tool result: ");
563 rendered.push_str(mime);
564 rendered.push_str(", ");
565 rendered.push_str(content.len().to_string().as_str());
566 rendered.push_str(" bytes]");
567 rendered
568}
569
570fn join_part_renders(parts: &[ToolResultPart]) -> String {
571 let mut rendered = String::new();
572 for part in parts {
573 let text = part.render_for_model();
574 if text.is_empty() {
575 continue;
576 }
577 if !rendered.is_empty() {
578 rendered.push('\n');
579 }
580 rendered.push_str(&text);
581 }
582 rendered
583}
584
585pub trait IntoToolResult {
591 fn into_tool_result(self) -> Result<ToolResult>;
597}
598
599impl IntoToolResult for ToolResult {
600 fn into_tool_result(self) -> Result<ToolResult> {
601 Ok(self)
602 }
603}
604
605impl IntoToolResult for CommandResult {
606 fn into_tool_result(self) -> Result<ToolResult> {
607 Ok(ToolResult::Command(Box::new(self)))
608 }
609}
610
611impl IntoToolResult for () {
612 fn into_tool_result(self) -> Result<ToolResult> {
613 Ok(ToolResult::Done)
614 }
615}
616
617impl IntoToolResult for String {
618 fn into_tool_result(self) -> Result<ToolResult> {
619 Ok(ToolResult::text(self))
620 }
621}
622
623impl IntoToolResult for &str {
624 fn into_tool_result(self) -> Result<ToolResult> {
625 Ok(ToolResult::text(self))
626 }
627}
628
629impl IntoToolResult for Cow<'_, str> {
630 fn into_tool_result(self) -> Result<ToolResult> {
631 Ok(ToolResult::text(self.into_owned()))
632 }
633}
634
635impl<T> IntoToolResult for Option<T>
636where
637 T: IntoToolResult,
638{
639 fn into_tool_result(self) -> Result<ToolResult> {
640 self.map_or_else(|| Ok(ToolResult::Done), IntoToolResult::into_tool_result)
641 }
642}
643
644impl IntoToolResult for Vec<ToolResultPart> {
645 fn into_tool_result(self) -> Result<ToolResult> {
646 Ok(ToolResult::parts(self))
647 }
648}
649
650impl<T, E> IntoToolResult for core::result::Result<T, E>
651where
652 T: IntoToolResult,
653 E: Display,
654{
655 fn into_tool_result(self) -> Result<ToolResult> {
656 match self {
657 Ok(value) => value.into_tool_result(),
658 Err(error) => Ok(ToolResult::error(error.to_string())),
659 }
660 }
661}
662
663fn parse_media_type_or_octet_stream(media_type: &str) -> String {
664 media_type
665 .parse::<Mime>()
666 .unwrap_or(mime::APPLICATION_OCTET_STREAM)
667 .essence_str()
668 .to_string()
669}
670
671pub trait Tool: Send + Sync {
711 fn name(&self) -> Cow<'static, str>;
713
714 fn description(&self) -> Cow<'static, str> {
724 description_from_schema::<Self::Arguments>().unwrap_or_default()
725 }
726
727 type Arguments: Send + JsonSchema + DeserializeOwned;
730
731 type Res: IntoToolResult + Send;
733
734 fn call(
745 &self,
746 arguments: Self::Arguments,
747 cx: ToolContext,
748 ) -> impl Future<Output = Result<Self::Res>> + Send;
749}
750
751trait ToolImpl: Send + Sync + Any {
752 fn call(
753 &self,
754 args: &str,
755 cx: ToolContext,
756 ) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send + '_>>;
757
758 fn definition(&self) -> &ToolDefinition;
761
762 fn as_any(&self) -> &dyn Any;
767
768 fn as_any_mut(&mut self) -> &mut dyn Any;
770}
771
772struct DynToolImpl<F>
774where
775 F: Fn(&str, ToolContext) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send>>
776 + Send
777 + Sync,
778{
779 definition: ToolDefinition,
780 handler: F,
781}
782
783impl<F> ToolImpl for DynToolImpl<F>
784where
785 F: Fn(&str, ToolContext) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send>>
786 + Send
787 + Sync
788 + 'static,
789{
790 fn call(
791 &self,
792 args: &str,
793 cx: ToolContext,
794 ) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send + '_>> {
795 (self.handler)(args, cx)
796 }
797
798 fn definition(&self) -> &ToolDefinition {
799 &self.definition
800 }
801
802 fn as_any(&self) -> &dyn Any {
803 self
804 }
805
806 fn as_any_mut(&mut self) -> &mut dyn Any {
807 self
808 }
809}
810
811fn schema_is_object(value: &Value) -> bool {
814 matches!(value.get("type").and_then(Value::as_str), Some("object"))
815 || value.get("properties").is_some()
816 || value.get("oneOf").is_some()
817 || value.get("anyOf").is_some()
818 || value.get("$defs").is_some()
819}
820
821fn is_object<T: JsonSchema>() -> bool {
822 schema_is_object(&schema_for!(T).to_value())
823}
824
825fn arguments_schema<T: JsonSchema>() -> Schema {
828 if is_object::<T>() {
829 schema_for!(T)
830 } else {
831 schema_for!(ToolArgument<T>)
832 }
833}
834
835fn description_from_schema<T: JsonSchema>() -> Option<Cow<'static, str>> {
837 schema_for!(T)
838 .to_value()
839 .get("description")
840 .and_then(Value::as_str)
841 .filter(|text| !text.trim().is_empty())
842 .map(|text| Cow::Owned(text.to_string()))
843}
844
845struct RegisteredTool<T: Tool> {
851 tool: T,
852 definition: ToolDefinition,
853 args_are_object: bool,
854}
855
856impl<T: Tool> RegisteredTool<T> {
857 fn new(tool: T) -> Self {
858 let definition = ToolDefinition::new(&tool);
859 let args_are_object = is_object::<T::Arguments>();
860 Self {
861 tool,
862 definition,
863 args_are_object,
864 }
865 }
866}
867
868#[derive(askama::Template)]
870#[template(path = "invalid_arguments.txt", escape = "none")]
871struct InvalidArguments<'a> {
872 tool: &'a str,
873 error: &'a serde_json::Error,
874}
875
876impl<T: Tool + 'static> ToolImpl for RegisteredTool<T> {
877 fn call(
878 &self,
879 args: &str,
880 cx: ToolContext,
881 ) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send + '_>> {
882 let result = if self.args_are_object {
883 serde_json::from_str::<T::Arguments>(args)
884 } else {
885 serde_json::from_str::<ToolArgument<T::Arguments>>(args).map(|wrapper| wrapper.value)
886 };
887
888 let arguments = match result {
889 Ok(arguments) => arguments,
890 Err(error) => {
891 let message = InvalidArguments {
892 tool: self.definition.name(),
893 error: &error,
894 }
895 .to_string();
896 return Box::pin(async move { Err(anyhow::Error::msg(message)) });
897 }
898 };
899
900 Box::pin(async move {
901 Tool::call(&self.tool, arguments, cx)
902 .await?
903 .into_tool_result()
904 })
905 }
906
907 fn definition(&self) -> &ToolDefinition {
908 &self.definition
909 }
910
911 fn as_any(&self) -> &dyn Any {
912 &self.tool
913 }
914
915 fn as_any_mut(&mut self) -> &mut dyn Any {
916 &mut self.tool
917 }
918}
919
920#[derive(Debug, Clone, PartialEq, Eq)]
925pub struct InvalidSchema {
926 name: Cow<'static, str>,
928}
929
930impl InvalidSchema {
931 #[must_use]
933 pub fn name(&self) -> &str {
934 &self.name
935 }
936}
937
938impl Display for InvalidSchema {
939 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
940 write!(
941 f,
942 "tool '{}' has an argument schema that is neither an object nor a boolean",
943 self.name
944 )
945 }
946}
947
948impl core::error::Error for InvalidSchema {}
949
950#[derive(Debug, Clone, PartialEq, Eq)]
952pub enum RegisterError {
953 DuplicateName(Cow<'static, str>),
957
958 EmptyDescription(Cow<'static, str>),
963}
964
965impl Display for RegisterError {
966 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
967 match self {
968 Self::DuplicateName(name) => {
969 write!(f, "a tool named '{name}' is already registered")
970 }
971 Self::EmptyDescription(name) => write!(
972 f,
973 "tool '{name}' has an empty description; add a rustdoc comment to its \
974 Arguments type or implement Tool::description"
975 ),
976 }
977 }
978}
979
980impl core::error::Error for RegisterError {}
981
982pub struct Tools {
998 tools: BTreeMap<Cow<'static, str>, Box<dyn ToolImpl>>,
999}
1000
1001impl Debug for Tools {
1002 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1003 f.debug_struct("Tools")
1004 .field("tools", &self.tools.keys().collect::<Vec<_>>())
1005 .finish()
1006 }
1007}
1008
1009#[derive(Debug, Clone, PartialEq)]
1013#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1014pub struct ToolDefinition {
1015 name: Cow<'static, str>,
1017 description: Cow<'static, str>,
1019 arguments: Schema,
1021}
1022
1023impl ToolDefinition {
1024 #[must_use]
1029 pub fn new<T: Tool>(tool: &T) -> Self {
1030 Self {
1031 name: tool.name(),
1032 description: tool.description(),
1033 arguments: arguments_schema::<T::Arguments>(),
1034 }
1035 }
1036
1037 pub fn from_parts(
1048 name: Cow<'static, str>,
1049 description: Cow<'static, str>,
1050 schema: Value,
1051 ) -> core::result::Result<Self, InvalidSchema> {
1052 let arguments: Schema = schema
1053 .try_into()
1054 .map_err(|_| InvalidSchema { name: name.clone() })?;
1055
1056 Ok(Self {
1057 name,
1058 description,
1059 arguments,
1060 })
1061 }
1062
1063 #[must_use]
1065 pub fn name(&self) -> &str {
1066 &self.name
1067 }
1068
1069 #[must_use]
1071 pub fn description(&self) -> &str {
1072 &self.description
1073 }
1074
1075 #[must_use]
1079 pub fn arguments_openai_schema(&self) -> serde_json::Value {
1080 let mut inner = self.arguments.clone().to_value();
1081 clean_schema(&mut inner);
1082
1083 inner
1084 }
1085}
1086
1087#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
1088struct ToolArgument<T> {
1089 value: T,
1090}
1091
1092fn clean_schema(value: &mut Value) {
1093 let defs = extract_defs(value);
1095
1096 resolve_and_clean(value, &defs);
1098
1099 if let Value::Object(map) = value {
1101 map.remove("description");
1104
1105 if map.contains_key("properties") && !map.contains_key("type") {
1107 map.insert("type".to_string(), Value::String("object".to_string()));
1108 }
1109 }
1110}
1111
1112fn extract_defs(value: &Value) -> serde_json::Map<String, Value> {
1114 if let Value::Object(map) = value
1115 && let Some(Value::Object(defs)) = map.get("$defs").or_else(|| map.get("definitions"))
1116 {
1117 return defs.clone();
1118 }
1119 serde_json::Map::new()
1120}
1121
1122#[allow(clippy::too_many_lines)]
1124fn resolve_and_clean(value: &mut Value, defs: &serde_json::Map<String, Value>) {
1125 resolve_and_clean_inner(value, defs, false);
1126}
1127
1128#[allow(clippy::too_many_lines)]
1130fn resolve_and_clean_inner(
1131 value: &mut Value,
1132 defs: &serde_json::Map<String, Value>,
1133 inside_properties: bool,
1134) {
1135 match value {
1136 Value::Object(map) => {
1137 if let Some(Value::String(ref_path)) = map.remove("$ref")
1139 && let Some(Value::Object(resolved_map)) = resolve_ref(&ref_path, defs)
1140 {
1141 let existing_description = map.remove("description");
1144 for (k, v) in resolved_map {
1145 map.entry(k).or_insert(v);
1146 }
1147 if let Some(desc) = existing_description {
1149 map.insert("description".to_string(), desc);
1150 }
1151 }
1152
1153 if let Some(const_val) = map.remove("const") {
1155 map.insert("enum".to_string(), Value::Array(alloc::vec![const_val]));
1156 }
1157
1158 if let Some(Value::Array(variants)) =
1160 map.remove("oneOf").or_else(|| map.remove("anyOf"))
1161 {
1162 let is_simple_enum = variants.iter().all(|v| {
1164 if let Value::Object(vm) = v {
1165 (vm.contains_key("const") || vm.contains_key("enum"))
1166 && !vm.contains_key("properties")
1167 } else {
1168 false
1169 }
1170 });
1171
1172 if is_simple_enum {
1173 let mut enum_values: alloc::vec::Vec<Value> = alloc::vec::Vec::new();
1175 let mut variant_type: Option<String> = None;
1176
1177 for variant in &variants {
1178 if let Value::Object(vm) = variant {
1179 if let Some(const_val) = vm.get("const")
1180 && !enum_values.contains(const_val)
1181 {
1182 enum_values.push(const_val.clone());
1183 }
1184 if let Some(Value::Array(arr)) = vm.get("enum") {
1185 for val in arr {
1186 if !enum_values.contains(val) {
1187 enum_values.push(val.clone());
1188 }
1189 }
1190 }
1191 if variant_type.is_none()
1192 && let Some(Value::String(t)) = vm.get("type")
1193 {
1194 variant_type = Some(t.clone());
1195 }
1196 }
1197 }
1198
1199 if !enum_values.is_empty() {
1200 map.insert("enum".to_string(), Value::Array(enum_values));
1201 if let Some(t) = variant_type {
1202 map.insert("type".to_string(), Value::String(t));
1203 }
1204 }
1205 } else {
1206 let mut all_properties = serde_json::Map::new();
1208
1209 for variant in variants {
1210 if let Value::Object(variant_map) = variant
1211 && let Some(Value::Object(props)) = variant_map.get("properties")
1212 {
1213 for (key, val) in props {
1214 let new_values: Option<alloc::vec::Vec<Value>> =
1216 if let Value::Object(val_obj) = val {
1217 if let Some(Value::Array(arr)) = val_obj.get("enum") {
1218 Some(arr.clone())
1219 } else {
1220 val_obj
1221 .get("const")
1222 .map(|const_val| alloc::vec![const_val.clone()])
1223 }
1224 } else {
1225 None
1226 };
1227
1228 if all_properties.contains_key(key) {
1229 if let Some(values) = new_values
1231 && let Some(Value::Object(existing_obj)) =
1232 all_properties.get_mut(key)
1233 && let Some(Value::Array(existing_enum)) =
1234 existing_obj.get_mut("enum")
1235 {
1236 for e in values {
1237 if !existing_enum.contains(&e) {
1238 existing_enum.push(e);
1239 }
1240 }
1241 }
1242 } else {
1243 let mut val_clone = val.clone();
1245 if let Value::Object(obj) = &mut val_clone
1246 && let Some(const_val) = obj.remove("const")
1247 {
1248 obj.insert(
1249 "enum".to_string(),
1250 Value::Array(alloc::vec![const_val]),
1251 );
1252 }
1253 all_properties.insert(key.clone(), val_clone);
1254 }
1255 }
1256 }
1257 }
1258
1259 if !all_properties.is_empty() {
1261 map.insert("type".to_string(), Value::String("object".to_string()));
1262 map.insert("properties".to_string(), Value::Object(all_properties));
1263 }
1264 }
1265 }
1266
1267 if !inside_properties {
1270 let allowed = [
1271 "type",
1272 "description",
1273 "properties",
1274 "required",
1275 "items",
1276 "enum",
1277 "nullable",
1278 ];
1279 map.retain(|k, _| allowed.contains(&k.as_str()));
1280 }
1281
1282 if let Some(Value::Array(types)) = map.get("type") {
1284 let non_null: Vec<&Value> = types
1286 .iter()
1287 .filter(|t| !matches!(t, Value::String(s) if s == "null"))
1288 .collect();
1289 if non_null.len() == 1 {
1290 map.insert("type".to_string(), non_null[0].clone());
1291 }
1292 }
1293
1294 for (key, v) in map.iter_mut() {
1296 let child_inside_props = key == "properties";
1298 resolve_and_clean_inner(v, defs, child_inside_props);
1299 }
1300 }
1301 Value::Array(arr) => {
1302 for v in arr {
1303 resolve_and_clean_inner(v, defs, false);
1304 }
1305 }
1306 _ => {}
1307 }
1308}
1309
1310fn resolve_ref(ref_path: &str, defs: &serde_json::Map<String, Value>) -> Option<Value> {
1312 let name = ref_path
1314 .strip_prefix("#/$defs/")
1315 .or_else(|| ref_path.strip_prefix("#/definitions/"))?;
1316
1317 defs.get(name).cloned()
1318}
1319
1320impl Default for Tools {
1321 fn default() -> Self {
1322 Self::new()
1323 }
1324}
1325
1326impl Tools {
1327 #[must_use]
1329 pub const fn new() -> Self {
1330 Self {
1331 tools: BTreeMap::new(),
1332 }
1333 }
1334
1335 #[must_use]
1339 pub fn get<T>(&self) -> Option<&T>
1340 where
1341 T: Tool + 'static,
1342 {
1343 self.tools
1344 .values()
1345 .find_map(|tool| tool.as_any().downcast_ref::<T>())
1346 }
1347
1348 #[must_use]
1352 pub fn get_mut<T>(&mut self) -> Option<&mut T>
1353 where
1354 T: Tool + 'static,
1355 {
1356 self.tools
1357 .values_mut()
1358 .find_map(|tool| tool.as_any_mut().downcast_mut::<T>())
1359 }
1360
1361 #[must_use]
1363 pub fn definitions(&self) -> Vec<ToolDefinition> {
1364 self.tools
1365 .values()
1366 .map(|tool| tool.definition().clone())
1367 .collect()
1368 }
1369
1370 pub fn register<T: Tool + 'static>(
1381 &mut self,
1382 tool: T,
1383 ) -> core::result::Result<(), RegisterError> {
1384 self.insert(Box::new(RegisteredTool::new(tool)))
1385 }
1386
1387 pub fn register_dyn<F>(
1397 &mut self,
1398 definition: ToolDefinition,
1399 handler: F,
1400 ) -> core::result::Result<(), RegisterError>
1401 where
1402 F: Fn(&str, ToolContext) -> Pin<Box<dyn Future<Output = Result<ToolResult>> + Send>>
1403 + Send
1404 + Sync
1405 + 'static,
1406 {
1407 self.insert(Box::new(DynToolImpl {
1408 definition,
1409 handler,
1410 }))
1411 }
1412
1413 fn insert(&mut self, tool: Box<dyn ToolImpl>) -> core::result::Result<(), RegisterError> {
1414 let name = tool.definition().name.clone();
1415 if self.tools.contains_key(&name) {
1416 return Err(RegisterError::DuplicateName(name));
1417 }
1418 if tool.definition().description().trim().is_empty() {
1419 return Err(RegisterError::EmptyDescription(name));
1420 }
1421 self.tools.insert(name, tool);
1422 Ok(())
1423 }
1424
1425 pub fn unregister(&mut self, name: &str) {
1427 self.tools.remove(name);
1428 }
1429
1430 pub async fn call(&self, name: &str, args: &str, cx: ToolContext) -> Result<ToolResult> {
1438 if let Some(tool) = self.tools.get(name) {
1439 tool.call(args, cx).await
1440 } else {
1441 Err(anyhow::Error::msg(format!("Tool '{name}' not found")))
1442 }
1443 }
1444}
1445
1446#[cfg(test)]
1447mod tests {
1448 use super::*;
1449 use alloc::{format, string::ToString, vec};
1450 use schemars::JsonSchema;
1451 use serde::Deserialize;
1452
1453 #[derive(JsonSchema, Deserialize, Debug, PartialEq)]
1455 struct CalculatorArgs {
1456 operation: String,
1457 a: f64,
1458 b: f64,
1459 }
1460
1461 struct Calculator;
1462
1463 impl Tool for Calculator {
1464 fn name(&self) -> Cow<'static, str> {
1465 "calculator".into()
1466 }
1467 type Arguments = CalculatorArgs;
1468 type Res = ToolResult;
1469
1470 fn call(
1471 &self,
1472 args: Self::Arguments,
1473 _cx: ToolContext,
1474 ) -> impl Future<Output = Result<Self::Res>> + Send {
1475 core::future::ready(match args.operation.as_str() {
1476 "add" => Ok(ToolResult::text((args.a + args.b).to_string())),
1477 "subtract" => Ok(ToolResult::text((args.a - args.b).to_string())),
1478 "multiply" => Ok(ToolResult::text((args.a * args.b).to_string())),
1479 "divide" => {
1480 if args.b == 0.0 {
1481 Err(anyhow::Error::msg("Division by zero"))
1482 } else {
1483 Ok(ToolResult::text((args.a / args.b).to_string()))
1484 }
1485 }
1486 _ => Err(anyhow::Error::msg(format!(
1487 "Unknown operation: {}",
1488 args.operation
1489 ))),
1490 })
1491 }
1492 }
1493
1494 #[derive(JsonSchema, Deserialize)]
1496 struct GreetArgs {
1497 name: String,
1498 }
1499
1500 struct Greeter;
1501
1502 impl Tool for Greeter {
1503 fn name(&self) -> Cow<'static, str> {
1504 "greeter".into()
1505 }
1506 type Arguments = GreetArgs;
1507 type Res = ToolResult;
1508
1509 fn call(
1510 &self,
1511 args: Self::Arguments,
1512 _cx: ToolContext,
1513 ) -> impl Future<Output = Result<Self::Res>> + Send {
1514 core::future::ready(Ok(ToolResult::text(format!("Hello, {}!", args.name))))
1515 }
1516 }
1517
1518 #[test]
1519 fn from_parts_accepts_object_and_boolean_schemas() {
1520 for schema in [
1522 serde_json::json!({"type": "object"}),
1523 serde_json::json!(true),
1524 ] {
1525 assert!(
1526 ToolDefinition::from_parts("t".into(), "does a thing".into(), schema.clone())
1527 .is_ok(),
1528 "{schema} should be accepted"
1529 );
1530 }
1531 }
1532
1533 #[test]
1534 fn from_parts_rejects_non_schema_values() {
1535 for schema in [
1538 serde_json::json!("a string"),
1539 serde_json::json!([1, 2, 3]),
1540 serde_json::json!(7),
1541 serde_json::json!(null),
1542 ] {
1543 let result = ToolDefinition::from_parts("weird".into(), "d".into(), schema.clone());
1544 let Err(err) = result else {
1545 panic!("{schema} should be rejected");
1546 };
1547 assert_eq!(err.name(), "weird");
1548 }
1549 }
1550
1551 #[test]
1552 fn tool_definition_creation() {
1553 let calculator = Calculator;
1554 let definition = ToolDefinition::new(&calculator);
1555
1556 assert_eq!(definition.name, "calculator");
1557 assert_eq!(
1558 definition.description,
1559 "Performs basic mathematical operations."
1560 );
1561 }
1564
1565 #[test]
1566 fn tools_creation() {
1567 let tools = Tools::new();
1568 assert_eq!(tools.definitions().len(), 0);
1569 }
1570
1571 #[test]
1572 fn tools_default() {
1573 let tools = Tools::default();
1574 assert_eq!(tools.definitions().len(), 0);
1575 }
1576
1577 #[tokio::test]
1578 async fn tools_register_and_call() {
1579 let mut tools = Tools::new();
1580 tools.register(Calculator).expect("calculator registers");
1581
1582 let definitions = tools.definitions();
1583 assert_eq!(definitions.len(), 1);
1584 assert_eq!(definitions[0].name, "calculator");
1585
1586 let result = tools
1587 .call(
1588 "calculator",
1589 r#"{"operation": "add", "a": 5, "b": 3}"#,
1590 ToolContext::new(),
1591 )
1592 .await;
1593 assert!(result.is_ok());
1594 assert_eq!(result.unwrap().as_text(), Some("8"));
1595 }
1596
1597 #[tokio::test]
1598 async fn invalid_arguments_report_the_parse_error_without_schema() {
1599 let mut tools = Tools::new();
1600 tools.register(Calculator).expect("calculator registers");
1601
1602 let error = tools
1603 .call("calculator", r#"{"operation": "add"}"#, ToolContext::new())
1604 .await
1605 .unwrap_err()
1606 .to_string();
1607 assert_eq!(
1608 error,
1609 "Invalid arguments for tool 'calculator': missing field `a` at line 1 column 20"
1610 );
1611 }
1612
1613 #[test]
1614 fn command_result_renders_its_text() {
1615 let result = ToolResult::Command(Box::new(CommandResult {
1616 stdout: CommandStream::Inline {
1617 text: "hi\n".to_string(),
1618 },
1619 stderr: None,
1620 outcome: CommandOutcome::Exited { code: 3 },
1621 interruptions: Vec::new(),
1622 }));
1623 assert_eq!(result.render_for_model(), "hi\nExit code: 3.\n");
1624 assert_eq!(
1625 result.command().map(|command| &command.outcome),
1626 Some(&CommandOutcome::Exited { code: 3 })
1627 );
1628 }
1629
1630 #[cfg(feature = "serde")]
1631 #[test]
1632 fn command_result_round_trips_through_serde() {
1633 let file = || crate::llm::StoredFile {
1634 url: "outputs/x.txt".to_string(),
1635 path: "/sandbox/outputs/x.txt".to_string(),
1636 };
1637 let results = [
1638 CommandResult {
1639 stdout: CommandStream::Inline {
1640 text: "hi\n".to_string(),
1641 },
1642 stderr: Some(CommandStream::Inline {
1643 text: "oops".to_string(),
1644 }),
1645 outcome: CommandOutcome::Exited { code: 1 },
1646 interruptions: Vec::new(),
1647 },
1648 CommandResult {
1649 stdout: CommandStream::Stored {
1650 file: file(),
1651 preview: "head".to_string(),
1652 truncated: true,
1653 },
1654 stderr: None,
1655 outcome: CommandOutcome::Exited { code: 0 },
1656 interruptions: Vec::new(),
1657 },
1658 CommandResult {
1659 stdout: CommandStream::Image {
1660 media_type: "image/png".to_string(),
1661 file: file(),
1662 },
1663 stderr: None,
1664 outcome: CommandOutcome::Running {
1665 task_id: "t1".to_string(),
1666 output: file(),
1667 },
1668 interruptions: Vec::new(),
1669 },
1670 ];
1671 for command in results {
1672 let result = ToolResult::Command(Box::new(command));
1673 let json = serde_json::to_string(&result).expect("a command result serializes");
1674 let back: ToolResult = serde_json::from_str(&json).expect("it deserializes back");
1675 assert_eq!(back, result, "{json}");
1676 }
1677 }
1678
1679 #[tokio::test]
1680 async fn calculator_operations() {
1681 let mut tools = Tools::new();
1682 tools.register(Calculator).expect("calculator registers");
1683
1684 let result = tools
1686 .call(
1687 "calculator",
1688 r#"{"operation": "add", "a": 10, "b": 5}"#,
1689 ToolContext::new(),
1690 )
1691 .await;
1692 assert_eq!(result.unwrap().as_text(), Some("15"));
1693
1694 let result = tools
1696 .call(
1697 "calculator",
1698 r#"{"operation": "subtract", "a": 10, "b": 3}"#,
1699 ToolContext::new(),
1700 )
1701 .await;
1702 assert_eq!(result.unwrap().as_text(), Some("7"));
1703
1704 let result = tools
1706 .call(
1707 "calculator",
1708 r#"{"operation": "multiply", "a": 4, "b": 3}"#,
1709 ToolContext::new(),
1710 )
1711 .await;
1712 assert_eq!(result.unwrap().as_text(), Some("12"));
1713
1714 let result = tools
1716 .call(
1717 "calculator",
1718 r#"{"operation": "divide", "a": 15, "b": 3}"#,
1719 ToolContext::new(),
1720 )
1721 .await;
1722 assert_eq!(result.unwrap().as_text(), Some("5"));
1723 }
1724
1725 #[tokio::test]
1726 async fn calculator_division_by_zero() {
1727 let mut tools = Tools::new();
1728 tools.register(Calculator).expect("calculator registers");
1729
1730 let result = tools
1731 .call(
1732 "calculator",
1733 r#"{"operation": "divide", "a": 10, "b": 0}"#,
1734 ToolContext::new(),
1735 )
1736 .await;
1737 assert!(result.is_err());
1738 assert!(result.unwrap_err().to_string().contains("Division by zero"));
1739 }
1740
1741 #[tokio::test]
1742 async fn calculator_unknown_operation() {
1743 let mut tools = Tools::new();
1744 tools.register(Calculator).expect("calculator registers");
1745
1746 let result = tools
1747 .call(
1748 "calculator",
1749 r#"{"operation": "modulo", "a": 10, "b": 3}"#,
1750 ToolContext::new(),
1751 )
1752 .await;
1753 assert!(result.is_err());
1754 assert!(
1755 result
1756 .unwrap_err()
1757 .to_string()
1758 .contains("Unknown operation")
1759 );
1760 }
1761
1762 #[tokio::test]
1763 async fn multiple_tools() {
1764 let mut tools = Tools::new();
1765 tools.register(Calculator).expect("calculator registers");
1766 tools.register(Greeter).expect("greeter registers");
1767
1768 let definitions = tools.definitions();
1769 assert_eq!(definitions.len(), 2);
1770
1771 let calc_def = definitions.iter().find(|d| d.name == "calculator").unwrap();
1773 let greet_def = definitions.iter().find(|d| d.name == "greeter").unwrap();
1774
1775 assert_eq!(
1776 calc_def.description,
1777 "Performs basic mathematical operations."
1778 );
1779 assert_eq!(greet_def.description, "Greets a person by name.");
1780
1781 let calc_result = tools
1783 .call(
1784 "calculator",
1785 r#"{"operation": "add", "a": 2, "b": 3}"#,
1786 ToolContext::new(),
1787 )
1788 .await;
1789 assert_eq!(calc_result.unwrap().as_text(), Some("5"));
1790
1791 let greet_result = tools
1792 .call("greeter", r#"{"name": "Alice"}"#, ToolContext::new())
1793 .await;
1794 assert_eq!(greet_result.unwrap().as_text(), Some("Hello, Alice!"));
1795 }
1796
1797 #[derive(Debug)]
1798 struct ToolFailure(&'static str);
1799
1800 impl core::fmt::Display for ToolFailure {
1801 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1802 f.write_str(self.0)
1803 }
1804 }
1805
1806 impl core::error::Error for ToolFailure {}
1807
1808 #[test]
1809 fn into_tool_result_string_is_plain_text() {
1810 assert_eq!(
1811 String::from("hello").into_tool_result().unwrap(),
1812 ToolResult::text("hello")
1813 );
1814 }
1815
1816 #[test]
1817 fn into_tool_result_str_is_plain_text() {
1818 assert_eq!(
1819 "hello".into_tool_result().unwrap(),
1820 ToolResult::text("hello")
1821 );
1822 }
1823
1824 #[test]
1825 fn into_tool_result_option_none_is_done() {
1826 let result = Option::<String>::None.into_tool_result().unwrap();
1827 assert_eq!(result, ToolResult::Done);
1828 }
1829
1830 #[test]
1831 fn into_tool_result_option_some_delegates() {
1832 let result = Some("hello").into_tool_result().unwrap();
1833 assert_eq!(result, ToolResult::text("hello"));
1834 }
1835
1836 #[test]
1837 fn into_tool_result_result_ok_string_is_text() {
1838 let result = core::result::Result::<String, ToolFailure>::Ok(String::from("hello"))
1839 .into_tool_result()
1840 .unwrap();
1841 assert_eq!(result, ToolResult::text("hello"));
1842 }
1843
1844 #[test]
1845 fn into_tool_result_result_err_is_typed_error() {
1846 let result = core::result::Result::<String, ToolFailure>::Err(ToolFailure("boom"))
1847 .into_tool_result()
1848 .unwrap();
1849 assert_eq!(result, ToolResult::error("boom"));
1850 assert!(result.is_error());
1851 assert_eq!(result.error_message(), Some("boom"));
1852 }
1853
1854 #[tokio::test]
1855 async fn tool_not_found() {
1856 let tools = Tools::new();
1857
1858 let result = tools.call("nonexistent", "{}", ToolContext::new()).await;
1859 assert!(result.is_err());
1860 assert!(
1861 result
1862 .unwrap_err()
1863 .to_string()
1864 .contains("Tool 'nonexistent' not found")
1865 );
1866 }
1867
1868 #[tokio::test]
1869 async fn invalid_json() {
1870 let mut tools = Tools::new();
1871 tools.register(Calculator).expect("calculator registers");
1872
1873 let result = tools
1874 .call("calculator", "invalid json", ToolContext::new())
1875 .await;
1876 assert!(result.is_err());
1877 }
1878
1879 #[test]
1880 fn tools_unregister() {
1881 let mut tools = Tools::new();
1882 tools.register(Calculator).expect("calculator registers");
1883 tools.register(Greeter).expect("greeter registers");
1884
1885 assert_eq!(tools.definitions().len(), 2);
1886
1887 tools.unregister("calculator");
1888 assert_eq!(tools.definitions().len(), 1);
1889
1890 let remaining = &tools.definitions()[0];
1891 assert_eq!(remaining.name, "greeter");
1892
1893 tools.unregister("greeter");
1894 assert_eq!(tools.definitions().len(), 0);
1895 }
1896
1897 #[test]
1898 fn tools_debug() {
1899 let mut tools = Tools::new();
1900 tools.register(Calculator).expect("calculator registers");
1901 tools.register(Greeter).expect("greeter registers");
1902
1903 let debug_str = format!("{tools:?}");
1904 assert!(debug_str.contains("Tools"));
1905 assert!(debug_str.contains("calculator"));
1906 assert!(debug_str.contains("greeter"));
1907 }
1908
1909 #[test]
1910 fn tool_definition_debug() {
1911 let calculator = Calculator;
1912 let definition = ToolDefinition::new(&calculator);
1913 let debug_str = format!("{definition:?}");
1914
1915 assert!(debug_str.contains("ToolDefinition"));
1916 assert!(debug_str.contains("calculator"));
1917 assert!(debug_str.contains("Performs basic mathematical operations"));
1918 }
1919
1920 #[test]
1921 fn tool_definition_clone() {
1922 let calculator = Calculator;
1923 let original = ToolDefinition::new(&calculator);
1924 let cloned = original.clone();
1925
1926 assert_eq!(original.name, cloned.name);
1927 assert_eq!(original.description, cloned.description);
1928 }
1929
1930 #[test]
1931 fn schema_preserves_enum() {
1932 #[derive(JsonSchema, Deserialize)]
1933 #[serde(rename_all = "snake_case")]
1934 enum Status {
1935 Pending,
1936 InProgress,
1937 Completed,
1938 }
1939
1940 #[allow(dead_code)]
1941 #[derive(JsonSchema, Deserialize)]
1942 struct Item {
1943 status: Status,
1944 }
1945
1946 #[allow(dead_code)]
1947 #[derive(JsonSchema, Deserialize)]
1948 struct Args {
1949 items: Vec<Item>,
1950 }
1951
1952 struct TestTool;
1953
1954 impl Tool for TestTool {
1955 fn name(&self) -> Cow<'static, str> {
1956 "test".into()
1957 }
1958 type Arguments = Args;
1959 type Res = ToolResult;
1960
1961 fn call(
1962 &self,
1963 _args: Self::Arguments,
1964 _cx: ToolContext,
1965 ) -> impl Future<Output = Result<Self::Res>> + Send {
1966 core::future::ready(Ok(ToolResult::text("ok")))
1967 }
1968 }
1969
1970 let tool = TestTool;
1971 let def = ToolDefinition::new(&tool);
1972 let schema = def.arguments_openai_schema();
1973
1974 let schema_obj = schema.as_object().expect("schema should be object");
1976 let properties = schema_obj
1977 .get("properties")
1978 .expect("should have properties")
1979 .as_object()
1980 .unwrap();
1981 let items = properties
1982 .get("items")
1983 .expect("should have items")
1984 .as_object()
1985 .unwrap();
1986 let item_props = items
1987 .get("items")
1988 .expect("items should have items schema")
1989 .as_object()
1990 .unwrap();
1991 let item_properties = item_props
1992 .get("properties")
1993 .expect("item should have properties")
1994 .as_object()
1995 .unwrap();
1996 let status = item_properties
1997 .get("status")
1998 .expect("should have status")
1999 .as_object()
2000 .unwrap();
2001
2002 assert!(
2004 status.contains_key("enum"),
2005 "Status should have enum field. Full schema: {}",
2006 serde_json::to_string_pretty(&schema).unwrap()
2007 );
2008 }
2009
2010 #[test]
2011 fn schema_ref_resolution() {
2012 let raw_schema = serde_json::json!({
2014 "type": "object",
2015 "properties": {
2016 "status": {
2017 "$ref": "#/$defs/Status"
2018 }
2019 },
2020 "$defs": {
2021 "Status": {
2022 "type": "string",
2023 "enum": ["pending", "in_progress", "completed"]
2024 }
2025 }
2026 });
2027
2028 let mut schema = raw_schema;
2029 clean_schema(&mut schema);
2030
2031 let props = schema.get("properties").unwrap().as_object().unwrap();
2032 let status = props.get("status").unwrap().as_object().unwrap();
2033
2034 assert!(
2035 status.contains_key("enum"),
2036 "Status should have enum after ref resolution. Got: {}",
2037 serde_json::to_string_pretty(&schema).unwrap()
2038 );
2039 }
2040
2041 #[test]
2042 fn schema_nested_ref_in_array() {
2043 let raw_schema = serde_json::json!({
2045 "type": "object",
2046 "properties": {
2047 "entries": {
2048 "type": "array",
2049 "items": {
2050 "$ref": "#/$defs/BatchItem"
2051 }
2052 }
2053 },
2054 "$defs": {
2055 "BatchItem": {
2056 "type": "object",
2057 "properties": {
2058 "content": { "type": "string" },
2059 "status": { "$ref": "#/$defs/BatchStatus" }
2060 },
2061 "required": ["content", "status"]
2062 },
2063 "BatchStatus": {
2064 "type": "string",
2065 "enum": ["pending", "in_progress", "completed"]
2066 }
2067 }
2068 });
2069
2070 let mut schema = raw_schema;
2071 clean_schema(&mut schema);
2072
2073 let props = schema.get("properties").unwrap().as_object().unwrap();
2075 let todos = props.get("entries").unwrap().as_object().unwrap();
2076 let items = todos.get("items").unwrap().as_object().unwrap();
2077 let item_props = items.get("properties").unwrap().as_object().unwrap();
2078 let status = item_props.get("status").unwrap().as_object().unwrap();
2079
2080 assert!(
2081 status.contains_key("enum"),
2082 "Nested status should have enum. Full schema: {}",
2083 serde_json::to_string_pretty(&schema).unwrap()
2084 );
2085 }
2086
2087 #[test]
2088 fn parts_collapses_degenerate_inputs() {
2089 assert_eq!(ToolResult::parts(vec![]), ToolResult::Done);
2090 assert_eq!(
2091 ToolResult::parts(vec![ToolResultPart::text("only")]),
2092 ToolResult::text("only")
2093 );
2094 let multi = ToolResult::parts(vec![
2095 ToolResultPart::text("a"),
2096 ToolResultPart::image(vec![1, 2], "image/png"),
2097 ]);
2098 let ToolResult::Parts { parts } = &multi else {
2099 panic!("two parts must stay a Parts result");
2100 };
2101 assert_eq!(parts.len(), 2);
2102 }
2103
2104 #[test]
2105 fn parts_render_joins_text_and_marks_binary() {
2106 let result = ToolResult::parts(vec![
2107 ToolResultPart::text("header"),
2108 ToolResultPart::tsv("n\n1\n"),
2109 ToolResultPart::image(vec![0x89], "image/png"),
2110 ]);
2111 assert_eq!(
2112 result.render_for_model(),
2113 "header\nn\n1\n\n[binary tool result: image/png, 1 bytes]"
2114 );
2115 assert!(result.mime().is_none() && result.content().is_none());
2116 }
2117
2118 #[test]
2119 fn parts_with_one_binary_expose_mime_and_content() {
2120 let result = ToolResult::Parts {
2121 parts: vec![ToolResultPart::image(vec![9, 9], "image/png")],
2122 };
2123 assert_eq!(result.mime().unwrap().essence_str(), "image/png");
2124 assert_eq!(result.content().unwrap(), &[9, 9]);
2125 }
2126
2127 #[cfg(feature = "serde")]
2128 #[test]
2129 fn parts_serde_roundtrip_uses_kind_tag() {
2130 let result = ToolResult::parts(vec![
2131 ToolResultPart::text("note"),
2132 ToolResultPart::tsv("k\ntrue\n"),
2133 ]);
2134 let value = serde_json::to_value(&result).unwrap();
2135 assert_eq!(value["kind"], "parts");
2136 assert_eq!(value["parts"][0]["kind"], "text");
2137 let back: ToolResult = serde_json::from_value(value).unwrap();
2138 assert_eq!(back, result);
2139 }
2140}