mod engine;
mod matcher;
mod reader;
use super::{
ToolResult, ToolResultDisplay, ToolRuntime,
args::{GrepArgs, GrepMode},
contract::tool_name,
};
use crate::cancellation::AgentCancellation;
use serde_json::json;
impl ToolRuntime {
pub(super) fn grep(
&self,
args: GrepArgs,
cancellation: &AgentCancellation,
) -> anyhow::Result<ToolResult> {
let path_arg = args.path.clone().unwrap_or_else(|| ".".into());
let path = self.resolve_existing_path(
&path_arg,
super::fs::ExistingPathPolicy::grep(self.grep_absolute_paths),
)?;
cancellation.check()?;
let mode = args.mode.unwrap_or(GrepMode::Ranked);
let raw = mode == GrepMode::Raw || args.pattern.is_some();
let limit = args.limit.unwrap_or(crate::tools::args::GREP_DEFAULT_LIMIT);
let offset = args.offset.unwrap_or(0);
let context = args.context.unwrap_or(0);
let scan = engine::run(engine::GrepRequest {
cwd: &self.cwd_canonical,
walker: &self.workspace_walker,
patterns: args
.patterns
.as_ref()
.ok_or_else(|| anyhow::anyhow!("patterns required"))?,
root: &path,
limit,
offset,
context,
raw,
cancellation,
})?;
let pattern_count = args.patterns.as_ref().map_or(0, Vec::len);
let mut metadata = json!({
"engine": "native",
"patterns_count": pattern_count,
"mode": if raw { "raw" } else { "ranked" },
"offset": offset,
"limit": limit,
"output_format": if raw { "raw_v1" } else { "grouped_v1" },
"order": if raw { "path_line_v1" } else { "ranked_v1" },
"matches_returned": scan.matches_returned,
"output_lines_returned": scan.output_lines_returned,
"truncated": scan.truncated,
"timed_out": scan.timed_out,
"files_scanned": scan.files_scanned,
"bytes_scanned": scan.bytes_scanned,
"byte_limit_reached": scan.byte_limit_reached,
"scan_byte_limit_reached": scan.scan_byte_limit_reached,
"file_limit_reached": scan.file_limit_reached,
"match_limit_reached": scan.match_limit_reached,
"per_file_limit_reached": scan.per_file_limit_reached,
"files_with_matches": scan.files_with_matches,
"files_returned": scan.files_returned,
"invalid_utf8_files": scan.invalid_utf8_files,
"invalid_utf8_lines": scan.invalid_utf8_lines,
"read_errors_skipped": scan.read_errors_skipped,
"walk_errors": scan.walk_errors,
"walk_entries_omitted": scan.walk_entries_omitted,
"binary_files_skipped": scan.binary_files_skipped,
"scan_complete": scan.scan_complete,
"matches_seen": scan.matches_seen,
"matches_seen_exact": scan.matches_seen_exact,
"output_bytes": scan.output_bytes,
"output_byte_limit_reached": scan.output_byte_limit_reached,
"context_lines_omitted": scan.context_lines_omitted,
"patterns_adjusted": scan.patterns_adjusted,
"literal_fallbacks": scan.literal_fallbacks,
"raw_lookahead_reached": scan.raw_lookahead_reached,
"truncation_reasons": scan.truncation_reasons,
"limit_reason": scan.truncation_reasons.first().copied(),
});
metadata["next_offset"] = scan
.next_offset
.map_or(serde_json::Value::Null, |v| json!(v));
metadata["has_more"] = scan.has_more.map_or(serde_json::Value::Null, |v| json!(v));
Ok(ToolResult {
tool_name: tool_name::GREP.into(),
success: true,
content: scan.lines.join("\n"),
metadata,
display: ToolResultDisplay::default(),
})
}
}