Files
AX-Copilot-Codex/src/AxCopilot/Services/DraftQueueProcessorService.cs

128 lines
4.9 KiB
C#

using AxCopilot.Models;
namespace AxCopilot.Services;
/// <summary>
/// 드래프트 큐 항목의 실행 전이와 재시도 정책을 담당합니다.
/// ChatWindow가 직접 상태 전이를 조합하지 않도록 별도 서비스로 분리합니다.
/// </summary>
public sealed class DraftQueueProcessorService
{
public bool CanStartNext(ChatSessionStateService? session, string tab)
=> session?.GetNextQueuedDraft(tab) != null;
public DraftQueueItem? TryStartNext(ChatSessionStateService? session, string tab, ChatStorageService? storage = null, string? preferredDraftId = null, TaskRunService? taskRuns = null)
{
if (session == null)
return null;
DraftQueueItem? next = null;
if (!string.IsNullOrWhiteSpace(preferredDraftId))
{
next = session.GetDraftQueueItems(tab)
.FirstOrDefault(x => string.Equals(x.Id, preferredDraftId, StringComparison.OrdinalIgnoreCase));
if (next != null &&
!string.Equals(next.State, "queued", StringComparison.OrdinalIgnoreCase))
{
session.ResetDraftToQueued(tab, next.Id, storage);
next = session.GetDraftQueueItems(tab)
.FirstOrDefault(x => string.Equals(x.Id, preferredDraftId, StringComparison.OrdinalIgnoreCase));
}
}
next ??= session.GetNextQueuedDraft(tab);
if (next == null)
return null;
if (!session.MarkDraftRunning(tab, next.Id, storage))
return null;
taskRuns?.StartQueueRun(tab, next.Id, next.Text);
return session.GetDraftQueueItems(tab)
.FirstOrDefault(x => string.Equals(x.Id, next.Id, StringComparison.OrdinalIgnoreCase))
?? next;
}
public bool Complete(ChatSessionStateService? session, string tab, string draftId, ChatStorageService? storage = null, TaskRunService? taskRuns = null)
{
var completed = session?.MarkDraftCompleted(tab, draftId, storage) ?? false;
if (completed)
taskRuns?.CompleteQueueRun(tab, draftId, "대기열 작업 완료", "completed");
return completed;
}
public bool HandleFailure(ChatSessionStateService? session, string tab, string draftId, string? error, bool cancelled = false, int maxAutoRetries = 3, ChatStorageService? storage = null, TaskRunService? taskRuns = null)
{
if (session == null)
return false;
if (cancelled)
{
var reset = session.ResetDraftToQueued(tab, draftId, storage);
if (reset)
taskRuns?.CompleteQueueRun(tab, draftId, string.IsNullOrWhiteSpace(error) ? "대기열 작업 중단" : error, "cancelled");
return reset;
}
var handled = session.ScheduleDraftRetry(tab, draftId, error, maxAutoRetries, storage);
if (handled)
{
var item = session.GetDraftQueueItems(tab)
.FirstOrDefault(x => string.Equals(x.Id, draftId, StringComparison.OrdinalIgnoreCase));
var blocked = item?.NextRetryAt.HasValue == true;
taskRuns?.CompleteQueueRun(
tab,
draftId,
string.IsNullOrWhiteSpace(error) ? (blocked ? "재시도 대기" : "대기열 작업 실패") : error,
blocked ? "blocked" : "failed");
}
return handled;
}
public int PromoteReadyBlockedItems(ChatSessionStateService? session, string tab, ChatStorageService? storage = null)
{
if (session == null)
return 0;
var promoted = 0;
foreach (var item in session.GetDraftQueueItems(tab)
.Where(x => string.Equals(x.State, "queued", StringComparison.OrdinalIgnoreCase)
&& x.NextRetryAt.HasValue
&& x.NextRetryAt.Value <= DateTime.Now)
.ToList())
{
if (session.ResetDraftToQueued(tab, item.Id, storage))
promoted++;
}
return promoted;
}
public int ClearCompleted(ChatSessionStateService? session, string tab, ChatStorageService? storage = null)
=> ClearByState(session, tab, "completed", storage);
public int ClearFailed(ChatSessionStateService? session, string tab, ChatStorageService? storage = null)
=> ClearByState(session, tab, "failed", storage);
private static int ClearByState(ChatSessionStateService? session, string tab, string state, ChatStorageService? storage)
{
if (session == null)
return 0;
var removed = 0;
foreach (var item in session.GetDraftQueueItems(tab)
.Where(x => string.Equals(x.State, state, StringComparison.OrdinalIgnoreCase))
.ToList())
{
if (session.RemoveDraft(tab, item.Id, storage))
removed++;
}
return removed;
}
}