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