.NET: Align skill folder discovery with spec#5078
.NET: Align skill folder discovery with spec#5078SergeyMenshykh wants to merge 9 commits intomicrosoft:mainfrom
Conversation
The filtered solution file is generated dynamically by eng/scripts/New-FilteredSolution.ps1 during CI. Checking it in risks it becoming stale and out-of-sync with the real solution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The filtered solution file is generated dynamically by eng/scripts/New-FilteredSolution.ps1 during CI. Checking it in risks it becoming stale and out-of-sync with the real solution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ent-framework into skill-as-class
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the .NET file-based skill discovery implementation to align script/resource folder scanning with the Agent Skills specification, while allowing callers to override the scanned folders via new options.
Changes:
- Added
ScriptFoldersandResourceFolderstoAgentFileSkillsSourceOptionsto control which subfolders are scanned (including support for"."to include skill-root files). - Updated
AgentFileSkillsSourceto scan only the configured folders (non-recursive within each folder) and added validation/filtering for configured folder values. - Updated and expanded unit tests to reflect spec folder names (
scripts/,references/,assets/) and new configuration behaviors.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSourceOptions.cs | Introduces new options for script/resource folder discovery defaults and overrides. |
| dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs | Implements folder-based scanning and folder name validation/filtering for scripts/resources. |
| dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs | Updates resource discovery tests for spec folders and adds tests for folder configuration/validation. |
| dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentFileSkillsSourceScriptTests.cs | Updates script discovery expectations and adds tests for nested/dot-slash script folder configuration. |
| // Reject absolute paths and any path segments that escape upward. | ||
| if (Path.IsPathRooted(folder) || folder.Contains("..", StringComparison.Ordinal)) | ||
| { | ||
| LogFolderNameSkippedInvalid(logger, folder); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
FilterValidFolderNames rejects any folder string containing ".." anywhere, which will also skip legitimate relative folders like "my..scripts" even though they don’t escape upward. Consider validating path traversal by splitting into segments and rejecting only segments equal to ".." (and optionally normalizing separators) so valid names aren’t blocked.
| private static IEnumerable<string> FilterValidFolderNames(IEnumerable<string> folders, ILogger logger) | ||
| { | ||
| foreach (string folder in folders) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(folder)) | ||
| { | ||
| throw new ArgumentException("Folder names must not be null or whitespace.", nameof(folders)); | ||
| } | ||
|
|
||
| // "." is valid — it means the skill root directory. | ||
| if (string.Equals(folder, RootFolderIndicator, StringComparison.Ordinal)) | ||
| { | ||
| yield return folder; | ||
| continue; | ||
| } | ||
|
|
||
| // Reject absolute paths and any path segments that escape upward. | ||
| if (Path.IsPathRooted(folder) || folder.Contains("..", StringComparison.Ordinal)) | ||
| { | ||
| LogFolderNameSkippedInvalid(logger, folder); | ||
| continue; | ||
| } | ||
|
|
||
| yield return folder; | ||
| } |
There was a problem hiding this comment.
Folder values aren’t normalized before Distinct/scanning. If callers pass both "references" and "./references" (or include trailing slashes), the same directory will be scanned multiple times and duplicate resources/scripts with identical names can be produced. Normalize folder strings in FilterValidFolderNames (e.g., trim leading "./"/"." and trailing separators) before storing them.
| foreach (string folder in this._resourceFolders.Distinct(StringComparer.OrdinalIgnoreCase)) | ||
| { | ||
| string fileName = Path.GetFileName(filePath); | ||
| string targetDirectory = string.Equals(folder, RootFolderIndicator, StringComparison.Ordinal) | ||
| ? skillDirectoryFullPath | ||
| : Path.Combine(skillDirectoryFullPath, folder); | ||
|
|
||
| // Exclude SKILL.md itself | ||
| if (string.Equals(fileName, SkillFileName, StringComparison.OrdinalIgnoreCase)) | ||
| if (!Directory.Exists(targetDirectory)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| // Filter by extension | ||
| string extension = Path.GetExtension(filePath); | ||
| if (string.IsNullOrEmpty(extension) || !this._allowedResourceExtensions.Contains(extension)) | ||
| #if NET | ||
| var enumerationOptions = new EnumerationOptions | ||
| { | ||
| RecurseSubdirectories = false, | ||
| IgnoreInaccessible = true, | ||
| AttributesToSkip = FileAttributes.ReparsePoint, | ||
| }; | ||
|
|
||
| foreach (string filePath in Directory.EnumerateFiles(targetDirectory, "*", enumerationOptions)) |
There was a problem hiding this comment.
When targetDirectory itself is a symlink/reparse point (e.g., references/ symlinked outside), the code will still enumerate that external directory and then filter files out via the containment check. That can be abused for unnecessary I/O (DoS) and is avoidable—consider detecting FileAttributes.ReparsePoint on targetDirectory and skipping enumeration of that folder entirely.
| /// <summary> | ||
| /// Gets or sets the sub-folder names to scan for script files, relative to the skill directory. | ||
| /// Use <c>"."</c> to include files directly at the skill root. | ||
| /// When <see langword="null"/>, defaults to <c>scripts</c> (per the | ||
| /// <see href="https://agentskills.io/specification">Agent Skills specification</see>). | ||
| /// When set, replaces the defaults entirely. | ||
| /// </summary> | ||
| public IEnumerable<string>? ScriptFolders { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the sub-folder names to scan for resource files, relative to the skill directory. | ||
| /// Use <c>"."</c> to include files directly at the skill root. | ||
| /// When <see langword="null"/>, defaults to <c>references</c> and <c>assets</c> (per the | ||
| /// <see href="https://agentskills.io/specification">Agent Skills specification</see>). | ||
| /// When set, replaces the defaults entirely. | ||
| /// </summary> |
There was a problem hiding this comment.
The XML docs say “sub-folder names”, but the implementation/tests accept multi-segment relative paths (e.g., "f1/f2/f3", "./scripts/f1") and "." for the root. Please update the documentation to describe these as relative folder paths (and mention normalization expectations) to avoid misleading API consumers.
This PR adds support for discovering skill scripts and resources from folders defined in the agent skill specification, with the ability to specify other folders if needed via options.