// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Agents.AI.Workflows;
///
/// Executes a user-provided asynchronous function in response to workflow messages of the specified input type.
///
/// The type of input message.
/// A unique identifier for the executor.
/// A delegate that defines the asynchronous function to execute for each input message.
/// Configuration options for the executor. If null, default options will be used.
/// Message types sent by the handler. Defaults to empty, and will filter out non-matching messages.
/// Message types yielded as output by the handler. Defaults to empty.
/// Declare that this executor may be used simultaneously by multiple runs safely.
public class FunctionExecutor(string id,
Func handlerAsync,
ExecutorOptions? options = null,
IEnumerable? sentMessageTypes = null,
IEnumerable? outputTypes = null,
bool declareCrossRunShareable = false) : Executor(id, options, declareCrossRunShareable)
{
internal static Func WrapAction(Action handlerSync, out IEnumerable sentTypes, out IEnumerable yieldedTypes)
{
if (handlerSync.Method != null)
{
MethodInfo method = handlerSync.Method;
(sentTypes, yieldedTypes) = method.GetAttributeTypes();
}
else
{
sentTypes = yieldedTypes = [];
}
return RunActionAsync;
ValueTask RunActionAsync(TInput input, IWorkflowContext workflowContext, CancellationToken cancellationToken)
{
handlerSync(input, workflowContext, cancellationToken);
return default;
}
}
///
protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder protocolBuilder) =>
base.ConfigureProtocol(protocolBuilder)
// We have to register the delegate handlers here because the base class gets the RunActionAsync local function in
// WrapAction, which cannot have the right annotations.
.AddDelegateAttributeTypes(handlerAsync)
.SendsMessageTypes(sentMessageTypes ?? [])
.YieldsOutputTypes(outputTypes ?? []);
///
public override ValueTask HandleAsync(TInput message, IWorkflowContext context, CancellationToken cancellationToken) => handlerAsync(message, context, cancellationToken);
///
/// Creates a new instance of the class.
///
/// A unique identifier for the executor.
/// A synchronous function to execute for each input message and workflow context.
/// Configuration options for the executor. If null, default options will be used.
/// Message types sent by the handler. Defaults to empty, and will filter out non-matching messages.
/// Message types yielded as output by the handler. Defaults to empty.
/// Declare that this executor may be used simultaneously by multiple runs safely.
public FunctionExecutor(string id,
Action handlerSync,
ExecutorOptions? options = null,
IEnumerable? sentMessageTypes = null,
IEnumerable? outputTypes = null,
bool declareCrossRunShareable = false) : this(id,
WrapAction(handlerSync,
out var attributeSentTypes,
out var attributeYieldTypes),
options,
attributeSentTypes.Concat(sentMessageTypes ?? []),
attributeYieldTypes.Concat(outputTypes ?? []),
declareCrossRunShareable)
{
}
}
///
/// Executes a user-provided asynchronous function in response to workflow messages of the specified input type,
///
/// The type of input message.
/// The type of output message.
/// A unique identifier for the executor.
/// A delegate that defines the asynchronous function to execute for each input message.
/// Configuration options for the executor. If null, default options will be used.
/// Additional message types sent by the handler. Defaults to empty, and will filter out non-matching messages.
/// Additional message types yielded as output by the handler. Defaults to empty.
/// Declare that this executor may be used simultaneously by multiple runs safely.
public class FunctionExecutor(string id,
Func> handlerAsync,
ExecutorOptions? options = null,
IEnumerable? sentMessageTypes = null,
IEnumerable? outputTypes = null,
bool declareCrossRunShareable = false) : Executor(id, options, declareCrossRunShareable)
{
internal static Func> WrapFunc(Func handlerSync, out IEnumerable sentTypes, out IEnumerable yieldedTypes)
{
if (handlerSync.Method != null)
{
MethodInfo method = handlerSync.Method;
(sentTypes, yieldedTypes) = method.GetAttributeTypes();
}
else
{
sentTypes = yieldedTypes = [];
}
return RunFuncAsync;
ValueTask RunFuncAsync(TInput input, IWorkflowContext workflowContext, CancellationToken cancellationToken)
{
TOutput result = handlerSync(input, workflowContext, cancellationToken);
return new ValueTask(result);
}
}
///
protected override ProtocolBuilder ConfigureProtocol(ProtocolBuilder protocolBuilder) =>
base.ConfigureProtocol(protocolBuilder)
// We have to register the delegate handlers here because the base class gets the RunFuncAsync local function in
// WrapFunc, which cannot have the right annotations.
.AddDelegateAttributeTypes(handlerAsync)
.SendsMessageTypes(sentMessageTypes ?? [])
.YieldsOutputTypes(outputTypes ?? []);
///
public override ValueTask HandleAsync(TInput message, IWorkflowContext context, CancellationToken cancellationToken) => handlerAsync(message, context, cancellationToken);
///
/// Creates a new instance of the class.
///
/// A unique identifier for the executor.
/// A synchronous function to execute for each input message and workflow context.
/// Configuration options for the executor. If null, default options will be used.
/// Additional message types sent by the handler. Defaults to empty, and will filter out non-matching messages.
/// Additional message types yielded as output by the handler. Defaults to empty.
/// Declare that this executor may be used simultaneously by multiple runs safely.
public FunctionExecutor(string id,
Func handlerSync,
ExecutorOptions? options = null,
IEnumerable? sentMessageTypes = null,
IEnumerable? outputTypes = null,
bool declareCrossRunShareable = false) : this(id,
WrapFunc(handlerSync,
out var attributeSentTypes,
out var attributeYieldTypes),
options,
attributeSentTypes.Concat(sentMessageTypes ?? []),
attributeYieldTypes.Concat(outputTypes ?? []),
declareCrossRunShareable)
{
}
}