using System.IO; using System.Text; using System.Text.Json; namespace AxCopilot.Services.Agent; /// /// CSV (.csv) 파일을 생성하는 내장 스킬. /// LLM이 헤더와 데이터 행을 전달하면 CSV 파일을 생성합니다. /// public class CsvSkill : IAgentTool { public string Name => "csv_create"; public string Description => "Create a CSV (.csv) file with structured data. Provide headers and rows as JSON arrays."; public ToolParameterSchema Parameters => new() { Properties = new() { ["path"] = new() { Type = "string", Description = "Output file path (.csv). Relative to work folder." }, ["headers"] = new() { Type = "array", Description = "Column headers as JSON array of strings.", Items = new() { Type = "string" } }, ["rows"] = new() { Type = "array", Description = "Data rows as JSON array of arrays.", Items = new() { Type = "array", Items = new() { Type = "string" } } }, ["encoding"] = new() { Type = "string", Description = "File encoding: 'utf-8' (default) or 'euc-kr'." }, }, Required = ["path", "headers", "rows"] }; public async Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct) { var path = args.GetProperty("path").GetString() ?? ""; var encodingName = args.TryGetProperty("encoding", out var enc) ? enc.GetString() ?? "utf-8" : "utf-8"; var fullPath = FileReadTool.ResolvePath(path, context.WorkFolder); if (context.ActiveTab == "Cowork") fullPath = AgentContext.EnsureTimestampedPath(fullPath); if (!fullPath.EndsWith(".csv", StringComparison.OrdinalIgnoreCase)) fullPath += ".csv"; if (!context.IsPathAllowed(fullPath)) return ToolResult.Fail($"경로 접근 차단: {fullPath}"); if (!await context.CheckWritePermissionAsync(Name, fullPath)) return ToolResult.Fail($"쓰기 권한 거부: {fullPath}"); try { var headers = args.GetProperty("headers"); var rows = args.GetProperty("rows"); var dir = Path.GetDirectoryName(fullPath); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); Encoding fileEncoding; try { fileEncoding = Encoding.GetEncoding(encodingName); } catch { fileEncoding = new UTF8Encoding(true); } var sb = new StringBuilder(); // 헤더 var headerValues = new List(); foreach (var h in headers.EnumerateArray()) headerValues.Add(EscapeCsvField(h.GetString() ?? "")); sb.AppendLine(string.Join(",", headerValues)); // 데이터 int rowCount = 0; foreach (var row in rows.EnumerateArray()) { var fields = new List(); foreach (var cell in row.EnumerateArray()) fields.Add(EscapeCsvField(cell.ToString())); sb.AppendLine(string.Join(",", fields)); rowCount++; } await File.WriteAllTextAsync(fullPath, sb.ToString(), fileEncoding, ct); return ToolResult.Ok( $"CSV 파일 생성 완료: {fullPath}\n열: {headerValues.Count}, 행: {rowCount}, 인코딩: {encodingName}", fullPath); } catch (Exception ex) { return ToolResult.Fail($"CSV 생성 실패: {ex.Message}"); } } private static string EscapeCsvField(string field) { if (field.Contains(',') || field.Contains('"') || field.Contains('\n') || field.Contains('\r')) return $"\"{field.Replace("\"", "\"\"")}\""; return field; } }