< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Generators.TopologyGraphCodeGenerator
Assembly: NexusLabs.Needlr.AgentFramework.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Generators/CodeGen/TopologyGraphCodeGenerator.cs
Line coverage
75%
Covered lines: 57
Uncovered lines: 19
Coverable lines: 76
Total lines: 160
Line coverage: 75%
Branch coverage
69%
Covered branches: 36
Total branches: 52
Branch coverage: 69.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GenerateTopologyGraphSource(...)100%11100%
GenerateMermaidDiagram(...)68.75%1254867.79%
SanitizeMermaidId(...)75%44100%
EscapeMermaidLabel(...)100%11100%

File(s)

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

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System.Collections.Generic;
 5using System.Linq;
 6using System.Text;
 7
 8namespace NexusLabs.Needlr.AgentFramework.Generators;
 9
 10internal static class TopologyGraphCodeGenerator
 11{
 12    public static string GenerateTopologyGraphSource(
 13        string diagram,
 14        string safeAssemblyName)
 15    {
 616        var escaped = diagram.Replace("\"", "\"\"");
 17
 618        var sb = new StringBuilder();
 619        sb.AppendLine("// <auto-generated/>");
 620        sb.AppendLine("#nullable enable");
 621        sb.AppendLine();
 622        sb.AppendLine($"namespace {safeAssemblyName}.Generated;");
 623        sb.AppendLine();
 624        sb.AppendLine("internal static class AgentTopologyGraphDiagnostics");
 625        sb.AppendLine("{");
 626        sb.AppendLine($"    public const string AgentTopologyGraph = @\"{escaped}\";");
 627        sb.AppendLine("}");
 628        return sb.ToString();
 29    }
 30
 31    public static string GenerateMermaidDiagram(
 32        Dictionary<(string InitialAgentTypeName, string InitialAgentClassName), List<(string TargetAgentTypeName, string
 33        Dictionary<string, List<string>> groupChatByGroupName,
 34        Dictionary<string, List<string>> sequenceByPipelineName,
 35        Dictionary<string, GraphData>? graphDataByName = null)
 36    {
 637        var sb = new StringBuilder();
 638        sb.AppendLine("graph TD");
 39
 1240        foreach (var kvp in handoffByInitialAgent.OrderBy(k => k.Key.InitialAgentClassName))
 41        {
 042            var sourceClass = kvp.Key.InitialAgentClassName;
 043            foreach (var (targetFqn, reason) in kvp.Value)
 44            {
 045                var targetClass = AgentDiscoveryHelper.GetShortName(targetFqn);
 046                if (string.IsNullOrWhiteSpace(reason))
 047                    sb.AppendLine($"    {sourceClass} --> {targetClass}");
 48                else
 049                    sb.AppendLine($"    {sourceClass} -->|\"{EscapeMermaidLabel(reason!)}\"| {targetClass}");
 50            }
 51        }
 52
 1253        foreach (var kvp in groupChatByGroupName.OrderBy(k => k.Key))
 54        {
 055            sb.AppendLine($"    subgraph GroupChat_{SanitizeMermaidId(kvp.Key)}");
 056            foreach (var memberFqn in kvp.Value)
 057                sb.AppendLine($"        {AgentDiscoveryHelper.GetShortName(memberFqn)}");
 058            sb.AppendLine("    end");
 59        }
 60
 1261        foreach (var kvp in sequenceByPipelineName.OrderBy(k => k.Key))
 62        {
 063            sb.AppendLine($"    subgraph Sequential_{SanitizeMermaidId(kvp.Key)}");
 064            var agents = kvp.Value;
 065            if (agents.Count == 1)
 66            {
 067                sb.AppendLine($"        {AgentDiscoveryHelper.GetShortName(agents[0])}");
 68            }
 69            else
 70            {
 071                for (int i = 0; i < agents.Count - 1; i++)
 72                {
 073                    var cur = AgentDiscoveryHelper.GetShortName(agents[i]);
 074                    var next = AgentDiscoveryHelper.GetShortName(agents[i + 1]);
 075                    sb.AppendLine($"        {cur} -->|\"{i + 1}\"| {next}");
 76                }
 77            }
 078            sb.AppendLine("    end");
 79        }
 80
 681        if (graphDataByName != null)
 82        {
 3083            foreach (var kvp in graphDataByName.OrderBy(k => k.Key))
 84            {
 685                var graphName = kvp.Key;
 686                var graphData = kvp.Value;
 687                sb.AppendLine($"    subgraph Graph_{SanitizeMermaidId(graphName)}");
 88
 89                // Collect entry point class names for stadium shape rendering
 690                var entryPointClassNames = new HashSet<string>();
 2491                foreach (var ep in graphData.EntryPoints)
 692                    entryPointClassNames.Add(ep.AgentClassName);
 93
 94                // Collect reducer class names for hexagon shape rendering
 695                var reducerClassNames = new HashSet<string>();
 1496                foreach (var r in graphData.Reducers)
 197                    reducerClassNames.Add(r.AgentClassName);
 98
 99                // Collect node join modes for labeling
 6100                var joinModeByClassName = new Dictionary<string, int>();
 14101                foreach (var node in graphData.Nodes)
 102                {
 1103                    if (node.JoinMode != 0)
 1104                        joinModeByClassName[node.AgentClassName] = node.JoinMode;
 105                }
 106
 107                // Emit entry point node declarations with stadium shape
 24108                foreach (var epClassName in entryPointClassNames)
 6109                    sb.AppendLine($"        {epClassName}([{epClassName}])");
 110
 111                // Emit reducer node declarations with hexagon shape
 14112                foreach (var reducerClassName in reducerClassNames)
 113                {
 1114                    if (!entryPointClassNames.Contains(reducerClassName))
 1115                        sb.AppendLine($"        {reducerClassName}{{{{{reducerClassName}}}}}");
 116                }
 117
 26118                foreach (var edge in graphData.Edges)
 119                {
 7120                    var source = edge.SourceAgentClassName;
 7121                    var target = AgentDiscoveryHelper.GetShortName(edge.TargetAgentTypeName);
 122
 7123                    var arrow = edge.IsRequired ? "-->" : "-.->";
 124
 125                    // Build label parts
 7126                    string? label = null;
 7127                    if (!string.IsNullOrWhiteSpace(edge.Condition))
 2128                        label = EscapeMermaidLabel(edge.Condition!);
 129
 130                    // Add JoinMode annotation on edges targeting nodes with non-default join mode
 7131                    if (joinModeByClassName.TryGetValue(target, out var joinMode))
 132                    {
 1133                        var joinLabel = joinMode == 1 ? "WaitAny" : $"JoinMode{joinMode}";
 1134                        label = label != null ? $"{label}, {joinLabel}" : joinLabel;
 135                    }
 136
 7137                    if (label != null)
 3138                        sb.AppendLine($"        {source} {arrow}|\"{label}\"| {target}");
 139                    else
 4140                        sb.AppendLine($"        {source} {arrow} {target}");
 141                }
 142
 6143                sb.AppendLine("    end");
 144            }
 145        }
 146
 6147        return sb.ToString();
 148    }
 149
 150    private static string SanitizeMermaidId(string name)
 151    {
 6152        var sb = new StringBuilder();
 68153        foreach (var c in name)
 28154            sb.Append(char.IsLetterOrDigit(c) ? c : '_');
 6155        return sb.ToString();
 156    }
 157
 158    private static string EscapeMermaidLabel(string label) =>
 2159        label.Replace("\"", "&quot;");
 160}