< 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
83%
Covered lines: 69
Uncovered lines: 14
Coverable lines: 83
Total lines: 284
Line coverage: 83.1%
Branch coverage
80%
Covered branches: 24
Total branches: 30
Branch coverage: 80%
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;
 3using NexusLabs.Needlr.AgentFramework.FunctionScanners;
 4
 5using System.Diagnostics.CodeAnalysis;
 6using System.Reflection;
 7
 8namespace NexusLabs.Needlr.AgentFramework;
 9
 10/// <summary>
 11/// Extension methods for <see cref="AgentFrameworkSyringe"/> providing fluent configuration
 12/// of the Microsoft Agent Framework integration.
 13/// </summary>
 14public static class AgentFrameworkSyringeExtensions
 15{
 16    /// <summary>
 17    /// Sets the <see cref="IChatClient"/> used by all agents created from the factory.
 18    /// This is the preferred alternative to calling
 19    /// <see cref="Configure"/> and setting <see cref="AgentFrameworkConfigureOptions.ChatClientFactory"/>.
 20    /// </summary>
 21    public static AgentFrameworkSyringe UsingChatClient(
 22        this AgentFrameworkSyringe syringe,
 23        IChatClient chatClient)
 24    {
 025        ArgumentNullException.ThrowIfNull(syringe);
 026        ArgumentNullException.ThrowIfNull(chatClient);
 27
 028        return syringe.Configure(opts => opts.ChatClientFactory = _ => chatClient);
 29    }
 30
 31    /// <summary>
 32    /// Sets a factory that creates the <see cref="IChatClient"/> used by all agents.
 33    /// The factory receives the DI <see cref="IServiceProvider"/> for resolving dependencies.
 34    /// </summary>
 35    public static AgentFrameworkSyringe UsingChatClient(
 36        this AgentFrameworkSyringe syringe,
 37        Func<IServiceProvider, IChatClient> chatClientFactory)
 38    {
 039        ArgumentNullException.ThrowIfNull(syringe);
 040        ArgumentNullException.ThrowIfNull(chatClientFactory);
 41
 042        return syringe.Configure(opts => opts.ChatClientFactory = chatClientFactory);
 43    }
 44
 45    public static AgentFrameworkSyringe Configure(
 46        this AgentFrameworkSyringe syringe,
 47        Action<AgentFrameworkConfigureOptions> configure)
 48    {
 6149        ArgumentNullException.ThrowIfNull(syringe);
 6150        ArgumentNullException.ThrowIfNull(configure);
 51
 6152        return syringe with
 6153        {
 6154            ConfigureAgentFactory = (syringe.ConfigureAgentFactory ?? []).Append(configure).ToList()
 6155        };
 56    }
 57
 58    public static AgentFrameworkSyringe AddAgentFunction<T>(
 59        this AgentFrameworkSyringe syringe)
 60    {
 061        ArgumentNullException.ThrowIfNull(syringe);
 62
 063        return syringe.AddAgentFunctions([typeof(T)]);
 64    }
 65
 66    /// <summary>
 67    /// Adds agent functions from a compile-time generated list of types.
 68    /// This is the recommended approach for AOT/trimmed applications because
 69    /// the source generator discovers types at build time rather than runtime.
 70    /// </summary>
 71    /// <param name="syringe">The agent framework syringe to configure.</param>
 72    /// <param name="functionTypes">
 73    /// Compile-time discovered function types, typically from the generated
 74    /// <c>NexusLabs.Needlr.Generated.AgentFrameworkFunctions.AllFunctionTypes</c>.
 75    /// </param>
 76    /// <remarks>
 77    /// This overload performs no reflection for type discovery. The
 78    /// <see cref="AgentFactory"/> still uses reflection when building
 79    /// <see cref="Microsoft.Extensions.AI.AIFunction"/> schema from method signatures —
 80    /// the same inherent limitation present in Microsoft.Extensions.AI.
 81    /// </remarks>
 82    public static AgentFrameworkSyringe AddAgentFunctionsFromGenerated(
 83        this AgentFrameworkSyringe syringe,
 84        IReadOnlyList<Type> functionTypes)
 85    {
 886        ArgumentNullException.ThrowIfNull(syringe);
 887        ArgumentNullException.ThrowIfNull(functionTypes);
 88
 889        return syringe with
 890        {
 891            FunctionTypes = (syringe.FunctionTypes ?? []).Concat(functionTypes).Distinct().ToList()
 892        };
 93    }
 94
 95    public static AgentFrameworkSyringe AddAgentFunctionsFromScanner(
 96        this AgentFrameworkSyringe syringe,
 97        IAgentFrameworkFunctionScanner scanner)
 98    {
 1899        ArgumentNullException.ThrowIfNull(syringe);
 18100        ArgumentNullException.ThrowIfNull(scanner);
 101
 18102        return syringe.AddAgentFunctions(scanner.ScanForFunctionTypes());
 103    }
 104
 105    [RequiresUnreferencedCode("Assembly scanning uses reflection to discover types with [AgentFunction] methods.")]
 106    [RequiresDynamicCode("Assembly scanning uses reflection APIs that may require dynamic code generation.")]
 107    public static AgentFrameworkSyringe AddAgentFunctionsFromAssemblies(
 108        this AgentFrameworkSyringe syringe)
 109    {
 1110        ArgumentNullException.ThrowIfNull(syringe);
 111
 1112        var assemblies = syringe.ServiceProvider.GetRequiredService<IReadOnlyList<Assembly>>();
 1113        return syringe.AddAgentFunctionsFromAssemblies(assemblies);
 114    }
 115
 116    [RequiresUnreferencedCode("Assembly scanning uses reflection to discover types with [AgentFunction] methods.")]
 117    [RequiresDynamicCode("Assembly scanning uses reflection APIs that may require dynamic code generation.")]
 118    public static AgentFrameworkSyringe AddAgentFunctionsFromAssemblies(
 119        this AgentFrameworkSyringe syringe,
 120        IReadOnlyList<Assembly> assemblies)
 121    {
 18122        ArgumentNullException.ThrowIfNull(syringe);
 18123        ArgumentNullException.ThrowIfNull(assemblies);
 124
 18125        var scanner = new AssemblyAgentFunctionScanner(assemblies);
 18126        return syringe.AddAgentFunctionsFromScanner(scanner);
 127    }
 128
 129    [RequiresUnreferencedCode("Service provider scanning uses reflection to discover types with [AgentFunction] methods.
 130    [RequiresDynamicCode("Service provider scanning uses reflection APIs that may require dynamic code generation.")]
 131    public static AgentFrameworkSyringe AddAgentFunctionsFromProvider(
 132        this AgentFrameworkSyringe syringe)
 133    {
 0134        ArgumentNullException.ThrowIfNull(syringe);
 135
 0136        var scanner = new ServiceProviderAgentFunctionScanner(syringe.ServiceProvider);
 0137        return syringe.AddAgentFunctionsFromScanner(scanner);
 138    }
 139
 140    [RequiresUnreferencedCode("Function type inspection uses reflection to check for [AgentFunction] methods.")]
 141    [RequiresDynamicCode("Function type inspection uses reflection APIs that may require dynamic code generation.")]
 142    public static AgentFrameworkSyringe AddAgentFunctions(
 143        this AgentFrameworkSyringe syringe,
 144        IReadOnlyList<Type> functionTypes)
 145    {
 20146        ArgumentNullException.ThrowIfNull(syringe);
 20147        ArgumentNullException.ThrowIfNull(functionTypes);
 148
 20149        List<Type> typesToAdd = [];
 150
 404151        foreach (var functionType in functionTypes)
 152        {
 182153            var bindingFlags = functionType.IsStatic()
 182154                ? BindingFlags.Public | BindingFlags.Static
 182155                : BindingFlags.Public | BindingFlags.Instance;
 156
 182157            if (!functionType.GetMethods(bindingFlags)
 364158                .Any(m => m.IsDefined(typeof(AgentFunctionAttribute), inherit: true)))
 159            {
 160                continue;
 161            }
 162
 182163            typesToAdd.Add(functionType);
 164        }
 165
 20166        return syringe with
 20167        {
 20168            FunctionTypes = (syringe.FunctionTypes ?? []).Concat(typesToAdd).Distinct().ToList()
 20169        };
 170    }
 171
 172    /// <summary>
 173    /// Adds agent function groups from a compile-time generated group map.
 174    /// This is the recommended approach for AOT/trimmed applications.
 175    /// </summary>
 176    /// <param name="syringe">The agent framework syringe to configure.</param>
 177    /// <param name="groups">
 178    /// Compile-time discovered group map, typically from the generated
 179    /// <c>AgentFrameworkFunctionGroupRegistry.AllGroups</c>.
 180    /// </param>
 181    public static AgentFrameworkSyringe AddAgentFunctionGroupsFromGenerated(
 182        this AgentFrameworkSyringe syringe,
 183        IReadOnlyDictionary<string, IReadOnlyList<Type>> groups)
 184    {
 5185        ArgumentNullException.ThrowIfNull(syringe);
 5186        ArgumentNullException.ThrowIfNull(groups);
 187
 5188        return syringe.MergeFunctionGroups(groups);
 189    }
 190
 191    /// <summary>
 192    /// Scans registered assemblies for classes decorated with
 193    /// <see cref="AgentFunctionGroupAttribute"/> and registers them by group name.
 194    /// Requires <c>UsingReflection()</c> to be configured.
 195    /// </summary>
 196    [RequiresUnreferencedCode("Assembly scanning uses reflection to discover types with [AgentFunctionGroup] attributes.
 197    [RequiresDynamicCode("Assembly scanning uses reflection APIs that may require dynamic code generation.")]
 198    public static AgentFrameworkSyringe AddAgentFunctionGroupsFromAssemblies(
 199        this AgentFrameworkSyringe syringe)
 200    {
 0201        ArgumentNullException.ThrowIfNull(syringe);
 202
 0203        var assemblies = syringe.ServiceProvider.GetRequiredService<IReadOnlyList<Assembly>>();
 0204        return syringe.AddAgentFunctionGroupsFromAssemblies(assemblies);
 205    }
 206
 207    /// <summary>
 208    /// Scans the provided assemblies for classes decorated with
 209    /// <see cref="AgentFunctionGroupAttribute"/> and registers them by group name.
 210    /// </summary>
 211    [RequiresUnreferencedCode("Assembly scanning uses reflection to discover types with [AgentFunctionGroup] attributes.
 212    [RequiresDynamicCode("Assembly scanning uses reflection APIs that may require dynamic code generation.")]
 213    public static AgentFrameworkSyringe AddAgentFunctionGroupsFromAssemblies(
 214        this AgentFrameworkSyringe syringe,
 215        IReadOnlyList<Assembly> assemblies)
 216    {
 10217        ArgumentNullException.ThrowIfNull(syringe);
 10218        ArgumentNullException.ThrowIfNull(assemblies);
 219
 10220        var scanner = new FunctionScanners.AssemblyAgentFunctionGroupScanner(assemblies);
 10221        var groups = scanner.ScanForFunctionGroups();
 10222        return syringe.MergeFunctionGroups(groups);
 223    }
 224
 225    /// <summary>
 226    /// Registers a single declared agent type for string-based lookup via
 227    /// <see cref="IAgentFactory.CreateAgent(string)"/>.
 228    /// The type must be decorated with <see cref="NeedlrAiAgentAttribute"/>.
 229    /// </summary>
 230    public static AgentFrameworkSyringe AddAgent<TAgent>(
 231        this AgentFrameworkSyringe syringe)
 232        where TAgent : class
 233    {
 113234        ArgumentNullException.ThrowIfNull(syringe);
 113235        return syringe.AddAgentsFromGenerated([typeof(TAgent)]);
 236    }
 237
 238    /// <summary>
 239    /// Registers a compile-time generated list of declared agent types for
 240    /// string-based lookup via <see cref="IAgentFactory.CreateAgent(string)"/>.
 241    /// Typically called with the output of the generated <c>AgentRegistry.AllAgentTypes</c>.
 242    /// </summary>
 243    public static AgentFrameworkSyringe AddAgentsFromGenerated(
 244        this AgentFrameworkSyringe syringe,
 245        IReadOnlyList<Type> agentTypes)
 246    {
 114247        ArgumentNullException.ThrowIfNull(syringe);
 114248        ArgumentNullException.ThrowIfNull(agentTypes);
 249
 114250        return syringe with
 114251        {
 114252            AgentTypes = (syringe.AgentTypes ?? []).Concat(agentTypes).Distinct().ToList()
 114253        };
 254    }
 255
 256    private static AgentFrameworkSyringe MergeFunctionGroups(        this AgentFrameworkSyringe syringe,
 257        IReadOnlyDictionary<string, IReadOnlyList<Type>> newGroups)
 258    {
 15259        if (newGroups.Count == 0)
 1260            return syringe;
 261
 14262        var merged = new Dictionary<string, List<Type>>();
 263
 30264        foreach (var (k, v) in syringe.FunctionGroupMap ?? new Dictionary<string, IReadOnlyList<Type>>())
 1265            merged[k] = new List<Type>(v);
 266
 198267        foreach (var (groupName, types) in newGroups)
 268        {
 85269            if (!merged.TryGetValue(groupName, out var list))
 84270                merged[groupName] = list = [];
 271
 360272            foreach (var t in types)
 95273                if (!list.Contains(t))
 95274                    list.Add(t);
 275        }
 276
 14277        return syringe with
 14278        {
 14279            FunctionGroupMap = merged.ToDictionary(
 85280                k => k.Key,
 85281                v => (IReadOnlyList<Type>)v.Value.AsReadOnly())
 14282        };
 283    }
 284}

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>)
MergeFunctionGroups(NexusLabs.Needlr.AgentFramework.AgentFrameworkSyringe,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Collections.Generic.IReadOnlyList`1<System.Type>>)