using System.Text; using System.Text.Json; namespace AxCopilot.Services.Agent; /// /// 작업 완료 후 후속 액션을 구조화하여 제안하는 도구. /// UI가 클릭 가능한 칩으로 렌더링할 수 있도록 JSON 형태로 반환합니다. /// public class SuggestActionsTool : IAgentTool { public string Name => "suggest_actions"; public string Description => "Suggest 2-5 follow-up actions after completing a task. " + "Returns structured JSON that the UI renders as clickable action chips. " + "Each action has a label (display text), command (slash command or natural language prompt), " + "optional icon (Segoe MDL2 Assets code), and priority (high/medium/low)."; public ToolParameterSchema Parameters => new() { Properties = new() { ["actions"] = new() { Type = "array", Description = "List of action objects. Each object: {\"label\": \"표시 텍스트\", \"command\": \"/slash 또는 자연어\", \"icon\": \"\\uE8A5\" (optional), \"priority\": \"high|medium|low\"}", Items = new() { Type = "object", Description = "Action object with label, command, icon, priority" }, }, ["context"] = new() { Type = "string", Description = "Current task context summary (optional)", }, }, Required = ["actions"], }; public Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default) { try { if (!args.TryGetProperty("actions", out var actionsEl) || actionsEl.ValueKind != JsonValueKind.Array) return Task.FromResult(ToolResult.Fail("actions 배열이 필요합니다.")); var actions = new List>(); foreach (var item in actionsEl.EnumerateArray()) { var label = item.TryGetProperty("label", out var l) ? l.GetString() ?? "" : ""; var command = item.TryGetProperty("command", out var c) ? c.GetString() ?? "" : ""; var icon = item.TryGetProperty("icon", out var i) ? i.GetString() ?? "" : ""; var priority = item.TryGetProperty("priority", out var p) ? p.GetString() ?? "medium" : "medium"; if (string.IsNullOrWhiteSpace(label)) return Task.FromResult(ToolResult.Fail("각 action에는 label이 필요합니다.")); if (string.IsNullOrWhiteSpace(command)) return Task.FromResult(ToolResult.Fail("각 action에는 command가 필요합니다.")); // priority 유효성 검사 var validPriorities = new[] { "high", "medium", "low" }; if (!validPriorities.Contains(priority)) priority = "medium"; var action = new Dictionary { ["label"] = label, ["command"] = command, ["priority"] = priority, }; if (!string.IsNullOrEmpty(icon)) action["icon"] = icon; actions.Add(action); } if (actions.Count < 1 || actions.Count > 5) return Task.FromResult(ToolResult.Fail("actions는 1~5개 사이여야 합니다.")); var contextSummary = args.TryGetProperty("context", out var ctx) ? ctx.GetString() ?? "" : ""; // 구조화된 JSON 응답 생성 var result = new Dictionary { ["type"] = "suggest_actions", ["actions"] = actions, }; if (!string.IsNullOrEmpty(contextSummary)) result["context"] = contextSummary; var json = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }); return Task.FromResult(ToolResult.Ok(json)); } catch (Exception ex) { return Task.FromResult(ToolResult.Fail($"액션 제안 오류: {ex.Message}")); } } }