// Copyright (c) Microsoft. All rights reserved. using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace Microsoft.Agents.AI.Workflows; /// /// Represents the binding information for a workflow executor, including its identifier, factory method, type, and /// optional raw value. /// /// The unique identifier for the executor in the workflow. /// A factory function that creates an instance of the executor. The function accepts two string parameters and returns /// a ValueTask containing the created Executor instance. /// The type of the executor. Must be a type derived from Executor. /// An optional raw value associated with the binding. public abstract record class ExecutorBinding(string Id, Func>? FactoryAsync, Type ExecutorType, object? RawValue = null) : IIdentified, IEquatable, IEquatable { /// /// Gets a value indicating whether the binding is a placeholder (i.e., does not have a factory method defined). /// [MemberNotNullWhen(false, nameof(FactoryAsync))] public bool IsPlaceholder => this.FactoryAsync == null; /// /// Gets a value whether the executor created from this binding is a shared instance across all runs. /// public abstract bool IsSharedInstance { get; } /// /// Gets a value whether instances of the executor created from this binding can be used in concurrent runs /// from the same instance. /// public abstract bool SupportsConcurrentSharedExecution { get; } /// /// Gets a value whether instances of the executor created from this binding can be reset between subsequent /// runs from the same instance. This value is not relevant for executors that . /// public abstract bool SupportsResetting { get; } /// public override string ToString() => $"{this.Id}:{(this.IsPlaceholder ? ":" : this.ExecutorType.Name)}"; private Executor CheckId(Executor executor) { if (executor.Id != this.Id) { throw new InvalidOperationException( $"Executor ID mismatch: expected '{this.Id}', but got '{executor.Id}'."); } return executor; } internal async ValueTask CreateInstanceAsync(string sessionId) => !this.IsPlaceholder ? this.CheckId(await this.FactoryAsync(sessionId).ConfigureAwait(false)) : throw new InvalidOperationException( $"Cannot create executor with ID '{this.Id}': Binding ({this.GetType().Name}) is a placeholder."); /// public virtual bool Equals(ExecutorBinding? 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; internal ValueTask TryResetAsync() { // Non-shared instances do not need resetting if (!this.IsSharedInstance) { return new(true); } // If the executor supports concurrent use, then resetting is a no-op. if (!this.SupportsResetting) { return new(false); } return this.ResetCoreAsync(); } /// /// Resets the executor's shared resources to their initial state. Must be overridden by bindings that support /// resetting. /// /// protected virtual ValueTask ResetCoreAsync() => throw new InvalidOperationException("ExecutorBindings that support resetting must override ResetCoreAsync()"); /// public override int GetHashCode() => this.Id.GetHashCode(); /// /// Defines an implicit conversion from an Executor to a . /// /// The Executor instance to convert. public static implicit operator ExecutorBinding(Executor executor) => executor.BindExecutor(); /// /// Defines an implicit conversion from a string identifier to an . /// /// The string identifier to convert to a placeholder. public static implicit operator ExecutorBinding(string id) => new ExecutorPlaceholder(id); /// /// Defines an implicit conversion from a to an . /// /// The RequestPort instance to convert. public static implicit operator ExecutorBinding(RequestPort port) => port.BindAsExecutor(); /// /// Defines an implicit conversion from an to an instance. /// /// public static implicit operator ExecutorBinding(AIAgent agent) => agent.BindAsExecutor(); }