// Copyright (c) Microsoft. All rights reserved. #pragma warning disable CS0618 // Type or member is obsolete - Internal use of obsolete types for backward compatibility using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Workflows.Reflection; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Workflows; /// /// Provides a base class for executors that maintain and manage state across multiple message handling operations. /// /// The type of state associated with this Executor. public abstract class StatefulExecutor : Executor { private readonly Func _initialStateFactory; private TState? _stateCache; /// /// Initializes the executor with a unique id and an initial value for the state. /// /// The unique identifier for this executor instance. Cannot be null or empty. /// A factory to initialize the state value to be used by the executor. /// Optional configuration settings for the executor. If null, default options are used. /// true to declare that the executor's state can be shared across multiple runs; otherwise, false. protected StatefulExecutor(string id, Func initialStateFactory, StatefulExecutorOptions? options = null, bool declareCrossRunShareable = false) : base(id, options ?? new StatefulExecutorOptions(), declareCrossRunShareable) { this.Options = (StatefulExecutorOptions)base.Options; this._initialStateFactory = Throw.IfNull(initialStateFactory); } /// protected new StatefulExecutorOptions Options { get; } private string DefaultStateKey => $"{this.GetType().Name}.State"; /// /// Gets the key used to identify the executor's state. /// protected string StateKey => this.Options.StateKey ?? this.DefaultStateKey; /// /// Reads the state associated with this executor. If it is not initialized, it will be set to the initial state. /// /// The workflow context in which the executor executes. /// Ignore the cached value, if any. State is not cached when running in Cross-Run Shareable /// mode. /// The to monitor for cancellation requests. /// The default is . /// protected async ValueTask ReadStateAsync(IWorkflowContext context, bool skipCache = false, CancellationToken cancellationToken = default) { if (!skipCache && this._stateCache is not null) { return this._stateCache; } TState? state = await context.ReadOrInitStateAsync(this.StateKey, this._initialStateFactory, this.Options.ScopeName, cancellationToken) .ConfigureAwait(false); if (!context.ConcurrentRunsEnabled) { this._stateCache = state; } return state; } /// /// Queues up an update to the executor's state. /// /// The new value of state. /// The workflow context in which the executor executes. /// The to monitor for cancellation requests. /// The default is . /// protected ValueTask QueueStateUpdateAsync(TState state, IWorkflowContext context, CancellationToken cancellationToken = default) { if (!context.ConcurrentRunsEnabled) { this._stateCache = state; } return context.QueueStateUpdateAsync(this.StateKey, state, this.Options.ScopeName, cancellationToken); } /// /// Invokes an asynchronous operation that reads, updates, and persists workflow state associated with the specified /// key. /// /// A delegate that receives the current state, workflow context, and cancellation token, /// and returns the updated state asynchronously. /// The workflow context in which the executor executes. /// Ignore the cached value, if any. State is not cached when running in Cross-Run Shareable /// mode. /// The to monitor for cancellation requests. /// The default is . /// A ValueTask that represents the asynchronous operation. protected async ValueTask InvokeWithStateAsync( Func> invocation, IWorkflowContext context, bool skipCache = false, CancellationToken cancellationToken = default) { if (!skipCache && !context.ConcurrentRunsEnabled) { TState newState = await invocation(this._stateCache ?? this._initialStateFactory(), context, cancellationToken).ConfigureAwait(false) ?? this._initialStateFactory(); await context.QueueStateUpdateAsync(this.StateKey, newState, this.Options.ScopeName, cancellationToken).ConfigureAwait(false); this._stateCache = newState; } else { await context.InvokeWithStateAsync(invocation, this.StateKey, this._initialStateFactory, this.Options.ScopeName, cancellationToken) .ConfigureAwait(false); } } /// protected ValueTask ResetAsync() { this._stateCache = this._initialStateFactory(); return default; } } /// /// Provides a simple executor implementation that uses a single message handler function to process incoming messages, /// and maintain state across invocations. /// /// The type of state associated with this Executor. /// The type of input message. /// A unique identifier for the executor. /// A factory to initialize the state value to be used by the executor. /// Configuration options for the executor. If null, default options will be used. /// Declare that this executor may be used simultaneously by multiple runs safely. public abstract class StatefulExecutor(string id, Func initialStateFactory, StatefulExecutorOptions? options = null, bool declareCrossRunShareable = false) : StatefulExecutor(id, initialStateFactory, options, declareCrossRunShareable), IMessageHandler { /// protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) => routeBuilder.AddHandler(this.HandleAsync); /// public abstract ValueTask HandleAsync(TInput message, IWorkflowContext context, CancellationToken cancellationToken = default); } /// /// Provides a simple executor implementation that uses a single message handler function to process incoming messages, /// and maintain state across invocations. /// /// The type of state associated with this Executor. /// The type of input message. /// The type of output message. /// A unique identifier for the executor. /// A factory to initialize the state value to be used by the executor. /// Configuration options for the executor. If null, default options will be used. /// Declare that this executor may be used simultaneously by multiple runs safely. public abstract class StatefulExecutor(string id, Func initialStateFactory, StatefulExecutorOptions? options = null, bool declareCrossRunShareable = false) : StatefulExecutor(id, initialStateFactory, options, declareCrossRunShareable), IMessageHandler { /// protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) => routeBuilder.AddHandler(this.HandleAsync); /// public abstract ValueTask HandleAsync(TInput message, IWorkflowContext context, CancellationToken cancellationToken = default); }