using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace AxCopilot.Services.Agent; public class MultiReadTool : IAgentTool { public string Name => "multi_read"; public string Description => "Read multiple files in a single call (max 10). Returns concatenated contents with file headers. More efficient than calling file_read multiple times."; public ToolParameterSchema Parameters { get { ToolParameterSchema obj = new ToolParameterSchema { Properties = new Dictionary { ["paths"] = new ToolProperty { Type = "array", Description = "List of file paths to read (max 10)", Items = new ToolProperty { Type = "string", Description = "File path" } }, ["max_lines"] = new ToolProperty { Type = "integer", Description = "Max lines per file (default 200)" } } }; int num = 1; List list = new List(num); CollectionsMarshal.SetCount(list, num); CollectionsMarshal.AsSpan(list)[0] = "paths"; obj.Required = list; return obj; } } public Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken)) { JsonElement value; int num = (args.TryGetProperty("max_lines", out value) ? value.GetInt32() : 200); if (num <= 0) { num = 200; } if (!args.TryGetProperty("paths", out var value2) || value2.ValueKind != JsonValueKind.Array) { return Task.FromResult(ToolResult.Fail("'paths'는 문자열 배열이어야 합니다.")); } List list = new List(); foreach (JsonElement item in value2.EnumerateArray()) { string text = item.GetString(); if (!string.IsNullOrEmpty(text)) { list.Add(text); } } if (list.Count == 0) { return Task.FromResult(ToolResult.Fail("읽을 파일이 없습니다.")); } if (list.Count > 10) { return Task.FromResult(ToolResult.Fail("최대 10개 파일만 지원합니다.")); } StringBuilder stringBuilder = new StringBuilder(); int num2 = 0; foreach (string item2 in list) { string text2 = (Path.IsPathRooted(item2) ? item2 : Path.Combine(context.WorkFolder, item2)); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(8, 1, stringBuilder2); handler.AppendLiteral("═══ "); handler.AppendFormatted(Path.GetFileName(text2)); handler.AppendLiteral(" ═══"); stringBuilder3.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(6, 1, stringBuilder2); handler.AppendLiteral("Path: "); handler.AppendFormatted(text2); stringBuilder4.AppendLine(ref handler); if (!context.IsPathAllowed(text2)) { stringBuilder.AppendLine("[접근 차단됨]"); } else if (!File.Exists(text2)) { stringBuilder.AppendLine("[파일 없음]"); } else { try { List list2 = File.ReadLines(text2).Take(num).ToList(); for (int i = 0; i < list2.Count; i++) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(1, 2, stringBuilder2); handler.AppendFormatted(i + 1); handler.AppendLiteral("\t"); handler.AppendFormatted(list2[i]); stringBuilder5.AppendLine(ref handler); } if (list2.Count >= num) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(23, 1, stringBuilder2); handler.AppendLiteral("... (이후 생략, max_lines="); handler.AppendFormatted(num); handler.AppendLiteral(")"); stringBuilder6.AppendLine(ref handler); } num2++; } catch (Exception ex) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2); handler.AppendLiteral("[읽기 오류: "); handler.AppendFormatted(ex.Message); handler.AppendLiteral("]"); stringBuilder7.AppendLine(ref handler); } } stringBuilder.AppendLine(); } return Task.FromResult(ToolResult.Ok($"{num2}/{list.Count}개 파일 읽기 완료.\n\n{stringBuilder}")); } }