Files
agent-framework/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/HttpActorProcessor.cs
T
Reuben Bond e7441ee29e .NET: Add agent hosting package and update sample (#296)
* Add agent hosting package and update sample

* Review feedback and cleanup

* Include the narrator

* wip

* wip

* Remove workaround for empty state writes.

* Handle changes to AgentThread.

* One more.

* Fix.

---------

Co-authored-by: Aditya Mandaleeka <adityam@microsoft.com>
2025-08-06 21:26:36 +00:00

146 lines
4.6 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.AI.Agents.Hosting;
using Microsoft.Extensions.AI.Agents.Runtime;
namespace AgentWebChat.AgentHost;
internal static class HttpActorProcessor
{
public static async Task<IResult> GetResponseAsync(
string actorType,
string actorKey,
string messageId,
bool? blocking,
bool? streaming,
HttpContext context,
IActorClient actorClient,
CancellationToken cancellationToken)
{
var actorId = new ActorId(actorType, actorKey);
var responseHandle = await actorClient.GetResponseAsync(actorId, messageId, cancellationToken);
if (responseHandle.TryGetResponse(out var response))
{
return GetResult(response);
}
if (streaming == true)
{
return new ActorUpdateStreamingResult(responseHandle);
}
if (blocking == true)
{
response = await responseHandle.GetResponseAsync(cancellationToken);
return GetResult(response);
}
return Results.Ok(new ActorResponse
{
ActorId = actorId,
MessageId = messageId,
Status = RequestStatus.Pending,
Data = JsonDocument.Parse("{}").RootElement
});
}
public static async Task<IResult> SendRequestAsync(
string actorType,
string actorKey,
string messageId,
bool? blocking,
bool? streaming,
ActorRequest request,
IActorClient actorClient,
CancellationToken cancellationToken)
{
var responseHandle = await actorClient.SendRequestAsync(request, cancellationToken);
if (responseHandle.TryGetResponse(out var response))
{
return GetResult(response);
}
if (streaming == true)
{
return new ActorUpdateStreamingResult(responseHandle);
}
if (blocking == true)
{
response = await responseHandle.GetResponseAsync(cancellationToken);
return GetResult(response);
}
return Results.Accepted();
}
private static IResult GetResult(ActorResponse response)
{
if (response.Status == RequestStatus.NotFound)
{
return Results.NotFound();
}
return Results.Ok(response);
}
public static async Task<IResult> CancelRequestAsync(
string actorType,
string actorKey,
string messageId,
IActorClient actorClient,
CancellationToken cancellationToken)
{
var actorId = new ActorId(actorType, actorKey);
var responseHandle = await actorClient.GetResponseAsync(actorId, messageId, cancellationToken);
if (responseHandle.TryGetResponse(out var response))
{
if (response.Status is RequestStatus.NotFound)
{
return Results.NotFound();
}
else if (response.Status is RequestStatus.Completed or RequestStatus.Failed)
{
return Results.Conflict("The request has already completed and cannot be cancelled.");
}
}
await responseHandle.CancelAsync(cancellationToken);
return Results.NoContent();
}
private sealed class ActorUpdateStreamingResult(
ActorResponseHandle responseHandle) : IResult
{
public async Task ExecuteAsync(HttpContext httpContext)
{
var cancellationToken = httpContext.RequestAborted;
var response = httpContext.Response;
response.Headers.ContentType = "text/event-stream";
response.Headers.CacheControl = "no-cache,no-store";
response.Headers.Connection = "keep-alive";
// Make sure we disable all response buffering for SSE.
response.Headers.ContentEncoding = "identity";
httpContext.Features.GetRequiredFeature<IHttpResponseBodyFeature>().DisableBuffering();
await response.Body.FlushAsync(cancellationToken);
var updateTypeInfo = AgentHostingJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ActorRequestUpdate));
await foreach (var update in responseHandle.WatchUpdatesAsync(cancellationToken).ConfigureAwait(false))
{
var eventData = JsonSerializer.Serialize(update, updateTypeInfo);
var eventText = $"data: {eventData}\n\n";
await response.WriteAsync(eventText, cancellationToken);
await response.Body.FlushAsync(cancellationToken);
}
}
}
}