forked from kent/consciousness
Tool definitions are now &'static str (name, description, parameters_json) instead of runtime-constructed serde_json::Value. No more json!() macro, no more ToolDef::new() for tool definitions. The JSON schema strings are written directly as string literals. When sent to the API, they can be interpolated without serialization/deserialization. Multi-tool modules return fixed-size arrays instead of Vecs: - memory: [Tool; 12], journal: [Tool; 3] - channels: [Tool; 4] - control: [Tool; 3] - web: [Tool; 2] ToolDef/FunctionDef remain for backward compat (API wire format, summarize_args) but are no longer used in tool definitions. Co-Authored-By: Proof of Concept <poc@bcachefs.org> Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
101 lines
3.3 KiB
Rust
101 lines
3.3 KiB
Rust
// tools/grep.rs — Search file contents
|
|
//
|
|
// Prefers ripgrep (rg) for speed, falls back to grep -r if rg
|
|
// isn't installed. Both produce compatible output.
|
|
|
|
use anyhow::{Context, Result};
|
|
use serde::Deserialize;
|
|
use std::process::Command;
|
|
|
|
#[derive(Deserialize)]
|
|
struct Args {
|
|
pattern: String,
|
|
#[serde(default = "default_path")]
|
|
path: String,
|
|
glob: Option<String>,
|
|
#[serde(default)]
|
|
show_content: bool,
|
|
context_lines: Option<u64>,
|
|
}
|
|
|
|
fn default_path() -> String { ".".into() }
|
|
|
|
pub fn tool() -> super::Tool {
|
|
super::Tool {
|
|
name: "grep",
|
|
description: "Search for a pattern in files. Returns matching file paths by default, or matching lines with context.",
|
|
parameters_json: r#"{"type":"object","properties":{"pattern":{"type":"string","description":"Regex pattern to search for"},"path":{"type":"string","description":"Directory or file to search in (default: current directory)"},"glob":{"type":"string","description":"Glob pattern to filter files (e.g. '*.rs')"},"show_content":{"type":"boolean","description":"Show matching lines instead of just file paths"},"context_lines":{"type":"integer","description":"Lines of context around matches"}},"required":["pattern"]}"#,
|
|
handler: |_a, v| Box::pin(async move { grep(&v) }),
|
|
}
|
|
}
|
|
|
|
/// Check if ripgrep is available (cached after first check).
|
|
fn has_rg() -> bool {
|
|
use std::sync::OnceLock;
|
|
static HAS_RG: OnceLock<bool> = OnceLock::new();
|
|
*HAS_RG.get_or_init(|| Command::new("rg").arg("--version").output().is_ok())
|
|
}
|
|
|
|
fn grep(args: &serde_json::Value) -> Result<String> {
|
|
let a: Args = serde_json::from_value(args.clone())
|
|
.context("invalid grep arguments")?;
|
|
|
|
let output = if has_rg() {
|
|
run_search("rg", &a.pattern, &a.path, a.glob.as_deref(), a.show_content, a.context_lines, true)?
|
|
} else {
|
|
run_search("grep", &a.pattern, &a.path, a.glob.as_deref(), a.show_content, a.context_lines, false)?
|
|
};
|
|
|
|
if output.is_empty() {
|
|
return Ok("No matches found.".to_string());
|
|
}
|
|
|
|
Ok(super::truncate_output(output, 30000))
|
|
}
|
|
|
|
/// Run a grep/rg search. Unified implementation for both tools.
|
|
fn run_search(
|
|
tool: &str,
|
|
pattern: &str,
|
|
path: &str,
|
|
file_glob: Option<&str>,
|
|
show_content: bool,
|
|
context: Option<u64>,
|
|
use_rg: bool,
|
|
) -> Result<String> {
|
|
let mut cmd = Command::new(tool);
|
|
|
|
if use_rg {
|
|
// ripgrep args
|
|
if show_content {
|
|
cmd.arg("-n");
|
|
if let Some(c) = context {
|
|
cmd.arg("-C").arg(c.to_string());
|
|
}
|
|
} else {
|
|
cmd.arg("--files-with-matches");
|
|
}
|
|
if let Some(g) = file_glob {
|
|
cmd.arg("--glob").arg(g);
|
|
}
|
|
} else {
|
|
// grep args
|
|
cmd.arg("-r"); // recursive
|
|
if show_content {
|
|
cmd.arg("-n"); // line numbers
|
|
if let Some(c) = context {
|
|
cmd.arg("-C").arg(c.to_string());
|
|
}
|
|
} else {
|
|
cmd.arg("-l"); // files-with-matches
|
|
}
|
|
if let Some(g) = file_glob {
|
|
cmd.arg("--include").arg(g);
|
|
}
|
|
cmd.arg("-E"); // extended regex
|
|
}
|
|
|
|
cmd.arg(pattern).arg(path);
|
|
let output = cmd.output().with_context(|| format!("Failed to run {}", tool))?;
|
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
|
}
|