coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
//! Tools module for CoderLib
//!
//! This module provides the tool system that allows AI agents to perform
//! various operations like file manipulation, shell commands, and code analysis.

pub mod file_ops;
pub mod shell;
pub mod search;
pub mod code_analysis;
pub mod git;
pub mod project_structure;
pub mod diagnostics;
pub mod completion;
pub mod hover;
pub mod goto_definition;
pub mod edit;
pub mod patch;
pub mod fetch;
pub mod glob;
pub mod router;
pub mod calculator;

use async_trait::async_trait;
// // use serde::{Deserialize, Serialize};
use std::path::PathBuf;

use crate::integration::HostIntegration;

/// Trait for tools that can be executed by AI agents
#[async_trait]
pub trait Tool: Send + Sync {
    /// Execute the tool with the given parameters
    async fn execute(
        &self,
        parameters: serde_json::Value,
        host: &dyn HostIntegration,
    ) -> Result<ToolResponse, ToolError>;
    
    /// Get the permission required to execute this tool
    fn requires_permission(&self) -> Permission;
    
    /// Get a description of what this tool does
    fn description(&self) -> &str;
    
    /// Get the tool's name/identifier
    fn name(&self) -> &str;
    
    /// Get the JSON schema for the tool's parameters
    fn parameter_schema(&self) -> serde_json::Value;
    
    /// Clone the tool (for use in collections)
    fn clone_box(&self) -> Box<dyn Tool>;
}

/// Response from tool execution
#[derive(Debug, Clone)]
pub struct ToolResponse {
    /// The main content/result of the tool execution
    pub content: String,
    
    /// Whether the tool execution was successful
    pub success: bool,
    
    /// Additional metadata about the execution
    pub metadata: serde_json::Value,
    
    /// Files that were created or modified
    pub affected_files: Vec<PathBuf>,
}

/// Permissions required for tool execution
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Permission {
    /// Read files from the filesystem
    ReadFile(PathBuf),
    
    /// Write files to the filesystem
    WriteFile(PathBuf),
    
    /// Execute shell commands
    ExecuteShell,
    
    /// Access network resources
    NetworkAccess,
    
    /// Modify system settings
    SystemModification,
    
    /// Access environment variables
    EnvironmentAccess,
    
    /// No special permission required
    None,
}

/// Error types for tool operations
#[derive(Debug, thiserror::Error)]
pub enum ToolError {
    #[error("Tool not found: {0}")]
    NotFound(String),
    
    #[error("Permission denied: {0}")]
    PermissionDenied(String),
    
    #[error("Execution failed: {0}")]
    ExecutionFailed(String),
    
    #[error("Invalid parameters: {0}")]
    InvalidParameters(String),
    
    #[error("Timeout: {0}")]
    Timeout(String),
    
    #[error("Tool unavailable: {0}")]
    Unavailable(String),
    
    #[error("Security violation: {0}")]
    SecurityViolation(String),
    
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("Git error: {0}")]
    Git(#[from] git2::Error),
}

/// Registry for managing available tools (both legacy and enhanced)
pub struct ToolRegistry {
    tools: std::collections::HashMap<String, Box<dyn Tool>>,
    enhanced_router: router::ToolRouter,
}

impl ToolRegistry {
    /// Create a new tool registry
    pub fn new() -> Self {
        Self {
            tools: std::collections::HashMap::new(),
            enhanced_router: router::ToolRouter::new(),
        }
    }

    /// Register a legacy tool
    pub fn register<T: Tool + 'static>(&mut self, tool: T) {
        self.tools.insert(tool.name().to_string(), Box::new(tool));
    }

    /// Register an enhanced tool
    pub fn register_enhanced<T: router::EnhancedTool + Clone + 'static>(&mut self, tool: T) {
        // Create adapter first for backward compatibility
        let name = tool.name().to_string();
        let adapter = router::EnhancedToolAdapter::new(tool.clone());

        // Register in enhanced router
        self.enhanced_router.register(tool);

        // Also register as legacy tool via adapter
        self.tools.insert(name, Box::new(adapter));
    }

    /// Get a tool by name (legacy interface)
    pub fn get(&self, name: &str) -> Option<&dyn Tool> {
        self.tools.get(name).map(|t| t.as_ref())
    }

    /// Get an enhanced tool by name
    pub fn get_enhanced(&self, name: &str) -> Option<&dyn router::EnhancedTool> {
        self.enhanced_router.get(name)
    }

    /// Get the enhanced tool router
    pub fn enhanced_router(&self) -> &router::ToolRouter {
        &self.enhanced_router
    }

    /// List all registered tools (both legacy and enhanced)
    pub fn list_tools(&self) -> Vec<&str> {
        let mut tools: Vec<&str> = self.tools.keys().map(|s| s.as_str()).collect();
        tools.sort();
        tools.dedup();
        tools
    }

    /// Get tool schemas for all enhanced tools
    pub fn get_tool_schemas(&self) -> std::collections::HashMap<String, router::ToolSchema> {
        self.enhanced_router.get_tool_schemas()
    }

    /// Get tools that require a specific permission
    pub fn tools_requiring_permission(&self, permission: &Permission) -> Vec<&str> {
        self.tools
            .iter()
            .filter(|(_, tool)| tool.requires_permission() == *permission)
            .map(|(name, _)| name.as_str())
            .collect()
    }
    
    /// Create a default registry with standard tools
    pub fn with_default_tools() -> Self {
        let mut registry = Self::new();

        // Register file operation tools
        registry.register(file_ops::FileReadTool::new());
        registry.register(file_ops::FileWriteTool::new());
        registry.register(file_ops::FileListTool::new());

        // Register shell tools
        registry.register(shell::ShellCommandTool::new());

        // Register search tools
        registry.register(search::FileSearchTool::new());
        registry.register(search::ContentSearchTool::new());
        registry.register(search::GrepTool::new());

        // Register advanced analysis tools
        if let Ok(code_analysis) = code_analysis::CodeAnalysisTool::new() {
            registry.register(code_analysis);
        }
        registry.register(git::GitTool::new());
        registry.register(project_structure::ProjectStructureTool::new());
        registry.register(diagnostics::DiagnosticsTool::new());
        registry.register(completion::CompletionTool::new());
        registry.register(hover::HoverTool::new());
        registry.register(goto_definition::GotoDefinitionTool::new());

        // Register advanced tools
        registry.register(edit::EditTool::new());
        registry.register(patch::PatchTool::new());
        if let Ok(fetch_tool) = fetch::FetchTool::new() {
            registry.register(fetch_tool);
        }
        registry.register(glob::GlobTool::new());

        // Register enhanced tools (demonstrating new patterns)
        registry.register_enhanced(calculator::CalculatorTool::new());

        registry
    }
}

impl Default for ToolRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl ToolResponse {
    /// Create a successful tool response
    pub fn success(content: String) -> Self {
        Self {
            content,
            success: true,
            metadata: serde_json::Value::Null,
            affected_files: Vec::new(),
        }
    }
    
    /// Create a failed tool response
    pub fn failure(error: String) -> Self {
        Self {
            content: error,
            success: false,
            metadata: serde_json::Value::Null,
            affected_files: Vec::new(),
        }
    }
    
    /// Create a response with metadata
    pub fn with_metadata(content: String, metadata: serde_json::Value) -> Self {
        Self {
            content,
            success: true,
            metadata,
            affected_files: Vec::new(),
        }
    }
    
    /// Create a response with affected files
    pub fn with_files(content: String, files: Vec<PathBuf>) -> Self {
        Self {
            content,
            success: true,
            metadata: serde_json::Value::Null,
            affected_files: files,
        }
    }
}

impl Permission {
    /// Check if this permission allows access to a specific path
    pub fn allows_path(&self, path: &PathBuf) -> bool {
        match self {
            Permission::ReadFile(allowed_path) | Permission::WriteFile(allowed_path) => {
                path.starts_with(allowed_path) || allowed_path.starts_with(path)
            }
            Permission::None => true,
            _ => false,
        }
    }
    
    /// Check if this permission is more restrictive than another
    pub fn is_more_restrictive_than(&self, other: &Permission) -> bool {
        match (self, other) {
            (Permission::None, _) => false,
            (_, Permission::None) => true,
            (Permission::ReadFile(_), Permission::WriteFile(_)) => true,
            (Permission::ReadFile(_), Permission::ExecuteShell) => true,
            (Permission::ReadFile(_), Permission::NetworkAccess) => true,
            (Permission::ReadFile(_), Permission::SystemModification) => true,
            _ => false,
        }
    }
    
    /// Get a human-readable description of the permission
    pub fn description(&self) -> String {
        match self {
            Permission::ReadFile(path) => format!("Read file: {}", path.display()),
            Permission::WriteFile(path) => format!("Write file: {}", path.display()),
            Permission::ExecuteShell => "Execute shell commands".to_string(),
            Permission::NetworkAccess => "Access network resources".to_string(),
            Permission::SystemModification => "Modify system settings".to_string(),
            Permission::EnvironmentAccess => "Access environment variables".to_string(),
            Permission::None => "No special permission required".to_string(),
        }
    }
}

/// Utility functions for tool parameter validation
pub mod validation {
    use super::*;
    
    /// Validate that a parameter exists and is a string
    pub fn require_string(params: &serde_json::Value, key: &str) -> Result<String, ToolError> {
        params
            .get(key)
            .and_then(|v| v.as_str())
            .map(|s| s.to_string())
            .ok_or_else(|| ToolError::InvalidParameters(format!("Missing or invalid parameter: {}", key)))
    }
    
    /// Validate that a parameter exists and is a valid path
    pub fn require_path(params: &serde_json::Value, key: &str) -> Result<PathBuf, ToolError> {
        let path_str = require_string(params, key)?;
        Ok(PathBuf::from(path_str))
    }
    
    /// Validate that a parameter exists and is a boolean
    pub fn require_bool(params: &serde_json::Value, key: &str) -> Result<bool, ToolError> {
        params
            .get(key)
            .and_then(|v| v.as_bool())
            .ok_or_else(|| ToolError::InvalidParameters(format!("Missing or invalid parameter: {}", key)))
    }
    
    /// Get an optional string parameter
    pub fn optional_string(params: &serde_json::Value, key: &str) -> Option<String> {
        params.get(key).and_then(|v| v.as_str()).map(|s| s.to_string())
    }
    
    /// Get an optional path parameter
    pub fn optional_path(params: &serde_json::Value, key: &str) -> Option<PathBuf> {
        optional_string(params, key).map(PathBuf::from)
    }
    
    /// Validate that a path is safe to access
    pub fn validate_safe_path(path: &PathBuf) -> Result<(), ToolError> {
        // Check for path traversal attempts
        if path.to_string_lossy().contains("..") {
            return Err(ToolError::SecurityViolation(
                "Path traversal not allowed".to_string()
            ));
        }
        
        // Check for absolute paths to sensitive directories
        let sensitive_dirs = [
            "/etc", "/sys", "/proc", "/dev",
            "C:\\Windows", "C:\\System32", "C:\\Program Files",
        ];
        
        for sensitive in &sensitive_dirs {
            if path.starts_with(sensitive) {
                return Err(ToolError::SecurityViolation(
                    format!("Access to {} is not allowed", sensitive)
                ));
            }
        }
        
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tool_response_creation() {
        let success = ToolResponse::success("Operation completed".to_string());
        assert!(success.success);
        assert_eq!(success.content, "Operation completed");
        
        let failure = ToolResponse::failure("Operation failed".to_string());
        assert!(!failure.success);
        assert_eq!(failure.content, "Operation failed");
    }

    #[test]
    fn test_permission_path_checking() {
        let read_permission = Permission::ReadFile(PathBuf::from("/home/user"));
        assert!(read_permission.allows_path(&PathBuf::from("/home/user/file.txt")));
        assert!(!read_permission.allows_path(&PathBuf::from("/etc/passwd")));
    }

    #[test]
    fn test_permission_description() {
        let permission = Permission::ExecuteShell;
        assert_eq!(permission.description(), "Execute shell commands");
        
        let file_permission = Permission::ReadFile(PathBuf::from("/test"));
        assert!(file_permission.description().contains("/test"));
    }

    #[test]
    fn test_tool_registry() {
        let registry = ToolRegistry::new();
        assert_eq!(registry.list_tools().len(), 0);
        
        // Note: We can't test with actual tools here since they're not implemented yet
        // This test will be expanded when we implement the actual tools
    }

    #[test]
    fn test_validation_functions() {
        use validation::*;
        
        let params = serde_json::json!({
            "name": "test",
            "path": "/home/user/file.txt",
            "enabled": true
        });
        
        assert_eq!(require_string(&params, "name").unwrap(), "test");
        assert_eq!(require_path(&params, "path").unwrap(), PathBuf::from("/home/user/file.txt"));
        assert_eq!(require_bool(&params, "enabled").unwrap(), true);
        
        assert!(require_string(&params, "missing").is_err());
        assert!(optional_string(&params, "missing").is_none());
    }

    #[test]
    fn test_path_validation() {
        use validation::validate_safe_path;
        
        assert!(validate_safe_path(&PathBuf::from("safe/path")).is_ok());
        assert!(validate_safe_path(&PathBuf::from("../dangerous")).is_err());
        assert!(validate_safe_path(&PathBuf::from("/etc/passwd")).is_err());
    }
}