refactor: decouple turn execution state from Copilot SDK events

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-08 11:12:40 +02:00
co-authored by Copilot
parent 2c165e453f
commit e85906669f
6 changed files with 449 additions and 146 deletions
@@ -0,0 +1,110 @@
namespace Aryx.AgentHost.Contracts;
internal abstract record ProviderSessionEvent;
internal sealed record ProviderAssistantMessageDeltaEvent(string MessageId) : ProviderSessionEvent;
internal sealed record ProviderAssistantMessageEvent(
string MessageId,
bool HasToolRequests) : ProviderSessionEvent;
internal sealed record ProviderToolExecutionStartEvent(
string ToolCallId,
string ToolName,
IReadOnlyDictionary<string, object?>? ToolArguments) : ProviderSessionEvent;
internal sealed record ProviderAssistantIntentEvent(string? Intent) : ProviderSessionEvent;
internal sealed record ProviderAssistantReasoningDeltaEvent(
string? ReasoningId,
string? DeltaContent) : ProviderSessionEvent;
internal sealed record ProviderSubagentStartedEvent(
string? ToolCallId,
string? AgentName,
string? AgentDisplayName,
string? AgentDescription) : ProviderSessionEvent;
internal sealed record ProviderSubagentCompletedEvent(
string? ToolCallId,
string? AgentName,
string? AgentDisplayName) : ProviderSessionEvent;
internal sealed record ProviderSubagentFailedEvent(
string? ToolCallId,
string? AgentName,
string? AgentDisplayName,
string? Error) : ProviderSessionEvent;
internal sealed record ProviderSubagentSelectedEvent(
string? AgentName,
string? AgentDisplayName,
IReadOnlyList<string>? Tools) : ProviderSessionEvent;
internal sealed record ProviderSubagentDeselectedEvent() : ProviderSessionEvent;
internal sealed record ProviderSkillInvokedEvent(
string SkillName,
string Path,
string Content,
IReadOnlyList<string>? AllowedTools,
string? PluginName,
string? PluginVersion) : ProviderSessionEvent;
internal sealed record ProviderHookStartEvent(
string HookInvocationId,
string HookType,
object? Input) : ProviderSessionEvent;
internal sealed record ProviderHookEndEvent(
string HookInvocationId,
string HookType,
bool? Success,
object? Output,
string? Error) : ProviderSessionEvent;
internal sealed record ProviderAssistantUsageEvent(
string Model,
double? InputTokens,
double? OutputTokens,
double? CacheReadTokens,
double? CacheWriteTokens,
double? Cost,
double? Duration,
double? TotalNanoAiu,
Dictionary<string, QuotaSnapshotDto>? QuotaSnapshots) : ProviderSessionEvent;
internal sealed record ProviderSessionUsageEvent(
double TokenLimit,
double CurrentTokens,
double MessagesLength,
double? SystemTokens,
double? ConversationTokens,
double? ToolDefinitionsTokens,
bool? IsInitial) : ProviderSessionEvent;
internal sealed record ProviderSessionCompactionStartEvent(
double? SystemTokens,
double? ConversationTokens,
double? ToolDefinitionsTokens) : ProviderSessionEvent;
internal sealed record ProviderSessionCompactionCompleteEvent(
bool? Success,
string? Error,
double? SystemTokens,
double? ConversationTokens,
double? ToolDefinitionsTokens,
double? PreCompactionTokens,
double? PostCompactionTokens,
double? PreCompactionMessagesLength,
double? MessagesRemoved,
double? TokensRemoved,
string? SummaryContent,
double? CheckpointNumber,
string? CheckpointPath) : ProviderSessionEvent;
internal sealed record ProviderPendingMessagesModifiedEvent() : ProviderSessionEvent;
internal sealed record ProviderMcpOauthRequiredEvent() : ProviderSessionEvent;
internal sealed record ProviderExitPlanModeRequestedEvent() : ProviderSessionEvent;
@@ -0,0 +1,157 @@
using Aryx.AgentHost.Contracts;
using GitHub.Copilot.SDK;
namespace Aryx.AgentHost.Services;
internal sealed class CopilotEventAdapter : IProviderEventAdapter
{
public ProviderSessionEvent? TryAdapt(object rawEvent)
{
return rawEvent switch
{
AssistantMessageDeltaEvent messageDelta
when NormalizeRequiredString(messageDelta.Data?.MessageId) is { } messageId =>
new ProviderAssistantMessageDeltaEvent(messageId),
AssistantMessageEvent assistantMessage
when NormalizeRequiredString(assistantMessage.Data?.MessageId) is { } messageId =>
new ProviderAssistantMessageEvent(
messageId,
assistantMessage.Data?.ToolRequests is { Length: > 0 }),
ToolExecutionStartEvent toolExecutionStart
when NormalizeRequiredString(toolExecutionStart.Data?.ToolCallId) is { } toolCallId
&& NormalizeRequiredString(toolExecutionStart.Data?.ToolName) is { } toolName =>
new ProviderToolExecutionStartEvent(
toolCallId,
toolName,
WorkflowRequestInfoInterpreter.NormalizeRawToolArguments(toolExecutionStart.Data?.Arguments)),
AssistantIntentEvent intentEvent =>
new ProviderAssistantIntentEvent(NormalizeOptionalString(intentEvent.Data?.Intent)),
AssistantReasoningDeltaEvent reasoningDelta =>
new ProviderAssistantReasoningDeltaEvent(
NormalizeOptionalString(reasoningDelta.Data?.ReasoningId),
reasoningDelta.Data?.DeltaContent),
SubagentStartedEvent started =>
new ProviderSubagentStartedEvent(
started.Data?.ToolCallId,
started.Data?.AgentName,
started.Data?.AgentDisplayName,
started.Data?.AgentDescription),
SubagentCompletedEvent completed =>
new ProviderSubagentCompletedEvent(
completed.Data?.ToolCallId,
completed.Data?.AgentName,
completed.Data?.AgentDisplayName),
SubagentFailedEvent failed =>
new ProviderSubagentFailedEvent(
failed.Data?.ToolCallId,
failed.Data?.AgentName,
failed.Data?.AgentDisplayName,
failed.Data?.Error),
SubagentSelectedEvent selected =>
new ProviderSubagentSelectedEvent(
selected.Data?.AgentName,
selected.Data?.AgentDisplayName,
selected.Data?.Tools),
SubagentDeselectedEvent =>
new ProviderSubagentDeselectedEvent(),
SkillInvokedEvent skillInvoked =>
new ProviderSkillInvokedEvent(
skillInvoked.Data?.Name ?? string.Empty,
skillInvoked.Data?.Path ?? string.Empty,
skillInvoked.Data?.Content ?? string.Empty,
skillInvoked.Data?.AllowedTools,
skillInvoked.Data?.PluginName,
skillInvoked.Data?.PluginVersion),
HookStartEvent hookStart =>
new ProviderHookStartEvent(
hookStart.Data?.HookInvocationId ?? string.Empty,
hookStart.Data?.HookType ?? string.Empty,
hookStart.Data?.Input),
HookEndEvent hookEnd =>
new ProviderHookEndEvent(
hookEnd.Data?.HookInvocationId ?? string.Empty,
hookEnd.Data?.HookType ?? string.Empty,
hookEnd.Data?.Success,
hookEnd.Data?.Output,
hookEnd.Data?.Error?.Message),
AssistantUsageEvent assistantUsage =>
new ProviderAssistantUsageEvent(
assistantUsage.Data?.Model ?? string.Empty,
assistantUsage.Data?.InputTokens,
assistantUsage.Data?.OutputTokens,
assistantUsage.Data?.CacheReadTokens,
assistantUsage.Data?.CacheWriteTokens,
assistantUsage.Data?.Cost,
assistantUsage.Data?.Duration,
assistantUsage.Data?.CopilotUsage?.TotalNanoAiu,
QuotaSnapshotMapper.MapOrNull(assistantUsage.Data?.QuotaSnapshots)),
SessionUsageInfoEvent usageInfo =>
new ProviderSessionUsageEvent(
usageInfo.Data?.TokenLimit ?? 0,
usageInfo.Data?.CurrentTokens ?? 0,
usageInfo.Data?.MessagesLength ?? 0,
usageInfo.Data?.SystemTokens,
usageInfo.Data?.ConversationTokens,
usageInfo.Data?.ToolDefinitionsTokens,
usageInfo.Data?.IsInitial),
SessionCompactionStartEvent compactionStart =>
new ProviderSessionCompactionStartEvent(
compactionStart.Data?.SystemTokens,
compactionStart.Data?.ConversationTokens,
compactionStart.Data?.ToolDefinitionsTokens),
SessionCompactionCompleteEvent compactionComplete =>
new ProviderSessionCompactionCompleteEvent(
compactionComplete.Data?.Success,
compactionComplete.Data?.Error,
compactionComplete.Data?.SystemTokens,
compactionComplete.Data?.ConversationTokens,
compactionComplete.Data?.ToolDefinitionsTokens,
compactionComplete.Data?.PreCompactionTokens,
compactionComplete.Data?.PostCompactionTokens,
compactionComplete.Data?.PreCompactionMessagesLength,
compactionComplete.Data?.MessagesRemoved,
compactionComplete.Data?.TokensRemoved,
compactionComplete.Data?.SummaryContent,
compactionComplete.Data?.CheckpointNumber,
compactionComplete.Data?.CheckpointPath),
PendingMessagesModifiedEvent =>
new ProviderPendingMessagesModifiedEvent(),
McpOauthRequiredEvent =>
new ProviderMcpOauthRequiredEvent(),
ExitPlanModeRequestedEvent =>
new ProviderExitPlanModeRequestedEvent(),
_ => null,
};
}
private static string? NormalizeRequiredString(string? value)
{
string? normalized = NormalizeOptionalString(value);
return string.IsNullOrWhiteSpace(normalized) ? null : normalized;
}
private static string? NormalizeOptionalString(string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
}
@@ -1,6 +1,5 @@
using System.Collections.Concurrent;
using Aryx.AgentHost.Contracts;
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;
namespace Aryx.AgentHost.Services;
@@ -70,7 +69,7 @@ internal sealed class CopilotTurnExecutionState
}
}
public void ObserveSessionEvent(WorkflowNodeDto agentDefinition, SessionEvent sessionEvent)
public void ObserveSessionEvent(WorkflowNodeDto agentDefinition, ProviderSessionEvent sessionEvent)
{
AgentIdentity agent = AgentIdentityResolver.ResolveAgentIdentity(
_command.Workflow,
@@ -79,27 +78,25 @@ internal sealed class CopilotTurnExecutionState
switch (sessionEvent)
{
case AssistantMessageDeltaEvent messageDelta when !string.IsNullOrWhiteSpace(messageDelta.Data?.MessageId):
RecordObservedAgentForMessage(agent, messageDelta.Data!.MessageId);
case ProviderAssistantMessageDeltaEvent messageDelta:
RecordObservedAgentForMessage(agent, messageDelta.MessageId);
QueueThinkingIfNeeded(agent);
break;
case AssistantMessageEvent assistantMessage when !string.IsNullOrWhiteSpace(assistantMessage.Data?.MessageId):
RecordObservedAgentForMessage(agent, assistantMessage.Data!.MessageId);
case ProviderAssistantMessageEvent assistantMessage:
RecordObservedAgentForMessage(agent, assistantMessage.MessageId);
QueueThinkingIfNeeded(agent);
if (assistantMessage.Data?.ToolRequests is { Length: > 0 })
if (assistantMessage.HasToolRequests)
{
QueueMessageReclassifiedIfNeeded(assistantMessage.Data.MessageId);
QueueMessageReclassifiedIfNeeded(assistantMessage.MessageId);
}
break;
case ToolExecutionStartEvent toolExecutionStart
when !string.IsNullOrWhiteSpace(toolExecutionStart.Data?.ToolCallId)
&& !string.IsNullOrWhiteSpace(toolExecutionStart.Data?.ToolName):
string toolCallId = toolExecutionStart.Data.ToolCallId.Trim();
string toolName = toolExecutionStart.Data.ToolName.Trim();
case ProviderToolExecutionStartEvent toolExecutionStart:
string toolCallId = toolExecutionStart.ToolCallId;
string toolName = toolExecutionStart.ToolName;
ToolNamesByCallId[toolCallId] = toolName;
ActiveAgent = agent;
AgentActivityEventDto? toolActivity = CreateToolCallingActivity(
agent, toolName, toolCallId, toolExecutionStart.Data.Arguments);
agent, toolName, toolCallId, toolExecutionStart.ToolArguments);
if (toolActivity is not null)
{
_pendingEvents.Enqueue(toolActivity);
@@ -107,86 +104,101 @@ internal sealed class CopilotTurnExecutionState
QueueMessageReclassifiedIfNeeded(_lastObservedMessageId);
break;
case AssistantIntentEvent intentEvent:
case ProviderAssistantIntentEvent intentEvent:
ActiveAgent = agent;
QueueThinkingIfNeeded(agent);
AssistantIntentEventDto? assistantIntent = CreateAssistantIntentEvent(agent, intentEvent.Data);
AssistantIntentEventDto? assistantIntent = CreateAssistantIntentEvent(agent, intentEvent.Intent);
if (assistantIntent is not null)
{
_pendingEvents.Enqueue(assistantIntent);
}
break;
case AssistantReasoningDeltaEvent reasoningDelta:
case ProviderAssistantReasoningDeltaEvent reasoningDelta:
ActiveAgent = agent;
QueueThinkingIfNeeded(agent);
ReasoningDeltaEventDto? reasoningDeltaEvent = CreateReasoningDeltaEvent(agent, reasoningDelta.Data);
ReasoningDeltaEventDto? reasoningDeltaEvent = CreateReasoningDeltaEvent(
agent,
reasoningDelta.ReasoningId,
reasoningDelta.DeltaContent);
if (reasoningDeltaEvent is not null)
{
_pendingEvents.Enqueue(reasoningDeltaEvent);
}
break;
case SubagentStartedEvent started:
case ProviderSubagentStartedEvent started:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateSubagentEvent(agent, "started", started.Data));
_pendingEvents.Enqueue(CreateSubagentStartedEvent(agent, started));
break;
case SubagentCompletedEvent completed:
case ProviderSubagentCompletedEvent completed:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateSubagentCompletedEvent(agent, completed.Data));
_pendingEvents.Enqueue(CreateSubagentCompletedEvent(agent, completed));
break;
case SubagentFailedEvent failed:
case ProviderSubagentFailedEvent failed:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateSubagentFailedEvent(agent, failed.Data));
_pendingEvents.Enqueue(CreateSubagentFailedEvent(agent, failed));
break;
case SubagentSelectedEvent selected:
case ProviderSubagentSelectedEvent selected:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateSubagentSelectedEvent(agent, selected.Data));
_pendingEvents.Enqueue(CreateSubagentSelectedEvent(agent, selected));
break;
case SubagentDeselectedEvent:
case ProviderSubagentDeselectedEvent:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateSubagentDeselectedEvent(agent));
break;
case SkillInvokedEvent skillInvoked:
case ProviderSkillInvokedEvent skillInvoked:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateSkillInvokedEvent(agent, skillInvoked.Data));
_pendingEvents.Enqueue(CreateSkillInvokedEvent(agent, skillInvoked));
break;
case HookStartEvent hookStart:
case ProviderHookStartEvent hookStart:
ActiveAgent = agent;
if (!SuppressHookLifecycleEvents)
{
_pendingEvents.Enqueue(CreateHookLifecycleEvent(agent, "start", hookStart.Data));
_pendingEvents.Enqueue(CreateHookLifecycleEvent(
agent,
"start",
hookStart.HookInvocationId,
hookStart.HookType,
input: hookStart.Input));
}
break;
case HookEndEvent hookEnd:
case ProviderHookEndEvent hookEnd:
ActiveAgent = agent;
if (!SuppressHookLifecycleEvents)
{
_pendingEvents.Enqueue(CreateHookLifecycleEvent(agent, "end", hookEnd.Data));
_pendingEvents.Enqueue(CreateHookLifecycleEvent(
agent,
"end",
hookEnd.HookInvocationId,
hookEnd.HookType,
success: hookEnd.Success,
output: hookEnd.Output,
error: hookEnd.Error));
}
break;
case AssistantUsageEvent assistantUsage:
case ProviderAssistantUsageEvent assistantUsage:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateAssistantUsageEvent(agent, assistantUsage.Data));
_pendingEvents.Enqueue(CreateAssistantUsageEvent(agent, assistantUsage));
break;
case SessionUsageInfoEvent usageInfo:
case ProviderSessionUsageEvent usageInfo:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateUsageEvent(agent, usageInfo.Data));
_pendingEvents.Enqueue(CreateUsageEvent(agent, usageInfo));
break;
case SessionCompactionStartEvent compactionStart:
case ProviderSessionCompactionStartEvent compactionStart:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateCompactionStartEvent(agent, compactionStart.Data));
_pendingEvents.Enqueue(CreateCompactionStartEvent(agent, compactionStart));
break;
case SessionCompactionCompleteEvent compactionComplete:
case ProviderSessionCompactionCompleteEvent compactionComplete:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateCompactionCompleteEvent(agent, compactionComplete.Data));
_pendingEvents.Enqueue(CreateCompactionCompleteEvent(agent, compactionComplete));
break;
case PendingMessagesModifiedEvent:
case ProviderPendingMessagesModifiedEvent:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreatePendingMessagesModifiedEvent(agent));
break;
case McpOauthRequiredEvent:
case ProviderMcpOauthRequiredEvent:
ActiveAgent = agent;
break;
case ExitPlanModeRequestedEvent:
case ProviderExitPlanModeRequestedEvent:
HasPendingExitPlanModeRequest = true;
ActiveAgent = agent;
break;
@@ -297,7 +309,7 @@ internal sealed class CopilotTurnExecutionState
AgentIdentity agent,
string toolName,
string toolCallId,
object? rawArguments = null)
IReadOnlyDictionary<string, object?>? toolArguments = null)
{
if (toolName.StartsWith("handoff_to_", StringComparison.Ordinal))
{
@@ -314,7 +326,7 @@ internal sealed class CopilotTurnExecutionState
AgentName = agent.AgentName,
ToolName = toolName,
ToolCallId = toolCallId,
ToolArguments = WorkflowRequestInfoInterpreter.NormalizeRawToolArguments(rawArguments),
ToolArguments = toolArguments,
};
}
@@ -377,29 +389,28 @@ internal sealed class CopilotTurnExecutionState
return CompletedMessages;
}
private SubagentEventDto CreateSubagentEvent(
private SubagentEventDto CreateSubagentStartedEvent(
AgentIdentity agent,
string eventKind,
SubagentStartedData? data)
ProviderSubagentStartedEvent data)
{
return new SubagentEventDto
{
Type = "subagent-event",
RequestId = _command.RequestId,
SessionId = _command.SessionId,
EventKind = eventKind,
EventKind = "started",
AgentId = agent.AgentId,
AgentName = agent.AgentName,
ToolCallId = data?.ToolCallId,
CustomAgentName = data?.AgentName,
CustomAgentDisplayName = data?.AgentDisplayName,
CustomAgentDescription = data?.AgentDescription,
ToolCallId = data.ToolCallId,
CustomAgentName = data.AgentName,
CustomAgentDisplayName = data.AgentDisplayName,
CustomAgentDescription = data.AgentDescription,
};
}
private SubagentEventDto CreateSubagentCompletedEvent(
AgentIdentity agent,
SubagentCompletedData? data)
ProviderSubagentCompletedEvent data)
{
return new SubagentEventDto
{
@@ -409,15 +420,15 @@ internal sealed class CopilotTurnExecutionState
EventKind = "completed",
AgentId = agent.AgentId,
AgentName = agent.AgentName,
ToolCallId = data?.ToolCallId,
CustomAgentName = data?.AgentName,
CustomAgentDisplayName = data?.AgentDisplayName,
ToolCallId = data.ToolCallId,
CustomAgentName = data.AgentName,
CustomAgentDisplayName = data.AgentDisplayName,
};
}
private SubagentEventDto CreateSubagentFailedEvent(
AgentIdentity agent,
SubagentFailedData? data)
ProviderSubagentFailedEvent data)
{
return new SubagentEventDto
{
@@ -427,16 +438,16 @@ internal sealed class CopilotTurnExecutionState
EventKind = "failed",
AgentId = agent.AgentId,
AgentName = agent.AgentName,
ToolCallId = data?.ToolCallId,
CustomAgentName = data?.AgentName,
CustomAgentDisplayName = data?.AgentDisplayName,
Error = data?.Error,
ToolCallId = data.ToolCallId,
CustomAgentName = data.AgentName,
CustomAgentDisplayName = data.AgentDisplayName,
Error = data.Error,
};
}
private SubagentEventDto CreateSubagentSelectedEvent(
AgentIdentity agent,
SubagentSelectedData? data)
ProviderSubagentSelectedEvent data)
{
return new SubagentEventDto
{
@@ -446,9 +457,9 @@ internal sealed class CopilotTurnExecutionState
EventKind = "selected",
AgentId = agent.AgentId,
AgentName = agent.AgentName,
CustomAgentName = data?.AgentName,
CustomAgentDisplayName = data?.AgentDisplayName,
Tools = data?.Tools,
CustomAgentName = data.AgentName,
CustomAgentDisplayName = data.AgentDisplayName,
Tools = data.Tools,
};
}
@@ -467,10 +478,10 @@ internal sealed class CopilotTurnExecutionState
private AssistantIntentEventDto? CreateAssistantIntentEvent(
AgentIdentity agent,
AssistantIntentData? data)
string? intent)
{
string? intent = data?.Intent?.Trim();
if (string.IsNullOrWhiteSpace(intent))
string? normalizedIntent = intent?.Trim();
if (string.IsNullOrWhiteSpace(normalizedIntent))
{
return null;
}
@@ -482,17 +493,17 @@ internal sealed class CopilotTurnExecutionState
SessionId = _command.SessionId,
AgentId = agent.AgentId,
AgentName = agent.AgentName,
Intent = intent,
Intent = normalizedIntent,
};
}
private ReasoningDeltaEventDto? CreateReasoningDeltaEvent(
AgentIdentity agent,
AssistantReasoningDeltaData? data)
string? reasoningId,
string? deltaContent)
{
if (data is null
|| string.IsNullOrWhiteSpace(data.ReasoningId)
|| string.IsNullOrEmpty(data.DeltaContent))
if (string.IsNullOrWhiteSpace(reasoningId)
|| string.IsNullOrEmpty(deltaContent))
{
return null;
}
@@ -504,14 +515,14 @@ internal sealed class CopilotTurnExecutionState
SessionId = _command.SessionId,
AgentId = agent.AgentId,
AgentName = agent.AgentName,
ReasoningId = data.ReasoningId,
ContentDelta = data.DeltaContent,
ReasoningId = reasoningId,
ContentDelta = deltaContent,
};
}
private SkillInvokedEventDto CreateSkillInvokedEvent(
AgentIdentity agent,
SkillInvokedData? data)
ProviderSkillInvokedEvent data)
{
return new SkillInvokedEventDto
{
@@ -520,19 +531,24 @@ internal sealed class CopilotTurnExecutionState
SessionId = _command.SessionId,
AgentId = agent.AgentId,
AgentName = agent.AgentName,
SkillName = data?.Name ?? string.Empty,
Path = data?.Path ?? string.Empty,
Content = data?.Content ?? string.Empty,
AllowedTools = data?.AllowedTools,
PluginName = data?.PluginName,
PluginVersion = data?.PluginVersion,
SkillName = data.SkillName,
Path = data.Path,
Content = data.Content,
AllowedTools = data.AllowedTools,
PluginName = data.PluginName,
PluginVersion = data.PluginVersion,
};
}
private HookLifecycleEventDto CreateHookLifecycleEvent(
AgentIdentity agent,
string phase,
HookStartData? data)
string hookInvocationId,
string hookType,
object? input = null,
bool? success = null,
object? output = null,
string? error = null)
{
return new HookLifecycleEventDto
{
@@ -541,37 +557,19 @@ internal sealed class CopilotTurnExecutionState
SessionId = _command.SessionId,
AgentId = agent.AgentId,
AgentName = agent.AgentName,
HookInvocationId = data?.HookInvocationId ?? string.Empty,
HookType = data?.HookType ?? string.Empty,
HookInvocationId = hookInvocationId,
HookType = hookType,
Phase = phase,
Input = data?.Input,
};
}
private HookLifecycleEventDto CreateHookLifecycleEvent(
AgentIdentity agent,
string phase,
HookEndData? data)
{
return new HookLifecycleEventDto
{
Type = "hook-lifecycle",
RequestId = _command.RequestId,
SessionId = _command.SessionId,
AgentId = agent.AgentId,
AgentName = agent.AgentName,
HookInvocationId = data?.HookInvocationId ?? string.Empty,
HookType = data?.HookType ?? string.Empty,
Phase = phase,
Success = data?.Success,
Output = data?.Output,
Error = data?.Error?.Message,
Input = input,
Success = success,
Output = output,
Error = error,
};
}
private AssistantUsageEventDto CreateAssistantUsageEvent(
AgentIdentity agent,
AssistantUsageData? data)
ProviderAssistantUsageEvent data)
{
return new AssistantUsageEventDto
{
@@ -580,19 +578,19 @@ internal sealed class CopilotTurnExecutionState
SessionId = _command.SessionId,
AgentId = agent.AgentId,
AgentName = agent.AgentName,
Model = data?.Model ?? string.Empty,
InputTokens = data?.InputTokens,
OutputTokens = data?.OutputTokens,
CacheReadTokens = data?.CacheReadTokens,
CacheWriteTokens = data?.CacheWriteTokens,
Cost = data?.Cost,
Duration = data?.Duration,
TotalNanoAiu = data?.CopilotUsage?.TotalNanoAiu,
QuotaSnapshots = QuotaSnapshotMapper.MapOrNull(data?.QuotaSnapshots),
Model = data.Model,
InputTokens = data.InputTokens,
OutputTokens = data.OutputTokens,
CacheReadTokens = data.CacheReadTokens,
CacheWriteTokens = data.CacheWriteTokens,
Cost = data.Cost,
Duration = data.Duration,
TotalNanoAiu = data.TotalNanoAiu,
QuotaSnapshots = data.QuotaSnapshots,
};
}
private SessionUsageEventDto CreateUsageEvent(AgentIdentity agent, SessionUsageInfoData? data)
private SessionUsageEventDto CreateUsageEvent(AgentIdentity agent, ProviderSessionUsageEvent data)
{
return new SessionUsageEventDto
{
@@ -601,19 +599,19 @@ internal sealed class CopilotTurnExecutionState
SessionId = _command.SessionId,
AgentId = agent.AgentId,
AgentName = agent.AgentName,
TokenLimit = data?.TokenLimit ?? 0,
CurrentTokens = data?.CurrentTokens ?? 0,
MessagesLength = data?.MessagesLength ?? 0,
SystemTokens = data?.SystemTokens,
ConversationTokens = data?.ConversationTokens,
ToolDefinitionsTokens = data?.ToolDefinitionsTokens,
IsInitial = data?.IsInitial,
TokenLimit = data.TokenLimit,
CurrentTokens = data.CurrentTokens,
MessagesLength = data.MessagesLength,
SystemTokens = data.SystemTokens,
ConversationTokens = data.ConversationTokens,
ToolDefinitionsTokens = data.ToolDefinitionsTokens,
IsInitial = data.IsInitial,
};
}
private SessionCompactionEventDto CreateCompactionStartEvent(
AgentIdentity agent,
SessionCompactionStartData? data)
ProviderSessionCompactionStartEvent data)
{
return new SessionCompactionEventDto
{
@@ -623,15 +621,15 @@ internal sealed class CopilotTurnExecutionState
AgentId = agent.AgentId,
AgentName = agent.AgentName,
Phase = "start",
SystemTokens = data?.SystemTokens,
ConversationTokens = data?.ConversationTokens,
ToolDefinitionsTokens = data?.ToolDefinitionsTokens,
SystemTokens = data.SystemTokens,
ConversationTokens = data.ConversationTokens,
ToolDefinitionsTokens = data.ToolDefinitionsTokens,
};
}
private SessionCompactionEventDto CreateCompactionCompleteEvent(
AgentIdentity agent,
SessionCompactionCompleteData? data)
ProviderSessionCompactionCompleteEvent data)
{
return new SessionCompactionEventDto
{
@@ -641,19 +639,19 @@ internal sealed class CopilotTurnExecutionState
AgentId = agent.AgentId,
AgentName = agent.AgentName,
Phase = "complete",
Success = data?.Success,
Error = data?.Error,
SystemTokens = data?.SystemTokens,
ConversationTokens = data?.ConversationTokens,
ToolDefinitionsTokens = data?.ToolDefinitionsTokens,
PreCompactionTokens = data?.PreCompactionTokens,
PostCompactionTokens = data?.PostCompactionTokens,
PreCompactionMessagesLength = data?.PreCompactionMessagesLength,
MessagesRemoved = data?.MessagesRemoved,
TokensRemoved = data?.TokensRemoved,
SummaryContent = data?.SummaryContent,
CheckpointNumber = data?.CheckpointNumber,
CheckpointPath = data?.CheckpointPath,
Success = data.Success,
Error = data.Error,
SystemTokens = data.SystemTokens,
ConversationTokens = data.ConversationTokens,
ToolDefinitionsTokens = data.ToolDefinitionsTokens,
PreCompactionTokens = data.PreCompactionTokens,
PostCompactionTokens = data.PostCompactionTokens,
PreCompactionMessagesLength = data.PreCompactionMessagesLength,
MessagesRemoved = data.MessagesRemoved,
TokensRemoved = data.TokensRemoved,
SummaryContent = data.SummaryContent,
CheckpointNumber = data.CheckpointNumber,
CheckpointPath = data.CheckpointPath,
};
}
@@ -44,6 +44,7 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
throw new InvalidOperationException(validationError);
}
IProviderEventAdapter providerEventAdapter = new CopilotEventAdapter();
CopilotTurnExecutionState state = new(command);
using CancellationTokenSource runCancellation =
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
@@ -70,7 +71,11 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
runCancellation.Token),
(agent, sessionEvent) =>
{
state.ObserveSessionEvent(agent, sessionEvent);
if (providerEventAdapter.TryAdapt(sessionEvent) is { } providerEvent)
{
state.ObserveSessionEvent(agent, providerEvent);
}
if (sessionEvent is McpOauthRequiredEvent mcpOauthRequired)
{
state.EnqueuePendingMcpOauthRequest(
@@ -0,0 +1,8 @@
using Aryx.AgentHost.Contracts;
namespace Aryx.AgentHost.Services;
internal interface IProviderEventAdapter
{
ProviderSessionEvent? TryAdapt(object rawEvent);
}
@@ -0,0 +1,25 @@
using Aryx.AgentHost.Contracts;
using Aryx.AgentHost.Services;
using GitHub.Copilot.SDK;
namespace Aryx.AgentHost.Tests;
internal static class SessionEventTestExtensions
{
private static readonly IProviderEventAdapter ProviderEventAdapter = new CopilotEventAdapter();
public static void ObserveSessionEvent(
this CopilotTurnExecutionState state,
WorkflowNodeDto agentDefinition,
SessionEvent sessionEvent)
{
ArgumentNullException.ThrowIfNull(state);
ArgumentNullException.ThrowIfNull(agentDefinition);
ArgumentNullException.ThrowIfNull(sessionEvent);
ProviderSessionEvent providerEvent = Assert.IsAssignableFrom<ProviderSessionEvent>(
ProviderEventAdapter.TryAdapt(sessionEvent));
state.ObserveSessionEvent(agentDefinition, providerEvent);
}
}