< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Generators.ExtensionsCodeGenerator
Assembly: NexusLabs.Needlr.AgentFramework.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Generators/CodeGen/ExtensionsCodeGenerator.cs
Line coverage
99%
Covered lines: 411
Uncovered lines: 1
Coverable lines: 412
Total lines: 570
Line coverage: 99.7%
Branch coverage
93%
Covered branches: 75
Total branches: 80
Branch coverage: 93.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Generators/CodeGen/ExtensionsCodeGenerator.cs

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System.Collections.Generic;
 5using System.Collections.Immutable;
 6using System.Linq;
 7using System.Text;
 8
 9namespace NexusLabs.Needlr.AgentFramework.Generators;
 10
 11internal static class ExtensionsCodeGenerator
 12{
 13    public static string GenerateWorkflowFactoryExtensionsSource(
 14        Dictionary<(string InitialAgentTypeName, string InitialAgentClassName), List<(string TargetAgentTypeName, string
 15        Dictionary<string, List<string>> groupChatByGroupName,
 16        Dictionary<string, List<string>> sequenceByPipelineName,
 17        Dictionary<string, List<TerminationConditionEntry>> conditionsByAgentTypeName,
 18        Dictionary<string, GraphData> graphDataByName,
 19        string safeAssemblyName)
 20    {
 10121        var sb = new StringBuilder();
 10122        sb.AppendLine("// <auto-generated/>");
 10123        sb.AppendLine("// Needlr AgentFramework IWorkflowFactory Extension Methods");
 10124        sb.AppendLine("#nullable enable");
 10125        sb.AppendLine();
 10126        sb.AppendLine("using Microsoft.Agents.AI.Workflows;");
 10127        sb.AppendLine("using NexusLabs.Needlr.AgentFramework;");
 10128        sb.AppendLine();
 10129        sb.AppendLine($"namespace {safeAssemblyName}.Generated;");
 10130        sb.AppendLine();
 10131        sb.AppendLine("/// <summary>");
 10132        sb.AppendLine("/// Generated strongly-typed extension methods on <see cref=\"IWorkflowFactory\"/>.");
 10133        sb.AppendLine("/// Each method encapsulates an agent type or group name so the composition root");
 10134        sb.AppendLine("/// requires no direct agent type references or magic strings.");
 10135        sb.AppendLine("/// </summary>");
 10136        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 10137        sb.AppendLine("public static partial class WorkflowFactoryExtensions");
 10138        sb.AppendLine("{");
 39
 21740        foreach (var kvp in handoffByInitialAgent.OrderBy(k => k.Key.InitialAgentClassName))
 41        {
 542            var className = kvp.Key.InitialAgentClassName;
 543            var typeName = kvp.Key.InitialAgentTypeName;
 544            var methodName = $"Create{AgentDiscoveryHelper.StripAgentSuffix(className)}HandoffWorkflow";
 45
 546            sb.AppendLine($"    /// <summary>");
 547            sb.AppendLine($"    /// Creates a handoff workflow starting from <see cref=\"{typeName.Replace("global::", "
 548            sb.AppendLine($"    /// </summary>");
 549            sb.AppendLine($"    /// <remarks>");
 550            sb.AppendLine($"    /// Handoff targets declared via <see cref=\"global::NexusLabs.Needlr.AgentFramework.Age
 551            sb.AppendLine($"    /// <list type=\"bullet\">");
 2052            foreach (var (targetTypeName, reason) in kvp.Value)
 53            {
 554                var cref = targetTypeName.Replace("global::", "");
 555                if (string.IsNullOrEmpty(reason))
 456                    sb.AppendLine($"    /// <item><description><see cref=\"{cref}\"/></description></item>");
 57                else
 158                    sb.AppendLine($"    /// <item><description><see cref=\"{cref}\"/> — {reason}</description></item>");
 59            }
 560            sb.AppendLine($"    /// </list>");
 561            sb.AppendLine($"    /// </remarks>");
 562            sb.AppendLine($"    public static Workflow {methodName}(this IWorkflowFactory workflowFactory)");
 563            sb.AppendLine($"        => workflowFactory.CreateHandoffWorkflow<{typeName}>();");
 564            sb.AppendLine();
 65
 566            var allHandoffAgents = new[] { typeName }
 567                .Concat(kvp.Value.Select(v => v.TargetAgentTypeName))
 568                .ToList();
 569            var handoffConditions = allHandoffAgents
 1070                .SelectMany(t => conditionsByAgentTypeName.TryGetValue(t, out var c) ? c : Enumerable.Empty<TerminationC
 571                .ToList();
 72
 573            if (handoffConditions.Count > 0)
 74            {
 175                var runMethodName = $"Run{AgentDiscoveryHelper.StripAgentSuffix(className)}HandoffWorkflowAsync";
 176                sb.AppendLine($"    /// <summary>");
 177                sb.AppendLine($"    /// Creates and runs the handoff workflow starting from <see cref=\"{typeName.Replac
 178                sb.AppendLine($"    /// </summary>");
 179                sb.AppendLine($"    /// <remarks>");
 180                sb.AppendLine($"    /// Termination conditions are evaluated after each completed agent turn (Layer 2)."
 181                sb.AppendLine($"    /// The workflow stops early when any condition is satisfied.");
 182                sb.AppendLine($"    /// </remarks>");
 183                sb.AppendLine($"    /// <param name=\"workflowFactory\">The workflow factory.</param>");
 184                sb.AppendLine($"    /// <param name=\"message\">The input message to start the workflow.</param>");
 185                sb.AppendLine($"    /// <param name=\"cancellationToken\">Optional cancellation token.</param>");
 186                sb.AppendLine($"    /// <returns>A dictionary mapping executor IDs to their response text.</returns>");
 187                sb.AppendLine($"    public static async global::System.Threading.Tasks.Task<global::System.Collections.G
 188                sb.AppendLine($"        this IWorkflowFactory workflowFactory,");
 189                sb.AppendLine($"        string message,");
 190                sb.AppendLine($"        global::System.Threading.CancellationToken cancellationToken = default)");
 191                sb.AppendLine($"    {{");
 192                sb.AppendLine($"        var workflow = workflowFactory.{methodName}();");
 193                sb.AppendLine($"        global::System.Collections.Generic.IReadOnlyList<global::NexusLabs.Needlr.AgentF
 194                sb.AppendLine($"            new global::System.Collections.Generic.List<global::NexusLabs.Needlr.AgentFr
 195                sb.AppendLine($"            {{");
 496                foreach (var cond in handoffConditions)
 97                {
 198                    var args = cond.CtorArgLiterals.IsEmpty ? "" : string.Join(", ", cond.CtorArgLiterals);
 199                    sb.AppendLine($"                new {cond.ConditionTypeFQN}({args}),");
 100                }
 1101                sb.AppendLine($"            }};");
 1102                sb.AppendLine($"        return await global::NexusLabs.Needlr.AgentFramework.Workflows.StreamingRunWorkf
 1103                sb.AppendLine($"    }}");
 1104                sb.AppendLine();
 105            }
 106        }
 107
 214108        foreach (var kvp in groupChatByGroupName.OrderBy(k => k.Key))
 109        {
 4110            var groupName = kvp.Key;
 4111            var methodSuffix = AgentDiscoveryHelper.GroupNameToPascalCase(groupName);
 4112            var methodName = $"Create{methodSuffix}GroupChatWorkflow";
 4113            var escapedGroupName = groupName.Replace("\"", "\\\"");
 114
 4115            sb.AppendLine($"    /// <summary>");
 4116            sb.AppendLine($"    /// Creates a round-robin group chat workflow for the \"{escapedGroupName}\" group.");
 4117            sb.AppendLine($"    /// </summary>");
 4118            sb.AppendLine($"    /// <remarks>");
 4119            sb.AppendLine($"    /// Participants declared via <see cref=\"global::NexusLabs.Needlr.AgentFramework.AgentG
 4120            sb.AppendLine($"    /// <list type=\"bullet\">");
 24121            foreach (var participantTypeName in kvp.Value)
 122            {
 8123                var cref = participantTypeName.Replace("global::", "");
 8124                sb.AppendLine($"    /// <item><description><see cref=\"{cref}\"/></description></item>");
 125            }
 4126            sb.AppendLine($"    /// </list>");
 4127            sb.AppendLine($"    /// </remarks>");
 4128            sb.AppendLine($"    public static Workflow {methodName}(this IWorkflowFactory workflowFactory, int maxIterat
 4129            sb.AppendLine($"        => workflowFactory.CreateGroupChatWorkflow(\"{escapedGroupName}\", maxIterations);")
 4130            sb.AppendLine();
 131
 4132            var gcConditions = kvp.Value
 8133                .SelectMany(t => conditionsByAgentTypeName.TryGetValue(t, out var c) ? c : Enumerable.Empty<TerminationC
 4134                .ToList();
 135
 4136            if (gcConditions.Count > 0)
 137            {
 1138                var runMethodName = $"Run{methodSuffix}GroupChatWorkflowAsync";
 1139                sb.AppendLine($"    /// <summary>");
 1140                sb.AppendLine($"    /// Creates and runs the \"{escapedGroupName}\" group chat workflow, applying declar
 1141                sb.AppendLine($"    /// </summary>");
 1142                sb.AppendLine($"    /// <remarks>");
 1143                sb.AppendLine($"    /// Termination conditions are evaluated after each completed agent turn (Layer 2)."
 1144                sb.AppendLine($"    /// The workflow stops early when any condition is satisfied.");
 1145                sb.AppendLine($"    /// </remarks>");
 1146                sb.AppendLine($"    /// <param name=\"workflowFactory\">The workflow factory.</param>");
 1147                sb.AppendLine($"    /// <param name=\"message\">The input message to start the workflow.</param>");
 1148                sb.AppendLine($"    /// <param name=\"maxIterations\">Maximum number of group chat iterations.</param>")
 1149                sb.AppendLine($"    /// <param name=\"cancellationToken\">Optional cancellation token.</param>");
 1150                sb.AppendLine($"    /// <returns>A dictionary mapping executor IDs to their response text.</returns>");
 1151                sb.AppendLine($"    public static async global::System.Threading.Tasks.Task<global::System.Collections.G
 1152                sb.AppendLine($"        this IWorkflowFactory workflowFactory,");
 1153                sb.AppendLine($"        string message,");
 1154                sb.AppendLine($"        int maxIterations = 10,");
 1155                sb.AppendLine($"        global::System.Threading.CancellationToken cancellationToken = default)");
 1156                sb.AppendLine($"    {{");
 1157                sb.AppendLine($"        var workflow = workflowFactory.{methodName}(maxIterations);");
 1158                sb.AppendLine($"        global::System.Collections.Generic.IReadOnlyList<global::NexusLabs.Needlr.AgentF
 1159                sb.AppendLine($"            new global::System.Collections.Generic.List<global::NexusLabs.Needlr.AgentFr
 1160                sb.AppendLine($"            {{");
 4161                foreach (var cond in gcConditions)
 162                {
 1163                    var args = cond.CtorArgLiterals.IsEmpty ? "" : string.Join(", ", cond.CtorArgLiterals);
 1164                    sb.AppendLine($"                new {cond.ConditionTypeFQN}({args}),");
 165                }
 1166                sb.AppendLine($"            }};");
 1167                sb.AppendLine($"        return await global::NexusLabs.Needlr.AgentFramework.Workflows.StreamingRunWorkf
 1168                sb.AppendLine($"    }}");
 1169                sb.AppendLine();
 170            }
 171        }
 172
 241173        foreach (var kvp in sequenceByPipelineName.OrderBy(k => k.Key))
 174        {
 13175            var pipelineName = kvp.Key;
 13176            var methodSuffix = AgentDiscoveryHelper.GroupNameToPascalCase(pipelineName);
 13177            var methodName = $"Create{methodSuffix}SequentialWorkflow";
 13178            var escapedPipelineName = pipelineName.Replace("\"", "\\\"");
 179
 13180            sb.AppendLine($"    /// <summary>");
 13181            sb.AppendLine($"    /// Creates a sequential pipeline workflow for the \"{escapedPipelineName}\" pipeline.")
 13182            sb.AppendLine($"    /// </summary>");
 13183            sb.AppendLine($"    /// <remarks>");
 13184            sb.AppendLine($"    /// Agents declared via <see cref=\"global::NexusLabs.Needlr.AgentFramework.AgentSequenc
 13185            sb.AppendLine($"    /// <list type=\"number\">");
 74186            foreach (var memberTypeName in kvp.Value)
 187            {
 24188                var cref = memberTypeName.Replace("global::", "");
 24189                sb.AppendLine($"    /// <item><description><see cref=\"{cref}\"/></description></item>");
 190            }
 13191            sb.AppendLine($"    /// </list>");
 13192            sb.AppendLine($"    /// </remarks>");
 13193            sb.AppendLine($"    public static Workflow {methodName}(this IWorkflowFactory workflowFactory)");
 13194            sb.AppendLine($"        => workflowFactory.CreateSequentialWorkflow(\"{escapedPipelineName}\");");
 13195            sb.AppendLine();
 196
 13197            var seqConditions = kvp.Value
 24198                .SelectMany(t => conditionsByAgentTypeName.TryGetValue(t, out var c) ? c : Enumerable.Empty<TerminationC
 13199                .ToList();
 200
 13201            if (seqConditions.Count > 0)
 202            {
 5203                var runMethodName = $"Run{methodSuffix}SequentialWorkflowAsync";
 5204                sb.AppendLine($"    /// <summary>");
 5205                sb.AppendLine($"    /// Creates and runs the \"{escapedPipelineName}\" sequential workflow, applying dec
 5206                sb.AppendLine($"    /// </summary>");
 5207                sb.AppendLine($"    /// <remarks>");
 5208                sb.AppendLine($"    /// Termination conditions are evaluated after each completed agent turn (Layer 2)."
 5209                sb.AppendLine($"    /// The workflow stops early when any condition is satisfied.");
 5210                sb.AppendLine($"    /// </remarks>");
 5211                sb.AppendLine($"    /// <param name=\"workflowFactory\">The workflow factory.</param>");
 5212                sb.AppendLine($"    /// <param name=\"message\">The input message to start the workflow.</param>");
 5213                sb.AppendLine($"    /// <param name=\"cancellationToken\">Optional cancellation token.</param>");
 5214                sb.AppendLine($"    /// <returns>A dictionary mapping executor IDs to their response text.</returns>");
 5215                sb.AppendLine($"    public static async global::System.Threading.Tasks.Task<global::System.Collections.G
 5216                sb.AppendLine($"        this IWorkflowFactory workflowFactory,");
 5217                sb.AppendLine($"        string message,");
 5218                sb.AppendLine($"        global::System.Threading.CancellationToken cancellationToken = default)");
 5219                sb.AppendLine($"    {{");
 5220                sb.AppendLine($"        var workflow = workflowFactory.{methodName}();");
 5221                sb.AppendLine($"        global::System.Collections.Generic.IReadOnlyList<global::NexusLabs.Needlr.AgentF
 5222                sb.AppendLine($"            new global::System.Collections.Generic.List<global::NexusLabs.Needlr.AgentFr
 5223                sb.AppendLine($"            {{");
 22224                foreach (var cond in seqConditions)
 225                {
 6226                    var args = cond.CtorArgLiterals.IsEmpty ? "" : string.Join(", ", cond.CtorArgLiterals);
 6227                    sb.AppendLine($"                new {cond.ConditionTypeFQN}({args}),");
 228                }
 5229                sb.AppendLine($"            }};");
 5230                sb.AppendLine($"        return await global::NexusLabs.Needlr.AgentFramework.Workflows.StreamingRunWorkf
 5231                sb.AppendLine($"    }}");
 5232                sb.AppendLine();
 233            }
 234        }
 235
 265236        foreach (var kvp in graphDataByName.OrderBy(k => k.Key))
 237        {
 21238            var graphName = kvp.Key;
 21239            var graphData = kvp.Value;
 21240            var methodSuffix = AgentDiscoveryHelper.GroupNameToPascalCase(graphName);
 21241            var methodName = $"Create{methodSuffix}GraphWorkflow";
 21242            var escapedGraphName = graphName.Replace("\"", "\\\"");
 243
 21244            sb.AppendLine($"    /// <summary>");
 21245            sb.AppendLine($"    /// Creates a DAG graph workflow for the \"{escapedGraphName}\" graph.");
 21246            sb.AppendLine($"    /// </summary>");
 21247            sb.AppendLine($"    /// <remarks>");
 21248            sb.AppendLine($"    /// Topology declared via <see cref=\"global::NexusLabs.Needlr.AgentFramework.AgentGraph
 21249            sb.AppendLine($"    /// <list type=\"bullet\">");
 88250            foreach (var edge in graphData.Edges)
 251            {
 23252                var sourceCref = AgentDiscoveryHelper.GetShortName(edge.SourceAgentTypeName);
 23253                var targetCref = AgentDiscoveryHelper.GetShortName(edge.TargetAgentTypeName);
 23254                if (string.IsNullOrEmpty(edge.Condition))
 17255                    sb.AppendLine($"    /// <item><description>{sourceCref} -> {targetCref}</description></item>");
 256                else
 6257                    sb.AppendLine($"    /// <item><description>{sourceCref} -> {targetCref} (condition: {edge.Condition}
 258            }
 21259            sb.AppendLine($"    /// </list>");
 21260            sb.AppendLine($"    /// </remarks>");
 21261            sb.AppendLine($"    public static global::Microsoft.Agents.AI.Workflows.Workflow {methodName}(this global::N
 21262            sb.AppendLine($"        => workflowFactory.CreateGraphWorkflow(\"{escapedGraphName}\");");
 21263            sb.AppendLine();
 264
 265            // Run helper — thin convenience wrapper that bakes in the graph name at compile time
 21266            var runMethodName = $"Run{methodSuffix}GraphWorkflowAsync";
 21267            sb.AppendLine($"    /// <summary>");
 21268            sb.AppendLine($"    /// Runs the \"{escapedGraphName}\" graph workflow via <see cref=\"global::NexusLabs.Nee
 21269            sb.AppendLine($"    /// The graph name is baked in at compile time — no magic strings required.");
 21270            sb.AppendLine($"    /// </summary>");
 21271            sb.AppendLine($"    /// <param name=\"runner\">The graph workflow runner.</param>");
 21272            sb.AppendLine($"    /// <param name=\"input\">The input message to send to the entry node.</param>");
 21273            sb.AppendLine($"    /// <param name=\"progress\">Optional progress reporter for real-time execution events.<
 21274            sb.AppendLine($"    /// <param name=\"cancellationToken\">Cancellation token.</param>");
 21275            sb.AppendLine($"    /// <returns>An <see cref=\"global::NexusLabs.Needlr.AgentFramework.Diagnostics.IDagRunR
 21276            sb.AppendLine($"    public static async global::System.Threading.Tasks.Task<global::NexusLabs.Needlr.AgentFr
 21277            sb.AppendLine($"        this global::NexusLabs.Needlr.AgentFramework.IGraphWorkflowRunner runner,");
 21278            sb.AppendLine($"        string input,");
 21279            sb.AppendLine($"        global::NexusLabs.Needlr.AgentFramework.Progress.IProgressReporter? progress = null,
 21280            sb.AppendLine($"        global::System.Threading.CancellationToken cancellationToken = default)");
 21281            sb.AppendLine($"        => await runner.RunGraphAsync(\"{escapedGraphName}\", input, progress, cancellationT
 21282            sb.AppendLine();
 283        }
 284
 101285        sb.AppendLine("}");
 101286        return sb.ToString();
 287    }
 288
 289    public static string GenerateAgentFactoryExtensionsSource(
 290        List<NeedlrAiAgentTypeInfo> agents,
 291        Dictionary<string, ImmutableArray<string>> progressSinksByAgent,
 292        string safeAssemblyName)
 293    {
 101294        var sb = new StringBuilder();
 101295        sb.AppendLine("// <auto-generated/>");
 101296        sb.AppendLine("// Needlr AgentFramework IAgentFactory Extension Methods");
 101297        sb.AppendLine("#nullable enable");
 101298        sb.AppendLine();
 101299        sb.AppendLine("using Microsoft.Agents.AI;");
 101300        sb.AppendLine("using NexusLabs.Needlr.AgentFramework;");
 101301        sb.AppendLine();
 101302        sb.AppendLine($"namespace {safeAssemblyName}.Generated;");
 101303        sb.AppendLine();
 101304        sb.AppendLine("/// <summary>");
 101305        sb.AppendLine("/// Generated strongly-typed extension methods on <see cref=\"IAgentFactory\"/>.");
 101306        sb.AppendLine("/// Each method creates an agent from its <c>[NeedlrAiAgent]</c> declaration,");
 101307        sb.AppendLine("/// eliminating magic strings and direct type references at the composition root.");
 101308        sb.AppendLine("/// </summary>");
 101309        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 101310        sb.AppendLine("public static class GeneratedAgentFactoryExtensions");
 101311        sb.AppendLine("{");
 312
 523313        foreach (var agent in agents.OrderBy(a => a.ClassName))
 314        {
 107315            sb.AppendLine($"    /// <summary>");
 107316            sb.AppendLine($"    /// Creates an <see cref=\"AIAgent\"/> configured for <see cref=\"{agent.TypeName.Replac
 107317            sb.AppendLine($"    /// </summary>");
 107318            sb.AppendLine($"    public static AIAgent Create{agent.ClassName}(this IAgentFactory factory)");
 107319            sb.AppendLine($"        => factory.CreateAgent<{agent.TypeName}>();");
 107320            sb.AppendLine();
 321
 322            // If this agent has [ProgressSinks], emit a companion method
 107323            if (progressSinksByAgent.TryGetValue(agent.ClassName, out var sinkFQNs) && sinkFQNs.Length > 0)
 324            {
 4325                sb.AppendLine($"    /// <summary>");
 4326                sb.AppendLine($"    /// Returns the progress sink types declared via <c>[ProgressSinks]</c> on <see cref
 4327                sb.AppendLine($"    /// Orchestrators use this to create reporters with the correct sinks for this agent
 4328                sb.AppendLine($"    /// </summary>");
 4329                sb.AppendLine($"    public static global::System.Type[] Get{agent.ClassName}ProgressSinkTypes()");
 4330                sb.AppendLine("    {");
 4331                sb.AppendLine("        return new global::System.Type[]");
 4332                sb.AppendLine("        {");
 16333                foreach (var sinkFQN in sinkFQNs)
 334                {
 4335                    sb.AppendLine($"            typeof({sinkFQN}),");
 336                }
 4337                sb.AppendLine("        };");
 4338                sb.AppendLine("    }");
 4339                sb.AppendLine();
 340
 341                // Auto-wiring method: resolves sinks from DI, creates reporter, sets scope.
 342                // The returned IDisposable is a CompositeDisposable that tears down both the
 343                // reporter accessor scope AND any sink instances that implement IDisposable,
 344                // preventing leaks of sinks instantiated via ActivatorUtilities.
 4345                sb.AppendLine($"    /// <summary>");
 4346                sb.AppendLine($"    /// Opens a progress reporting scope for <see cref=\"{agent.TypeName.Replace("global
 4347                sb.AppendLine($"    /// the sinks declared via <c>[ProgressSinks]</c>. Dispose the returned handle to en
 4348                sb.AppendLine($"    /// and dispose any sinks that implement <see cref=\"global::System.IDisposable\"/>.
 4349                sb.AppendLine($"    /// </summary>");
 4350                sb.AppendLine($"    public static global::System.IDisposable Begin{agent.ClassName}ProgressScope(");
 4351                sb.AppendLine($"        this global::System.IServiceProvider sp,");
 4352                sb.AppendLine($"        string? workflowId = null)");
 4353                sb.AppendLine("    {");
 354                // Materialize each sink into its own local so we can pass them both to the
 355                // IProgressSink[] array AND as 'as IDisposable' casts to the composite.
 16356                for (int i = 0; i < sinkFQNs.Length; i++)
 357                {
 4358                    sb.AppendLine($"        var sink{i} = global::Microsoft.Extensions.DependencyInjection.ActivatorUtil
 359                }
 4360                sb.AppendLine($"        var sinks = new global::NexusLabs.Needlr.AgentFramework.Progress.IProgressSink[]
 4361                sb.AppendLine("        {");
 16362                for (int i = 0; i < sinkFQNs.Length; i++)
 363                {
 4364                    sb.AppendLine($"            sink{i},");
 365                }
 4366                sb.AppendLine("        };");
 4367                sb.AppendLine($"        var factory = global::Microsoft.Extensions.DependencyInjection.ServiceProviderSe
 4368                sb.AppendLine($"        var accessor = global::Microsoft.Extensions.DependencyInjection.ServiceProviderS
 4369                sb.AppendLine("        var reporter = factory.Create(workflowId ?? \"" + agent.ClassName + "-\" + global
 4370                sb.AppendLine("        var scopeHandle = accessor.BeginScope(reporter);");
 4371                sb.AppendLine("        return new global::NexusLabs.Needlr.AgentFramework.Progress.CompositeDisposable("
 4372                sb.AppendLine("            new global::System.IDisposable?[]");
 4373                sb.AppendLine("            {");
 4374                sb.AppendLine("                scopeHandle,");
 375                // Box through object so the 'as IDisposable' cast compiles regardless
 376                // of whether the sealed sink type statically implements IDisposable.
 377                // Sinks that don't implement IDisposable become null and are skipped
 378                // by CompositeDisposable's null-tolerant disposal loop.
 16379                for (int i = 0; i < sinkFQNs.Length; i++)
 380                {
 4381                    sb.AppendLine($"                ((object)sink{i}) as global::System.IDisposable,");
 382                }
 4383                sb.AppendLine("            });");
 4384                sb.AppendLine("    }");
 4385                sb.AppendLine();
 386            }
 387        }
 388
 101389        sb.AppendLine("}");
 101390        return sb.ToString();
 391    }
 392
 393    public static string GenerateProgressSinkRegistrationSource(
 394        Dictionary<string, ImmutableArray<string>> progressSinksByAgent,
 395        string safeAssemblyName)
 396    {
 397        // Collect all unique sink types across all agents
 3398        var allSinkFQNs = progressSinksByAgent.Values
 4399            .SelectMany(v => v)
 3400            .Distinct()
 2401            .OrderBy(s => s)
 3402            .ToList();
 403
 3404        var sb = new StringBuilder();
 3405        sb.AppendLine("// <auto-generated/>");
 3406        sb.AppendLine("// Needlr AgentFramework Progress Sink DI Registration");
 3407        sb.AppendLine("#nullable enable");
 3408        sb.AppendLine();
 3409        sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 3410        sb.AppendLine("using Microsoft.Extensions.DependencyInjection.Extensions;");
 3411        sb.AppendLine();
 3412        sb.AppendLine($"namespace {safeAssemblyName}.Generated;");
 3413        sb.AppendLine();
 3414        sb.AppendLine("/// <summary>");
 3415        sb.AppendLine("/// Registers all progress sink types discovered via <c>[ProgressSinks]</c> attributes.");
 3416        sb.AppendLine("/// Call from your composition root to register all agent-declared sinks in DI.");
 3417        sb.AppendLine("/// </summary>");
 3418        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 3419        sb.AppendLine("public static class GeneratedProgressSinkRegistrations");
 3420        sb.AppendLine("{");
 3421        sb.AppendLine("    /// <summary>");
 3422        sb.AppendLine("    /// Registers all sink types found on [ProgressSinks] agent attributes as singletons.");
 3423        sb.AppendLine("    /// </summary>");
 3424        sb.AppendLine("    public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddGenerate
 3425        sb.AppendLine("        this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services)");
 3426        sb.AppendLine("    {");
 427
 3428        if (allSinkFQNs.Count == 0)
 429        {
 0430            sb.AppendLine("        // No [ProgressSinks] attributes found.");
 431        }
 432        else
 433        {
 434            // Register each concrete sink as a singleton once, then expose it via
 435            // AddSingleton<IProgressSink>(factory) so multiple sinks stack in
 436            // GetServices<IProgressSink>(). TryAddSingleton<IProgressSink, T>()
 437            // would silently drop every sink after the first.
 14438            foreach (var sinkFQN in allSinkFQNs)
 439            {
 4440                sb.AppendLine($"        services.TryAddSingleton<{sinkFQN}>();");
 4441                sb.AppendLine($"        services.AddSingleton<global::NexusLabs.Needlr.AgentFramework.Progress.IProgress
 442            }
 443        }
 444
 3445        sb.AppendLine("        return services;");
 3446        sb.AppendLine("    }");
 3447        sb.AppendLine("}");
 3448        return sb.ToString();
 449    }
 450
 451    public static string GenerateAgentTopologyConstantsSource(
 452        List<NeedlrAiAgentTypeInfo> agents,
 453        List<AgentFunctionGroupEntry> groupEntries,
 454        Dictionary<string, List<string>> sequenceByPipelineName,
 455        IEnumerable<string> graphNames,
 456        string safeAssemblyName)
 457    {
 101458        var sb = new StringBuilder();
 101459        sb.AppendLine("// <auto-generated/>");
 101460        sb.AppendLine("// Needlr AgentFramework Topology Constants");
 101461        sb.AppendLine("#nullable enable");
 101462        sb.AppendLine();
 101463        sb.AppendLine($"namespace {safeAssemblyName}.Generated;");
 101464        sb.AppendLine();
 465
 101466        sb.AppendLine("/// <summary>String constants for agent type names discovered at compile time.</summary>");
 101467        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 101468        sb.AppendLine("public static class AgentNames");
 101469        sb.AppendLine("{");
 523470        foreach (var agent in agents.OrderBy(a => a.ClassName))
 471        {
 107472            var fullName = agent.NamespaceName is not null
 107473                ? $"{agent.NamespaceName}.{agent.ClassName}"
 107474                : agent.ClassName;
 107475            var escapedFullName = fullName.Replace("\"", "\\\"");
 107476            sb.AppendLine($"    /// <summary>The fully-qualified name of <see cref=\"{agent.TypeName.Replace("global::",
 107477            sb.AppendLine($"    public const string {agent.ClassName} = \"{escapedFullName}\";");
 478        }
 101479        sb.AppendLine("}");
 101480        sb.AppendLine();
 481
 119482        var groupNames = groupEntries.Select(e => e.GroupName).Distinct().OrderBy(g => g).ToList();
 101483        sb.AppendLine("/// <summary>String constants for function group names discovered at compile time.</summary>");
 101484        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 101485        sb.AppendLine("public static class GroupNames");
 101486        sb.AppendLine("{");
 228487        foreach (var gn in groupNames)
 488        {
 13489            var propName = AgentDiscoveryHelper.GroupNameToPascalCase(gn);
 13490            var escaped = gn.Replace("\"", "\\\"");
 13491            sb.AppendLine($"    /// <summary>The group name <c>\"{escaped}\"</c>.</summary>");
 13492            sb.AppendLine($"    public const string {propName} = \"{escaped}\";");
 493        }
 101494        sb.AppendLine("}");
 101495        sb.AppendLine();
 496
 101497        sb.AppendLine("/// <summary>String constants for sequential pipeline names discovered at compile time.</summary>
 101498        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 101499        sb.AppendLine("public static class PipelineNames");
 101500        sb.AppendLine("{");
 241501        foreach (var pn in sequenceByPipelineName.Keys.OrderBy(k => k))
 502        {
 13503            var propName = AgentDiscoveryHelper.GroupNameToPascalCase(pn);
 13504            var escaped = pn.Replace("\"", "\\\"");
 13505            sb.AppendLine($"    /// <summary>The pipeline name <c>\"{escaped}\"</c>.</summary>");
 13506            sb.AppendLine($"    public const string {propName} = \"{escaped}\";");
 507        }
 101508        sb.AppendLine("}");
 101509        sb.AppendLine();
 510
 101511        sb.AppendLine("/// <summary>String constants for graph names discovered at compile time.</summary>");
 101512        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 101513        sb.AppendLine("public static class GraphNames");
 101514        sb.AppendLine("{");
 265515        foreach (var gn in graphNames.OrderBy(k => k))
 516        {
 21517            var propName = AgentDiscoveryHelper.GroupNameToPascalCase(gn);
 21518            var escaped = gn.Replace("\"", "\\\"");
 21519            sb.AppendLine($"    /// <summary>The graph name <c>\"{escaped}\"</c>.</summary>");
 21520            sb.AppendLine($"    public const string {propName} = \"{escaped}\";");
 521        }
 101522        sb.AppendLine("}");
 523
 101524        return sb.ToString();
 525    }
 526
 527    public static string GenerateSyringeExtensionsSource(
 528        List<AgentFunctionGroupEntry> groupEntries,
 529        string safeAssemblyName)
 530    {
 101531        var sb = new StringBuilder();
 101532        sb.AppendLine("// <auto-generated/>");
 101533        sb.AppendLine("// Needlr AgentFramework AgentFrameworkSyringe Extension Methods");
 101534        sb.AppendLine("#nullable enable");
 101535        sb.AppendLine();
 101536        sb.AppendLine("using System.Diagnostics.CodeAnalysis;");
 101537        sb.AppendLine("using NexusLabs.Needlr.AgentFramework;");
 101538        sb.AppendLine();
 101539        sb.AppendLine($"namespace {safeAssemblyName}.Generated;");
 101540        sb.AppendLine();
 101541        sb.AppendLine("/// <summary>");
 101542        sb.AppendLine("/// Generated strongly-typed extension methods on <see cref=\"AgentFrameworkSyringe\"/>.");
 101543        sb.AppendLine("/// Each method registers a named function group without requiring direct type references.");
 101544        sb.AppendLine("/// </summary>");
 101545        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 101546        sb.AppendLine("public static class GeneratedAgentFrameworkSyringeExtensions");
 101547        sb.AppendLine("{");
 548
 101549        var byGroupName = groupEntries
 14550            .GroupBy(e => e.GroupName)
 114551            .OrderBy(g => g.Key);
 552
 228553        foreach (var grp in byGroupName)
 554        {
 13555            var methodName = $"With{AgentDiscoveryHelper.GroupNameToPascalCase(grp.Key)}";
 13556            var escapedGroupName = grp.Key.Replace("\"", "\\\"");
 27557            var types = grp.Select(e => e.TypeName).Distinct().ToList();
 558
 13559            sb.AppendLine($"    /// <summary>Registers the '{escapedGroupName}' function group with the syringe.</summar
 13560            sb.AppendLine($"    [RequiresUnreferencedCode(\"AgentFramework function setup uses reflection to discover [A
 13561            sb.AppendLine($"    [RequiresDynamicCode(\"AgentFramework function setup uses reflection APIs that require d
 13562            sb.AppendLine($"    public static AgentFrameworkSyringe {methodName}(this AgentFrameworkSyringe syringe)");
 27563            sb.AppendLine($"        => syringe.AddAgentFunctions(new global::System.Type[] {{ {string.Join(", ", types.S
 13564            sb.AppendLine();
 565        }
 566
 101567        sb.AppendLine("}");
 101568        return sb.ToString();
 569    }
 570}

Methods/Properties

GenerateWorkflowFactoryExtensionsSource(System.Collections.Generic.Dictionary`2<System.ValueTuple`2<System.String,System.String>,System.Collections.Generic.List`1<System.ValueTuple`2<System.String,System.String>>>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<System.String>>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<System.String>>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<NexusLabs.Needlr.AgentFramework.Generators.TerminationConditionEntry>>,System.Collections.Generic.Dictionary`2<System.String,NexusLabs.Needlr.AgentFramework.Generators.GraphData>,System.String)
GenerateAgentFactoryExtensionsSource(System.Collections.Generic.List`1<NexusLabs.Needlr.AgentFramework.Generators.NeedlrAiAgentTypeInfo>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Immutable.ImmutableArray`1<System.String>>,System.String)
GenerateProgressSinkRegistrationSource(System.Collections.Generic.Dictionary`2<System.String,System.Collections.Immutable.ImmutableArray`1<System.String>>,System.String)
GenerateAgentTopologyConstantsSource(System.Collections.Generic.List`1<NexusLabs.Needlr.AgentFramework.Generators.NeedlrAiAgentTypeInfo>,System.Collections.Generic.List`1<NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionGroupEntry>,System.Collections.Generic.Dictionary`2<System.String,System.Collections.Generic.List`1<System.String>>,System.Collections.Generic.IEnumerable`1<System.String>,System.String)
GenerateSyringeExtensionsSource(System.Collections.Generic.List`1<NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionGroupEntry>,System.String)