From 361fcafd0eb19a98f434c6f9e871264af58fb411 Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Mon, 23 Feb 2026 18:53:03 +0000
Subject: [PATCH 1/9] support script execution by code interpretor
---
dotnet/agent-framework-dotnet.slnx | 1 +
..._ScriptExecutionWithCodeInterpreter.csproj | 28 +++
.../Program.cs | 46 +++++
.../README.md | 72 ++++++++
.../skills/password-generator/SKILL.md | 16 ++
.../references/PASSWORD_GUIDELINES.md | 24 +++
.../password-generator/scripts/generate.py | 11 ++
.../GettingStarted/AgentSkills/README.md | 1 +
.../Skills/FileAgentSkillLoader.cs | 9 +-
.../Skills/FileAgentSkillsProvider.cs | 45 ++---
.../Skills/FileAgentSkillsProviderOptions.cs | 14 +-
.../Skills/HostedCodeInterpreterExecutor.cs | 37 ++++
.../Skills/SkillScriptExecutor.cs | 46 +++++
.../AgentSkills/FileAgentSkillLoaderTests.cs | 63 +++++++
.../FileAgentSkillsProviderTests.cs | 17 +-
.../AgentSkills/SkillScriptExecutorTests.cs | 163 ++++++++++++++++++
16 files changed, 546 insertions(+), 47 deletions(-)
create mode 100644 dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj
create mode 100644 dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
create mode 100644 dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
create mode 100644 dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/SKILL.md
create mode 100644 dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/references/PASSWORD_GUIDELINES.md
create mode 100644 dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/scripts/generate.py
create mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterExecutor.cs
create mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
create mode 100644 dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index adc941d582..5601e198e6 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -99,6 +99,7 @@
+
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj
new file mode 100644
index 0000000000..2a503bbfb2
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+ $(NoWarn);MAAI001
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
new file mode 100644
index 0000000000..c4c3cd43eb
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates how to use Agent Skills with script execution via the hosted code interpreter.
+// When SkillScriptExecutor.HostedCodeInterpreter() is configured, the agent can load and execute scripts
+// from skill resources using the LLM provider's built-in code interpreter.
+//
+// This sample includes the password-generator skill:
+// - A Python script for generating secure passwords
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using OpenAI.Responses;
+
+// --- Configuration ---
+string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
+ ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+// --- Skills Provider with Script Execution ---
+// Discovers skills and enables script execution via the hosted code interpreter
+var skillsProvider = new FileAgentSkillsProvider(
+ skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
+ options: new FileAgentSkillsProviderOptions
+ {
+ Executor = SkillScriptExecutor.HostedCodeInterpreter()
+ });
+
+// --- Agent Setup ---
+AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
+ .GetResponsesClient(deploymentName)
+ .AsAIAgent(new ChatClientAgentOptions
+ {
+ Name = "SkillsAgent",
+ ChatOptions = new()
+ {
+ Instructions = "You are a helpful assistant that can generate secure passwords.",
+ },
+ AIContextProviders = [skillsProvider],
+ });
+
+// --- Example: Password generation with script execution ---
+Console.WriteLine("Example: Generating a password with a skill script");
+Console.WriteLine("---------------------------------------------------");
+AgentResponse response = await agent.RunAsync("Generate a secure password for my database account.");
+Console.WriteLine($"Agent: {response.Text}\n");
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
new file mode 100644
index 0000000000..5295a0b9d7
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
@@ -0,0 +1,72 @@
+# Script Execution with Code Interpreter
+
+This sample demonstrates how to use **Agent Skills** with **script execution** via the hosted code interpreter.
+
+## What's Different from Step01?
+
+In the [basic skills sample](../Agent_Step01_BasicSkills/), skills only provide instructions and resources as text. This sample adds **script execution** — the agent can load Python scripts from skill resources and execute them using the LLM provider's built-in code interpreter.
+
+This is enabled by configuring `SkillScriptExecutor.HostedCodeInterpreter()` on the skills provider options:
+
+```csharp
+var skillsProvider = new FileAgentSkillsProvider(
+ skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
+ options: new FileAgentSkillsProviderOptions
+ {
+ Executor = SkillScriptExecutor.HostedCodeInterpreter()
+ });
+```
+
+## Skills Included
+
+### password-generator
+Generates secure passwords using a Python script with configurable length and complexity.
+- `scripts/generate.py` — Password generation script
+- `references/PASSWORD_GUIDELINES.md` — Recommended length and symbol sets by use case
+
+## Project Structure
+
+```
+Agent_Step02_ScriptExecutionWithCodeInterpreter/
+├── Program.cs
+├── Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj
+└── skills/
+ └── password-generator/
+ ├── SKILL.md
+ ├── scripts/
+ │ └── generate.py
+ └── references/
+ └── PASSWORD_GUIDELINES.md
+```
+
+## Running the Sample
+
+### Prerequisites
+- .NET 10.0 SDK
+- Azure OpenAI endpoint with a deployed model that supports code interpreter
+
+### Setup
+1. Set environment variables:
+ ```bash
+ export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com/"
+ export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
+ ```
+
+2. Run the sample:
+ ```bash
+ dotnet run
+ ```
+
+### Example
+
+The sample asks the agent to generate a secure password. The agent:
+1. Loads the password-generator skill
+2. Reads the `generate.py` script via `read_skill_resource`
+3. Executes the script using the code interpreter with appropriate parameters
+4. Returns the generated password
+
+## Learn More
+
+- [Agent Skills Specification](https://agentskills.io/)
+- [Step01: Basic Skills](../Agent_Step01_BasicSkills/) — Skills without script execution
+- [Microsoft Agent Framework Documentation](../../../../../docs/)
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/SKILL.md b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/SKILL.md
new file mode 100644
index 0000000000..c3ef67401b
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/SKILL.md
@@ -0,0 +1,16 @@
+---
+name: password-generator
+description: Generate secure passwords using a Python script. Use when asked to create passwords or credentials.
+---
+
+# Password Generator
+
+This skill generates secure passwords using a Python script.
+
+## Usage
+
+When the user requests a password:
+1. First, review `references/PASSWORD_GUIDELINES.md` to determine the recommended password length and character sets for the user's use case
+2. Load `scripts/generate.py` and adjust its parameters (length, character set) based on the guidelines and user's requirements
+3. Execute the script
+4. Present the generated password clearly
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/references/PASSWORD_GUIDELINES.md b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/references/PASSWORD_GUIDELINES.md
new file mode 100644
index 0000000000..be9145a4dd
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/references/PASSWORD_GUIDELINES.md
@@ -0,0 +1,24 @@
+# Password Generation Guidelines
+
+## General Rules
+
+- Never reuse passwords across services.
+- Always use cryptographically secure randomness (e.g., `random.SystemRandom()`).
+- Avoid dictionary words, keyboard patterns, and personal information.
+
+## Recommended Settings by Use Case
+
+| Use Case | Min Length | Character Set | Example |
+|-----------------------|-----------|----------------------------------------|--------------------------|
+| Web account | 16 | Upper + lower + digits + symbols | `G7!kQp@2xM#nW9$z` |
+| Database credential | 24 | Upper + lower + digits + symbols | `aR3$vK8!mN2@pQ7&xL5#wY` |
+| Wi-Fi / network key | 20 | Upper + lower + digits + symbols | `Ht4&jL9!rP2#mK7@xQ` |
+| API key / token | 32 | Upper + lower + digits (no symbols) | `k8Rm3xQ7nW2pL9vT4jH6yA` |
+| Encryption passphrase | 32 | Upper + lower + digits + symbols | `Xp4!kR8@mN2#vQ7&jL9$wT` |
+
+## Symbol Sets
+
+- **Standard symbols**: `!@#$%^&*()-_=+`
+- **Extended symbols**: `~`{}[]|;:'",.<>?/\`
+- **Safe symbols** (URL/shell-safe): `!@#$&*-_=+`
+- If the target system restricts symbols, use only the **safe** set.
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/scripts/generate.py b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/scripts/generate.py
new file mode 100644
index 0000000000..b44f3d9731
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/scripts/generate.py
@@ -0,0 +1,11 @@
+# Password generator script
+# Usage: Adjust 'length' as needed, then run
+
+import random
+import string
+
+length = 16 # desired length
+
+pool = string.ascii_lowercase + string.ascii_uppercase + string.digits + string.punctuation
+password = "".join(random.SystemRandom().choice(pool) for _ in range(length))
+print(f"Generated password ({length} chars): {password}")
diff --git a/dotnet/samples/GettingStarted/AgentSkills/README.md b/dotnet/samples/GettingStarted/AgentSkills/README.md
index 8488ec9eed..477a738fb8 100644
--- a/dotnet/samples/GettingStarted/AgentSkills/README.md
+++ b/dotnet/samples/GettingStarted/AgentSkills/README.md
@@ -5,3 +5,4 @@ Samples demonstrating Agent Skills capabilities.
| Sample | Description |
|--------|-------------|
| [Agent_Step01_BasicSkills](Agent_Step01_BasicSkills/) | Using Agent Skills with a ChatClientAgent, including progressive disclosure and skill resources |
+| [Agent_Step02_ScriptExecutionWithCodeInterpreter](Agent_Step02_ScriptExecutionWithCodeInterpreter/) | Using Agent Skills with script execution via the hosted code interpreter |
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs
index 8c034b3122..c6be21e9d2 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs
@@ -33,13 +33,16 @@ internal 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 markdown links to local resource files. Group 1 = relative file path.
+ // 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`
// 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", [s](./s.json) → "./s.json",
+ // Examples: [doc](refs/FAQ.md) → "refs/FAQ.md", `./scripts/run.py` → "./scripts/run.py",
// [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.
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
index 847bf36a52..b13916c1d2 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
@@ -48,21 +48,21 @@ You have access to skills containing domain-specific knowledge and capabilities.
Each skill provides specialized instructions, reference documents, and assets for specific tasks.
- {0}
+ {skills}
When a task aligns with a skill's domain:
- 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
-
+ - 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
+ {executor_instructions}
Only load what is needed, when it is needed.
""";
private readonly Dictionary _skills;
private readonly ILogger _logger;
private readonly FileAgentSkillLoader _loader;
- private readonly AITool[] _tools;
+ private readonly IEnumerable _tools;
private readonly string? _skillsInstructionPrompt;
///
@@ -91,9 +91,9 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr
this._loader = new FileAgentSkillLoader(this._logger);
this._skills = this._loader.DiscoverAndLoadSkills(skillPaths);
- this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills);
+ this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills, options?.Executor);
- this._tools =
+ AITool[] baseTools =
[
AIFunctionFactory.Create(
this.LoadSkill,
@@ -104,6 +104,10 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr
name: "read_skill_resource",
description: "Reads a file associated with a skill, such as references or assets."),
];
+
+ this._tools = options?.Executor?.GetTools() is { Count: > 0 } executorTools
+ ? baseTools.Concat(executorTools)
+ : baseTools;
}
///
@@ -117,7 +121,7 @@ protected override ValueTask ProvideAIContextAsync(InvokingContext co
return new ValueTask(new AIContext
{
Instructions = this._skillsInstructionPrompt,
- Tools = this._tools
+ Tools = this._tools,
});
}
@@ -166,24 +170,9 @@ private async Task ReadSkillResourceAsync(string skillName, string resou
}
}
- private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary skills)
+ private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary skills, SkillScriptExecutor? executor)
{
- 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);
- }
- }
+ string promptTemplate = options?.SkillsInstructionPrompt ?? DefaultSkillsInstructionPrompt;
if (skills.Count == 0)
{
@@ -202,7 +191,9 @@ private async Task ReadSkillResourceAsync(string skillName, string resou
sb.AppendLine(" ");
}
- return string.Format(promptTemplate, sb.ToString().TrimEnd());
+ return promptTemplate
+ .Replace("{skills}", sb.ToString().TrimEnd())
+ .Replace("{executor_instructions}", executor?.GetInstructions() ?? "\n");
}
[LoggerMessage(LogLevel.Information, "Loading skill: {SkillName}")]
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
index a47841c260..dbfcb851a4 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
@@ -13,8 +13,20 @@ public sealed class FileAgentSkillsProviderOptions
{
///
/// Gets or sets a custom system prompt template for advertising skills.
- /// Use {0} as the placeholder for the generated skills list.
+ /// Use {skills} as the placeholder for the generated skills list and
+ /// {executor_instructions} for executor-provided instructions.
/// When , a default template is used.
///
public string? SkillsInstructionPrompt { get; set; }
+
+ ///
+ /// Gets or sets the skill executor that enables script execution for loaded skills.
+ ///
+ ///
+ /// When (the default), script execution is disabled and skills only provide
+ /// instructions and resources. Set this to a instance (e.g.,
+ /// ) to enable script execution with
+ /// mode-specific instructions and tools.
+ ///
+ public SkillScriptExecutor? Executor { get; set; }
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterExecutor.cs
new file mode 100644
index 0000000000..0f0af6317f
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterExecutor.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// A that uses the LLM provider's hosted code interpreter for script execution.
+///
+///
+/// This executor directs the LLM to load scripts via read_skill_resource and execute them
+/// using the provider's built-in code interpreter. A is
+/// registered to signal the provider to enable its code interpreter sandbox.
+///
+internal sealed class HostedCodeInterpreterExecutor : SkillScriptExecutor
+{
+ // Leading and trailing blank lines are intentional to logically separate this content
+ // from the surrounding text when merged into the FileAgentSkillsProvider instructions template.
+ private const string 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
+
+ """;
+
+ private static readonly AITool[] s_tools = [new HostedCodeInterpreterTool()];
+
+ ///
+ public override string GetInstructions() => Instructions;
+
+ ///
+ public override IReadOnlyList GetTools() => s_tools;
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
new file mode 100644
index 0000000000..5cdd6ec508
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
@@ -0,0 +1,46 @@
+// 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;
+
+///
+/// Defines the contract for skill script execution modes.
+///
+///
+///
+/// A 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).
+///
+///
+/// Use the static factory methods to create instances:
+///
+/// - — executes scripts using the LLM provider's built-in code interpreter.
+///
+///
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public abstract class SkillScriptExecutor
+{
+ ///
+ /// Creates a that uses the LLM provider's hosted code interpreter for script execution.
+ ///
+ /// A instance configured for hosted code interpreter execution.
+ public static SkillScriptExecutor HostedCodeInterpreter() => new HostedCodeInterpreterExecutor();
+
+ ///
+ /// Gets the additional instructions to provide to the agent for script execution.
+ ///
+ /// Instructions string, or if no additional instructions are needed.
+ public abstract string? GetInstructions();
+
+ ///
+ /// Gets the additional tools to provide to the agent for script execution.
+ ///
+ /// A read-only list of tools, or if no additional tools are needed.
+ public abstract IReadOnlyList? GetTools();
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
index c34eb6d7f2..2dbd0a5ce9 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
@@ -532,6 +532,69 @@ public void DiscoverAndLoadSkills_FileWithUtf8Bom_ParsesSuccessfully()
Assert.Equal("Body content.", skills["bom-skill"].Body);
}
+ [Fact]
+ public void DiscoverAndLoadSkills_BacktickResourcePath_ExtractsResourceNames()
+ {
+ // Arrange — body references a resource using backtick-quoted path instead of a markdown link
+ string skillDir = Path.Combine(this._testRoot, "backtick-skill");
+ string refsDir = Path.Combine(skillDir, "refs");
+ Directory.CreateDirectory(refsDir);
+ File.WriteAllText(Path.Combine(refsDir, "FAQ.md"), "FAQ content");
+ File.WriteAllText(
+ Path.Combine(skillDir, "SKILL.md"),
+ "---\nname: backtick-skill\ndescription: Has backtick resources\n---\nReview `refs/FAQ.md` for details.");
+
+ // Act
+ var skills = this._loader.DiscoverAndLoadSkills(new[] { this._testRoot });
+
+ // Assert
+ Assert.Single(skills);
+ var skill = skills["backtick-skill"];
+ Assert.Single(skill.ResourceNames);
+ Assert.Equal("refs/FAQ.md", skill.ResourceNames[0]);
+ }
+
+ [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);
+ }
+
+ [Fact]
+ public void DiscoverAndLoadSkills_MixedBacktickAndMarkdownLinks_ExtractsBothResources()
+ {
+ // Arrange — body uses both markdown link and backtick-quoted path
+ string skillDir = Path.Combine(this._testRoot, "mixed-ref-skill");
+ string refsDir = Path.Combine(skillDir, "refs");
+ string scriptsDir = Path.Combine(skillDir, "scripts");
+ Directory.CreateDirectory(refsDir);
+ Directory.CreateDirectory(scriptsDir);
+ File.WriteAllText(Path.Combine(refsDir, "guide.md"), "guide");
+ File.WriteAllText(Path.Combine(scriptsDir, "run.py"), "print('hi')");
+ File.WriteAllText(
+ Path.Combine(skillDir, "SKILL.md"),
+ "---\nname: mixed-ref-skill\ndescription: Mixed references\n---\nSee [guide](refs/guide.md) then run `scripts/run.py`.");
+
+ // Act
+ var skills = this._loader.DiscoverAndLoadSkills(new[] { this._testRoot });
+
+ // Assert
+ Assert.Single(skills);
+ var skill = skills["mixed-ref-skill"];
+ Assert.Equal(2, skill.ResourceNames.Count);
+ Assert.Contains("refs/guide.md", skill.ResourceNames);
+ Assert.Contains("scripts/run.py", skill.ResourceNames);
+ }
+
private string CreateSkillDirectory(string name, string description, string body)
{
string skillDir = Path.Combine(this._testRoot, name);
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillsProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillsProviderTests.cs
index 6bfaf1b546..f95f3a7080 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillsProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillsProviderTests.cs
@@ -96,7 +96,7 @@ public async Task InvokingCoreAsync_CustomPromptTemplate_UsesCustomTemplateAsync
this.CreateSkill("custom-prompt-skill", "Custom prompt", "Body.");
var options = new FileAgentSkillsProviderOptions
{
- SkillsInstructionPrompt = "Custom template: {0}"
+ SkillsInstructionPrompt = "Custom template: {skills}"
};
var provider = new FileAgentSkillsProvider(this._testRoot, options);
var inputContext = new AIContext();
@@ -110,21 +110,6 @@ public async Task InvokingCoreAsync_CustomPromptTemplate_UsesCustomTemplateAsync
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(() => new FileAgentSkillsProvider(this._testRoot, options));
- Assert.Contains("SkillsInstructionPrompt", ex.Message);
- Assert.Equal("options", ex.ParamName);
- }
-
[Fact]
public async Task InvokingCoreAsync_SkillNamesAreXmlEscapedAsync()
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
new file mode 100644
index 0000000000..ca3cfac9f1
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
@@ -0,0 +1,163 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
+
+///
+/// Unit tests for and its integration with .
+///
+public sealed class SkillScriptExecutorTests : IDisposable
+{
+ private readonly string _testRoot;
+ private readonly TestAIAgent _agent = new();
+
+ public SkillScriptExecutorTests()
+ {
+ 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 = SkillScriptExecutor.HostedCodeInterpreter();
+
+ // Assert
+ Assert.NotNull(executor);
+ }
+
+ [Fact]
+ public void HostedCodeInterpreter_GetInstructions_ReturnsNonNullString()
+ {
+ // Arrange
+ var executor = SkillScriptExecutor.HostedCodeInterpreter();
+
+ // Act
+ string? instructions = executor.GetInstructions();
+
+ // Assert
+ Assert.NotNull(instructions);
+ Assert.NotEmpty(instructions);
+ }
+
+ [Fact]
+ public void HostedCodeInterpreter_GetTools_ReturnsNonEmptyList()
+ {
+ // Arrange
+ var executor = SkillScriptExecutor.HostedCodeInterpreter();
+
+ // Act
+ var tools = executor.GetTools();
+
+ // Assert
+ Assert.NotNull(tools);
+ Assert.NotEmpty(tools);
+ }
+
+ [Fact]
+ public async Task Provider_WithExecutor_IncludesExecutorInstructionsInPromptAsync()
+ {
+ // Arrange
+ CreateSkill(this._testRoot, "exec-skill", "Executor test", "Body.");
+ var executor = SkillScriptExecutor.HostedCodeInterpreter();
+ var options = new FileAgentSkillsProviderOptions { Executor = 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 = SkillScriptExecutor.HostedCodeInterpreter();
+ var options = new FileAgentSkillsProviderOptions { Executor = 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 = SkillScriptExecutor.HostedCodeInterpreter();
+ var options = new FileAgentSkillsProviderOptions { Executor = 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}");
+ }
+}
From fe25aeff7df8d3395efe79f97c3a20ea7eadb06f Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Mon, 23 Feb 2026 19:59:08 +0000
Subject: [PATCH 2/9] improve the instruction prompt
---
.../src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
index b13916c1d2..8c2998ce8e 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
@@ -54,7 +54,7 @@ You have access to skills containing domain-specific knowledge and capabilities.
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
+ - 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}
Only load what is needed, when it is needed.
""";
From c67746f9615c5a71bf7e81d571e13a567695bad7 Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Mon, 23 Feb 2026 20:13:49 +0000
Subject: [PATCH 3/9] Add DefaultAzureCredential production warning to
AgentSkills samples
Add the standard three-line WARNING comment about DefaultAzureCredential
production considerations to both AgentSkills sample Program.cs files,
matching the convention used in all other GettingStarted/Agents samples.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../AgentSkills/Agent_Step01_BasicSkills/Program.cs | 3 +++
.../Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs | 3 +++
2 files changed, 6 insertions(+)
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step01_BasicSkills/Program.cs b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step01_BasicSkills/Program.cs
index 290c3f9b6b..eef57e840a 100644
--- a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step01_BasicSkills/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step01_BasicSkills/Program.cs
@@ -22,6 +22,9 @@
var skillsProvider = new FileAgentSkillsProvider(skillPath: Path.Combine(AppContext.BaseDirectory, "skills"));
// --- Agent Setup ---
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.AsAIAgent(new ChatClientAgentOptions
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
index c4c3cd43eb..30e347a9d3 100644
--- a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
@@ -27,6 +27,9 @@
});
// --- Agent Setup ---
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.AsAIAgent(new ChatClientAgentOptions
From fad82f1ae67f81e906c170bae11a4cf0722c5053 Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Tue, 24 Feb 2026 11:30:23 +0000
Subject: [PATCH 4/9] address pr review comments
---
.../Skills/FileAgentSkillsProvider.cs | 4 +-
...stedCodeInterpreterSkillScriptExecutor.cs} | 16 ++---
.../Skills/SkillScriptExecutor.cs | 8 +--
...CodeInterpreterSkillScriptExecutorTests.cs | 65 +++++++++++++++++++
.../AgentSkills/SkillScriptExecutorTests.cs | 4 +-
5 files changed, 78 insertions(+), 19 deletions(-)
rename dotnet/src/Microsoft.Agents.AI/Skills/{HostedCodeInterpreterExecutor.cs => HostedCodeInterpreterSkillScriptExecutor.cs} (70%)
create mode 100644 dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
index 8c2998ce8e..1fc292ef00 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
@@ -105,7 +105,7 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr
description: "Reads a file associated with a skill, such as references or assets."),
];
- this._tools = options?.Executor?.GetTools() is { Count: > 0 } executorTools
+ this._tools = options?.Executor?.Tools is { Count: > 0 } executorTools
? baseTools.Concat(executorTools)
: baseTools;
}
@@ -193,7 +193,7 @@ private async Task ReadSkillResourceAsync(string skillName, string resou
return promptTemplate
.Replace("{skills}", sb.ToString().TrimEnd())
- .Replace("{executor_instructions}", executor?.GetInstructions() ?? "\n");
+ .Replace("{executor_instructions}", executor?.Instructions ?? "\n");
}
[LoggerMessage(LogLevel.Information, "Loading skill: {SkillName}")]
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs
similarity index 70%
rename from dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterExecutor.cs
rename to dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs
index 0f0af6317f..f15092c467 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs
@@ -13,11 +13,12 @@ namespace Microsoft.Agents.AI;
/// using the provider's built-in code interpreter. A is
/// registered to signal the provider to enable its code interpreter sandbox.
///
-internal sealed class HostedCodeInterpreterExecutor : SkillScriptExecutor
+internal sealed class HostedCodeInterpreterSkillScriptExecutor : SkillScriptExecutor
{
- // Leading and trailing blank lines are intentional to logically separate this content
- // from the surrounding text when merged into the FileAgentSkillsProvider instructions template.
- private const string Instructions =
+ private static readonly AITool[] s_tools = [new HostedCodeInterpreterTool()];
+
+ ///
+ public override string Instructions { get; } =
"""
Some skills include executable scripts (e.g., Python files) in their resources.
@@ -27,11 +28,6 @@ 1. Use `read_skill_resource` to load the script content
""";
- private static readonly AITool[] s_tools = [new HostedCodeInterpreterTool()];
-
- ///
- public override string GetInstructions() => Instructions;
-
///
- public override IReadOnlyList GetTools() => s_tools;
+ public override IReadOnlyList Tools => s_tools;
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
index 5cdd6ec508..5ba40432e6 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
@@ -30,17 +30,15 @@ public abstract class SkillScriptExecutor
/// Creates a that uses the LLM provider's hosted code interpreter for script execution.
///
/// A instance configured for hosted code interpreter execution.
- public static SkillScriptExecutor HostedCodeInterpreter() => new HostedCodeInterpreterExecutor();
+ public static SkillScriptExecutor HostedCodeInterpreter() => new HostedCodeInterpreterSkillScriptExecutor();
///
/// Gets the additional instructions to provide to the agent for script execution.
///
- /// Instructions string, or if no additional instructions are needed.
- public abstract string? GetInstructions();
+ public abstract string? Instructions { get; }
///
/// Gets the additional tools to provide to the agent for script execution.
///
- /// A read-only list of tools, or if no additional tools are needed.
- public abstract IReadOnlyList? GetTools();
+ public abstract IReadOnlyList? Tools { get; }
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs
new file mode 100644
index 0000000000..3ef58624d0
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
+
+///
+/// Unit tests for .
+///
+public sealed class HostedCodeInterpreterSkillScriptExecutorTests
+{
+ [Fact]
+ public void GetInstructions_ReturnsScriptExecutionGuidance()
+ {
+ // Arrange
+ var executor = new HostedCodeInterpreterSkillScriptExecutor();
+
+ // Act
+ string? instructions = executor.Instructions;
+
+ // Assert
+ Assert.NotNull(instructions);
+ Assert.Contains("read_skill_resource", instructions);
+ Assert.Contains("code interpreter", instructions);
+ }
+
+ [Fact]
+ public void GetTools_ReturnsSingleHostedCodeInterpreterTool()
+ {
+ // Arrange
+ var executor = new HostedCodeInterpreterSkillScriptExecutor();
+
+ // Act
+ var tools = executor.Tools;
+
+ // Assert
+ Assert.NotNull(tools);
+ Assert.Single(tools!);
+ Assert.IsType(tools![0]);
+ }
+
+ [Fact]
+ public void GetTools_ReturnsSameInstanceOnMultipleCalls()
+ {
+ // Arrange
+ var executor = new HostedCodeInterpreterSkillScriptExecutor();
+
+ // Act
+ var tools1 = executor.Tools;
+ var tools2 = executor.Tools;
+
+ // Assert — static tools array should be reused
+ Assert.Same(tools1, tools2);
+ }
+
+ [Fact]
+ public void FactoryMethod_ReturnsHostedCodeInterpreterSkillScriptExecutor()
+ {
+ // Act
+ var executor = SkillScriptExecutor.HostedCodeInterpreter();
+
+ // Assert
+ Assert.IsType(executor);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
index ca3cfac9f1..8455d49310 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
@@ -48,7 +48,7 @@ public void HostedCodeInterpreter_GetInstructions_ReturnsNonNullString()
var executor = SkillScriptExecutor.HostedCodeInterpreter();
// Act
- string? instructions = executor.GetInstructions();
+ string? instructions = executor.Instructions;
// Assert
Assert.NotNull(instructions);
@@ -62,7 +62,7 @@ public void HostedCodeInterpreter_GetTools_ReturnsNonEmptyList()
var executor = SkillScriptExecutor.HostedCodeInterpreter();
// Act
- var tools = executor.GetTools();
+ var tools = executor.Tools;
// Assert
Assert.NotNull(tools);
From eb16b1ab1c6a495683bc7bbcfc2c334de78b7d36 Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Wed, 25 Feb 2026 12:38:39 +0000
Subject: [PATCH 5/9] address feedback
---
.../Program.cs | 2 +-
.../README.md | 2 +-
.../Skills/FileAgentSkillsProvider.cs | 4 +-
.../Skills/FileAgentSkillsProviderOptions.cs | 2 +-
.../AgentSkills/FileAgentSkillLoaderTests.cs | 59 +++++++------------
.../AgentSkills/SkillScriptExecutorTests.cs | 6 +-
6 files changed, 30 insertions(+), 45 deletions(-)
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
index 30e347a9d3..b931acd789 100644
--- a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
@@ -23,7 +23,7 @@
skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
options: new FileAgentSkillsProviderOptions
{
- Executor = SkillScriptExecutor.HostedCodeInterpreter()
+ ScriptExecutor = SkillScriptExecutor.HostedCodeInterpreter()
});
// --- Agent Setup ---
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
index 5295a0b9d7..0f5d31f35f 100644
--- a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
@@ -13,7 +13,7 @@ var skillsProvider = new FileAgentSkillsProvider(
skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
options: new FileAgentSkillsProviderOptions
{
- Executor = SkillScriptExecutor.HostedCodeInterpreter()
+ ScriptExecutor = SkillScriptExecutor.HostedCodeInterpreter()
});
```
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
index 1fc292ef00..010be474e9 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
@@ -91,7 +91,7 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr
this._loader = new FileAgentSkillLoader(this._logger);
this._skills = this._loader.DiscoverAndLoadSkills(skillPaths);
- this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills, options?.Executor);
+ this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills, options?.ScriptExecutor);
AITool[] baseTools =
[
@@ -105,7 +105,7 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr
description: "Reads a file associated with a skill, such as references or assets."),
];
- this._tools = options?.Executor?.Tools is { Count: > 0 } executorTools
+ this._tools = options?.ScriptExecutor?.Tools is { Count: > 0 } executorTools
? baseTools.Concat(executorTools)
: baseTools;
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
index dbfcb851a4..1e034f9c52 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
@@ -28,5 +28,5 @@ public sealed class FileAgentSkillsProviderOptions
/// ) to enable script execution with
/// mode-specific instructions and tools.
///
- public SkillScriptExecutor? Executor { get; set; }
+ public SkillScriptExecutor? ScriptExecutor { get; set; }
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
index 2dbd0a5ce9..49bf730db3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
@@ -532,26 +532,37 @@ public void DiscoverAndLoadSkills_FileWithUtf8Bom_ParsesSuccessfully()
Assert.Equal("Body content.", skills["bom-skill"].Body);
}
- [Fact]
- public void DiscoverAndLoadSkills_BacktickResourcePath_ExtractsResourceNames()
+ [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 — body references a resource using backtick-quoted path instead of a markdown link
- string skillDir = Path.Combine(this._testRoot, "backtick-skill");
- string refsDir = Path.Combine(skillDir, "refs");
- Directory.CreateDirectory(refsDir);
- File.WriteAllText(Path.Combine(refsDir, "FAQ.md"), "FAQ content");
+ // 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: backtick-skill\ndescription: Has backtick resources\n---\nReview `refs/FAQ.md` for details.");
+ $"---\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["backtick-skill"];
- Assert.Single(skill.ResourceNames);
- Assert.Equal("refs/FAQ.md", skill.ResourceNames[0]);
+ var skill = skills["res-skill"];
+ Assert.Equal(expectedResources.Length, skill.ResourceNames.Count);
+ foreach (string expected in expectedResources)
+ {
+ Assert.Contains(expected, skill.ResourceNames);
+ }
}
[Fact]
@@ -569,32 +580,6 @@ public async Task ReadSkillResourceAsync_BacktickResourcePath_ReturnsContentAsyn
Assert.Equal("Backtick content.", content);
}
- [Fact]
- public void DiscoverAndLoadSkills_MixedBacktickAndMarkdownLinks_ExtractsBothResources()
- {
- // Arrange — body uses both markdown link and backtick-quoted path
- string skillDir = Path.Combine(this._testRoot, "mixed-ref-skill");
- string refsDir = Path.Combine(skillDir, "refs");
- string scriptsDir = Path.Combine(skillDir, "scripts");
- Directory.CreateDirectory(refsDir);
- Directory.CreateDirectory(scriptsDir);
- File.WriteAllText(Path.Combine(refsDir, "guide.md"), "guide");
- File.WriteAllText(Path.Combine(scriptsDir, "run.py"), "print('hi')");
- File.WriteAllText(
- Path.Combine(skillDir, "SKILL.md"),
- "---\nname: mixed-ref-skill\ndescription: Mixed references\n---\nSee [guide](refs/guide.md) then run `scripts/run.py`.");
-
- // Act
- var skills = this._loader.DiscoverAndLoadSkills(new[] { this._testRoot });
-
- // Assert
- Assert.Single(skills);
- var skill = skills["mixed-ref-skill"];
- Assert.Equal(2, skill.ResourceNames.Count);
- Assert.Contains("refs/guide.md", skill.ResourceNames);
- Assert.Contains("scripts/run.py", skill.ResourceNames);
- }
-
private string CreateSkillDirectory(string name, string description, string body)
{
string skillDir = Path.Combine(this._testRoot, name);
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
index 8455d49310..9e57b5b93e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
@@ -75,7 +75,7 @@ public async Task Provider_WithExecutor_IncludesExecutorInstructionsInPromptAsyn
// Arrange
CreateSkill(this._testRoot, "exec-skill", "Executor test", "Body.");
var executor = SkillScriptExecutor.HostedCodeInterpreter();
- var options = new FileAgentSkillsProviderOptions { Executor = executor };
+ var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor };
var provider = new FileAgentSkillsProvider(this._testRoot, options);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
@@ -93,7 +93,7 @@ public async Task Provider_WithExecutor_IncludesExecutorToolsAsync()
// Arrange
CreateSkill(this._testRoot, "tools-exec-skill", "Executor tools test", "Body.");
var executor = SkillScriptExecutor.HostedCodeInterpreter();
- var options = new FileAgentSkillsProviderOptions { Executor = executor };
+ var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor };
var provider = new FileAgentSkillsProvider(this._testRoot, options);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
@@ -131,7 +131,7 @@ public async Task Provider_WithHostedCodeInterpreter_MergesScriptInstructionsInt
// Arrange
CreateSkill(this._testRoot, "merge-skill", "Merge test", "Body.");
var executor = SkillScriptExecutor.HostedCodeInterpreter();
- var options = new FileAgentSkillsProviderOptions { Executor = executor };
+ var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor };
var provider = new FileAgentSkillsProvider(this._testRoot, options);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
From ac61a4629188ff0ad093ac8de49d84819dc3875a Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Wed, 25 Feb 2026 15:18:16 +0000
Subject: [PATCH 6/9] rename Skill* types to FileAgentSkill* prefix for
consistency
- Rename SkillFrontmatter -> FileAgentSkillFrontmatter
- Rename SkillScriptExecutor -> FileAgentSkillScriptExecutor
- Add FileAgentSkillScriptExecutionContext and FileAgentSkillScriptExecutionDetails
- Update sample, provider, loader, and tests accordingly
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Program.cs | 4 +-
.../README.md | 4 +-
.../Skills/FileAgentSkill.cs | 21 +++++----
...matter.cs => FileAgentSkillFrontmatter.cs} | 9 ++--
.../Skills/FileAgentSkillLoader.cs | 13 +++---
.../FileAgentSkillScriptExecutionContext.cs | 35 +++++++++++++++
.../FileAgentSkillScriptExecutionDetails.cs | 25 +++++++++++
.../Skills/FileAgentSkillScriptExecutor.cs | 42 ++++++++++++++++++
.../Skills/FileAgentSkillsProvider.cs | 12 +++--
.../Skills/FileAgentSkillsProviderOptions.cs | 6 +--
...ostedCodeInterpreterSkillScriptExecutor.cs | 30 +++++++------
.../Skills/SkillScriptExecutor.cs | 44 -------------------
.../AgentSkills/FileAgentSkillLoaderTests.cs | 2 +-
...s => FileAgentSkillScriptExecutorTests.cs} | 41 ++++++++++-------
...CodeInterpreterSkillScriptExecutorTests.cs | 39 +++++++++-------
15 files changed, 207 insertions(+), 120 deletions(-)
rename dotnet/src/Microsoft.Agents.AI/Skills/{SkillFrontmatter.cs => FileAgentSkillFrontmatter.cs} (70%)
create mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionContext.cs
create mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs
create mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs
delete mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
rename dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/{SkillScriptExecutorTests.cs => FileAgentSkillScriptExecutorTests.cs} (76%)
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
index b931acd789..2835ec70ab 100644
--- a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use Agent Skills with script execution via the hosted code interpreter.
-// When SkillScriptExecutor.HostedCodeInterpreter() is configured, the agent can load and execute scripts
+// When FileAgentSkillScriptExecutor.HostedCodeInterpreter() is configured, the agent can load and execute scripts
// from skill resources using the LLM provider's built-in code interpreter.
//
// This sample includes the password-generator skill:
@@ -23,7 +23,7 @@
skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
options: new FileAgentSkillsProviderOptions
{
- ScriptExecutor = SkillScriptExecutor.HostedCodeInterpreter()
+ ScriptExecutor = FileAgentSkillScriptExecutor.HostedCodeInterpreter()
});
// --- Agent Setup ---
diff --git a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
index 0f5d31f35f..f5bf63c44a 100644
--- a/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
+++ b/dotnet/samples/GettingStarted/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md
@@ -6,14 +6,14 @@ This sample demonstrates how to use **Agent Skills** with **script execution** v
In the [basic skills sample](../Agent_Step01_BasicSkills/), skills only provide instructions and resources as text. This sample adds **script execution** — the agent can load Python scripts from skill resources and execute them using the LLM provider's built-in code interpreter.
-This is enabled by configuring `SkillScriptExecutor.HostedCodeInterpreter()` on the skills provider options:
+This is enabled by configuring `FileAgentSkillScriptExecutor.HostedCodeInterpreter()` on the skills provider options:
```csharp
var skillsProvider = new FileAgentSkillsProvider(
skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
options: new FileAgentSkillsProviderOptions
{
- ScriptExecutor = SkillScriptExecutor.HostedCodeInterpreter()
+ ScriptExecutor = FileAgentSkillScriptExecutor.HostedCodeInterpreter()
});
```
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkill.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkill.cs
index f28bad3ab0..da0d0b83dd 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkill.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkill.cs
@@ -1,6 +1,8 @@
// 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;
@@ -13,7 +15,8 @@ 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.
///
-internal sealed class FileAgentSkill
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class FileAgentSkill
{
///
/// Initializes a new instance of the class.
@@ -22,8 +25,8 @@ internal sealed class FileAgentSkill
/// The SKILL.md content after the closing --- delimiter.
/// Absolute path to the directory containing this skill.
/// Relative paths of resource files referenced in the skill body.
- public FileAgentSkill(
- SkillFrontmatter frontmatter,
+ internal FileAgentSkill(
+ FileAgentSkillFrontmatter frontmatter,
string body,
string sourcePath,
IReadOnlyList? resourceNames = null)
@@ -37,20 +40,20 @@ public FileAgentSkill(
///
/// Gets the parsed YAML frontmatter (name and description).
///
- public SkillFrontmatter Frontmatter { get; }
+ public FileAgentSkillFrontmatter Frontmatter { get; }
///
- /// Gets the SKILL.md body content (without the YAML frontmatter).
+ /// Gets the directory path where the skill was discovered.
///
- public string Body { get; }
+ public string SourcePath { get; }
///
- /// Gets the directory path where the skill was discovered.
+ /// Gets the SKILL.md body content (without the YAML frontmatter).
///
- public string SourcePath { get; }
+ internal string Body { get; }
///
/// Gets the relative paths of resource files referenced in the skill body (e.g., "references/FAQ.md").
///
- public IReadOnlyList ResourceNames { get; }
+ internal IReadOnlyList ResourceNames { get; }
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/SkillFrontmatter.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs
similarity index 70%
rename from dotnet/src/Microsoft.Agents.AI/Skills/SkillFrontmatter.cs
rename to dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs
index 123a6c43f4..ee48a9ccc1 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/SkillFrontmatter.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs
@@ -1,20 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Shared.Diagnostics;
+using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Agents.AI;
///
/// Parsed YAML frontmatter from a SKILL.md file, containing the skill's name and description.
///
-internal sealed class SkillFrontmatter
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class FileAgentSkillFrontmatter
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Skill name.
/// Skill description.
- public SkillFrontmatter(string name, string description)
+ internal FileAgentSkillFrontmatter(string name, string description)
{
this.Name = Throw.IfNullOrWhitespace(name);
this.Description = Throw.IfNullOrWhitespace(description);
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs
index c6be21e9d2..8f55fc93c3 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
@@ -9,6 +10,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Agents.AI;
@@ -20,7 +22,8 @@ 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.
///
-internal sealed partial class FileAgentSkillLoader
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed partial class FileAgentSkillLoader
{
private const string SkillFileName = "SKILL.md";
private const int MaxSearchDepth = 2;
@@ -114,7 +117,7 @@ internal Dictionary DiscoverAndLoadSkills(IEnumerable
/// The resource is not registered, resolves outside the skill directory, or does not exist.
///
- internal async Task ReadSkillResourceAsync(FileAgentSkill skill, string resourceName, CancellationToken cancellationToken = default)
+ public async Task ReadSkillResourceAsync(FileAgentSkill skill, string resourceName, CancellationToken cancellationToken = default)
{
resourceName = NormalizeResourcePath(resourceName);
@@ -192,7 +195,7 @@ private static void SearchDirectoriesForSkills(string directory, List re
string content = File.ReadAllText(skillFilePath, Encoding.UTF8);
- if (!this.TryParseSkillDocument(content, skillFilePath, out SkillFrontmatter frontmatter, out string body))
+ if (!this.TryParseSkillDocument(content, skillFilePath, out FileAgentSkillFrontmatter frontmatter, out string body))
{
return null;
}
@@ -211,7 +214,7 @@ private static void SearchDirectoriesForSkills(string directory, List re
resourceNames: resourceNames);
}
- private bool TryParseSkillDocument(string content, string skillFilePath, out SkillFrontmatter frontmatter, out string body)
+ private bool TryParseSkillDocument(string content, string skillFilePath, out FileAgentSkillFrontmatter frontmatter, out string body)
{
frontmatter = null!;
body = null!;
@@ -267,7 +270,7 @@ private bool TryParseSkillDocument(string content, string skillFilePath, out Ski
return false;
}
- frontmatter = new SkillFrontmatter(name, description);
+ frontmatter = new FileAgentSkillFrontmatter(name, description);
body = content.Substring(match.Index + match.Length).TrimStart();
return true;
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionContext.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionContext.cs
new file mode 100644
index 0000000000..c28333a715
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionContext.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Shared.DiagnosticIds;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides access to loaded skills and the skill loader for use by implementations.
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class FileAgentSkillScriptExecutionContext
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The loaded skills dictionary.
+ /// The skill loader for reading resources.
+ internal FileAgentSkillScriptExecutionContext(Dictionary skills, FileAgentSkillLoader loader)
+ {
+ this.Skills = skills;
+ this.Loader = loader;
+ }
+
+ ///
+ /// Gets the loaded skills keyed by name.
+ ///
+ public IReadOnlyDictionary Skills { get; }
+
+ ///
+ /// Gets the skill loader for reading resources.
+ ///
+ public FileAgentSkillLoader Loader { get; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs
new file mode 100644
index 0000000000..756edf9ede
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs
@@ -0,0 +1,25 @@
+// 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;
+
+///
+/// Represents the tools and instructions contributed by a .
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class FileAgentSkillScriptExecutionDetails
+{
+ ///
+ /// Gets the additional instructions to provide to the agent for script execution.
+ ///
+ public string? Instructions { get; init; }
+
+ ///
+ /// Gets the additional tools to provide to the agent for script execution.
+ ///
+ public IReadOnlyList? Tools { get; init; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs
new file mode 100644
index 0000000000..e7f718d129
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Shared.DiagnosticIds;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Defines the contract for skill script execution modes.
+///
+///
+///
+/// A 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).
+///
+///
+/// Use the static factory methods to create instances:
+///
+/// - — executes scripts using the LLM provider's built-in code interpreter.
+///
+///
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public abstract class FileAgentSkillScriptExecutor
+{
+ ///
+ /// Creates a that uses the LLM provider's hosted code interpreter for script execution.
+ ///
+ /// A instance configured for hosted code interpreter execution.
+ public static FileAgentSkillScriptExecutor HostedCodeInterpreter() => new HostedCodeInterpreterSkillScriptExecutor();
+
+ ///
+ /// Returns the tools and instructions contributed by this executor.
+ ///
+ ///
+ /// The execution context provided by the skills provider, containing the loaded skills
+ /// and the skill loader for reading resources.
+ ///
+ /// A containing the executor's tools and instructions.
+ protected internal abstract FileAgentSkillScriptExecutionDetails GetExecutionDetails(FileAgentSkillScriptExecutionContext context);
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
index 010be474e9..7acec160d4 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProvider.cs
@@ -91,7 +91,11 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr
this._loader = new FileAgentSkillLoader(this._logger);
this._skills = this._loader.DiscoverAndLoadSkills(skillPaths);
- this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills, options?.ScriptExecutor);
+ var executionDetails = options?.ScriptExecutor is { } executor
+ ? executor.GetExecutionDetails(new(this._skills, this._loader))
+ : null;
+
+ this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills, executionDetails?.Instructions);
AITool[] baseTools =
[
@@ -105,7 +109,7 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr
description: "Reads a file associated with a skill, such as references or assets."),
];
- this._tools = options?.ScriptExecutor?.Tools is { Count: > 0 } executorTools
+ this._tools = executionDetails?.Tools is { Count: > 0 } executorTools
? baseTools.Concat(executorTools)
: baseTools;
}
@@ -170,7 +174,7 @@ private async Task ReadSkillResourceAsync(string skillName, string resou
}
}
- private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary skills, SkillScriptExecutor? executor)
+ private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary skills, string? instructions)
{
string promptTemplate = options?.SkillsInstructionPrompt ?? DefaultSkillsInstructionPrompt;
@@ -193,7 +197,7 @@ private async Task ReadSkillResourceAsync(string skillName, string resou
return promptTemplate
.Replace("{skills}", sb.ToString().TrimEnd())
- .Replace("{executor_instructions}", executor?.Instructions ?? "\n");
+ .Replace("{executor_instructions}", instructions ?? "\n");
}
[LoggerMessage(LogLevel.Information, "Loading skill: {SkillName}")]
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
index 1e034f9c52..7d86d3b4ae 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs
@@ -24,9 +24,9 @@ public sealed class FileAgentSkillsProviderOptions
///
///
/// When (the default), script execution is disabled and skills only provide
- /// instructions and resources. Set this to a instance (e.g.,
- /// ) to enable script execution with
+ /// instructions and resources. Set this to a instance (e.g.,
+ /// ) to enable script execution with
/// mode-specific instructions and tools.
///
- public SkillScriptExecutor? ScriptExecutor { get; set; }
+ public FileAgentSkillScriptExecutor? ScriptExecutor { get; set; }
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs
index f15092c467..585b05d788 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs
@@ -1,33 +1,35 @@
// Copyright (c) Microsoft. All rights reserved.
-using System.Collections.Generic;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI;
///
-/// A that uses the LLM provider's hosted code interpreter for script execution.
+/// A that uses the LLM provider's hosted code interpreter for script execution.
///
///
/// This executor directs the LLM to load scripts via read_skill_resource and execute them
/// using the provider's built-in code interpreter. A is
/// registered to signal the provider to enable its code interpreter sandbox.
///
-internal sealed class HostedCodeInterpreterSkillScriptExecutor : SkillScriptExecutor
+internal sealed class HostedCodeInterpreterSkillScriptExecutor : FileAgentSkillScriptExecutor
{
- private static readonly AITool[] s_tools = [new HostedCodeInterpreterTool()];
+ private static readonly FileAgentSkillScriptExecutionDetails s_contribution = new()
+ {
+ Instructions =
+ """
- ///
- public override string Instructions { get; } =
- """
-
- 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
+ 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()],
+ };
///
- public override IReadOnlyList Tools => s_tools;
+#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
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
deleted file mode 100644
index 5ba40432e6..0000000000
--- a/dotnet/src/Microsoft.Agents.AI/Skills/SkillScriptExecutor.cs
+++ /dev/null
@@ -1,44 +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;
-
-///
-/// Defines the contract for skill script execution modes.
-///
-///
-///
-/// A 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).
-///
-///
-/// Use the static factory methods to create instances:
-///
-/// - — executes scripts using the LLM provider's built-in code interpreter.
-///
-///
-///
-[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
-public abstract class SkillScriptExecutor
-{
- ///
- /// Creates a that uses the LLM provider's hosted code interpreter for script execution.
- ///
- /// A instance configured for hosted code interpreter execution.
- public static SkillScriptExecutor HostedCodeInterpreter() => new HostedCodeInterpreterSkillScriptExecutor();
-
- ///
- /// Gets the additional instructions to provide to the agent for script execution.
- ///
- public abstract string? Instructions { get; }
-
- ///
- /// Gets the additional tools to provide to the agent for script execution.
- ///
- public abstract IReadOnlyList? Tools { get; }
-}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
index 49bf730db3..c9e154a277 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs
@@ -501,7 +501,7 @@ public async Task ReadSkillResourceAsync_SymlinkInPath_ThrowsInvalidOperationExc
}
// Manually construct a skill that bypasses discovery validation
- var frontmatter = new SkillFrontmatter("symlink-read-skill", "A skill");
+ var frontmatter = new FileAgentSkillFrontmatter("symlink-read-skill", "A skill");
var skill = new FileAgentSkill(
frontmatter: frontmatter,
body: "See [doc](refs/data.md).",
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillScriptExecutorTests.cs
similarity index 76%
rename from dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillScriptExecutorTests.cs
index 9e57b5b93e..1be56e49c9 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/SkillScriptExecutorTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillScriptExecutorTests.cs
@@ -1,23 +1,28 @@
// 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;
///
-/// Unit tests for and its integration with .
+/// Unit tests for and its integration with .
///
-public sealed class SkillScriptExecutorTests : IDisposable
+public sealed class FileAgentSkillScriptExecutorTests : IDisposable
{
private readonly string _testRoot;
private readonly TestAIAgent _agent = new();
+ private static readonly FileAgentSkillScriptExecutionContext s_emptyContext = new(
+ new Dictionary(StringComparer.OrdinalIgnoreCase),
+ new FileAgentSkillLoader(NullLogger.Instance));
- public SkillScriptExecutorTests()
+ public FileAgentSkillScriptExecutorTests()
{
this._testRoot = Path.Combine(Path.GetTempPath(), "skill-executor-tests-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(this._testRoot);
@@ -35,38 +40,40 @@ public void Dispose()
public void HostedCodeInterpreter_ReturnsNonNullInstance()
{
// Act
- var executor = SkillScriptExecutor.HostedCodeInterpreter();
+ var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Assert
Assert.NotNull(executor);
}
[Fact]
- public void HostedCodeInterpreter_GetInstructions_ReturnsNonNullString()
+ public void HostedCodeInterpreter_GetExecutionDetails_ReturnsNonNullInstructions()
{
// Arrange
- var executor = SkillScriptExecutor.HostedCodeInterpreter();
+ var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Act
- string? instructions = executor.Instructions;
+ var details = executor.GetExecutionDetails(s_emptyContext);
// Assert
- Assert.NotNull(instructions);
- Assert.NotEmpty(instructions);
+ Assert.NotNull(details);
+ Assert.NotNull(details.Instructions);
+ Assert.NotEmpty(details.Instructions);
}
[Fact]
- public void HostedCodeInterpreter_GetTools_ReturnsNonEmptyList()
+ public void HostedCodeInterpreter_GetExecutionDetails_ReturnsNonEmptyToolsList()
{
// Arrange
- var executor = SkillScriptExecutor.HostedCodeInterpreter();
+ var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Act
- var tools = executor.Tools;
+ var details = executor.GetExecutionDetails(s_emptyContext);
// Assert
- Assert.NotNull(tools);
- Assert.NotEmpty(tools);
+ Assert.NotNull(details);
+ Assert.NotNull(details.Tools);
+ Assert.NotEmpty(details.Tools);
}
[Fact]
@@ -74,7 +81,7 @@ public async Task Provider_WithExecutor_IncludesExecutorInstructionsInPromptAsyn
{
// Arrange
CreateSkill(this._testRoot, "exec-skill", "Executor test", "Body.");
- var executor = SkillScriptExecutor.HostedCodeInterpreter();
+ 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());
@@ -92,7 +99,7 @@ public async Task Provider_WithExecutor_IncludesExecutorToolsAsync()
{
// Arrange
CreateSkill(this._testRoot, "tools-exec-skill", "Executor tools test", "Body.");
- var executor = SkillScriptExecutor.HostedCodeInterpreter();
+ 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());
@@ -130,7 +137,7 @@ public async Task Provider_WithHostedCodeInterpreter_MergesScriptInstructionsInt
{
// Arrange
CreateSkill(this._testRoot, "merge-skill", "Merge test", "Body.");
- var executor = SkillScriptExecutor.HostedCodeInterpreter();
+ 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());
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs
index 3ef58624d0..12f81d6cb4 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs
@@ -1,6 +1,9 @@
// 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;
@@ -9,55 +12,59 @@ namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
///
public sealed class HostedCodeInterpreterSkillScriptExecutorTests
{
+ private static readonly FileAgentSkillScriptExecutionContext s_emptyContext = new(
+ new Dictionary(StringComparer.OrdinalIgnoreCase),
+ new FileAgentSkillLoader(NullLogger.Instance));
+
[Fact]
- public void GetInstructions_ReturnsScriptExecutionGuidance()
+ public void GetExecutionDetails_ReturnsScriptExecutionGuidance()
{
// Arrange
var executor = new HostedCodeInterpreterSkillScriptExecutor();
// Act
- string? instructions = executor.Instructions;
+ var details = executor.GetExecutionDetails(s_emptyContext);
// Assert
- Assert.NotNull(instructions);
- Assert.Contains("read_skill_resource", instructions);
- Assert.Contains("code interpreter", instructions);
+ Assert.NotNull(details.Instructions);
+ Assert.Contains("read_skill_resource", details.Instructions);
+ Assert.Contains("code interpreter", details.Instructions);
}
[Fact]
- public void GetTools_ReturnsSingleHostedCodeInterpreterTool()
+ public void GetExecutionDetails_ReturnsSingleHostedCodeInterpreterTool()
{
// Arrange
var executor = new HostedCodeInterpreterSkillScriptExecutor();
// Act
- var tools = executor.Tools;
+ var details = executor.GetExecutionDetails(s_emptyContext);
// Assert
- Assert.NotNull(tools);
- Assert.Single(tools!);
- Assert.IsType(tools![0]);
+ Assert.NotNull(details.Tools);
+ Assert.Single(details.Tools!);
+ Assert.IsType(details.Tools![0]);
}
[Fact]
- public void GetTools_ReturnsSameInstanceOnMultipleCalls()
+ public void GetExecutionDetails_ReturnsSameInstanceOnMultipleCalls()
{
// Arrange
var executor = new HostedCodeInterpreterSkillScriptExecutor();
// Act
- var tools1 = executor.Tools;
- var tools2 = executor.Tools;
+ var details1 = executor.GetExecutionDetails(s_emptyContext);
+ var details2 = executor.GetExecutionDetails(s_emptyContext);
- // Assert — static tools array should be reused
- Assert.Same(tools1, tools2);
+ // Assert — static details should be reused
+ Assert.Same(details1, details2);
}
[Fact]
public void FactoryMethod_ReturnsHostedCodeInterpreterSkillScriptExecutor()
{
// Act
- var executor = SkillScriptExecutor.HostedCodeInterpreter();
+ var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Assert
Assert.IsType(executor);
From f73dd58126f1c2045550d0a5b0c0caa51319fb74 Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Wed, 25 Feb 2026 15:23:09 +0000
Subject: [PATCH 7/9] reorder usings
---
.../src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs
index ee48a9ccc1..c369ad319f 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
-using Microsoft.Shared.Diagnostics;
using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
From a4ca6e225fca3d50a41eece985a2a130475a3969 Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Wed, 25 Feb 2026 16:42:09 +0000
Subject: [PATCH 8/9] use set for props initialization instead of init
---
.../Skills/FileAgentSkillScriptExecutionDetails.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs
index 756edf9ede..4c12848386 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs
@@ -16,10 +16,10 @@ public sealed class FileAgentSkillScriptExecutionDetails
///
/// Gets the additional instructions to provide to the agent for script execution.
///
- public string? Instructions { get; init; }
+ public string? Instructions { get; set; }
///
/// Gets the additional tools to provide to the agent for script execution.
///
- public IReadOnlyList? Tools { get; init; }
+ public IReadOnlyList? Tools { get; set; }
}
From 2a93a0ef07360bbb55f7ccb2f88d79f4e52ccad2 Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Wed, 25 Feb 2026 17:23:00 +0000
Subject: [PATCH 9/9] rename HostedCodeInterpreterSkillScriptExecutor
---
.../Skills/FileAgentSkillScriptExecutor.cs | 2 +-
...CodeInterpreterFileAgentSkillScriptExecutor.cs} | 2 +-
...nterpreterFileAgentSkillScriptExecutorTests.cs} | 14 +++++++-------
3 files changed, 9 insertions(+), 9 deletions(-)
rename dotnet/src/Microsoft.Agents.AI/Skills/{HostedCodeInterpreterSkillScriptExecutor.cs => HostedCodeInterpreterFileAgentSkillScriptExecutor.cs} (93%)
rename dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/{HostedCodeInterpreterSkillScriptExecutorTests.cs => HostedCodeInterpreterFileAgentSkillScriptExecutorTests.cs} (75%)
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs
index e7f718d129..1171940e72 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs
@@ -28,7 +28,7 @@ public abstract class FileAgentSkillScriptExecutor
/// Creates a that uses the LLM provider's hosted code interpreter for script execution.
///
/// A instance configured for hosted code interpreter execution.
- public static FileAgentSkillScriptExecutor HostedCodeInterpreter() => new HostedCodeInterpreterSkillScriptExecutor();
+ public static FileAgentSkillScriptExecutor HostedCodeInterpreter() => new HostedCodeInterpreterFileAgentSkillScriptExecutor();
///
/// Returns the tools and instructions contributed by this executor.
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterFileAgentSkillScriptExecutor.cs
similarity index 93%
rename from dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs
rename to dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterFileAgentSkillScriptExecutor.cs
index 585b05d788..88fb1f86a2 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterSkillScriptExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterFileAgentSkillScriptExecutor.cs
@@ -12,7 +12,7 @@ namespace Microsoft.Agents.AI;
/// using the provider's built-in code interpreter. A is
/// registered to signal the provider to enable its code interpreter sandbox.
///
-internal sealed class HostedCodeInterpreterSkillScriptExecutor : FileAgentSkillScriptExecutor
+internal sealed class HostedCodeInterpreterFileAgentSkillScriptExecutor : FileAgentSkillScriptExecutor
{
private static readonly FileAgentSkillScriptExecutionDetails s_contribution = new()
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterFileAgentSkillScriptExecutorTests.cs
similarity index 75%
rename from dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterFileAgentSkillScriptExecutorTests.cs
index 12f81d6cb4..84a4446779 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterSkillScriptExecutorTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterFileAgentSkillScriptExecutorTests.cs
@@ -8,9 +8,9 @@
namespace Microsoft.Agents.AI.UnitTests.AgentSkills;
///
-/// Unit tests for .
+/// Unit tests for .
///
-public sealed class HostedCodeInterpreterSkillScriptExecutorTests
+public sealed class HostedCodeInterpreterFileAgentSkillScriptExecutorTests
{
private static readonly FileAgentSkillScriptExecutionContext s_emptyContext = new(
new Dictionary(StringComparer.OrdinalIgnoreCase),
@@ -20,7 +20,7 @@ public sealed class HostedCodeInterpreterSkillScriptExecutorTests
public void GetExecutionDetails_ReturnsScriptExecutionGuidance()
{
// Arrange
- var executor = new HostedCodeInterpreterSkillScriptExecutor();
+ var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor();
// Act
var details = executor.GetExecutionDetails(s_emptyContext);
@@ -35,7 +35,7 @@ public void GetExecutionDetails_ReturnsScriptExecutionGuidance()
public void GetExecutionDetails_ReturnsSingleHostedCodeInterpreterTool()
{
// Arrange
- var executor = new HostedCodeInterpreterSkillScriptExecutor();
+ var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor();
// Act
var details = executor.GetExecutionDetails(s_emptyContext);
@@ -50,7 +50,7 @@ public void GetExecutionDetails_ReturnsSingleHostedCodeInterpreterTool()
public void GetExecutionDetails_ReturnsSameInstanceOnMultipleCalls()
{
// Arrange
- var executor = new HostedCodeInterpreterSkillScriptExecutor();
+ var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor();
// Act
var details1 = executor.GetExecutionDetails(s_emptyContext);
@@ -61,12 +61,12 @@ public void GetExecutionDetails_ReturnsSameInstanceOnMultipleCalls()
}
[Fact]
- public void FactoryMethod_ReturnsHostedCodeInterpreterSkillScriptExecutor()
+ public void FactoryMethod_ReturnsHostedCodeInterpreterFileAgentSkillScriptExecutor()
{
// Act
var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter();
// Assert
- Assert.IsType(executor);
+ Assert.IsType(executor);
}
}