BotSharp/src/Plugins/BotSharp.Plugin.Twilio/Services/Stream/TwilioStreamMiddleware.cs

115 lines
3.7 KiB
C#
Raw Normal View History

2025-02-07 17:18:47 +00:00
using BotSharp.Abstraction.Realtime;
2025-02-07 22:40:57 +00:00
using BotSharp.Abstraction.Realtime.Models;
2025-02-10 21:52:10 +00:00
using BotSharp.Core.Infrastructures;
using BotSharp.Plugin.Twilio.Interfaces;
2025-02-07 17:18:47 +00:00
using BotSharp.Plugin.Twilio.Models.Stream;
using Microsoft.AspNetCore.Http;
using System.Net.WebSockets;
using Task = System.Threading.Tasks.Task;
namespace BotSharp.Plugin.Twilio.Services.Stream;
/// <summary>
/// Refrence to https://github.com/twilio-samples/speech-assistant-openai-realtime-api-node/blob/main/index.js
/// </summary>
public class TwilioStreamMiddleware
{
private readonly RequestDelegate _next;
public TwilioStreamMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var request = httpContext.Request;
if (request.Path.StartsWithSegments("/twilio/stream"))
{
if (httpContext.WebSockets.IsWebSocketRequest)
{
var services = httpContext.RequestServices;
2025-02-10 21:52:10 +00:00
var conversationId = request.Path.Value.Split("/").Last();
2025-02-07 17:18:47 +00:00
using WebSocket webSocket = await httpContext.WebSockets.AcceptWebSocketAsync();
2025-02-10 21:52:10 +00:00
await HandleWebSocket(services, conversationId, webSocket);
return;
2025-02-07 17:18:47 +00:00
}
}
await _next(httpContext);
}
2025-02-10 21:52:10 +00:00
private async Task HandleWebSocket(IServiceProvider services, string conversationId, WebSocket webSocket)
2025-02-07 17:18:47 +00:00
{
2025-02-07 22:40:57 +00:00
var hub = services.GetRequiredService<IRealtimeHub>();
2025-02-10 21:52:10 +00:00
var conn = new RealtimeHubConnection
{
ConversationId = conversationId
};
// load conversation and state
var convService = services.GetRequiredService<IConversationService>();
convService.SetConversationId(conversationId, []);
var hooks = services.GetServices<ITwilioSessionHook>();
foreach (var hook in hooks)
{
await hook.OnStreamingStarted(conn);
}
convService.States.Save();
2025-02-07 17:18:47 +00:00
2025-02-07 22:40:57 +00:00
await hub.Listen(webSocket, (receivedText) =>
2025-02-07 17:18:47 +00:00
{
var response = JsonSerializer.Deserialize<StreamEventResponse>(receivedText);
2025-02-07 22:40:57 +00:00
conn.StreamId = response.StreamSid;
conn.Event = response.Event switch
2025-02-07 17:18:47 +00:00
{
2025-02-09 23:31:46 +00:00
"start" => "user_connected",
"media" => "user_data_received",
"stop" => "user_disconnected",
2025-02-07 22:40:57 +00:00
_ => response.Event
};
if (string.IsNullOrEmpty(conn.Event))
{
return conn;
}
2025-02-07 17:18:47 +00:00
2025-02-07 22:40:57 +00:00
conn.OnModelMessageReceived = message =>
new
2025-02-07 17:18:47 +00:00
{
2025-02-07 22:40:57 +00:00
@event = "media",
streamSid = response.StreamSid,
media = new { payload = message }
};
conn.OnModelAudioResponseDone = () =>
new
2025-02-07 17:18:47 +00:00
{
2025-02-07 22:40:57 +00:00
@event = "mark",
streamSid = response.StreamSid,
mark = new { name = "responsePart" }
};
conn.OnModelUserInterrupted = () =>
new
2025-02-07 17:18:47 +00:00
{
2025-02-07 22:40:57 +00:00
@event = "clear",
streamSid = response.StreamSid
};
2025-02-07 17:18:47 +00:00
2025-02-07 22:40:57 +00:00
if (response.Event == "start")
{
var startResponse = JsonSerializer.Deserialize<StreamEventStartResponse>(receivedText);
2025-02-09 23:31:46 +00:00
conn.Data = JsonSerializer.Serialize(startResponse.Body.CustomParameters);
2025-02-07 17:18:47 +00:00
}
else if (response.Event == "media")
{
var mediaResponse = JsonSerializer.Deserialize<StreamEventMediaResponse>(receivedText);
2025-02-07 22:40:57 +00:00
conn.Data = mediaResponse.Body.Payload;
2025-02-07 17:18:47 +00:00
}
2025-02-07 22:40:57 +00:00
return conn;
});
2025-02-07 17:18:47 +00:00
}
}