< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringeExtensions
Assembly: NexusLabs.Needlr.AgentFramework
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/AgentFrameworkSyringeExtensions.cs
Line coverage
78%
Covered lines: 69
Uncovered lines: 19
Coverable lines: 88
Total lines: 310
Line coverage: 78.4%
Branch coverage
75%
Covered branches: 24
Total branches: 32
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

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

#LineLine coverage
 1using Microsoft.Extensions.AI;
 2using Microsoft.Extensions.DependencyInjection;
 3
 4using NexusLabs.Needlr.AgentFramework.Collectors;
 5using NexusLabs.Needlr.AgentFramework.FunctionScanners;
 6
 7using System.Diagnostics.CodeAnalysis;
 8using System.Reflection;
 9
 10namespace NexusLabs.Needlr.AgentFramework;
 11
 12/// <summary>
 13/// Extension methods for <see cref="AgentFrameworkSyringe"/> providing fluent configuration
 14/// of the Microsoft Agent Framework integration.
 15/// </summary>
 16public static class AgentFrameworkSyringeExtensions
 17{
 18    /// <summary>
 19    /// Sets the <see cref="IChatClient"/> used by all agents created from the factory.
 20    /// This is the preferred alternative to calling
 21    /// <see cref="Configure"/> and setting <see cref="AgentFrameworkConfigureOptions.ChatClientFactory"/>.
 22    /// </summary>
 23    public static AgentFrameworkSyringe UsingChatClient(
 24        this AgentFrameworkSyringe syringe,
 25        IChatClient chatClient)
 26    {
 027        ArgumentNullException.ThrowIfNull(syringe);
 028        ArgumentNullException.ThrowIfNull(chatClient);
 29
 030        return syringe.Configure(opts => opts.ChatClientFactory = _ => chatClient);
 31    }
 32
 33    /// <summary>
 34    /// Sets a factory that creates the <see cref="IChatClient"/> used by all agents.
 35    /// The factory receives the DI <see cref="IServiceProvider"/> for resolving dependencies.
 36    /// </summary>
 37    public static AgentFrameworkSyringe UsingChatClient(
 38        this AgentFrameworkSyringe syringe,
 39        Func<IServiceProvider, IChatClient> chatClientFactory)
 40    {
 041        ArgumentNullException.ThrowIfNull(syringe);
 042        ArgumentNullException.ThrowIfNull(chatClientFactory);
 43
 044        return syringe.Configure(opts => opts.ChatClientFactory = chatClientFactory);
 45    }
 46
 47    public static AgentFrameworkSyringe Configure(
 48        this AgentFrameworkSyringe syringe,
 49        Action<AgentFrameworkConfigureOptions> configure)
 50    {
 25951        ArgumentNullException.ThrowIfNull(syringe);
 25952        ArgumentNullException.ThrowIfNull(configure);
 53
 25954        return syringe with
 25955        {
 25956            ConfigureAgentFactory = (syringe.ConfigureAgentFactory ?? []).Append(configure).ToList()
 25957        };
 58    }
 59
 60    public static AgentFrameworkSyringe AddAgentFunction<T>(
 61        this AgentFrameworkSyringe syringe)
 62    {
 063        ArgumentNullException.ThrowIfNull(syringe);
 64
 065        return syringe.AddAgentFunctions([typeof(T)]);
 66    }
 67
 68    /// <summary>
 69    /// Adds agent functions from a compile-time generated list of types.
 70    /// This is the recommended approach for AOT/trimmed applications because
 71    /// the source generator discovers types at build time rather than runtime.
 72    /// </summary>
 73    /// <param name="syringe">The agent framework syringe to configure.</param>
 74    /// <param name="functionTypes">
 75    /// Compile-time discovered function types, typically from the generated
 76    /// <c>NexusLabs.Needlr.Generated.AgentFrameworkFunctions.AllFunctionTypes</c>.
 77    /// </param>
 78    /// <remarks>
 79    /// This overload performs no reflection for type discovery. The
 80    /// <see cref="AgentFactory"/> still uses reflection when building
 81    /// <see cref="Microsoft.Extensions.AI.AIFunction"/> schema from method signatures —
 82    /// the same inherent limitation present in Microsoft.Extensions.AI.
 83    /// </remarks>
 84    public static AgentFrameworkSyringe AddAgentFunctionsFromGenerated(
 85        this AgentFrameworkSyringe syringe,
 86        IReadOnlyList<Type> functionTypes)
 87    {
 888        ArgumentNullException.ThrowIfNull(syringe);
 889        ArgumentNullException.ThrowIfNull(functionTypes);
 90
 891        return syringe with
 892        {
 893            FunctionTypes = (syringe.FunctionTypes ?? []).Concat(functionTypes).Distinct().ToList()
 894        };
 95    }
 96
 97    public static AgentFrameworkSyringe AddAgentFunctionsFromScanner(
 98        this AgentFrameworkSyringe syringe,
 99        IAgentFrameworkFunctionScanner scanner)
 100    {
 36101        ArgumentNullException.ThrowIfNull(syringe);
 36102        ArgumentNullException.ThrowIfNull(scanner);
 103
 36104        return syringe.AddAgentFunctions(scanner.ScanForFunctionTypes());
 105    }
 106
 107    [RequiresUnreferencedCode("Assembly scanning uses reflection to discover types with [AgentFunction] methods.")]
 108    [RequiresDynamicCode("Assembly scanning uses reflection APIs that may require dynamic code generation.")]
 109    public static AgentFrameworkSyringe AddAgentFunctionsFromAssemblies(
 110        this AgentFrameworkSyringe syringe)
 111    {
 1112        ArgumentNullException.ThrowIfNull(syringe);
 113
 1114        var assemblies = syringe.ServiceProvider.GetRequiredService<IReadOnlyList<Assembly>>();
 1115        return syringe.AddAgentFunctionsFromAssemblies(assemblies);
 116    }
 117
 118    [RequiresUnreferencedCode("Assembly scanning uses reflection to discover types with [AgentFunction] methods.")]
 119    [RequiresDynamicCode("Assembly scanning uses reflection APIs that may require dynamic code generation.")]
 120    public static AgentFrameworkSyringe AddAgentFunctionsFromAssemblies(
 121        this AgentFrameworkSyringe syringe,
 122        IReadOnlyList<Assembly> assemblies)
 123    {
 36124        ArgumentNullException.ThrowIfNull(syringe);
 36125        ArgumentNullException.ThrowIfNull(assemblies);
 126
 36127        var scanner = new AssemblyAgentFunctionScanner(assemblies);
 36128        return syringe.AddAgentFunctionsFromScanner(scanner);
 129    }
 130
 131    [RequiresUnreferencedCode("Service provider scanning uses reflection to discover types with [AgentFunction] methods.
 132    [RequiresDynamicCode("Service provider scanning uses reflection APIs that may require dynamic code generation.")]
 133    public static AgentFrameworkSyringe AddAgentFunctionsFromProvider(
 134        this AgentFrameworkSyringe syringe)
 135    {
 0136        ArgumentNullException.ThrowIfNull(syringe);
 137
 0138        var scanner = new ServiceProviderAgentFunctionScanner(syringe.ServiceProvider);
 0139        return syringe.AddAgentFunctionsFromScanner(scanner);
 140    }
 141
 142    [RequiresUnreferencedCode("Function type inspection uses reflection to check for [AgentFunction] methods.")]
 143    [RequiresDynamicCode("Function type inspection uses reflection APIs that may require dynamic code generation.")]
 144    public static AgentFrameworkSyringe AddAgentFunctions(
 145        this AgentFrameworkSyringe syringe,
 146        IReadOnlyList<Type> functionTypes)
 147    {
 38148        ArgumentNullException.ThrowIfNull(syringe);
 38149        ArgumentNullException.ThrowIfNull(functionTypes);
 150
 38151        List<Type> typesToAdd = [];
 152
 872153        foreach (var functionType in functionTypes)
 154        {
 398155            var bindingFlags = functionType.IsStatic()
 398156                ? BindingFlags.Public | BindingFlags.Static
 398157                : BindingFlags.Public | BindingFlags.Instance;
 158
 398159            if (!functionType.GetMethods(bindingFlags)
 796160                .Any(m => m.IsDefined(typeof(AgentFunctionAttribute), inherit: true)))
 161            {
 162                continue;
 163            }
 164
 398165            typesToAdd.Add(functionType);
 166        }
 167
 38168        return syringe with
 38169        {
 38170            FunctionTypes = (syringe.FunctionTypes ?? []).Concat(typesToAdd).Distinct().ToList()
 38171        };
 172    }
 173
 174    /// <summary>
 175    /// Adds agent function groups from a compile-time generated group map.
 176    /// This is the recommended approach for AOT/trimmed applications.
 177    /// </summary>
 178    /// <param name="syringe">The agent framework syringe to configure.</param>
 179    /// <param name="groups">
 180    /// Compile-time discovered group map, typically from the generated
 181    /// <c>AgentFrameworkFunctionGroupRegistry.AllGroups</c>.
 182    /// </param>
 183    public static AgentFrameworkSyringe AddAgentFunctionGroupsFromGenerated(
 184        this AgentFrameworkSyringe syringe,
 185        IReadOnlyDictionary<string, IReadOnlyList<Type>> groups)
 186    {
 5187        ArgumentNullException.ThrowIfNull(syringe);
 5188        ArgumentNullException.ThrowIfNull(groups);
 189
 5190        return syringe.MergeFunctionGroups(groups);
 191    }
 192
 193    /// <summary>
 194    /// Scans registered assemblies for classes decorated with
 195    /// <see cref="AgentFunctionGroupAttribute"/> and registers them by group name.
 196    /// Requires <c>UsingReflection()</c> to be configured.
 197    /// </summary>
 198    [RequiresUnreferencedCode("Assembly scanning uses reflection to discover types with [AgentFunctionGroup] attributes.
 199    [RequiresDynamicCode("Assembly scanning uses reflection APIs that may require dynamic code generation.")]
 200    public static AgentFrameworkSyringe AddAgentFunctionGroupsFromAssemblies(
 201        this AgentFrameworkSyringe syringe)
 202    {
 0203        ArgumentNullException.ThrowIfNull(syringe);
 204
 0205        var assemblies = syringe.ServiceProvider.GetRequiredService<IReadOnlyList<Assembly>>();
 0206        return syringe.AddAgentFunctionGroupsFromAssemblies(assemblies);
 207    }
 208
 209    /// <summary>
 210    /// Scans the provided assemblies for classes decorated with
 211    /// <see cref="AgentFunctionGroupAttribute"/> and registers them by group name.
 212    /// </summary>
 213    [RequiresUnreferencedCode("Assembly scanning uses reflection to discover types with [AgentFunctionGroup] attributes.
 214    [RequiresDynamicCode("Assembly scanning uses reflection APIs that may require dynamic code generation.")]
 215    public static AgentFrameworkSyringe AddAgentFunctionGroupsFromAssemblies(
 216        this AgentFrameworkSyringe syringe,
 217        IReadOnlyList<Assembly> assemblies)
 218    {
 14219        ArgumentNullException.ThrowIfNull(syringe);
 14220        ArgumentNullException.ThrowIfNull(assemblies);
 221
 14222        var scanner = new FunctionScanners.AssemblyAgentFunctionGroupScanner(assemblies);
 14223        var groups = scanner.ScanForFunctionGroups();
 14224        return syringe.MergeFunctionGroups(groups);
 225    }
 226
 227    /// <summary>
 228    /// Registers a single declared agent type for string-based lookup via
 229    /// <see cref="IAgentFactory.CreateAgent(string)"/>.
 230    /// The type must be decorated with <see cref="NeedlrAiAgentAttribute"/>.
 231    /// </summary>
 232    public static AgentFrameworkSyringe AddAgent<TAgent>(
 233        this AgentFrameworkSyringe syringe)
 234        where TAgent : class
 235    {
 337236        ArgumentNullException.ThrowIfNull(syringe);
 337237        return syringe.AddAgentsFromGenerated([typeof(TAgent)]);
 238    }
 239
 240    /// <summary>
 241    /// Registers a compile-time generated list of declared agent types for
 242    /// string-based lookup via <see cref="IAgentFactory.CreateAgent(string)"/>.
 243    /// Typically called with the output of the generated <c>AgentRegistry.AllAgentTypes</c>.
 244    /// </summary>
 245    public static AgentFrameworkSyringe AddAgentsFromGenerated(
 246        this AgentFrameworkSyringe syringe,
 247        IReadOnlyList<Type> agentTypes)
 248    {
 338249        ArgumentNullException.ThrowIfNull(syringe);
 338250        ArgumentNullException.ThrowIfNull(agentTypes);
 251
 338252        return syringe with
 338253        {
 338254            AgentTypes = (syringe.AgentTypes ?? []).Concat(agentTypes).Distinct().ToList()
 338255        };
 256    }
 257
 258    /// <summary>
 259    /// Configures the meter and ActivitySource names used by <see cref="Diagnostics.IAgentMetrics"/>.
 260    /// Without this call, both default to <c>"NexusLabs.Needlr.AgentFramework"</c>.
 261    /// </summary>
 262    /// <remarks>
 263    /// Consumers with existing dashboards keyed to a specific meter name can set it here
 264    /// to avoid a dashboard migration when adopting Needlr's metrics:
 265    /// <code>
 266    /// .UsingAgentFramework(af => af
 267    ///     .ConfigureMetrics(o => o.MeterName = "BrandGhost.Agents"))
 268    /// </code>
 269    /// </remarks>
 270    public static AgentFrameworkSyringe ConfigureMetrics(
 271        this AgentFrameworkSyringe syringe,
 272        Action<Diagnostics.AgentFrameworkMetricsOptions> configure)
 273    {
 0274        ArgumentNullException.ThrowIfNull(syringe);
 0275        ArgumentNullException.ThrowIfNull(configure);
 276
 0277        var options = syringe.MetricsOptions ?? new Diagnostics.AgentFrameworkMetricsOptions();
 0278        configure(options);
 0279        return syringe with { MetricsOptions = options };
 280    }
 281
 282    private static AgentFrameworkSyringe MergeFunctionGroups(        this AgentFrameworkSyringe syringe,
 283        IReadOnlyDictionary<string, IReadOnlyList<Type>> newGroups)
 284    {
 19285        if (newGroups.Count == 0)
 1286            return syringe;
 287
 18288        var merged = new Dictionary<string, List<Type>>();
 289
 38290        foreach (var (k, v) in syringe.FunctionGroupMap ?? new Dictionary<string, IReadOnlyList<Type>>())
 1291            merged[k] = new List<Type>(v);
 292
 298293        foreach (var (groupName, types) in newGroups)
 294        {
 131295            if (!merged.TryGetValue(groupName, out var list))
 130296                merged[groupName] = list = [];
 297
 552298            foreach (var t in types)
 145299                if (!list.Contains(t))
 145300                    list.Add(t);
 301        }
 302
 18303        return syringe with
 18304        {
 18305            FunctionGroupMap = merged.ToDictionary(
 131306                k => k.Key,
 131307                v => (IReadOnlyList<Type>)v.Value.AsReadOnly())
 18308        };
 309    }
 310}

Methods/Properties

UsingChatClient(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,Microsoft.Extensions.AI.IChatClient)
UsingChatClient(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Func`2<System.IServiceProvider,Microsoft.Extensions.AI.IChatClient>)
Configure(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Action`1<NexusLabs.Needlr.AgentFramework.AgentFrameworkConfigureOptions>)
AddAgentFunction(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe)
AddAgentFunctionsFromGenerated(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Collections.Generic.IReadOnlyList`1<System.Type>)
AddAgentFunctionsFromScanner(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,NexusLabs.Needlr.AgentFramework.IAgentFrameworkFunctionScanner)
AddAgentFunctionsFromAssemblies(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe)
AddAgentFunctionsFromAssemblies(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Collections.Generic.IReadOnlyList`1<System.Reflection.Assembly>)
AddAgentFunctionsFromProvider(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe)
AddAgentFunctions(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Collections.Generic.IReadOnlyList`1<System.Type>)
AddAgentFunctionGroupsFromGenerated(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Collections.Generic.IReadOnlyList`1<System.Type>>)
AddAgentFunctionGroupsFromAssemblies(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe)
AddAgentFunctionGroupsFromAssemblies(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Collections.Generic.IReadOnlyList`1<System.Reflection.Assembly>)
AddAgent(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe)
AddAgentsFromGenerated(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Collections.Generic.IReadOnlyList`1<System.Type>)
ConfigureMetrics(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Action`1<NexusLabs.Needlr.AgentFramework.Diagnostics.AgentFrameworkMetricsOptions>)
MergeFunctionGroups(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Collections.Generic.IReadOnlyList`1<System.Type>>)