1use std::{
4 borrow::Cow,
5 collections::{HashMap, hash_map::DefaultHasher},
6 hash::{Hash, Hasher},
7 sync::Arc,
8};
9
10use async_trait::async_trait;
11use jsonschema;
12use once_cell::sync::Lazy;
13use parking_lot::RwLock;
14use serde::{Deserialize, Serialize};
15use tracing::warn;
16
17use crate::toolkits::error::{ToolResult, error_context};
18
19#[async_trait]
21pub trait DynTool: Send + Sync {
22 fn metadata(&self) -> &ToolMetadata;
24
25 async fn execute_json(&self, input: serde_json::Value) -> ToolResult<serde_json::Value>;
27
28 fn input_schema(&self) -> serde_json::Value;
30
31 fn name(&self) -> &str {
33 &self.metadata().name
34 }
35
36 fn clone_box(&self) -> Box<dyn DynTool>;
38}
39
40static SCHEMA_CACHE: Lazy<RwLock<HashMap<u64, Arc<jsonschema::Validator>>>> =
42 Lazy::new(|| RwLock::new(HashMap::new()));
43
44const SCHEMA_CACHE_MAX_SIZE: usize = 256;
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ToolMetadata {
50 pub name: Cow<'static, str>,
52
53 pub description: Cow<'static, str>,
55
56 pub version: Cow<'static, str>,
58
59 pub author: Option<Cow<'static, str>>,
61
62 pub tags: Vec<Cow<'static, str>>,
64
65 pub enabled: bool,
67
68 pub metadata: HashMap<Cow<'static, str>, serde_json::Value>,
70}
71
72impl ToolMetadata {
73 pub fn new(name: impl Into<String>, description: impl Into<String>) -> ToolResult<Self> {
75 let name = name.into();
76 let description = description.into();
77
78 if name.trim().is_empty() {
80 return Err(error_context().invalid_parameters("Tool name cannot be empty"));
81 }
82 if name.contains(|c: char| !c.is_alphanumeric() && c != '_') {
83 return Err(error_context()
84 .invalid_parameters("Tool name must be alphanumeric with underscores only"));
85 }
86
87 Ok(Self {
88 name: Cow::Owned(name),
89 description: Cow::Owned(description),
90 version: Cow::Borrowed("1.0.0"),
91 author: None,
92 tags: Vec::new(),
93 enabled: true,
94 metadata: HashMap::new(),
95 })
96 }
97
98 pub fn version(mut self, version: impl Into<Cow<'static, str>>) -> Self {
100 self.version = version.into();
101 self
102 }
103
104 pub fn author(mut self, author: impl Into<Cow<'static, str>>) -> Self {
105 self.author = Some(author.into());
106 self
107 }
108
109 pub fn tags<T: Into<Cow<'static, str>>>(mut self, tags: impl IntoIterator<Item = T>) -> Self {
110 self.tags = tags.into_iter().map(Into::into).collect();
111 self
112 }
113
114 pub fn enabled(mut self, enabled: bool) -> Self {
115 self.enabled = enabled;
116 self
117 }
118
119 pub fn with_metadata(
120 mut self,
121 key: impl Into<Cow<'static, str>>,
122 value: serde_json::Value,
123 ) -> Self {
124 self.metadata.insert(key.into(), value);
125 self
126 }
127}
128
129pub mod conversions {
131 use crate::toolkits::error::{ToolResult, error_context};
132
133 pub fn to_json<T: serde::Serialize>(value: T) -> ToolResult<serde_json::Value> {
135 serde_json::to_value(value).map_err(|e| error_context().serialization_error(e))
136 }
137
138 pub fn from_json_string(value: serde_json::Value) -> ToolResult<String> {
140 match value {
141 serde_json::Value::String(s) => Ok(s),
142 _ => Err(error_context().invalid_parameters("Expected string value")),
143 }
144 }
145
146 pub fn from_json_i32(value: serde_json::Value) -> ToolResult<i32> {
148 match value {
149 serde_json::Value::Number(n) => n
150 .as_i64()
151 .and_then(|i| i.try_into().ok())
152 .ok_or_else(|| error_context().invalid_parameters("Expected i32 value")),
153 _ => Err(error_context().invalid_parameters("Expected number value")),
154 }
155 }
156
157 pub fn from_json_f64(value: serde_json::Value) -> ToolResult<f64> {
159 match value {
160 serde_json::Value::Number(n) => n
161 .as_f64()
162 .ok_or_else(|| error_context().invalid_parameters("Expected f64 value")),
163 _ => Err(error_context().invalid_parameters("Expected number value")),
164 }
165 }
166
167 pub fn from_json_bool(value: serde_json::Value) -> ToolResult<bool> {
169 match value {
170 serde_json::Value::Bool(b) => Ok(b),
171 _ => Err(error_context().invalid_parameters("Expected boolean value")),
172 }
173 }
174}
175
176pub(crate) type ToolHandler = std::sync::Arc<
182 dyn Fn(
183 serde_json::Value,
184 ) -> std::pin::Pin<
185 Box<
186 dyn std::future::Future<
187 Output = crate::toolkits::error::ToolResult<serde_json::Value>,
188 > + Send,
189 >,
190 > + Send
191 + Sync,
192>;
193
194pub struct FunctionTool {
197 metadata: ToolMetadata,
198 input_schema: serde_json::Value,
199 compiled_schema: Arc<jsonschema::Validator>,
200 handler: ToolHandler,
201}
202
203impl Clone for FunctionTool {
204 fn clone(&self) -> Self {
205 Self {
206 metadata: self.metadata.clone(),
207 input_schema: self.input_schema.clone(),
208 compiled_schema: Arc::clone(&self.compiled_schema),
209 handler: self.handler.clone(),
210 }
211 }
212}
213
214impl FunctionTool {
215 pub fn builder(name: impl Into<String>, description: impl Into<String>) -> FunctionToolBuilder {
216 FunctionToolBuilder::new(name, description)
217 }
218 pub fn from_schema<F, Fut>(
221 name: impl Into<String>,
222 description: impl Into<String>,
223 schema: serde_json::Value,
224 f: F,
225 ) -> crate::toolkits::error::ToolResult<FunctionTool>
226 where
227 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
228 Fut: std::future::Future<Output = crate::toolkits::error::ToolResult<serde_json::Value>>
229 + Send
230 + 'static,
231 {
232 Self::builder(name, description)
233 .schema(schema)
234 .handler(f)
235 .build()
236 }
237 pub fn from_function_spec<F, Fut>(
242 spec: serde_json::Value,
243 f: F,
244 ) -> crate::toolkits::error::ToolResult<FunctionTool>
245 where
246 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
247 Fut: std::future::Future<Output = crate::toolkits::error::ToolResult<serde_json::Value>>
248 + Send
249 + 'static,
250 {
251 let (name, description, parameters) = parse_function_spec_details(&spec)?;
252 let mut builder = Self::builder(name, description);
253 if let Some(p) = parameters {
254 builder = builder.schema(p);
255 }
256 builder.handler(f).build()
257 }
258
259 pub fn from_function_spec_file<F, Fut>(
261 path: impl AsRef<std::path::Path>,
262 f: F,
263 ) -> crate::toolkits::error::ToolResult<FunctionTool>
264 where
265 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
266 Fut: std::future::Future<Output = crate::toolkits::error::ToolResult<serde_json::Value>>
267 + Send
268 + 'static,
269 {
270 let content = std::fs::read_to_string(path).map_err(|e| {
271 error_context().invalid_parameters(format!("Failed to read spec file: {}", e))
272 })?;
273 let spec: serde_json::Value = serde_json::from_str(&content)
274 .map_err(|e| error_context().invalid_parameters(format!("Invalid JSON: {}", e)))?;
275 Self::from_function_spec(spec, f)
276 }
277}
278
279fn compile_schema_cached(schema: &serde_json::Value) -> ToolResult<Arc<jsonschema::Validator>> {
281 let mut hasher = DefaultHasher::new();
282 schema.to_string().hash(&mut hasher);
283 let hash = hasher.finish();
284
285 {
287 let cache = SCHEMA_CACHE.read();
288 if let Some(cached) = cache.get(&hash) {
289 return Ok(Arc::clone(cached));
290 }
291 }
292
293 let validator = jsonschema::validator_for(schema).map_err(|e| {
295 error_context().schema_validation(format!("Failed to compile schema: {}", e))
296 })?;
297
298 let validator = Arc::new(validator);
299
300 {
301 let mut cache = SCHEMA_CACHE.write();
302 if cache.len() >= SCHEMA_CACHE_MAX_SIZE {
304 let remove_count = (SCHEMA_CACHE_MAX_SIZE / 10).max(1);
306 let keys: Vec<u64> = cache.keys().take(remove_count).copied().collect();
307 for k in keys {
308 cache.remove(&k);
309 }
310 }
311 cache.insert(hash, Arc::clone(&validator));
312 }
313
314 Ok(validator)
315}
316
317pub(crate) fn parse_function_spec_details(
320 spec: &serde_json::Value,
321) -> crate::toolkits::error::ToolResult<(String, String, Option<serde_json::Value>)> {
322 use serde_json::Value;
323 let obj = match spec {
324 Value::Object(map) => map,
325 _ => return Err(error_context().invalid_parameters("Function spec must be a JSON object")),
326 };
327 let (name, desc, params) = if obj.get("type").and_then(|v| v.as_str()) == Some("function") {
329 let f = obj
330 .get("function")
331 .and_then(|v| v.as_object())
332 .ok_or_else(|| error_context().invalid_parameters("Missing 'function' object"))?;
333 let name = f
334 .get("name")
335 .and_then(|v| v.as_str())
336 .ok_or_else(|| error_context().invalid_parameters("Missing function.name"))?
337 .to_string();
338 let desc = f
339 .get("description")
340 .and_then(|v| v.as_str())
341 .unwrap_or("")
342 .to_string();
343 let params = f.get("parameters").cloned();
344 (name, desc, params)
345 } else {
346 let name = obj
348 .get("name")
349 .and_then(|v| v.as_str())
350 .ok_or_else(|| error_context().invalid_parameters("Missing name"))?
351 .to_string();
352 let desc = obj
353 .get("description")
354 .and_then(|v| v.as_str())
355 .unwrap_or("")
356 .to_string();
357 let params = obj.get("parameters").cloned();
358 (name, desc, params)
359 };
360 Ok((name, desc, params))
361}
362
363pub struct FunctionToolBuilder {
365 metadata: ToolMetadata,
366 input_schema: Option<serde_json::Value>,
367 staged_properties: Option<serde_json::Map<String, serde_json::Value>>,
370 staged_required: Vec<String>,
371 handler: Option<ToolHandler>,
372}
373
374impl FunctionToolBuilder {
375 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
376 let name_str = name.into();
377 let desc_str = description.into();
378 let metadata = ToolMetadata::new(&name_str, &desc_str).unwrap_or_else(|e| {
379 warn!(
380 "Invalid tool name '{}': {}. Falling back to 'unknown'.",
381 name_str,
382 e
383 );
384 ToolMetadata {
385 name: Cow::Borrowed("unknown"),
386 description: Cow::Owned(desc_str),
387 version: Cow::Borrowed("1.0.0"),
388 author: None,
389 tags: Vec::new(),
390 enabled: true,
391 metadata: HashMap::new(),
392 }
393 });
394 Self {
395 metadata,
396 input_schema: None,
397 staged_properties: None,
398 staged_required: Vec::new(),
399 handler: None,
400 }
401 }
402
403 pub fn schema(mut self, schema: serde_json::Value) -> Self {
404 self.input_schema = Some(schema);
405 self
406 }
407
408 pub fn metadata(mut self, f: impl FnOnce(ToolMetadata) -> ToolMetadata) -> Self {
409 self.metadata = f(self.metadata);
410 self
411 }
412
413 pub fn handler<F, Fut>(mut self, f: F) -> Self
414 where
415 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
416 Fut: std::future::Future<Output = crate::toolkits::error::ToolResult<serde_json::Value>>
417 + Send
418 + 'static,
419 {
420 let wrapped = move |args: serde_json::Value| -> std::pin::Pin<
421 Box<
422 dyn std::future::Future<
423 Output = crate::toolkits::error::ToolResult<serde_json::Value>,
424 > + Send,
425 >,
426 > { Box::pin(f(args)) };
427 self.handler = Some(std::sync::Arc::new(wrapped));
428 self
429 }
430
431 pub fn property(mut self, name: impl Into<String>, schema: serde_json::Value) -> Self {
435 let name = name.into();
436 let entry = self
437 .staged_properties
438 .get_or_insert_with(serde_json::Map::new);
439 entry.insert(name, schema);
440 self
441 }
442
443 pub fn required(mut self, name: impl Into<String>) -> Self {
446 self.staged_required.push(name.into());
447 self
448 }
449
450 pub fn build(mut self) -> crate::toolkits::error::ToolResult<FunctionTool> {
451 let handler = self
452 .handler
453 .ok_or_else(|| error_context().invalid_parameters("FunctionTool handler not set"))?;
454 let mut schema = self
456 .input_schema
457 .take()
458 .unwrap_or_else(|| serde_json::json!({}));
459
460 if let serde_json::Value::Object(ref mut obj) = schema {
462 obj.entry("type")
464 .or_insert(serde_json::Value::String("object".to_string()));
465 obj.entry("additionalProperties")
466 .or_insert(serde_json::Value::Bool(false));
467
468 if let Some(staged) = self.staged_properties.take() {
470 let props = obj
471 .entry("properties")
472 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
473 if let serde_json::Value::Object(props_obj) = props {
474 for (k, v) in staged {
475 props_obj.insert(k, v);
476 }
477 }
478 }
479 if !self.staged_required.is_empty() {
481 use std::collections::BTreeSet;
482 let mut set: BTreeSet<String> = obj
483 .get("required")
484 .and_then(|v| v.as_array())
485 .map(|arr| {
486 arr.iter()
487 .filter_map(|v| v.as_str().map(|s| s.to_string()))
488 .collect()
489 })
490 .unwrap_or_default();
491 for r in self.staged_required.into_iter() {
492 set.insert(r);
493 }
494 obj.insert(
495 "required".to_string(),
496 serde_json::Value::Array(
497 set.into_iter().map(serde_json::Value::String).collect(),
498 ),
499 );
500 }
501 } else {
502 }
506
507 if let serde_json::Value::Object(ref mut obj) = schema {
509 obj.entry("type")
510 .or_insert(serde_json::Value::String("object".to_string()));
511 obj.entry("additionalProperties")
512 .or_insert(serde_json::Value::Bool(false));
513 if obj.get("properties").is_none() {
516 obj.insert(
517 "properties".to_string(),
518 serde_json::Value::Object(serde_json::Map::new()),
519 );
520 }
521 }
522
523 let compiled_schema = compile_schema_cached(&schema).map_err(|e| {
524 error_context()
525 .with_tool(self.metadata.name.clone())
526 .schema_validation(format!("Failed to compile schema: {}", e))
527 })?;
528
529 Ok(FunctionTool {
530 metadata: self.metadata,
531 input_schema: schema,
532 compiled_schema,
533 handler,
534 })
535 }
536}
537
538#[async_trait]
539impl DynTool for FunctionTool {
540 fn metadata(&self) -> &ToolMetadata {
541 &self.metadata
542 }
543
544 async fn execute_json(&self, input: serde_json::Value) -> ToolResult<serde_json::Value> {
545 if let Err(validation_error) = self.compiled_schema.validate(&input) {
547 return Err(error_context()
548 .with_tool(self.name())
549 .invalid_parameters(format!("Input validation failed: {}", validation_error)));
550 }
551
552 (self.handler)(input).await
554 }
555
556 fn input_schema(&self) -> serde_json::Value {
557 self.input_schema.clone()
558 }
559
560 fn clone_box(&self) -> Box<dyn DynTool> {
561 Box::new(self.clone())
562 }
563}
564
565#[cfg(test)]
566mod tests {
567 use super::*;
568 use crate::toolkits::ToolError;
569
570 #[test]
571 fn test_tool_metadata_new() {
572 let metadata = ToolMetadata::new("test_tool", "A test tool").unwrap();
573 assert_eq!(metadata.name, "test_tool");
574 assert_eq!(metadata.description, "A test tool");
575 assert_eq!(metadata.version, "1.0.0");
576 assert!(metadata.enabled);
577 }
578
579 #[test]
580 fn test_tool_metadata_invalid_name_empty() {
581 let result = ToolMetadata::new("", "A test tool");
582 assert!(result.is_err());
583 match result.unwrap_err() {
584 ToolError::InvalidParameters { .. } => {},
585 _ => panic!("Expected InvalidParameters error"),
586 }
587 }
588
589 #[test]
590 fn test_tool_metadata_invalid_name_special_chars() {
591 let result = ToolMetadata::new("test-tool!", "A test tool");
592 assert!(result.is_err());
593 match result.unwrap_err() {
594 ToolError::InvalidParameters { .. } => {},
595 _ => panic!("Expected InvalidParameters error"),
596 }
597 }
598
599 #[test]
600 fn test_tool_metadata_builder() {
601 let metadata = ToolMetadata::new("test_tool", "A test tool")
602 .unwrap()
603 .version("2.0.0")
604 .author("Test Author")
605 .tags(vec!["tag1", "tag2"])
606 .enabled(false);
607
608 assert_eq!(metadata.version, "2.0.0");
609 assert_eq!(metadata.author, Some(Cow::Borrowed("Test Author")));
610 assert_eq!(metadata.tags.len(), 2);
611 assert!(!metadata.enabled);
612 }
613
614 #[test]
615 fn test_conversions_to_json() {
616 let value = conversions::to_json(42).unwrap();
617 assert_eq!(value, 42);
618 }
619
620 #[test]
621 fn test_conversions_from_json_string() {
622 let value = serde_json::Value::String("hello".to_string());
623 let result = conversions::from_json_string(value).unwrap();
624 assert_eq!(result, "hello");
625 }
626
627 #[test]
628 fn test_conversions_from_json_string_invalid() {
629 let value = serde_json::Value::Number(42.into());
630 let result = conversions::from_json_string(value);
631 assert!(result.is_err());
632 }
633
634 #[test]
635 fn test_conversions_from_json_i32() {
636 let value = serde_json::Value::Number(42.into());
637 let result = conversions::from_json_i32(value).unwrap();
638 assert_eq!(result, 42);
639 }
640
641 #[test]
642 fn test_conversions_from_json_f64() {
643 let value = serde_json::json!(3.5);
644 let result = conversions::from_json_f64(value).unwrap();
645 assert_eq!(result, 3.5);
646 }
647
648 #[test]
649 fn test_conversions_from_json_bool() {
650 let value = serde_json::Value::Bool(true);
651 let result = conversions::from_json_bool(value).unwrap();
652 assert!(result);
653 }
654
655 #[test]
656 fn test_function_tool_builder() {
657 let tool = FunctionTool::builder("test_tool", "A test tool")
658 .property("param1", serde_json::json!({"type": "string"}))
659 .property("param2", serde_json::json!({"type": "number"}))
660 .required("param1")
661 .handler(|_args| async move { Ok(serde_json::json!({"result": "ok"})) })
662 .build();
663
664 assert!(tool.is_ok());
665 let tool = tool.unwrap();
666 assert_eq!(tool.name(), "test_tool");
667 }
668
669 #[test]
670 fn test_function_tool_clone() {
671 let tool1 = FunctionTool::builder("test_tool", "A test tool")
672 .property("param1", serde_json::json!({"type": "string"}))
673 .required("param1")
674 .handler(|_args| async move { Ok(serde_json::json!({"result": "ok"})) })
675 .build()
676 .unwrap();
677
678 let tool2 = tool1.clone();
679 assert_eq!(tool1.name(), tool2.name());
680 assert_eq!(tool1.input_schema(), tool2.input_schema());
681 }
682
683 #[test]
684 fn test_parse_function_spec_shape1() {
685 let spec = serde_json::json!({
686 "name": "test_tool",
687 "description": "A test tool",
688 "parameters": {
689 "type": "object",
690 "properties": {
691 "param1": {"type": "string"}
692 }
693 }
694 });
695
696 let (name, description, parameters) = parse_function_spec_details(&spec).unwrap();
697 assert_eq!(name, "test_tool");
698 assert_eq!(description, "A test tool");
699 assert!(parameters.is_some());
700 }
701
702 #[test]
703 fn test_parse_function_spec_shape2() {
704 let spec = serde_json::json!({
705 "type": "function",
706 "function": {
707 "name": "test_tool",
708 "description": "A test tool",
709 "parameters": {
710 "type": "object",
711 "properties": {
712 "param1": {"type": "string"}
713 }
714 }
715 }
716 });
717
718 let (name, description, parameters) = parse_function_spec_details(&spec).unwrap();
719 assert_eq!(name, "test_tool");
720 assert_eq!(description, "A test tool");
721 assert!(parameters.is_some());
722 }
723
724 #[test]
725 fn test_parse_function_spec_invalid() {
726 let spec = serde_json::Value::String("invalid".to_string());
727 let result = parse_function_spec_details(&spec);
728 assert!(result.is_err());
729 }
730}