< Summary

Information
Class: Elsa.AI.Host.Endpoints.AI.Chat.Endpoint
Assembly: Elsa.AI.Host
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Host/Endpoints/AI/Chat/Endpoint.cs
Line coverage
87%
Covered lines: 43
Uncovered lines: 6
Coverable lines: 49
Total lines: 91
Line coverage: 87.7%
Branch coverage
95%
Covered branches: 19
Total branches: 20
Branch coverage: 95%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Configure()100%210%
HandleAsync()95%202092.85%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Host/Endpoints/AI/Chat/Endpoint.cs

#LineLine coverage
 1using Elsa.Authorization;
 2using System.Text.Json;
 3using Elsa.Abstractions;
 4using Elsa.AI.Abstractions.Contracts;
 5using Elsa.AI.Abstractions.Models;
 6using Elsa.AI.Host.Endpoints.AI;
 7using Elsa.AI.Host.Options;
 8using Elsa.AI.Host.Permissions;
 9using Elsa.AI.Host.Streaming;
 10using JetBrains.Annotations;
 11using Microsoft.AspNetCore.Http;
 12using Microsoft.Extensions.Options;
 13
 14namespace Elsa.AI.Host.Endpoints.AI.Chat;
 15
 16[PublicAPI]
 817public class Endpoint(
 818    IAIOrchestrator orchestrator,
 819    AIStreamSessionManager sessionManager,
 820    IOptions<AIHostOptions> options) : ElsaEndpoint<AIChatRequest>
 21{
 22    public override void Configure()
 23    {
 024        Post("/ai/chat");
 025        RequirePermission(Elsa.AI.Host.Permissions.AIResourcePermissions.Chat, CoreVerbs.Execute);
 026    }
 27
 28    public override async Task HandleAsync(AIChatRequest request, CancellationToken cancellationToken)
 29    {
 830        var conversationId = string.IsNullOrWhiteSpace(request.ConversationId) ? Guid.NewGuid().ToString("N") : request.
 831        var userPermissions = AIHttpContextIdentity.GetPermissions(HttpContext);
 832        request = request with
 833        {
 834            ConversationId = conversationId,
 835            Message = request.Message ?? "",
 836            Attachments = request.Attachments ?? [],
 837            IsReconnect = sessionManager.CanReconnect(conversationId),
 838            TenantId = AIHttpContextIdentity.GetTenantId(HttpContext),
 839            UserId = AIHttpContextIdentity.GetActorId(HttpContext),
 840            UserPermissions = userPermissions,
 841            Agent = AIHttpContextIdentity.GetAuthorizedAgent(request.Agent, options.Value, HttpContext.User),
 842            ProviderName = null
 843        };
 844        var response = HttpContext.Response;
 845        response.ContentType = "text/event-stream";
 846        response.Headers["Cache-Control"] = "no-cache";
 47
 848        var completed = false;
 849        var reconnectAccepted = request.IsReconnect;
 850        var requestedReconnectConversationId = request.ConversationId;
 851        var reconnectConnected = false;
 852        var disconnectedConversationId = request.ConversationId;
 53        try
 54        {
 3255            await foreach (var streamEvent in orchestrator.ExecuteChatAsync(request, cancellationToken))
 56            {
 857                disconnectedConversationId = streamEvent.ConversationId;
 858                if (reconnectAccepted && !reconnectConnected)
 59                {
 160                    if (!string.Equals(requestedReconnectConversationId, disconnectedConversationId, StringComparison.Or
 161                        sessionManager.ReleaseReconnect(requestedReconnectConversationId);
 62
 163                    sessionManager.MarkConnected(disconnectedConversationId);
 164                    reconnectConnected = true;
 65                }
 66
 867                await response.WriteAsync($"event: {streamEvent.Type}\n", cancellationToken);
 868                await response.WriteAsync($"data: {JsonSerializer.Serialize(streamEvent)}\n\n", cancellationToken);
 869                await response.Body.FlushAsync(cancellationToken);
 870            }
 71
 872            completed = true;
 873        }
 074        catch (OperationCanceledException) when (HttpContext.RequestAborted.IsCancellationRequested)
 75        {
 76            // Expected when the client disconnects; the finally block records reconnect state.
 077            return;
 78        }
 79        finally
 80        {
 881            if (!completed)
 82            {
 083                sessionManager.MarkDisconnected(disconnectedConversationId, options.Value.ReconnectGrace);
 84            }
 885            else if (reconnectAccepted && !reconnectConnected)
 86            {
 187                sessionManager.ReleaseReconnect(requestedReconnectConversationId);
 88            }
 89        }
 890    }
 91}