< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Workflows.ToolCallTerminationCondition
Assembly: NexusLabs.Needlr.AgentFramework.Workflows
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Workflows/ToolCallTerminationCondition.cs
Line coverage
100%
Covered lines: 17
Uncovered lines: 0
Coverable lines: 17
Total lines: 86
Line coverage: 100%
Branch coverage
100%
Covered branches: 10
Total branches: 10
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%
.ctor(...)100%11100%
ShouldTerminate(...)100%1010100%

File(s)

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

#LineLine coverage
 1using NexusLabs.Needlr.AgentFramework;
 2
 3namespace NexusLabs.Needlr.AgentFramework.Workflows;
 4
 5/// <summary>
 6/// Terminates a workflow when an agent calls a specific tool/function during its turn.
 7/// Unlike <see cref="KeywordTerminationCondition"/> which matches response text,
 8/// this condition matches on structured tool call data — eliminating false positives
 9/// from keywords appearing in natural language.
 10/// </summary>
 11/// <remarks>
 12/// <para>
 13/// This condition inspects <see cref="TerminationContext.ToolCallNames"/> which is
 14/// populated from <c>FunctionCallContent</c> entries in the agent's response message.
 15/// The match is exact and case-sensitive on the tool name.
 16/// </para>
 17/// <para>
 18/// Designed for scenarios where an agent should signal approval or completion via a
 19/// dedicated tool call rather than embedding a keyword in free text. For example, a
 20/// reviewer agent calls <c>ApproveArticle()</c> instead of writing "APPROVED" — which
 21/// avoids the problem of the LLM accidentally including the keyword in rejection text.
 22/// </para>
 23/// </remarks>
 24/// <example>
 25/// <code>
 26/// // Declare on the reviewer agent class:
 27/// [AgentGroupChatMember("article-writing")]
 28/// [AgentTerminationCondition(typeof(ToolCallTerminationCondition), "ApproveArticle", "ReviewerAgent")]
 29/// public sealed class ReviewerAgent;
 30///
 31/// // The reviewer's instructions tell it to call ApproveArticle() when satisfied:
 32/// // "If no Critical or Major issues remain, call the ApproveArticle tool."
 33/// </code>
 34/// </example>
 35public sealed class ToolCallTerminationCondition : IWorkflowTerminationCondition
 36{
 37    private readonly string _toolName;
 38    private readonly string? _agentId;
 39
 40    /// <summary>
 41    /// Initializes a new instance that fires when <em>any</em> agent calls the
 42    /// specified tool.
 43    /// </summary>
 44    /// <param name="toolName">The exact tool/function name to match (case-sensitive).</param>
 45    public ToolCallTerminationCondition(string toolName)
 946        : this(toolName, agentId: null)
 47    {
 748    }
 49
 50    /// <summary>
 51    /// Initializes a new instance that fires when a specific agent calls the
 52    /// specified tool.
 53    /// </summary>
 54    /// <param name="toolName">The exact tool/function name to match (case-sensitive).</param>
 55    /// <param name="agentId">
 56    /// The agent name or executor ID to restrict the match to, or <see langword="null"/>
 57    /// to match any agent. Supports MAF's GUID-suffixed executor IDs via prefix matching.
 58    /// </param>
 1259    public ToolCallTerminationCondition(string toolName, string? agentId)
 60    {
 1261        ArgumentException.ThrowIfNullOrWhiteSpace(toolName);
 1062        _toolName = toolName;
 1063        _agentId = agentId;
 1064    }
 65
 66    /// <inheritdoc/>
 67    public bool ShouldTerminate(TerminationContext context)
 68    {
 1069        ArgumentNullException.ThrowIfNull(context);
 70
 971        if (_agentId is not null
 972            && !string.Equals(context.AgentId, _agentId, StringComparison.Ordinal)
 973            && !context.AgentId.StartsWith(_agentId + "_", StringComparison.Ordinal))
 74        {
 175            return false;
 76        }
 77
 2678        foreach (var toolName in context.ToolCallNames)
 79        {
 780            if (string.Equals(toolName, _toolName, StringComparison.Ordinal))
 481                return true;
 82        }
 83
 484        return false;
 485    }
 86}