Files

244 lines
6.9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace AxCopilot.Services.Agent;
public class GrepTool : IAgentTool
{
public string Name => "grep";
public string Description => "Search file contents for a pattern (regex supported). Returns matching lines with file paths and line numbers.";
public ToolParameterSchema Parameters
{
get
{
ToolParameterSchema obj = new ToolParameterSchema
{
Properties = new Dictionary<string, ToolProperty>
{
["pattern"] = new ToolProperty
{
Type = "string",
Description = "Search pattern (regex supported)"
},
["path"] = new ToolProperty
{
Type = "string",
Description = "File or directory to search in. Optional, defaults to work folder."
},
["glob"] = new ToolProperty
{
Type = "string",
Description = "File pattern filter (e.g. '*.cs', '*.json'). Optional."
},
["context_lines"] = new ToolProperty
{
Type = "integer",
Description = "Number of context lines before/after each match (0-5). Default 0."
},
["case_sensitive"] = new ToolProperty
{
Type = "boolean",
Description = "Case-sensitive search. Default false (case-insensitive)."
}
}
};
int num = 1;
List<string> list = new List<string>(num);
CollectionsMarshal.SetCount(list, num);
CollectionsMarshal.AsSpan(list)[0] = "pattern";
obj.Required = list;
return obj;
}
}
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct)
{
string text = args.GetProperty("pattern").GetString() ?? "";
JsonElement value;
string text2 = (args.TryGetProperty("path", out value) ? (value.GetString() ?? "") : "");
JsonElement value2;
string text3 = (args.TryGetProperty("glob", out value2) ? (value2.GetString() ?? "") : "");
JsonElement value3;
int num = (args.TryGetProperty("context_lines", out value3) ? Math.Clamp(value3.GetInt32(), 0, 5) : 0);
JsonElement value4;
bool flag = args.TryGetProperty("case_sensitive", out value4) && value4.GetBoolean();
string text4 = (string.IsNullOrEmpty(text2) ? context.WorkFolder : FileReadTool.ResolvePath(text2, context.WorkFolder));
if (string.IsNullOrEmpty(text4))
{
return Task.FromResult(ToolResult.Fail("작업 폴더가 설정되지 않았습니다."));
}
try
{
RegexOptions options = (RegexOptions)(8 | ((!flag) ? 1 : 0));
Regex regex = new Regex(text, options, TimeSpan.FromSeconds(5.0));
string searchPattern = (string.IsNullOrEmpty(text3) ? "*" : text3);
IEnumerable<string> enumerable;
if (File.Exists(text4))
{
enumerable = new _003C_003Ez__ReadOnlySingleElementList<string>(text4);
}
else
{
if (!Directory.Exists(text4))
{
return Task.FromResult(ToolResult.Fail("경로가 존재하지 않습니다: " + text4));
}
enumerable = Directory.EnumerateFiles(text4, searchPattern, SearchOption.AllDirectories);
}
StringBuilder stringBuilder = new StringBuilder();
int num2 = 0;
int num3 = 0;
foreach (string item in enumerable)
{
if (ct.IsCancellationRequested)
{
break;
}
if (!context.IsPathAllowed(item) || IsBinaryFile(item))
{
continue;
}
try
{
string[] array = File.ReadAllLines(item, Encoding.UTF8);
bool flag2 = false;
for (int i = 0; i < array.Length; i++)
{
if (num2 >= 100)
{
break;
}
if (!regex.IsMatch(array[i]))
{
continue;
}
StringBuilder stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler;
if (!flag2)
{
string value5 = (Directory.Exists(context.WorkFolder) ? Path.GetRelativePath(context.WorkFolder, item) : item);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2);
handler.AppendLiteral("\n");
handler.AppendFormatted(value5);
handler.AppendLiteral(":");
stringBuilder3.AppendLine(ref handler);
flag2 = true;
num3++;
}
if (num > 0)
{
for (int j = Math.Max(0, i - num); j < i; j++)
{
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(4, 2, stringBuilder2);
handler.AppendLiteral(" ");
handler.AppendFormatted(j + 1);
handler.AppendLiteral(" ");
handler.AppendFormatted(array[j].TrimEnd());
stringBuilder4.AppendLine(ref handler);
}
}
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(4, 2, stringBuilder2);
handler.AppendLiteral(" ");
handler.AppendFormatted(i + 1);
handler.AppendLiteral(": ");
handler.AppendFormatted(array[i].TrimEnd());
stringBuilder5.AppendLine(ref handler);
if (num > 0)
{
for (int k = i + 1; k <= Math.Min(array.Length - 1, i + num); k++)
{
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder6 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(4, 2, stringBuilder2);
handler.AppendLiteral(" ");
handler.AppendFormatted(k + 1);
handler.AppendLiteral(" ");
handler.AppendFormatted(array[k].TrimEnd());
stringBuilder6.AppendLine(ref handler);
}
stringBuilder.AppendLine(" ---");
}
num2++;
}
}
catch
{
}
if (num2 < 100)
{
continue;
}
break;
}
if (num2 == 0)
{
return Task.FromResult(ToolResult.Ok("패턴 '" + text + "'에 일치하는 결과가 없습니다."));
}
string text5 = $"{num3}개 파일에서 {num2}개 일치{((num2 >= 100) ? " ( )" : "")}:";
return Task.FromResult(ToolResult.Ok(text5 + stringBuilder));
}
catch (RegexParseException)
{
return Task.FromResult(ToolResult.Fail("잘못된 정규식 패턴: " + text));
}
catch (Exception ex2)
{
return Task.FromResult(ToolResult.Fail("검색 실패: " + ex2.Message));
}
}
private static bool IsBinaryFile(string path)
{
switch (Path.GetExtension(path).ToLowerInvariant())
{
case ".exe":
case ".dll":
case ".zip":
case ".7z":
case ".rar":
case ".tar":
case ".gz":
case ".png":
case ".jpg":
case ".jpeg":
case ".gif":
case ".bmp":
case ".ico":
case ".webp":
case ".pdf":
case ".docx":
case ".xlsx":
case ".pptx":
case ".mp3":
case ".mp4":
case ".avi":
case ".mov":
case ".mkv":
case ".psd":
case ".msi":
case ".iso":
case ".bin":
case ".dat":
case ".db":
return true;
default:
return false;
}
}
}