< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.SyringeExtensionsForAgentFramework
Assembly: NexusLabs.Needlr.AgentFramework
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/SyringeExtensionsForAgentFramework.cs
Line coverage
100%
Covered lines: 90
Uncovered lines: 0
Coverable lines: 90
Total lines: 208
Line coverage: 100%
Branch coverage
92%
Covered branches: 24
Total branches: 26
Branch coverage: 92.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
UsingAgentFramework(...)100%11100%
UsingAgentFramework(...)100%11100%
UsingAgentFramework(...)100%11100%
RegisterAgentFrameworkCore(...)95.45%2222100%
RegisterAgentFrameworkInfrastructure(...)75%44100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/SyringeExtensionsForAgentFramework.cs

#LineLine coverage
 1using Microsoft.Extensions.DependencyInjection;
 2using Microsoft.Extensions.DependencyInjection.Extensions;
 3
 4using NexusLabs.Needlr.AgentFramework.Budget;
 5using NexusLabs.Needlr.AgentFramework.Context;
 6using NexusLabs.Needlr.AgentFramework.Diagnostics;
 7using NexusLabs.Needlr.AgentFramework.Iterative;
 8using NexusLabs.Needlr.AgentFramework.Progress;
 9using NexusLabs.Needlr.Injection;
 10
 11namespace NexusLabs.Needlr.AgentFramework;
 12
 13/// <summary>
 14/// Extension methods for <see cref="ConfiguredSyringe"/> that enable registering
 15/// Microsoft Agent Framework infrastructure (namely <see cref="IAgentFactory"/>)
 16/// as part of the Needlr build pipeline.
 17/// </summary>
 18/// <remarks>
 19/// <para>
 20/// These helpers defer service registration using the Syringe
 21/// post-plugin registration callback so that function discovery and
 22/// registration are completed before the agent factory is added.
 23/// </para>
 24/// <para>
 25/// When the Needlr source generator is active, the generated <c>[ModuleInitializer]</c> registers
 26/// an <see cref="IAIFunctionProvider"/> that is used instead of reflection. This makes the
 27/// integration NativeAOT-compatible without any code changes.
 28/// </para>
 29/// <para>
 30/// When the source generator is not used, the integration falls back to reflection-based
 31/// <see cref="Microsoft.Extensions.AI.AIFunction"/> schema generation, which requires dynamic code.
 32/// </para>
 33/// </remarks>
 34public static class SyringeExtensionsForAgentFramework
 35{
 36    /// <summary>
 37    /// Registers an <see cref="IAgentFactory"/> built via a
 38    /// <see cref="AgentFrameworkSyringe"/> instance.
 39    /// </summary>
 40    /// <param name="syringe">
 41    /// The <see cref="ConfiguredSyringe"/> to augment with the registration.
 42    /// </param>
 43    /// <returns>
 44    /// A new <see cref="ConfiguredSyringe"/> instance containing the registration.
 45    /// </returns>
 46    public static ConfiguredSyringe UsingAgentFramework(
 47        this ConfiguredSyringe syringe)
 48    {
 449        ArgumentNullException.ThrowIfNull(syringe);
 850        return syringe.UsingAgentFramework(s => s);
 51    }
 52
 53    /// <summary>
 54    /// Registers an <see cref="IAgentFactory"/> built via a configurable
 55    /// <see cref="AgentFrameworkSyringe"/> instance.
 56    /// </summary>
 57    /// <param name="syringe">
 58    /// The <see cref="ConfiguredSyringe"/> to augment with the registration.
 59    /// </param>
 60    /// <param name="configure">
 61    /// A delegate that receives a pre-initialized <see cref="AgentFrameworkSyringe"/>
 62    /// (with its <see cref="AgentFrameworkSyringe.ServiceProvider"/> set) and
 63    /// returns the configured instance used to build the agent factory.
 64    /// </param>
 65    /// <returns>
 66    /// A new <see cref="ConfiguredSyringe"/> instance containing the registration.
 67    /// </returns>
 68    public static ConfiguredSyringe UsingAgentFramework(
 69        this ConfiguredSyringe syringe,
 70        Func<AgentFrameworkSyringe, AgentFrameworkSyringe> configure)
 71    {
 20872        ArgumentNullException.ThrowIfNull(syringe);
 20873        ArgumentNullException.ThrowIfNull(configure);
 74
 20875        return syringe.UsingPostPluginRegistrationCallback(services =>
 20876        {
 20877            RegisterAgentFrameworkCore(services, configure);
 41678        });
 79    }
 80
 81    /// <summary>
 82    /// Registers an <see cref="IAgentFactory"/> built via an
 83    /// <see cref="AgentFrameworkSyringe"/> created by the supplied delegate.
 84    /// </summary>
 85    /// <param name="syringe">
 86    /// The <see cref="ConfiguredSyringe"/> to augment with the registration.
 87    /// </param>
 88    /// <param name="configure">
 89    /// A factory that creates a fully-configured <see cref="AgentFrameworkSyringe"/>
 90    /// used to build the agent factory. Useful when configuration does
 91    /// not need the service provider.
 92    /// </param>
 93    /// <returns>
 94    /// A new <see cref="ConfiguredSyringe"/> instance containing the registration.
 95    /// </returns>
 96    public static ConfiguredSyringe UsingAgentFramework(
 97        this ConfiguredSyringe syringe,
 98        Func<AgentFrameworkSyringe> configure)
 99    {
 1100        ArgumentNullException.ThrowIfNull(syringe);
 1101        ArgumentNullException.ThrowIfNull(configure);
 102
 103        // Route through the configurable overload so both paths share the same
 104        // BuiltAgentFrameworkSyringe holder, progress sink wiring, and
 105        // IProgressReporterFactory construction.
 2106        return syringe.UsingAgentFramework(_ => configure.Invoke());
 107    }
 108
 109    /// <summary>
 110    /// Shared implementation for agent framework registration. Called by both
 111    /// <see cref="UsingAgentFramework(ConfiguredSyringe, Func{AgentFrameworkSyringe, AgentFrameworkSyringe})"/>
 112    /// and <see cref="ServiceCollectionAgentFrameworkExtensions.AddNeedlrAgentFramework(IServiceCollection)"/>
 113    /// to ensure a single code path with zero drift between the two entry points.
 114    /// </summary>
 115    internal static void RegisterAgentFrameworkCore(
 116        IServiceCollection services,
 117        Func<AgentFrameworkSyringe, AgentFrameworkSyringe>? configure = null)
 118    {
 681119        RegisterAgentFrameworkInfrastructure(services);
 120
 688121        configure ??= s => s;
 122
 681123        services.TryAddSingleton<BuiltAgentFrameworkSyringe>(sp =>
 681124        {
 200125            AgentFrameworkSyringe afSyringe = new()
 200126            {
 200127                ServiceProvider = sp,
 200128            };
 200129            afSyringe = configure.Invoke(afSyringe);
 681130
 200131            if ((afSyringe.FunctionTypes is null || afSyringe.FunctionTypes.Count == 0) &&
 200132                AgentFrameworkGeneratedBootstrap.TryGetFunctionTypes(out var functionProvider))
 681133            {
 154134                afSyringe = afSyringe with { FunctionTypes = functionProvider().ToList() };
 681135            }
 681136
 200137            if ((afSyringe.FunctionGroupMap is null || afSyringe.FunctionGroupMap.Count == 0) &&
 200138                AgentFrameworkGeneratedBootstrap.TryGetGroupTypes(out var groupProvider))
 681139            {
 183140                afSyringe = afSyringe with { FunctionGroupMap = groupProvider() };
 681141            }
 681142
 200143            if ((afSyringe.AgentTypes is null || afSyringe.AgentTypes.Count == 0) &&
 200144                AgentFrameworkGeneratedBootstrap.TryGetAgentTypes(out var agentProvider))
 681145            {
 101146                afSyringe = afSyringe with { AgentTypes = agentProvider().ToList() };
 681147            }
 681148
 200149            return new BuiltAgentFrameworkSyringe(afSyringe);
 681150        });
 151
 681152        services.TryAddSingleton<IProgressReporterFactory>(sp =>
 681153        {
 17154            var defaultSinks = sp.GetServices<IProgressSink>();
 17155            return new ProgressReporterFactory(
 17156                defaultSinks,
 17157                sp.GetRequiredService<IProgressSequence>(),
 17158                sp.GetRequiredService<IProgressReporterErrorHandler>());
 681159        });
 160
 681161        services.TryAddSingleton<IAgentFactory>(sp =>
 869162            sp.GetRequiredService<BuiltAgentFrameworkSyringe>().Value.BuildAgentFactory());
 163
 681164        services.TryAddSingleton<IChatClientAccessor>(sp =>
 681165        {
 43166            var afSyringe = sp.GetRequiredService<BuiltAgentFrameworkSyringe>().Value;
 43167            return new ChatClientAccessor(sp, afSyringe.ConfigureAgentFactory ?? []);
 681168        });
 169
 681170        services.TryAddSingleton<IIterativeAgentLoop>(sp =>
 691171            new IterativeAgentLoop(
 691172                sp.GetRequiredService<IChatClientAccessor>(),
 691173                sp.GetService<IAgentDiagnosticsWriter>(),
 691174                sp.GetService<IAgentExecutionContextAccessor>(),
 691175                sp.GetService<IProgressReporterAccessor>(),
 691176                sp.GetService<ITokenBudgetTracker>(),
 691177                sp.GetService<IAgentMetrics>()));
 178
 681179        services.TryAddSingleton<IWorkflowFactory>(provider =>
 767180            new WorkflowFactory(provider.GetRequiredService<IAgentFactory>()));
 681181    }
 182
 183    private static void RegisterAgentFrameworkInfrastructure(IServiceCollection services)
 184    {
 681185        services.TryAddSingleton<ITokenBudgetTracker, TokenBudgetTracker>();
 681186        services.TryAddSingleton<IAgentExecutionContextAccessor, AgentExecutionContextAccessor>();
 681187        services.TryAddSingleton<AgentDiagnosticsAccessor>(sp =>
 769188            new AgentDiagnosticsAccessor(
 769189                sp.GetService<ChatCompletionCollectorHolder>(),
 769190                sp.GetService<ToolCallCollectorHolder>()));
 753191        services.TryAddSingleton<IAgentDiagnosticsAccessor>(sp => sp.GetRequiredService<AgentDiagnosticsAccessor>());
 721192        services.TryAddSingleton<IAgentDiagnosticsWriter>(sp => sp.GetRequiredService<AgentDiagnosticsAccessor>());
 681193        services.TryAddSingleton<IToolMetricsAccessor, ToolMetricsAccessor>();
 681194        services.TryAddSingleton<IAgentMetrics>(sp =>
 681195        {
 44196            var syringe = sp.GetService<BuiltAgentFrameworkSyringe>();
 44197            var options = syringe?.Value.MetricsOptions ?? new AgentFrameworkMetricsOptions();
 44198            return new AgentMetrics(options);
 681199        });
 681200        services.TryAddSingleton<ChatCompletionCollectorHolder>();
 684201        services.TryAddSingleton<IChatCompletionCollector>(sp => sp.GetRequiredService<ChatCompletionCollectorHolder>())
 681202        services.TryAddSingleton<ToolCallCollectorHolder>();
 711203        services.TryAddSingleton<IToolCallCollector>(sp => sp.GetRequiredService<ToolCallCollectorHolder>());
 681204        services.TryAddSingleton<IProgressSequence, ProgressSequenceProvider>();
 681205        services.TryAddSingleton<IProgressReporterAccessor, ProgressReporterAccessor>();
 681206        services.TryAddSingleton<IProgressReporterErrorHandler, NullProgressReporterErrorHandler>();
 681207    }
 208}