// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Workflows.Specialized; /// /// Provides an executor that accepts the output messages from each of the concurrent agents /// and produces a result list containing the last message from each. /// internal sealed class ConcurrentEndExecutor : Executor, IResettableExecutor { public const string ExecutorId = "ConcurrentEnd"; private readonly int _expectedInputs; private readonly Func>, List> _aggregator; private List> _allResults; private int _remaining; public ConcurrentEndExecutor(int expectedInputs, Func>, List> aggregator) : base(ExecutorId) { this._expectedInputs = expectedInputs; this._aggregator = Throw.IfNull(aggregator); this._allResults = new List>(expectedInputs); this._remaining = expectedInputs; } private void Reset() { this._allResults = new List>(this._expectedInputs); this._remaining = this._expectedInputs; } protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder protocolBuilder) { protocolBuilder.RouteBuilder.AddHandler>(async (messages, context, cancellationToken) => { // TODO: https://github.com/microsoft/agent-framework/issues/784 // This locking should not be necessary. bool done; lock (this._allResults) { this._allResults.Add(messages); done = --this._remaining == 0; } if (done) { this._remaining = this._expectedInputs; var results = this._allResults; this._allResults = new List>(this._expectedInputs); await context.YieldOutputAsync(this._aggregator(results), cancellationToken).ConfigureAwait(false); } }); return protocolBuilder.YieldsOutput>(); } public ValueTask ResetAsync() { this.Reset(); return default; } }