// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Agents.AI.Workflows; /// /// Provides extension methods for working with instances. /// public static class WorkflowContextExtensions { /// /// Invokes an asynchronous operation that reads, updates, and persists workflow state associated with the specified /// key. /// /// The type of the state object to read, update, and persist. /// The workflow context used to access and update state. /// A delegate that receives the current state, workflow context, and cancellation token, and returns the updated /// state asynchronously. /// The key identifying the state to read and update. Cannot be null or empty. /// An optional scope name that further qualifies the state key. If null, the default scope is used. /// A cancellation token that can be used to cancel the asynchronous operation. /// A ValueTask that represents the asynchronous operation. public static async ValueTask InvokeWithStateAsync(this IWorkflowContext context, Func> invocation, string key, string? scopeName = null, CancellationToken cancellationToken = default) { TState? state = await context.ReadStateAsync(key, scopeName, cancellationToken).ConfigureAwait(false); state = await invocation(state, context, cancellationToken).ConfigureAwait(false); await context.QueueStateUpdateAsync(key, state, scopeName, cancellationToken).ConfigureAwait(false); } /// /// Invokes an asynchronous operation that reads, updates, and persists workflow state associated with the specified /// key. /// /// The type of the state object to read, update, and persist. /// The workflow context used to access and update state. /// A delegate that receives the current state, workflow context, and cancellation token, and returns the updated /// state asynchronously. /// The key identifying the state to read and update. Cannot be null or empty. /// A factory to initialize state to if it is not set at the provided key. /// An optional scope name that further qualifies the state key. If null, the default scope is used. /// A cancellation token that can be used to cancel the asynchronous operation. /// A ValueTask that represents the asynchronous operation. public static async ValueTask InvokeWithStateAsync(this IWorkflowContext context, Func> invocation, string key, Func initialStateFactory, string? scopeName = null, CancellationToken cancellationToken = default) { TState? state = await context.ReadOrInitStateAsync(key, initialStateFactory, scopeName, cancellationToken).ConfigureAwait(false); state = await invocation(state, context, cancellationToken).ConfigureAwait(false); await context.QueueStateUpdateAsync(key, state ?? initialStateFactory(), scopeName, cancellationToken).ConfigureAwait(false); } } /// /// Provides services for an during the execution of a workflow. /// public interface IWorkflowContext { /// /// Adds an event to the workflow's output queue. These events will be raised to the caller of the workflow at the /// end of the current SuperStep. /// /// The event to be raised. /// The to monitor for cancellation requests. /// The default is . /// A representing the asynchronous operation. ValueTask AddEventAsync(WorkflowEvent workflowEvent, CancellationToken cancellationToken = default); /// /// Queues a message to be sent to connected executors. The message will be sent during the next SuperStep. /// /// The message to be sent. /// An optional identifier of the target executor. If null, the message is sent to all connected /// executors. If the target executor is not connected from this executor via an edge, it will still not receive the /// message. /// The to monitor for cancellation requests. /// The default is . /// A representing the asynchronous operation. ValueTask SendMessageAsync(object message, string? targetId = null, CancellationToken cancellationToken = default); #if NET // What's the right way to do this so we do not make life a misery for netstandard2.0 targets? // What's the value if they have to still write `cancellationToken: cancellationToken` to skip the targetId parameter? // TODO: Remove this? (Maybe not: NET will eventually be the only target framework, right?) /// /// Queues a message to be sent to connected executors. The message will be sent during the next SuperStep. /// /// The message to be sent. /// The to monitor for cancellation requests. /// A representing the asynchronous operation. ValueTask SendMessageAsync(object message, CancellationToken cancellationToken) => this.SendMessageAsync(message, null, cancellationToken); #endif /// /// Adds an output value to the workflow's output queue. These outputs will be bubbled out of the workflow using the /// /// /// /// The type of the output message must match one of the output types declared by the Executor. By default, the return /// types of registered message handlers are considered output types, unless otherwise specified using . /// /// The output value to be returned. /// The to monitor for cancellation requests. /// The default is . /// A representing the asynchronous operation. ValueTask YieldOutputAsync(object output, CancellationToken cancellationToken = default); /// /// Adds a request to "halt" workflow execution at the end of the current SuperStep. /// /// ValueTask RequestHaltAsync(); /// /// Reads a state value from the workflow's state store. If no scope is provided, the executor's /// default scope is used. /// /// The type of the state value. /// The key of the state value. /// An optional name that specifies the scope to read.If null, the default scope is /// used. /// The to monitor for cancellation requests. /// The default is . /// A representing the asynchronous operation. ValueTask ReadStateAsync(string key, string? scopeName = null, CancellationToken cancellationToken = default); /// /// Reads or initialized a state value from the workflow's state store. If no scope is provided, the executor's /// default scope is used. /// /// /// When initializing the state, the state will be queued as an update. If multiple initializations are done in the same /// SuperStep from different executors, an error will be generated at the end of the SuperStep. /// /// The type of the state value. /// The key of the state value. /// A factory to initialize the state if the key has no value associated with it. /// An optional name that specifies the scope to read. If null, the default scope is /// used. /// The to monitor for cancellation requests. /// The default is . /// A representing the asynchronous operation. ValueTask ReadOrInitStateAsync(string key, Func initialStateFactory, string? scopeName = null, CancellationToken cancellationToken = default); #if NET // See above for musings about this construction /// /// Reads a state value from the workflow's state store. If no scope is provided, the executor's /// default scope is used. /// /// The type of the state value. /// The key of the state value. /// The to monitor for cancellation requests. /// A representing the asynchronous operation. ValueTask ReadStateAsync(string key, CancellationToken cancellationToken) => this.ReadStateAsync(key, null, cancellationToken); /// /// Reads a state value from the workflow's state store. If no scope is provided, the executor's /// default scope is used. /// /// The type of the state value. /// The key of the state value. /// A factory to initialize the state if the key has no value associated with it. /// The to monitor for cancellation requests. /// The default is . /// A representing the asynchronous operation. ValueTask ReadOrInitStateAsync(string key, Func initialStateFactory, CancellationToken cancellationToken) => this.ReadOrInitStateAsync(key, initialStateFactory, null, cancellationToken); #endif /// /// Asynchronously reads all state keys within the specified scope. /// /// An optional name that specifies the scope to read. If null, the default scope is /// used. /// The to monitor for cancellation requests. /// The default is . ValueTask> ReadStateKeysAsync(string? scopeName = null, CancellationToken cancellationToken = default); /// /// Asynchronously updates the state of a queue entry identified by the specified key and optional scope. /// /// /// Subsequent reads by this executor will result in the new value of the state. Other executors will only see /// the new state starting from the next SuperStep. /// /// The type of the value to associate with the queue entry. /// The unique identifier for the queue entry to update. Cannot be null or empty. /// The value to set for the queue entry. If null, the entry's state may be cleared or reset depending on /// implementation. /// An optional name that specifies the scope to update. If null, the default scope is /// used. /// The to monitor for cancellation requests. /// The default is . /// A ValueTask that represents the asynchronous update operation. ValueTask QueueStateUpdateAsync(string key, T? value, string? scopeName = null, CancellationToken cancellationToken = default); #if NET // See above for musings about this construction /// /// Asynchronously updates the state of a queue entry identified by the specified key and optional scope. /// /// /// Subsequent reads by this executor will result in the new value of the state. Other executors will only see /// the new state starting from the next SuperStep. /// /// The type of the value to associate with the queue entry. /// The unique identifier for the queue entry to update. Cannot be null or empty. /// The value to set for the queue entry. If null, the entry's state may be cleared or reset depending on /// implementation. /// The to monitor for cancellation requests. /// A ValueTask that represents the asynchronous update operation. ValueTask QueueStateUpdateAsync(string key, T? value, CancellationToken cancellationToken) => this.QueueStateUpdateAsync(key, value, null, cancellationToken); #endif /// /// Asynchronously clears all state entries within the specified scope. /// /// This semantically equivalent to retrieving all keys in the scope and deleting them one-by-one. /// /// /// Subsequent reads by this executor will not find any entries in the cleared scope. Other executors will only /// see the cleared state starting from the next SuperStep. /// /// An optional name that specifies the scope to clear. If null, the default scope is used. /// The to monitor for cancellation requests. /// The default is . /// A ValueTask that represents the asynchronous clear operation. ValueTask QueueClearScopeAsync(string? scopeName = null, CancellationToken cancellationToken = default); #if NET // See above for musings about this construction /// /// Asynchronously clears all state entries within the specified scope. /// /// This semantically equivalent to retrieving all keys in the scope and deleting them one-by-one. /// /// /// Subsequent reads by this executor will not find any entries in the cleared scope. Other executors will only /// see the cleared state starting from the next SuperStep. /// /// The to monitor for cancellation requests. /// A ValueTask that represents the asynchronous clear operation. ValueTask QueueClearScopeAsync(CancellationToken cancellationToken) => this.QueueClearScopeAsync(null, cancellationToken); #endif /// /// The trace context associated with the current message about to be processed by the executor, if any. /// IReadOnlyDictionary? TraceContext { get; } /// /// Whether the current execution environment support concurrent runs against the same workflow instance. /// bool ConcurrentRunsEnabled { get; } }