Initial commit to new repository

This commit is contained in:
2026-04-03 18:22:19 +09:00
commit 4458bb0f52
7672 changed files with 452440 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
using System.IO;
using System.Text;
using System.Text.Json;
namespace AxCopilot.Services.Agent;
/// <summary>파일 인코딩 감지 및 변환 도구.</summary>
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 => new()
{
Properties = new()
{
["action"] = new()
{
Type = "string",
Description = "Action: detect, convert, list",
Enum = ["detect", "convert", "list"],
},
["path"] = new()
{
Type = "string",
Description = "File path",
},
["from_encoding"] = new()
{
Type = "string",
Description = "Source encoding name (e.g. 'euc-kr', 'shift-jis'). Auto-detected if omitted.",
},
["to_encoding"] = new()
{
Type = "string",
Description = "Target encoding name (default: 'utf-8')",
},
},
Required = ["action"],
};
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
{
var action = args.GetProperty("action").GetString() ?? "";
if (action == "list")
return ListEncodings();
var rawPath = args.TryGetProperty("path", out var pv) ? pv.GetString() ?? "" : "";
if (string.IsNullOrEmpty(rawPath))
return ToolResult.Fail("'path'가 필요합니다.");
var 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
{
return action switch
{
"detect" => DetectEncoding(path),
"convert" => await ConvertEncoding(path, args, context),
_ => ToolResult.Fail($"Unknown action: {action}"),
};
}
catch (Exception ex)
{
return ToolResult.Fail($"인코딩 처리 오류: {ex.Message}");
}
}
private static ToolResult DetectEncoding(string path)
{
var bytes = File.ReadAllBytes(path);
var detected = DetectEncodingFromBytes(bytes);
var sb = new StringBuilder();
sb.AppendLine($"File: {Path.GetFileName(path)}");
sb.AppendLine($"Size: {bytes.Length:N0} bytes");
sb.AppendLine($"Detected Encoding: {detected.EncodingName}");
sb.AppendLine($"Code Page: {detected.CodePage}");
sb.AppendLine($"BOM Present: {HasBom(bytes)}");
return ToolResult.Ok(sb.ToString());
}
private static async Task<ToolResult> ConvertEncoding(string path, JsonElement args, AgentContext context)
{
var toName = args.TryGetProperty("to_encoding", out var te) ? te.GetString() ?? "utf-8" : "utf-8";
// 쓰기 권한 확인
var allowed = await context.CheckWritePermissionAsync("encoding_tool", path);
if (!allowed) 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
{
var rawBytes = File.ReadAllBytes(path);
fromEnc = DetectEncodingFromBytes(rawBytes);
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var toEnc = Encoding.GetEncoding(toName);
var content = File.ReadAllText(path, fromEnc);
File.WriteAllText(path, content, toEnc);
return ToolResult.Ok(
$"변환 완료: {fromEnc.EncodingName} → {toEnc.EncodingName}\nFile: {path}",
filePath: path);
}
private static ToolResult ListEncodings()
{
var sb = new StringBuilder();
sb.AppendLine("주요 인코딩 목록:");
sb.AppendLine(" utf-8 — UTF-8 (유니코드, 기본)");
sb.AppendLine(" utf-16 — UTF-16 LE");
sb.AppendLine(" utf-16BE — UTF-16 BE");
sb.AppendLine(" euc-kr — EUC-KR (한국어)");
sb.AppendLine(" ks_c_5601-1987 — 한글 완성형");
sb.AppendLine(" shift_jis — Shift-JIS (일본어)");
sb.AppendLine(" gb2312 — GB2312 (중국어 간체)");
sb.AppendLine(" iso-8859-1 — Latin-1 (서유럽)");
sb.AppendLine(" ascii — US-ASCII");
sb.AppendLine(" utf-32 — UTF-32");
return ToolResult.Ok(sb.ToString());
}
private static Encoding DetectEncodingFromBytes(byte[] bytes)
{
// BOM 감지
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
return Encoding.UTF8;
if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE)
return Encoding.Unicode; // UTF-16 LE
if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF)
return Encoding.BigEndianUnicode;
// 간단한 UTF-8 유효성 검사
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)
{
var i = 0;
var hasMultibyte = false;
while (i < bytes.Length)
{
if (bytes[i] <= 0x7F) { i++; continue; }
int extra;
if ((bytes[i] & 0xE0) == 0xC0) extra = 1;
else if ((bytes[i] & 0xF0) == 0xE0) extra = 2;
else if ((bytes[i] & 0xF8) == 0xF0) extra = 3;
else return false;
if (i + extra >= bytes.Length) return false;
for (var j = 1; j <= extra; j++)
if ((bytes[i + j] & 0xC0) != 0x80) return false;
hasMultibyte = true;
i += extra + 1;
}
return hasMultibyte || bytes.Length < 100; // 순수 ASCII도 UTF-8으로 간주
}
private static bool HasBom(byte[] bytes)
{
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) return true;
if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) return true;
if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) return true;
return false;
}
}