using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace AxCopilot.Services.Agent; public class GlobTool : IAgentTool { public string Name => "glob"; public string Description => "Find files matching a glob pattern (e.g. '**/*.cs', 'src/**/*.json'). Returns matching file paths."; public ToolParameterSchema Parameters { get { ToolParameterSchema obj = new ToolParameterSchema { Properties = new Dictionary { ["pattern"] = new ToolProperty { Type = "string", Description = "Glob pattern to match files (e.g. '**/*.cs', '*.txt')" }, ["path"] = new ToolProperty { Type = "string", Description = "Directory to search in. Optional, defaults to work folder." } } }; int num = 1; List list = new List(num); CollectionsMarshal.SetCount(list, num); CollectionsMarshal.AsSpan(list)[0] = "pattern"; obj.Required = list; return obj; } } public Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct) { string text = args.GetProperty("pattern").GetString() ?? ""; JsonElement value; string text2 = (args.TryGetProperty("path", out value) ? (value.GetString() ?? "") : ""); string baseDir = (string.IsNullOrEmpty(text2) ? context.WorkFolder : FileReadTool.ResolvePath(text2, context.WorkFolder)); if (string.IsNullOrEmpty(baseDir) || !Directory.Exists(baseDir)) { return Task.FromResult(ToolResult.Fail("디렉토리가 존재하지 않습니다: " + baseDir)); } if (!context.IsPathAllowed(baseDir)) { return Task.FromResult(ToolResult.Fail("경로 접근 차단: " + baseDir)); } try { string searchPattern = ExtractSearchPattern(text); SearchOption searchOption = ((text.Contains("**") || text.Contains('/') || text.Contains('\\')) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); List list = (from f in Directory.EnumerateFiles(baseDir, searchPattern, searchOption) where context.IsPathAllowed(f) orderby f select f).Take(200).ToList(); if (list.Count == 0) { return Task.FromResult(ToolResult.Ok("패턴 '" + text + "'에 일치하는 파일이 없습니다.")); } string value2 = string.Join("\n", list.Select((string f) => Path.GetRelativePath(baseDir, f))); return Task.FromResult(ToolResult.Ok($"{list.Count}개 파일 발견:\n{value2}")); } catch (Exception ex) { return Task.FromResult(ToolResult.Fail("검색 실패: " + ex.Message)); } } private static string ExtractSearchPattern(string globPattern) { string text = globPattern.Replace('/', '\\').Split('\\')[^1]; return (string.IsNullOrEmpty(text) || text == "**") ? "*" : text; } }