@@ -39,6 +39,11 @@ fn validate_path_security(path: &str) -> Result<PathBuf, String> {
3939 let current_dir = std:: env:: current_dir ( )
4040 . map_err ( |e| format ! ( "Failed to get current directory: {}" , e) ) ?;
4141
42+ // Normalize current_dir for consistent comparison (handle UNC paths on Windows)
43+ // On Windows, canonicalize() may return \\?\ prefix paths
44+ let normalized_current_dir = current_dir. canonicalize ( )
45+ . unwrap_or_else ( |_| current_dir. clone ( ) ) ;
46+
4247 let full_path = current_dir. join ( path) ;
4348
4449 // Canonicalize if path exists, otherwise just check the constructed path
@@ -51,11 +56,13 @@ fn validate_path_security(path: &str) -> Result<PathBuf, String> {
5156 } ;
5257
5358 // Verify the path is within current directory
54- if !canonical_path. starts_with ( & current_dir) {
59+ // Use normalized paths for comparison to handle Windows UNC path prefixes
60+ if !canonical_path. starts_with ( & normalized_current_dir) {
5561 return Err ( format ! (
56- "Security: Path escapes current directory. Path '{}' resolves to '{}'" ,
62+ "Security: Path escapes current directory. Path '{}' resolves to '{}', expected to be within '{}' " ,
5763 path,
58- canonical_path. display( )
64+ canonical_path. display( ) ,
65+ normalized_current_dir. display( )
5966 ) ) ;
6067 }
6168
@@ -486,3 +493,163 @@ impl Tool for RunCommandTool {
486493 }
487494 }
488495}
496+
497+ // ============================================================================
498+ // DeleteFileTool
499+ // ============================================================================
500+
501+ pub struct DeleteFileTool ;
502+
503+ #[ async_trait]
504+ impl Tool for DeleteFileTool {
505+ fn name ( & self ) -> & str {
506+ "delete_file"
507+ }
508+
509+ fn description ( & self ) -> & str {
510+ "Delete a file from the project. \
511+ SECURITY: Only works within current directory. \
512+ Useful for removing deprecated or unused files during iterative changes."
513+ }
514+
515+ fn parameters_schema ( & self ) -> Option < Value > {
516+ Some ( json ! ( {
517+ "type" : "object" ,
518+ "properties" : {
519+ "path" : {
520+ "type" : "string" ,
521+ "description" : "File path to delete (must be relative path within current directory)"
522+ }
523+ } ,
524+ "required" : [ "path" ]
525+ } ) )
526+ }
527+
528+ async fn execute ( & self , _ctx : Arc < dyn ToolContext > , args : Value ) -> adk_core:: Result < Value > {
529+ let path = args[ "path" ] . as_str ( ) . unwrap ( ) ;
530+
531+ // Security check
532+ let safe_path = match validate_path_security ( path) {
533+ Ok ( p) => p,
534+ Err ( e) => {
535+ return Ok ( json ! ( {
536+ "status" : "security_error" ,
537+ "message" : e
538+ } ) ) ;
539+ }
540+ } ;
541+
542+ // Check if file exists
543+ if !safe_path. exists ( ) {
544+ return Ok ( json ! ( {
545+ "status" : "not_found" ,
546+ "message" : format!( "File not found: {}" , path)
547+ } ) ) ;
548+ }
549+
550+ // Check if it's a directory (we only delete files, not directories)
551+ if safe_path. is_dir ( ) {
552+ return Ok ( json ! ( {
553+ "status" : "error" ,
554+ "message" : format!( "Path '{}' is a directory. Use delete_directory for directories." , path)
555+ } ) ) ;
556+ }
557+
558+ // Delete the file
559+ match fs:: remove_file ( & safe_path) {
560+ Ok ( _) => {
561+ // Log file deletion for user visibility
562+ println ! ( "🗑️ Deleted file: {}" , path) ;
563+ Ok ( json ! ( {
564+ "status" : "success" ,
565+ "path" : path,
566+ "message" : format!( "File '{}' deleted successfully" , path)
567+ } ) )
568+ } ,
569+ Err ( e) => Ok ( json ! ( {
570+ "status" : "error" ,
571+ "message" : format!( "Failed to delete file: {}" , e)
572+ } ) ) ,
573+ }
574+ }
575+ }
576+
577+ // ============================================================================
578+ // DeleteDirectoryTool
579+ // ============================================================================
580+
581+ pub struct DeleteDirectoryTool ;
582+
583+ #[ async_trait]
584+ impl Tool for DeleteDirectoryTool {
585+ fn name ( & self ) -> & str {
586+ "delete_directory"
587+ }
588+
589+ fn description ( & self ) -> & str {
590+ "Delete a directory and all its contents recursively. \
591+ SECURITY: Only works within current directory. \
592+ WARNING: This operation is irreversible! Use with caution."
593+ }
594+
595+ fn parameters_schema ( & self ) -> Option < Value > {
596+ Some ( json ! ( {
597+ "type" : "object" ,
598+ "properties" : {
599+ "path" : {
600+ "type" : "string" ,
601+ "description" : "Directory path to delete (must be relative path within current directory)"
602+ }
603+ } ,
604+ "required" : [ "path" ]
605+ } ) )
606+ }
607+
608+ async fn execute ( & self , _ctx : Arc < dyn ToolContext > , args : Value ) -> adk_core:: Result < Value > {
609+ let path = args[ "path" ] . as_str ( ) . unwrap ( ) ;
610+
611+ // Security check
612+ let safe_path = match validate_path_security ( path) {
613+ Ok ( p) => p,
614+ Err ( e) => {
615+ return Ok ( json ! ( {
616+ "status" : "security_error" ,
617+ "message" : e
618+ } ) ) ;
619+ }
620+ } ;
621+
622+ // Check if directory exists
623+ if !safe_path. exists ( ) {
624+ return Ok ( json ! ( {
625+ "status" : "not_found" ,
626+ "message" : format!( "Directory not found: {}" , path)
627+ } ) ) ;
628+ }
629+
630+ // Check if it's actually a directory
631+ if !safe_path. is_dir ( ) {
632+ return Ok ( json ! ( {
633+ "status" : "error" ,
634+ "message" : format!( "Path '{}' is a file. Use delete_file for files." , path)
635+ } ) ) ;
636+ }
637+
638+ // Delete the directory recursively
639+ match fs:: remove_dir_all ( & safe_path) {
640+ Ok ( _) => {
641+ // Log directory deletion for user visibility
642+ println ! ( "🗑️ Deleted directory: {}" , path) ;
643+ Ok ( json ! ( {
644+ "status" : "success" ,
645+ "path" : path,
646+ "message" : format!( "Directory '{}' and all its contents deleted successfully" , path)
647+ } ) )
648+ } ,
649+ Err ( e) => Ok ( json ! ( {
650+ "status" : "error" ,
651+ "message" : format!( "Failed to delete directory: {}" , e)
652+ } ) ) ,
653+ }
654+ }
655+ }
0 commit comments