| 61 | } |
| 62 | |
| 63 | async fn execute(&self, args: &serde_json::Value, ctx: &ToolContext) -> Result<ToolOutput> { |
| 64 | let pattern_str = match args.get("pattern").and_then(|v| v.as_str()) { |
| 65 | Some(p) => p, |
| 66 | None => return Ok(ToolOutput::error("pattern parameter is required")), |
| 67 | }; |
| 68 | |
| 69 | let case_insensitive = args.get("-i").and_then(|v| v.as_bool()).unwrap_or(false); |
| 70 | |
| 71 | let regex_pattern = if case_insensitive { |
| 72 | format!("(?i){}", pattern_str) |
| 73 | } else { |
| 74 | pattern_str.to_string() |
| 75 | }; |
| 76 | |
| 77 | if let Err(e) = Regex::new(®ex_pattern) { |
| 78 | return Ok(ToolOutput::error(format!( |
| 79 | "Invalid regex pattern '{}': {}", |
| 80 | pattern_str, e |
| 81 | ))); |
| 82 | } |
| 83 | |
| 84 | let path_str = args.get("path").and_then(|v| v.as_str()).unwrap_or("."); |
| 85 | let base = match ctx.resolve_workspace_path(path_str) { |
| 86 | Ok(path) => path, |
| 87 | Err(e) => return Ok(ToolOutput::error(format!("Failed to resolve path: {}", e))), |
| 88 | }; |
| 89 | |
| 90 | let glob_filter = args.get("glob").and_then(|v| v.as_str()); |
| 91 | let context_lines = args.get("context").and_then(|v| v.as_u64()).unwrap_or(0) as usize; |
| 92 | |
| 93 | let Some(search) = ctx.workspace_services.search() else { |
| 94 | return Ok(ToolOutput::error( |
| 95 | "grep is not available: this workspace backend did not provide search", |
| 96 | )); |
| 97 | }; |
| 98 | let request = WorkspaceGrepRequest { |
| 99 | base, |
| 100 | pattern: pattern_str.to_string(), |
| 101 | glob: glob_filter.map(str::to_string), |
| 102 | context_lines, |
| 103 | case_insensitive, |
| 104 | max_output_size: MAX_OUTPUT_SIZE, |
| 105 | }; |
| 106 | let result = match ctx |
| 107 | .workspace_services |
| 108 | .run_with_timeout("grep", async move { search.grep(request).await }) |
| 109 | .await |
| 110 | { |
| 111 | Ok(result) => result, |
| 112 | Err(e) => return Ok(ToolOutput::error(format!("Grep search failed: {}", e))), |
| 113 | }; |
| 114 | |
| 115 | if result.match_count == 0 { |
| 116 | Ok(ToolOutput::success(format!( |
| 117 | "No matches found for pattern: {}", |
| 118 | pattern_str |
| 119 | ))) |
| 120 | } else if result.truncated { |