// Copyright (c) Microsoft. All rights reserved. using System; using System.Diagnostics.CodeAnalysis; using Microsoft.Agents.AI; using Microsoft.Agents.AI.Hosting; using Microsoft.Agents.AI.Hosting.OpenAI; using Microsoft.Agents.AI.Hosting.OpenAI.Conversations; using Microsoft.Agents.AI.Hosting.OpenAI.Responses; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Builder; /// /// Provides extension methods for mapping OpenAI capabilities to an . /// public static partial class MicrosoftAgentAIHostingOpenAIEndpointRouteBuilderExtensions { /// /// Maps OpenAI Responses API endpoints to the specified for the given . /// /// The to add the OpenAI Responses endpoints to. /// The builder for to map the OpenAI Responses endpoints for. public static IEndpointConventionBuilder MapOpenAIResponses(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder) => MapOpenAIResponses(endpoints, agentBuilder, path: null); /// /// Maps OpenAI Responses API endpoints to the specified for the given . /// /// The to add the OpenAI Responses endpoints to. /// The builder for to map the OpenAI Responses endpoints for. /// Custom route path for the OpenAI Responses endpoint. public static IEndpointConventionBuilder MapOpenAIResponses(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string? path) { ArgumentNullException.ThrowIfNull(endpoints); ArgumentNullException.ThrowIfNull(agentBuilder); var agent = endpoints.ServiceProvider.GetRequiredKeyedService(agentBuilder.Name); return MapOpenAIResponses(endpoints, agent, path); } /// /// Maps OpenAI Responses API endpoints to the specified for the given . /// /// The to add the OpenAI Responses endpoints to. /// The instance to map the OpenAI Responses endpoints for. public static IEndpointConventionBuilder MapOpenAIResponses(this IEndpointRouteBuilder endpoints, AIAgent agent) => MapOpenAIResponses(endpoints, agent, responsesPath: null); /// /// Maps OpenAI Responses API endpoints to the specified for the given . /// /// The to add the OpenAI Responses endpoints to. /// The instance to map the OpenAI Responses endpoints for. /// Custom route path for the responses endpoint. public static IEndpointConventionBuilder MapOpenAIResponses( this IEndpointRouteBuilder endpoints, AIAgent agent, [StringSyntax("Route")] string? responsesPath) { ArgumentNullException.ThrowIfNull(endpoints); ArgumentNullException.ThrowIfNull(agent); ArgumentException.ThrowIfNullOrWhiteSpace(agent.Name, nameof(agent.Name)); ValidateAgentName(agent.Name); responsesPath ??= $"/{agent.Name}/v1/responses"; // Create an executor for this agent var executor = new AIAgentResponseExecutor(agent); var storageOptions = endpoints.ServiceProvider.GetService() ?? new InMemoryStorageOptions(); var conversationStorage = endpoints.ServiceProvider.GetService(); var responsesService = new InMemoryResponsesService(executor, storageOptions, conversationStorage); var handlers = new ResponsesHttpHandler(responsesService); var group = endpoints.MapGroup(responsesPath); var endpointAgentName = agent.DisplayName; // Create response endpoint group.MapPost("/", handlers.CreateResponseAsync) .WithName(endpointAgentName + "/CreateResponse") .WithSummary("Creates a model response for the given input"); // Get response endpoint group.MapGet("{responseId}", handlers.GetResponseAsync) .WithName(endpointAgentName + "/GetResponse") .WithSummary("Retrieves a response by ID"); // Cancel response endpoint group.MapPost("{responseId}/cancel", handlers.CancelResponseAsync) .WithName(endpointAgentName + "/CancelResponse") .WithSummary("Cancels an in-progress response"); // Delete response endpoint group.MapDelete("{responseId}", handlers.DeleteResponseAsync) .WithName(endpointAgentName + "/DeleteResponse") .WithSummary("Deletes a response"); // List response input items endpoint group.MapGet("{responseId}/input_items", handlers.ListResponseInputItemsAsync) .WithName(endpointAgentName + "/ListResponseInputItems") .WithSummary("Lists the input items for a response"); return group; } /// /// Maps OpenAI Responses API endpoints to the specified . /// /// The to add the OpenAI Responses endpoints to. public static IEndpointConventionBuilder MapOpenAIResponses(this IEndpointRouteBuilder endpoints) => MapOpenAIResponses(endpoints, responsesPath: null); /// /// Maps OpenAI Responses API endpoints to the specified . /// /// The to add the OpenAI Responses endpoints to. /// Custom route path for the responses endpoint. public static IEndpointConventionBuilder MapOpenAIResponses( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string? responsesPath) { ArgumentNullException.ThrowIfNull(endpoints); responsesPath ??= "/v1/responses"; var responsesService = endpoints.ServiceProvider.GetService() ?? throw new InvalidOperationException("IResponsesService is not registered. Call AddOpenAIResponses() in your service configuration."); var handlers = new ResponsesHttpHandler(responsesService); var group = endpoints.MapGroup(responsesPath); // Create response endpoint group.MapPost("/", handlers.CreateResponseAsync) .WithName("CreateResponse") .WithSummary("Creates a model response for the given input"); // Get response endpoint group.MapGet("{responseId}", handlers.GetResponseAsync) .WithName("GetResponse") .WithSummary("Retrieves a response by ID"); // Cancel response endpoint group.MapPost("{responseId}/cancel", handlers.CancelResponseAsync) .WithName("CancelResponse") .WithSummary("Cancels an in-progress response"); // Delete response endpoint group.MapDelete("{responseId}", handlers.DeleteResponseAsync) .WithName("DeleteResponse") .WithSummary("Deletes a response"); // List response input items endpoint group.MapGet("{responseId}/input_items", handlers.ListResponseInputItemsAsync) .WithName("ListResponseInputItems") .WithSummary("Lists the input items for a response"); return group; } private static void ValidateAgentName([NotNull] string agentName) { var escaped = Uri.EscapeDataString(agentName); if (!string.Equals(escaped, agentName, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException($"Agent name '{agentName}' contains characters invalid for URL routes.", nameof(agentName)); } } }