diff --git a/dotnet/src/Microsoft.Agents.AI/CompatibilitySuppressions.xml b/dotnet/src/Microsoft.Agents.AI/CompatibilitySuppressions.xml index 8b7fd65d4c..daad8d79a1 100644 --- a/dotnet/src/Microsoft.Agents.AI/CompatibilitySuppressions.xml +++ b/dotnet/src/Microsoft.Agents.AI/CompatibilitySuppressions.xml @@ -1,6 +1,34 @@  + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String) + lib/net10.0/Microsoft.Agents.AI.dll + lib/net10.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary) + lib/net10.0/Microsoft.Agents.AI.dll + lib/net10.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String) + lib/net10.0/Microsoft.Agents.AI.dll + lib/net10.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String) + lib/net10.0/Microsoft.Agents.AI.dll + lib/net10.0/Microsoft.Agents.AI.dll + true + CP0002 M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[]) @@ -36,6 +64,34 @@ lib/net10.0/Microsoft.Agents.AI.dll true + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String) + lib/net472/Microsoft.Agents.AI.dll + lib/net472/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary) + lib/net472/Microsoft.Agents.AI.dll + lib/net472/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String) + lib/net472/Microsoft.Agents.AI.dll + lib/net472/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String) + lib/net472/Microsoft.Agents.AI.dll + lib/net472/Microsoft.Agents.AI.dll + true + CP0002 M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[]) @@ -71,6 +127,34 @@ lib/net472/Microsoft.Agents.AI.dll true + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String) + lib/net8.0/Microsoft.Agents.AI.dll + lib/net8.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary) + lib/net8.0/Microsoft.Agents.AI.dll + lib/net8.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String) + lib/net8.0/Microsoft.Agents.AI.dll + lib/net8.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String) + lib/net8.0/Microsoft.Agents.AI.dll + lib/net8.0/Microsoft.Agents.AI.dll + true + CP0002 M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[]) @@ -106,6 +190,34 @@ lib/net8.0/Microsoft.Agents.AI.dll true + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String) + lib/net9.0/Microsoft.Agents.AI.dll + lib/net9.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary) + lib/net9.0/Microsoft.Agents.AI.dll + lib/net9.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String) + lib/net9.0/Microsoft.Agents.AI.dll + lib/net9.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String) + lib/net9.0/Microsoft.Agents.AI.dll + lib/net9.0/Microsoft.Agents.AI.dll + true + CP0002 M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[]) @@ -141,6 +253,34 @@ lib/net9.0/Microsoft.Agents.AI.dll true + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(Microsoft.Agents.AI.AgentSkillFrontmatter,System.String) + lib/netstandard2.0/Microsoft.Agents.AI.dll + lib/netstandard2.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,Microsoft.Extensions.AI.AdditionalPropertiesDictionary) + lib/netstandard2.0/Microsoft.Agents.AI.dll + lib/netstandard2.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddResource(System.String,System.Delegate,System.String) + lib/netstandard2.0/Microsoft.Agents.AI.dll + lib/netstandard2.0/Microsoft.Agents.AI.dll + true + + + CP0002 + M:Microsoft.Agents.AI.AgentInlineSkill.AddScript(System.String,System.Delegate,System.String) + lib/netstandard2.0/Microsoft.Agents.AI.dll + lib/netstandard2.0/Microsoft.Agents.AI.dll + true + CP0002 M:Microsoft.Agents.AI.AgentSkillsProvider.#ctor(Microsoft.Agents.AI.AgentInlineSkill[]) diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentClassSkill.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentClassSkill.cs index 47f12d5ea5..4febb8bc79 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentClassSkill.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentClassSkill.cs @@ -2,6 +2,8 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using Microsoft.Extensions.AI; using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Agents.AI; @@ -13,7 +15,7 @@ namespace Microsoft.Agents.AI; /// /// Inherit from this class to create a self-contained skill definition. Override the abstract /// properties to provide name, description, and instructions. Use , -/// , and to define +/// , and to define /// inline resources and scripts. /// /// @@ -79,9 +81,13 @@ protected static AgentSkillResource CreateResource(string name, object value, st /// The resource name. /// A method that produces the resource value when requested. /// An optional description of the resource. + /// + /// Optional used to marshal the delegate's parameters and return value. + /// When , is used. + /// /// A new instance. - protected static AgentSkillResource CreateResource(string name, Delegate method, string? description = null) - => new AgentInlineSkillResource(name, method, description); + protected static AgentSkillResource CreateResource(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null) + => new AgentInlineSkillResource(name, method, description, serializerOptions); /// /// Creates a skill script backed by a delegate. @@ -89,7 +95,11 @@ protected static AgentSkillResource CreateResource(string name, Delegate method, /// The script name. /// A method to execute when the script is invoked. /// An optional description of the script. + /// + /// Optional used to marshal the delegate's parameters and return value. + /// When , is used. + /// /// A new instance. - protected static AgentSkillScript CreateScript(string name, Delegate method, string? description = null) - => new AgentInlineSkillScript(name, method, description); + protected static AgentSkillScript CreateScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null) + => new AgentInlineSkillScript(name, method, description, serializerOptions); } diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkill.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkill.cs index f24d41c362..cdfb14a584 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkill.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkill.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using Microsoft.Extensions.AI; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; @@ -14,7 +15,7 @@ namespace Microsoft.Agents.AI; /// /// /// All calls to , -/// , and +/// , and /// must be made before the skill's is first accessed. /// Calls made after that point will not be reflected in the generated /// . In typical usage, this means configuring all @@ -25,6 +26,7 @@ namespace Microsoft.Agents.AI; public sealed class AgentInlineSkill : AgentSkill { private readonly string _instructions; + private readonly JsonSerializerOptions? _serializerOptions; private List? _resources; private List? _scripts; private string? _cachedContent; @@ -35,10 +37,16 @@ public sealed class AgentInlineSkill : AgentSkill /// /// The skill frontmatter containing name, description, and other metadata. /// Skill instructions text. - public AgentInlineSkill(AgentSkillFrontmatter frontmatter, string instructions) + /// + /// Optional applied by default to all scripts and delegate resources + /// added to this skill. Individual and + /// calls can override this default. When , is used. + /// + public AgentInlineSkill(AgentSkillFrontmatter frontmatter, string instructions, JsonSerializerOptions? serializerOptions = null) { this.Frontmatter = Throw.IfNull(frontmatter); this._instructions = Throw.IfNullOrWhitespace(instructions); + this._serializerOptions = serializerOptions; } /// @@ -52,6 +60,11 @@ public AgentInlineSkill(AgentSkillFrontmatter frontmatter, string instructions) /// Optional compatibility information (max 500 chars). /// Optional space-delimited list of pre-approved tools. /// Optional arbitrary key-value metadata. + /// + /// Optional applied by default to all scripts and delegate resources + /// added to this skill. Individual and + /// calls can override this default. When , is used. + /// public AgentInlineSkill( string name, string description, @@ -59,7 +72,8 @@ public AgentInlineSkill( string? license = null, string? compatibility = null, string? allowedTools = null, - AdditionalPropertiesDictionary? metadata = null) + AdditionalPropertiesDictionary? metadata = null, + JsonSerializerOptions? serializerOptions = null) : this( new AgentSkillFrontmatter(name, description, compatibility) { @@ -67,7 +81,8 @@ public AgentInlineSkill( AllowedTools = allowedTools, Metadata = metadata, }, - instructions) + instructions, + serializerOptions) { } @@ -103,10 +118,14 @@ public AgentInlineSkill AddResource(string name, object value, string? descripti /// The resource name. /// A method that produces the resource value when requested. /// An optional description of the resource. + /// + /// Optional for this resource's delegate marshaling. + /// When , the skill-level default (if any) is used; otherwise is used. + /// /// This instance, for chaining. - public AgentInlineSkill AddResource(string name, Delegate method, string? description = null) + public AgentInlineSkill AddResource(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null) { - (this._resources ??= []).Add(new AgentInlineSkillResource(name, method, description)); + (this._resources ??= []).Add(new AgentInlineSkillResource(name, method, description, serializerOptions ?? this._serializerOptions)); return this; } @@ -117,10 +136,14 @@ public AgentInlineSkill AddResource(string name, Delegate method, string? descri /// The script name. /// A method to execute when the script is invoked. /// An optional description of the script. + /// + /// Optional for this script's delegate marshaling. + /// When , the skill-level default (if any) is used; otherwise is used. + /// /// This instance, for chaining. - public AgentInlineSkill AddScript(string name, Delegate method, string? description = null) + public AgentInlineSkill AddScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null) { - (this._scripts ??= []).Add(new AgentInlineSkillScript(name, method, description)); + (this._scripts ??= []).Add(new AgentInlineSkillScript(name, method, description, serializerOptions ?? this._serializerOptions)); return this; } } diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillResource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillResource.cs index 38791c3b21..5e032f073f 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillResource.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillResource.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; @@ -40,11 +41,17 @@ public AgentInlineSkillResource(string name, object value, string? description = /// The resource name. /// A method that produces the resource value when requested. /// An optional description of the resource. - public AgentInlineSkillResource(string name, Delegate method, string? description = null) + /// + /// Optional used to marshal the delegate's parameters and return value. + /// When , is used. + /// + public AgentInlineSkillResource(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null) : base(name, description) { Throw.IfNull(method); - this._function = AIFunctionFactory.Create(method, name: this.Name); + + var options = new AIFunctionFactoryOptions { Name = this.Name, SerializerOptions = serializerOptions }; + this._function = AIFunctionFactory.Create(method, options); } /// diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillScript.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillScript.cs index e851c6f9fc..232a2fefce 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillScript.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillScript.cs @@ -26,11 +26,17 @@ internal sealed class AgentInlineSkillScript : AgentSkillScript /// The script name. /// A method to execute when the script is invoked. Parameters are automatically deserialized from JSON. /// An optional description of the script. - public AgentInlineSkillScript(string name, Delegate method, string? description = null) + /// + /// Optional used to marshal the delegate's parameters and return value. + /// When , is used. + /// + public AgentInlineSkillScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null) : base(Throw.IfNullOrWhitespace(name), description) { Throw.IfNull(method); - this._function = AIFunctionFactory.Create(method, name: this.Name); + + var options = new AIFunctionFactoryOptions { Name = this.Name, SerializerOptions = serializerOptions }; + this._function = AIFunctionFactory.Create(method, options); } /// diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs index 2c453f21b6..f03c2d65fe 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.UnitTests.AgentSkills; @@ -374,9 +375,68 @@ public CustomScript(string name, string? description = null) { } - public override Task RunAsync(AgentSkill skill, Extensions.AI.AIFunctionArguments arguments, CancellationToken cancellationToken = default) + public override Task RunAsync(AgentSkill skill, AIFunctionArguments arguments, CancellationToken cancellationToken = default) => Task.FromResult("script-result"); } #endregion + + [Fact] + public async Task CreateScript_WithSerializerOptions_DeserializesCustomInputTypeAsync() + { + // Arrange + var skill = new CustomTypeSkill(); + var jso = SkillTestJsonContext.Default.Options; + + // Act — pass a custom type as JSON; the JSO enables deserialization + var script = skill.Scripts![0]; + var inputJson = JsonSerializer.SerializeToElement(new LookupRequest { Query = "test", MaxResults = 5 }, jso); + var args = new AIFunctionArguments { ["request"] = inputJson }; + var result = await script.RunAsync(skill, args, CancellationToken.None); + + // Assert — the custom input type was deserialized and the response was produced + Assert.NotNull(result); + var resultText = result!.ToString()!; + Assert.Contains("result for test", resultText); + Assert.Contains("5", resultText); + } + + [Fact] + public async Task CreateResource_WithSerializerOptions_SerializesReturnsCustomTypeAsync() + { + // Arrange + var skill = new CustomTypeSkill(); + + // Act + var result = await skill.Resources![0].ReadAsync(); + + // Assert — the custom type was returned successfully + Assert.NotNull(result); + Assert.Contains("dark", result!.ToString()!); + } + + private sealed class CustomTypeSkill : AgentClassSkill + { + public override AgentSkillFrontmatter Frontmatter { get; } = new("custom-type-skill", "Skill with custom-typed scripts and resources."); + + protected override string Instructions => "Body."; + + public override IReadOnlyList? Resources => + [ + CreateResource("config", () => new SkillConfig + { + Theme = "dark", + Verbose = true + }, serializerOptions: SkillTestJsonContext.Default.Options), + ]; + + public override IReadOnlyList? Scripts => + [ + CreateScript("Lookup", (LookupRequest request) => new LookupResponse + { + Items = [$"result for {request.Query}"], + TotalCount = request.MaxResults, + }, serializerOptions: SkillTestJsonContext.Default.Options), + ]; + } } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillResourceTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillResourceTests.cs index 202a90d460..3901e61c7c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillResourceTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillResourceTests.cs @@ -139,6 +139,21 @@ public void Constructor_Delegate_SetsNameAndDescription() Assert.Equal("Dynamic resource.", resource.Description); } + [Fact] + public async Task ReadAsync_WithSerializerOptions_SerializesReturnCustomTypeAsync() + { + // Arrange — delegate resource returns a custom type; the JSO includes a source-generated context for it + var jso = SkillTestJsonContext.Default.Options; + var resource = new AgentInlineSkillResource("config", () => new SkillConfig { Theme = "dark", Verbose = true }, serializerOptions: jso); + + // Act + var result = await resource.ReadAsync(); + + // Assert — the custom type was returned successfully + Assert.NotNull(result); + Assert.Contains("dark", result!.ToString()!); + } + [Fact] public async Task ReadAsync_SupportsCancellationTokenAsync() { diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillScriptTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillScriptTests.cs index 61f42ca66d..5d5dc5bd02 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillScriptTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillScriptTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; @@ -115,6 +116,28 @@ public void Constructor_NullMethod_Throws() new AgentInlineSkillScript("my-script", null!)); } + [Fact] + public async Task RunAsync_WithSerializerOptions_MarshalsCustomTypesAsync() + { + // Arrange — script accepts a custom type; the JSO includes a source-generated context for it + var jso = SkillTestJsonContext.Default.Options; + var script = new AgentInlineSkillScript("lookup", (LookupRequest request) => new LookupResponse + { + Items = ["result-1", "result-2"], + TotalCount = request.MaxResults, + }, serializerOptions: jso); + var skill = new AgentInlineSkill("test-skill", "Test.", "Instructions."); + var inputJson = JsonSerializer.SerializeToElement(new LookupRequest { Query = "test", MaxResults = 5 }, jso); + var args = new AIFunctionArguments { ["request"] = inputJson }; + + // Act + var result = await script.RunAsync(skill, args, CancellationToken.None); + + // Assert — the custom input type was deserialized and the response was produced + Assert.NotNull(result); + Assert.Contains("5", result!.ToString()!); + } + [Fact] public async Task RunAsync_StringParameter_WorksAsync() { diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillTests.cs index 9a1f6a4951..5aad5ef2f3 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillTests.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.UnitTests.AgentSkills; @@ -417,4 +420,82 @@ public void Content_ResourceWithDescription_IncludesDescriptionAttribute() Assert.Contains("description=\"A described resource.\"", content); Assert.DoesNotContain("no-desc\" description", content); } + + [Fact] + public async Task AddScript_SkillLevelSerializerOptions_AppliedToScriptAsync() + { + // Arrange — skill-level JSO with source-generated context for custom types + var jso = SkillTestJsonContext.Default.Options; + var skill = new AgentInlineSkill("jso-skill", "JSO test.", "Instructions.", serializerOptions: jso); + skill.AddScript("lookup", (LookupRequest request) => new LookupResponse + { + Items = [$"result for {request.Query}"], + TotalCount = request.MaxResults, + }); + var inputJson = JsonSerializer.SerializeToElement(new LookupRequest { Query = "test", MaxResults = 3 }, jso); + var args = new AIFunctionArguments { ["request"] = inputJson }; + + // Act + var result = await skill.Scripts![0].RunAsync(skill, args, CancellationToken.None); + + // Assert — the custom input was deserialized via skill-level JSO and response was produced + Assert.NotNull(result); + Assert.Contains("result for test", result!.ToString()!); + } + + [Fact] + public async Task AddScript_PerScriptSerializerOptions_OverridesSkillLevelAsync() + { + // Arrange — skill-level JSO uses snake_case naming; per-script JSO overrides with source-generated context + var skillJso = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; + var scriptJso = SkillTestJsonContext.Default.Options; + var skill = new AgentInlineSkill("override-skill", "Override test.", "Instructions.", serializerOptions: skillJso); + skill.AddScript("lookup", (LookupRequest request) => new LookupResponse + { + Items = [$"found {request.Query}"], + TotalCount = request.MaxResults, + }, serializerOptions: scriptJso); + var inputJson = JsonSerializer.SerializeToElement(new LookupRequest { Query = "override", MaxResults = 7 }, scriptJso); + var args = new AIFunctionArguments { ["request"] = inputJson }; + + // Act + var result = await skill.Scripts![0].RunAsync(skill, args, CancellationToken.None); + + // Assert — per-script JSO takes effect and custom types are properly marshaled + Assert.NotNull(result); + Assert.Contains("found override", result!.ToString()!); + } + + [Fact] + public async Task AddResource_SkillLevelSerializerOptions_AppliedToDelegateResourceAsync() + { + // Arrange — skill-level JSO with source-generated context; delegate resource returns a custom type + var jso = SkillTestJsonContext.Default.Options; + var skill = new AgentInlineSkill("custom-type-resource-skill", "Custom type resource test.", "Instructions.", serializerOptions: jso); + skill.AddResource("config", () => new SkillConfig { Theme = "dark", Verbose = true }); + + // Act + var result = await skill.Resources![0].ReadAsync(); + + // Assert — the custom type was returned successfully via skill-level JSO + Assert.NotNull(result); + Assert.Contains("dark", result!.ToString()!); + } + + [Fact] + public async Task AddResource_PerResourceSerializerOptions_OverridesSkillLevelAsync() + { + // Arrange — skill-level JSO uses snake_case naming; per-resource JSO overrides with source-generated context + var skillJso = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; + var resourceJso = SkillTestJsonContext.Default.Options; + var skill = new AgentInlineSkill("override-resource-skill", "Override resource test.", "Instructions.", serializerOptions: skillJso); + skill.AddResource("config", () => new SkillConfig { Theme = "dark", Verbose = true }, serializerOptions: resourceJso); + + // Act + var result = await skill.Resources![0].ReadAsync(); + + // Assert — per-resource JSO takes effect and custom type is properly marshaled + Assert.NotNull(result); + Assert.Contains("dark", result!.ToString()!); + } } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/TestSkillTypes.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/TestSkillTypes.cs index 8c97a31ae4..a2fb4155e4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/TestSkillTypes.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/TestSkillTypes.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; @@ -70,3 +71,52 @@ public override Task> GetSkillsAsync(CancellationToken cancell return Task.FromResult(this._skills); } } + +/// +/// Custom input type accepted by skill script delegates in JSO tests. +/// +internal sealed class LookupRequest +{ + /// Gets or sets the search query. + public string Query { get; set; } = string.Empty; + + /// Gets or sets the maximum number of results. + public int MaxResults { get; set; } +} + +/// +/// Custom output type returned by skill script delegates in JSO tests. +/// +internal sealed class LookupResponse +{ + /// Gets or sets the items found. + public IList Items { get; set; } = []; + + /// Gets or sets the total number of matches. + public int TotalCount { get; set; } +} + +/// +/// Custom output type returned by skill resource delegates in JSO tests. +/// +internal sealed class SkillConfig +{ + /// Gets or sets the theme name. + public string Theme { get; set; } = string.Empty; + + /// Gets or sets whether verbose mode is enabled. + public bool Verbose { get; set; } +} + +/// +/// Source-generated JSON serializer context for skill test types. +/// Provides serialization support for , , +/// and without requiring runtime reflection. +/// +[JsonSourceGenerationOptions] +[JsonSerializable(typeof(LookupRequest))] +[JsonSerializable(typeof(LookupResponse))] +[JsonSerializable(typeof(SkillConfig))] +internal sealed partial class SkillTestJsonContext : JsonSerializerContext +{ +}