// 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}':"
};
}