Files
agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/ExecutorBindingExtensions.cs
Jacob Alber 0086d38f58 .NET: [BREAKING] Workflows API Review Naming Changes (Part 1?) (#4090)
* refactor: Normalize Run/RunStreaming with AIAgent

* refactor: Clarify Session vs. Run -level concepts

* Rename RunId to SessionId to better match Run/Session terminology in AIAgent
* [BREAKING]: Will break existing checkpointed sessions in CosmosDb due to field rename

* refactor: Rename and simplify interface around getting typed data out of ExternalRequest/Response

* Also adds hints around using value types in PortableValue

* refactor: Rename AddFanInEdge to AddFanInBarrierEdge

This will prevent a breaking change later when we introduce a programmable FanIn edge, analogous to the FanOut edge's EdgeSelector.

The goal, in the long run is to support a number of different FanIn scenarios, with naive FanIn (no barrier) by default, similar to FanOut.

* refactor: AsAgent(this Workflow, ...) => AsAIAgent(...)

* misc - part1: SwitchBuilder internal

---------

Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
2026-02-20 02:05:18 +00:00

444 lines
34 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows;
/// <summary>
/// Extension methods for configuring executors and functions as <see cref="ExecutorBinding"/> instances.
/// </summary>
public static class ExecutorBindingExtensions
{
/// <summary>
/// Configures an <see cref="Executor"/> instance for use in a workflow.
/// </summary>
/// <remarks>
/// Note that Executor Ids must be unique within a workflow.
/// </remarks>
/// <param name="executor">The executor instance.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance wrapping the specified <see cref="Executor"/>.</returns>
public static ExecutorBinding BindExecutor(this Executor executor)
=> new ExecutorInstanceBinding(executor);
/// <summary>
/// Configures a factory method for creating an <see cref="Executor"/> of type <typeparamref name="TExecutor"/>, using the
/// type name as the id.
/// </summary>
/// <remarks>
/// Note that Executor Ids must be unique within a workflow.
///
/// Although this will generally result in a delay-instantiated <see cref="Executor"/> once messages are available
/// for it, it will be instantiated if a <see cref="ProtocolDescriptor"/> for the <see cref="Workflow"/> is requested,
/// and it is the starting executor.
/// </remarks>
/// <typeparam name="TExecutor">The type of the resulting executor</typeparam>
/// <param name="factoryAsync">The factory method.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that resolves to the result of the factory call when messages get sent to it.</returns>
public static ExecutorBinding BindExecutor<TExecutor>(this Func<string, string, ValueTask<TExecutor>> factoryAsync)
where TExecutor : Executor
=> BindExecutor<TExecutor, ExecutorOptions>((config, sessionId) => factoryAsync(config.Id, sessionId), id: typeof(TExecutor).Name, options: null);
/// <summary>
/// Configures a factory method for creating an <see cref="Executor"/> of type <typeparamref name="TExecutor"/>, using the
/// type name as the id.
/// </summary>
/// <remarks>
/// Note that Executor Ids must be unique within a workflow.
///
/// Although this will generally result in a delay-instantiated <see cref="Executor"/> once messages are available
/// for it, it will be instantiated if a <see cref="ProtocolDescriptor"/> for the <see cref="Workflow"/> is requested,
/// and it is the starting executor.
/// </remarks>
/// <typeparam name="TExecutor">The type of the resulting executor</typeparam>
/// <param name="factoryAsync">The factory method.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that resolves to the result of the factory call when messages get sent to it.</returns>
[Obsolete("Use BindExecutor() instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static ExecutorBinding ConfigureFactory<TExecutor>(this Func<string, string, ValueTask<TExecutor>> factoryAsync)
where TExecutor : Executor
=> factoryAsync.BindExecutor();
/// <summary>
/// Configures a factory method for creating an <see cref="Executor"/> of type <typeparamref name="TExecutor"/>, with
/// the specified id.
/// </summary>
/// <remarks>
/// Although this will generally result in a delay-instantiated <see cref="Executor"/> once messages are available
/// for it, it will be instantiated if a <see cref="ProtocolDescriptor"/> for the <see cref="Workflow"/> is requested,
/// and it is the starting executor.
/// </remarks>
/// <typeparam name="TExecutor">The type of the resulting executor</typeparam>
/// <param name="factoryAsync">The factory method.</param>
/// <param name="id">An id for the executor to be instantiated.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that resolves to the result of the factory call when messages get sent to it.</returns>
public static ExecutorBinding BindExecutor<TExecutor>(this Func<string, string, ValueTask<TExecutor>> factoryAsync, string id)
where TExecutor : Executor
=> BindExecutor<TExecutor, ExecutorOptions>((_, sessionId) => factoryAsync(id, sessionId), id, options: null);
/// <summary>
/// Configures a factory method for creating an <see cref="Executor"/> of type <typeparamref name="TExecutor"/>, with
/// the specified id.
/// </summary>
/// <remarks>
/// Although this will generally result in a delay-instantiated <see cref="Executor"/> once messages are available
/// for it, it will be instantiated if a <see cref="ProtocolDescriptor"/> for the <see cref="Workflow"/> is requested,
/// and it is the starting executor.
/// </remarks>
/// <typeparam name="TExecutor">The type of the resulting executor</typeparam>
/// <param name="factoryAsync">The factory method.</param>
/// <param name="id">An id for the executor to be instantiated.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that resolves to the result of the factory call when messages get sent to it.</returns>
[Obsolete("Use BindExecutor() instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static ExecutorBinding ConfigureFactory<TExecutor>(this Func<string, string, ValueTask<TExecutor>> factoryAsync, string id)
where TExecutor : Executor
=> factoryAsync.BindExecutor(id);
/// <summary>
/// Configures a factory method for creating an <see cref="Executor"/> of type <typeparamref name="TExecutor"/>, with
/// the specified id and options.
/// </summary>
/// <remarks>
/// Although this will generally result in a delay-instantiated <see cref="Executor"/> once messages are available
/// for it, it will be instantiated if a <see cref="ProtocolDescriptor"/> for the <see cref="Workflow"/> is requested,
/// and it is the starting executor.
/// </remarks>
/// <typeparam name="TExecutor">The type of the resulting executor</typeparam>
/// <typeparam name="TOptions">The type of options object to be passed to the factory method.</typeparam>
/// <param name="factoryAsync">The factory method.</param>
/// <param name="id">An id for the executor to be instantiated.</param>
/// <param name="options">An optional parameter specifying the options.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that resolves to the result of the factory call when messages get sent to it.</returns>
public static ExecutorBinding BindExecutor<TExecutor, TOptions>(this Func<Config<TOptions>, string, ValueTask<TExecutor>> factoryAsync, string id, TOptions? options = null)
where TExecutor : Executor
where TOptions : ExecutorOptions
{
Configured<TExecutor, TOptions> configured = new(factoryAsync, id, options);
return new ConfiguredExecutorBinding(configured.Super<TExecutor, Executor, TOptions>(), typeof(TExecutor));
}
/// <summary>
/// Configures a factory method for creating an <see cref="Executor"/> of type <typeparamref name="TExecutor"/>, with
/// the specified id and options.
/// </summary>
/// <remarks>
/// Although this will generally result in a delay-instantiated <see cref="Executor"/> once messages are available
/// for it, it will be instantiated if a <see cref="ProtocolDescriptor"/> for the <see cref="Workflow"/> is requested,
/// and it is the starting executor.
/// </remarks>
/// <typeparam name="TExecutor">The type of the resulting executor</typeparam>
/// <typeparam name="TOptions">The type of options object to be passed to the factory method.</typeparam>
/// <param name="factoryAsync">The factory method.</param>
/// <param name="id">An id for the executor to be instantiated.</param>
/// <param name="options">An optional parameter specifying the options.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that resolves to the result of the factory call when messages get sent to it.</returns>
[Obsolete("Use BindExecutor() instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static ExecutorBinding ConfigureFactory<TExecutor, TOptions>(this Func<Config<TOptions>, string, ValueTask<TExecutor>> factoryAsync, string id, TOptions? options = null)
where TExecutor : Executor
where TOptions : ExecutorOptions
=> factoryAsync.BindExecutor(id, options);
private static ConfiguredExecutorBinding ToBinding<TInput>(this FunctionExecutor<TInput> executor, Delegate raw)
=> new(Configured.FromInstance(executor, raw: raw)
.Super<FunctionExecutor<TInput>, Executor>(),
typeof(FunctionExecutor<TInput>));
private static ConfiguredExecutorBinding ToBinding<TInput, TOutput>(this FunctionExecutor<TInput, TOutput> executor, Delegate raw)
=> new(Configured.FromInstance(executor, raw: raw)
.Super<FunctionExecutor<TInput, TOutput>, Executor>(),
typeof(FunctionExecutor<TInput, TOutput>));
/// <summary>
/// Configures a sub-workflow executor for the specified workflow, using the provided identifier and options.
/// </summary>
/// <param name="workflow">The workflow instance to be executed as a sub-workflow. Cannot be null.</param>
/// <param name="id">A unique identifier for the sub-workflow execution. Used to distinguish this sub-workflow instance.</param>
/// <param name="options">Optional configuration options for the sub-workflow executor. If null, default options are used.</param>
/// <returns>An ExecutorRegistration instance representing the configured sub-workflow executor.</returns>
[Obsolete("Use BindAsExecutor() instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static ExecutorBinding ConfigureSubWorkflow(this Workflow workflow, string id, ExecutorOptions? options = null)
=> workflow.BindAsExecutor(id, options);
/// <summary>
/// Configures a sub-workflow executor for the specified workflow, using the provided identifier and options.
/// </summary>
/// <param name="workflow">The workflow instance to be executed as a sub-workflow. Cannot be null.</param>
/// <param name="id">A unique identifier for the sub-workflow execution. Used to distinguish this sub-workflow instance.</param>
/// <param name="options">Optional configuration options for the sub-workflow executor. If null, default options are used.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance representing the configured sub-workflow executor.</returns>
public static ExecutorBinding BindAsExecutor(this Workflow workflow, string id, ExecutorOptions? options = null)
=> new SubworkflowBinding(workflow, id, options);
/// <summary>
/// Configures a function-based asynchronous message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <param name="messageHandlerAsync">A delegate that defines the asynchronous function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput>(this Func<TInput, IWorkflowContext, CancellationToken, ValueTask> messageHandlerAsync, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> new FunctionExecutor<TInput>(id, messageHandlerAsync, options, declareCrossRunShareable: threadsafe).ToBinding(messageHandlerAsync);
/// <summary>
/// Configures a function-based asynchronous message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <param name="messageHandlerAsync">A delegate that defines the asynchronous function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput>(this Func<TInput, ValueTask> messageHandlerAsync, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, ValueTask>)((input, _, __) => messageHandlerAsync(input)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based asynchronous message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <param name="messageHandlerAsync">A delegate that defines the asynchronous function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput>(this Func<TInput, IWorkflowContext, ValueTask> messageHandlerAsync, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, ValueTask>)((input, ctx, __) => messageHandlerAsync(input, ctx)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based asynchronous message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <param name="messageHandlerAsync">A delegate that defines the asynchronous function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput>(this Func<TInput, CancellationToken, ValueTask> messageHandlerAsync, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, ValueTask>)((input, _, ct) => messageHandlerAsync(input, ct)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <param name="messageHandler">A delegate that defines the function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput>(this Action<TInput, IWorkflowContext, CancellationToken> messageHandler, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> new FunctionExecutor<TInput>(id, messageHandler, options, declareCrossRunShareable: threadsafe).ToBinding(messageHandler);
/// <summary>
/// Configures a function-based message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <param name="messageHandler">A delegate that defines the function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput>(this Action<TInput> messageHandler, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Action<TInput, IWorkflowContext, CancellationToken>)((input, _, __) => messageHandler(input)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <param name="messageHandler">A delegate that defines the function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput>(this Action<TInput, IWorkflowContext> messageHandler, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Action<TInput, IWorkflowContext, CancellationToken>)((input, ctx, __) => messageHandler(input, ctx)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <param name="messageHandler">A delegate that defines the function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput>(this Action<TInput, CancellationToken> messageHandler, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Action<TInput, IWorkflowContext, CancellationToken>)((input, _, ct) => messageHandler(input, ct)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based asynchronous message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TOutput">The type of output message.</typeparam>
/// <param name="messageHandlerAsync">A delegate that defines the asynchronous function to execute for each input message.</param>
/// <param name="id">A unique identifier for the executor.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TOutput>(this Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>> messageHandlerAsync, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> new FunctionExecutor<TInput, TOutput>(Throw.IfNull(id), messageHandlerAsync, options, declareCrossRunShareable: threadsafe).ToBinding(messageHandlerAsync);
/// <summary>
/// Configures a function-based asynchronous message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TOutput">The type of output message.</typeparam>
/// <param name="messageHandlerAsync">A delegate that defines the asynchronous function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TOutput>(this Func<TInput, ValueTask<TOutput>> messageHandlerAsync, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>)((input, _, __) => messageHandlerAsync(input)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based asynchronous message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TOutput">The type of output message.</typeparam>
/// <param name="messageHandlerAsync">A delegate that defines the asynchronous function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TOutput>(this Func<TInput, IWorkflowContext, ValueTask<TOutput>> messageHandlerAsync, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>)((input, ctx, __) => messageHandlerAsync(input, ctx)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based asynchronous message handler as an executor with the specified identifier and
/// options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TOutput">The type of output message.</typeparam>
/// <param name="messageHandlerAsync">A delegate that defines the asynchronous function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TOutput>(this Func<TInput, CancellationToken, ValueTask<TOutput>> messageHandlerAsync, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>)((input, _, ct) => messageHandlerAsync(input, ct)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based message handler as an executor with the specified identifier and options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TOutput">The type of output message.</typeparam>
/// <param name="messageHandler">A delegate that defines the function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TOutput>(this Func<TInput, IWorkflowContext, CancellationToken, TOutput> messageHandler, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> new FunctionExecutor<TInput, TOutput>(id, messageHandler, options, declareCrossRunShareable: threadsafe).ToBinding(messageHandler);
/// <summary>
/// Configures a function-based message handler as an executor with the specified identifier and options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TOutput">The type of output message.</typeparam>
/// <param name="messageHandler">A delegate that defines the function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TOutput>(this Func<TInput, TOutput> messageHandler, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, TOutput>)((input, _, __) => messageHandler(input)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based message handler as an executor with the specified identifier and options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TOutput">The type of output message.</typeparam>
/// <param name="messageHandler">A delegate that defines the function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TOutput>(this Func<TInput, IWorkflowContext, TOutput> messageHandler, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, TOutput>)((input, ctx, __) => messageHandler(input, ctx)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based message handler as an executor with the specified identifier and options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TOutput">The type of output message.</typeparam>
/// <param name="messageHandler">A delegate that defines the function to execute for each input message.</param>
/// <param name="id">An optional unique identifier for the executor. If <c>null</c>, will use the function argument as an id.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TOutput>(this Func<TInput, CancellationToken, TOutput> messageHandler, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> ((Func<TInput, IWorkflowContext, CancellationToken, TOutput>)((input, _, ct) => messageHandler(input, ct)))
.BindAsExecutor(id, options, threadsafe);
/// <summary>
/// Configures a function-based aggregating executor with the specified identifier and options.
/// </summary>
/// <typeparam name="TInput">The type of input message.</typeparam>
/// <typeparam name="TAccumulate">The type of the accumulating object.</typeparam>
/// <param name="aggregatorFunc">A delegate the defines the aggregation procedure</param>
/// <param name="id">A unique identifier for the executor.</param>
/// <param name="options">Configuration options for the executor. If <c>null</c>, default options will be used.</param>
/// <param name="threadsafe">Declare that the message handler may be used simultaneously by multiple runs concurrently.</param>
/// <returns>An <see cref="ExecutorBinding"/> instance that wraps the provided asynchronous message handler and configuration.</returns>
public static ExecutorBinding BindAsExecutor<TInput, TAccumulate>(this Func<TAccumulate?, TInput, TAccumulate?> aggregatorFunc, string id, ExecutorOptions? options = null, bool threadsafe = false)
=> new AggregatingExecutor<TInput, TAccumulate>(id, aggregatorFunc, options, declareCrossRunShareable: threadsafe);
/// <summary>
/// Configure an <see cref="AIAgent"/> as an executor for use in a workflow.
/// </summary>
/// <param name="agent">The agent instance.</param>
/// <param name="emitEvents">Specifies whether the agent should emit streaming events.</param>
/// <returns>An <see cref="AIAgentBinding"/> instance that wraps the provided agent.</returns>
public static ExecutorBinding BindAsExecutor(this AIAgent agent, bool emitEvents)
=> new AIAgentBinding(agent, emitEvents);
/// <summary>
/// Configure an <see cref="AIAgent"/> as an executor for use in a workflow.
/// </summary>
/// <param name="agent">The agent instance.</param>
/// <param name="options">Optional configuration options for the AI agent executor. If null, default options are used.</param>
/// <returns>An <see cref="AIAgentBinding"/> instance that wraps the provided agent.</returns>
public static ExecutorBinding BindAsExecutor(this AIAgent agent, AIAgentHostOptions? options = null)
=> new AIAgentBinding(agent, options);
/// <summary>
/// Configure a <see cref="RequestPort"/> as an executor for use in a workflow.
/// </summary>
/// <param name="port">The port configuration.</param>
/// <param name="allowWrappedRequests">Specifies whether the port should accept requests already wrapped in
/// <see cref="ExternalRequest"/>.</param>
/// <returns>A <see cref="RequestPortBinding"/> instance that wraps the provided port.</returns>
public static ExecutorBinding BindAsExecutor(this RequestPort port, bool allowWrappedRequests = true)
=> new RequestPortBinding(port, allowWrappedRequests);
}