< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Workflows.Budget.TokenUsageRecordingMiddleware
Assembly: NexusLabs.Needlr.AgentFramework.Workflows
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Workflows/Budget/TokenUsageRecordingMiddleware.cs
Line coverage
100%
Covered lines: 16
Uncovered lines: 0
Coverable lines: 16
Total lines: 59
Line coverage: 100%
Branch coverage
100%
Covered branches: 8
Total branches: 8
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%11100%
GetResponseAsync()100%88100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Workflows/Budget/TokenUsageRecordingMiddleware.cs

#LineLine coverage
 1using Microsoft.Extensions.AI;
 2
 3using NexusLabs.Needlr.AgentFramework.Budget;
 4
 5namespace NexusLabs.Needlr.AgentFramework.Workflows.Budget;
 6
 7/// <summary>
 8/// Lightweight <see cref="DelegatingChatClient"/> that records token usage from
 9/// each LLM call into <see cref="ITokenBudgetTracker"/>. Does NOT enforce budgets
 10/// — that is the responsibility of <see cref="TokenBudgetChatMiddleware"/>.
 11/// </summary>
 12/// <remarks>
 13/// Wired automatically by <c>UsingTokenTracking()</c>, <c>UsingTokenBudget()</c>,
 14/// and <c>UsingDiagnostics()</c>. Idempotent — only one instance is wired
 15/// regardless of how many extensions request it.
 16/// </remarks>
 17public sealed class TokenUsageRecordingMiddleware : DelegatingChatClient
 18{
 19    private readonly ITokenBudgetTracker _tracker;
 20
 21    /// <param name="innerClient">The inner chat client to delegate to.</param>
 22    /// <param name="tracker">The token budget tracker to record usage into.</param>
 23    public TokenUsageRecordingMiddleware(
 24        IChatClient innerClient,
 25        ITokenBudgetTracker tracker)
 5326        : base(innerClient)
 27    {
 5328        ArgumentNullException.ThrowIfNull(tracker);
 5329        _tracker = tracker;
 5330    }
 31
 32    /// <inheritdoc />
 33    public override async Task<ChatResponse> GetResponseAsync(
 34        IEnumerable<ChatMessage> messages,
 35        ChatOptions? options = null,
 36        CancellationToken cancellationToken = default)
 37    {
 4238        var response = await base.GetResponseAsync(messages, options, cancellationToken)
 4239            .ConfigureAwait(false);
 40
 4141        var usage = response.Usage;
 4142        if (usage is not null)
 43        {
 3444            var inputCount = usage.InputTokenCount ?? 0;
 3445            var outputCount = usage.OutputTokenCount ?? 0;
 46
 3447            if (inputCount > 0 || outputCount > 0)
 48            {
 3049                _tracker.Record(inputCount, outputCount);
 50            }
 451            else if (usage.TotalTokenCount is long totalOnly)
 52            {
 453                _tracker.Record(totalOnly);
 54            }
 55        }
 56
 4157        return response;
 4158    }
 59}