// Copyright (c) Microsoft. All rights reserved. using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Options; namespace Microsoft.Agents.AI.DevUI; /// /// Provides helper methods for configuring the Microsoft Agents AI DevUI in ASP.NET applications. /// public static class DevUIExtensions { /// /// Maps an endpoint that serves the DevUI from the '/devui' path. /// /// /// /// DevUI requires the OpenAI Responses and Conversations services to be registered with /// and /// , /// and the corresponding endpoints to be mapped using /// and /// . /// /// /// DevUI is restricted to loopback callers unless /// is set. See /// for the available authentication and authorization hooks. /// /// /// The to add the endpoint to. /// A that can be used to add authorization or other endpoint configuration. /// /// /// /// /// Thrown when is null. public static IEndpointConventionBuilder MapDevUI( this IEndpointRouteBuilder endpoints) { ArgumentNullException.ThrowIfNull(endpoints); var authFilter = endpoints.ServiceProvider.GetRequiredService(); var options = endpoints.ServiceProvider.GetRequiredService>().Value; var startupLogger = endpoints.ServiceProvider.GetRequiredService>(); WarnIfInsecurelyExposed(startupLogger, options); // /meta must remain reachable without authentication so the frontend can // discover whether a bearer token is required before prompting for one. endpoints.MapMeta(authRequired: authFilter.TokenRequired); var protectedGroup = endpoints.MapGroup(""); // Conventions must be applied before endpoints are added to the group so // they reliably attach to every protected DevUI endpoint. options.ConfigureEndpoints?.Invoke(protectedGroup); protectedGroup.AddEndpointFilter(authFilter); protectedGroup.MapDevUI(pattern: "/devui"); protectedGroup.MapEntities(); return protectedGroup; } /// /// Maps an endpoint that serves the DevUI. /// /// The to add the endpoint to. /// /// The route pattern for the endpoint (e.g., "/devui", "/agent-ui"). /// Defaults to "/devui" if not specified. This is the path where DevUI will be accessible. /// /// A that can be used to add authorization or other endpoint configuration. /// Thrown when is null. /// Thrown when is null or whitespace. internal static IEndpointConventionBuilder MapDevUI( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern = "/devui") { ArgumentNullException.ThrowIfNull(endpoints); ArgumentException.ThrowIfNullOrWhiteSpace(pattern); // Ensure the pattern doesn't end with a slash for consistency var cleanPattern = pattern.TrimEnd('/'); // Create the DevUI handler var logger = endpoints.ServiceProvider.GetRequiredService>(); var devUIHandler = new DevUIMiddleware(logger, cleanPattern); return endpoints.MapGet($"{cleanPattern}/{{*path}}", devUIHandler.HandleRequestAsync) .WithName($"DevUI at {cleanPattern}") .WithDescription("Interactive developer interface for Microsoft Agent Framework"); } private static void WarnIfInsecurelyExposed(ILogger logger, DevUIOptions options) { var tokenConfigured = !string.IsNullOrEmpty(options.AuthToken) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DevUIOptions.AuthTokenEnvironmentVariable)); if (options.AllowRemoteAccess && !tokenConfigured && options.ConfigureEndpoints is null) { logger.LogWarning( "DevUI is configured with AllowRemoteAccess=true and no authentication. " + "Set DevUIOptions.AuthToken, the {EnvVar} environment variable, or attach an authorization policy via ConfigureEndpoints.", DevUIOptions.AuthTokenEnvironmentVariable); } } }