< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Diagnostics.TeeDiagnosticsSink
Assembly: NexusLabs.Needlr.AgentFramework
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/Diagnostics/TeeDiagnosticsSink.cs
Line coverage
100%
Covered lines: 22
Uncovered lines: 0
Coverable lines: 22
Total lines: 96
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
get_AgentName()100%11100%
NextChatCompletionSequence()100%11100%
NextToolCallSequence()100%11100%
AddChatCompletion(...)100%22100%
AddToolCall(...)100%22100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/Diagnostics/TeeDiagnosticsSink.cs

#LineLine coverage
 1namespace NexusLabs.Needlr.AgentFramework.Diagnostics;
 2
 3/// <summary>
 4/// An <see cref="IDiagnosticsSink"/> that dispatches every record to N inner sinks.
 5/// Owns its own sequence counters so all sinks see consistent sequence numbers.
 6/// </summary>
 7/// <remarks>
 8/// <para>
 9/// Secondary sink failures are best-effort: if an inner sink throws, the exception
 10/// is swallowed and dispatch continues to remaining sinks. This ensures that a
 11/// broken observability sink (e.g., a file writer with a full disk) never breaks
 12/// production agent execution.
 13/// </para>
 14/// <para>
 15/// Use this when you need to fan out diagnostic records to multiple consumers —
 16/// for example, an in-memory builder for programmatic access plus a file-based
 17/// sink for post-hoc analysis.
 18/// </para>
 19/// </remarks>
 20/// <example>
 21/// <code>
 22/// var inMemory = AgentRunDiagnosticsBuilder.StartNew("Agent");
 23/// var fileSink = new MyFileDiagnosticsSink("Agent");
 24/// var tee = new TeeDiagnosticsSink("Agent", [inMemory, fileSink]);
 25/// tee.AddToolCall(toolDiag); // dispatched to both sinks
 26/// </code>
 27/// </example>
 28public sealed class TeeDiagnosticsSink : IDiagnosticsSink
 29{
 30    private readonly IReadOnlyList<IDiagnosticsSink> _sinks;
 31    private int _nextChatCompletionSequence;
 32    private int _nextToolCallSequence;
 33
 34    /// <summary>
 35    /// Creates a tee-sink that dispatches to the specified inner sinks.
 36    /// </summary>
 37    /// <param name="agentName">The agent name attributed to records routed through this sink.</param>
 38    /// <param name="sinks">The inner sinks to dispatch to. Must not be empty.</param>
 39    /// <exception cref="ArgumentNullException"><paramref name="sinks"/> is <see langword="null"/>.</exception>
 40    /// <exception cref="ArgumentException"><paramref name="sinks"/> is empty.</exception>
 1341    public TeeDiagnosticsSink(string agentName, IReadOnlyList<IDiagnosticsSink> sinks)
 42    {
 1343        ArgumentNullException.ThrowIfNull(sinks);
 1244        if (sinks.Count == 0)
 45        {
 146            throw new ArgumentException("At least one inner sink is required.", nameof(sinks));
 47        }
 48
 1149        AgentName = agentName;
 1150        _sinks = sinks;
 1151    }
 52
 53    /// <inheritdoc />
 154    public string? AgentName { get; }
 55
 56    /// <inheritdoc />
 57    public int NextChatCompletionSequence() =>
 458        Interlocked.Increment(ref _nextChatCompletionSequence) - 1;
 59
 60    /// <inheritdoc />
 61    public int NextToolCallSequence() =>
 10462        Interlocked.Increment(ref _nextToolCallSequence) - 1;
 63
 64    /// <inheritdoc />
 65    public void AddChatCompletion(ChatCompletionDiagnostics diagnostics)
 66    {
 1667        for (var i = 0; i < _sinks.Count; i++)
 68        {
 69            try
 70            {
 571                _sinks[i].AddChatCompletion(diagnostics);
 372            }
 273            catch
 74            {
 75                // Best-effort: swallow failures from individual sinks so one
 76                // broken sink does not prevent other sinks from receiving data.
 277            }
 78        }
 379    }
 80
 81    /// <inheritdoc />
 82    public void AddToolCall(ToolCallDiagnostics diagnostics)
 83    {
 1684        for (var i = 0; i < _sinks.Count; i++)
 85        {
 86            try
 87            {
 588                _sinks[i].AddToolCall(diagnostics);
 389            }
 290            catch
 91            {
 92                // Best-effort: swallow failures from individual sinks.
 293            }
 94        }
 395    }
 96}