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
parent de791fb8a9
commit 26cef555ce
22 changed files with 66 additions and 702 deletions
@@ -501,7 +501,7 @@ public sealed class FileAgentSkillLoaderTests : IDisposable
}
// Manually construct a skill that bypasses discovery validation
var frontmatter = new FileAgentSkillFrontmatter("symlink-read-skill", "A skill");
var frontmatter = new SkillFrontmatter("symlink-read-skill", "A skill");
var skill = new FileAgentSkill(
frontmatter: frontmatter,
body: "See [doc](refs/data.md).",
@@ -532,54 +532,6 @@ public sealed class FileAgentSkillLoaderTests : IDisposable
Assert.Equal("Body content.", skills["bom-skill"].Body);
}
[Theory]
[InlineData("No resource references.", new string[0])]
[InlineData("Review `refs/FAQ.md` for details.", new[] { "refs/FAQ.md" })]
[InlineData("See [guide](refs/guide.md) then run `scripts/run.py`.", new[] { "refs/guide.md", "scripts/run.py" })]
public void DiscoverAndLoadSkills_ResourceReferences_ExtractsExpectedResourceNames(string body, string[] expectedResources)
{
// Arrange — create skill with resource files on disk so validation passes
string skillDir = Path.Combine(this._testRoot, "res-skill");
Directory.CreateDirectory(skillDir);
foreach (string resource in expectedResources)
{
string resourcePath = Path.Combine(skillDir, resource.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(Path.GetDirectoryName(resourcePath)!);
File.WriteAllText(resourcePath, "content");
}
File.WriteAllText(
Path.Combine(skillDir, "SKILL.md"),
$"---\nname: res-skill\ndescription: Resource test\n---\n{body}");
// Act
var skills = this._loader.DiscoverAndLoadSkills(new[] { this._testRoot });
// Assert
Assert.Single(skills);
var skill = skills["res-skill"];
Assert.Equal(expectedResources.Length, skill.ResourceNames.Count);
foreach (string expected in expectedResources)
{
Assert.Contains(expected, skill.ResourceNames);
}
}
[Fact]
public async Task ReadSkillResourceAsync_BacktickResourcePath_ReturnsContentAsync()
{
// Arrange — skill body uses backtick-quoted path
_ = this.CreateSkillDirectoryWithResource("backtick-read", "A skill", "Load `refs/doc.md` first.", "refs/doc.md", "Backtick content.");
var skills = this._loader.DiscoverAndLoadSkills(new[] { this._testRoot });
var skill = skills["backtick-read"];
// Act
string content = await this._loader.ReadSkillResourceAsync(skill, "refs/doc.md");
// Assert
Assert.Equal("Backtick content.", content);
}
private string CreateSkillDirectory(string name, string description, string body)
{
string skillDir = Path.Combine(this._testRoot, name);
@@ -1,170 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
/// <summary>
/// Unit tests for <see cref="FileAgentSkillScriptExecutor"/> and its integration with <see cref="FileAgentSkillsProvider"/>.
/// </summary>
public sealed class FileAgentSkillScriptExecutorTests : IDisposable
{
private readonly string _testRoot;
private readonly TestAIAgent _agent = new();
private static readonly FileAgentSkillScriptExecutionContext s_emptyContext = new(
new Dictionary<string, FileAgentSkill>(StringComparer.OrdinalIgnoreCase),
new FileAgentSkillLoader(NullLogger.Instance));
public FileAgentSkillScriptExecutorTests()
{
this._testRoot = Path.Combine(Path.GetTempPath(), "skill-executor-tests-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(this._testRoot);
}
public void Dispose()
{
if (Directory.Exists(this._testRoot))
{
Directory.Delete(this._testRoot, recursive: true);
}
}
[Fact]
public void HostedCodeInterpreter_ReturnsNonNullInstance()
{
// Act
var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Assert
Assert.NotNull(executor);
}
[Fact]
public void HostedCodeInterpreter_GetExecutionDetails_ReturnsNonNullInstructions()
{
// Arrange
var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Act
var details = executor.GetExecutionDetails(s_emptyContext);
// Assert
Assert.NotNull(details);
Assert.NotNull(details.Instructions);
Assert.NotEmpty(details.Instructions);
}
[Fact]
public void HostedCodeInterpreter_GetExecutionDetails_ReturnsNonEmptyToolsList()
{
// Arrange
var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Act
var details = executor.GetExecutionDetails(s_emptyContext);
// Assert
Assert.NotNull(details);
Assert.NotNull(details.Tools);
Assert.NotEmpty(details.Tools);
}
[Fact]
public async Task Provider_WithExecutor_IncludesExecutorInstructionsInPromptAsync()
{
// Arrange
CreateSkill(this._testRoot, "exec-skill", "Executor test", "Body.");
var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor };
var provider = new FileAgentSkillsProvider(this._testRoot, options);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
// Act
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);
// Assert — executor instructions should be merged into the prompt
Assert.NotNull(result.Instructions);
Assert.Contains("code interpreter", result.Instructions, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task Provider_WithExecutor_IncludesExecutorToolsAsync()
{
// Arrange
CreateSkill(this._testRoot, "tools-exec-skill", "Executor tools test", "Body.");
var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor };
var provider = new FileAgentSkillsProvider(this._testRoot, options);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
// Act
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);
// Assert — should have 3 tools: load_skill, read_skill_resource, and HostedCodeInterpreterTool
Assert.NotNull(result.Tools);
Assert.Equal(3, result.Tools!.Count());
var toolNames = result.Tools!.Select(t => t.Name).ToList();
Assert.Contains("load_skill", toolNames);
Assert.Contains("read_skill_resource", toolNames);
Assert.Single(result.Tools!, t => t is HostedCodeInterpreterTool);
}
[Fact]
public async Task Provider_WithoutExecutor_DoesNotIncludeExecutorToolsAsync()
{
// Arrange
CreateSkill(this._testRoot, "no-exec-skill", "No executor test", "Body.");
var provider = new FileAgentSkillsProvider(this._testRoot);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
// Act
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);
// Assert — should only have the two base tools
Assert.NotNull(result.Tools);
Assert.Equal(2, result.Tools!.Count());
}
[Fact]
public async Task Provider_WithHostedCodeInterpreter_MergesScriptInstructionsIntoPromptAsync()
{
// Arrange
CreateSkill(this._testRoot, "merge-skill", "Merge test", "Body.");
var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor };
var provider = new FileAgentSkillsProvider(this._testRoot, options);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
// Act
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);
// Assert — prompt should contain both the skill listing and the executor's script instructions
Assert.NotNull(result.Instructions);
string instructions = result.Instructions!;
// Skill listing is present
Assert.Contains("merge-skill", instructions);
Assert.Contains("Merge test", instructions);
// Hosted code interpreter script instructions are merged into the prompt
Assert.Contains("executable scripts", instructions);
Assert.Contains("read_skill_resource", instructions);
Assert.Contains("Execute the script using the code interpreter", instructions);
}
private static void CreateSkill(string root, string name, string description, string body)
{
string skillDir = Path.Combine(root, name);
Directory.CreateDirectory(skillDir);
File.WriteAllText(
Path.Combine(skillDir, "SKILL.md"),
$"---\nname: {name}\ndescription: {description}\n---\n{body}");
}
}
@@ -96,7 +96,7 @@ public sealed class FileAgentSkillsProviderTests : IDisposable
this.CreateSkill("custom-prompt-skill", "Custom prompt", "Body.");
var options = new FileAgentSkillsProviderOptions
{
SkillsInstructionPrompt = "Custom template: {skills}"
SkillsInstructionPrompt = "Custom template: {0}"
};
var provider = new FileAgentSkillsProvider(this._testRoot, options);
var inputContext = new AIContext();
@@ -110,6 +110,21 @@ public sealed class FileAgentSkillsProviderTests : IDisposable
Assert.StartsWith("Custom template:", result.Instructions);
}
[Fact]
public void Constructor_InvalidPromptTemplate_ThrowsArgumentException()
{
// Arrange — template with unescaped braces and no valid {0} placeholder
var options = new FileAgentSkillsProviderOptions
{
SkillsInstructionPrompt = "Bad template with {unescaped} braces"
};
// Act & Assert
var ex = Assert.Throws<ArgumentException>(() => new FileAgentSkillsProvider(this._testRoot, options));
Assert.Contains("SkillsInstructionPrompt", ex.Message);
Assert.Equal("options", ex.ParamName);
}
[Fact]
public async Task InvokingCoreAsync_SkillNamesAreXmlEscapedAsync()
{
@@ -1,72 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
/// <summary>
/// Unit tests for <see cref="HostedCodeInterpreterFileAgentSkillScriptExecutor"/>.
/// </summary>
public sealed class HostedCodeInterpreterFileAgentSkillScriptExecutorTests
{
private static readonly FileAgentSkillScriptExecutionContext s_emptyContext = new(
new Dictionary<string, FileAgentSkill>(StringComparer.OrdinalIgnoreCase),
new FileAgentSkillLoader(NullLogger.Instance));
[Fact]
public void GetExecutionDetails_ReturnsScriptExecutionGuidance()
{
// Arrange
var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor();
// Act
var details = executor.GetExecutionDetails(s_emptyContext);
// Assert
Assert.NotNull(details.Instructions);
Assert.Contains("read_skill_resource", details.Instructions);
Assert.Contains("code interpreter", details.Instructions);
}
[Fact]
public void GetExecutionDetails_ReturnsSingleHostedCodeInterpreterTool()
{
// Arrange
var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor();
// Act
var details = executor.GetExecutionDetails(s_emptyContext);
// Assert
Assert.NotNull(details.Tools);
Assert.Single(details.Tools!);
Assert.IsType<HostedCodeInterpreterTool>(details.Tools![0]);
}
[Fact]
public void GetExecutionDetails_ReturnsSameInstanceOnMultipleCalls()
{
// Arrange
var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor();
// Act
var details1 = executor.GetExecutionDetails(s_emptyContext);
var details2 = executor.GetExecutionDetails(s_emptyContext);
// Assert — static details should be reused
Assert.Same(details1, details2);
}
[Fact]
public void FactoryMethod_ReturnsHostedCodeInterpreterFileAgentSkillScriptExecutor()
{
// Act
var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Assert
Assert.IsType<HostedCodeInterpreterFileAgentSkillScriptExecutor>(executor);
}
}