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