use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ToolError {
#[error("File not found: {path}")]
FileNotFound { path: String },
#[error("Invalid arguments: {message}")]
InvalidArgs { message: String },
#[error("Tool not found: {name}")]
ToolNotFound { name: String },
#[error("Linting failed: {errors:?}")]
LintingFailed { errors: Vec<String> },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Regex error: {0}")]
Regex(#[from] regex::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolArgs {
pub args: Vec<String>,
pub named_args: HashMap<String, String>,
}
impl ToolArgs {
pub fn from_args(args: &[&str]) -> Self {
let mut positional = Vec::new();
let mut named = HashMap::new();
for &arg in args {
if arg.starts_with("--") {
if let Some(eq) = arg.find('=') {
let key = arg[2..eq].to_string();
let value = arg[eq + 1..].to_string();
named.insert(key, value);
} else {
let key = arg[2..].to_string();
named.insert(key, "true".to_string());
}
} else {
positional.push(arg.to_string());
}
}
Self {
args: positional,
named_args: named,
}
}
pub fn with_named_args(args: Vec<String>, named_args: HashMap<String, String>) -> Self {
Self { args, named_args }
}
pub fn get_arg(&self, index: usize) -> Option<&String> {
self.args.get(index)
}
pub fn get_named_arg(&self, name: &str) -> Option<&String> {
self.named_args.get(name)
}
pub fn len(&self) -> usize {
self.args.len()
}
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
pub success: bool,
pub message: String,
pub data: Option<serde_json::Value>,
}
impl ToolResult {
pub fn success(message: impl Into<String>) -> Self {
Self {
success: true,
message: message.into(),
data: None,
}
}
pub fn success_with_data(message: impl Into<String>, data: serde_json::Value) -> Self {
Self {
success: true,
message: message.into(),
data: Some(data),
}
}
pub fn error(message: impl Into<String>) -> Self {
Self {
success: false,
message: message.into(),
data: None,
}
}
pub fn error_with_data(message: impl Into<String>, data: serde_json::Value) -> Self {
Self {
success: false,
message: message.into(),
data: Some(data),
}
}
}
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn signature(&self) -> &str;
fn validate_args(&self, args: &ToolArgs) -> Result<(), ToolError>;
fn execute(
&mut self,
args: &ToolArgs,
state: &Arc<Mutex<crate::state::ToolState>>,
) -> Result<ToolResult>;
fn get_openai_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "function",
"function": {
"name": self.name(),
"description": self.description(),
"parameters": self.get_parameters_schema()
}
})
}
fn get_parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {},
"required": []
})
}
}
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn Tool>>,
state: Arc<Mutex<crate::state::ToolState>>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
state: Arc::new(Mutex::new(crate::state::ToolState::new())),
}
}
pub fn register(&mut self, tool: Box<dyn Tool>) {
let name = tool.name().to_string();
self.tools.insert(name, tool);
}
pub fn execute_tool(&mut self, name: &str, args: &ToolArgs) -> Result<ToolResult, ToolError> {
let tool = self
.tools
.get_mut(name)
.ok_or_else(|| ToolError::ToolNotFound {
name: name.to_string(),
})?;
tool.validate_args(args)?;
tool.execute(args, &self.state)
.map_err(|e| ToolError::InvalidArgs {
message: e.to_string(),
})
}
pub fn list_tools(&self) -> Vec<String> {
self.tools.keys().cloned().collect()
}
pub fn get_tool(&self, name: &str) -> Option<&dyn Tool> {
self.tools.get(name).map(|t| t.as_ref())
}
pub fn get_all_schemas(&self) -> Vec<serde_json::Value> {
self.tools
.values()
.map(|tool| tool.get_openai_schema())
.collect()
}
pub fn get_state(&self) -> Arc<Mutex<crate::state::ToolState>> {
Arc::clone(&self.state)
}
pub fn set_working_directory(&mut self, path: std::path::PathBuf) {
if let Ok(mut state) = self.state.lock() {
state.working_directory = path;
}
}
pub fn get_working_directory(&self) -> std::path::PathBuf {
self.state
.lock()
.map(|s| s.working_directory.clone())
.unwrap_or_else(|_| std::path::PathBuf::from("."))
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockTool {
name: String,
}
impl MockTool {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
}
impl Tool for MockTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Mock tool for testing"
}
fn signature(&self) -> &str {
"mock_tool <arg>"
}
fn validate_args(&self, args: &ToolArgs) -> Result<(), ToolError> {
if args.is_empty() {
return Err(ToolError::InvalidArgs {
message: "Mock tool requires at least one argument".to_string(),
});
}
Ok(())
}
fn execute(
&mut self,
args: &ToolArgs,
_state: &Arc<Mutex<crate::state::ToolState>>,
) -> Result<ToolResult> {
Ok(ToolResult::success(format!(
"Mock tool {} executed with {} args",
self.name,
args.len()
)))
}
}
#[test]
fn test_tool_args_creation() {
let args = ToolArgs::from_args(&["arg1", "arg2"]);
assert_eq!(args.len(), 2);
assert_eq!(args.get_arg(0), Some(&"arg1".to_string()));
assert_eq!(args.get_arg(1), Some(&"arg2".to_string()));
}
#[test]
fn test_tool_result_creation() {
let result = ToolResult::success("Test message");
assert!(result.success);
assert_eq!(result.message, "Test message");
assert!(result.data.is_none());
let error_result = ToolResult::error("Error message");
assert!(!error_result.success);
assert_eq!(error_result.message, "Error message");
}
#[test]
fn test_tool_registry() {
let mut registry = ToolRegistry::new();
let mock_tool = Box::new(MockTool::new("test_tool"));
registry.register(mock_tool);
let tools = registry.list_tools();
assert!(tools.contains(&"test_tool".to_string()));
let args = ToolArgs::from_args(&["test_arg"]);
let result = registry.execute_tool("test_tool", &args);
assert!(result.is_ok());
assert!(result.unwrap().success);
let result = registry.execute_tool("nonexistent", &args);
assert!(result.is_err());
}
#[test]
fn test_tool_validation() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(MockTool::new("test_tool")));
let empty_args = ToolArgs::from_args(&[]);
let result = registry.execute_tool("test_tool", &empty_args);
assert!(result.is_err());
}
}