use crate::common::Result;
use serde::Deserialize;
use tokio::process::Command;
use tokio::time::{Duration, timeout};
#[derive(Debug, Deserialize)]
pub struct SearchInput {
pub pattern: String,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub glob: Option<String>,
#[serde(default = "default_max_results")]
pub max_results: usize,
}
fn default_max_results() -> usize {
50
}
pub fn definition() -> serde_json::Value {
serde_json::json!({
"type": "function",
"function": {
"name": "search",
"description": "Search for a regex pattern in files using ripgrep. Returns matching lines with file paths and line numbers.",
"parameters": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "Regex pattern to search for"
},
"path": {
"type": "string",
"description": "Directory or file to search in (default: working directory)"
},
"glob": {
"type": "string",
"description": "Glob pattern to filter files (e.g., '*.rs', '*.py')"
},
"max_results": {
"type": "integer",
"description": "Maximum number of results (default: 50)"
}
},
"required": ["pattern"]
}
}
})
}
pub async fn execute(input: SearchInput, working_dir: &str) -> Result<String> {
let search_path = input.path.unwrap_or_else(|| working_dir.to_string());
let mut cmd = Command::new("rg");
cmd.arg("--line-number")
.arg("--no-heading")
.arg("--color=never")
.arg("--max-count")
.arg(input.max_results.to_string());
if let Some(ref glob) = input.glob {
cmd.arg("--glob").arg(glob);
}
cmd.arg(&input.pattern).arg(&search_path);
cmd.current_dir(working_dir);
let result = timeout(Duration::from_secs(30), cmd.output()).await;
match result {
Ok(Ok(output)) => {
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.is_empty() {
Ok("No matches found.".to_string())
} else {
Ok(stdout.to_string())
}
}
Ok(Err(_)) => {
let mut cmd = Command::new("grep");
cmd.arg("-rn").arg("--color=never");
if let Some(ref glob) = input.glob {
cmd.arg("--include").arg(glob);
}
cmd.arg(&input.pattern).arg(&search_path);
cmd.current_dir(working_dir);
let output = cmd.output().await?;
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.is_empty() {
Ok("No matches found.".to_string())
} else {
let lines: Vec<&str> = stdout.lines().take(input.max_results).collect();
Ok(lines.join("\n"))
}
}
Err(_) => Err(crate::common::AgentError::Timeout(30)),
}
}