< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Workflows.Middleware.NeedlrAgentMiddlewareExtensions
Assembly: NexusLabs.Needlr.AgentFramework.Workflows
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Workflows/Middleware/NeedlrAgentMiddlewareExtensions.cs
Line coverage
63%
Covered lines: 44
Uncovered lines: 25
Coverable lines: 69
Total lines: 178
Line coverage: 63.7%
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
UsingToolResultMiddleware(...)100%22100%
UsingResilience(...)100%11100%
UsingResilience(...)100%22100%
UseToolResultMiddleware(...)100%210%
UseResilience(...)100%210%
UseResilience(...)100%210%
BuildPerAgentPlugin(...)100%44100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Workflows/Middleware/NeedlrAgentMiddlewareExtensions.cs

#LineLine coverage
 1using Microsoft.Agents.AI;
 2
 3using NexusLabs.Needlr.AgentFramework;
 4
 5using Polly;
 6using Polly.Retry;
 7using Polly.Timeout;
 8
 9namespace NexusLabs.Needlr.AgentFramework.Workflows.Middleware;
 10
 11/// <summary>
 12/// Extension methods that add Needlr middleware to the <see cref="AgentFrameworkSyringe"/>
 13/// and to MAF's <see cref="AIAgentBuilder"/> directly.
 14/// </summary>
 15public static class NeedlrAgentMiddlewareExtensions
 16{
 17    // ─── AgentFrameworkSyringe extensions ────────────────────────────────────
 18
 19    /// <summary>
 20    /// Adds <see cref="ToolResultFunctionMiddleware"/> to every agent created by the factory.
 21    /// </summary>
 22    /// <remarks>
 23    /// This ensures that all <c>[AgentFunction]</c> methods return a safe, structured JSON
 24    /// payload to the LLM — even when they throw an unhandled exception.
 25    /// </remarks>
 26    public static AgentFrameworkSyringe UsingToolResultMiddleware(
 27        this AgentFrameworkSyringe syringe)
 28    {
 729        ArgumentNullException.ThrowIfNull(syringe);
 30
 631        return syringe with
 632        {
 633            Plugins = (syringe.Plugins ?? []).Append(new ToolResultFunctionMiddleware()).ToList()
 634        };
 35    }
 36
 37    /// <summary>
 38    /// Wraps every agent created by the factory with a default resilience pipeline:
 39    /// 2 retries with exponential back-off and a 120-second timeout.
 40    /// </summary>
 41    /// <remarks>
 42    /// <para>
 43    /// Per-agent settings can be overridden by applying <see cref="AgentResilienceAttribute"/>
 44    /// to the agent class.
 45    /// </para>
 46    /// <para>
 47    /// For a custom pipeline use the <see cref="UsingResilience(AgentFrameworkSyringe, Action{ResiliencePipelineBuilder
 48    /// overload.
 49    /// </para>
 50    /// </remarks>
 51    public static AgentFrameworkSyringe UsingResilience(
 52        this AgentFrameworkSyringe syringe)
 53    {
 1054        ArgumentNullException.ThrowIfNull(syringe);
 55
 956        return syringe.UsingResilience(builder =>
 957        {
 958            builder
 959                .AddRetry(new RetryStrategyOptions<AgentResponse>
 960                {
 961                    MaxRetryAttempts = 2,
 962                    BackoffType = DelayBackoffType.Exponential,
 963                    UseJitter = true,
 964                    ShouldHandle = new PredicateBuilder<AgentResponse>()
 965                        .Handle<HttpRequestException>()
 966                        .Handle<TimeoutRejectedException>()
 967                })
 968                .AddTimeout(TimeSpan.FromSeconds(120));
 1869        });
 70    }
 71
 72    /// <summary>
 73    /// Wraps every agent created by the factory with a custom resilience pipeline.
 74    /// </summary>
 75    /// <param name="syringe">The syringe to configure.</param>
 76    /// <param name="configure">Callback to configure the <see cref="ResiliencePipelineBuilder{TResult}"/>.</param>
 77    public static AgentFrameworkSyringe UsingResilience(
 78        this AgentFrameworkSyringe syringe,
 79        Action<ResiliencePipelineBuilder<AgentResponse>> configure)
 80    {
 1181        ArgumentNullException.ThrowIfNull(syringe);
 1082        ArgumentNullException.ThrowIfNull(configure);
 83
 984        var builder = new ResiliencePipelineBuilder<AgentResponse>();
 985        configure(builder);
 986        var globalPipeline = builder.Build();
 87
 988        return syringe with
 989        {
 990            Plugins = (syringe.Plugins ?? []).Append(new AgentResiliencePlugin(globalPipeline)).ToList(),
 191            PerAgentResilienceFactory = attr => BuildPerAgentPlugin(attr)
 992        };
 93    }
 94
 95    // ─── AIAgentBuilder extensions (any-agent, not just Needlr-managed) ──────
 96
 97    /// <summary>
 98    /// Adds <see cref="ToolResultFunctionMiddleware"/> to an <see cref="AIAgentBuilder"/>
 99    /// directly, for agents not managed by Needlr.
 100    /// </summary>
 101    public static AIAgentBuilder UseToolResultMiddleware(this AIAgentBuilder builder)
 102    {
 0103        ArgumentNullException.ThrowIfNull(builder);
 104
 0105        var plugin = new ToolResultFunctionMiddleware();
 0106        plugin.Configure(new AIAgentBuilderPluginOptions { AgentBuilder = builder });
 0107        return builder;
 108    }
 109
 110    /// <summary>
 111    /// Adds a default resilience pipeline to an <see cref="AIAgentBuilder"/>
 112    /// directly, for agents not managed by Needlr.
 113    /// </summary>
 114    public static AIAgentBuilder UseResilience(this AIAgentBuilder builder)
 115    {
 0116        ArgumentNullException.ThrowIfNull(builder);
 117
 0118        var pipeline = new ResiliencePipelineBuilder<AgentResponse>()
 0119            .AddRetry(new RetryStrategyOptions<AgentResponse>
 0120            {
 0121                MaxRetryAttempts = 2,
 0122                BackoffType = DelayBackoffType.Exponential,
 0123                UseJitter = true,
 0124                ShouldHandle = new PredicateBuilder<AgentResponse>()
 0125                    .Handle<HttpRequestException>()
 0126                    .Handle<TimeoutRejectedException>()
 0127            })
 0128            .AddTimeout(TimeSpan.FromSeconds(120))
 0129            .Build();
 130
 0131        var plugin = new AgentResiliencePlugin(pipeline);
 0132        plugin.Configure(new AIAgentBuilderPluginOptions { AgentBuilder = builder });
 0133        return builder;
 134    }
 135
 136    /// <summary>
 137    /// Adds a custom resilience pipeline to an <see cref="AIAgentBuilder"/>
 138    /// directly, for agents not managed by Needlr.
 139    /// </summary>
 140    public static AIAgentBuilder UseResilience(
 141        this AIAgentBuilder builder,
 142        ResiliencePipeline<AgentResponse> pipeline)
 143    {
 0144        ArgumentNullException.ThrowIfNull(builder);
 0145        ArgumentNullException.ThrowIfNull(pipeline);
 146
 0147        var plugin = new AgentResiliencePlugin(pipeline);
 0148        plugin.Configure(new AIAgentBuilderPluginOptions { AgentBuilder = builder });
 0149        return builder;
 150    }
 151
 152    // ─── Internal helpers ────────────────────────────────────────────────────
 153
 154    private static AgentResiliencePlugin BuildPerAgentPlugin(AgentResilienceAttribute attr)
 155    {
 1156        var pipelineBuilder = new ResiliencePipelineBuilder<AgentResponse>();
 157
 1158        if (attr.MaxRetries > 0)
 159        {
 1160            pipelineBuilder.AddRetry(new RetryStrategyOptions<AgentResponse>
 1161            {
 1162                MaxRetryAttempts = attr.MaxRetries,
 1163                BackoffType = DelayBackoffType.Exponential,
 1164                UseJitter = true,
 1165                ShouldHandle = new PredicateBuilder<AgentResponse>()
 1166                    .Handle<HttpRequestException>()
 1167                    .Handle<TimeoutRejectedException>()
 1168            });
 169        }
 170
 1171        if (attr.TimeoutSeconds > 0)
 172        {
 1173            pipelineBuilder.AddTimeout(TimeSpan.FromSeconds(attr.TimeoutSeconds));
 174        }
 175
 1176        return new AgentResiliencePlugin(pipelineBuilder.Build());
 177    }
 178}