| 1 | #nullable enable |
| 2 | using System.Diagnostics; |
| 3 | using System.Text; |
| 4 | using CascadeIDE.Contracts; |
| 5 | |
| 6 | namespace CascadeIDE.Features.CursorAcp.DataAcquisition; |
| 7 | |
| 8 | /// <summary> |
| 9 | /// DAL: stdio-процесс <c>cursor-agent acp</c> для <see cref="CascadeIDE.Services.CursorAcp.CursorAcpChatConnection"/> (UTF-8, перенаправленные потоки). |
| 10 | /// </summary> |
| 11 | [IoBoundary] |
| 12 | public static class CursorAcpChatAgentProcess |
| 13 | { |
| 14 | /// <summary> |
| 15 | /// Создаёт процесс, подписывается на stderr (построчно), вызывает <see cref="Process.Start"/>, затем <see cref="Process.BeginErrorReadLine"/>. |
| 16 | /// </summary> |
| 17 | /// <param name="cmdPath">Полный путь к <c>cursor-agent.cmd</c> или аналогу.</param> |
| 18 | /// <param name="agentWorkingDirectory">Рабочий каталог агента; если пусто — каталог <paramref name="cmdPath"/>.</param> |
| 19 | /// <param name="onStderrLine">Не-null строки из stderr (как в <c>ErrorDataReceived</c>).</param> |
| 20 | /// <exception cref="InvalidOperationException">Не удалось запустить процесс.</exception> |
| 21 | public static Process Start(string cmdPath, string agentWorkingDirectory, Action<string>? onStderrLine) |
| 22 | { |
| 23 | var workDir = string.IsNullOrEmpty(agentWorkingDirectory) |
| 24 | ? Path.GetDirectoryName(cmdPath) ?? "" |
| 25 | : agentWorkingDirectory; |
| 26 | |
| 27 | var psi = new ProcessStartInfo |
| 28 | { |
| 29 | FileName = cmdPath, |
| 30 | Arguments = "acp", |
| 31 | WorkingDirectory = workDir, |
| 32 | UseShellExecute = false, |
| 33 | CreateNoWindow = true, |
| 34 | RedirectStandardInput = true, |
| 35 | RedirectStandardOutput = true, |
| 36 | RedirectStandardError = true, |
| 37 | // Иначе на Windows OEM — JSON/UTF-8 от агента искажается. |
| 38 | StandardInputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), |
| 39 | StandardOutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), |
| 40 | StandardErrorEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), |
| 41 | }; |
| 42 | |
| 43 | var p = new Process { StartInfo = psi }; |
| 44 | if (onStderrLine is not null) |
| 45 | { |
| 46 | p.ErrorDataReceived += (_, e) => |
| 47 | { |
| 48 | if (!string.IsNullOrEmpty(e.Data)) |
| 49 | onStderrLine(e.Data); |
| 50 | }; |
| 51 | } |
| 52 | |
| 53 | if (!p.Start()) |
| 54 | throw new InvalidOperationException("Не удалось запустить cursor-agent (ACP)."); |
| 55 | |
| 56 | p.BeginErrorReadLine(); |
| 57 | return p; |
| 58 | } |
| 59 | } |
| 60 | |