Files
Jacob Alber 0756c45702 .NET: [BREAKING] Update type names and source generator to reduce conflicts (#4903)
* refactor: [BREAKING] Config => ExecutorConfig

Make the Config name less likely to collide with other classes by renaming to ExecutorConfig. Makes Configured and related classes internal as they do not need to be part of the public surface.

* fix: Make RouteBuilder explicit in SourceGen to avoid conflicts
2026-03-25 20:35:17 +00:00

154 lines
6.3 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
namespace Microsoft.Agents.AI.Workflows;
/// <summary>
/// Provides methods for creating <see cref="Configured{TSubject}"/> instances.
/// </summary>
internal static class Configured
{
/// <summary>
/// Creates a <see cref="Configured{TSubject}"/> instance from an existing subject instance.
/// </summary>
/// <param name="subject">
/// The subject instance. If the subject implements <see cref="IIdentified"/>, its ID will be used
/// and checked against the provided ID (if any).
/// </param>
/// <param name="id">
/// A unique identifier for the configured subject. This is required if the subject does not implement
/// <see cref="IIdentified"/>
/// </param>
/// <param name="raw">
/// The raw representation of the subject instance.
/// </param>
/// <returns></returns>
public static Configured<TSubject> FromInstance<TSubject>(TSubject subject, string? id = null, object? raw = null)
{
if (subject is IIdentified identified)
{
if (id is not null && identified.Id != id)
{
throw new ArgumentException($"Provided ID '{id}' does not match subject's ID '{identified.Id}'.", nameof(id));
}
return new Configured<TSubject>((_, __) => new(subject), id: identified.Id, raw: raw ?? subject);
}
if (id is null)
{
throw new ArgumentNullException(nameof(id), "ID must be provided when the subject does not implement IIdentified.");
}
return new Configured<TSubject>((_, __) => new(subject), id, raw: raw ?? subject);
}
}
/// <summary>
/// A representation of a preconfigured, lazy-instantiatable instance of <typeparamref name="TSubject"/>.
/// </summary>
/// <typeparam name="TSubject">The type of the preconfigured subject.</typeparam>
/// <param name="factoryAsync">A factory to instantiate the subject when desired.</param>
/// <param name="id">The unique identifier for the configured subject.</param>
/// <param name="raw"></param>
internal class Configured<TSubject>(Func<ExecutorConfig, string, ValueTask<TSubject>> factoryAsync, string id, object? raw = null)
{
/// <summary>
/// Gets the raw representation of the configured object, if any.
/// </summary>
public object? Raw => raw;
/// <summary>
/// Gets the configured identifier for the subject.
/// </summary>
public string Id => id;
/// <summary>
/// Gets the factory function to create an instance of <typeparamref name="TSubject"/> given a <see cref="ExecutorConfig"/>.
/// </summary>
public Func<ExecutorConfig, string, ValueTask<TSubject>> FactoryAsync => factoryAsync;
/// <summary>
/// The configuration for this configured instance.
/// </summary>
public ExecutorConfig Configuration => new(this.Id);
/// <summary>
/// Gets a "partially" applied factory function that only requires no parameters to create an instance of
/// <typeparamref name="TSubject"/> with the provided <see cref="Configuration"/> instance.
/// </summary>
internal Func<string, ValueTask<TSubject>> BoundFactoryAsync => (sessionId) => this.FactoryAsync(this.Configuration, sessionId);
}
/// <summary>
/// A representation of a preconfigured, lazy-instantiatable instance of <typeparamref name="TSubject"/>.
/// </summary>
/// <typeparam name="TSubject">The type of the preconfigured subject.</typeparam>
/// <typeparam name="TOptions">The type of configuration options for the preconfigured subject.</typeparam>
/// <param name="factoryAsync">A factory to instantiate the subject when desired.</param>
/// <param name="id">The unique identifier for the configured subject.</param>
/// <param name="options">Additional configuration options for the subject.</param>
/// <param name="raw"></param>
internal class Configured<TSubject, TOptions>(Func<ExecutorConfig<TOptions>, string, ValueTask<TSubject>> factoryAsync, string id, TOptions? options = default, object? raw = null)
{
/// <summary>
/// The raw representation of the configured object, if any.
/// </summary>
public object? Raw => raw;
/// <summary>
/// Gets the configured identifier for the subject.
/// </summary>
public string Id => id;
/// <summary>
/// Gets the options associated with this instance.
/// </summary>
public TOptions? Options => options;
/// <summary>
/// Gets the factory function to create an instance of <typeparamref name="TSubject"/> given a <see cref="ExecutorConfig{TOptions}"/>.
/// </summary>
public Func<ExecutorConfig<TOptions>, string, ValueTask<TSubject>> FactoryAsync => factoryAsync;
/// <summary>
/// The configuration for this configured instance.
/// </summary>
public ExecutorConfig<TOptions> Configuration => new(this.Id, this.Options);
/// <summary>
/// Gets a "partially" applied factory function that only requires no parameters to create an instance of
/// <typeparamref name="TSubject"/> with the provided <see cref="Configuration"/> instance.
/// </summary>
internal Func<string, ValueTask<TSubject>> BoundFactoryAsync => (sessionId) => this.CreateValidatingMemoizedFactory()(this.Configuration, sessionId);
private Func<ExecutorConfig, string, ValueTask<TSubject>> CreateValidatingMemoizedFactory()
{
return FactoryAsync;
async ValueTask<TSubject> FactoryAsync(ExecutorConfig configuration, string sessionId)
{
if (this.Id != configuration.Id)
{
throw new InvalidOperationException($"Requested instance ID '{configuration.Id}' does not match configured ID '{this.Id}'.");
}
TSubject subject = await this.FactoryAsync(this.Configuration, sessionId).ConfigureAwait(false);
if (this.Id is not null && subject is IIdentified identified && identified.Id != this.Id)
{
throw new InvalidOperationException($"Created instance ID '{identified.Id}' does not match configured ID '{this.Id}'.");
}
return subject;
}
}
/// <summary>
/// Memoizes and erases the typed configuration options for the subject.
/// </summary>
public Configured<TSubject> Memoize() => new(this.CreateValidatingMemoizedFactory(), this.Id);
}