// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Agents.AI.Workflows;
///
/// Executes a workflow step that incrementally aggregates input messages using a user-provided aggregation function.
///
/// The aggregate state is persisted and restored automatically during workflow checkpointing. This
/// executor is suitable for scenarios where stateful, incremental aggregation of messages is required, such as running
/// totals or event accumulation.
/// The type of input messages to be processed and aggregated.
/// The type representing the aggregate state produced by the aggregator function.
/// The unique identifier for this executor instance.
/// A function that computes the new aggregate state from the previous aggregate and the current input message. The
/// function receives the current aggregate (or null if this is the first message) and the input message, and returns
/// the updated aggregate.
/// Optional configuration settings for the executor. If null, default options are used.
/// Declare that this executor may be used simultaneously by multiple runs safely.
///
public class AggregatingExecutor(string id,
Func aggregator,
ExecutorOptions? options = null,
bool declareCrossRunShareable = false) : Executor(id, options, declareCrossRunShareable)
{
private const string AggregateStateKey = "Aggregate";
///
public override async ValueTask HandleAsync(TInput message, IWorkflowContext context, CancellationToken cancellationToken)
{
TAggregate? runningAggregate = default;
await context.InvokeWithStateAsync(InvokeAggregatorAsync, AggregateStateKey, cancellationToken: cancellationToken)
.ConfigureAwait(false);
return runningAggregate;
ValueTask InvokeAggregatorAsync(PortableValue? maybeState, IWorkflowContext context, CancellationToken cancellationToken)
{
if (maybeState == null || !maybeState.Is(out runningAggregate))
{
runningAggregate = default;
}
runningAggregate = aggregator(runningAggregate, message);
if (runningAggregate == null)
{
return new((PortableValue?)null);
}
return new(new PortableValue(runningAggregate));
}
}
}