< 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: 111
Uncovered lines: 0
Coverable lines: 111
Total lines: 229
Line coverage: 100%
Branch coverage
86%
Covered branches: 33
Total branches: 38
Branch coverage: 86.8%
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%1616100%

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    {
 849        ArgumentNullException.ThrowIfNull(syringe);
 1650        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    {
 22272        ArgumentNullException.ThrowIfNull(syringe);
 22273        ArgumentNullException.ThrowIfNull(configure);
 74
 22275        return syringe.UsingPostPluginRegistrationCallback(services =>
 22276        {
 22277            RegisterAgentFrameworkCore(services, configure);
 44478        });
 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    /// (and its configure overloads) 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    {
 711119        RegisterAgentFrameworkInfrastructure(services);
 120
 718121        configure ??= s => s;
 122
 711123        services.TryAddSingleton<BuiltAgentFrameworkSyringe>(sp =>
 711124        {
 225125            AgentFrameworkSyringe afSyringe = new()
 225126            {
 225127                ServiceProvider = sp,
 225128            };
 225129            afSyringe = configure.Invoke(afSyringe);
 711130
 225131            if ((afSyringe.FunctionTypes is null || afSyringe.FunctionTypes.Count == 0) &&
 225132                AgentFrameworkGeneratedBootstrap.TryGetFunctionTypes(out var functionProvider))
 711133            {
 179134                afSyringe = afSyringe with { FunctionTypes = functionProvider().ToList() };
 711135            }
 711136
 225137            if ((afSyringe.FunctionGroupMap is null || afSyringe.FunctionGroupMap.Count == 0) &&
 225138                AgentFrameworkGeneratedBootstrap.TryGetGroupTypes(out var groupProvider))
 711139            {
 208140                afSyringe = afSyringe with { FunctionGroupMap = groupProvider() };
 711141            }
 711142
 225143            if ((afSyringe.AgentTypes is null || afSyringe.AgentTypes.Count == 0) &&
 225144                AgentFrameworkGeneratedBootstrap.TryGetAgentTypes(out var agentProvider))
 711145            {
 126146                afSyringe = afSyringe with { AgentTypes = agentProvider().ToList() };
 711147            }
 711148
 225149            return new BuiltAgentFrameworkSyringe(afSyringe);
 711150        });
 151
 711152        services.TryAddSingleton<IProgressReporterFactory>(sp =>
 711153        {
 18154            var defaultSinks = sp.GetServices<IProgressSink>();
 18155            return new ProgressReporterFactory(
 18156                defaultSinks,
 18157                sp.GetRequiredService<IProgressSequence>(),
 18158                sp.GetRequiredService<IProgressReporterErrorHandler>());
 711159        });
 160
 711161        services.TryAddSingleton<IAgentFactory>(sp =>
 905162            sp.GetRequiredService<BuiltAgentFrameworkSyringe>().Value.BuildAgentFactory());
 163
 711164        services.TryAddSingleton<IChatClientAccessor>(sp =>
 711165        {
 47166            var afSyringe = sp.GetRequiredService<BuiltAgentFrameworkSyringe>().Value;
 47167            return new ChatClientAccessor(sp, afSyringe.ConfigureAgentFactory ?? []);
 711168        });
 169
 711170        services.TryAddSingleton<IIterativeAgentLoop>(sp =>
 724171            new IterativeAgentLoop(
 724172                sp.GetRequiredService<IChatClientAccessor>(),
 724173                sp.GetService<IAgentDiagnosticsWriter>(),
 724174                sp.GetService<IAgentExecutionContextAccessor>(),
 724175                sp.GetService<IProgressReporterAccessor>(),
 724176                sp.GetService<ITokenBudgetTracker>(),
 724177                sp.GetService<IAgentMetrics>(),
 724178                genAiTokenMetrics: sp.GetService<IGenAiTokenMetrics>()));
 179
 711180        services.TryAddSingleton<IWorkflowFactory>(provider =>
 801181            new WorkflowFactory(provider.GetRequiredService<IAgentFactory>()));
 711182    }
 183
 184    private static void RegisterAgentFrameworkInfrastructure(IServiceCollection services)
 185    {
 711186        services.TryAddSingleton<ITokenBudgetTracker, TokenBudgetTracker>();
 711187        services.TryAddSingleton<IAgentExecutionContextAccessor, AgentExecutionContextAccessor>();
 711188        services.TryAddSingleton<AgentDiagnosticsAccessor>(sp =>
 802189            new AgentDiagnosticsAccessor(
 802190                sp.GetService<ChatCompletionCollectorHolder>(),
 802191                sp.GetService<ToolCallCollectorHolder>()));
 786192        services.TryAddSingleton<IAgentDiagnosticsAccessor>(sp => sp.GetRequiredService<AgentDiagnosticsAccessor>());
 754193        services.TryAddSingleton<IAgentDiagnosticsWriter>(sp => sp.GetRequiredService<AgentDiagnosticsAccessor>());
 711194        services.TryAddSingleton<IInFlightAgentDiagnosticsAccessor, InFlightAgentDiagnosticsAccessor>();
 711195        services.TryAddSingleton<IToolMetricsAccessor, ToolMetricsAccessor>();
 711196        services.TryAddSingleton<AgentFrameworkMetricsOptions>(sp =>
 711197        {
 39198            var syringe = sp.GetService<BuiltAgentFrameworkSyringe>();
 39199            return syringe?.Value.MetricsOptions ?? new AgentFrameworkMetricsOptions();
 711200        });
 711201        services.TryAddSingleton<IAgentMetrics>(sp =>
 711202        {
 55203            var syringe = sp.GetService<BuiltAgentFrameworkSyringe>();
 55204            var options = syringe?.Value.MetricsOptions ?? new AgentFrameworkMetricsOptions();
 55205            return new AgentMetrics(options);
 711206        });
 711207        services.TryAddSingleton<IGenAiTokenMetrics>(sp =>
 711208        {
 48209            var syringe = sp.GetService<BuiltAgentFrameworkSyringe>();
 48210            var options = syringe?.Value.MetricsOptions ?? new AgentFrameworkMetricsOptions();
 48211            return new GenAiTokenMetrics(options);
 711212        });
 711213        services.TryAddSingleton<IPipelineMetrics>(sp =>
 711214        {
 3215            var syringe = sp.GetService<BuiltAgentFrameworkSyringe>();
 3216            var options = syringe?.Value.PipelineMetricsOptions;
 3217            return options is null
 3218                ? new NoOpPipelineMetrics()
 3219                : new PipelineMetrics(options);
 711220        });
 711221        services.TryAddSingleton<ChatCompletionCollectorHolder>();
 714222        services.TryAddSingleton<IChatCompletionCollector>(sp => sp.GetRequiredService<ChatCompletionCollectorHolder>())
 711223        services.TryAddSingleton<ToolCallCollectorHolder>();
 741224        services.TryAddSingleton<IToolCallCollector>(sp => sp.GetRequiredService<ToolCallCollectorHolder>());
 711225        services.TryAddSingleton<IProgressSequence, ProgressSequenceProvider>();
 711226        services.TryAddSingleton<IProgressReporterAccessor, ProgressReporterAccessor>();
 711227        services.TryAddSingleton<IProgressReporterErrorHandler, NullProgressReporterErrorHandler>();
 711228    }
 229}