diff --git a/sidecar/src/Aryx.AgentHost/Contracts/ProviderSessionEvent.cs b/sidecar/src/Aryx.AgentHost/Contracts/ProviderSessionEvent.cs new file mode 100644 index 0000000..b4ad9d2 --- /dev/null +++ b/sidecar/src/Aryx.AgentHost/Contracts/ProviderSessionEvent.cs @@ -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? 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? Tools) : ProviderSessionEvent; + +internal sealed record ProviderSubagentDeselectedEvent() : ProviderSessionEvent; + +internal sealed record ProviderSkillInvokedEvent( + string SkillName, + string Path, + string Content, + IReadOnlyList? 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? 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; diff --git a/sidecar/src/Aryx.AgentHost/Services/CopilotEventAdapter.cs b/sidecar/src/Aryx.AgentHost/Services/CopilotEventAdapter.cs new file mode 100644 index 0000000..6a2b587 --- /dev/null +++ b/sidecar/src/Aryx.AgentHost/Services/CopilotEventAdapter.cs @@ -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(); + } +} diff --git a/sidecar/src/Aryx.AgentHost/Services/CopilotTurnExecutionState.cs b/sidecar/src/Aryx.AgentHost/Services/CopilotTurnExecutionState.cs index 11b05b5..fd10b34 100644 --- a/sidecar/src/Aryx.AgentHost/Services/CopilotTurnExecutionState.cs +++ b/sidecar/src/Aryx.AgentHost/Services/CopilotTurnExecutionState.cs @@ -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? 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, }; } diff --git a/sidecar/src/Aryx.AgentHost/Services/CopilotWorkflowRunner.cs b/sidecar/src/Aryx.AgentHost/Services/CopilotWorkflowRunner.cs index dc0b338..70cddb3 100644 --- a/sidecar/src/Aryx.AgentHost/Services/CopilotWorkflowRunner.cs +++ b/sidecar/src/Aryx.AgentHost/Services/CopilotWorkflowRunner.cs @@ -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( diff --git a/sidecar/src/Aryx.AgentHost/Services/IProviderEventAdapter.cs b/sidecar/src/Aryx.AgentHost/Services/IProviderEventAdapter.cs new file mode 100644 index 0000000..55ceb6e --- /dev/null +++ b/sidecar/src/Aryx.AgentHost/Services/IProviderEventAdapter.cs @@ -0,0 +1,8 @@ +using Aryx.AgentHost.Contracts; + +namespace Aryx.AgentHost.Services; + +internal interface IProviderEventAdapter +{ + ProviderSessionEvent? TryAdapt(object rawEvent); +} diff --git a/sidecar/tests/Aryx.AgentHost.Tests/SessionEventTestExtensions.cs b/sidecar/tests/Aryx.AgentHost.Tests/SessionEventTestExtensions.cs new file mode 100644 index 0000000..5640dc9 --- /dev/null +++ b/sidecar/tests/Aryx.AgentHost.Tests/SessionEventTestExtensions.cs @@ -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( + ProviderEventAdapter.TryAdapt(sessionEvent)); + + state.ObserveSessionEvent(agentDefinition, providerEvent); + } +}