diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Agent_Step03_ClassBasedSkills.csproj b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Agent_Step03_ClassBasedSkills.csproj
index fd3d71fe7e..d7233702ac 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Agent_Step03_ClassBasedSkills.csproj
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Agent_Step03_ClassBasedSkills.csproj
@@ -6,7 +6,7 @@
enableenable
- $(NoWarn);MAAI001
+ $(NoWarn);MAAI001;IDE0051
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
index 8be991598a..7f5e356a60 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
@@ -1,8 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
-// This sample demonstrates how to define Agent Skills as C# classes using AgentClassSkill.
-// Class-based skills bundle all components into a single class implementation.
+// This sample demonstrates how to define Agent Skills as C# classes using AgentClassSkill
+// with attributes for automatic script and resource discovery.
+using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
@@ -44,17 +45,16 @@
Console.WriteLine($"Agent: {response.Text}");
///
-/// A unit-converter skill defined as a C# class.
+/// A unit-converter skill defined as a C# class using attributes for discovery.
///
///
-/// Class-based skills bundle all components (name, description, body, resources, scripts)
-/// into a single class.
+/// Properties annotated with are automatically
+/// discovered as skill resources, and methods annotated with
+/// are automatically discovered as skill scripts. Alternatively,
+/// and can be overridden.
///
-internal sealed class UnitConverterSkill : AgentClassSkill
+internal sealed class UnitConverterSkill : AgentClassSkill
{
- private IReadOnlyList? _resources;
- private IReadOnlyList? _scripts;
-
///
public override AgentSkillFrontmatter Frontmatter { get; } = new(
"unit-converter",
@@ -69,31 +69,40 @@ Use this skill when the user asks to convert between units.
3. Present the result clearly with both units.
""";
- ///
- public override IReadOnlyList? Resources => this._resources ??=
- [
- CreateResource(
- "conversion-table",
- """
- # Conversion Tables
-
- Formula: **result = value × factor**
-
- | From | To | Factor |
- |-------------|-------------|----------|
- | miles | kilometers | 1.60934 |
- | kilometers | miles | 0.621371 |
- | pounds | kilograms | 0.453592 |
- | kilograms | pounds | 2.20462 |
- """),
- ];
-
- ///
- public override IReadOnlyList? Scripts => this._scripts ??=
- [
- CreateScript("convert", ConvertUnits),
- ];
+ ///
+ /// Gets the used to marshal parameters and return values
+ /// for scripts and resources.
+ ///
+ ///
+ /// This override is not necessary for this sample, but can be used to provide custom
+ /// serialization options, for example a source-generated JsonTypeInfoResolver
+ /// for Native AOT compatibility.
+ ///
+ protected override JsonSerializerOptions? SerializerOptions => null;
+
+ ///
+ /// A conversion table resource providing multiplication factors.
+ ///
+ [AgentSkillResource("conversion-table")]
+ [Description("Lookup table of multiplication factors for common unit conversions.")]
+ public string ConversionTable => """
+ # Conversion Tables
+
+ Formula: **result = value × factor**
+
+ | From | To | Factor |
+ |-------------|-------------|----------|
+ | miles | kilometers | 1.60934 |
+ | kilometers | miles | 0.621371 |
+ | pounds | kilograms | 0.453592 |
+ | kilograms | pounds | 2.20462 |
+ """;
+ ///
+ /// Converts a value by the given factor.
+ ///
+ [AgentSkillScript("convert")]
+ [Description("Multiplies a value by a conversion factor and returns the result as JSON.")]
private static string ConvertUnits(double value, double factor)
{
double result = Math.Round(value * factor, 4);
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/README.md b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/README.md
index 3525bb7a98..028cb05a37 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/README.md
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/README.md
@@ -1,12 +1,16 @@
# Class-Based Agent Skills Sample
-This sample demonstrates how to define **Agent Skills as C# classes** using `AgentClassSkill`.
+This sample demonstrates how to define **Agent Skills as C# classes** using `AgentClassSkill`
+with **attributes** for automatic script and resource discovery.
## What it demonstrates
- Creating skills as classes that extend `AgentClassSkill`
-- Bundling name, description, body, resources, and scripts into a single class
+- Using `[AgentSkillResource]` on properties to define resources
+- Using `[AgentSkillScript]` on methods to define scripts
+- Automatic discovery (no need to override `Resources`/`Scripts`)
- Using the `AgentSkillsProvider` constructor with class-based skills
+- Overriding `SerializerOptions` for Native AOT compatibility
## Skills Included
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj b/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj
index 7e7e9ef0fa..01abf37da8 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Agent_Step04_MixedSkills.csproj
@@ -6,7 +6,7 @@
enableenable
- $(NoWarn);MAAI001
+ $(NoWarn);MAAI001;IDE0051
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Program.cs
index ab5da71a3c..28d5cb9ee9 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step04_MixedSkills/Program.cs
@@ -8,11 +8,12 @@
// Three different skill sources are registered here:
// 1. File-based: unit-converter (miles↔km, pounds↔kg) from SKILL.md on disk
// 2. Code-defined: volume-converter (gallons↔liters) using AgentInlineSkill
-// 3. Class-based: temperature-converter (°F↔°C↔K) using AgentClassSkill
+// 3. Class-based: temperature-converter (°F↔°C↔K) using AgentClassSkill with attributes
//
// For simpler, single-source scenarios, see the earlier steps in this sample series
// (e.g., Step01 for file-based, Step02 for code-defined, Step03 for class-based).
+using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
@@ -89,13 +90,15 @@ 1. Review the volume-conversion-table resource to find the correct factor.
Console.WriteLine($"Agent: {response.Text}");
///
-/// A temperature-converter skill defined as a C# class.
+/// A temperature-converter skill defined as a C# class using attributes for discovery.
///
-internal sealed class TemperatureConverterSkill : AgentClassSkill
+///
+/// Properties annotated with are automatically
+/// discovered as skill resources, and methods annotated with
+/// are automatically discovered as skill scripts.
+///
+internal sealed class TemperatureConverterSkill : AgentClassSkill
{
- private IReadOnlyList? _resources;
- private IReadOnlyList? _scripts;
-
///
public override AgentSkillFrontmatter Frontmatter { get; } = new(
"temperature-converter",
@@ -110,29 +113,27 @@ Use this skill when the user asks to convert temperatures.
3. Present the result clearly with both temperature scales.
""";
- ///
- public override IReadOnlyList? Resources => this._resources ??=
- [
- CreateResource(
- "temperature-conversion-formulas",
- """
- # Temperature Conversion Formulas
-
- | From | To | Formula |
- |-------------|-------------|---------------------------|
- | Fahrenheit | Celsius | °C = (°F − 32) × 5/9 |
- | Celsius | Fahrenheit | °F = (°C × 9/5) + 32 |
- | Celsius | Kelvin | K = °C + 273.15 |
- | Kelvin | Celsius | °C = K − 273.15 |
- """),
- ];
-
- ///
- public override IReadOnlyList? Scripts => this._scripts ??=
- [
- CreateScript("convert-temperature", ConvertTemperature),
- ];
+ ///
+ /// A reference table of temperature conversion formulas.
+ ///
+ [AgentSkillResource("temperature-conversion-formulas")]
+ [Description("Formulas for converting between Fahrenheit, Celsius, and Kelvin.")]
+ public string ConversionFormulas => """
+ # Temperature Conversion Formulas
+
+ | From | To | Formula |
+ |-------------|-------------|---------------------------|
+ | Fahrenheit | Celsius | °C = (°F − 32) × 5/9 |
+ | Celsius | Fahrenheit | °F = (°C × 9/5) + 32 |
+ | Celsius | Kelvin | K = °C + 273.15 |
+ | Kelvin | Celsius | °C = K − 273.15 |
+ """;
+ ///
+ /// Converts a temperature value between scales.
+ ///
+ [AgentSkillScript("convert-temperature")]
+ [Description("Converts a temperature value from one scale to another.")]
private static string ConvertTemperature(double value, string from, string to)
{
double result = (from.ToUpperInvariant(), to.ToUpperInvariant()) switch
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj b/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj
index 959fa29167..699672ded5 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Agent_Step05_SkillsWithDI.csproj
@@ -6,7 +6,7 @@
enableenable
- $(NoWarn);MAAI001;CA1812
+ $(NoWarn);MAAI001;CA1812;IDE0051
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Program.cs
index 50b0545be3..251503a918 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step05_SkillsWithDI/Program.cs
@@ -13,6 +13,7 @@
// showing that DI works identically regardless of how the skill is defined.
// When prompted with a question spanning both domains, the agent uses both skills.
+using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
@@ -62,8 +63,8 @@ Use this skill when the user asks to convert between distance units (miles and k
// Approach 2: Class-Based Skill with DI (AgentClassSkill)
// =====================================================================
// Handles weight conversions (pounds ↔ kilograms).
-// Resources and scripts are encapsulated in a class. Factory methods
-// CreateResource and CreateScript accept delegates with IServiceProvider.
+// Resources and scripts are discovered via reflection using attributes.
+// Methods with an IServiceProvider parameter receive DI automatically.
//
// Alternatively, class-based skills can accept dependencies through their
// constructor. Register the skill class itself in the ServiceCollection and
@@ -113,14 +114,13 @@ Use this skill when the user asks to convert between distance units (miles and k
///
///
/// This skill resolves from the DI container
-/// in both its resource and script functions. This enables clean separation of
-/// concerns and testability while retaining the class-based skill pattern.
+/// in both its resource and script methods. Methods with an
+/// parameter are automatically injected by the framework. Properties and methods annotated
+/// with and
+/// are automatically discovered via reflection.
///
-internal sealed class WeightConverterSkill : AgentClassSkill
+internal sealed class WeightConverterSkill : AgentClassSkill
{
- private IReadOnlyList? _resources;
- private IReadOnlyList? _scripts;
-
///
public override AgentSkillFrontmatter Frontmatter { get; } = new(
"weight-converter",
@@ -135,25 +135,27 @@ Use this skill when the user asks to convert between weight units (pounds and ki
3. Present the result clearly with both units.
""";
- ///
- public override IReadOnlyList? Resources => this._resources ??=
- [
- CreateResource("weight-table", (IServiceProvider serviceProvider) =>
- {
- var service = serviceProvider.GetRequiredService();
- return service.GetWeightTable();
- }),
- ];
+ ///
+ /// Returns the weight conversion table from the DI-registered .
+ ///
+ [AgentSkillResource("weight-table")]
+ [Description("Lookup table of multiplication factors for weight conversions.")]
+ private static string GetWeightTable(IServiceProvider serviceProvider)
+ {
+ var service = serviceProvider.GetRequiredService();
+ return service.GetWeightTable();
+ }
- ///
- public override IReadOnlyList? Scripts => this._scripts ??=
- [
- CreateScript("convert", (double value, double factor, IServiceProvider serviceProvider) =>
- {
- var service = serviceProvider.GetRequiredService();
- return service.Convert(value, factor);
- }),
- ];
+ ///
+ /// Converts a value by the given factor using the DI-registered .
+ ///
+ [AgentSkillScript("convert")]
+ [Description("Multiplies a value by a conversion factor and returns the result as JSON.")]
+ private static string Convert(double value, double factor, IServiceProvider serviceProvider)
+ {
+ var service = serviceProvider.GetRequiredService();
+ return service.Convert(value, factor);
+ }
}
// ---------------------------------------------------------------------------
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProviderBuilder.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProviderBuilder.cs
index 0da54d0426..e49c620187 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProviderBuilder.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProviderBuilder.cs
@@ -21,7 +21,7 @@ namespace Microsoft.Agents.AI;
///
///
/// Mixed skill types — combine file-based, code-defined (),
-/// and class-based () skills in a single provider.
+/// and class-based () skills in a single provider.
/// Multiple file script runners — use different script runners for different
/// file skill directories via per-source scriptRunner parameters on
/// / .
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentClassSkill.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentClassSkill.cs
index 4febb8bc79..b44f423bc2 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentClassSkill.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentClassSkill.cs
@@ -1,8 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
+using System.Collections.Generic;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
using System.Text.Json;
+using System.Threading;
using Microsoft.Extensions.AI;
using Microsoft.Shared.DiagnosticIds;
@@ -11,17 +15,55 @@ namespace Microsoft.Agents.AI;
///
/// Abstract base class for defining skills as C# classes that bundle all components together.
///
+///
+/// The concrete skill type. This type parameter is annotated with
+/// to ensure that the IL trimmer and Native AOT compiler
+/// preserve the members needed for attribute-based discovery.
+///
///
///
/// Inherit from this class to create a self-contained skill definition. Override the abstract
-/// properties to provide name, description, and instructions. Use ,
-/// , and to define
-/// inline resources and scripts.
+/// properties to provide name, description, and instructions.
+///
+///
+/// Scripts and resources can be defined in two ways:
+///
+///
+/// Attribute-based (recommended): Annotate methods with to define scripts,
+/// and properties or methods with to define resources. These are automatically
+/// discovered via reflection on . This approach is compatible with Native AOT.
+///
+///
+/// Explicit override: Override and , using
+/// , ,
+/// and to define inline resources and scripts. This approach is also compatible with Native AOT.
+///
+///
+///
+///
+/// Multi-level inheritance limitation: Discovery reflects only on ,
+/// so if a further-derived subclass adds new attributed members, they will not be discovered unless
+/// that subclass also uses the CRTP pattern
+/// (e.g., class SpecialSkill : AgentClassSkill<SpecialSkill>).
///
///
///
///
-/// public class PdfFormatterSkill : AgentClassSkill
+/// // Attribute-based approach (recommended, AOT-compatible):
+/// public class PdfFormatterSkill : AgentClassSkill<PdfFormatterSkill>
+/// {
+/// public override AgentSkillFrontmatter Frontmatter { get; } = new("pdf-formatter", "Format documents as PDF.");
+/// protected override string Instructions => "Use this skill to format documents...";
+///
+/// [AgentSkillResource("template")]
+/// public string Template => "Use this template...";
+///
+/// [AgentSkillScript("format-pdf")]
+/// private static string FormatPdf(string content) => content;
+/// }
+///
+/// // Explicit override approach (AOT-compatible):
+/// public class ExplicitPdfFormatterSkill : AgentClassSkill<ExplicitPdfFormatterSkill>
/// {
/// private IReadOnlyList<AgentSkillResource>? _resources;
/// private IReadOnlyList<AgentSkillScript>? _scripts;
@@ -44,15 +86,41 @@ namespace Microsoft.Agents.AI;
///
///
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
-public abstract class AgentClassSkill : AgentSkill
+public abstract class AgentClassSkill<
+ [DynamicallyAccessedMembers(
+ DynamicallyAccessedMemberTypes.PublicProperties |
+ DynamicallyAccessedMemberTypes.NonPublicProperties |
+ DynamicallyAccessedMemberTypes.PublicMethods |
+ DynamicallyAccessedMemberTypes.NonPublicMethods)] TSelf>
+ : AgentSkill
+ where TSelf : AgentClassSkill
{
+ private const BindingFlags DiscoveryBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
+
private string? _content;
+ private bool _resourcesDiscovered;
+ private bool _scriptsDiscovered;
+ private IReadOnlyList? _reflectedResources;
+ private IReadOnlyList? _reflectedScripts;
///
/// Gets the raw instructions text for this skill.
///
protected abstract string Instructions { get; }
+ ///
+ /// Gets the used to marshal parameters and return values
+ /// for scripts and resources.
+ ///
+ ///
+ /// Override this property to provide custom serialization options. This value is used by
+ /// reflection-discovered scripts and resources, and also as a fallback by
+ /// and when no
+ /// explicit is passed to those methods.
+ /// The default value is , which causes to be used.
+ ///
+ protected virtual JsonSerializerOptions? SerializerOptions => null;
+
///
///
/// Returns a synthesized XML document containing name, description, instructions, resources, and scripts.
@@ -65,6 +133,48 @@ public abstract class AgentClassSkill : AgentSkill
this.Resources,
this.Scripts);
+ ///
+ ///
+ /// Returns resources discovered via reflection by scanning for
+ /// members annotated with . This discovery is
+ /// compatible with Native AOT because is annotated with
+ /// . The result is cached after the first access.
+ ///
+ public override IReadOnlyList? Resources
+ {
+ get
+ {
+ if (!this._resourcesDiscovered)
+ {
+ this._reflectedResources = this.DiscoverResources();
+ this._resourcesDiscovered = true;
+ }
+
+ return this._reflectedResources;
+ }
+ }
+
+ ///
+ ///
+ /// Returns scripts discovered via reflection by scanning for
+ /// methods annotated with . This discovery is
+ /// compatible with Native AOT because is annotated with
+ /// . The result is cached after the first access.
+ ///
+ public override IReadOnlyList? Scripts
+ {
+ get
+ {
+ if (!this._scriptsDiscovered)
+ {
+ this._reflectedScripts = this.DiscoverScripts();
+ this._scriptsDiscovered = true;
+ }
+
+ return this._reflectedScripts;
+ }
+ }
+
///
/// Creates a skill resource backed by a static value.
///
@@ -72,7 +182,7 @@ public abstract class AgentClassSkill : AgentSkill
/// The static resource value.
/// An optional description of the resource.
/// A new instance.
- protected static AgentSkillResource CreateResource(string name, object value, string? description = null)
+ protected AgentSkillResource CreateResource(string name, object value, string? description = null)
=> new AgentInlineSkillResource(name, value, description);
///
@@ -83,11 +193,11 @@ protected static AgentSkillResource CreateResource(string name, object value, st
/// An optional description of the resource.
///
/// Optional used to marshal the delegate's parameters and return value.
- /// When , is used.
+ /// When , falls back to .
///
/// A new instance.
- protected static AgentSkillResource CreateResource(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
- => new AgentInlineSkillResource(name, method, description, serializerOptions);
+ protected AgentSkillResource CreateResource(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
+ => new AgentInlineSkillResource(name, method, description, serializerOptions ?? this.SerializerOptions);
///
/// Creates a skill script backed by a delegate.
@@ -97,9 +207,129 @@ protected static AgentSkillResource CreateResource(string name, Delegate method,
/// An optional description of the script.
///
/// Optional used to marshal the delegate's parameters and return value.
- /// When , is used.
+ /// When , falls back to .
///
/// A new instance.
- protected static AgentSkillScript CreateScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
- => new AgentInlineSkillScript(name, method, description, serializerOptions);
+ protected AgentSkillScript CreateScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null)
+ => new AgentInlineSkillScript(name, method, description, serializerOptions ?? this.SerializerOptions);
+
+ private List? DiscoverResources()
+ {
+ List? resources = null;
+
+ var selfType = typeof(TSelf);
+
+ // Discover resources from properties annotated with [AgentSkillResource].
+ foreach (var property in selfType.GetProperties(DiscoveryBindingFlags))
+ {
+ var attr = property.GetCustomAttribute();
+ if (attr is null)
+ {
+ continue;
+ }
+
+ var getter = property.GetGetMethod(nonPublic: true);
+ if (getter is null)
+ {
+ continue;
+ }
+
+ // Indexer properties have getter parameters and cannot be used as resources
+ // because ReadAsync invokes the underlying AIFunction with no named arguments.
+ if (getter.GetParameters().Length > 0)
+ {
+ throw new InvalidOperationException(
+ $"Property '{property.Name}' on type '{selfType.Name}' is an indexer and cannot be used as a skill resource. " +
+ "Remove the [AgentSkillResource] attribute or use a non-indexer property.");
+ }
+
+ var name = attr.Name ?? property.Name;
+ if (resources?.Exists(r => r.Name == name) == true)
+ {
+ throw new InvalidOperationException($"Skill '{this.Frontmatter.Name}' already has a resource named '{name}'. Ensure each [AgentSkillResource] has a unique name.");
+ }
+
+ resources ??= [];
+ resources.Add(new AgentInlineSkillResource(
+ name: name,
+ method: getter,
+ target: getter.IsStatic ? null : this,
+ description: property.GetCustomAttribute()?.Description,
+ serializerOptions: this.SerializerOptions));
+ }
+
+ // Discover resources from methods annotated with [AgentSkillResource].
+ foreach (var method in selfType.GetMethods(DiscoveryBindingFlags))
+ {
+ var attr = method.GetCustomAttribute();
+ if (attr is null)
+ {
+ continue;
+ }
+
+ ValidateResourceMethodParameters(method, selfType);
+
+ var name = attr.Name ?? method.Name;
+ if (resources?.Exists(r => r.Name == name) == true)
+ {
+ throw new InvalidOperationException($"Skill '{this.Frontmatter.Name}' already has a resource named '{name}'. Ensure each [AgentSkillResource] has a unique name.");
+ }
+
+ resources ??= [];
+ resources.Add(new AgentInlineSkillResource(
+ name: name,
+ method: method,
+ target: method.IsStatic ? null : this,
+ description: method.GetCustomAttribute()?.Description,
+ serializerOptions: this.SerializerOptions));
+ }
+
+ return resources;
+ }
+
+ private static void ValidateResourceMethodParameters(MethodInfo method, Type skillType)
+ {
+ foreach (var param in method.GetParameters())
+ {
+ if (param.ParameterType != typeof(IServiceProvider) &&
+ param.ParameterType != typeof(CancellationToken))
+ {
+ throw new InvalidOperationException(
+ $"Method '{method.Name}' on type '{skillType.Name}' has parameter '{param.Name}' of type " +
+ $"'{param.ParameterType}' which cannot be supplied when reading a resource. " +
+ "Resource methods may only accept IServiceProvider and/or CancellationToken parameters. " +
+ "Remove the [AgentSkillResource] attribute or change the method signature.");
+ }
+ }
+ }
+
+ private List? DiscoverScripts()
+ {
+ List? scripts = null;
+
+ foreach (var method in typeof(TSelf).GetMethods(DiscoveryBindingFlags))
+ {
+ var attr = method.GetCustomAttribute();
+ if (attr is null)
+ {
+ continue;
+ }
+
+ var name = attr.Name ?? method.Name;
+ if (scripts?.Exists(s => s.Name == name) == true)
+ {
+ throw new InvalidOperationException($"Skill '{this.Frontmatter.Name}' already has a script named '{name}'. Ensure each [AgentSkillScript] has a unique name.");
+ }
+
+ scripts ??= [];
+ scripts.Add(new AgentInlineSkillScript(
+ name: name,
+ method: method,
+ target: method.IsStatic ? null : this,
+ description: method.GetCustomAttribute()?.Description,
+ serializerOptions: this.SerializerOptions));
+ }
+
+ return scripts;
+ }
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillResource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillResource.cs
index 5e032f073f..556cfdc781 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillResource.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/Programmatic/AgentInlineSkillResource.cs
@@ -2,6 +2,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -54,6 +55,28 @@ public AgentInlineSkillResource(string name, Delegate method, string? descriptio
this._function = AIFunctionFactory.Create(method, options);
}
+ ///
+ /// Initializes a new instance of the class from a .
+ /// The method is invoked via an each time is called,
+ /// producing a dynamic (computed) value.
+ ///
+ /// The resource name.
+ /// A method that produces the resource value when requested.
+ /// The target instance for instance methods, or for static methods.
+ /// An optional description of the resource.
+ ///
+ /// Optional used to marshal the method's parameters and return value.
+ /// When , is used.
+ ///
+ public AgentInlineSkillResource(string name, MethodInfo method, object? target, string? description = null, JsonSerializerOptions? serializerOptions = null)
+ : base(name, description)
+ {
+ Throw.IfNull(method);
+
+ var options = new AIFunctionFactoryOptions { Name = this.Name, SerializerOptions = serializerOptions };
+ this._function = AIFunctionFactory.Create(method, target, options);
+ }
+
///
public override async Task