From c0b88f2864cb3b0aa942128603b9cf151849af78 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:41:19 +0000 Subject: [PATCH] Revert ".NET: Support hosted code interpreter for skill script execution (#4192)" This reverts commit c9cd067be6b2981791a9d93b1c832390a39b507a. --- dotnet/agent-framework-dotnet.slnx | 1 - .../Agent_Step01_BasicSkills/Program.cs | 3 - ..._ScriptExecutionWithCodeInterpreter.csproj | 28 --- .../Program.cs | 49 ----- .../README.md | 72 -------- .../skills/password-generator/SKILL.md | 16 -- .../references/PASSWORD_GUIDELINES.md | 24 --- .../password-generator/scripts/generate.py | 11 -- .../samples/02-agents/AgentSkills/README.md | 1 - .../Skills/FileAgentSkill.cs | 21 +-- .../Skills/FileAgentSkillLoader.cs | 22 +-- .../FileAgentSkillScriptExecutionContext.cs | 35 ---- .../FileAgentSkillScriptExecutionDetails.cs | 25 --- .../Skills/FileAgentSkillScriptExecutor.cs | 42 ----- .../Skills/FileAgentSkillsProvider.cs | 49 ++--- .../Skills/FileAgentSkillsProviderOptions.cs | 14 +- ...InterpreterFileAgentSkillScriptExecutor.cs | 35 ---- ...killFrontmatter.cs => SkillFrontmatter.cs} | 9 +- .../AgentSkills/FileAgentSkillLoaderTests.cs | 50 +----- .../FileAgentSkillScriptExecutorTests.cs | 170 ------------------ .../FileAgentSkillsProviderTests.cs | 17 +- ...preterFileAgentSkillScriptExecutorTests.cs | 72 -------- 22 files changed, 65 insertions(+), 701 deletions(-) delete mode 100644 dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj delete mode 100644 dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs delete mode 100644 dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md delete mode 100644 dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/SKILL.md delete mode 100644 dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/references/PASSWORD_GUIDELINES.md delete mode 100644 dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/scripts/generate.py delete mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionContext.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs delete mode 100644 dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterFileAgentSkillScriptExecutor.cs rename dotnet/src/Microsoft.Agents.AI/Skills/{FileAgentSkillFrontmatter.cs => SkillFrontmatter.cs} (70%) delete mode 100644 dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillScriptExecutorTests.cs delete mode 100644 dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterFileAgentSkillScriptExecutorTests.cs diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 1ab73c2b8b..b96b891b00 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -82,7 +82,6 @@ - diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step01_BasicSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step01_BasicSkills/Program.cs index eef57e840a..290c3f9b6b 100644 --- a/dotnet/samples/02-agents/AgentSkills/Agent_Step01_BasicSkills/Program.cs +++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step01_BasicSkills/Program.cs @@ -22,9 +22,6 @@ 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/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj b/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj deleted file mode 100644 index 2a503bbfb2..0000000000 --- a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Agent_Step02_ScriptExecutionWithCodeInterpreter.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Exe - net10.0 - - enable - enable - $(NoWarn);MAAI001 - - - - - - - - - - - - - - - PreserveNewest - - - - diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs deleted file mode 100644 index 2835ec70ab..0000000000 --- a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/Program.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -// This sample demonstrates how to use Agent Skills with script execution via the hosted code interpreter. -// 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: -// - 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 - { - ScriptExecutor = FileAgentSkillScriptExecutor.HostedCodeInterpreter() - }); - -// --- 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 - { - 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/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md b/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md deleted file mode 100644 index f5bf63c44a..0000000000 --- a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# 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 `FileAgentSkillScriptExecutor.HostedCodeInterpreter()` on the skills provider options: - -```csharp -var skillsProvider = new FileAgentSkillsProvider( - skillPath: Path.Combine(AppContext.BaseDirectory, "skills"), - options: new FileAgentSkillsProviderOptions - { - ScriptExecutor = FileAgentSkillScriptExecutor.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/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/SKILL.md b/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/SKILL.md deleted file mode 100644 index c3ef67401b..0000000000 --- a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/SKILL.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/references/PASSWORD_GUIDELINES.md b/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/references/PASSWORD_GUIDELINES.md deleted file mode 100644 index be9145a4dd..0000000000 --- a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/references/PASSWORD_GUIDELINES.md +++ /dev/null @@ -1,24 +0,0 @@ -# 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/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/scripts/generate.py b/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/scripts/generate.py deleted file mode 100644 index b44f3d9731..0000000000 --- a/dotnet/samples/02-agents/AgentSkills/Agent_Step02_ScriptExecutionWithCodeInterpreter/skills/password-generator/scripts/generate.py +++ /dev/null @@ -1,11 +0,0 @@ -# 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/02-agents/AgentSkills/README.md b/dotnet/samples/02-agents/AgentSkills/README.md index 477a738fb8..8488ec9eed 100644 --- a/dotnet/samples/02-agents/AgentSkills/README.md +++ b/dotnet/samples/02-agents/AgentSkills/README.md @@ -5,4 +5,3 @@ 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/FileAgentSkill.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkill.cs index da0d0b83dd..f28bad3ab0 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkill.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkill.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; @@ -15,8 +13,7 @@ namespace Microsoft.Agents.AI; /// and a markdown body with instructions. Resource files referenced in the body are validated at /// discovery time and read from disk on demand. /// -[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] -public sealed class FileAgentSkill +internal sealed class FileAgentSkill { /// /// Initializes a new instance of the class. @@ -25,8 +22,8 @@ public 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. - internal FileAgentSkill( - FileAgentSkillFrontmatter frontmatter, + public FileAgentSkill( + SkillFrontmatter frontmatter, string body, string sourcePath, IReadOnlyList? resourceNames = null) @@ -40,20 +37,20 @@ internal FileAgentSkill( /// /// Gets the parsed YAML frontmatter (name and description). /// - public FileAgentSkillFrontmatter Frontmatter { get; } + public SkillFrontmatter Frontmatter { get; } /// - /// Gets the directory path where the skill was discovered. + /// Gets the SKILL.md body content (without the YAML frontmatter). /// - public string SourcePath { get; } + public string Body { get; } /// - /// Gets the SKILL.md body content (without the YAML frontmatter). + /// Gets the directory path where the skill was discovered. /// - internal string Body { get; } + public string SourcePath { get; } /// /// Gets the relative paths of resource files referenced in the skill body (e.g., "references/FAQ.md"). /// - internal IReadOnlyList ResourceNames { get; } + public IReadOnlyList ResourceNames { get; } } diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs index 8f55fc93c3..8c034b3122 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillLoader.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; @@ -10,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Agents.AI; @@ -22,8 +20,7 @@ namespace Microsoft.Agents.AI; /// Each file is validated for YAML frontmatter and resource integrity. Invalid skills are excluded /// with logged warnings. Resource paths are checked against path traversal and symlink escape attacks. /// -[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] -public sealed partial class FileAgentSkillLoader +internal sealed partial class FileAgentSkillLoader { private const string SkillFileName = "SKILL.md"; private const int MaxSearchDepth = 2; @@ -36,16 +33,13 @@ public sealed partial class FileAgentSkillLoader // Example: "---\nname: foo\n---\nBody" → Group 1: "name: foo\n" private static readonly Regex s_frontmatterRegex = new(@"\A\uFEFF?^---\s*$(.+?)^---\s*$", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled, TimeSpan.FromSeconds(5)); - // Matches resource file references in skill markdown. Group 1 = relative file path. - // Supports two forms: - // 1. Markdown links: [text](path/file.ext) - // 2. Backtick-quoted paths: `path/file.ext` + // Matches markdown links to local resource files. Group 1 = relative file path. // Supports optional ./ or ../ prefixes; excludes URLs (no ":" in the path character class). // Intentionally conservative: only matches paths with word characters, hyphens, dots, // and forward slashes. Paths with spaces or special characters are not supported. - // Examples: [doc](refs/FAQ.md) → "refs/FAQ.md", `./scripts/run.py` → "./scripts/run.py", + // Examples: [doc](refs/FAQ.md) → "refs/FAQ.md", [s](./s.json) → "./s.json", // [p](../shared/doc.txt) → "../shared/doc.txt" - private static readonly Regex s_resourceLinkRegex = new(@"(?:\[.*?\]\(|`)(\.?\.?/?[\w][\w\-./]*\.\w+)(?:\)|`)", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + private static readonly Regex s_resourceLinkRegex = new(@"\[.*?\]\((\.?\.?/?[\w][\w\-./]*\.\w+)\)", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); // Matches YAML "key: value" lines. Group 1 = key, Group 2 = quoted value, Group 3 = unquoted value. // Accepts single or double quotes; the lazy quantifier trims trailing whitespace on unquoted values. @@ -117,7 +111,7 @@ internal Dictionary DiscoverAndLoadSkills(IEnumerable /// The resource is not registered, resolves outside the skill directory, or does not exist. /// - public async Task ReadSkillResourceAsync(FileAgentSkill skill, string resourceName, CancellationToken cancellationToken = default) + internal async Task ReadSkillResourceAsync(FileAgentSkill skill, string resourceName, CancellationToken cancellationToken = default) { resourceName = NormalizeResourcePath(resourceName); @@ -195,7 +189,7 @@ private static void SearchDirectoriesForSkills(string directory, List re string content = File.ReadAllText(skillFilePath, Encoding.UTF8); - if (!this.TryParseSkillDocument(content, skillFilePath, out FileAgentSkillFrontmatter frontmatter, out string body)) + if (!this.TryParseSkillDocument(content, skillFilePath, out SkillFrontmatter frontmatter, out string body)) { return null; } @@ -214,7 +208,7 @@ private static void SearchDirectoriesForSkills(string directory, List re resourceNames: resourceNames); } - private bool TryParseSkillDocument(string content, string skillFilePath, out FileAgentSkillFrontmatter frontmatter, out string body) + private bool TryParseSkillDocument(string content, string skillFilePath, out SkillFrontmatter frontmatter, out string body) { frontmatter = null!; body = null!; @@ -270,7 +264,7 @@ private bool TryParseSkillDocument(string content, string skillFilePath, out Fil return false; } - frontmatter = new FileAgentSkillFrontmatter(name, description); + frontmatter = new SkillFrontmatter(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 deleted file mode 100644 index c28333a715..0000000000 --- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionContext.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Shared.DiagnosticIds; - -namespace Microsoft.Agents.AI; - -/// -/// 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 deleted file mode 100644 index 4c12848386..0000000000 --- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutionDetails.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.AI; -using Microsoft.Shared.DiagnosticIds; - -namespace Microsoft.Agents.AI; - -/// -/// 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; set; } - - /// - /// Gets the additional tools to provide to the agent for script execution. - /// - public IReadOnlyList? Tools { get; set; } -} diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs deleted file mode 100644 index 1171940e72..0000000000 --- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillScriptExecutor.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 HostedCodeInterpreterFileAgentSkillScriptExecutor(); - - /// - /// 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 7acec160d4..847bf36a52 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. - {skills} + {0} When a task aligns with a skill's domain: - - Use `load_skill` to retrieve the skill's instructions - - Follow the provided guidance - - Use `read_skill_resource` to read any references or other files mentioned by the skill, always using the full path as written (e.g. `references/FAQ.md`, not just `FAQ.md`) - {executor_instructions} + 1. Use `load_skill` to retrieve the skill's instructions + 2. Follow the provided guidance + 3. Use `read_skill_resource` to read any references or other files mentioned by the skill + Only load what is needed, when it is needed. """; private readonly Dictionary _skills; private readonly ILogger _logger; private readonly FileAgentSkillLoader _loader; - private readonly IEnumerable _tools; + private readonly AITool[] _tools; private readonly string? _skillsInstructionPrompt; /// @@ -91,13 +91,9 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr this._loader = new FileAgentSkillLoader(this._logger); this._skills = this._loader.DiscoverAndLoadSkills(skillPaths); - var executionDetails = options?.ScriptExecutor is { } executor - ? executor.GetExecutionDetails(new(this._skills, this._loader)) - : null; - - this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills, executionDetails?.Instructions); + this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills); - AITool[] baseTools = + this._tools = [ AIFunctionFactory.Create( this.LoadSkill, @@ -108,10 +104,6 @@ public FileAgentSkillsProvider(IEnumerable skillPaths, FileAgentSkillsPr name: "read_skill_resource", description: "Reads a file associated with a skill, such as references or assets."), ]; - - this._tools = executionDetails?.Tools is { Count: > 0 } executorTools - ? baseTools.Concat(executorTools) - : baseTools; } /// @@ -125,7 +117,7 @@ protected override ValueTask ProvideAIContextAsync(InvokingContext co return new ValueTask(new AIContext { Instructions = this._skillsInstructionPrompt, - Tools = this._tools, + Tools = this._tools }); } @@ -174,9 +166,24 @@ private async Task ReadSkillResourceAsync(string skillName, string resou } } - private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary skills, string? instructions) + private static string? BuildSkillsInstructionPrompt(FileAgentSkillsProviderOptions? options, Dictionary skills) { - string promptTemplate = options?.SkillsInstructionPrompt ?? DefaultSkillsInstructionPrompt; + string promptTemplate = DefaultSkillsInstructionPrompt; + + if (options?.SkillsInstructionPrompt is { } optionsInstructions) + { + try + { + promptTemplate = string.Format(optionsInstructions, string.Empty); + } + catch (FormatException ex) + { + throw new ArgumentException( + "The provided SkillsInstructionPrompt is not a valid format string. It must contain a '{0}' placeholder and escape any literal '{' or '}' by doubling them ('{{' or '}}').", + nameof(options), + ex); + } + } if (skills.Count == 0) { @@ -195,9 +202,7 @@ private async Task ReadSkillResourceAsync(string skillName, string resou sb.AppendLine(" "); } - return promptTemplate - .Replace("{skills}", sb.ToString().TrimEnd()) - .Replace("{executor_instructions}", instructions ?? "\n"); + return string.Format(promptTemplate, sb.ToString().TrimEnd()); } [LoggerMessage(LogLevel.Information, "Loading skill: {SkillName}")] diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs index 7d86d3b4ae..a47841c260 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillsProviderOptions.cs @@ -13,20 +13,8 @@ public sealed class FileAgentSkillsProviderOptions { /// /// Gets or sets a custom system prompt template for advertising skills. - /// Use {skills} as the placeholder for the generated skills list and - /// {executor_instructions} for executor-provided instructions. + /// Use {0} as the placeholder for the generated skills list. /// 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 FileAgentSkillScriptExecutor? ScriptExecutor { get; set; } } diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterFileAgentSkillScriptExecutor.cs b/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterFileAgentSkillScriptExecutor.cs deleted file mode 100644 index 88fb1f86a2..0000000000 --- a/dotnet/src/Microsoft.Agents.AI/Skills/HostedCodeInterpreterFileAgentSkillScriptExecutor.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -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 HostedCodeInterpreterFileAgentSkillScriptExecutor : FileAgentSkillScriptExecutor -{ - private static readonly FileAgentSkillScriptExecutionDetails s_contribution = new() - { - Instructions = - """ - - Some skills include executable scripts (e.g., Python files) in their resources. - When a skill's instructions reference a script: - 1. Use `read_skill_resource` to load the script content - 2. Execute the script using the code interpreter - - """, - Tools = [new HostedCodeInterpreterTool()], - }; - - /// -#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/FileAgentSkillFrontmatter.cs b/dotnet/src/Microsoft.Agents.AI/Skills/SkillFrontmatter.cs similarity index 70% rename from dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs rename to dotnet/src/Microsoft.Agents.AI/Skills/SkillFrontmatter.cs index c369ad319f..123a6c43f4 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/FileAgentSkillFrontmatter.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/SkillFrontmatter.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Diagnostics.CodeAnalysis; -using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; @@ -9,15 +7,14 @@ namespace Microsoft.Agents.AI; /// /// Parsed YAML frontmatter from a SKILL.md file, containing the skill's name and description. /// -[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] -public sealed class FileAgentSkillFrontmatter +internal sealed class SkillFrontmatter { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Skill name. /// Skill description. - internal FileAgentSkillFrontmatter(string name, string description) + public SkillFrontmatter(string name, string description) { this.Name = Throw.IfNullOrWhitespace(name); this.Description = Throw.IfNullOrWhitespace(description); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs index c9e154a277..c34eb6d7f2 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 FileAgentSkillFrontmatter("symlink-read-skill", "A skill"); + var frontmatter = new SkillFrontmatter("symlink-read-skill", "A skill"); var skill = new FileAgentSkill( frontmatter: frontmatter, body: "See [doc](refs/data.md).", @@ -532,54 +532,6 @@ public void DiscoverAndLoadSkills_FileWithUtf8Bom_ParsesSuccessfully() Assert.Equal("Body content.", skills["bom-skill"].Body); } - [Theory] - [InlineData("No resource references.", new string[0])] - [InlineData("Review `refs/FAQ.md` for details.", new[] { "refs/FAQ.md" })] - [InlineData("See [guide](refs/guide.md) then run `scripts/run.py`.", new[] { "refs/guide.md", "scripts/run.py" })] - public void DiscoverAndLoadSkills_ResourceReferences_ExtractsExpectedResourceNames(string body, string[] expectedResources) - { - // Arrange — create skill with resource files on disk so validation passes - string skillDir = Path.Combine(this._testRoot, "res-skill"); - Directory.CreateDirectory(skillDir); - foreach (string resource in expectedResources) - { - string resourcePath = Path.Combine(skillDir, resource.Replace('/', Path.DirectorySeparatorChar)); - Directory.CreateDirectory(Path.GetDirectoryName(resourcePath)!); - File.WriteAllText(resourcePath, "content"); - } - - File.WriteAllText( - Path.Combine(skillDir, "SKILL.md"), - $"---\nname: res-skill\ndescription: Resource test\n---\n{body}"); - - // Act - var skills = this._loader.DiscoverAndLoadSkills(new[] { this._testRoot }); - - // Assert - Assert.Single(skills); - var skill = skills["res-skill"]; - Assert.Equal(expectedResources.Length, skill.ResourceNames.Count); - foreach (string expected in expectedResources) - { - Assert.Contains(expected, skill.ResourceNames); - } - } - - [Fact] - public async Task ReadSkillResourceAsync_BacktickResourcePath_ReturnsContentAsync() - { - // Arrange — skill body uses backtick-quoted path - _ = this.CreateSkillDirectoryWithResource("backtick-read", "A skill", "Load `refs/doc.md` first.", "refs/doc.md", "Backtick content."); - var skills = this._loader.DiscoverAndLoadSkills(new[] { this._testRoot }); - var skill = skills["backtick-read"]; - - // Act - string content = await this._loader.ReadSkillResourceAsync(skill, "refs/doc.md"); - - // Assert - Assert.Equal("Backtick content.", content); - } - private string CreateSkillDirectory(string name, string description, string body) { string skillDir = Path.Combine(this._testRoot, name); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillScriptExecutorTests.cs deleted file mode 100644 index 1be56e49c9..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillScriptExecutorTests.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Microsoft.Agents.AI.UnitTests.AgentSkills; - -/// -/// Unit tests for and its integration with . -/// -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 FileAgentSkillScriptExecutorTests() - { - this._testRoot = Path.Combine(Path.GetTempPath(), "skill-executor-tests-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(this._testRoot); - } - - public void Dispose() - { - if (Directory.Exists(this._testRoot)) - { - Directory.Delete(this._testRoot, recursive: true); - } - } - - [Fact] - public void HostedCodeInterpreter_ReturnsNonNullInstance() - { - // Act - var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter(); - - // Assert - Assert.NotNull(executor); - } - - [Fact] - public void HostedCodeInterpreter_GetExecutionDetails_ReturnsNonNullInstructions() - { - // Arrange - var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter(); - - // Act - var details = executor.GetExecutionDetails(s_emptyContext); - - // Assert - Assert.NotNull(details); - Assert.NotNull(details.Instructions); - Assert.NotEmpty(details.Instructions); - } - - [Fact] - public void HostedCodeInterpreter_GetExecutionDetails_ReturnsNonEmptyToolsList() - { - // Arrange - var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter(); - - // Act - var details = executor.GetExecutionDetails(s_emptyContext); - - // Assert - Assert.NotNull(details); - Assert.NotNull(details.Tools); - Assert.NotEmpty(details.Tools); - } - - [Fact] - public async Task Provider_WithExecutor_IncludesExecutorInstructionsInPromptAsync() - { - // Arrange - CreateSkill(this._testRoot, "exec-skill", "Executor test", "Body."); - var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter(); - var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor }; - var provider = new FileAgentSkillsProvider(this._testRoot, options); - var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext()); - - // Act - var result = await provider.InvokingAsync(invokingContext, CancellationToken.None); - - // Assert — executor instructions should be merged into the prompt - Assert.NotNull(result.Instructions); - Assert.Contains("code interpreter", result.Instructions, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task Provider_WithExecutor_IncludesExecutorToolsAsync() - { - // Arrange - CreateSkill(this._testRoot, "tools-exec-skill", "Executor tools test", "Body."); - var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter(); - var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor }; - var provider = new FileAgentSkillsProvider(this._testRoot, options); - var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext()); - - // Act - var result = await provider.InvokingAsync(invokingContext, CancellationToken.None); - - // Assert — should have 3 tools: load_skill, read_skill_resource, and HostedCodeInterpreterTool - Assert.NotNull(result.Tools); - Assert.Equal(3, result.Tools!.Count()); - var toolNames = result.Tools!.Select(t => t.Name).ToList(); - Assert.Contains("load_skill", toolNames); - Assert.Contains("read_skill_resource", toolNames); - Assert.Single(result.Tools!, t => t is HostedCodeInterpreterTool); - } - - [Fact] - public async Task Provider_WithoutExecutor_DoesNotIncludeExecutorToolsAsync() - { - // Arrange - CreateSkill(this._testRoot, "no-exec-skill", "No executor test", "Body."); - var provider = new FileAgentSkillsProvider(this._testRoot); - var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext()); - - // Act - var result = await provider.InvokingAsync(invokingContext, CancellationToken.None); - - // Assert — should only have the two base tools - Assert.NotNull(result.Tools); - Assert.Equal(2, result.Tools!.Count()); - } - - [Fact] - public async Task Provider_WithHostedCodeInterpreter_MergesScriptInstructionsIntoPromptAsync() - { - // Arrange - CreateSkill(this._testRoot, "merge-skill", "Merge test", "Body."); - var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter(); - var options = new FileAgentSkillsProviderOptions { ScriptExecutor = executor }; - var provider = new FileAgentSkillsProvider(this._testRoot, options); - var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext()); - - // Act - var result = await provider.InvokingAsync(invokingContext, CancellationToken.None); - - // Assert — prompt should contain both the skill listing and the executor's script instructions - Assert.NotNull(result.Instructions); - string instructions = result.Instructions!; - - // Skill listing is present - Assert.Contains("merge-skill", instructions); - Assert.Contains("Merge test", instructions); - - // Hosted code interpreter script instructions are merged into the prompt - Assert.Contains("executable scripts", instructions); - Assert.Contains("read_skill_resource", instructions); - Assert.Contains("Execute the script using the code interpreter", instructions); - } - - private static void CreateSkill(string root, string name, string description, string body) - { - string skillDir = Path.Combine(root, name); - Directory.CreateDirectory(skillDir); - File.WriteAllText( - Path.Combine(skillDir, "SKILL.md"), - $"---\nname: {name}\ndescription: {description}\n---\n{body}"); - } -} diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillsProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillsProviderTests.cs index f95f3a7080..6bfaf1b546 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: {skills}" + SkillsInstructionPrompt = "Custom template: {0}" }; var provider = new FileAgentSkillsProvider(this._testRoot, options); var inputContext = new AIContext(); @@ -110,6 +110,21 @@ 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/HostedCodeInterpreterFileAgentSkillScriptExecutorTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterFileAgentSkillScriptExecutorTests.cs deleted file mode 100644 index 84a4446779..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/HostedCodeInterpreterFileAgentSkillScriptExecutorTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Microsoft.Agents.AI.UnitTests.AgentSkills; - -/// -/// Unit tests for . -/// -public sealed class HostedCodeInterpreterFileAgentSkillScriptExecutorTests -{ - private static readonly FileAgentSkillScriptExecutionContext s_emptyContext = new( - new Dictionary(StringComparer.OrdinalIgnoreCase), - new FileAgentSkillLoader(NullLogger.Instance)); - - [Fact] - public void GetExecutionDetails_ReturnsScriptExecutionGuidance() - { - // Arrange - var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor(); - - // Act - var details = executor.GetExecutionDetails(s_emptyContext); - - // Assert - Assert.NotNull(details.Instructions); - Assert.Contains("read_skill_resource", details.Instructions); - Assert.Contains("code interpreter", details.Instructions); - } - - [Fact] - public void GetExecutionDetails_ReturnsSingleHostedCodeInterpreterTool() - { - // Arrange - var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor(); - - // Act - var details = executor.GetExecutionDetails(s_emptyContext); - - // Assert - Assert.NotNull(details.Tools); - Assert.Single(details.Tools!); - Assert.IsType(details.Tools![0]); - } - - [Fact] - public void GetExecutionDetails_ReturnsSameInstanceOnMultipleCalls() - { - // Arrange - var executor = new HostedCodeInterpreterFileAgentSkillScriptExecutor(); - - // Act - var details1 = executor.GetExecutionDetails(s_emptyContext); - var details2 = executor.GetExecutionDetails(s_emptyContext); - - // Assert — static details should be reused - Assert.Same(details1, details2); - } - - [Fact] - public void FactoryMethod_ReturnsHostedCodeInterpreterFileAgentSkillScriptExecutor() - { - // Act - var executor = FileAgentSkillScriptExecutor.HostedCodeInterpreter(); - - // Assert - Assert.IsType(executor); - } -}