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