< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Generators.AIFunctionProviderCodeGenerator
Assembly: NexusLabs.Needlr.AgentFramework.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Generators/CodeGen/AIFunctionProviderCodeGenerator.cs
Line coverage
94%
Covered lines: 297
Uncovered lines: 18
Coverable lines: 315
Total lines: 604
Line coverage: 94.2%
Branch coverage
82%
Covered branches: 142
Total branches: 172
Branch coverage: 82.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Generators/CodeGen/AIFunctionProviderCodeGenerator.cs

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System.Collections.Generic;
 5using System.Collections.Immutable;
 6using System.Linq;
 7using System.Text;
 8
 9namespace NexusLabs.Needlr.AgentFramework.Generators;
 10
 11internal static class AIFunctionProviderCodeGenerator
 12{
 13    public static string GenerateAIFunctionProviderSource(
 14        List<AgentFunctionTypeInfo> types,
 15        string safeAssemblyName)
 16    {
 13817        var sb = new StringBuilder();
 13818        sb.AppendLine("// <auto-generated/>");
 13819        sb.AppendLine("#nullable enable");
 13820        sb.AppendLine();
 13821        sb.AppendLine("using global::Microsoft.Extensions.DependencyInjection;");
 13822        sb.AppendLine();
 13823        sb.AppendLine($"namespace {safeAssemblyName}.Generated;");
 13824        sb.AppendLine();
 13825        sb.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.AgentFramework.Generat
 13826        sb.AppendLine("internal sealed class GeneratedAIFunctionProvider : global::NexusLabs.Needlr.AgentFramework.IAIFu
 13827        sb.AppendLine("{");
 13828        sb.AppendLine("    public bool TryGetFunctions(");
 13829        sb.AppendLine("        global::System.Type functionType,");
 13830        sb.AppendLine("        global::System.IServiceProvider serviceProvider,");
 13831        sb.AppendLine("        [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)]");
 13832        sb.AppendLine("        out global::System.Collections.Generic.IReadOnlyList<global::Microsoft.Extensions.AI.AIFu
 13833        sb.AppendLine("    {");
 34
 13835        if (types.Count == 0)
 36        {
 8137            sb.AppendLine("        functions = null;");
 8138            sb.AppendLine("        return false;");
 39        }
 40        else
 41        {
 5742            var first = true;
 22843            foreach (var type in types)
 44            {
 5745                var keyword = first ? "if" : "else if";
 5746                first = false;
 5747                sb.AppendLine($"        {keyword} (functionType == typeof({type.TypeName}))");
 5748                sb.AppendLine("        {");
 5749                if (!type.IsStatic)
 5550                    sb.AppendLine($"            var typed = serviceProvider.GetRequiredService<{type.TypeName}>();");
 5751                sb.AppendLine("            functions = new global::System.Collections.Generic.List<global::Microsoft.Ext
 5752                sb.AppendLine("            {");
 22853                foreach (var m in type.Methods)
 54                {
 5755                    var nestedName = $"{AgentDiscoveryHelper.GetShortName(type.TypeName)}_{m.MethodName}";
 5756                    if (type.IsStatic)
 257                        sb.AppendLine($"                new {nestedName}(),");
 58                    else
 5559                        sb.AppendLine($"                new {nestedName}(typed),");
 60                }
 5761                sb.AppendLine("            }.AsReadOnly();");
 5762                sb.AppendLine("            return true;");
 5763                sb.AppendLine("        }");
 64            }
 5765            sb.AppendLine("        functions = null;");
 5766            sb.AppendLine("        return false;");
 67        }
 68
 13869        sb.AppendLine("    }");
 13870        sb.AppendLine();
 71
 39072        foreach (var type in types)
 73        {
 5774            var shortTypeName = AgentDiscoveryHelper.GetShortName(type.TypeName);
 22875            foreach (var m in type.Methods)
 76            {
 5777                var nestedName = $"{shortTypeName}_{m.MethodName}";
 5778                AppendAIFunctionNestedClass(sb, type, m, nestedName);
 79            }
 80        }
 81
 13882        sb.AppendLine("}");
 13883        return sb.ToString();
 84    }
 85
 86    private static void AppendAIFunctionNestedClass(
 87        StringBuilder sb,
 88        AgentFunctionTypeInfo type,
 89        AgentFunctionMethodInfo method,
 90        string nestedClassName)
 91    {
 5792        sb.AppendLine($"    private sealed class {nestedClassName} : global::Microsoft.Extensions.AI.AIFunction");
 5793        sb.AppendLine("    {");
 94
 5795        if (!type.IsStatic)
 5596            sb.AppendLine($"        private readonly {type.TypeName} _instance;");
 97
 5798        var schemaJson = BuildJsonSchema(method.Parameters);
 5799        sb.AppendLine("        private static readonly global::System.Text.Json.JsonElement _schema =");
 57100        sb.AppendLine($"            global::System.Text.Json.JsonDocument.Parse(\"\"\"{schemaJson}\"\"\").RootElement.Cl
 101
 57102        if (!string.IsNullOrEmpty(method.ReturnJsonSchemaType))
 103        {
 53104            var returnSchemaJson = !string.IsNullOrEmpty(method.ReturnObjectSchemaJson)
 53105                ? method.ReturnObjectSchemaJson
 53106                : $"{{\"type\":\"{method.ReturnJsonSchemaType}\"}}";
 53107            sb.AppendLine("        private static readonly global::System.Text.Json.JsonElement _returnSchema =");
 53108            sb.AppendLine($"            global::System.Text.Json.JsonDocument.Parse(\"\"\"{returnSchemaJson}\"\"\").Root
 109        }
 110
 57111        sb.AppendLine();
 112
 57113        if (!type.IsStatic)
 55114            sb.AppendLine($"        public {nestedClassName}({type.TypeName} instance) {{ _instance = instance; }}");
 115        else
 2116            sb.AppendLine($"        public {nestedClassName}() {{ }}");
 117
 57118        sb.AppendLine();
 119
 57120        var escapedName = method.MethodName.Replace("\"", "\\\"");
 57121        var escapedDesc = method.Description.Replace("\\", "\\\\").Replace("\"", "\\\"");
 57122        sb.AppendLine($"        public override string Name => \"{escapedName}\";");
 57123        sb.AppendLine($"        public override string Description => \"{escapedDesc}\";");
 57124        sb.AppendLine("        public override global::System.Text.Json.JsonElement JsonSchema => _schema;");
 125
 57126        if (!string.IsNullOrEmpty(method.ReturnJsonSchemaType))
 127        {
 53128            sb.AppendLine("        public override global::System.Text.Json.JsonElement? ReturnJsonSchema => _returnSche
 129        }
 130
 57131        sb.AppendLine();
 132
 57133        if (method.IsAsync)
 3134            sb.AppendLine("        protected override async global::System.Threading.Tasks.ValueTask<object?> InvokeCore
 135        else
 54136            sb.AppendLine("        protected override global::System.Threading.Tasks.ValueTask<object?> InvokeCoreAsync(
 137
 57138        sb.AppendLine("            global::Microsoft.Extensions.AI.AIFunctionArguments arguments,");
 57139        sb.AppendLine("            global::System.Threading.CancellationToken ct)");
 57140        sb.AppendLine("        {");
 141
 224142        foreach (var param in method.Parameters)
 143        {
 55144            if (param.IsCancellationToken)
 145                continue;
 54146            AppendParameterExtraction(sb, type, method, param);
 147        }
 148
 112149        var paramList = string.Join(", ", method.Parameters.Select(p => p.IsCancellationToken ? "ct" : p.Name));
 57150        var instanceExpr = type.IsStatic ? type.TypeName : "_instance";
 151
 57152        if (method.IsAsync)
 153        {
 3154            if (method.IsVoidLike)
 155            {
 1156                sb.AppendLine($"            await {instanceExpr}.{method.MethodName}({paramList}).ConfigureAwait(false);
 1157                sb.AppendLine("            return null;");
 158            }
 159            else
 160            {
 2161                sb.AppendLine($"            var result = await {instanceExpr}.{method.MethodName}({paramList}).Configure
 2162                sb.AppendLine("            return result;");
 163            }
 164        }
 165        else
 166        {
 54167            if (method.IsVoidLike)
 168            {
 3169                sb.AppendLine($"            {instanceExpr}.{method.MethodName}({paramList});");
 3170                sb.AppendLine("            return global::System.Threading.Tasks.ValueTask.FromResult<object?>(null);");
 171            }
 172            else
 173            {
 51174                sb.AppendLine($"            var result = {instanceExpr}.{method.MethodName}({paramList});");
 51175                sb.AppendLine("            return global::System.Threading.Tasks.ValueTask.FromResult<object?>(result);"
 176            }
 177        }
 178
 57179        sb.AppendLine("        }");
 57180        sb.AppendLine("    }");
 57181        sb.AppendLine();
 57182    }
 183
 184    private static string BuildJsonSchema(ImmutableArray<AgentFunctionParameterInfo> parameters)
 185    {
 112186        var nonCtParams = parameters.Where(p => !p.IsCancellationToken).ToList();
 57187        if (nonCtParams.Count == 0)
 6188            return "{\"type\":\"object\",\"properties\":{}}";
 189
 51190        var props = new StringBuilder();
 51191        var required = new List<string>();
 192
 51193        props.Append("{");
 51194        var firstProp = true;
 210195        foreach (var param in nonCtParams)
 196        {
 57197            if (!firstProp) props.Append(",");
 54198            firstProp = false;
 54199            var escapedParamName = param.Name.Replace("\"", "\\\"");
 54200            props.Append($"\"{escapedParamName}\":");
 54201            props.Append(BuildJsonSchemaTypeEntry(param));
 54202            if (param.IsRequired)
 36203                required.Add(param.Name);
 204        }
 51205        props.Append("}");
 206
 51207        var sb = new StringBuilder();
 51208        sb.Append("{\"type\":\"object\",\"properties\":");
 51209        sb.Append(props);
 51210        if (required.Count > 0)
 211        {
 34212            sb.Append(",\"required\":[");
 70213            sb.Append(string.Join(",", required.Select(r => "\"" + r.Replace("\"", "\\\"") + "\"")));
 34214            sb.Append("]");
 215        }
 51216        sb.Append("}");
 51217        return sb.ToString();
 218    }
 219
 220    private static string BuildJsonSchemaTypeEntry(AgentFunctionParameterInfo param)
 221    {
 54222        if (string.IsNullOrEmpty(param.JsonSchemaType))
 0223            return "{}";
 224
 54225        if (param.JsonSchemaType == "array")
 226        {
 8227            var desc = string.IsNullOrEmpty(param.Description)
 8228                ? ""
 8229                : $",\"description\":\"{param.Description!.Replace("\\", "\\\\").Replace("\"", "\\\"")}\"";
 230
 8231            if (param.ItemJsonSchemaType == "object" && !string.IsNullOrEmpty(param.ItemObjectSchemaJson))
 232            {
 233                // Complex object array items — emit the full object schema
 5234                return $"{{\"type\":\"array\",\"items\":{param.ItemObjectSchemaJson}{desc}}}";
 235            }
 236
 3237            if (!string.IsNullOrEmpty(param.ItemJsonSchemaType))
 3238                return $"{{\"type\":\"array\",\"items\":{{\"type\":\"{param.ItemJsonSchemaType}\"}}{desc}}}";
 239
 0240            return $"{{\"type\":\"array\"{desc}}}";
 241        }
 242
 46243        if (param.JsonSchemaType == "object")
 244        {
 245            // Direct complex object parameter (not in an array). Use the pre-built object
 246            // schema (with properties + required fields) when discovery generated one;
 247            // otherwise fall back to the bare {"type":"object"} shape — usually the case
 248            // for empty DTOs or types with no public properties.
 4249            var desc = string.IsNullOrEmpty(param.Description)
 4250                ? ""
 4251                : $",\"description\":\"{param.Description!.Replace("\\", "\\\\").Replace("\"", "\\\"")}\"";
 4252            if (!string.IsNullOrEmpty(param.ObjectSchemaJson))
 253            {
 254                // ObjectSchemaJson already starts with {"type":"object","properties":{…},"required":[…]}.
 255                // Splice the description in before the closing brace.
 4256                var inner = param.ObjectSchemaJson!.Substring(0, param.ObjectSchemaJson.Length - 1);
 4257                return string.IsNullOrEmpty(desc)
 4258                    ? param.ObjectSchemaJson!
 4259                    : $"{inner}{desc}}}";
 260            }
 0261            return $"{{\"type\":\"object\"{desc}}}";
 262        }
 263
 42264        var format = string.IsNullOrEmpty(param.JsonSchemaFormat)
 42265            ? ""
 42266            : $",\"format\":\"{param.JsonSchemaFormat}\"";
 267
 42268        if (!string.IsNullOrEmpty(param.Description))
 269        {
 1270            var escapedDesc = param.Description!.Replace("\\", "\\\\").Replace("\"", "\\\"");
 1271            return $"{{\"type\":\"{param.JsonSchemaType}\"{format},\"description\":\"{escapedDesc}\"}}";
 272        }
 273
 41274        return $"{{\"type\":\"{param.JsonSchemaType}\"{format}}}";
 275    }
 276
 277    private const string ExtractorFqn = "global::NexusLabs.Needlr.AgentFramework.AgentFrameworkArgumentExtractor";
 278
 279    private static void AppendParameterExtraction(
 280        StringBuilder sb,
 281        AgentFunctionTypeInfo type,
 282        AgentFunctionMethodInfo method,
 283        AgentFunctionParameterInfo param)
 284    {
 54285        var rawVar = $"_raw_{param.Name}";
 54286        var jVar = $"_j_{param.Name}";
 54287        sb.AppendLine($"            arguments.TryGetValue(\"{param.Name}\", out var {rawVar});");
 288
 54289        switch (param.JsonSchemaType)
 290        {
 291            case "string":
 20292                if (param.IsEnum)
 2293                    AppendEnumExtraction(sb, type, method, param, rawVar);
 294                else
 18295                    AppendPrimitiveExtraction(sb, type, method, param, rawVar, GetStringFamilyHelper(param));
 18296                break;
 297            case "boolean":
 5298                AppendPrimitiveExtraction(sb, type, method, param, rawVar, "GetBooleanArgument");
 5299                break;
 300            case "integer":
 11301                AppendPrimitiveExtraction(sb, type, method, param, rawVar, GetIntegerHelperForType(param.TypeFullName));
 11302                break;
 303            case "number":
 6304                AppendPrimitiveExtraction(sb, type, method, param, rawVar, GetNumberHelperForType(param.TypeFullName));
 6305                break;
 306            case "array":
 307            {
 8308                var elementType = GetArrayElementType(param.TypeFullName);
 8309                sb.AppendLine($"            {param.TypeFullName} {param.Name};");
 8310                sb.AppendLine($"            if ({rawVar} is global::System.Text.Json.JsonElement {jVar} && {jVar}.ValueK
 8311                sb.AppendLine("            {");
 8312                sb.AppendLine($"                var _list_{param.Name} = new global::System.Collections.Generic.List<{el
 8313                sb.AppendLine($"                foreach (var _elem in {jVar}.EnumerateArray())");
 8314                sb.AppendLine("                {");
 315
 8316                if (param.ItemJsonSchemaType == "object" && param.ItemObjectProperties is { Count: > 0 })
 317                {
 5318                    sb.AppendLine($"                    var _obj = new {elementType}();");
 5319                    AppendObjectPropertyAssignments(
 5320                        sb,
 5321                        targetVar: "_obj",
 5322                        sourceJsonElementVar: "_elem",
 5323                        properties: param.ItemObjectProperties,
 5324                        indent: "                    ");
 5325                    sb.AppendLine($"                    _list_{param.Name}.Add(_obj);");
 326                }
 3327                else if (param.ItemJsonSchemaType == "string")
 2328                    sb.AppendLine($"                    _list_{param.Name}.Add({ExtractorFqn}.GetStringArgument(_elem));
 1329                else if (param.ItemJsonSchemaType == "integer")
 1330                    sb.AppendLine($"                    _list_{param.Name}.Add({ExtractorFqn}.GetInt32Argument(_elem));"
 0331                else if (param.ItemJsonSchemaType == "number")
 0332                    sb.AppendLine($"                    _list_{param.Name}.Add({ExtractorFqn}.GetDoubleArgument(_elem));
 0333                else if (param.ItemJsonSchemaType == "boolean")
 0334                    sb.AppendLine($"                    _list_{param.Name}.Add({ExtractorFqn}.GetBooleanArgument(_elem))
 335                else
 0336                    sb.AppendLine($"                    _list_{param.Name}.Add(default!);");
 337
 8338                sb.AppendLine("                }");
 8339                sb.AppendLine($"                {param.Name} = _list_{param.Name}.ToArray();");
 8340                sb.AppendLine("            }");
 8341                sb.AppendLine($"            else {{ {param.Name} = {rawVar} as {param.TypeFullName} ?? global::System.Ar
 8342                break;
 343            }
 344            case "object":
 345            {
 4346                if (param.ObjectProperties is { Count: > 0 })
 347                {
 4348                    var cVar = $"_c_{param.Name}";
 4349                    var jObjVar = $"_j_{param.Name}";
 4350                    var nullSuppress = param.IsNullable ? "" : "!";
 4351                    sb.AppendLine($"            {param.TypeFullName} {param.Name};");
 4352                    sb.AppendLine($"            if ({rawVar} is global::System.Text.Json.JsonElement {jObjVar} && {jObjV
 4353                    sb.AppendLine("            {");
 4354                    sb.AppendLine($"                {param.Name} = new {param.TypeFullName}();");
 4355                    AppendObjectPropertyAssignments(
 4356                        sb,
 4357                        targetVar: param.Name,
 4358                        sourceJsonElementVar: jObjVar,
 4359                        properties: param.ObjectProperties,
 4360                        indent: "                ");
 4361                    sb.AppendLine("            }");
 4362                    sb.AppendLine($"            else if ({rawVar} is {param.TypeFullName} {cVar})");
 4363                    sb.AppendLine("            {");
 4364                    sb.AppendLine($"                {param.Name} = {cVar};");
 4365                    sb.AppendLine("            }");
 4366                    sb.AppendLine("            else");
 4367                    sb.AppendLine("            {");
 4368                    sb.AppendLine($"                {param.Name} = default({param.TypeFullName}){nullSuppress};");
 4369                    sb.AppendLine("            }");
 370                }
 371                else
 372                {
 0373                    var cVar = $"_c_{param.Name}";
 0374                    var nullSuppress = param.IsNullable ? "" : "!";
 0375                    sb.AppendLine($"            var {param.Name} = {rawVar} is {param.TypeFullName} {cVar} ? {cVar} : de
 376                }
 0377                break;
 378            }
 379            default:
 380            {
 0381                var cVar = $"_c_{param.Name}";
 0382                var nullSuppress = param.IsNullable ? "" : "!";
 0383                sb.AppendLine($"            var {param.Name} = {rawVar} is {param.TypeFullName} {cVar} ? {cVar} : defaul
 384                break;
 385            }
 386        }
 0387    }
 388
 389    /// <summary>
 390    /// Emits a kind-tolerant extraction call for a primitive parameter, gated by
 391    /// <c>AgentFrameworkArgumentExtractor.IsArgumentSupplied</c>:
 392    /// <list type="bullet">
 393    /// <item><description>Required (not nullable, no default): emit a fail-fast guard that
 394    /// throws <see cref="System.ArgumentException"/> citing the tool/method/argument name.</description></item>
 395    /// <item><description>Has default: fall back to the captured C# default literal when the
 396    /// argument is missing, <c>JsonValueKind.Null</c>, or <c>JsonValueKind.Undefined</c>. The
 397    /// default literal wins over <c>null</c> even when the parameter is nullable
 398    /// (<c>string? p = "x"</c> falls back to <c>"x"</c>, not <c>null</c>).</description></item>
 399    /// <item><description>Nullable with no default: fall back to <see langword="null"/>.</description></item>
 400    /// </list>
 401    /// </summary>
 402    private static void AppendPrimitiveExtraction(
 403        StringBuilder sb,
 404        AgentFunctionTypeInfo type,
 405        AgentFunctionMethodInfo method,
 406        AgentFunctionParameterInfo param,
 407        string rawVar,
 408        string extractorMethod)
 409    {
 40410        var varType = GetVariableTypeForParam(param);
 40411        var extractorCall = $"{ExtractorFqn}.{extractorMethod}({rawVar})";
 412
 40413        if (param.IsRequired)
 414        {
 23415            var toolMethodName = $"{AgentDiscoveryHelper.GetShortName(type.TypeName)}.{method.MethodName}";
 23416            sb.AppendLine($"            if (!{ExtractorFqn}.IsArgumentSupplied({rawVar}))");
 23417            sb.AppendLine("            {");
 23418            sb.AppendLine("                throw new global::System.ArgumentException(");
 23419            sb.AppendLine($"                    \"Required argument '{param.Name}' was not supplied to AIFunction '{tool
 23420            sb.AppendLine("                    nameof(arguments));");
 23421            sb.AppendLine("            }");
 23422            sb.AppendLine($"            {varType} {param.Name} = {extractorCall};");
 23423            return;
 424        }
 425
 17426        var fallback = param.HasDefault ? param.DefaultLiteral! : "null";
 17427        sb.AppendLine($"            {varType} {param.Name};");
 17428        sb.AppendLine($"            if (!{ExtractorFqn}.IsArgumentSupplied({rawVar}))");
 17429        sb.AppendLine($"                {param.Name} = {fallback};");
 17430        sb.AppendLine("            else");
 17431        sb.AppendLine($"                {param.Name} = {extractorCall};");
 17432    }
 433
 434    /// <summary>
 435    /// Resolves the C# variable-declaration type for a parameter. For nullable reference
 436    /// types (where <see cref="AgentFunctionParameterInfo.TypeFullName"/> does not already
 437    /// carry a trailing <c>?</c> from Roslyn's nullable-value-type shorthand), appends
 438    /// <c>?</c> so the declaration accepts a <see langword="null"/> assignment without
 439    /// warnings under <c>#nullable enable</c>. For nullable value types, the type name
 440    /// already ends with <c>?</c> (e.g., <c>global::System.Int32?</c>) and is returned
 441    /// unchanged to avoid the invalid <c>int??</c> shape.
 442    /// </summary>
 443    private static string GetVariableTypeForParam(AgentFunctionParameterInfo param)
 444    {
 42445        if (param.IsNullable && !param.TypeFullName.EndsWith("?"))
 2446            return param.TypeFullName + "?";
 40447        return param.TypeFullName;
 448    }
 449
 450    /// <summary>
 451    /// Emits enum-aware extraction. The JSON schema treats enums as <c>"string"</c> (LLMs
 452    /// emit enum values as strings), but the variable type is the enum itself — so the raw
 453    /// pipeline of <c>GetStringArgument</c> won't compile against an enum target. This
 454    /// emission gates with <c>AgentFrameworkArgumentExtractor.IsArgumentSupplied</c>, then
 455    /// routes the supplied string through <c>Enum.Parse&lt;T&gt;(s, ignoreCase: true)</c>.
 456    /// Default-value semantics mirror <see cref="AppendPrimitiveExtraction"/>: required →
 457    /// fail-fast guard; defaulted → emit the captured enum-member literal; nullable, no
 458    /// default → fall back to <see langword="null"/>.
 459    /// </summary>
 460    private static void AppendEnumExtraction(
 461        StringBuilder sb,
 462        AgentFunctionTypeInfo type,
 463        AgentFunctionMethodInfo method,
 464        AgentFunctionParameterInfo param,
 465        string rawVar)
 466    {
 2467        var varType = GetVariableTypeForParam(param);
 2468        var enumType = param.TypeFullName.TrimEnd('?');
 2469        var stringExtract = $"{ExtractorFqn}.GetStringArgument({rawVar})";
 2470        var enumParse = $"global::System.Enum.Parse<{enumType}>({stringExtract}, ignoreCase: true)";
 471
 2472        if (param.IsRequired)
 473        {
 1474            var toolMethodName = $"{AgentDiscoveryHelper.GetShortName(type.TypeName)}.{method.MethodName}";
 1475            sb.AppendLine($"            if (!{ExtractorFqn}.IsArgumentSupplied({rawVar}))");
 1476            sb.AppendLine("            {");
 1477            sb.AppendLine("                throw new global::System.ArgumentException(");
 1478            sb.AppendLine($"                    \"Required argument '{param.Name}' was not supplied to AIFunction '{tool
 1479            sb.AppendLine("                    nameof(arguments));");
 1480            sb.AppendLine("            }");
 1481            sb.AppendLine($"            {varType} {param.Name} = {enumParse};");
 1482            return;
 483        }
 484
 1485        var fallback = param.HasDefault ? param.DefaultLiteral! : "null";
 1486        sb.AppendLine($"            {varType} {param.Name};");
 1487        sb.AppendLine($"            if (!{ExtractorFqn}.IsArgumentSupplied({rawVar}))");
 1488        sb.AppendLine($"                {param.Name} = {fallback};");
 1489        sb.AppendLine("            else");
 1490        sb.AppendLine($"                {param.Name} = {enumParse};");
 1491    }
 492
 493    /// <summary>
 494    /// Resolves the right helper for a "string" schema type, using the
 495    /// <see cref="AgentFunctionParameterInfo.JsonSchemaFormat"/> hint and the C# type to
 496    /// route <see cref="System.Guid"/>/<see cref="System.DateTime"/>/<see cref="System.DateTimeOffset"/>/
 497    /// <see cref="System.TimeSpan"/> through their typed extractors instead of
 498    /// <c>GetStringArgument</c> (which would fail to compile against a non-string parameter type).
 499    /// JSON Schema collapses <see cref="System.DateTime"/> and <see cref="System.DateTimeOffset"/>
 500    /// under the same <c>"date-time"</c> format; the C# type disambiguates.
 501    /// </summary>
 18502    private static string GetStringFamilyHelper(AgentFunctionParameterInfo param) => param.JsonSchemaFormat switch
 18503    {
 2504        "uuid" => "GetGuidArgument",
 2505        "duration" => "GetTimeSpanArgument",
 4506        "date-time" => param.TypeFullName.Contains("DateTimeOffset") ? "GetDateTimeOffsetArgument" : "GetDateTimeArgumen
 10507        _ => "GetStringArgument",
 18508    };
 509
 510    /// <summary>
 511    /// Resolves the right helper for a "string"-schema object property. Same disambiguation
 512    /// as <see cref="GetStringFamilyHelper(AgentFunctionParameterInfo)"/> but uses
 513    /// <see cref="ObjectPropertyInfo.CSharpTypeFullName"/> for the DateTime/DateTimeOffset split.
 514    /// </summary>
 16515    private static string GetStringFamilyHelperForProperty(ObjectPropertyInfo prop) => prop.SchemaFormat switch
 16516    {
 2517        "uuid" => "GetGuidArgument",
 1518        "duration" => "GetTimeSpanArgument",
 2519        "date-time" => prop.CSharpTypeFullName.Contains("DateTimeOffset") ? "GetDateTimeOffsetArgument" : "GetDateTimeAr
 11520        _ => "GetStringArgument",
 16521    };
 522
 523    private static string GetIntegerHelperForType(string typeFullName)
 524    {
 16525        if (typeFullName.Contains("System.Int64")) return "GetInt64Argument";
 14526        if (typeFullName.Contains("System.Int16")) return "GetInt16Argument";
 14527        if (typeFullName.Contains("System.SByte")) return "GetSByteArgument";
 14528        if (typeFullName.Contains("System.Byte")) return "GetByteArgument";
 14529        if (typeFullName.Contains("System.UInt64")) return "GetUInt64Argument";
 14530        if (typeFullName.Contains("System.UInt32")) return "GetUInt32Argument";
 14531        if (typeFullName.Contains("System.UInt16")) return "GetUInt16Argument";
 14532        return "GetInt32Argument";
 533    }
 534
 535    private static string GetNumberHelperForType(string typeFullName)
 536    {
 9537        if (typeFullName.Contains("System.Single")) return "GetSingleArgument";
 7538        if (typeFullName.Contains("System.Decimal")) return "GetDecimalArgument";
 3539        return "GetDoubleArgument";
 540    }
 541
 542    /// <summary>
 543    /// Emits per-property assignment statements for a complex object — used by both the
 544    /// array-of-objects branch (one assignment block per array element) and the top-level
 545    /// DTO branch (one assignment block for the parameter itself). Mirrors the schema-side
 546    /// machinery in <c>BuildObjectSchemaJson</c> but on the C# code-emit side.
 547    /// <para>
 548    /// Each property assignment is gated on both <c>TryGetProperty</c> (key present) and an
 549    /// inline <c>JsonValueKind.Null</c>/<c>JsonValueKind.Undefined</c> check (value supplied).
 550    /// When the value is missing or null, the property is left at whatever the parent
 551    /// <c>new T()</c> established — which respects any C# init-default declared on the
 552    /// property (e.g., <c>public string Foo { get; init; } = "default";</c>) without the
 553    /// generator needing to re-emit the literal. This avoids invoking the strict extractors
 554    /// (which throw on null) and makes <c>{"foo": null}</c> payloads symmetric with omitted
 555    /// keys.
 556    /// </para>
 557    /// </summary>
 558    private static void AppendObjectPropertyAssignments(
 559        StringBuilder sb,
 560        string targetVar,
 561        string sourceJsonElementVar,
 562        IReadOnlyList<ObjectPropertyInfo> properties,
 563        string indent)
 564    {
 62565        foreach (var prop in properties)
 566        {
 22567            sb.AppendLine($"{indent}if ({sourceJsonElementVar}.TryGetProperty(\"{prop.JsonName}\", out var _p_{prop.Json
 22568            sb.AppendLine($"{indent}    _p_{prop.JsonName}.ValueKind != global::System.Text.Json.JsonValueKind.Null &&")
 22569            sb.AppendLine($"{indent}    _p_{prop.JsonName}.ValueKind != global::System.Text.Json.JsonValueKind.Undefined
 22570            switch (prop.SchemaType)
 571            {
 572                case "string":
 16573                    sb.AppendLine($"{indent}    {targetVar}.{prop.CSharpName} = {ExtractorFqn}.{GetStringFamilyHelperFor
 16574                    break;
 575                case "integer":
 4576                    sb.AppendLine($"{indent}    {targetVar}.{prop.CSharpName} = {ExtractorFqn}.{GetIntegerHelperForType(
 4577                    break;
 578                case "number":
 1579                    sb.AppendLine($"{indent}    {targetVar}.{prop.CSharpName} = {ExtractorFqn}.{GetNumberHelperForType(p
 1580                    break;
 581                case "boolean":
 1582                    sb.AppendLine($"{indent}    {targetVar}.{prop.CSharpName} = {ExtractorFqn}.GetBooleanArgument(_p_{pr
 1583                    break;
 584                default:
 0585                    sb.AppendLine($"{indent}    {targetVar}.{prop.CSharpName} = {ExtractorFqn}.GetStringArgument(_p_{pro
 586                    break;
 587            }
 588        }
 9589    }
 590
 591    /// <summary>
 592    /// Extracts the element type from an array type's fully-qualified name.
 593    /// E.g., <c>"global::MyNamespace.FeedbackEntry[]"</c> → <c>"global::MyNamespace.FeedbackEntry"</c>.
 594    /// </summary>
 595    private static string GetArrayElementType(string arrayTypeFullName)
 596    {
 597        // Handle T[] syntax
 8598        if (arrayTypeFullName.EndsWith("[]"))
 8599            return arrayTypeFullName.Substring(0, arrayTypeFullName.Length - 2);
 600
 601        // Fallback: return as-is (shouldn't happen for valid array types)
 0602        return arrayTypeFullName;
 603    }
 604}

Methods/Properties

GenerateAIFunctionProviderSource(System.Collections.Generic.List`1<NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionTypeInfo>,System.String)
AppendAIFunctionNestedClass(System.Text.StringBuilder,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionTypeInfo,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionMethodInfo,System.String)
BuildJsonSchema(System.Collections.Immutable.ImmutableArray`1<NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionParameterInfo>)
BuildJsonSchemaTypeEntry(NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionParameterInfo)
AppendParameterExtraction(System.Text.StringBuilder,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionTypeInfo,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionMethodInfo,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionParameterInfo)
AppendPrimitiveExtraction(System.Text.StringBuilder,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionTypeInfo,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionMethodInfo,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionParameterInfo,System.String,System.String)
GetVariableTypeForParam(NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionParameterInfo)
AppendEnumExtraction(System.Text.StringBuilder,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionTypeInfo,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionMethodInfo,NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionParameterInfo,System.String)
GetStringFamilyHelper(NexusLabs.Needlr.AgentFramework.Generators.AgentFunctionParameterInfo)
GetStringFamilyHelperForProperty(NexusLabs.Needlr.AgentFramework.Generators.ObjectPropertyInfo)
GetIntegerHelperForType(System.String)
GetNumberHelperForType(System.String)
AppendObjectPropertyAssignments(System.Text.StringBuilder,System.String,System.String,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.AgentFramework.Generators.ObjectPropertyInfo>,System.String)
GetArrayElementType(System.String)