diff --git a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentEntityInfo.cs b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentEntityInfo.cs index e447e957af..c308963fc1 100644 --- a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentEntityInfo.cs +++ b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentEntityInfo.cs @@ -1,5 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +// Copyright (c) Microsoft. All rights reserved. namespace Aspire.Hosting.AgentFramework; diff --git a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentFrameworkBuilderExtensions.cs b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentFrameworkBuilderExtensions.cs index 218e892053..09e9305910 100644 --- a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentFrameworkBuilderExtensions.cs +++ b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentFrameworkBuilderExtensions.cs @@ -1,5 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; diff --git a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentServiceAnnotation.cs b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentServiceAnnotation.cs index ccdaf0ca04..15b3f7dd90 100644 --- a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentServiceAnnotation.cs +++ b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/AgentServiceAnnotation.cs @@ -1,5 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; @@ -33,9 +32,9 @@ public class AgentServiceAnnotation : IResourceAnnotation { ArgumentNullException.ThrowIfNull(agentService); - AgentService = agentService; - EntityIdPrefix = entityIdPrefix; - Agents = agents ?? []; + this.AgentService = agentService; + this.EntityIdPrefix = entityIdPrefix; + this.Agents = agents ?? []; } /// diff --git a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIAggregatorHostedService.cs b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIAggregatorHostedService.cs index 0d72ddbc7a..0c93337c4f 100644 --- a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIAggregatorHostedService.cs +++ b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIAggregatorHostedService.cs @@ -1,5 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; @@ -34,7 +33,6 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable private WebApplication? _app; private readonly DevUIResource _resource; private readonly ILogger _logger; - private int _allocatedPort; // Frontend resources loaded from the Microsoft.Agents.AI.DevUI assembly (null if unavailable) private readonly Dictionary? _frontendResources; @@ -46,15 +44,15 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable DevUIResource resource, ILogger logger) { - _resource = resource; - _logger = logger; - _frontendResources = LoadFrontendResources(logger); + this._resource = resource; + this._logger = logger; + this._frontendResources = LoadFrontendResources(logger); } /// /// Gets the port the aggregator is listening on, available after . /// - internal int AllocatedPort => _allocatedPort; + internal int AllocatedPort { get; private set; } public async Task StartAsync(CancellationToken cancellationToken) { @@ -67,39 +65,39 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable AllowAutoRedirect = false }); - _app = builder.Build(); - _app.Urls.Add("http://127.0.0.1:0"); + this._app = builder.Build(); + this._app.Urls.Add("http://127.0.0.1:0"); - MapRoutes(_app); + this.MapRoutes(this._app); - await _app.StartAsync(cancellationToken).ConfigureAwait(false); + await this._app.StartAsync(cancellationToken).ConfigureAwait(false); - var serverAddresses = _app.Services.GetRequiredService() + var serverAddresses = this._app.Services.GetRequiredService() .Features.Get(); if (serverAddresses is not null) { var address = serverAddresses.Addresses.First(); var uri = new Uri(address); - _allocatedPort = uri.Port; - _logger.LogInformation("DevUI aggregator started on port {Port}", _allocatedPort); + this.AllocatedPort = uri.Port; + this._logger.LogInformation("DevUI aggregator started on port {Port}", this.AllocatedPort); } } public async Task StopAsync(CancellationToken cancellationToken) { - if (_app is not null) + if (this._app is not null) { - await _app.StopAsync(cancellationToken).ConfigureAwait(false); + await this._app.StopAsync(cancellationToken).ConfigureAwait(false); } } public async ValueTask DisposeAsync() { - if (_app is not null) + if (this._app is not null) { - await _app.DisposeAsync().ConfigureAwait(false); - _app = null; + await this._app.DisposeAsync().ConfigureAwait(false); + this._app = null; } } @@ -169,11 +167,11 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable } // Try embedded resources first - if (_frontendResources is not null) + if (this._frontendResources is not null) { var resourcePath = string.IsNullOrEmpty(path) ? "index.html" : path; - if (await TryServeResourceAsync(context, resourcePath).ConfigureAwait(false)) + if (await this.TryServeResourceAsync(context, resourcePath).ConfigureAwait(false)) { return; } @@ -181,7 +179,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable // SPA fallback: serve index.html for paths without a file extension (client-side routing) if (!resourcePath.Contains('.', StringComparison.Ordinal)) { - if (await TryServeResourceAsync(context, "index.html").ConfigureAwait(false)) + if (await this.TryServeResourceAsync(context, "index.html").ConfigureAwait(false)) { return; } @@ -192,7 +190,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable } // Fallback: proxy from the first backend that serves /devui - var backends = ResolveBackends(); + var backends = this.ResolveBackends(); var firstBackendUrl = backends.Values.FirstOrDefault(); if (firstBackendUrl is null) @@ -211,14 +209,14 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable private async Task TryServeResourceAsync(HttpContext context, string resourcePath) { - if (_frontendResources is null) + if (this._frontendResources is null) { return false; } var key = resourcePath.Replace('.', '/'); - if (!_frontendResources.TryGetValue(key, out var entry)) + if (!this._frontendResources.TryGetValue(key, out var entry)) { return false; } @@ -269,14 +267,14 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable app.MapGet("/health", () => Results.Ok(new { status = "healthy" })); // Intercept API calls for multi-backend aggregation and routing - app.MapGet("/v1/entities", (Delegate)AggregateEntitiesAsync); - app.MapGet("/v1/entities/{**entityPath}", RouteEntityInfoAsync); - app.MapPost("/v1/responses", RouteResponsesAsync); - app.Map("/v1/conversations/{**path}", ProxyConversationsAsync); + app.MapGet("/v1/entities", (Delegate)this.AggregateEntitiesAsync); + app.MapGet("/v1/entities/{**entityPath}", this.RouteEntityInfoAsync); + app.MapPost("/v1/responses", this.RouteResponsesAsync); + app.Map("/v1/conversations/{**path}", this.ProxyConversationsAsync); app.MapGet("/meta", GetMeta); // Serve the DevUI frontend from embedded assembly resources - app.Map("/devui/{**path}", ServeDevUIFrontendAsync); + app.Map("/devui/{**path}", this.ServeDevUIFrontendAsync); } /// @@ -285,14 +283,14 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable /// private Dictionary ResolveBackends() { - if (_cachedBackends is not null) + if (this._cachedBackends is not null) { - return _cachedBackends; + return this._cachedBackends; } var result = new Dictionary(StringComparer.Ordinal); - foreach (var annotation in _resource.Annotations.OfType()) + foreach (var annotation in this._resource.Annotations.OfType()) { if (annotation.AgentService is not IResourceWithEndpoints rwe) { @@ -311,14 +309,14 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable } catch (Exception ex) { - _logger.LogDebug(ex, "Backend '{Prefix}' endpoint not yet available", prefix); + this._logger.LogDebug(ex, "Backend '{Prefix}' endpoint not yet available", prefix); } } // Only cache if we resolved at least one backend if (result.Count > 0) { - _cachedBackends = result; + this._cachedBackends = result; } return result; @@ -326,10 +324,10 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable private async Task AggregateEntitiesAsync(HttpContext context) { - var backends = ResolveBackends(); + var backends = this.ResolveBackends(); var allEntities = new JsonArray(); - foreach (var annotation in _resource.Annotations.OfType()) + foreach (var annotation in this._resource.Annotations.OfType()) { var prefix = annotation.EntityIdPrefix ?? annotation.AgentService.Name; @@ -369,7 +367,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable if (!response.IsSuccessStatusCode) { - _logger.LogWarning( + this._logger.LogWarning( "Failed to fetch entities from backend '{Prefix}' at {Url}: {Status}", prefix, baseUrl, response.StatusCode); continue; @@ -406,7 +404,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable } catch (Exception ex) when (ex is not OperationCanceledException) { - _logger.LogWarning(ex, "Error fetching entities from backend '{Prefix}' at {Url}", prefix, baseUrl); + this._logger.LogWarning(ex, "Error fetching entities from backend '{Prefix}' at {Url}", prefix, baseUrl); } } @@ -415,7 +413,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable private async Task RouteEntityInfoAsync(HttpContext context, string entityPath) { - var (backendUrl, actualPath) = ResolveBackend(entityPath); + var (backendUrl, actualPath) = this.ResolveBackend(entityPath); if (backendUrl is null) { @@ -439,7 +437,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable if (entityId is null) { - var firstBackend = ResolveBackends().Values.FirstOrDefault(); + var firstBackend = this.ResolveBackends().Values.FirstOrDefault(); if (firstBackend is null) { context.Response.StatusCode = StatusCodes.Status502BadGateway; @@ -450,7 +448,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable return; } - var (backendUrl, actualEntityId) = ResolveBackend(entityId); + var (backendUrl, actualEntityId) = this.ResolveBackend(entityId); if (backendUrl is null) { @@ -476,7 +474,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable var agentId = context.Request.Query["agent_id"].FirstOrDefault(); if (agentId is not null) { - (backendUrl, _) = ResolveBackend(agentId); + (backendUrl, _) = this.ResolveBackend(agentId); } if (backendUrl is null && context.Request.ContentLength > 0) @@ -489,7 +487,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable if (entityId is not null) { string actualId; - (backendUrl, actualId) = ResolveBackend(entityId); + (backendUrl, actualId) = this.ResolveBackend(entityId); if (backendUrl is not null) { @@ -513,7 +511,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable } // Couldn't determine backend from body; proxy raw bytes to first backend - backendUrl = ResolveBackends().Values.FirstOrDefault(); + backendUrl = this.ResolveBackends().Values.FirstOrDefault(); if (backendUrl is null) { context.Response.StatusCode = StatusCodes.Status502BadGateway; @@ -527,7 +525,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable } // No body and no query param — route to first backend - backendUrl ??= ResolveBackends().Values.FirstOrDefault(); + backendUrl ??= this.ResolveBackends().Values.FirstOrDefault(); if (backendUrl is null) { context.Response.StatusCode = StatusCodes.Status502BadGateway; @@ -596,7 +594,7 @@ internal sealed class DevUIAggregatorHostedService : IAsyncDisposable private (string? BackendUrl, string ActualPath) ResolveBackend(string prefixedId) { - var backends = ResolveBackends(); + var backends = this.ResolveBackends(); var slashIndex = prefixedId.IndexOf('/'); if (slashIndex > 0) diff --git a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIResource.cs b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIResource.cs index 180562edbf..1af2f5a1a0 100644 --- a/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIResource.cs +++ b/dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIResource.cs @@ -1,5 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +// Copyright (c) Microsoft. All rights reserved. using System.Net.Sockets; @@ -19,8 +18,6 @@ public class DevUIResource(string name) : Resource(name), IResourceWithEndpoints { internal const string PrimaryEndpointName = "http"; - private EndpointReference? _primaryEndpoint; - /// /// Initializes a new instance of the class with endpoint annotations. /// @@ -28,7 +25,7 @@ public class DevUIResource(string name) : Resource(name), IResourceWithEndpoints /// An optional fixed port. If null, a dynamic port is assigned. internal DevUIResource(string name, int? port) : this(name) { - Annotations.Add(new EndpointAnnotation( + this.Annotations.Add(new EndpointAnnotation( ProtocolType.Tcp, uriScheme: "http", name: PrimaryEndpointName, @@ -42,5 +39,5 @@ public class DevUIResource(string name) : Resource(name), IResourceWithEndpoints /// /// Gets the primary HTTP endpoint for the DevUI web interface. /// - public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName); + public EndpointReference PrimaryEndpoint => field ??= new(this, PrimaryEndpointName); }