using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace AxCopilot.Services.Agent; public class FileReadTool : IAgentTool { public string Name => "file_read"; public string Description => "Read the contents of a file. Returns the text content with line numbers."; public ToolParameterSchema Parameters { get { ToolParameterSchema obj = new ToolParameterSchema { Properties = new Dictionary { ["path"] = new ToolProperty { Type = "string", Description = "File path to read (absolute or relative to work folder)" }, ["offset"] = new ToolProperty { Type = "integer", Description = "Starting line number (1-based). Optional, default 1." }, ["limit"] = new ToolProperty { Type = "integer", Description = "Maximum number of lines to read. Optional, default 500." } } }; int num = 1; List list = new List(num); CollectionsMarshal.SetCount(list, num); CollectionsMarshal.AsSpan(list)[0] = "path"; obj.Required = list; return obj; } } public Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct) { if (!args.TryGetProperty("path", out var value)) { return Task.FromResult(ToolResult.Fail("path가 필요합니다.")); } string path = value.GetString() ?? ""; JsonElement value2; int num = ((!args.TryGetProperty("offset", out value2)) ? 1 : value2.GetInt32()); JsonElement value3; int num2 = (args.TryGetProperty("limit", out value3) ? value3.GetInt32() : 500); string text = ResolvePath(path, context.WorkFolder); if (!context.IsPathAllowed(text)) { return Task.FromResult(ToolResult.Fail("경로 접근 차단: " + text)); } if (!File.Exists(text)) { return Task.FromResult(ToolResult.Fail("파일이 존재하지 않습니다: " + text)); } try { string[] array = File.ReadAllLines(text, Encoding.UTF8); int num3 = array.Length; int num4 = Math.Max(0, num - 1); int num5 = Math.Min(num3, num4 + num2); StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(22, 4, stringBuilder2); handler.AppendLiteral("["); handler.AppendFormatted(text); handler.AppendLiteral("] ("); handler.AppendFormatted(num3); handler.AppendLiteral(" lines, showing "); handler.AppendFormatted(num4 + 1); handler.AppendLiteral("-"); handler.AppendFormatted(num5); handler.AppendLiteral(")"); stringBuilder3.AppendLine(ref handler); for (int i = num4; i < num5; i++) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(1, 2, stringBuilder2); handler.AppendFormatted(i + 1, 5); handler.AppendLiteral("\t"); handler.AppendFormatted(array[i]); stringBuilder4.AppendLine(ref handler); } return Task.FromResult(ToolResult.Ok(stringBuilder.ToString(), text)); } catch (Exception ex) { return Task.FromResult(ToolResult.Fail("파일 읽기 실패: " + ex.Message)); } } internal static string ResolvePath(string path, string workFolder) { if (Path.IsPathRooted(path)) { return Path.GetFullPath(path); } if (!string.IsNullOrEmpty(workFolder)) { return Path.GetFullPath(Path.Combine(workFolder, path)); } return Path.GetFullPath(path); } }