// Copyright (c) Microsoft. All rights reserved. using System.ClientModel; using System.ClientModel.Primitives; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using Azure.AI.Projects.OpenAI; using Microsoft.Agents.AI; using Microsoft.Agents.AI.AzureAI; using Microsoft.Extensions.AI; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; using OpenAI; using OpenAI.Responses; namespace Azure.AI.Projects; /// /// Provides extension methods for . /// [Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)] public static partial class AzureAIProjectChatClientExtensions { /// /// Uses an existing server side agent, wrapped as a using the provided and . /// /// The to create the with. Cannot be . /// The representing the name and version of the server side agent to create a for. Cannot be . /// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools. /// Provides a way to customize the creation of the underlying used by the agent. /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations based on the latest version of the named Azure AI Agent. /// Thrown when or is . /// The agent with the specified name was not found. /// /// When instantiating a by using an , minimal information will be available about the agent in the instance level, and any logic that relies /// on to retrieve information about the agent like will receive as the result. /// public static ChatClientAgent AsAIAgent( this AIProjectClient aiProjectClient, AgentReference agentReference, IList? tools = null, Func? clientFactory = null, IServiceProvider? services = null) { Throw.IfNull(aiProjectClient); Throw.IfNull(agentReference); ThrowIfInvalidAgentName(agentReference.Name); return AsChatClientAgent( aiProjectClient, agentReference, new ChatClientAgentOptions() { Id = $"{agentReference.Name}:{agentReference.Version}", Name = agentReference.Name, ChatOptions = new() { Tools = tools }, }, clientFactory, services); } /// /// Asynchronously retrieves an existing server side agent, wrapped as a using the provided . /// /// The to create the with. Cannot be . /// The name of the server side agent to create a for. Cannot be or whitespace. /// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools. /// Provides a way to customize the creation of the underlying used by the agent. /// An optional to use for resolving services required by the instances being invoked. /// The to monitor for cancellation requests. The default is . /// A instance that can be used to perform operations based on the latest version of the named Azure AI Agent. /// Thrown when or is . /// Thrown when is empty or whitespace, or when the agent with the specified name was not found. /// The agent with the specified name was not found. public static async Task GetAIAgentAsync( this AIProjectClient aiProjectClient, string name, IList? tools = null, Func? clientFactory = null, IServiceProvider? services = null, CancellationToken cancellationToken = default) { Throw.IfNull(aiProjectClient); ThrowIfInvalidAgentName(name); AgentRecord agentRecord = await GetAgentRecordByNameAsync(aiProjectClient, name, cancellationToken).ConfigureAwait(false); return AsAIAgent( aiProjectClient, agentRecord, tools, clientFactory, services); } /// /// Uses an existing server side agent, wrapped as a using the provided and . /// /// The client used to interact with Azure AI Agents. Cannot be . /// The agent record to be converted. The latest version will be used. Cannot be . /// The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools. /// Provides a way to customize the creation of the underlying used by the agent. /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations based on the latest version of the Azure AI Agent. /// Thrown when or is . public static ChatClientAgent AsAIAgent( this AIProjectClient aiProjectClient, AgentRecord agentRecord, IList? tools = null, Func? clientFactory = null, IServiceProvider? services = null) { Throw.IfNull(aiProjectClient); Throw.IfNull(agentRecord); var allowDeclarativeMode = tools is not { Count: > 0 }; return AsChatClientAgent( aiProjectClient, agentRecord, tools, clientFactory, !allowDeclarativeMode, services); } /// /// Uses an existing server side agent, wrapped as a using the provided and . /// /// The client used to interact with Azure AI Agents. Cannot be . /// The agent version to be converted. Cannot be . /// In-process invocable tools to be provided. If no tools are provided manual handling will be necessary to invoke in-process tools. /// Provides a way to customize the creation of the underlying used by the agent. /// An optional to use for resolving services required by the instances being invoked. /// A instance that can be used to perform operations based on the provided version of the Azure AI Agent. /// Thrown when or is . public static ChatClientAgent AsAIAgent( this AIProjectClient aiProjectClient, AgentVersion agentVersion, IList? tools = null, Func? clientFactory = null, IServiceProvider? services = null) { Throw.IfNull(aiProjectClient); Throw.IfNull(agentVersion); var allowDeclarativeMode = tools is not { Count: > 0 }; return AsChatClientAgent( aiProjectClient, agentVersion, tools, clientFactory, !allowDeclarativeMode, services); } /// /// Asynchronously retrieves an existing server side agent, wrapped as a using the provided . /// /// The client used to manage and interact with AI agents. Cannot be . /// The options for creating the agent. Cannot be . /// A factory function to customize the creation of the chat client used by the agent. /// An optional to use for resolving services required by the instances being invoked. /// A to cancel the operation if needed. /// A instance that can be used to perform operations on the newly created agent. /// Thrown when or is . public static async Task GetAIAgentAsync( this AIProjectClient aiProjectClient, ChatClientAgentOptions options, Func? clientFactory = null, IServiceProvider? services = null, CancellationToken cancellationToken = default) { Throw.IfNull(aiProjectClient); Throw.IfNull(options); if (string.IsNullOrWhiteSpace(options.Name)) { throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options)); } ThrowIfInvalidAgentName(options.Name); AgentRecord agentRecord = await GetAgentRecordByNameAsync(aiProjectClient, options.Name, cancellationToken).ConfigureAwait(false); var agentVersion = agentRecord.Versions.Latest; var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: true); return AsChatClientAgent( aiProjectClient, agentVersion, agentOptions, clientFactory, services); } /// /// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a . /// /// The client used to manage and interact with AI agents. Cannot be . /// The name for the agent. /// The name of the model to use for the agent. Cannot be or whitespace. /// The instructions that guide the agent's behavior. Cannot be or whitespace. /// The description for the agent. /// The tools to use when interacting with the agent, this is required when using prompt agent definitions with tools. /// A factory function to customize the creation of the chat client used by the agent. /// An optional to use for resolving services required by the instances being invoked. /// A token to monitor for cancellation requests. /// A instance that can be used to perform operations on the newly created agent. /// Thrown when , , or is . /// Thrown when or is empty or whitespace. /// When using prompt agent definitions with tools the parameter needs to be provided. public static Task CreateAIAgentAsync( this AIProjectClient aiProjectClient, string name, string model, string instructions, string? description = null, IList? tools = null, Func? clientFactory = null, IServiceProvider? services = null, CancellationToken cancellationToken = default) { Throw.IfNull(aiProjectClient); ThrowIfInvalidAgentName(name); Throw.IfNullOrWhitespace(model); Throw.IfNullOrWhitespace(instructions); return CreateAIAgentAsync( aiProjectClient, name, tools, new AgentVersionCreationOptions(new PromptAgentDefinition(model) { Instructions = instructions }) { Description = description }, clientFactory, services, cancellationToken); } /// /// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a . /// /// The client used to manage and interact with AI agents. Cannot be . /// The name of the model to use for the agent. Cannot be or whitespace. /// The options for creating the agent. Cannot be . /// A factory function to customize the creation of the chat client used by the agent. /// An optional to use for resolving services required by the instances being invoked. /// A to cancel the operation if needed. /// A instance that can be used to perform operations on the newly created agent. /// Thrown when or is . /// Thrown when is empty or whitespace, or when the agent name is not provided in the options. public static async Task CreateAIAgentAsync( this AIProjectClient aiProjectClient, string model, ChatClientAgentOptions options, Func? clientFactory = null, IServiceProvider? services = null, CancellationToken cancellationToken = default) { Throw.IfNull(aiProjectClient); Throw.IfNull(options); Throw.IfNullOrWhitespace(model); const bool RequireInvocableTools = true; if (string.IsNullOrWhiteSpace(options.Name)) { throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options)); } ThrowIfInvalidAgentName(options.Name); PromptAgentDefinition agentDefinition = new(model) { Instructions = options.ChatOptions?.Instructions, Temperature = options.ChatOptions?.Temperature, TopP = options.ChatOptions?.TopP, TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) } }; // Map reasoning options from the abstraction-level ChatOptions.Reasoning, // falling back to extracting from the raw representation factory for breaking glass scenarios. if (options.ChatOptions?.Reasoning is { } reasoning) { agentDefinition.ReasoningOptions = ToResponseReasoningOptions(reasoning); } else if (options.ChatOptions?.RawRepresentationFactory?.Invoke(new NoOpChatClient()) is CreateResponseOptions respCreationOptions) { agentDefinition.ReasoningOptions = respCreationOptions.ReasoningOptions; } ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools); AgentVersionCreationOptions? creationOptions = new(agentDefinition); if (!string.IsNullOrWhiteSpace(options.Description)) { creationOptions.Description = options.Description; } AgentVersion agentVersion = await CreateAgentVersionWithProtocolAsync(aiProjectClient, options.Name, creationOptions, cancellationToken).ConfigureAwait(false); var agentOptions = CreateChatClientAgentOptions(agentVersion, options, RequireInvocableTools); return AsChatClientAgent( aiProjectClient, agentVersion, agentOptions, clientFactory, services); } /// /// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a . /// parameters. /// /// The client used to manage and interact with AI agents. Cannot be . /// The name for the agent. /// Settings that control the creation of the agent. /// A factory function to customize the creation of the chat client used by the agent. /// A token to monitor for cancellation requests. /// A instance that can be used to perform operations on the newly created agent. /// Thrown when or is . /// /// When using this extension method with a the tools are only declarative and not invocable. /// Invocation of any in-process tools will need to be handled manually. /// public static Task CreateAIAgentAsync( this AIProjectClient aiProjectClient, string name, AgentVersionCreationOptions creationOptions, Func? clientFactory = null, CancellationToken cancellationToken = default) { Throw.IfNull(aiProjectClient); ThrowIfInvalidAgentName(name); Throw.IfNull(creationOptions); return CreateAIAgentAsync( aiProjectClient, name, tools: null, creationOptions, clientFactory, services: null, cancellationToken); } #region Private private static readonly ModelReaderWriterOptions s_modelWriterOptionsWire = new("W"); /// /// Asynchronously retrieves an agent record by name using the Protocol method with user-agent header. /// private static async Task GetAgentRecordByNameAsync(AIProjectClient aiProjectClient, string agentName, CancellationToken cancellationToken) { ClientResult protocolResponse = await aiProjectClient.Agents.GetAgentAsync(agentName, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false); var rawResponse = protocolResponse.GetRawResponse(); AgentRecord? result = ModelReaderWriter.Read(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsOpenAIContext.Default); return ClientResult.FromOptionalValue(result, rawResponse).Value! ?? throw new InvalidOperationException($"Agent with name '{agentName}' not found."); } /// /// Asynchronously creates an agent version using the Protocol method with user-agent header. /// private static async Task CreateAgentVersionWithProtocolAsync(AIProjectClient aiProjectClient, string agentName, AgentVersionCreationOptions creationOptions, CancellationToken cancellationToken) { using BinaryContent protocolRequest = BinaryContent.Create(ModelReaderWriter.Write(creationOptions, ModelReaderWriterOptions.Json, AzureAIProjectsContext.Default)); ClientResult protocolResponse = await aiProjectClient.Agents.CreateAgentVersionAsync(agentName, protocolRequest, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false); var rawResponse = protocolResponse.GetRawResponse(); AgentVersion? result = ModelReaderWriter.Read(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsOpenAIContext.Default); return ClientResult.FromValue(result, rawResponse).Value!; } private static async Task CreateAIAgentAsync( this AIProjectClient aiProjectClient, string name, IList? tools, AgentVersionCreationOptions creationOptions, Func? clientFactory, IServiceProvider? services, CancellationToken cancellationToken) { var allowDeclarativeMode = tools is not { Count: > 0 }; if (!allowDeclarativeMode) { ApplyToolsToAgentDefinition(creationOptions.Definition, tools); } AgentVersion agentVersion = await CreateAgentVersionWithProtocolAsync(aiProjectClient, name, creationOptions, cancellationToken).ConfigureAwait(false); return AsChatClientAgent( aiProjectClient, agentVersion, tools, clientFactory, !allowDeclarativeMode, services); } /// This method creates an with the specified ChatClientAgentOptions. private static ChatClientAgent AsChatClientAgent( AIProjectClient aiProjectClient, AgentVersion agentVersion, ChatClientAgentOptions agentOptions, Func? clientFactory, IServiceProvider? services) { IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentVersion, agentOptions.ChatOptions); if (clientFactory is not null) { chatClient = clientFactory(chatClient); } return new ChatClientAgent(chatClient, agentOptions, services: services); } /// This method creates an with the specified ChatClientAgentOptions. private static ChatClientAgent AsChatClientAgent( AIProjectClient aiProjectClient, AgentRecord agentRecord, ChatClientAgentOptions agentOptions, Func? clientFactory, IServiceProvider? services) { IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentRecord, agentOptions.ChatOptions); if (clientFactory is not null) { chatClient = clientFactory(chatClient); } return new ChatClientAgent(chatClient, agentOptions, services: services); } /// This method creates an with the specified ChatClientAgentOptions. private static ChatClientAgent AsChatClientAgent( AIProjectClient aiProjectClient, AgentReference agentReference, ChatClientAgentOptions agentOptions, Func? clientFactory, IServiceProvider? services) { IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentReference, defaultModelId: null, agentOptions.ChatOptions); if (clientFactory is not null) { chatClient = clientFactory(chatClient); } return new ChatClientAgent(chatClient, agentOptions, services: services); } /// This method creates an with a auto-generated ChatClientAgentOptions from the specified configuration parameters. private static ChatClientAgent AsChatClientAgent( AIProjectClient AIProjectClient, AgentVersion agentVersion, IList? tools, Func? clientFactory, bool requireInvocableTools, IServiceProvider? services) => AsChatClientAgent( AIProjectClient, agentVersion, CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools), clientFactory, services); /// This method creates an with a auto-generated ChatClientAgentOptions from the specified configuration parameters. private static ChatClientAgent AsChatClientAgent( AIProjectClient AIProjectClient, AgentRecord agentRecord, IList? tools, Func? clientFactory, bool requireInvocableTools, IServiceProvider? services) => AsChatClientAgent( AIProjectClient, agentRecord, CreateChatClientAgentOptions(agentRecord.Versions.Latest, new ChatOptions() { Tools = tools }, requireInvocableTools), clientFactory, services); /// /// This method creates for the specified and the provided tools. /// /// The agent version. /// The to use when interacting with the agent. /// Indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them. /// The created . /// Thrown when the agent definition requires in-process tools but none were provided. /// Thrown when the agent definition required tools were not provided. /// /// This method rebuilds the agent options from the agent definition returned by the version and combine with the in-proc tools when provided /// this ensures that all required tools are provided and the definition of the agent options are consistent with the agent definition coming from the server. /// private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatOptions? chatOptions, bool requireInvocableTools) { var agentDefinition = agentVersion.Definition; List? agentTools = null; if (agentDefinition is PromptAgentDefinition { Tools: { Count: > 0 } definitionTools }) { // Check if no tools were provided while the agent definition requires in-proc tools. if (requireInvocableTools && chatOptions?.Tools is not { Count: > 0 } && definitionTools.Any(t => t is FunctionTool)) { throw new ArgumentException("The agent definition in-process tools must be provided in the extension method tools parameter."); } // Agregate all missing tools for a single error message. List? missingTools = null; // Check function tools foreach (ResponseTool responseTool in definitionTools) { if (requireInvocableTools && responseTool is FunctionTool functionTool) { // Check if a tool with the same type and name exists in the provided tools. // When invocable tools are required, match only AIFunction. var matchingTool = chatOptions?.Tools?.FirstOrDefault(t => t is AIFunction tf && functionTool.FunctionName == tf.Name); if (matchingTool is null) { (missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}"); } else { (agentTools ??= []).Add(matchingTool!); } continue; } (agentTools ??= []).Add(responseTool.AsAITool()); } if (requireInvocableTools && missingTools is { Count: > 0 }) { throw new InvalidOperationException($"The following prompt agent definition required tools were not provided: {string.Join(", ", missingTools)}"); } } // Use the agent version's ID if available, otherwise generate one from name and version. // This handles cases where hosted agents (like MCP agents) may not have an ID assigned. var version = string.IsNullOrWhiteSpace(agentVersion.Version) ? "latest" : agentVersion.Version; var agentId = string.IsNullOrWhiteSpace(agentVersion.Id) ? $"{agentVersion.Name}:{version}" : agentVersion.Id; var agentOptions = new ChatClientAgentOptions() { Id = agentId, Name = agentVersion.Name, Description = agentVersion.Description, }; if (agentDefinition is PromptAgentDefinition promptAgentDefinition) { agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new(); agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions; agentOptions.ChatOptions.Temperature = promptAgentDefinition.Temperature; agentOptions.ChatOptions.TopP = promptAgentDefinition.TopP; } if (agentTools is { Count: > 0 }) { agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new(); agentOptions.ChatOptions.Tools = agentTools; } return agentOptions; } /// /// Creates a new instance of configured for the specified agent version and /// optional base options. /// /// The agent version to use when configuring the chat client agent options. /// An optional instance whose relevant properties will be copied to the /// returned options. If , only default values are used. /// Specifies whether the returned options must include invocable tools. Set to to require /// invocable tools; otherwise, . /// A instance configured according to the specified parameters. private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatClientAgentOptions? options, bool requireInvocableTools) { var agentOptions = CreateChatClientAgentOptions(agentVersion, options?.ChatOptions, requireInvocableTools); if (options is not null) { agentOptions.AIContextProviders = options.AIContextProviders; agentOptions.ChatHistoryProvider = options.ChatHistoryProvider; agentOptions.UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs; } return agentOptions; } /// /// Adds the specified AI tools to a prompt agent definition, while also ensuring that all invocable tools are provided. /// /// The agent definition to which the tools will be applied. Must be a PromptAgentDefinition to support tools. /// A list of AI tools to add to the agent definition. If null or empty, no tools are added. /// Thrown if tools were provided but is not a . /// When providing functions, they need to be invokable AIFunctions. private static void ApplyToolsToAgentDefinition(AgentDefinition agentDefinition, IList? tools) { if (tools is { Count: > 0 }) { if (agentDefinition is not PromptAgentDefinition promptAgentDefinition) { throw new ArgumentException("Only prompt agent definitions support tools.", nameof(agentDefinition)); } // When tools are provided, those should represent the complete set of tools for the agent definition. // This is particularly important for existing agents so no duplication happens for what was already defined. promptAgentDefinition.Tools.Clear(); foreach (var tool in tools) { // Ensure that any AIFunctions provided are In-Proc, not just the declarations. if (tool is not AIFunction && ( tool.GetService() is not null // Declarative FunctionTool converted as AsAITool() || tool is AIFunctionDeclaration)) // AIFunctionDeclaration type { throw new InvalidOperationException("When providing functions, they need to be invokable AIFunctions. AIFunctions can be created correctly using AIFunctionFactory.Create"); } promptAgentDefinition.Tools.Add( // If this is a converted ResponseTool as AITool, we can directly retrieve the ResponseTool instance from GetService. tool.GetService() // Otherwise we should be able to convert existing MEAI Tool abstractions into OpenAI ResponseTools ?? tool.AsOpenAIResponseTool() ?? throw new InvalidOperationException("The provided AITool could not be converted to a ResponseTool, ensure that the AITool was created using responseTool.AsAITool() extension.")); } } } private static ResponseTextFormat? ToOpenAIResponseTextFormat(ChatResponseFormat? format, ChatOptions? options = null) => format switch { ChatResponseFormatText => ResponseTextFormat.CreateTextFormat(), ChatResponseFormatJson jsonFormat when StrictSchemaTransformCache.GetOrCreateTransformedSchema(jsonFormat) is { } jsonSchema => ResponseTextFormat.CreateJsonSchemaFormat( jsonFormat.SchemaName ?? "json_schema", BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(jsonSchema, AgentClientJsonContext.Default.JsonElement)), jsonFormat.SchemaDescription, HasStrict(options?.AdditionalProperties)), ChatResponseFormatJson => ResponseTextFormat.CreateJsonObjectFormat(), _ => null, }; /// Key into AdditionalProperties used to store a strict option. private const string StrictKey = "strictJsonSchema"; /// Gets whether the properties specify that strict schema handling is desired. private static bool? HasStrict(IReadOnlyDictionary? additionalProperties) => additionalProperties?.TryGetValue(StrictKey, out object? strictObj) is true && strictObj is bool strictValue ? strictValue : null; /// /// Gets the JSON schema transformer cache conforming to OpenAI strict / structured output restrictions per /// https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#supported-schemas. /// private static AIJsonSchemaTransformCache StrictSchemaTransformCache { get; } = new(new() { DisallowAdditionalProperties = true, ConvertBooleanSchemas = true, MoveDefaultKeywordToDescription = true, RequireAllProperties = true, TransformSchemaNode = (ctx, node) => { // Move content from common but unsupported properties to description. In particular, we focus on properties that // the AIJsonUtilities schema generator might produce and/or that are explicitly mentioned in the OpenAI documentation. if (node is JsonObject schemaObj) { StringBuilder? additionalDescription = null; ReadOnlySpan unsupportedProperties = [ // Produced by AIJsonUtilities but not in allow list at https://platform.openai.com/docs/guides/structured-outputs#supported-properties: "contentEncoding", "contentMediaType", "not", // Explicitly mentioned at https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#key-ordering as being unsupported with some models: "minLength", "maxLength", "pattern", "format", "minimum", "maximum", "multipleOf", "patternProperties", "minItems", "maxItems", // Explicitly mentioned at https://learn.microsoft.com/azure/ai-services/openai/how-to/structured-outputs?pivots=programming-language-csharp&tabs=python-secure%2Cdotnet-entra-id#unsupported-type-specific-keywords // as being unsupported with Azure OpenAI: "unevaluatedProperties", "propertyNames", "minProperties", "maxProperties", "unevaluatedItems", "contains", "minContains", "maxContains", "uniqueItems", ]; foreach (string propName in unsupportedProperties) { if (schemaObj[propName] is { } propNode) { _ = schemaObj.Remove(propName); AppendLine(ref additionalDescription, propName, propNode); } } if (additionalDescription is not null) { schemaObj["description"] = schemaObj["description"] is { } descriptionNode && descriptionNode.GetValueKind() == JsonValueKind.String ? $"{descriptionNode.GetValue()}{Environment.NewLine}{additionalDescription}" : additionalDescription.ToString(); } return node; static void AppendLine(ref StringBuilder? sb, string propName, JsonNode propNode) { sb ??= new(); if (sb.Length > 0) { _ = sb.AppendLine(); } _ = sb.Append(propName).Append(": ").Append(propNode); } } return node; }, }); /// /// This class is a no-op implementation of to be used to honor the argument passed /// while triggering avoiding any unexpected exception on the caller implementation. /// private sealed class NoOpChatClient : IChatClient { public void Dispose() { } public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => Task.FromResult(new ChatResponse()); public object? GetService(Type serviceType, object? serviceKey = null) => null; public async IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { yield return new ChatResponseUpdate(); } } #endregion #if NET [GeneratedRegex("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$")] private static partial Regex AgentNameValidationRegex(); #else private static Regex AgentNameValidationRegex() => new("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$"); #endif private static string ThrowIfInvalidAgentName(string? name) { Throw.IfNullOrWhitespace(name); if (!AgentNameValidationRegex().IsMatch(name)) { throw new ArgumentException("Agent name must be 1-63 characters long, start and end with an alphanumeric character, and can only contain alphanumeric characters or hyphens.", nameof(name)); } return name; } private static ResponseReasoningOptions? ToResponseReasoningOptions(ReasoningOptions reasoning) { ResponseReasoningEffortLevel? effortLevel = reasoning.Effort switch { ReasoningEffort.Low => ResponseReasoningEffortLevel.Low, ReasoningEffort.Medium => ResponseReasoningEffortLevel.Medium, ReasoningEffort.High => ResponseReasoningEffortLevel.High, ReasoningEffort.ExtraHigh => ResponseReasoningEffortLevel.High, _ => null, }; ResponseReasoningSummaryVerbosity? summary = reasoning.Output switch { ReasoningOutput.Summary => ResponseReasoningSummaryVerbosity.Concise, ReasoningOutput.Full => ResponseReasoningSummaryVerbosity.Detailed, _ => null, }; if (effortLevel is null && summary is null) { return null; } return new ResponseReasoningOptions { ReasoningEffortLevel = effortLevel, ReasoningSummaryVerbosity = summary, }; } } [JsonSerializable(typeof(JsonElement))] internal sealed partial class AgentClientJsonContext : JsonSerializerContext;