// 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));
}
}
}