using BotSharp.Abstraction.Infrastructures; using BotSharp.Abstraction.Infrastructures.Enums; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.Twilio.Interfaces; using BotSharp.Plugin.Twilio.Models; using BotSharp.Plugin.Twilio.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Conversation = BotSharp.Abstraction.Conversations.Models.Conversation; namespace BotSharp.Plugin.Twilio.Controllers; public class TwilioStreamController : TwilioController { private readonly TwilioSetting _settings; private readonly IServiceProvider _services; private readonly IHttpContextAccessor _context; private readonly ILogger _logger; public TwilioStreamController(TwilioSetting settings, IServiceProvider services, IHttpContextAccessor context, ILogger logger) { _settings = settings; _services = services; _context = context; _logger = logger; } [ValidateRequest] [HttpPost("twilio/stream")] public async Task InitiateStreamConversation(ConversationalVoiceRequest request) { var text = JsonSerializer.Serialize(request); if (request?.CallSid == null) { throw new ArgumentNullException(nameof(VoiceRequest.CallSid)); } VoiceResponse response = default!; if (request.AnsweredBy == "machine_start" && request.Direction == "outbound-api" && request.InitAudioFile != null) { response = new VoiceResponse(); response.Play(new Uri(request.InitAudioFile)); return TwiML(response); } var instruction = new ConversationalVoiceResponse { ConversationId = request.ConversationId, SpeechPaths = [], ActionOnEmptyResult = true }; if (request.InitAudioFile != null) { instruction.SpeechPaths.Add($"twilio/voice/speeches/{request.ConversationId}/{request.InitAudioFile}"); } await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreating(request, instruction); }, new HookEmitOption { OnlyOnce = true }); request.ConversationId = await InitConversation(request); var twilio = _services.GetRequiredService(); response = twilio.ReturnBidirectionalMediaStreamsInstructions(request.ConversationId, instruction); await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreated(request); }, new HookEmitOption { OnlyOnce = true }); return TwiML(response); } private async Task InitConversation(ConversationalVoiceRequest request) { var convService = _services.GetRequiredService(); var conversation = await convService.GetConversation(request.ConversationId); if (conversation == null) { var conv = new Conversation { AgentId = request.AgentId ?? _settings.AgentId, Channel = ConversationChannel.Phone, ChannelId = request.CallSid, Title = $"Incoming phone call from {request.From}", Tags = [], }; conversation = await convService.NewConversation(conv); } var states = new List { new("channel", ConversationChannel.Phone), new("calling_phone", request.From), new("twilio_call_sid", request.CallSid), // Enable lazy routing mode to optimize realtime experience new(StateConst.ROUTING_MODE, "lazy"), }; if (request.InitAudioFile != null) { states.Add(new("init_audio_file", request.InitAudioFile)); } convService.SetConversationId(conversation.Id, states); convService.SaveStates(); return conversation.Id; } }