// 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;
}
}