< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Diagnostics.DiagnosticsRecordingChatClient
Assembly: NexusLabs.Needlr.AgentFramework
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/Diagnostics/DiagnosticsRecordingChatClient.cs
Line coverage
91%
Covered lines: 11
Uncovered lines: 1
Coverable lines: 12
Total lines: 87
Line coverage: 91.6%
Branch coverage
83%
Covered branches: 5
Total branches: 6
Branch coverage: 83.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
GetResponseAsync(...)100%22100%
GetStreamingResponseAsync(...)50%2266.66%

File(s)

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

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2
 3using Microsoft.Extensions.AI;
 4
 5namespace NexusLabs.Needlr.AgentFramework.Diagnostics;
 6
 7/// <summary>
 8/// <see cref="DelegatingChatClient"/> that routes calls through a
 9/// <see cref="DiagnosticsChatClientMiddleware"/> and is detectable via
 10/// <c>GetService</c>. Because this extends
 11/// <see cref="DelegatingChatClient"/>, MEAI's <c>GetService</c> walks the
 12/// delegation chain and returns this instance when queried by type — enabling
 13/// idempotent middleware installation.
 14/// </summary>
 15/// <remarks>
 16/// <para>
 17/// Both <c>UsingDiagnostics()</c> and <c>IterativeAgentLoop</c> use this class
 18/// to wrap the chat client. Before wrapping, the loop checks
 19/// <c>chatClient.GetService&lt;DiagnosticsRecordingChatClient&gt;()</c> — if
 20/// one already exists in the pipeline, it skips installation. This makes it
 21/// structurally impossible for two diagnostics middlewares to both record
 22/// <see cref="ChatCompletionDiagnostics"/> to the same
 23/// <see cref="AgentRunDiagnosticsBuilder"/>.
 24/// </para>
 25/// </remarks>
 26[DoNotAutoRegister]
 27public sealed class DiagnosticsRecordingChatClient : DelegatingChatClient
 28{
 29    private readonly DiagnosticsChatClientMiddleware? _middleware;
 30
 31    /// <summary>
 32    /// Creates a new diagnostics-recording wrapper around the specified client.
 33    /// If the inner client chain already contains a
 34    /// <see cref="DiagnosticsRecordingChatClient"/>, this instance becomes a
 35    /// passthrough — it delegates directly without invoking the middleware,
 36    /// guaranteeing at most one active recorder per pipeline.
 37    /// </summary>
 38    /// <param name="innerClient">The inner client to delegate to.</param>
 39    /// <param name="middleware">The middleware that records chat completions.</param>
 40    internal DiagnosticsRecordingChatClient(
 41        IChatClient innerClient,
 42        DiagnosticsChatClientMiddleware middleware)
 17043        : base(innerClient)
 44    {
 17045        ArgumentNullException.ThrowIfNull(middleware);
 46
 47        // If the inner chain already contains a DiagnosticsRecordingChatClient,
 48        // become a passthrough. This makes triple/N-wrapping safe — only the
 49        // innermost instance records, all outer instances are no-ops.
 17050        if (innerClient.GetService<DiagnosticsRecordingChatClient>() is not null)
 51        {
 252            _middleware = null;
 53        }
 54        else
 55        {
 16856            _middleware = middleware;
 57        }
 16858    }
 59
 60    /// <inheritdoc />
 61    public override Task<ChatResponse> GetResponseAsync(
 62        IEnumerable<ChatMessage> messages,
 63        ChatOptions? options,
 64        CancellationToken cancellationToken)
 65    {
 27566        if (_middleware is null)
 67        {
 268            return base.GetResponseAsync(messages, options, cancellationToken);
 69        }
 70
 27371        return _middleware.HandleAsync(messages, options, InnerClient, cancellationToken);
 72    }
 73
 74    /// <inheritdoc />
 75    public override IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
 76        IEnumerable<ChatMessage> messages,
 77        ChatOptions? options,
 78        CancellationToken cancellationToken)
 79    {
 1980        if (_middleware is null)
 81        {
 082            return base.GetStreamingResponseAsync(messages, options, cancellationToken);
 83        }
 84
 1985        return _middleware.HandleStreamingAsync(messages, options, InnerClient, cancellationToken);
 86    }
 87}