using System.Diagnostics; namespace AgentWikiUi.Core; // Unico punto di avvio dei motori, condiviso da CLI e GUI. static class EngineRunner { public static ProcessStartInfo CreateStart(Engine engine, string prompt, bool captured, string? sessionId = null, bool resume = false, string model = "", string effort = "") => CreateStart(engine, engine.BuildArgs(prompt, sessionId, resume, model, effort, structured: captured), captured); public static ProcessStartInfo CreateStart(Engine engine, IEnumerable args, bool captured) { var psi = new ProcessStartInfo(engine.Executable) { WorkingDirectory = AppContext.BaseDirectory, // mai la cwd del chiamante né Assembly.Location (vuoto in single-file) UseShellExecute = false, RedirectStandardInput = true, // chiuso subito dal chiamante: evita l'attesa di stdin del motore RedirectStandardOutput = captured, RedirectStandardError = captured, CreateNoWindow = true, }; foreach (var a in args) psi.ArgumentList.Add(a); return psi; } // CLI: stdout/stderr ereditati (streaming), exit code propagato, 127 se l'eseguibile manca. public static int RunInherited(Engine engine, string prompt) { try { using var p = Process.Start(CreateStart(engine, prompt, captured: false))!; p.StandardInput.Close(); p.WaitForExit(); return p.ExitCode; } catch (System.ComponentModel.Win32Exception) { Console.Error.WriteLine($"'{engine.Executable}' non trovato nel PATH."); return 127; } } // GUI: output catturato e interpretato dal motore. Win32Exception se l'eseguibile non è nel PATH. public static async Task RunCapturedAsync(Engine engine, string prompt, string? sessionId, bool resume, string model, string effort) { using var p = Process.Start(CreateStart(engine, prompt, captured: true, sessionId, resume, model, effort))!; p.StandardInput.Close(); var o = p.StandardOutput.ReadToEndAsync(); var e = p.StandardError.ReadToEndAsync(); await p.WaitForExitAsync(); return engine.Parse(p.ExitCode, await o, await e); } // Comandi di servizio del motore (--version, update): output catturato, tempo massimo, nessuna chat. // Win32Exception se l'eseguibile non è nel PATH; allo scadere il processo e i suoi figli sono terminati. public static async Task<(int Code, string Out, string Err, bool TimedOut)> RunToolAsync(Engine engine, IEnumerable args, TimeSpan timeout) { using var p = Process.Start(CreateStart(engine, args, captured: true))!; p.StandardInput.Close(); var o = p.StandardOutput.ReadToEndAsync(); var e = p.StandardError.ReadToEndAsync(); using var cts = new CancellationTokenSource(timeout); try { await p.WaitForExitAsync(cts.Token); } catch (OperationCanceledException) { try { p.Kill(entireProcessTree: true); } catch (InvalidOperationException) { } await p.WaitForExitAsync(); return (-1, await o, await e, true); } return (p.ExitCode, await o, await e, false); } }