1use async_trait::async_trait;
2use rand::{Rng, seq::SliceRandom};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::generate_schema;
8use ai_agents_core::{Tool, ToolResult, ToolSafetyMetadata};
9
10pub struct RandomTool;
11
12impl RandomTool {
13 pub fn new() -> Self {
14 Self
15 }
16}
17
18impl Default for RandomTool {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24#[derive(Debug, Deserialize, JsonSchema)]
25struct RandomInput {
26 operation: String,
28 #[serde(default)]
30 min: Option<f64>,
31 #[serde(default)]
33 max: Option<f64>,
34 #[serde(default)]
36 items: Option<Vec<Value>>,
37 #[serde(default)]
39 count: Option<usize>,
40 #[serde(default)]
42 length: Option<usize>,
43 #[serde(default)]
45 charset: Option<String>,
46}
47
48#[derive(Debug, Serialize, Deserialize)]
49struct UuidOutput {
50 uuid: String,
51}
52
53#[derive(Debug, Serialize, Deserialize)]
54struct NumberOutput {
55 value: f64,
56 min: f64,
57 max: f64,
58}
59
60#[derive(Debug, Serialize, Deserialize)]
61struct IntegerOutput {
62 value: i64,
63 min: i64,
64 max: i64,
65}
66
67#[derive(Debug, Serialize, Deserialize)]
68struct ChoiceOutput {
69 selected: Vec<Value>,
70 count: usize,
71}
72
73#[derive(Debug, Serialize, Deserialize)]
74struct ShuffleOutput {
75 shuffled: Vec<Value>,
76 count: usize,
77}
78
79#[derive(Debug, Serialize, Deserialize)]
80struct BoolOutput {
81 value: bool,
82}
83
84#[derive(Debug, Serialize, Deserialize)]
85struct StringOutput {
86 value: String,
87 length: usize,
88}
89
90#[async_trait]
91impl Tool for RandomTool {
92 fn id(&self) -> &str {
93 "random"
94 }
95
96 fn name(&self) -> &str {
97 "Random Generator"
98 }
99
100 fn description(&self) -> &str {
101 "Generate random values. Operations: uuid (generate UUID v4), number (random float), integer (random int), choice (pick from list), shuffle (randomize list order), bool (random true/false), string (random string)."
102 }
103
104 fn input_schema(&self) -> Value {
105 generate_schema::<RandomInput>()
106 }
107
108 fn safety_metadata(&self) -> ToolSafetyMetadata {
109 ToolSafetyMetadata::compute()
110 }
111
112 async fn execute(&self, args: Value, _ctx: ai_agents_core::ToolExecutionContext) -> ToolResult {
113 let input: RandomInput = match serde_json::from_value(args) {
114 Ok(input) => input,
115 Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
116 };
117
118 match input.operation.to_lowercase().as_str() {
119 "uuid" => self.handle_uuid(),
120 "number" => self.handle_number(&input),
121 "integer" | "int" => self.handle_integer(&input),
122 "choice" | "choose" | "pick" => self.handle_choice(&input),
123 "shuffle" => self.handle_shuffle(&input),
124 "bool" | "boolean" => self.handle_bool(),
125 "string" | "str" => self.handle_string(&input),
126 _ => ToolResult::error(format!(
127 "Unknown operation: {}. Valid operations: uuid, number, integer, choice, shuffle, bool, string",
128 input.operation
129 )),
130 }
131 }
132}
133
134impl RandomTool {
135 fn handle_uuid(&self) -> ToolResult {
136 let output = UuidOutput {
137 uuid: uuid::Uuid::new_v4().to_string(),
138 };
139 self.to_result(&output)
140 }
141
142 fn handle_number(&self, input: &RandomInput) -> ToolResult {
143 let min = input.min.unwrap_or(0.0);
144 let max = input.max.unwrap_or(1.0);
145
146 if min >= max {
147 return ToolResult::error("'min' must be less than 'max'");
148 }
149
150 let mut rng = rand::thread_rng();
151 let value: f64 = rng.gen_range(min..max);
152
153 let output = NumberOutput { value, min, max };
154 self.to_result(&output)
155 }
156
157 fn handle_integer(&self, input: &RandomInput) -> ToolResult {
158 let min = input.min.unwrap_or(0.0) as i64;
159 let max = input.max.unwrap_or(100.0) as i64;
160
161 if min >= max {
162 return ToolResult::error("'min' must be less than 'max'");
163 }
164
165 let mut rng = rand::thread_rng();
166 let value: i64 = rng.gen_range(min..=max);
167
168 let output = IntegerOutput { value, min, max };
169 self.to_result(&output)
170 }
171
172 fn handle_choice(&self, input: &RandomInput) -> ToolResult {
173 let items = match &input.items {
174 Some(i) if !i.is_empty() => i,
175 Some(_) => return ToolResult::error("'items' cannot be empty"),
176 None => return ToolResult::error("'items' is required for choice operation"),
177 };
178
179 let count = input.count.unwrap_or(1).min(items.len());
180
181 let mut rng = rand::thread_rng();
182 let selected: Vec<Value> = items.choose_multiple(&mut rng, count).cloned().collect();
183
184 let output = ChoiceOutput {
185 count: selected.len(),
186 selected,
187 };
188 self.to_result(&output)
189 }
190
191 fn handle_shuffle(&self, input: &RandomInput) -> ToolResult {
192 let items = match &input.items {
193 Some(i) => i.clone(),
194 None => return ToolResult::error("'items' is required for shuffle operation"),
195 };
196
197 let mut shuffled = items;
198 let mut rng = rand::thread_rng();
199 shuffled.shuffle(&mut rng);
200
201 let output = ShuffleOutput {
202 count: shuffled.len(),
203 shuffled,
204 };
205 self.to_result(&output)
206 }
207
208 fn handle_bool(&self) -> ToolResult {
209 let mut rng = rand::thread_rng();
210 let output = BoolOutput { value: rng.r#gen() };
211 self.to_result(&output)
212 }
213
214 fn handle_string(&self, input: &RandomInput) -> ToolResult {
215 let length = input.length.unwrap_or(16);
216 let charset = input.charset.as_deref().unwrap_or("alphanumeric");
217
218 let chars: Vec<char> = match charset.to_lowercase().as_str() {
219 "alphanumeric" | "alnum" => {
220 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
221 .chars()
222 .collect()
223 }
224 "alpha" | "letters" => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
225 .chars()
226 .collect(),
227 "numeric" | "digits" | "numbers" => "0123456789".chars().collect(),
228 "hex" | "hexadecimal" => "0123456789abcdef".chars().collect(),
229 "lower" | "lowercase" => "abcdefghijklmnopqrstuvwxyz".chars().collect(),
230 "upper" | "uppercase" => "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect(),
231 _ => {
232 return ToolResult::error(format!(
233 "Unknown charset: {}. Valid: alphanumeric, alpha, numeric, hex, lower, upper",
234 charset
235 ));
236 }
237 };
238
239 let mut rng = rand::thread_rng();
240 let value: String = (0..length)
241 .map(|_| chars[rng.gen_range(0..chars.len())])
242 .collect();
243
244 let output = StringOutput { value, length };
245 self.to_result(&output)
246 }
247
248 fn to_result<T: Serialize>(&self, output: &T) -> ToolResult {
249 match serde_json::to_string(output) {
250 Ok(json) => ToolResult::ok(json),
251 Err(e) => ToolResult::error(format!("Serialization error: {}", e)),
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[tokio::test]
261 async fn test_uuid() {
262 let tool = RandomTool::new();
263 let result = tool
264 .execute(
265 serde_json::json!({"operation": "uuid"}),
266 ai_agents_core::ToolExecutionContext::test("test"),
267 )
268 .await;
269 assert!(result.success);
270
271 let output: UuidOutput = serde_json::from_str(&result.output).unwrap();
272 assert_eq!(output.uuid.len(), 36);
273 assert!(output.uuid.contains('-'));
274 }
275
276 #[tokio::test]
277 async fn test_number() {
278 let tool = RandomTool::new();
279 let result = tool
280 .execute(
281 serde_json::json!({
282 "operation": "number",
283 "min": 10.0,
284 "max": 20.0
285 }),
286 ai_agents_core::ToolExecutionContext::test("test"),
287 )
288 .await;
289 assert!(result.success);
290
291 let output: NumberOutput = serde_json::from_str(&result.output).unwrap();
292 assert!(output.value >= 10.0 && output.value < 20.0);
293 }
294
295 #[tokio::test]
296 async fn test_number_invalid_range() {
297 let tool = RandomTool::new();
298 let result = tool
299 .execute(
300 serde_json::json!({
301 "operation": "number",
302 "min": 20.0,
303 "max": 10.0
304 }),
305 ai_agents_core::ToolExecutionContext::test("test"),
306 )
307 .await;
308 assert!(!result.success);
309 }
310
311 #[tokio::test]
312 async fn test_integer() {
313 let tool = RandomTool::new();
314 let result = tool
315 .execute(
316 serde_json::json!({
317 "operation": "integer",
318 "min": 1,
319 "max": 10
320 }),
321 ai_agents_core::ToolExecutionContext::test("test"),
322 )
323 .await;
324 assert!(result.success);
325
326 let output: IntegerOutput = serde_json::from_str(&result.output).unwrap();
327 assert!(output.value >= 1 && output.value <= 10);
328 }
329
330 #[tokio::test]
331 async fn test_choice() {
332 let tool = RandomTool::new();
333 let result = tool
334 .execute(
335 serde_json::json!({
336 "operation": "choice",
337 "items": ["a", "b", "c", "d"],
338 "count": 2
339 }),
340 ai_agents_core::ToolExecutionContext::test("test"),
341 )
342 .await;
343 assert!(result.success);
344
345 let output: ChoiceOutput = serde_json::from_str(&result.output).unwrap();
346 assert_eq!(output.count, 2);
347 assert_eq!(output.selected.len(), 2);
348 }
349
350 #[tokio::test]
351 async fn test_choice_single() {
352 let tool = RandomTool::new();
353 let result = tool
354 .execute(
355 serde_json::json!({
356 "operation": "choice",
357 "items": [1, 2, 3]
358 }),
359 ai_agents_core::ToolExecutionContext::test("test"),
360 )
361 .await;
362 assert!(result.success);
363
364 let output: ChoiceOutput = serde_json::from_str(&result.output).unwrap();
365 assert_eq!(output.count, 1);
366 }
367
368 #[tokio::test]
369 async fn test_choice_empty() {
370 let tool = RandomTool::new();
371 let result = tool
372 .execute(
373 serde_json::json!({
374 "operation": "choice",
375 "items": []
376 }),
377 ai_agents_core::ToolExecutionContext::test("test"),
378 )
379 .await;
380 assert!(!result.success);
381 }
382
383 #[tokio::test]
384 async fn test_shuffle() {
385 let tool = RandomTool::new();
386 let result = tool
387 .execute(
388 serde_json::json!({
389 "operation": "shuffle",
390 "items": [1, 2, 3, 4, 5]
391 }),
392 ai_agents_core::ToolExecutionContext::test("test"),
393 )
394 .await;
395 assert!(result.success);
396
397 let output: ShuffleOutput = serde_json::from_str(&result.output).unwrap();
398 assert_eq!(output.count, 5);
399 }
400
401 #[tokio::test]
402 async fn test_bool() {
403 let tool = RandomTool::new();
404 let result = tool
405 .execute(
406 serde_json::json!({"operation": "bool"}),
407 ai_agents_core::ToolExecutionContext::test("test"),
408 )
409 .await;
410 assert!(result.success);
411
412 let output: BoolOutput = serde_json::from_str(&result.output).unwrap();
413 assert!(output.value == true || output.value == false);
414 }
415
416 #[tokio::test]
417 async fn test_string_default() {
418 let tool = RandomTool::new();
419 let result = tool
420 .execute(
421 serde_json::json!({"operation": "string"}),
422 ai_agents_core::ToolExecutionContext::test("test"),
423 )
424 .await;
425 assert!(result.success);
426
427 let output: StringOutput = serde_json::from_str(&result.output).unwrap();
428 assert_eq!(output.length, 16);
429 assert_eq!(output.value.len(), 16);
430 }
431
432 #[tokio::test]
433 async fn test_string_hex() {
434 let tool = RandomTool::new();
435 let result = tool
436 .execute(
437 serde_json::json!({
438 "operation": "string",
439 "length": 8,
440 "charset": "hex"
441 }),
442 ai_agents_core::ToolExecutionContext::test("test"),
443 )
444 .await;
445 assert!(result.success);
446
447 let output: StringOutput = serde_json::from_str(&result.output).unwrap();
448 assert_eq!(output.length, 8);
449 assert!(output.value.chars().all(|c| c.is_ascii_hexdigit()));
450 }
451
452 #[tokio::test]
453 async fn test_string_numeric() {
454 let tool = RandomTool::new();
455 let result = tool
456 .execute(
457 serde_json::json!({
458 "operation": "string",
459 "length": 10,
460 "charset": "numeric"
461 }),
462 ai_agents_core::ToolExecutionContext::test("test"),
463 )
464 .await;
465 assert!(result.success);
466
467 let output: StringOutput = serde_json::from_str(&result.output).unwrap();
468 assert!(output.value.chars().all(|c| c.is_ascii_digit()));
469 }
470
471 #[tokio::test]
472 async fn test_invalid_operation() {
473 let tool = RandomTool::new();
474 let result = tool
475 .execute(
476 serde_json::json!({"operation": "invalid"}),
477 ai_agents_core::ToolExecutionContext::test("test"),
478 )
479 .await;
480 assert!(!result.success);
481 }
482}