< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Tools.ToolResult<T1, T2>
Assembly: NexusLabs.Needlr.AgentFramework
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/Tools/ToolResult.cs
Line coverage
100%
Covered lines: 15
Uncovered lines: 0
Coverable lines: 15
Total lines: 168
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Ok(...)100%11100%
Fail(...)100%11100%
get_IsSuccess()100%11100%
get_Value()100%11100%
get_Error()100%11100%
get_Exception()100%11100%
get_IsTransient()100%11100%
NexusLabs.Needlr.AgentFramework.Tools.IToolResult.get_BoxedValue()100%11100%
NexusLabs.Needlr.AgentFramework.Tools.IToolResult.get_BoxedError()100%11100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/Tools/ToolResult.cs

#LineLine coverage
 1namespace NexusLabs.Needlr.AgentFramework.Tools;
 2
 3/// <summary>
 4/// Non-generic marker interface for inspecting any <see cref="ToolResult{TValue, TError}"/>
 5/// without knowing its type arguments at compile time.
 6/// </summary>
 7public interface IToolResult
 8{
 9    /// <summary>Gets a value indicating whether the tool call succeeded.</summary>
 10    bool IsSuccess { get; }
 11
 12    /// <summary>
 13    /// Gets the success value as <see langword="object"/> — sent to the LLM on success.
 14    /// <see langword="null"/> on failure.
 15    /// </summary>
 16    object? BoxedValue { get; }
 17
 18    /// <summary>
 19    /// Gets the error value as <see langword="object"/> — sent to the LLM as <c>{ "error": … }</c> on failure.
 20    /// <see langword="null"/> on success.
 21    /// </summary>
 22    object? BoxedError { get; }
 23
 24    /// <summary>
 25    /// Gets the original unhandled <see cref="Exception"/>, if any.
 26    /// <strong>Never sent to the LLM.</strong> Preserved for diagnostics and resilience decisions.
 27    /// </summary>
 28    Exception? Exception { get; }
 29
 30    /// <summary>
 31    /// Indicates whether the failure is transient and suitable for retry.
 32    /// <see langword="true"/> = retry, <see langword="false"/> = don't retry,
 33    /// <see langword="null"/> = let the resilience layer decide via its own heuristics.
 34    /// </summary>
 35    bool? IsTransient { get; }
 36}
 37
 38/// <summary>
 39/// Standard return type for <c>[AgentFunction]</c> methods providing two separate channels:
 40/// one for the LLM (structured JSON, never a raw stack trace) and one for C#
 41/// (full exception context and retry signal).
 42/// </summary>
 43/// <typeparam name="TValue">The success value type. Serialised to JSON for the LLM on success.</typeparam>
 44/// <typeparam name="TError">
 45/// The structured error shape. Serialised as <c>{ "error": … }</c> for the LLM on failure.
 46/// Must be a reference type so <see langword="null"/> can represent the absence of an error.
 47/// </typeparam>
 48public readonly struct ToolResult<TValue, TError> : IToolResult
 49    where TError : class
 50{
 51    private readonly bool _isSuccess;
 52    private readonly TValue? _value;
 53    private readonly TError? _error;
 54    private readonly Exception? _exception;
 55    private readonly bool? _isTransient;
 56
 57    private ToolResult(
 58        bool isSuccess,
 59        TValue? value,
 60        TError? error,
 61        Exception? exception,
 62        bool? isTransient)
 63    {
 1164        _isSuccess = isSuccess;
 1165        _value = value;
 1166        _error = error;
 1167        _exception = exception;
 1168        _isTransient = isTransient;
 1169    }
 70
 71    /// <summary>Creates a successful result wrapping <paramref name="value"/>.</summary>
 72    public static ToolResult<TValue, TError> Ok(TValue value)
 373        => new(true, value, null, null, null);
 74
 75    /// <summary>
 76    /// Creates a failure result with a structured <paramref name="error"/> payload.
 77    /// </summary>
 78    /// <param name="error">Structured error sent to the LLM.</param>
 79    /// <param name="exception">Original exception, preserved for diagnostics (never sent to LLM).</param>
 80    /// <param name="isTransient">
 81    /// Whether the failure is transient. <see langword="null"/> lets the resilience layer decide.
 82    /// </param>
 83    public static ToolResult<TValue, TError> Fail(
 84        TError error,
 85        Exception? exception = null,
 86        bool? isTransient = null)
 887        => new(false, default, error, exception, isTransient);
 88
 89    /// <inheritdoc />
 890    public bool IsSuccess => _isSuccess;
 91
 92    /// <summary>Gets the success value. <see langword="default"/> on failure.</summary>
 393    public TValue? Value => _value;
 94
 95    /// <summary>Gets the structured error payload. <see langword="null"/> on success.</summary>
 796    public TError? Error => _error;
 97
 98    /// <inheritdoc />
 499    public Exception? Exception => _exception;
 100
 101    /// <inheritdoc />
 4102    public bool? IsTransient => _isTransient;
 103
 2104    object? IToolResult.BoxedValue => _value;
 2105    object? IToolResult.BoxedError => _error;
 106}
 107
 108/// <summary>
 109/// Opinionated default error shape — sufficient for most tool functions.
 110/// </summary>
 111/// <param name="Message">Human-readable error description sent to the LLM.</param>
 112/// <param name="Suggestion">Optional hint for the LLM on how to recover or try differently.</param>
 113public sealed record ToolError(string Message, string? Suggestion = null);
 114
 115/// <summary>
 116/// Static factory providing shorthand constructors for <see cref="ToolResult{TValue, TError}"/>
 117/// using the default <see cref="ToolError"/> shape.
 118/// </summary>
 119public static class ToolResult
 120{
 121    /// <summary>Creates a successful <see cref="ToolResult{TValue, ToolError}"/>.</summary>
 122    public static ToolResult<TValue, ToolError> Ok<TValue>(TValue value)
 123        => ToolResult<TValue, ToolError>.Ok(value);
 124
 125    /// <summary>
 126    /// Creates a failure <see cref="ToolResult{TValue, ToolError}"/> from a message string.
 127    /// </summary>
 128    public static ToolResult<TValue, ToolError> Fail<TValue>(
 129        string message,
 130        Exception? ex = null,
 131        bool? isTransient = null,
 132        string? suggestion = null)
 133        => ToolResult<TValue, ToolError>.Fail(
 134            new ToolError(message, suggestion),
 135            ex,
 136            isTransient);
 137
 138    /// <summary>
 139    /// Creates a failure <see cref="ToolResult{TValue, TError}"/> with a custom error shape.
 140    /// </summary>
 141    public static ToolResult<TValue, TError> Fail<TValue, TError>(
 142        TError error,
 143        Exception? ex = null,
 144        bool? isTransient = null)
 145        where TError : class
 146        => ToolResult<TValue, TError>.Fail(error, ex, isTransient);
 147
 148    /// <summary>
 149    /// Creates an <see cref="IToolResult"/> for an unhandled exception caught by middleware.
 150    /// <see cref="IToolResult.IsTransient"/> is <see langword="null"/> because middleware
 151    /// cannot determine whether the failure is transient without domain knowledge.
 152    /// </summary>
 153    public static IToolResult UnhandledFailure(Exception ex)
 154        => new UnhandledToolResult(ex);
 155
 156    private sealed class UnhandledToolResult : IToolResult
 157    {
 158        private readonly Exception _ex;
 159
 160        public UnhandledToolResult(Exception ex) => _ex = ex;
 161
 162        public bool IsSuccess => false;
 163        public object? BoxedValue => null;
 164        public object? BoxedError => new ToolError("An unexpected error occurred. Please try again.");
 165        public Exception? Exception => _ex;
 166        public bool? IsTransient => null;
 167    }
 168}