Skip to content

Commit b73b9d4

Browse files
committed
Track actual AI reconnect conversation
1 parent a0f8435 commit b73b9d4

2 files changed

Lines changed: 74 additions & 2 deletions

File tree

src/modules/Elsa.AI.Host/Endpoints/Ai/Chat/Endpoint.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ public override async Task HandleAsync(AiChatRequest request, CancellationToken
4848
{
4949
await foreach (var streamEvent in orchestrator.ExecuteChatAsync(request, cancellationToken))
5050
{
51+
disconnectedConversationId = streamEvent.ConversationId;
5152
if (reconnectAccepted && !reconnectConnected)
5253
{
53-
sessionManager.MarkConnected(request.ConversationId);
54+
sessionManager.MarkConnected(disconnectedConversationId);
5455
reconnectConnected = true;
5556
}
5657

57-
disconnectedConversationId = streamEvent.ConversationId;
5858
await response.WriteAsync($"event: {streamEvent.Type}\n", cancellationToken);
5959
await response.WriteAsync($"data: {JsonSerializer.Serialize(streamEvent)}\n\n", cancellationToken);
6060
await response.Body.FlushAsync(cancellationToken);
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using Elsa.AI.Abstractions.Contracts;
2+
using Elsa.AI.Abstractions.Models;
3+
using Elsa.AI.Host.Options;
4+
using Elsa.AI.Host.Streaming;
5+
using Microsoft.AspNetCore.Http;
6+
using ChatEndpoint = Elsa.AI.Host.Endpoints.Ai.Chat.Endpoint;
7+
using MicrosoftOptions = Microsoft.Extensions.Options.Options;
8+
9+
namespace Elsa.AI.IntegrationTests;
10+
11+
public class AiChatEndpointReconnectTests
12+
{
13+
[Fact(DisplayName = "Chat endpoint marks the actual reconnect conversation as connected")]
14+
public async Task ChatEndpointMarksTheActualReconnectConversationAsConnected()
15+
{
16+
var sessionManager = new AiStreamSessionManager();
17+
sessionManager.MarkDisconnected("foreign-conversation", TimeSpan.FromMinutes(5));
18+
sessionManager.MarkDisconnected("actual-conversation", TimeSpan.FromMinutes(5));
19+
var endpoint = new ChatEndpoint(
20+
new ReassignedConversationOrchestrator(),
21+
sessionManager,
22+
MicrosoftOptions.Create(new AiHostOptions()));
23+
SetHttpContext(endpoint, new DefaultHttpContext
24+
{
25+
Response =
26+
{
27+
Body = new MemoryStream()
28+
}
29+
});
30+
31+
await endpoint.HandleAsync(new AiChatRequest
32+
{
33+
ConversationId = "foreign-conversation",
34+
UserId = "user-1",
35+
Message = "Reconnect"
36+
}, CancellationToken.None);
37+
38+
Assert.True(sessionManager.CanReconnect("foreign-conversation"));
39+
Assert.False(sessionManager.CanReconnect("actual-conversation"));
40+
}
41+
42+
private static void SetHttpContext(ChatEndpoint endpoint, HttpContext httpContext)
43+
{
44+
var property = typeof(ChatEndpoint)
45+
.GetProperty(nameof(ChatEndpoint.HttpContext), System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
46+
property!.SetValue(endpoint, httpContext);
47+
}
48+
49+
private class ReassignedConversationOrchestrator : IAiOrchestrator
50+
{
51+
public async IAsyncEnumerable<AiStreamEvent> ExecuteChatAsync(AiChatRequest request, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
52+
{
53+
await Task.Yield();
54+
55+
yield return new AiStreamEvent
56+
{
57+
Type = "conversation.started",
58+
ConversationId = "actual-conversation",
59+
Sequence = 0,
60+
Timestamp = DateTimeOffset.UtcNow
61+
};
62+
63+
yield return new AiStreamEvent
64+
{
65+
Type = "conversation.completed",
66+
ConversationId = "actual-conversation",
67+
Sequence = 1,
68+
Timestamp = DateTimeOffset.UtcNow
69+
};
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)