// 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();
}