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 EncodingTool : IAgentTool { public string Name => "encoding_tool"; public string Description => "Detect and convert file text encoding. Actions: 'detect' — detect file encoding (UTF-8, EUC-KR, etc.); 'convert' — convert file from one encoding to another; 'list' — list common encoding names."; public ToolParameterSchema Parameters { get { ToolParameterSchema toolParameterSchema = new ToolParameterSchema(); Dictionary dictionary = new Dictionary(); ToolProperty obj = new ToolProperty { Type = "string", Description = "Action: detect, convert, list" }; int num = 3; List list = new List(num); CollectionsMarshal.SetCount(list, num); Span span = CollectionsMarshal.AsSpan(list); span[0] = "detect"; span[1] = "convert"; span[2] = "list"; obj.Enum = list; dictionary["action"] = obj; dictionary["path"] = new ToolProperty { Type = "string", Description = "File path" }; dictionary["from_encoding"] = new ToolProperty { Type = "string", Description = "Source encoding name (e.g. 'euc-kr', 'shift-jis'). Auto-detected if omitted." }; dictionary["to_encoding"] = new ToolProperty { Type = "string", Description = "Target encoding name (default: 'utf-8')" }; toolParameterSchema.Properties = dictionary; num = 1; List list2 = new List(num); CollectionsMarshal.SetCount(list2, num); CollectionsMarshal.AsSpan(list2)[0] = "action"; toolParameterSchema.Required = list2; return toolParameterSchema; } } public async Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken)) { string action = args.GetProperty("action").GetString() ?? ""; if (action == "list") { return ListEncodings(); } JsonElement pv; string rawPath = (args.TryGetProperty("path", out pv) ? (pv.GetString() ?? "") : ""); if (string.IsNullOrEmpty(rawPath)) { return ToolResult.Fail("'path'가 필요합니다."); } string path = (Path.IsPathRooted(rawPath) ? rawPath : Path.Combine(context.WorkFolder, rawPath)); if (!context.IsPathAllowed(path)) { return ToolResult.Fail("경로 접근 차단: " + path); } if (!File.Exists(path)) { return ToolResult.Fail("파일 없음: " + path); } try { if (1 == 0) { } string text = action; ToolResult result = ((text == "detect") ? DetectEncoding(path) : ((!(text == "convert")) ? ToolResult.Fail("Unknown action: " + action) : (await ConvertEncoding(path, args, context)))); if (1 == 0) { } return result; } catch (Exception ex) { return ToolResult.Fail("인코딩 처리 오류: " + ex.Message); } } private static ToolResult DetectEncoding(string path) { byte[] array = File.ReadAllBytes(path); Encoding encoding = DetectEncodingFromBytes(array); StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(6, 1, stringBuilder2); handler.AppendLiteral("File: "); handler.AppendFormatted(Path.GetFileName(path)); stringBuilder3.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(12, 1, stringBuilder2); handler.AppendLiteral("Size: "); handler.AppendFormatted(array.Length, "N0"); handler.AppendLiteral(" bytes"); stringBuilder4.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(19, 1, stringBuilder2); handler.AppendLiteral("Detected Encoding: "); handler.AppendFormatted(encoding.EncodingName); stringBuilder5.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder2); handler.AppendLiteral("Code Page: "); handler.AppendFormatted(encoding.CodePage); stringBuilder6.AppendLine(ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(13, 1, stringBuilder2); handler.AppendLiteral("BOM Present: "); handler.AppendFormatted(HasBom(array)); stringBuilder7.AppendLine(ref handler); return ToolResult.Ok(stringBuilder.ToString()); } private static async Task ConvertEncoding(string path, JsonElement args, AgentContext context) { JsonElement te; string toName = (args.TryGetProperty("to_encoding", out te) ? (te.GetString() ?? "utf-8") : "utf-8"); if (!(await context.CheckWritePermissionAsync("encoding_tool", path))) { return ToolResult.Fail("파일 쓰기 권한이 거부되었습니다."); } Encoding fromEnc; if (args.TryGetProperty("from_encoding", out var fe) && !string.IsNullOrEmpty(fe.GetString())) { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); fromEnc = Encoding.GetEncoding(fe.GetString()); } else { byte[] rawBytes = File.ReadAllBytes(path); fromEnc = DetectEncodingFromBytes(rawBytes); } Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding toEnc = Encoding.GetEncoding(toName); string content = File.ReadAllText(path, fromEnc); File.WriteAllText(path, content, toEnc); return ToolResult.Ok($"변환 완료: {fromEnc.EncodingName} → {toEnc.EncodingName}\nFile: {path}", path); } private static ToolResult ListEncodings() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("주요 인코딩 목록:"); stringBuilder.AppendLine(" utf-8 — UTF-8 (유니코드, 기본)"); stringBuilder.AppendLine(" utf-16 — UTF-16 LE"); stringBuilder.AppendLine(" utf-16BE — UTF-16 BE"); stringBuilder.AppendLine(" euc-kr — EUC-KR (한국어)"); stringBuilder.AppendLine(" ks_c_5601-1987 — 한글 완성형"); stringBuilder.AppendLine(" shift_jis — Shift-JIS (일본어)"); stringBuilder.AppendLine(" gb2312 — GB2312 (중국어 간체)"); stringBuilder.AppendLine(" iso-8859-1 — Latin-1 (서유럽)"); stringBuilder.AppendLine(" ascii — US-ASCII"); stringBuilder.AppendLine(" utf-32 — UTF-32"); return ToolResult.Ok(stringBuilder.ToString()); } private static Encoding DetectEncodingFromBytes(byte[] bytes) { if (bytes.Length >= 3 && bytes[0] == 239 && bytes[1] == 187 && bytes[2] == 191) { return Encoding.UTF8; } if (bytes.Length >= 2 && bytes[0] == byte.MaxValue && bytes[1] == 254) { return Encoding.Unicode; } if (bytes.Length >= 2 && bytes[0] == 254 && bytes[1] == byte.MaxValue) { return Encoding.BigEndianUnicode; } if (IsValidUtf8(bytes)) { return Encoding.UTF8; } Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); try { return Encoding.GetEncoding("euc-kr"); } catch { return Encoding.Default; } } private static bool IsValidUtf8(byte[] bytes) { int num = 0; bool flag = false; while (num < bytes.Length) { if (bytes[num] <= 127) { num++; continue; } int num2; if ((bytes[num] & 0xE0) == 192) { num2 = 1; } else if ((bytes[num] & 0xF0) == 224) { num2 = 2; } else { if ((bytes[num] & 0xF8) != 240) { return false; } num2 = 3; } if (num + num2 >= bytes.Length) { return false; } for (int i = 1; i <= num2; i++) { if ((bytes[num + i] & 0xC0) != 128) { return false; } } flag = true; num += num2 + 1; } return flag || bytes.Length < 100; } private static bool HasBom(byte[] bytes) { if (bytes.Length >= 3 && bytes[0] == 239 && bytes[1] == 187 && bytes[2] == 191) { return true; } if (bytes.Length >= 2 && bytes[0] == byte.MaxValue && bytes[1] == 254) { return true; } if (bytes.Length >= 2 && bytes[0] == 254 && bytes[1] == byte.MaxValue) { return true; } return false; } }