From fb5e1029ca9cbb3fde5f975f9bb343f5c6b9feb8 Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Sun, 31 May 2026 06:34:41 +0800 Subject: [PATCH] .NET: accept JSON string inline skill args --- .../Programmatic/AgentInlineSkillScript.cs | 38 +++++++++++++++---- .../AgentInlineSkillScriptTests.cs | 31 +++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillScript.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillScript.cs index c0abc73252..db4ed334df 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillScript.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillScript.cs @@ -92,18 +92,42 @@ private static AIFunctionArguments ConvertToFunctionArguments(JsonElement? argum return []; } - if (arguments.Value.ValueKind != JsonValueKind.Object) + JsonDocument? parsedStringArguments = null; + var argumentElement = arguments.Value; + + if (argumentElement.ValueKind == JsonValueKind.String) { - throw new InvalidOperationException( - $"Inline skill scripts expect arguments as a JSON object but received a JSON element of kind '{arguments.Value.ValueKind}'."); + string? rawArguments = argumentElement.GetString(); + try + { + parsedStringArguments = JsonDocument.Parse(rawArguments ?? string.Empty); + argumentElement = parsedStringArguments.RootElement; + } + catch (JsonException ex) + { + throw new InvalidOperationException("Inline skill scripts received arguments as a JSON string, but the string did not contain valid JSON.", ex); + } } - var dict = new Dictionary(); - foreach (var property in arguments.Value.EnumerateObject()) + if (argumentElement.ValueKind != JsonValueKind.Object) { - dict[property.Name] = property.Value; + throw new InvalidOperationException( + $"Inline skill scripts expect arguments as a JSON object but received a JSON element of kind '{argumentElement.ValueKind}'."); } - return new AIFunctionArguments(dict); + try + { + var dict = new Dictionary(); + foreach (var property in argumentElement.EnumerateObject()) + { + dict[property.Name] = parsedStringArguments is null ? property.Value : property.Value.Clone(); + } + + return new AIFunctionArguments(dict); + } + finally + { + parsedStringArguments?.Dispose(); + } } } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillScriptTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillScriptTests.cs index 85521ba4a9..dfd1110251 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillScriptTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillScriptTests.cs @@ -43,6 +43,37 @@ public async Task RunAsync_WithParameters_PassesArgumentsAsync() Assert.Equal(10, int.Parse(result?.ToString()!)); } + [Fact] + public async Task RunAsync_WithJsonStringObjectArguments_PassesArgumentsAsync() + { + // Arrange + var script = new AgentInlineSkillScript("add", (int a, int b) => a + b); + var skill = new AgentInlineSkill("calc-skill", "Calc.", "Instructions."); + using var argsDoc = JsonDocument.Parse("\"{\\\"a\\\":3,\\\"b\\\":7}\""); + var args = argsDoc.RootElement; + + // Act + var result = await script.RunAsync(skill, args, null, CancellationToken.None); + + // Assert + Assert.Equal(10, int.Parse(result?.ToString()!)); + } + + [Fact] + public async Task RunAsync_WithInvalidJsonStringArguments_ThrowsInvalidOperationExceptionAsync() + { + // Arrange + var script = new AgentInlineSkillScript("noop", () => "ok"); + var skill = new AgentInlineSkill("test-skill", "Test.", "Instructions."); + using var argsDoc = JsonDocument.Parse("\"not json\""); + var args = argsDoc.RootElement; + + // Act & Assert + var ex = await Assert.ThrowsAsync( + () => script.RunAsync(skill, args, null, CancellationToken.None)); + Assert.Contains("did not contain valid JSON", ex.Message); + } + [Fact] public void ParametersSchema_NoParameters_ReturnsSchema() {