1use async_trait::async_trait;
4use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
5use cortexai_core::{errors::ToolError, ExecutionContext, Tool, ToolSchema};
6use serde_json::{json, Value};
7use sha2::{Digest, Sha256, Sha512};
8
9pub struct JsonTool;
11
12impl Default for JsonTool {
13 fn default() -> Self {
14 Self::new()
15 }
16}
17
18impl JsonTool {
19 pub fn new() -> Self {
20 Self
21 }
22}
23
24#[async_trait]
25impl Tool for JsonTool {
26 fn schema(&self) -> ToolSchema {
27 ToolSchema::new("json", "Parse, format, and manipulate JSON data")
28 .with_parameters(json!({
29 "type": "object",
30 "properties": {
31 "operation": {
32 "type": "string",
33 "enum": ["parse", "stringify", "format", "minify", "get", "set", "merge", "validate"],
34 "description": "Operation to perform"
35 },
36 "data": {
37 "description": "JSON data (string or object)"
38 },
39 "path": {
40 "type": "string",
41 "description": "JSON path for get/set operations (e.g., 'user.name', 'items[0].id')"
42 },
43 "value": {
44 "description": "Value to set (for 'set' operation)"
45 },
46 "merge_with": {
47 "description": "Object to merge with (for 'merge' operation)"
48 }
49 },
50 "required": ["operation", "data"]
51 }))
52 }
53
54 async fn execute(
55 &self,
56 _context: &ExecutionContext,
57 arguments: serde_json::Value,
58 ) -> Result<serde_json::Value, ToolError> {
59 let operation = arguments["operation"]
60 .as_str()
61 .ok_or_else(|| ToolError::InvalidArguments("Missing 'operation'".to_string()))?;
62
63 match operation {
64 "parse" => {
65 let data_str = arguments["data"].as_str().ok_or_else(|| {
66 ToolError::InvalidArguments("'data' must be a string for parse".to_string())
67 })?;
68
69 let parsed: Value = serde_json::from_str(data_str)
70 .map_err(|e| ToolError::ExecutionFailed(format!("JSON parse error: {}", e)))?;
71
72 Ok(json!({
73 "success": true,
74 "result": parsed
75 }))
76 }
77 "stringify" | "format" => {
78 let data = &arguments["data"];
79 let formatted = serde_json::to_string_pretty(data)
80 .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
81
82 Ok(json!({
83 "success": true,
84 "result": formatted
85 }))
86 }
87 "minify" => {
88 let data = &arguments["data"];
89 let minified = serde_json::to_string(data)
90 .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
91
92 Ok(json!({
93 "success": true,
94 "result": minified
95 }))
96 }
97 "get" => {
98 let path = arguments["path"]
99 .as_str()
100 .ok_or_else(|| ToolError::InvalidArguments("Missing 'path'".to_string()))?;
101
102 let value = get_json_path(&arguments["data"], path)?;
103
104 Ok(json!({
105 "success": true,
106 "path": path,
107 "result": value
108 }))
109 }
110 "set" => {
111 let path = arguments["path"]
112 .as_str()
113 .ok_or_else(|| ToolError::InvalidArguments("Missing 'path'".to_string()))?;
114 let value = &arguments["value"];
115
116 let mut data = arguments["data"].clone();
117 set_json_path(&mut data, path, value.clone())?;
118
119 Ok(json!({
120 "success": true,
121 "path": path,
122 "result": data
123 }))
124 }
125 "merge" => {
126 let merge_with = &arguments["merge_with"];
127 let mut result = arguments["data"].clone();
128
129 merge_json(&mut result, merge_with);
130
131 Ok(json!({
132 "success": true,
133 "result": result
134 }))
135 }
136 "validate" => {
137 let data_str = arguments["data"].as_str();
138 let (valid, error) = match data_str {
139 Some(s) => match serde_json::from_str::<Value>(s) {
140 Ok(_) => (true, None),
141 Err(e) => (false, Some(e.to_string())),
142 },
143 None => (true, None), };
145
146 Ok(json!({
147 "valid": valid,
148 "error": error
149 }))
150 }
151 _ => Err(ToolError::InvalidArguments(format!(
152 "Unknown operation: {}",
153 operation
154 ))),
155 }
156 }
157}
158
159fn get_json_path(data: &Value, path: &str) -> Result<Value, ToolError> {
160 let mut current = data;
161
162 for part in path.split('.') {
163 if let Some(bracket_pos) = part.find('[') {
165 let key = &part[..bracket_pos];
166 let index_str = &part[bracket_pos + 1..part.len() - 1];
167 let index: usize = index_str.parse().map_err(|_| {
168 ToolError::InvalidArguments(format!("Invalid array index: {}", index_str))
169 })?;
170
171 if !key.is_empty() {
172 current = current
173 .get(key)
174 .ok_or_else(|| ToolError::ExecutionFailed(format!("Key not found: {}", key)))?;
175 }
176
177 current = current.get(index).ok_or_else(|| {
178 ToolError::ExecutionFailed(format!("Index {} out of bounds", index))
179 })?;
180 } else if !part.is_empty() {
181 current = current
182 .get(part)
183 .ok_or_else(|| ToolError::ExecutionFailed(format!("Key not found: {}", part)))?;
184 }
185 }
186
187 Ok(current.clone())
188}
189
190fn set_json_path(data: &mut Value, path: &str, value: Value) -> Result<(), ToolError> {
191 let parts: Vec<&str> = path.split('.').collect();
192 let mut current = data;
193
194 for (i, part) in parts.iter().enumerate() {
195 let is_last = i == parts.len() - 1;
196
197 if let Some(bracket_pos) = part.find('[') {
198 let key = &part[..bracket_pos];
199 let index_str = &part[bracket_pos + 1..part.len() - 1];
200 let index: usize = index_str.parse().map_err(|_| {
201 ToolError::InvalidArguments(format!("Invalid array index: {}", index_str))
202 })?;
203
204 if !key.is_empty() {
205 current = current
206 .get_mut(key)
207 .ok_or_else(|| ToolError::ExecutionFailed(format!("Key not found: {}", key)))?;
208 }
209
210 if is_last {
211 if let Some(arr) = current.as_array_mut() {
212 if index < arr.len() {
213 arr[index] = value;
214 return Ok(());
215 }
216 }
217 return Err(ToolError::ExecutionFailed(format!(
218 "Index {} out of bounds",
219 index
220 )));
221 }
222
223 current = current.get_mut(index).ok_or_else(|| {
224 ToolError::ExecutionFailed(format!("Index {} out of bounds", index))
225 })?;
226 } else if is_last {
227 if let Some(obj) = current.as_object_mut() {
228 obj.insert(part.to_string(), value);
229 return Ok(());
230 }
231 return Err(ToolError::ExecutionFailed(
232 "Cannot set property on non-object".to_string(),
233 ));
234 } else {
235 current = current
236 .get_mut(*part)
237 .ok_or_else(|| ToolError::ExecutionFailed(format!("Key not found: {}", part)))?;
238 }
239 }
240
241 Ok(())
242}
243
244fn merge_json(target: &mut Value, source: &Value) {
245 match (target, source) {
246 (Value::Object(target_map), Value::Object(source_map)) => {
247 for (key, value) in source_map {
248 if let Some(target_value) = target_map.get_mut(key) {
249 merge_json(target_value, value);
250 } else {
251 target_map.insert(key.clone(), value.clone());
252 }
253 }
254 }
255 (target, source) => {
256 *target = source.clone();
257 }
258 }
259}
260
261pub struct Base64Tool;
263
264impl Default for Base64Tool {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270impl Base64Tool {
271 pub fn new() -> Self {
272 Self
273 }
274}
275
276#[async_trait]
277impl Tool for Base64Tool {
278 fn schema(&self) -> ToolSchema {
279 ToolSchema::new("base64", "Encode or decode Base64 strings").with_parameters(json!({
280 "type": "object",
281 "properties": {
282 "operation": {
283 "type": "string",
284 "enum": ["encode", "decode"],
285 "description": "Whether to encode or decode"
286 },
287 "data": {
288 "type": "string",
289 "description": "Data to encode or decode"
290 }
291 },
292 "required": ["operation", "data"]
293 }))
294 }
295
296 async fn execute(
297 &self,
298 _context: &ExecutionContext,
299 arguments: serde_json::Value,
300 ) -> Result<serde_json::Value, ToolError> {
301 let operation = arguments["operation"]
302 .as_str()
303 .ok_or_else(|| ToolError::InvalidArguments("Missing 'operation'".to_string()))?;
304 let data = arguments["data"]
305 .as_str()
306 .ok_or_else(|| ToolError::InvalidArguments("Missing 'data'".to_string()))?;
307
308 match operation {
309 "encode" => {
310 let encoded = BASE64.encode(data.as_bytes());
311 Ok(json!({
312 "operation": "encode",
313 "input": data,
314 "result": encoded
315 }))
316 }
317 "decode" => {
318 let decoded_bytes = BASE64.decode(data).map_err(|e| {
319 ToolError::ExecutionFailed(format!("Base64 decode error: {}", e))
320 })?;
321 let decoded = String::from_utf8(decoded_bytes).map_err(|e| {
322 ToolError::ExecutionFailed(format!("UTF-8 decode error: {}", e))
323 })?;
324 Ok(json!({
325 "operation": "decode",
326 "input": data,
327 "result": decoded
328 }))
329 }
330 _ => Err(ToolError::InvalidArguments(format!(
331 "Unknown operation: {}",
332 operation
333 ))),
334 }
335 }
336}
337
338pub struct HashTool;
340
341impl Default for HashTool {
342 fn default() -> Self {
343 Self::new()
344 }
345}
346
347impl HashTool {
348 pub fn new() -> Self {
349 Self
350 }
351}
352
353#[async_trait]
354impl Tool for HashTool {
355 fn schema(&self) -> ToolSchema {
356 ToolSchema::new("hash", "Generate cryptographic hashes").with_parameters(json!({
357 "type": "object",
358 "properties": {
359 "data": {
360 "type": "string",
361 "description": "Data to hash"
362 },
363 "algorithm": {
364 "type": "string",
365 "enum": ["sha256", "sha512"],
366 "description": "Hash algorithm (default: sha256)"
367 },
368 "output": {
369 "type": "string",
370 "enum": ["hex", "base64"],
371 "description": "Output format (default: hex)"
372 }
373 },
374 "required": ["data"]
375 }))
376 }
377
378 async fn execute(
379 &self,
380 _context: &ExecutionContext,
381 arguments: serde_json::Value,
382 ) -> Result<serde_json::Value, ToolError> {
383 let data = arguments["data"]
384 .as_str()
385 .ok_or_else(|| ToolError::InvalidArguments("Missing 'data'".to_string()))?;
386 let algorithm = arguments["algorithm"].as_str().unwrap_or("sha256");
387 let output_format = arguments["output"].as_str().unwrap_or("hex");
388
389 let hash_bytes: Vec<u8> = match algorithm {
390 "sha256" => {
391 let mut hasher = Sha256::new();
392 hasher.update(data.as_bytes());
393 hasher.finalize().to_vec()
394 }
395 "sha512" => {
396 let mut hasher = Sha512::new();
397 hasher.update(data.as_bytes());
398 hasher.finalize().to_vec()
399 }
400 _ => {
401 return Err(ToolError::InvalidArguments(format!(
402 "Unknown algorithm: {}",
403 algorithm
404 )))
405 }
406 };
407
408 let result = match output_format {
409 "hex" => hex::encode(&hash_bytes),
410 "base64" => BASE64.encode(&hash_bytes),
411 _ => {
412 return Err(ToolError::InvalidArguments(format!(
413 "Unknown output format: {}",
414 output_format
415 )))
416 }
417 };
418
419 Ok(json!({
420 "data": data,
421 "algorithm": algorithm,
422 "output_format": output_format,
423 "hash": result,
424 "length": hash_bytes.len() * 8
425 }))
426 }
427}
428
429pub struct UrlEncodeTool;
431
432impl Default for UrlEncodeTool {
433 fn default() -> Self {
434 Self::new()
435 }
436}
437
438impl UrlEncodeTool {
439 pub fn new() -> Self {
440 Self
441 }
442}
443
444#[async_trait]
445impl Tool for UrlEncodeTool {
446 fn schema(&self) -> ToolSchema {
447 ToolSchema::new("url_encode", "Encode or decode URL strings").with_parameters(json!({
448 "type": "object",
449 "properties": {
450 "operation": {
451 "type": "string",
452 "enum": ["encode", "decode"],
453 "description": "Whether to encode or decode"
454 },
455 "data": {
456 "type": "string",
457 "description": "String to encode or decode"
458 }
459 },
460 "required": ["operation", "data"]
461 }))
462 }
463
464 async fn execute(
465 &self,
466 _context: &ExecutionContext,
467 arguments: serde_json::Value,
468 ) -> Result<serde_json::Value, ToolError> {
469 let operation = arguments["operation"]
470 .as_str()
471 .ok_or_else(|| ToolError::InvalidArguments("Missing 'operation'".to_string()))?;
472 let data = arguments["data"]
473 .as_str()
474 .ok_or_else(|| ToolError::InvalidArguments("Missing 'data'".to_string()))?;
475
476 match operation {
477 "encode" => {
478 let encoded = urlencoding::encode(data);
479 Ok(json!({
480 "operation": "encode",
481 "input": data,
482 "result": encoded.to_string()
483 }))
484 }
485 "decode" => {
486 let decoded = urlencoding::decode(data)
487 .map_err(|e| ToolError::ExecutionFailed(format!("URL decode error: {}", e)))?;
488 Ok(json!({
489 "operation": "decode",
490 "input": data,
491 "result": decoded.to_string()
492 }))
493 }
494 _ => Err(ToolError::InvalidArguments(format!(
495 "Unknown operation: {}",
496 operation
497 ))),
498 }
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use cortexai_core::types::AgentId;
506
507 fn test_ctx() -> ExecutionContext {
508 ExecutionContext::new(AgentId::new("test-agent"))
509 }
510
511 #[tokio::test]
512 async fn test_json_parse() {
513 let tool = JsonTool::new();
514 let ctx = test_ctx();
515
516 let result = tool
517 .execute(
518 &ctx,
519 json!({
520 "operation": "parse",
521 "data": r#"{"name": "test", "value": 42}"#
522 }),
523 )
524 .await
525 .unwrap();
526
527 assert!(result["success"].as_bool().unwrap());
528 assert_eq!(result["result"]["name"], "test");
529 assert_eq!(result["result"]["value"], 42);
530 }
531
532 #[tokio::test]
533 async fn test_json_get_path() {
534 let tool = JsonTool::new();
535 let ctx = test_ctx();
536
537 let result = tool
538 .execute(
539 &ctx,
540 json!({
541 "operation": "get",
542 "data": {"user": {"name": "John", "age": 30}},
543 "path": "user.name"
544 }),
545 )
546 .await
547 .unwrap();
548
549 assert_eq!(result["result"], "John");
550 }
551
552 #[tokio::test]
553 async fn test_json_merge() {
554 let tool = JsonTool::new();
555 let ctx = test_ctx();
556
557 let result = tool
558 .execute(
559 &ctx,
560 json!({
561 "operation": "merge",
562 "data": {"a": 1, "b": 2},
563 "merge_with": {"b": 3, "c": 4}
564 }),
565 )
566 .await
567 .unwrap();
568
569 assert_eq!(result["result"]["a"], 1);
570 assert_eq!(result["result"]["b"], 3);
571 assert_eq!(result["result"]["c"], 4);
572 }
573
574 #[tokio::test]
575 async fn test_base64() {
576 let tool = Base64Tool::new();
577 let ctx = test_ctx();
578
579 let encoded = tool
580 .execute(
581 &ctx,
582 json!({
583 "operation": "encode",
584 "data": "Hello, World!"
585 }),
586 )
587 .await
588 .unwrap();
589
590 assert_eq!(encoded["result"], "SGVsbG8sIFdvcmxkIQ==");
591
592 let decoded = tool
593 .execute(
594 &ctx,
595 json!({
596 "operation": "decode",
597 "data": "SGVsbG8sIFdvcmxkIQ=="
598 }),
599 )
600 .await
601 .unwrap();
602
603 assert_eq!(decoded["result"], "Hello, World!");
604 }
605
606 #[tokio::test]
607 async fn test_hash() {
608 let tool = HashTool::new();
609 let ctx = test_ctx();
610
611 let result = tool
612 .execute(
613 &ctx,
614 json!({
615 "data": "hello",
616 "algorithm": "sha256"
617 }),
618 )
619 .await
620 .unwrap();
621
622 assert_eq!(
623 result["hash"],
624 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
625 );
626 }
627}