using System.Diagnostics;using System.Text.Json;using Agent.Core.ToolCalling;namespace Agent.Addons.Tools.CSharpProjects;/// <summary>Base for addon tools that scaffold a C# project with <c>dotnet new</c>.</summary>public abstract class CSharpProjectTool : ICSharpProject{ public abstract string ToolName { get; } protected abstract string TemplateShortName { get; } protected abstract string ProjectKindName { get; } public virtual string ToolDescription => $"Creates a new {ProjectKindName} C# project (dotnet new {TemplateShortName}). " + "Requires JSON: name (project name), optional output (folder path)."; private static object ParametersSchema => new { type = "object", properties = new { name = new { type = "string", description = "Project name (also used as the default folder name)." }, output = new { type = "string", description = "Optional output directory. Defaults to a folder named after the project." }, }, required = new[] { "name" }, }; public static void Register<TTool>(ToolRegistry registry) where TTool : CSharpProjectTool, new() { var tool = new TTool(); registry.Register(new AgentTool( tool.ToolName, tool.ToolDescription, tool.Invoke, ParametersSchema)); } public static void RegisterAll(ToolRegistry registry) { ArgumentNullException.ThrowIfNull(registry); Register<CreateConsoleProjectTool>(registry); Register<CreateClassLibraryProjectTool>(registry); Register<CreateWebEmptyProjectTool>(registry); Register<CreateGrpcProjectTool>(registry); Register<CreateWebApiProjectTool>(registry); Register<CreateWebApiAotProjectTool>(registry); Register<CreateMvcProjectTool>(registry); Register<CreateRazorProjectTool>(registry); Register<CreateBlazorProjectTool>(registry); Register<CreateBlazorWasmProjectTool>(registry); Register<CreateRazorClassLibraryProjectTool>(registry); Register<CreateWorkerProjectTool>(registry); Register<CreateWinFormsProjectTool>(registry); Register<CreateWinFormsLibraryProjectTool>(registry); Register<CreateWinFormsControlLibraryProjectTool>(registry); Register<CreateWpfProjectTool>(registry); Register<CreateWpfLibraryProjectTool>(registry); Register<CreateWpfCustomControlLibraryProjectTool>(registry); Register<CreateWpfUserControlLibraryProjectTool>(registry); Register<CreateMstestProjectTool>(registry); Register<CreateMstestPlaywrightProjectTool>(registry); Register<CreateNunitProjectTool>(registry); Register<CreateNunitPlaywrightProjectTool>(registry); Register<CreateXunitProjectTool>(registry); } private string Invoke(string argumentsJson) { if (!TryParseArguments(argumentsJson, out string name, out string? output, out string error)) { return error; } return Create(name, output); } public string Create(string name, string? outputDirectory = null) { string projectName = name.Trim(); if (string.IsNullOrWhiteSpace(projectName)) { return "Error: Provide a project name (JSON property \"name\")."; } string outputPath = string.IsNullOrWhiteSpace(outputDirectory) ? projectName : outputDirectory.Trim(); try { using var process = StartDotNetNew(projectName, outputPath); string stdout = process.StandardOutput.ReadToEnd(); string stderr = process.StandardError.ReadToEnd(); process.WaitForExit(); if (process.ExitCode == 0) { return $"Created {ProjectKindName} '{projectName}' at '{Path.GetFullPath(outputPath)}'.{FormatProcessOutput(stdout)}"; } return $"Failed to create {ProjectKindName} '{projectName}': {TrimOrDefault(stderr, stdout, "dotnet new returned a non-zero exit code.")}"; } catch (Exception ex) { return $"Failed to create {ProjectKindName} '{projectName}': {ex.Message}"; } } private Process StartDotNetNew(string projectName, string outputPath) { var startInfo = new ProcessStartInfo { FileName = "dotnet", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; startInfo.ArgumentList.Add("new"); startInfo.ArgumentList.Add(TemplateShortName); startInfo.ArgumentList.Add("-n"); startInfo.ArgumentList.Add(projectName); startInfo.ArgumentList.Add("-o"); startInfo.ArgumentList.Add(outputPath); startInfo.ArgumentList.Add("--language"); startInfo.ArgumentList.Add("C#"); return Process.Start(startInfo) ?? throw new InvalidOperationException("Could not start dotnet CLI."); } private static bool TryParseArguments( string argumentsJson, out string name, out string? output, out string error) { name = ""; output = null; error = ""; try { using var doc = JsonDocument.Parse(argumentsJson); var root = doc.RootElement; if (root.TryGetProperty("name", out var nameProp)) { name = nameProp.GetString()?.Trim() ?? ""; } if (root.TryGetProperty("output", out var outputProp)) { string? outputValue = outputProp.GetString()?.Trim(); if (!string.IsNullOrWhiteSpace(outputValue)) { output = outputValue; } } if (string.IsNullOrWhiteSpace(name)) { error = "Error: Provide a project name (JSON property \"name\")."; return false; } return true; } catch (JsonException ex) { error = $"Error: Invalid JSON arguments — {ex.Message}"; return false; } } private static string FormatProcessOutput(string stdout) { string trimmed = stdout.Trim(); return string.IsNullOrWhiteSpace(trimmed) ? "" : $"\n{trimmed}"; } private static string TrimOrDefault(string primary, string secondary, string fallback) { string message = primary.Trim(); if (!string.IsNullOrWhiteSpace(message)) { return message; } message = secondary.Trim(); return string.IsNullOrWhiteSpace(message) ? fallback : message; }}