Files
AX-Copilot-Codex/etc/chat-ui-backup/2026-04-05-1215/AxAgentExecutionEngine.cs
lacvet 3ed454a98c
Some checks failed
Release Gate / gate (push) Has been cancelled
AX Agent 채팅 UI 백업 및 claw-code 기준 엔진 재정렬
- ChatWindow 현재 UI와 엔진 기준본을 etc/chat-ui-backup/2026-04-05-1215에 백업해 회귀 비교 지점을 확보함

- 메시지 컬럼과 컴포저 폭을 claw-code식 단일 축으로 다시 맞추고 입력 셸을 안정적인 하단 컬럼 구조로 정리함

- 입력창 높이를 실제 줄바꿈 수 기준으로 다시 계산해 전송 후 높이가 남는 버그를 줄임

- 메시지 편집/피드백 후 재생성 경로의 직접 UI 버블 주입을 제거하고 RenderMessages 중심으로 통합함

- SendRegenerateAsync가 Cowork/Code에서 ResolveExecutionMode와 RunAgentLoopAsync를 타도록 바꿔 재생성도 동일 엔진 축으로 정렬함

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 경고 0 / 오류 0
2026-04-05 12:19:20 +09:00

129 lines
4.2 KiB
C#

using AxCopilot.Models;
using AxCopilot.Services;
namespace AxCopilot.Services.Agent;
/// <summary>
/// AX Agent execution-prep engine.
/// Inspired by the `claw-code` split between input preparation and session execution,
/// so the UI layer stops owning message assembly and final assistant commit logic.
/// </summary>
public sealed class AxAgentExecutionEngine
{
public sealed record PreparedTurn(List<ChatMessage> Messages);
public sealed record ExecutionMode(bool UseAgentLoop, bool UseStreamingTransport, string? TaskSystemPrompt);
public IReadOnlyList<string> BuildPromptStack(
string? conversationSystem,
string? slashSystem,
string? taskSystem = null)
{
var prompts = new List<string>();
if (!string.IsNullOrWhiteSpace(conversationSystem))
prompts.Add(conversationSystem.Trim());
if (!string.IsNullOrWhiteSpace(slashSystem))
prompts.Add(slashSystem.Trim());
if (!string.IsNullOrWhiteSpace(taskSystem))
prompts.Add(taskSystem.Trim());
return prompts;
}
public ExecutionMode ResolveExecutionMode(
string runTab,
bool streamingEnabled,
string resolvedService,
string? coworkSystemPrompt,
string? codeSystemPrompt)
{
if (string.Equals(runTab, "Cowork", StringComparison.OrdinalIgnoreCase))
return new ExecutionMode(true, false, coworkSystemPrompt);
if (string.Equals(runTab, "Code", StringComparison.OrdinalIgnoreCase))
return new ExecutionMode(true, false, codeSystemPrompt);
return new ExecutionMode(false, false, null);
}
public PreparedTurn PrepareTurn(
ChatConversation conversation,
IEnumerable<string?> systemPrompts,
string? fileContext = null,
IReadOnlyList<ImageAttachment>? images = null)
{
var outbound = conversation.Messages
.Select(CloneMessage)
.ToList();
if (!string.IsNullOrWhiteSpace(fileContext))
{
var lastUserIndex = outbound.FindLastIndex(m => string.Equals(m.Role, "user", StringComparison.OrdinalIgnoreCase));
if (lastUserIndex >= 0)
outbound[lastUserIndex].Content = (outbound[lastUserIndex].Content ?? string.Empty) + fileContext;
}
if (images is { Count: > 0 })
{
var lastUserIndex = outbound.FindLastIndex(m => string.Equals(m.Role, "user", StringComparison.OrdinalIgnoreCase));
if (lastUserIndex >= 0)
outbound[lastUserIndex].Images = images.Select(CloneImage).ToList();
}
var promptList = systemPrompts
.Where(prompt => !string.IsNullOrWhiteSpace(prompt))
.Select(prompt => prompt!.Trim())
.ToList();
for (var i = promptList.Count - 1; i >= 0; i--)
outbound.Insert(0, new ChatMessage { Role = "system", Content = promptList[i] });
return new PreparedTurn(outbound);
}
public ChatMessage CommitAssistantMessage(
ChatSessionStateService? session,
ChatConversation conversation,
string tab,
string content,
ChatStorageService? storage = null)
{
var assistant = new ChatMessage
{
Role = "assistant",
Content = content,
};
if (session != null)
{
session.AppendMessage(tab, assistant, storage);
return assistant;
}
conversation.Messages.Add(assistant);
conversation.UpdatedAt = DateTime.Now;
return assistant;
}
private static ChatMessage CloneMessage(ChatMessage source)
{
return new ChatMessage
{
Role = source.Role,
Content = source.Content,
Timestamp = source.Timestamp,
MetaRunId = source.MetaRunId,
AttachedFiles = source.AttachedFiles?.ToList(),
Images = source.Images?.Select(CloneImage).ToList(),
};
}
private static ImageAttachment CloneImage(ImageAttachment source)
{
return new ImageAttachment
{
Base64 = source.Base64,
MimeType = source.MimeType,
FileName = source.FileName,
};
}
}