| File: Helpers\FoundryEchoTestApp.cs | Web Access |
| Project: src\tests\Aspire.Deployment.EndToEnd.Tests\Aspire.Deployment.EndToEnd.Tests.csproj (Aspire.Deployment.EndToEnd.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Aspire.Deployment.EndToEnd.Tests.Helpers; internal static class FoundryEchoTestApp { internal static void Write(string directory, string marker) { Directory.CreateDirectory(directory); // This model-free worker only needs the Responses hosting package. Let it select compatible // transitive SDK versions instead of copying model/MCP client pins from a different scenario. File.WriteAllText(Path.Combine(directory, "EchoAgent.csproj"), """ <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net10.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <NoWarn>$(NoWarn);OPENAI001;MAIF001;MAAI001</NoWarn> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Agents.AI.Foundry.Hosting" Version="1.12.0-preview.260629.1" /> </ItemGroup> </Project> """); File.WriteAllText(Path.Combine(directory, "Program.cs"), """ using Microsoft.Agents.AI.Foundry.Hosting; var builder = WebApplication.CreateBuilder(args); var port = Environment.GetEnvironmentVariable("DEFAULT_AD_PORT") ?? "8088"; builder.WebHost.UseUrls($"http://+:{port}"); builder.Services.AddFoundryResponses(new EchoAIAgent()); var app = builder.Build(); app.MapFoundryResponses(); app.MapGet("/liveness", () => Results.Ok("Healthy")); app.MapGet("/readiness", () => Results.Ok("Ready")); app.Run(); """); File.WriteAllText(Path.Combine(directory, "EchoAIAgent.cs"), $$""" using System.Runtime.CompilerServices; using System.Text.Json; using Microsoft.Agents.AI; using Microsoft.Extensions.AI; internal sealed class EchoAIAgent : AIAgent { public override string Name => "echo-agent"; public override string Description => "A deterministic model-free deployment probe."; protected override Task<AgentResponse> RunCoreAsync( IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { return Task.FromResult(new AgentResponse(new ChatMessage(ChatRole.Assistant, GetResponse(messages)))); } protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync( IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { yield return new AgentResponseUpdate { Role = ChatRole.Assistant, Contents = [new TextContent(GetResponse(messages))] }; await Task.CompletedTask; } protected override ValueTask<AgentSession> CreateSessionCoreAsync(CancellationToken cancellationToken = default) => new(new EchoAgentSession()); protected override ValueTask<JsonElement> SerializeSessionCoreAsync( AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(JsonSerializer.SerializeToElement(new { }, jsonSerializerOptions)); protected override ValueTask<AgentSession> DeserializeSessionCoreAsync( JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new EchoAgentSession()); private static string GetResponse(IEnumerable<ChatMessage> messages) { var input = messages.Last(message => message.Role == ChatRole.User).Text ?? string.Empty; // This source-only marker prevents a stale image from satisfying a new deployment probe. if (!input.StartsWith("{{marker}}:", StringComparison.Ordinal)) { throw new InvalidOperationException("The request targeted a different deployment's image."); } return $"Echo: {input}"; } private sealed class EchoAgentSession : AgentSession; } """); } }