Skip to content

Conditional Expression DSL

The conditional attributes ([ConditionalFunction], [ConditionalSkill], [ConditionalSubAgent], [ConditionalParameter]) use a simple expression DSL to control visibility at runtime.

Quick Reference

csharp
// Simple property (boolean)
[ConditionalFunction("HasAdvancedFeatures")]

// Comparison
[ConditionalFunction("ProviderCount > 1")]

// Boolean logic
[ConditionalFunction("HasTavilyProvider || HasBraveProvider")]

// Complex expression
[ConditionalFunction("(HasSearch || HasBrowse) && IsEnabled")]

Supported Syntax

Property Access

The simplest form—checks if a boolean property is true:

csharp
[ConditionalFunction("EnableSearch")]
[ConditionalSkill("HasAdvancedFeatures")]
[ConditionalSubAgent("AllowDelegation")]

Evaluates to:

csharp
return metadata.EnableSearch;

Comparison Operators

OperatorExampleDescription
>Count > 5Greater than
>=Count >= 5Greater than or equal
<Size < 1000Less than
<=Size <= 1000Less than or equal
==Mode == 'advanced'Equality
!=Status != 'disabled'Inequality
csharp
[ConditionalFunction("MaxFileSize > 1000000")]
[ConditionalFunction("ProviderCount >= 2")]
[ConditionalFunction("Environment == 'production'")]
[ConditionalFunction("Status != 'disabled'")]

Boolean Operators

OperatorExampleDescription
&&A && BAND (both must be true)
||A || BOR (at least one true)
!!IsDisabledNOT (negation)
csharp
// AND - both conditions required
[ConditionalFunction("EnableSearch && AllowAdvanced")]

// OR - any condition sufficient
[ConditionalFunction("HasTavilyProvider || HasBraveProvider || HasBingProvider")]

// NOT - negation
[ConditionalFunction("!IsDisabled")]

Parentheses for Grouping

Control operator precedence:

csharp
[ConditionalFunction("(HasTavilyProvider || HasBraveProvider) && IsEnabled")]
[ConditionalFunction("(ProviderCount > 1) && (MaxResults >= 10)")]

String Literals

Use single quotes for string comparisons:

csharp
[ConditionalFunction("Environment == 'production'")]
[ConditionalFunction("Provider != 'none'")]
[ConditionalFunction("Mode == 'advanced'")]

Numeric Literals

Numbers work directly:

csharp
[ConditionalFunction("MaxValue > 1000")]
[ConditionalFunction("Threshold <= 100")]
[ConditionalFunction("Count == 0")]

Property Naming Convention

Properties MUST use PascalCase (start with uppercase letter).

Correct ✓

csharp
public class MyMetadata : IToolMetadata
{
    public bool EnableSearch { get; set; }      // ✓ PascalCase
    public int MaxFileSize { get; set; }        // ✓ PascalCase
    public string ProviderName { get; set; }    // ✓ PascalCase
}

[ConditionalFunction("EnableSearch")]           // ✓ Works
[ConditionalFunction("MaxFileSize > 1000")]     // ✓ Works

Incorrect ✗

csharp
public class MyMetadata : IToolMetadata
{
    public bool enableSearch { get; set; }      // ✗ camelCase
}

[ConditionalFunction("enableSearch")]           // ✗ Won't work

Why? The DSL automatically transforms PascalCase identifiers to reference the metadata object. camelCase identifiers are ignored to prevent accidental variable name collisions.


All Four Conditional Attributes

ConditionalFunction

Controls visibility of AIFunctions:

csharp
[AIFunction<SearchMetadata>]
[ConditionalFunction("HasAdvancedSearch")]
[AIDescription("Advanced search with filters")]
public async Task<string> AdvancedSearch(string query, SearchFilters filters)
{
    // Only visible when metadata.HasAdvancedSearch is true
}

ConditionalSkill

Controls visibility of Skills:

csharp
[Skill<SearchMetadata>]
[ConditionalSkill("HasMultipleProviders")]
public Skill MultiProviderSearch()
{
    // Only visible when metadata.HasMultipleProviders is true
    return SkillFactory.Create(...);
}

ConditionalSubAgent

Controls visibility of SubAgents:

csharp
[SubAgent<DelegationMetadata>]
[ConditionalSubAgent("HasSpecializedAgents")]
public SubAgent SpecializedAnalyzer()
{
    // Only visible when metadata.HasSpecializedAgents is true
    return SubAgentFactory.Create(...);
}

ConditionalParameter

Controls visibility of individual parameters:

csharp
[AIFunction<SearchMetadata>]
public async Task<string> Search(
    string query,

    [ConditionalParameter("HasFilters")]
    [AIDescription("Optional search filters")]
    SearchFilters? filters = null,

    [ConditionalParameter("ProviderCount > 1")]
    [AIDescription("Which provider to use")]
    string? provider = null)
{
    // 'filters' only in schema when HasFilters is true
    // 'provider' only in schema when ProviderCount > 1
}

Real-World Examples

Multi-Provider Search Tool

csharp
public class WebSearchContext : IToolMetadata
{
    public bool HasTavilyProvider { get; set; }
    public bool HasBraveProvider { get; set; }
    public bool HasBingProvider { get; set; }
    public int ProviderCount { get; set; }
}

public class WebSearchTool
{
    // Available if ANY provider exists
    [AIFunction<WebSearchContext>]
    [ConditionalFunction("HasTavilyProvider || HasBraveProvider || HasBingProvider")]
    public async Task<string> WebSearch(string query) { }

    // Only for Tavily (has answer extraction)
    [AIFunction<WebSearchContext>]
    [ConditionalFunction("HasTavilyProvider")]
    public async Task<string> AnswerSearch(string query) { }

    // Only for Brave or Bing (have video search)
    [AIFunction<WebSearchContext>]
    [ConditionalFunction("HasBraveProvider || HasBingProvider")]
    public async Task<string> VideoSearch(string query) { }

    // Provider selection only when multiple available
    [AIFunction<WebSearchContext>]
    public async Task<string> Search(
        string query,
        [ConditionalParameter("ProviderCount > 1")]
        string? provider = null) { }
}

Feature-Gated Math Tool

csharp
public class MathContext : IToolMetadata
{
    public bool AllowNegative { get; set; }
    public long MaxValue { get; set; }
}

public class MathTool
{
    // Only when large values allowed
    [AIFunction<MathContext>]
    [ConditionalFunction("MaxValue > 1000")]
    public long Square(long value) { }

    // Only when negatives NOT allowed
    [AIFunction<MathContext>]
    [ConditionalFunction("AllowNegative == false")]
    public long Abs(long value) { }

    // Only when negatives ARE allowed
    [AIFunction<MathContext>]
    [ConditionalFunction("AllowNegative == true")]
    public long Subtract(long a, long b) { }
}

Environment-Based Tool

csharp
public class AppContext : IToolMetadata
{
    public string Environment { get; set; } = "development";
    public bool IsAdmin { get; set; }
}

public class AdminTool
{
    // Only in non-production
    [AIFunction<AppContext>]
    [ConditionalFunction("Environment != 'production'")]
    public void ResetDatabase() { }

    // Only for admins in production
    [AIFunction<AppContext>]
    [ConditionalFunction("Environment == 'production' && IsAdmin")]
    public void ViewAuditLogs() { }
}

How It Works

Compile-Time

  1. Source generator extracts the expression string from the attribute
  2. PascalCase identifiers are transformed to reference the metadata object
  3. A condition evaluation method is generated

Example: "HasAdvancedSearch" becomes metadata.HasAdvancedSearch

Runtime

  1. When functions are registered, the condition is evaluated against the metadata
  2. If true, the function is included
  3. If false, the function is hidden from the agent

Null Handling

ScenarioResultReason
No metadata providedtrue (show)No metadata = show everything
Wrong metadata typefalse (hide)Type mismatch = hide
Condition evaluatesResult of expressionNormal evaluation

Security

The DSL has compile-time restrictions to prevent code injection:

Allowed ✓

  • Property access: EnableSearch
  • Comparisons: Count > 5
  • Boolean logic: A && B, C || D
  • Literals: 'string', 123
  • Grouping: (A || B) && C

Blocked ✗

  • Method calls: GetType(), ToString()
  • Reflection: typeof(), nameof()
  • System namespaces: System.IO.*
  • Dynamic code: Assembly.Load()

These restrictions are enforced at compile-time by the source generator.


Common Patterns

Feature Flags

csharp
[ConditionalFunction("FeatureFlags.NewSearch")]
[ConditionalFunction("FeatureFlags.BetaFeatures && IsInternal")]

Subscription Tiers

csharp
[ConditionalFunction("Tier == 'premium' || Tier == 'enterprise'")]
[ConditionalFunction("RequestsRemaining > 0")]

Environment Gates

csharp
[ConditionalFunction("Environment != 'production'")]  // Dev only
[ConditionalFunction("Environment == 'production' && IsAdmin")]  // Prod admin

Capability Detection

csharp
[ConditionalFunction("HasGPU && ModelLoaded")]
[ConditionalFunction("DatabaseConnected && HasWriteAccess")]

Troubleshooting

IssueCauseFix
Function always hiddenProperty is false or metadata not setCheck metadata registration
Function always shownNo metadata passedEnsure .WithTool<T>(metadata) is called
Compile errorInvalid expression syntaxCheck for typos, use PascalCase
Wrong type errorMetadata class mismatchUse generic attribute [AIFunction<TMetadata>]

Debug: Check Generated Code

Look in obj/Debug/net8.0/generated/HPD.Agent.SourceGenerator/ for:

  • {ToolName}Registry.g.cs - Contains generated condition methods

Summary

FeatureSyntaxExample
Boolean propertyPropertyNameHasFeature
ComparisonProperty op ValueCount > 5
EqualityProperty == 'value'Mode == 'advanced'
ANDA && BHasA && HasB
ORA || BHasA || HasB
NOT!Property!IsDisabled
Grouping(expr)(A || B) && C

The conditional DSL provides a safe, type-checked way to dynamically control capability visibility based on runtime context.

Released under the MIT License.