Revert ".NET: Support hosted code interpreter for skill script execution (#4192)" (#4385)

This reverts commit c9cd067be6.
This commit is contained in:
SergeyMenshykh
2026-03-02 12:50:44 +00:00
committed by GitHub
Unverified
parent de791fb8a9
commit 26cef555ce
22 changed files with 66 additions and 702 deletions
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
@@ -15,8 +13,7 @@ namespace Microsoft.Agents.AI;
/// and a markdown body with instructions. Resource files referenced in the body are validated at
/// discovery time and read from disk on demand.
/// </remarks>
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class FileAgentSkill
internal sealed class FileAgentSkill
{
/// <summary>
/// Initializes a new instance of the <see cref="FileAgentSkill"/> class.
@@ -25,8 +22,8 @@ public sealed class FileAgentSkill
/// <param name="body">The SKILL.md content after the closing <c>---</c> delimiter.</param>
/// <param name="sourcePath">Absolute path to the directory containing this skill.</param>
/// <param name="resourceNames">Relative paths of resource files referenced in the skill body.</param>
internal FileAgentSkill(
FileAgentSkillFrontmatter frontmatter,
public FileAgentSkill(
SkillFrontmatter frontmatter,
string body,
string sourcePath,
IReadOnlyList<string>? resourceNames = null)
@@ -40,20 +37,20 @@ public sealed class FileAgentSkill
/// <summary>
/// Gets the parsed YAML frontmatter (name and description).
/// </summary>
public FileAgentSkillFrontmatter Frontmatter { get; }
public SkillFrontmatter Frontmatter { get; }
/// <summary>
/// Gets the SKILL.md body content (without the YAML frontmatter).
/// </summary>
public string Body { get; }
/// <summary>
/// Gets the directory path where the skill was discovered.
/// </summary>
public string SourcePath { get; }
/// <summary>
/// Gets the SKILL.md body content (without the YAML frontmatter).
/// </summary>
internal string Body { get; }
/// <summary>
/// Gets the relative paths of resource files referenced in the skill body (e.g., "references/FAQ.md").
/// </summary>
internal IReadOnlyList<string> ResourceNames { get; }
public IReadOnlyList<string> ResourceNames { get; }
}
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
@@ -10,7 +9,6 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Agents.AI;
@@ -22,8 +20,7 @@ namespace Microsoft.Agents.AI;
/// Each file is validated for YAML frontmatter and resource integrity. Invalid skills are excluded
/// with logged warnings. Resource paths are checked against path traversal and symlink escape attacks.
/// </remarks>
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed partial class FileAgentSkillLoader
internal sealed partial class FileAgentSkillLoader
{
private const string SkillFileName = "SKILL.md";
private const int MaxSearchDepth = 2;
@@ -36,16 +33,13 @@ public sealed partial class FileAgentSkillLoader
// Example: "---\nname: foo\n---\nBody" → Group 1: "name: foo\n"
private static readonly Regex s_frontmatterRegex = new(@"\A\uFEFF?^---\s*$(.+?)^---\s*$", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled, TimeSpan.FromSeconds(5));
// Matches resource file references in skill markdown. Group 1 = relative file path.
// Supports two forms:
// 1. Markdown links: [text](path/file.ext)
// 2. Backtick-quoted paths: `path/file.ext`
// Matches markdown links to local resource files. Group 1 = relative file path.
// Supports optional ./ or ../ prefixes; excludes URLs (no ":" in the path character class).
// Intentionally conservative: only matches paths with word characters, hyphens, dots,
// and forward slashes. Paths with spaces or special characters are not supported.
// Examples: [doc](refs/FAQ.md) → "refs/FAQ.md", `./scripts/run.py` → "./scripts/run.py",
// Examples: [doc](refs/FAQ.md) → "refs/FAQ.md", [s](./s.json) → "./s.json",
// [p](../shared/doc.txt) → "../shared/doc.txt"
private static readonly Regex s_resourceLinkRegex = new(@"(?:\[.*?\]\(|`)(\.?\.?/?[\w][\w\-./]*\.\w+)(?:\)|`)", RegexOptions.Compiled, TimeSpan.FromSeconds(5));
private static readonly Regex s_resourceLinkRegex = new(@"\[.*?\]\((\.?\.?/?[\w][\w\-./]*\.\w+)\)", RegexOptions.Compiled, TimeSpan.FromSeconds(5));
// Matches YAML "key: value" lines. Group 1 = key, Group 2 = quoted value, Group 3 = unquoted value.
// Accepts single or double quotes; the lazy quantifier trims trailing whitespace on unquoted values.
@@ -117,7 +111,7 @@ public sealed partial class FileAgentSkillLoader
/// <exception cref="InvalidOperationException">
/// The resource is not registered, resolves outside the skill directory, or does not exist.
/// </exception>
public async Task<string> ReadSkillResourceAsync(FileAgentSkill skill, string resourceName, CancellationToken cancellationToken = default)
internal async Task<string> ReadSkillResourceAsync(FileAgentSkill skill, string resourceName, CancellationToken cancellationToken = default)
{
resourceName = NormalizeResourcePath(resourceName);
@@ -195,7 +189,7 @@ public sealed partial class FileAgentSkillLoader
string content = File.ReadAllText(skillFilePath, Encoding.UTF8);
if (!this.TryParseSkillDocument(content, skillFilePath, out FileAgentSkillFrontmatter frontmatter, out string body))
if (!this.TryParseSkillDocument(content, skillFilePath, out SkillFrontmatter frontmatter, out string body))
{
return null;
}
@@ -214,7 +208,7 @@ public sealed partial class FileAgentSkillLoader
resourceNames: resourceNames);
}
private bool TryParseSkillDocument(string content, string skillFilePath, out FileAgentSkillFrontmatter frontmatter, out string body)
private bool TryParseSkillDocument(string content, string skillFilePath, out SkillFrontmatter frontmatter, out string body)
{
frontmatter = null!;
body = null!;
@@ -270,7 +264,7 @@ public sealed partial class FileAgentSkillLoader
return false;
}
frontmatter = new FileAgentSkillFrontmatter(name, description);
frontmatter = new SkillFrontmatter(name, description);
body = content.Substring(match.Index + match.Length).TrimStart();
return true;
@@ -1,35 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Agents.AI;
/// <summary>
/// Provides access to loaded skills and the skill loader for use by <see cref="FileAgentSkillScriptExecutor"/> implementations.
/// </summary>
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class FileAgentSkillScriptExecutionContext
{
/// <summary>
/// Initializes a new instance of the <see cref="FileAgentSkillScriptExecutionContext"/> class.
/// </summary>
/// <param name="skills">The loaded skills dictionary.</param>
/// <param name="loader">The skill loader for reading resources.</param>
internal FileAgentSkillScriptExecutionContext(Dictionary<string, FileAgentSkill> skills, FileAgentSkillLoader loader)
{
this.Skills = skills;
this.Loader = loader;
}
/// <summary>
/// Gets the loaded skills keyed by name.
/// </summary>
public IReadOnlyDictionary<string, FileAgentSkill> Skills { get; }
/// <summary>
/// Gets the skill loader for reading resources.
/// </summary>
public FileAgentSkillLoader Loader { get; }
}
@@ -1,25 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.AI;
using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Agents.AI;
/// <summary>
/// Represents the tools and instructions contributed by a <see cref="FileAgentSkillScriptExecutor"/>.
/// </summary>
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class FileAgentSkillScriptExecutionDetails
{
/// <summary>
/// Gets the additional instructions to provide to the agent for script execution.
/// </summary>
public string? Instructions { get; set; }
/// <summary>
/// Gets the additional tools to provide to the agent for script execution.
/// </summary>
public IReadOnlyList<AITool>? Tools { get; set; }
}
@@ -1,42 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Agents.AI;
/// <summary>
/// Defines the contract for skill script execution modes.
/// </summary>
/// <remarks>
/// <para>
/// A <see cref="FileAgentSkillScriptExecutor"/> provides the instructions and tools needed to enable
/// script execution within an agent skill. Concrete implementations determine how scripts
/// are executed (e.g., via the LLM's hosted code interpreter, an external executor, or a hybrid approach).
/// </para>
/// <para>
/// Use the static factory methods to create instances:
/// <list type="bullet">
/// <item><description><see cref="HostedCodeInterpreter"/> — executes scripts using the LLM provider's built-in code interpreter.</description></item>
/// </list>
/// </para>
/// </remarks>
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public abstract class FileAgentSkillScriptExecutor
{
/// <summary>
/// Creates a <see cref="FileAgentSkillScriptExecutor"/> that uses the LLM provider's hosted code interpreter for script execution.
/// </summary>
/// <returns>A <see cref="FileAgentSkillScriptExecutor"/> instance configured for hosted code interpreter execution.</returns>
public static FileAgentSkillScriptExecutor HostedCodeInterpreter() => new HostedCodeInterpreterFileAgentSkillScriptExecutor();
/// <summary>
/// Returns the tools and instructions contributed by this executor.
/// </summary>
/// <param name="context">
/// The execution context provided by the skills provider, containing the loaded skills
/// and the skill loader for reading resources.
/// </param>
/// <returns>A <see cref="FileAgentSkillScriptExecutionDetails"/> containing the executor's tools and instructions.</returns>
protected internal abstract FileAgentSkillScriptExecutionDetails GetExecutionDetails(FileAgentSkillScriptExecutionContext context);
}
@@ -48,21 +48,21 @@ public sealed partial class FileAgentSkillsProvider : AIContextProvider
Each skill provides specialized instructions, reference documents, and assets for specific tasks.
<available_skills>
{skills}
{0}
</available_skills>
When a task aligns with a skill's domain:
- Use `load_skill` to retrieve the skill's instructions
- Follow the provided guidance
- Use `read_skill_resource` to read any references or other files mentioned by the skill, always using the full path as written (e.g. `references/FAQ.md`, not just `FAQ.md`)
{executor_instructions}
1. Use `load_skill` to retrieve the skill's instructions
2. Follow the provided guidance
3. Use `read_skill_resource` to read any references or other files mentioned by the skill
Only load what is needed, when it is needed.
""";
private readonly Dictionary<string, FileAgentSkill> _skills;
private readonly ILogger<FileAgentSkillsProvider> _logger;
private readonly FileAgentSkillLoader _loader;
private readonly IEnumerable<AITool> _tools;
private readonly AITool[] _tools;
private readonly string? _skillsInstructionPrompt;
/// <summary>
@@ -91,13 +91,9 @@ public sealed partial class FileAgentSkillsProvider : AIContextProvider
this._loader = new FileAgentSkillLoader(this._logger);
this._skills = this._loader.DiscoverAndLoadSkills(skillPaths);
var executionDetails = options?.ScriptExecutor is { } executor
? executor.GetExecutionDetails(new(this._skills, this._loader))
: null;
this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills);
this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills, executionDetails?.Instructions);
AITool[] baseTools =
this._tools =
[
AIFunctionFactory.Create(
this.LoadSkill,
@@ -108,10 +104,6 @@ public sealed partial class FileAgentSkillsProvider : AIContextProvider
name: "read_skill_resource",
description: "Reads a file associated with a skill, such as references or assets."),
];
this._tools = executionDetails?.Tools is { Count: > 0 } executorTools
? baseTools.Concat(executorTools)
: baseTools;
}
/// <inheritdoc />
@@ -125,7 +117,7 @@ public sealed partial class FileAgentSkillsProvider : AIContextProvider
return new ValueTask<AIContext>(new AIContext
{
Instructions = this._skillsInstructionPrompt,
Tools = this._tools,
Tools = this._tools
});
}
@@ -174,9 +166,24 @@ public sealed partial class FileAgentSkillsProvider : AIContextProvider
}
}
private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary<string, FileAgentSkill> skills, string? instructions)
private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary<string, FileAgentSkill> skills)
{
string promptTemplate = options?.SkillsInstructionPrompt ?? DefaultSkillsInstructionPrompt;
string promptTemplate = DefaultSkillsInstructionPrompt;
if (options?.SkillsInstructionPrompt is { } optionsInstructions)
{
try
{
promptTemplate = string.Format(optionsInstructions, string.Empty);
}
catch (FormatException ex)
{
throw new ArgumentException(
"The provided SkillsInstructionPrompt is not a valid format string. It must contain a '{0}' placeholder and escape any literal '{' or '}' by doubling them ('{{' or '}}').",
nameof(options),
ex);
}
}
if (skills.Count == 0)
{
@@ -195,9 +202,7 @@ public sealed partial class FileAgentSkillsProvider : AIContextProvider
sb.AppendLine(" </skill>");
}
return promptTemplate
.Replace("{skills}", sb.ToString().TrimEnd())
.Replace("{executor_instructions}", instructions ?? "\n");
return string.Format(promptTemplate, sb.ToString().TrimEnd());
}
[LoggerMessage(LogLevel.Information, "Loading skill: {SkillName}")]
@@ -13,20 +13,8 @@ public sealed class FileAgentSkillsProviderOptions
{
/// <summary>
/// Gets or sets a custom system prompt template for advertising skills.
/// Use <c>{skills}</c> as the placeholder for the generated skills list and
/// <c>{executor_instructions}</c> for executor-provided instructions.
/// Use <c>{0}</c> as the placeholder for the generated skills list.
/// When <see langword="null"/>, a default template is used.
/// </summary>
public string? SkillsInstructionPrompt { get; set; }
/// <summary>
/// Gets or sets the skill executor that enables script execution for loaded skills.
/// </summary>
/// <remarks>
/// When <see langword="null"/> (the default), script execution is disabled and skills only provide
/// instructions and resources. Set this to a <see cref="FileAgentSkillScriptExecutor"/> instance (e.g.,
/// <see cref="FileAgentSkillScriptExecutor.HostedCodeInterpreter()"/>) to enable script execution with
/// mode-specific instructions and tools.
/// </remarks>
public FileAgentSkillScriptExecutor? ScriptExecutor { get; set; }
}
@@ -1,35 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI;
/// <summary>
/// A <see cref="FileAgentSkillScriptExecutor"/> that uses the LLM provider's hosted code interpreter for script execution.
/// </summary>
/// <remarks>
/// This executor directs the LLM to load scripts via <c>read_skill_resource</c> and execute them
/// using the provider's built-in code interpreter. A <see cref="HostedCodeInterpreterTool"/> is
/// registered to signal the provider to enable its code interpreter sandbox.
/// </remarks>
internal sealed class HostedCodeInterpreterFileAgentSkillScriptExecutor : FileAgentSkillScriptExecutor
{
private static readonly FileAgentSkillScriptExecutionDetails s_contribution = new()
{
Instructions =
"""
Some skills include executable scripts (e.g., Python files) in their resources.
When a skill's instructions reference a script:
1. Use `read_skill_resource` to load the script content
2. Execute the script using the code interpreter
""",
Tools = [new HostedCodeInterpreterTool()],
};
/// <inheritdoc />
#pragma warning disable RCS1168 // Parameter name differs from base name
protected internal override FileAgentSkillScriptExecutionDetails GetExecutionDetails(FileAgentSkillScriptExecutionContext _) => s_contribution;
#pragma warning restore RCS1168 // Parameter name differs from base name
}
@@ -1,7 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
@@ -9,15 +7,14 @@ namespace Microsoft.Agents.AI;
/// <summary>
/// Parsed YAML frontmatter from a SKILL.md file, containing the skill's name and description.
/// </summary>
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public sealed class FileAgentSkillFrontmatter
internal sealed class SkillFrontmatter
{
/// <summary>
/// Initializes a new instance of the <see cref="FileAgentSkillFrontmatter"/> class.
/// Initializes a new instance of the <see cref="SkillFrontmatter"/> class.
/// </summary>
/// <param name="name">Skill name.</param>
/// <param name="description">Skill description.</param>
internal FileAgentSkillFrontmatter(string name, string description)
public SkillFrontmatter(string name, string description)
{
this.Name = Throw.IfNullOrWhitespace(name);
this.Description = Throw.IfNullOrWhitespace(description);