use saya_agent::ToolError;
use super::DatabaseTools;
pub(crate) const WORKSPACE_GREP_MAX_VISITED: usize = 2_000;
pub(crate) const WORKSPACE_GREP_MAX_MATCHES: usize = 500;
pub(crate) const WORKSPACE_GREP_MAX_LINE_BYTES: usize = 2_000;
pub(crate) const WORKSPACE_GREP_MAX_FILE_BYTES: u64 =
saya_harness::workspace::patch::PATCH_MAX_FILE_BYTES;
impl DatabaseTools {
pub(super) async fn workspace_grep(
&self,
arguments: &serde_json::Value,
) -> Result<serde_json::Value, ToolError> {
let needle = arguments
.get("pattern")
.and_then(serde_json::Value::as_str)
.ok_or(ToolError::PatternNotString)?;
let case_insensitive = match arguments.get("case_insensitive") {
Some(value) => value.as_bool().ok_or(ToolError::CaseInsensitiveNotBool)?,
None => false,
};
let Some(workspace) = &self.workspace else {
return Err(ToolError::WorkspaceUnavailable);
};
let outcome = workspace
.grep(
needle,
case_insensitive,
WORKSPACE_GREP_MAX_VISITED,
WORKSPACE_GREP_MAX_MATCHES,
WORKSPACE_GREP_MAX_FILE_BYTES,
WORKSPACE_GREP_MAX_LINE_BYTES,
)
.map_err(|error| ToolError::Workspace(error.to_string()))?;
let hits: Vec<serde_json::Value> = outcome
.matches
.iter()
.map(|hit| {
serde_json::json!({
"path": hit.path,
"line": hit.line,
"text": hit.text,
"truncated": hit.truncated,
})
})
.collect();
Ok(serde_json::json!({
"matches": hits,
"files_scanned": outcome.files_scanned,
"files_skipped": outcome.files_skipped,
}))
}
}