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