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