// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.Workflows.Specialized; using Microsoft.Extensions.AI.Agents; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows; /// /// Extension methods for configuring executors and functions as instances. /// public static class ExecutorIshConfigurationExtensions { /// /// 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, if this is used as a start node of a typed via , /// it will be instantiated as part of the workflow's construction, to validate that its input type matches the /// demanded TInput. /// /// 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 ExecutorIsh instance that resolves to the result of the factory call when messages get sent to it. public static ExecutorIsh ConfigureFactory(this Func, ValueTask> factoryAsync, string id, TOptions? options = null) where TExecutor : Executor where TOptions : ExecutorOptions { Configured configured = new(factoryAsync, id, options); return new ExecutorIsh(configured.Super(), typeof(TExecutor), ExecutorIsh.Type.Executor); } private static ExecutorIsh ToExecutorIsh(this FunctionExecutor executor, Delegate raw) => new(Configured.FromInstance(executor, raw: raw) .Super, Executor>(), typeof(FunctionExecutor), ExecutorIsh.Type.Function); private static ExecutorIsh ToExecutorIsh(this FunctionExecutor executor, Delegate raw) => new(Configured.FromInstance(executor, raw: raw) .Super, Executor>(), typeof(FunctionExecutor), ExecutorIsh.Type.Function); /// /// 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. /// A optional unique identifier for the executor. If null, a type-tagged UUID will be generated. /// Configuration options for the executor. If null, default options will be used. /// An ExecutorIsh instance that wraps the provided asynchronous message handler and configuration. public static ExecutorIsh AsExecutor(this Func messageHandlerAsync, string id, ExecutorOptions? options = null) => new FunctionExecutor(messageHandlerAsync, id, options).ToExecutorIsh(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. /// A optional unique identifier for the executor. If null, a type-tagged UUID will be generated. /// Configuration options for the executor. If null, default options will be used. /// An ExecutorIsh instance that wraps the provided asynchronous message handler and configuration. public static ExecutorIsh AsExecutor(this Func> messageHandlerAsync, string id, ExecutorOptions? options = null) => new FunctionExecutor(messageHandlerAsync, id, options).ToExecutorIsh(messageHandlerAsync); } /// /// A tagged union representing an object that can function like an in a , /// or a reference to one by ID. /// public sealed class ExecutorIsh : IIdentified, IEquatable, IEquatable, IEquatable { /// /// The type of the . /// public enum Type { /// /// An unbound executor reference, identified only by ID. /// Unbound, /// /// An actual instance. /// Executor, /// /// A function delegate to be wrapped as an executor. /// Function, /// /// An for servicing external requests. /// InputPort, /// /// An instance. /// Agent, } /// /// Gets the type of data contained in this instance. /// public Type ExecutorType { get; init; } private readonly string? _idValue; private readonly Configured? _configuredExecutor; private readonly System.Type? _configuredExecutorType; internal readonly InputPort? _inputPortValue; private readonly AIAgent? _aiAgentValue; /// /// Initializes a new instance of the class as an unbound reference by ID. /// /// A unique identifier for an in the public ExecutorIsh(string id) { this.ExecutorType = Type.Unbound; this._idValue = Throw.IfNull(id); } internal ExecutorIsh(Configured configured, System.Type configuredExecutorType, Type type) { this.ExecutorType = type; this._configuredExecutor = configured; this._configuredExecutorType = configuredExecutorType; } /// /// Initializes a new instance of the ExecutorIsh class using the specified executor. /// /// The executor instance to be wrapped. public ExecutorIsh(Executor executor) { this.ExecutorType = Type.Executor; this._configuredExecutor = Configured.FromInstance(Throw.IfNull(executor)); this._configuredExecutorType = executor.GetType(); } /// /// Initializes a new instance of the ExecutorIsh class using the specified input port. /// /// The input port to associate to be wrapped. public ExecutorIsh(InputPort port) { this.ExecutorType = Type.InputPort; this._inputPortValue = Throw.IfNull(port); } /// /// Initializes a new instance of the ExecutorIsh class using the specified AI agent. /// /// public ExecutorIsh(AIAgent aiAgent) { this.ExecutorType = Type.Agent; this._aiAgentValue = Throw.IfNull(aiAgent); } internal bool IsUnbound => this.ExecutorType == Type.Unbound; /// public string Id => this.ExecutorType switch { Type.Unbound => this._idValue ?? throw new InvalidOperationException("This ExecutorIsh is unbound and has no ID."), Type.Executor => this._configuredExecutor!.Id, Type.InputPort => this._inputPortValue!.Id, Type.Agent => this._aiAgentValue!.Id, Type.Function => this._configuredExecutor!.Id, _ => throw new InvalidOperationException($"Unknown ExecutorIsh type: {this.ExecutorType}") }; internal object? RawData => this.ExecutorType switch { Type.Unbound => this._idValue, Type.Executor => this._configuredExecutor!.Raw ?? this._configuredExecutor, Type.InputPort => this._inputPortValue, Type.Agent => this._aiAgentValue, Type.Function => this._configuredExecutor!.Raw ?? this._configuredExecutor, _ => throw new InvalidOperationException($"Unknown ExecutorIsh type: {this.ExecutorType}") }; /// /// Gets the registration details for the current executor. /// /// The returned registration depends on the type of the executor. If the executor is unbound, an /// is thrown. For other executor types, the registration includes the /// appropriate ID, type, and provider based on the executor's configuration. internal ExecutorRegistration Registration => new(this.Id, this.RuntimeType, this.ExecutorProvider, this.RawData); private System.Type RuntimeType => this.ExecutorType switch { Type.Unbound => throw new InvalidOperationException($"ExecutorIsh with ID '{this.Id}' is unbound."), Type.Executor => this._configuredExecutorType!, Type.InputPort => typeof(RequestInfoExecutor), Type.Agent => typeof(AIAgentHostExecutor), Type.Function => this._configuredExecutorType!, _ => throw new InvalidOperationException($"Unknown ExecutorIsh type: {this.ExecutorType}") }; /// /// Gets an that can be used to obtain an instance /// corresponding to this . /// private Func> ExecutorProvider => this.ExecutorType switch { Type.Unbound => throw new InvalidOperationException($"Executor with ID '{this.Id}' is unbound."), Type.Executor => this._configuredExecutor!.BoundFactoryAsync, Type.InputPort => () => new(new RequestInfoExecutor(this._inputPortValue!)), Type.Agent => () => new(new AIAgentHostExecutor(this._aiAgentValue!)), Type.Function => this._configuredExecutor!.BoundFactoryAsync, _ => throw new InvalidOperationException($"Unknown ExecutorIsh type: {this.ExecutorType}") }; /// /// Defines an implicit conversion from an instance to an object. /// /// The instance to convert to . public static implicit operator ExecutorIsh(Executor executor) => new(executor); /// /// Defines an implicit conversion from an to an instance. /// /// The to convert to an . public static implicit operator ExecutorIsh(InputPort inputPort) => new(inputPort); /// /// Defines an implicit conversion from an to an instance. /// /// The to convert to an . public static implicit operator ExecutorIsh(AIAgent aiAgent) => new(aiAgent); /// /// Defines an implicit conversion from a string to an instance. /// /// The string ID to convert to an . public static implicit operator ExecutorIsh(string id) => new(id); /// public bool Equals(ExecutorIsh? other) => other is not null && other.Id == this.Id; /// public bool Equals(IIdentified? other) => other is not null && other.Id == this.Id; /// public bool Equals(string? other) => other is not null && other == this.Id; /// public override bool Equals(object? obj) => obj switch { null => false, ExecutorIsh ish => this.Equals(ish), IIdentified identified => this.Equals(identified), string str => this.Equals(str), _ => false }; /// public override int GetHashCode() => this.Id.GetHashCode(); /// public override string ToString() => this.ExecutorType switch { Type.Unbound => $"'{this.Id}':", Type.Executor => $"'{this.Id}':{this._configuredExecutorType!.Name}", Type.InputPort => $"'{this.Id}':Input({this._inputPortValue!.Request.Name}->{this._inputPortValue!.Response.Name})", Type.Agent => $"{this.Id}':AIAgent(@{this._aiAgentValue!.GetType().Name})", Type.Function => $"'{this.Id}':{this._configuredExecutorType!.Name}", _ => $"'{this.Id}':" }; }