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); } }