diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillContentBuilder.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillContentBuilder.cs index bae35766856..d1498dc25ce 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillContentBuilder.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillContentBuilder.cs @@ -72,7 +72,14 @@ public static string BuildAvailableResourcesBlock(IReadOnlyList\n"); + if (!string.IsNullOrWhiteSpace(resource.Description)) + { + sb.Append($" \n"); + } + else + { + sb.Append($" \n"); + } } sb.Append(""); @@ -108,14 +115,18 @@ public static string BuildAvailableScriptsBlock(IReadOnlyList foreach (var script in scripts) { var parametersSchema = script.ParametersSchema; + var nameAttr = $"name=\"{EscapeXmlString(script.Name)}\""; + var descAttr = !string.IsNullOrWhiteSpace(script.Description) + ? $" description=\"{EscapeXmlString(script.Description)}\"" + : string.Empty; if (parametersSchema is null) { - sb.Append($" \n"); } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs index 5fd1fa77bab..250dfa3f9cf 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs @@ -511,10 +511,10 @@ public async Task Content_RendersResources_InBodyAsync() // Act var content = await skill.GetContentAsync(); - // Assert — resources are rendered in body content by name; descriptions are not emitted + // Assert — resources are rendered in body content; described resources include description attribute Assert.Contains("", content); Assert.Contains("ref-data", content); - Assert.DoesNotContain("Some important data.", content); + Assert.Contains("description=\"Some important data.\"", content); } [Fact] diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillContentBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillContentBuilderTests.cs index df9242abb80..d1969961098 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillContentBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentInlineSkillContentBuilderTests.cs @@ -55,12 +55,11 @@ public void Build_ResourcesOnly_EmitsResourceEntriesAndSelfClosingScripts() // Act var content = AgentInlineSkillContentBuilder.Build("my-skill", "A skill.", "Instructions.", resources, scripts: null); - // Assert — resources are listed by name (no description), scripts are an empty element + // Assert — resources are listed by name with optional description, scripts are an empty element Assert.Contains("", content); - Assert.Contains("", content); + Assert.Contains("", content); Assert.Contains("", content); Assert.Contains("", content); - Assert.DoesNotContain("A described resource.", content); Assert.Contains("", content); } @@ -125,7 +124,7 @@ public void BuildAvailableResourcesBlock_EmptyList_ReturnsSelfClosingElement() } [Fact] - public void BuildAvailableResourcesBlock_WithResources_EmitsSelfClosingResourceEntries() + public void BuildAvailableResourcesBlock_WithResources_EmitsDescriptionWhenPresent() { // Arrange var resources = new AgentSkillResource[] @@ -137,12 +136,52 @@ public void BuildAvailableResourcesBlock_WithResources_EmitsSelfClosingResourceE // Act var block = AgentInlineSkillContentBuilder.BuildAvailableResourcesBlock(resources); - // Assert — resources are listed by name only (no description) + // Assert — resources with description include it as an attribute; those without omit it Assert.Contains("", block); - Assert.Contains("", block); + Assert.Contains("", block); Assert.Contains("", block); Assert.Contains("", block); - Assert.DoesNotContain("A described resource.", block); + } + + [Fact] + public void BuildAvailableResourcesBlock_ResourceDescriptionWithSpecialCharacters_IsXmlEscaped() + { + // Arrange + var resources = new AgentSkillResource[] { new AgentInlineSkillResource("cfg", "v", "has & \"chars\"") }; + + // Act + var block = AgentInlineSkillContentBuilder.BuildAvailableResourcesBlock(resources); + + // Assert + Assert.Contains("description=\"has <special> & "chars"\"", block); + } + + [Fact] + public void BuildAvailableResourcesBlock_ResourceWithEmptyDescription_OmitsDescriptionAttribute() + { + // Arrange + var resources = new AgentSkillResource[] { new AgentInlineSkillResource("config", "value", string.Empty) }; + + // Act + var block = AgentInlineSkillContentBuilder.BuildAvailableResourcesBlock(resources); + + // Assert + Assert.Contains("", block); + Assert.DoesNotContain("description=", block); + } + + [Fact] + public void BuildAvailableResourcesBlock_ResourceWithWhitespaceDescription_OmitsDescriptionAttribute() + { + // Arrange + var resources = new AgentSkillResource[] { new AgentInlineSkillResource("config", "value", " ") }; + + // Act + var block = AgentInlineSkillContentBuilder.BuildAvailableResourcesBlock(resources); + + // Assert + Assert.Contains("", block); + Assert.DoesNotContain("description=", block); } [Fact] @@ -175,7 +214,7 @@ public void BuildAvailableScriptsBlock_EmptyList_ReturnsSelfClosingElement() [Fact] public void BuildAvailableScriptsBlock_ScriptWithoutSchema_UsesSelfClosingScript() { - // Arrange — a script whose ParametersSchema is null + // Arrange — a script whose ParametersSchema is null and has no description var scripts = new AgentSkillScript[] { new FakeScript("no-params", parametersSchema: null) }; // Act @@ -186,6 +225,74 @@ public void BuildAvailableScriptsBlock_ScriptWithoutSchema_UsesSelfClosingScript Assert.DoesNotContain("", block); } + [Fact] + public void BuildAvailableScriptsBlock_ScriptWithDescription_EmitsDescriptionAttribute() + { + // Arrange — a script with a description but no schema + var scripts = new AgentSkillScript[] { new FakeScript("deploy", parametersSchema: null, description: "Deploy the app.") }; + + // Act + var block = AgentInlineSkillContentBuilder.BuildAvailableScriptsBlock(scripts); + + // Assert — description attribute is included + Assert.Contains("