// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// /// A skill script backed by a delegate. /// [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] internal sealed class AgentInlineSkillScript : AgentSkillScript { private readonly AIFunction _function; /// /// Initializes a new instance of the class from a delegate. /// The delegate's parameters and return type are automatically marshaled via . /// /// The script name. /// A method to execute when the script is invoked. Parameters are automatically deserialized from JSON. /// An optional description of the script. /// /// Optional used to marshal the delegate's parameters and return value. /// When , is used. /// public AgentInlineSkillScript(string name, Delegate method, string? description = null, JsonSerializerOptions? serializerOptions = null) : base(Throw.IfNullOrWhitespace(name), description) { Throw.IfNull(method); var options = new AIFunctionFactoryOptions { Name = this.Name, SerializerOptions = serializerOptions }; this._function = AIFunctionFactory.Create(method, options); } /// /// Initializes a new instance of the class from a . /// The method's parameters and return type are automatically marshaled via . /// /// The script name. /// The method to execute when the script is invoked. /// The target instance for instance methods, or for static methods. /// An optional description of the script. /// /// Optional used to marshal the method's parameters and return value. /// When , is used. /// public AgentInlineSkillScript(string name, MethodInfo method, object? target, string? description = null, JsonSerializerOptions? serializerOptions = null) : base(Throw.IfNullOrWhitespace(name), description) { Throw.IfNull(method); var options = new AIFunctionFactoryOptions { Name = this.Name, SerializerOptions = serializerOptions }; this._function = AIFunctionFactory.Create(method, target, options); } /// /// Gets the JSON schema describing the parameters accepted by this script, or if not available. /// public override JsonElement? ParametersSchema => this._function.JsonSchema; /// public override async Task RunAsync(AgentSkill skill, JsonElement? arguments, IServiceProvider? serviceProvider, CancellationToken cancellationToken = default) { var funcArgs = ConvertToFunctionArguments(arguments); funcArgs.Services = serviceProvider; return await this._function.InvokeAsync(funcArgs, cancellationToken).ConfigureAwait(false); } /// /// Converts a raw to for delegate invocation. /// /// /// Thrown when is provided but is not a JSON object. /// Inline skill scripts expect arguments as a JSON object whose properties map to the delegate's parameters. /// private static AIFunctionArguments ConvertToFunctionArguments(JsonElement? arguments) { if (arguments is null || arguments.Value.ValueKind == JsonValueKind.Null || arguments.Value.ValueKind == JsonValueKind.Undefined) { return []; } if (arguments.Value.ValueKind != JsonValueKind.Object) { throw new InvalidOperationException( $"Inline skill scripts expect arguments as a JSON object but received a JSON element of kind '{arguments.Value.ValueKind}'."); } var dict = new Dictionary(); foreach (var property in arguments.Value.EnumerateObject()) { dict[property.Name] = property.Value; } return new AIFunctionArguments(dict); } }