244 lines
6.9 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|