Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,14 @@ public static string BuildAvailableResourcesBlock(IReadOnlyList<AgentSkillResour

foreach (var resource in resources)
{
sb.Append($" <resource name=\"{EscapeXmlString(resource.Name)}\"/>\n");
if (!string.IsNullOrWhiteSpace(resource.Description))
{
sb.Append($" <resource name=\"{EscapeXmlString(resource.Name)}\" description=\"{EscapeXmlString(resource.Description)}\"/>\n");
}
else
{
sb.Append($" <resource name=\"{EscapeXmlString(resource.Name)}\"/>\n");
}
}

sb.Append("</available_resources>");
Expand Down Expand Up @@ -108,14 +115,18 @@ public static string BuildAvailableScriptsBlock(IReadOnlyList<AgentSkillScript>
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($" <script name=\"{EscapeXmlString(script.Name)}\"/>\n");
sb.Append($" <script {nameAttr}{descAttr}/>\n");
}
else
{
sb.Append($" <script name=\"{EscapeXmlString(script.Name)}\">\n");
sb.Append($" <script {nameAttr}{descAttr}>\n");
sb.Append($" <parameters_schema>{EscapeXmlString(parametersSchema.Value.GetRawText(), preserveQuotes: true)}</parameters_schema>\n");
sb.Append(" </script>\n");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("<available_resources>", content);
Assert.Contains("ref-data", content);
Assert.DoesNotContain("Some important data.", content);
Assert.Contains("description=\"Some important data.\"", content);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("<available_resources>", content);
Assert.Contains("<resource name=\"config\"/>", content);
Assert.Contains("<resource name=\"config\" description=\"A described resource.\"/>", content);
Assert.Contains("<resource name=\"table\"/>", content);
Assert.Contains("</available_resources>", content);
Assert.DoesNotContain("A described resource.", content);
Assert.Contains("<available_scripts />", content);
}

Expand Down Expand Up @@ -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[]
Expand All @@ -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("<available_resources>", block);
Assert.Contains("<resource name=\"config\"/>", block);
Assert.Contains("<resource name=\"config\" description=\"A described resource.\"/>", block);
Assert.Contains("<resource name=\"table\"/>", block);
Assert.Contains("</available_resources>", block);
Assert.DoesNotContain("A described resource.", block);
}

[Fact]
public void BuildAvailableResourcesBlock_ResourceDescriptionWithSpecialCharacters_IsXmlEscaped()
{
// Arrange
var resources = new AgentSkillResource[] { new AgentInlineSkillResource("cfg", "v", "has <special> & \"chars\"") };

// Act
var block = AgentInlineSkillContentBuilder.BuildAvailableResourcesBlock(resources);

// Assert
Assert.Contains("description=\"has &lt;special&gt; &amp; &quot;chars&quot;\"", 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("<resource name=\"config\"/>", 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("<resource name=\"config\"/>", block);
Assert.DoesNotContain("description=", block);
}

[Fact]
Expand Down Expand Up @@ -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
Expand All @@ -186,6 +225,74 @@ public void BuildAvailableScriptsBlock_ScriptWithoutSchema_UsesSelfClosingScript
Assert.DoesNotContain("<parameters_schema>", 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("<script name=\"deploy\" description=\"Deploy the app.\"/>", block);
}

[Fact]
public void BuildAvailableScriptsBlock_ScriptDescriptionWithSpecialCharacters_IsXmlEscaped()
{
// Arrange
var scripts = new AgentSkillScript[] { new FakeScript("deploy", parametersSchema: null, description: "has <special> & \"chars\"") };

// Act
var block = AgentInlineSkillContentBuilder.BuildAvailableScriptsBlock(scripts);

// Assert
Assert.Contains("description=\"has &lt;special&gt; &amp; &quot;chars&quot;\"", block);
}

[Fact]
public void BuildAvailableScriptsBlock_ScriptWithEmptyDescription_OmitsDescriptionAttribute()
{
// Arrange
var scripts = new AgentSkillScript[] { new FakeScript("deploy", parametersSchema: null, description: string.Empty) };

// Act
var block = AgentInlineSkillContentBuilder.BuildAvailableScriptsBlock(scripts);

// Assert
Assert.Contains("<script name=\"deploy\"/>", block);
Assert.DoesNotContain("description=", block);
}

[Fact]
public void BuildAvailableScriptsBlock_ScriptWithWhitespaceDescription_OmitsDescriptionAttribute()
{
// Arrange
var scripts = new AgentSkillScript[] { new FakeScript("deploy", parametersSchema: null, description: " ") };

// Act
var block = AgentInlineSkillContentBuilder.BuildAvailableScriptsBlock(scripts);

// Assert
Assert.Contains("<script name=\"deploy\"/>", block);
Assert.DoesNotContain("description=", block);
}

[Fact]
public void BuildAvailableScriptsBlock_ScriptWithDescriptionAndSchema_EmitsDescriptionAttribute()
{
// Arrange — a script with both description and schema
var scripts = new AgentSkillScript[] { new FakeScript("search", ParseSchema("{\"type\":\"object\"}"), description: "Search something.") };

// Act
var block = AgentInlineSkillContentBuilder.BuildAvailableScriptsBlock(scripts);

// Assert — description attribute is on the opening tag, schema is nested
Assert.Contains("<script name=\"search\" description=\"Search something.\">", block);
Assert.Contains("<parameters_schema>", block);
}

[Fact]
public void BuildAvailableScriptsBlock_ScriptWithSchema_WrapsSchemaInParametersSchemaElement()
{
Expand Down Expand Up @@ -227,8 +334,8 @@ private sealed class FakeScript : AgentSkillScript
{
private readonly JsonElement? _parametersSchema;

public FakeScript(string name, JsonElement? parametersSchema)
: base(name)
public FakeScript(string name, JsonElement? parametersSchema, string? description = null)
: base(name, description)
{
this._parametersSchema = parametersSchema;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ public async Task Content_IncludesResourcesInBodyAsync()

// Assert — resources are rendered in the body so the model can discover them
Assert.Contains("<available_resources>", content);
Assert.Contains("<resource name=\"config\"/>", content);
Assert.DoesNotContain("A config resource.", content);
Assert.Contains("<resource name=\"config\" description=\"A config resource.\"/>", content);
}

[Fact]
Expand Down Expand Up @@ -466,7 +465,7 @@ public async Task Content_ScriptsAddedAfterCaching_AreNotIncludedAsync()
}

[Fact]
public async Task Content_ScriptWithDescription_DoesNotEmitDescriptionAttributeAsync()
public async Task Content_ScriptWithDescription_EmitsDescriptionAttributeAsync()
{
// Arrange
var skill = new AgentInlineSkill("my-skill", "A valid skill.", "Instructions.");
Expand All @@ -475,9 +474,9 @@ public async Task Content_ScriptWithDescription_DoesNotEmitDescriptionAttributeA
// Act
var content = await skill.GetContentAsync();

// Assert — only the script name is emitted; the description is not rendered as an attribute
// Assert — the script description is emitted as an attribute
Assert.Contains("<script name=\"my-script\"", content);
Assert.DoesNotContain("description=\"Runs something.\"", content);
Assert.Contains("description=\"Runs something.\"", content);
}

[Fact]
Expand All @@ -496,7 +495,7 @@ public async Task Content_ScriptWithoutParametersOrDescription_UsesSelfClosingTa
}

[Fact]
public async Task Content_ResourceWithDescription_RenderedInBodyWithoutDescriptionAsync()
public async Task Content_ResourceWithDescription_RenderedInBodyWithDescriptionAsync()
{
// Arrange
var skill = new AgentInlineSkill("my-skill", "A valid skill.", "Instructions.");
Expand All @@ -506,11 +505,10 @@ public async Task Content_ResourceWithDescription_RenderedInBodyWithoutDescripti
// Act
var content = await skill.GetContentAsync();

// Assert — resources are rendered by name in the body; descriptions are not emitted
// Assert — resources with descriptions include the attribute; those without omit it
Assert.Contains("<available_resources>", content);
Assert.Contains("<resource name=\"with-desc\"/>", content);
Assert.Contains("<resource name=\"with-desc\" description=\"A described resource.\"/>", content);
Assert.Contains("<resource name=\"no-desc\"/>", content);
Assert.DoesNotContain("A described resource.", content);
}

[Fact]
Expand Down
Loading