// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Workflows.InProc; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Workflows.UnitTests; /// /// Container for shared test helpers used by every orchestration-builder test class — /// the DoubleEchoAgent family and the RunWorkflow* methods. The actual /// test methods live in per-builder files (SequentialWorkflowBuilderTests, /// ConcurrentWorkflowBuilderTests, GroupChatWorkflowBuilderTests, etc.). /// public static class OrchestrationTestHelpers { internal class DoubleEchoAgent(string name) : AIAgent { public override string Name => name; protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => new(new DoubleEchoAgentSession()); protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new DoubleEchoAgentSession()); protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => default; protected override Task RunCoreAsync( IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override async IAsyncEnumerable RunCoreStreamingAsync( IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { await Task.Yield(); var contents = messages.SelectMany(m => m.Contents).ToList(); string id = Guid.NewGuid().ToString("N"); yield return new AgentResponseUpdate(ChatRole.Assistant, this.Name) { AuthorName = this.Name, MessageId = id }; yield return new AgentResponseUpdate(ChatRole.Assistant, contents) { AuthorName = this.Name, MessageId = id }; yield return new AgentResponseUpdate(ChatRole.Assistant, contents) { AuthorName = this.Name, MessageId = id }; } } internal sealed class DoubleEchoAgentSession() : AgentSession(); internal sealed class DoubleEchoAgentWithBarrier(string name, StrongBox> barrier, StrongBox remaining) : DoubleEchoAgent(name) { protected override async IAsyncEnumerable RunCoreStreamingAsync( IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { if (Interlocked.Decrement(ref remaining.Value) == 0) { barrier.Value!.SetResult(true); } await barrier.Value!.Task.ConfigureAwait(false); await foreach (var update in base.RunCoreStreamingAsync(messages, session, options, cancellationToken)) { await Task.Yield(); yield return update; } } } internal sealed record WorkflowRunResult(string UpdateText, List? Result, CheckpointInfo? LastCheckpoint, List PendingRequests); internal static async Task RunWorkflowCheckpointedAsync( Workflow workflow, List input, InProcessExecutionEnvironment environment, CheckpointInfo? fromCheckpoint = null) { await using StreamingRun run = fromCheckpoint != null ? await environment.ResumeStreamingAsync(workflow, fromCheckpoint) : await environment.OpenStreamingAsync(workflow); await run.TrySendMessageAsync(input); await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); return await ProcessWorkflowRunAsync(run); } internal static async Task ProcessWorkflowRunAsync(StreamingRun run) { StringBuilder sb = new(); WorkflowOutputEvent? output = null; CheckpointInfo? lastCheckpoint = null; List pendingRequests = []; await foreach (WorkflowEvent evt in run.WatchStreamAsync(blockOnPendingRequest: false).ConfigureAwait(false)) { switch (evt) { case AgentResponseUpdateEvent responseUpdate: sb.Append(responseUpdate.Data); break; case RequestInfoEvent requestInfo: pendingRequests.Add(requestInfo); break; case WorkflowOutputEvent e: output = e; break; case WorkflowErrorEvent errorEvent: Assert.Fail($"Workflow execution failed with error: {errorEvent.Exception}"); break; case SuperStepCompletedEvent stepCompleted: lastCheckpoint = stepCompleted.CompletionInfo?.Checkpoint; break; } } return new(sb.ToString(), output?.As>(), lastCheckpoint, pendingRequests); } internal static Task RunWorkflowAsync( Workflow workflow, List input, ExecutionEnvironment executionEnvironment = ExecutionEnvironment.InProcess_Lockstep) => RunWorkflowCheckpointedAsync(workflow, input, executionEnvironment.ToWorkflowExecutionEnvironment()); }