1use schemars::JsonSchema;
2use serde::de::DeserializeOwned;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "lowercase")]
9pub enum ImageFormat {
10 Png,
11 Jpeg,
12 Gif,
13 Webp,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum DocumentFormat {
20 Pdf,
21 Csv,
22 Doc,
23 Docx,
24 Html,
25 Md,
26 Txt,
27 Xls,
28 Xlsx,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
37pub enum ToolResult {
38 Text(String),
40
41 Json(Value),
43
44 Image {
46 format: ImageFormat,
47 data: Vec<u8>,
49 },
50
51 Document {
53 format: DocumentFormat,
54 data: Vec<u8>,
56 name: Option<String>,
58 },
59}
60
61impl ToolResult {
62 pub fn json<T: Serialize>(value: T) -> Result<Self, serde_json::Error> {
64 Ok(Self::Json(serde_json::to_value(value)?))
65 }
66
67 pub fn text(s: impl Into<String>) -> Self {
69 Self::Text(s.into())
70 }
71
72 pub fn image(format: ImageFormat, data: Vec<u8>) -> Self {
74 Self::Image { format, data }
75 }
76
77 pub fn document(format: DocumentFormat, data: Vec<u8>) -> Self {
79 Self::Document {
80 format,
81 data,
82 name: None,
83 }
84 }
85
86 pub fn document_with_name(
88 format: DocumentFormat,
89 data: Vec<u8>,
90 name: impl Into<String>,
91 ) -> Self {
92 Self::Document {
93 format,
94 data,
95 name: Some(name.into()),
96 }
97 }
98
99 pub fn as_text(&self) -> String {
101 match self {
102 ToolResult::Text(s) => s.clone(),
103 ToolResult::Json(v) => v.to_string(),
104 ToolResult::Image { format, data } => {
105 format!("[Image: {:?}, {} bytes]", format, data.len())
106 }
107 ToolResult::Document { format, data, name } => {
108 let name_str = name.as_deref().unwrap_or("unnamed");
109 format!(
110 "[Document: {:?}, {}, {} bytes]",
111 format,
112 name_str,
113 data.len()
114 )
115 }
116 }
117 }
118
119 pub fn as_str(&self) -> Option<&str> {
121 match self {
122 ToolResult::Text(s) => Some(s),
123 _ => None,
124 }
125 }
126}
127
128impl From<String> for ToolResult {
130 fn from(s: String) -> Self {
131 Self::Text(s)
132 }
133}
134
135impl From<&str> for ToolResult {
136 fn from(s: &str) -> Self {
137 Self::Text(s.to_string())
138 }
139}
140
141#[derive(Debug, thiserror::Error)]
143pub enum ToolError {
144 #[error("IO error: {0}")]
145 Io(#[from] std::io::Error),
146
147 #[error("Serialization error: {0}")]
148 Serialization(#[from] serde_json::Error),
149
150 #[error("Path validation failed: {0}")]
151 PathValidation(String),
152
153 #[error("{0}")]
154 Custom(String),
155}
156
157impl From<String> for ToolError {
158 fn from(s: String) -> Self {
159 Self::Custom(s)
160 }
161}
162
163impl From<&str> for ToolError {
164 fn from(s: &str) -> Self {
165 Self::Custom(s.to_string())
166 }
167}
168
169pub trait Tool: Send + Sync {
247 type Input: DeserializeOwned + JsonSchema;
249
250 fn name(&self) -> &str;
252
253 fn description(&self) -> &str;
255
256 fn execute(
258 &self,
259 input: Self::Input,
260 ) -> impl std::future::Future<Output = Result<ToolResult, ToolError>> + Send;
261
262 fn input_schema(&self) -> Value {
267 let schema = schemars::schema_for!(Self::Input);
268 serde_json::to_value(schema).expect("Failed to serialize schema")
269 }
270
271 fn format_input_plain(&self, params: &Value) -> String {
279 format_params_plain(self.name(), params)
280 }
281
282 fn format_input_ansi(&self, params: &Value) -> String {
286 format_params_ansi(self.name(), params)
287 }
288
289 fn format_input_markdown(&self, params: &Value) -> String {
293 format_params_markdown(self.name(), params)
294 }
295
296 fn format_output_plain(&self, result: &ToolResult) -> String {
300 format_result_plain(result)
301 }
302
303 fn format_output_ansi(&self, result: &ToolResult) -> String {
307 format_result_ansi(result)
308 }
309
310 fn format_output_markdown(&self, result: &ToolResult) -> String {
314 format_result_markdown(result)
315 }
316}
317
318pub trait DynTool: Send + Sync {
322 fn name(&self) -> &str;
323 fn description(&self) -> &str;
324 fn input_schema(&self) -> Value;
325 fn execute_raw(
326 &self,
327 input: Value,
328 ) -> std::pin::Pin<
329 Box<dyn std::future::Future<Output = Result<ToolResult, ToolError>> + Send + '_>,
330 >;
331
332 fn format_input_plain(&self, params: &Value) -> String;
334 fn format_input_ansi(&self, params: &Value) -> String;
335 fn format_input_markdown(&self, params: &Value) -> String;
336 fn format_output_plain(&self, result: &ToolResult) -> String;
337 fn format_output_ansi(&self, result: &ToolResult) -> String;
338 fn format_output_markdown(&self, result: &ToolResult) -> String;
339}
340
341pub fn box_tool<T: Tool + 'static>(tool: T) -> Box<dyn DynTool> {
343 Box::new(ToolWrapper(tool))
344}
345
346#[macro_export]
371macro_rules! box_tools {
372 ($($tool:expr),* $(,)?) => {
373 vec![$($crate::tool::box_tool($tool)),*]
374 };
375}
376
377struct ToolWrapper<T>(T);
379
380impl<T: Tool + 'static> DynTool for ToolWrapper<T> {
381 fn name(&self) -> &str {
382 self.0.name()
383 }
384
385 fn description(&self) -> &str {
386 self.0.description()
387 }
388
389 fn input_schema(&self) -> Value {
390 self.0.input_schema()
391 }
392
393 fn execute_raw(
394 &self,
395 input: Value,
396 ) -> std::pin::Pin<
397 Box<dyn std::future::Future<Output = Result<ToolResult, ToolError>> + Send + '_>,
398 > {
399 Box::pin(async move {
400 let typed_input: T::Input = serde_json::from_value(input)
401 .map_err(|e| ToolError::Custom(format!("Failed to deserialize input: {}", e)))?;
402
403 self.0.execute(typed_input).await
404 })
405 }
406
407 fn format_input_plain(&self, params: &Value) -> String {
408 self.0.format_input_plain(params)
409 }
410
411 fn format_input_ansi(&self, params: &Value) -> String {
412 self.0.format_input_ansi(params)
413 }
414
415 fn format_input_markdown(&self, params: &Value) -> String {
416 self.0.format_input_markdown(params)
417 }
418
419 fn format_output_plain(&self, result: &ToolResult) -> String {
420 self.0.format_output_plain(result)
421 }
422
423 fn format_output_ansi(&self, result: &ToolResult) -> String {
424 self.0.format_output_ansi(result)
425 }
426
427 fn format_output_markdown(&self, result: &ToolResult) -> String {
428 self.0.format_output_markdown(result)
429 }
430}
431
432const MAX_PARAMS: usize = 10;
437const MAX_VALUE_LEN: usize = 80;
438const MAX_OUTPUT_LINES: usize = 12;
439
440fn format_value_preview(value: &Value) -> String {
442 match value {
443 Value::String(s) => {
444 if s.len() > MAX_VALUE_LEN {
445 format!("\"{}…\"", &s[..MAX_VALUE_LEN])
446 } else {
447 format!("\"{}\"", s)
448 }
449 }
450 Value::Array(arr) => format!("[{} items]", arr.len()),
451 Value::Object(obj) => format!("{{{} keys}}", obj.len()),
452 Value::Null => "null".to_string(),
453 Value::Bool(b) => b.to_string(),
454 Value::Number(n) => n.to_string(),
455 }
456}
457
458pub fn format_params_plain(tool_name: &str, params: &Value) -> String {
460 let mut output = tool_name.to_string();
461
462 if let Some(obj) = params.as_object() {
463 for (key, value) in obj.iter().take(MAX_PARAMS) {
464 output.push_str(&format!("\n {}: {}", key, format_value_preview(value)));
465 }
466 if obj.len() > MAX_PARAMS {
467 output.push_str(&format!("\n … +{} more", obj.len() - MAX_PARAMS));
468 }
469 }
470
471 output
472}
473
474pub fn format_params_ansi(tool_name: &str, params: &Value) -> String {
476 let mut output = format!("\x1b[1m{}\x1b[0m", tool_name);
478
479 if let Some(obj) = params.as_object() {
480 for (key, value) in obj.iter().take(MAX_PARAMS) {
481 output.push_str(&format!(
483 "\n \x1b[2m{}:\x1b[0m {}",
484 key,
485 format_value_preview(value)
486 ));
487 }
488 if obj.len() > MAX_PARAMS {
489 output.push_str(&format!(
490 "\n \x1b[2m… +{} more\x1b[0m",
491 obj.len() - MAX_PARAMS
492 ));
493 }
494 }
495
496 output
497}
498
499pub fn format_params_markdown(tool_name: &str, params: &Value) -> String {
501 let mut output = format!("**{}**", tool_name);
502
503 if let Some(obj) = params.as_object() {
504 for (key, value) in obj.iter().take(MAX_PARAMS) {
505 output.push_str(&format!("\n- `{}`: {}", key, format_value_preview(value)));
506 }
507 if obj.len() > MAX_PARAMS {
508 output.push_str(&format!("\n- *… +{} more*", obj.len() - MAX_PARAMS));
509 }
510 }
511
512 output
513}
514
515fn result_to_text(result: &ToolResult) -> String {
517 match result {
518 ToolResult::Text(s) => s.clone(),
519 ToolResult::Json(v) => format_json_truncated(v),
520 ToolResult::Image { format, data } => {
521 format!("[Image: {:?}, {} bytes]", format, data.len())
522 }
523 ToolResult::Document { format, data, name } => {
524 let name_str = name.as_deref().unwrap_or("unnamed");
525 format!(
526 "[Document: {:?}, {}, {} bytes]",
527 format,
528 name_str,
529 data.len()
530 )
531 }
532 }
533}
534
535fn format_json_truncated(value: &Value) -> String {
537 format_json_truncated_inner(value, 0)
538}
539
540fn format_json_truncated_inner(value: &Value, depth: usize) -> String {
541 let indent = " ".repeat(depth);
542 let child_indent = " ".repeat(depth + 1);
543
544 match value {
545 Value::String(s) => {
546 if s.len() > MAX_VALUE_LEN {
547 format!("\"{}…\"", &s[..MAX_VALUE_LEN])
548 } else {
549 format!("\"{}\"", s)
550 }
551 }
552 Value::Array(arr) => {
553 if arr.is_empty() {
554 "[]".to_string()
555 } else if arr.len() > MAX_PARAMS {
556 format!("[{} items]", arr.len())
557 } else {
558 let items: Vec<String> = arr
559 .iter()
560 .take(MAX_PARAMS)
561 .map(|v| {
562 format!(
563 "{}{}",
564 child_indent,
565 format_json_truncated_inner(v, depth + 1)
566 )
567 })
568 .collect();
569 format!("[\n{}\n{}]", items.join(",\n"), indent)
570 }
571 }
572 Value::Object(obj) => {
573 if obj.is_empty() {
574 "{}".to_string()
575 } else {
576 let mut items: Vec<String> = obj
577 .iter()
578 .take(MAX_PARAMS)
579 .map(|(k, v)| {
580 format!(
581 "{}\"{}\": {}",
582 child_indent,
583 k,
584 format_json_truncated_inner(v, depth + 1)
585 )
586 })
587 .collect();
588 if obj.len() > MAX_PARAMS {
589 items.push(format!(
590 "{}… +{} more",
591 child_indent,
592 obj.len() - MAX_PARAMS
593 ));
594 }
595 format!("{{\n{}\n{}}}", items.join(",\n"), indent)
596 }
597 }
598 Value::Null => "null".to_string(),
599 Value::Bool(b) => b.to_string(),
600 Value::Number(n) => n.to_string(),
601 }
602}
603
604fn truncate_lines(text: &str, max_lines: usize) -> (String, usize) {
606 let lines: Vec<&str> = text.lines().collect();
607 if lines.len() <= max_lines {
608 (text.to_string(), 0)
609 } else {
610 let truncated = lines[..max_lines].join("\n");
611 (truncated, lines.len() - max_lines)
612 }
613}
614
615pub fn format_result_plain(result: &ToolResult) -> String {
617 let text = result_to_text(result);
618 let (truncated, remaining) = truncate_lines(&text, MAX_OUTPUT_LINES);
619
620 if remaining > 0 {
621 format!("{}\n… +{} more lines", truncated, remaining)
622 } else {
623 truncated
624 }
625}
626
627pub fn format_result_ansi(result: &ToolResult) -> String {
629 let text = result_to_text(result);
630 let (truncated, remaining) = truncate_lines(&text, MAX_OUTPUT_LINES);
631
632 if remaining > 0 {
633 format!(
634 "\x1b[32m✓\x1b[0m\n{}\n\x1b[2m… +{} more lines\x1b[0m",
635 truncated, remaining
636 )
637 } else {
638 format!("\x1b[32m✓\x1b[0m\n{}", truncated)
639 }
640}
641
642pub fn format_result_markdown(result: &ToolResult) -> String {
644 let text = result_to_text(result);
645 let (truncated, remaining) = truncate_lines(&text, MAX_OUTPUT_LINES);
646
647 let mut output = String::from("```\n");
648 output.push_str(&truncated);
649 output.push_str("\n```");
650
651 if remaining > 0 {
652 output.push_str(&format!("\n*… +{} more lines*", remaining));
653 }
654
655 output
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
665 fn test_format_value_preview_string_short() {
666 let value = serde_json::json!("hello");
667 assert_eq!(format_value_preview(&value), "\"hello\"");
668 }
669
670 #[test]
671 fn test_format_value_preview_string_long() {
672 let long_string = "x".repeat(100);
673 let value = serde_json::json!(long_string);
674 let preview = format_value_preview(&value);
675
676 assert!(preview.len() < 100);
678 assert!(preview.ends_with("…\""));
679 }
680
681 #[test]
682 fn test_format_value_preview_array() {
683 let value = serde_json::json!([1, 2, 3, 4, 5]);
684 assert_eq!(format_value_preview(&value), "[5 items]");
685 }
686
687 #[test]
688 fn test_format_value_preview_object() {
689 let value = serde_json::json!({"a": 1, "b": 2});
690 assert_eq!(format_value_preview(&value), "{2 keys}");
691 }
692
693 #[test]
694 fn test_format_value_preview_null() {
695 let value = serde_json::json!(null);
696 assert_eq!(format_value_preview(&value), "null");
697 }
698
699 #[test]
700 fn test_format_value_preview_bool() {
701 assert_eq!(format_value_preview(&serde_json::json!(true)), "true");
702 assert_eq!(format_value_preview(&serde_json::json!(false)), "false");
703 }
704
705 #[test]
706 fn test_format_value_preview_number() {
707 assert_eq!(format_value_preview(&serde_json::json!(42)), "42");
708 assert_eq!(format_value_preview(&serde_json::json!(1.5)), "1.5");
709 }
710
711 #[test]
714 fn test_truncate_lines_no_truncation() {
715 let text = "line1\nline2\nline3";
716 let (result, remaining) = truncate_lines(text, 5);
717 assert_eq!(result, text);
718 assert_eq!(remaining, 0);
719 }
720
721 #[test]
722 fn test_truncate_lines_with_truncation() {
723 let text = "line1\nline2\nline3\nline4\nline5";
724 let (result, remaining) = truncate_lines(text, 3);
725 assert_eq!(result, "line1\nline2\nline3");
726 assert_eq!(remaining, 2);
727 }
728
729 #[test]
730 fn test_truncate_lines_exact_limit() {
731 let text = "line1\nline2\nline3";
732 let (result, remaining) = truncate_lines(text, 3);
733 assert_eq!(result, text);
734 assert_eq!(remaining, 0);
735 }
736
737 #[test]
740 fn test_format_params_plain_simple() {
741 let params = serde_json::json!({"path": "/tmp/test.txt"});
742 let output = format_params_plain("read_file", ¶ms);
743
744 assert!(output.starts_with("read_file"));
745 assert!(output.contains("path:"));
746 assert!(output.contains("/tmp/test.txt"));
747 }
748
749 #[test]
750 fn test_format_params_plain_many_params() {
751 let mut obj = serde_json::Map::new();
753 for i in 0..15 {
754 obj.insert(format!("key{}", i), serde_json::json!(i));
755 }
756 let params = serde_json::Value::Object(obj);
757 let output = format_params_plain("test_tool", ¶ms);
758
759 assert!(output.contains("… +"));
760 assert!(output.contains("more"));
761 }
762
763 #[test]
764 fn test_format_params_ansi_has_codes() {
765 let params = serde_json::json!({"name": "test"});
766 let output = format_params_ansi("my_tool", ¶ms);
767
768 assert!(output.contains("\x1b["));
770 assert!(output.contains("my_tool"));
772 }
773
774 #[test]
775 fn test_format_params_markdown_format() {
776 let params = serde_json::json!({"file": "test.rs"});
777 let output = format_params_markdown("edit", ¶ms);
778
779 assert!(output.starts_with("**edit**"));
781 assert!(output.contains("- `file`:"));
783 }
784
785 #[test]
788 fn test_format_result_plain_short() {
789 let result = ToolResult::Text("Success!".to_string());
790 let output = format_result_plain(&result);
791 assert_eq!(output, "Success!");
792 }
793
794 #[test]
795 fn test_format_result_plain_truncated() {
796 let long_text = (0..20)
798 .map(|i| format!("line {}", i))
799 .collect::<Vec<_>>()
800 .join("\n");
801 let result = ToolResult::Text(long_text);
802 let output = format_result_plain(&result);
803
804 assert!(output.contains("… +"));
805 assert!(output.contains("more lines"));
806 }
807
808 #[test]
809 fn test_format_result_ansi_success_marker() {
810 let result = ToolResult::Text("Done".to_string());
811 let output = format_result_ansi(&result);
812
813 assert!(output.contains("\x1b[32m✓\x1b[0m"));
815 }
816
817 #[test]
818 fn test_format_result_markdown_code_block() {
819 let result = ToolResult::Text("code here".to_string());
820 let output = format_result_markdown(&result);
821
822 assert!(output.starts_with("```\n"));
823 assert!(output.contains("code here"));
824 assert!(output.contains("\n```"));
825 }
826
827 #[test]
828 fn test_format_result_json() {
829 let result = ToolResult::Json(serde_json::json!({"status": "ok"}));
830 let output = format_result_plain(&result);
831
832 assert!(output.contains("status"));
834 assert!(output.contains("ok"));
835 }
836
837 #[test]
838 fn test_format_result_image() {
839 let result = ToolResult::Image {
840 format: ImageFormat::Png,
841 data: vec![0u8; 1000],
842 };
843 let output = format_result_plain(&result);
844
845 assert!(output.contains("Image"));
846 assert!(output.contains("Png"));
847 assert!(output.contains("1000 bytes"));
848 }
849
850 #[test]
851 fn test_format_result_document() {
852 let result = ToolResult::Document {
853 format: DocumentFormat::Pdf,
854 data: vec![0u8; 500],
855 name: Some("report.pdf".to_string()),
856 };
857 let output = format_result_plain(&result);
858
859 assert!(output.contains("Document"));
860 assert!(output.contains("Pdf"));
861 assert!(output.contains("report.pdf"));
862 assert!(output.contains("500 bytes"));
863 }
864
865 #[test]
866 fn test_format_result_document_unnamed() {
867 let result = ToolResult::Document {
868 format: DocumentFormat::Txt,
869 data: vec![0u8; 100],
870 name: None,
871 };
872 let output = format_result_plain(&result);
873
874 assert!(output.contains("unnamed"));
875 }
876
877 #[test]
880 fn test_tool_result_image_factory() {
881 let result = ToolResult::image(ImageFormat::Jpeg, vec![1, 2, 3]);
882
883 if let ToolResult::Image { format, data } = result {
884 assert_eq!(format, ImageFormat::Jpeg);
885 assert_eq!(data, vec![1, 2, 3]);
886 } else {
887 panic!("Expected Image variant");
888 }
889 }
890
891 #[test]
892 fn test_tool_result_document_factory() {
893 let result = ToolResult::document(DocumentFormat::Csv, vec![4, 5, 6]);
894
895 if let ToolResult::Document { format, data, name } = result {
896 assert_eq!(format, DocumentFormat::Csv);
897 assert_eq!(data, vec![4, 5, 6]);
898 assert!(name.is_none());
899 } else {
900 panic!("Expected Document variant");
901 }
902 }
903
904 #[test]
905 fn test_tool_result_document_with_name_factory() {
906 let result = ToolResult::document_with_name(DocumentFormat::Html, vec![7, 8], "page.html");
907
908 if let ToolResult::Document { format, data, name } = result {
909 assert_eq!(format, DocumentFormat::Html);
910 assert_eq!(data, vec![7, 8]);
911 assert_eq!(name, Some("page.html".to_string()));
912 } else {
913 panic!("Expected Document variant");
914 }
915 }
916
917 #[test]
920 fn test_tool_result_as_text_image() {
921 let result = ToolResult::Image {
922 format: ImageFormat::Gif,
923 data: vec![0u8; 2000],
924 };
925 let text = result.as_text();
926
927 assert!(text.contains("Image"));
928 assert!(text.contains("Gif"));
929 assert!(text.contains("2000 bytes"));
930 }
931
932 #[test]
933 fn test_tool_result_as_text_document() {
934 let result = ToolResult::Document {
935 format: DocumentFormat::Xlsx,
936 data: vec![0u8; 3000],
937 name: Some("data.xlsx".to_string()),
938 };
939 let text = result.as_text();
940
941 assert!(text.contains("Document"));
942 assert!(text.contains("Xlsx"));
943 assert!(text.contains("data.xlsx"));
944 assert!(text.contains("3000 bytes"));
945 }
946
947 #[test]
948 fn test_tool_result_as_str_binary_types() {
949 let image = ToolResult::Image {
950 format: ImageFormat::Webp,
951 data: vec![],
952 };
953 assert!(image.as_str().is_none());
954
955 let doc = ToolResult::Document {
956 format: DocumentFormat::Doc,
957 data: vec![],
958 name: None,
959 };
960 assert!(doc.as_str().is_none());
961 }
962}