diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs index 94284150f..33c64c8c5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs @@ -7,6 +7,10 @@ public interface ICrontabHook : IHookBase string[]? Triggers => null; + /// + /// Establishes the identity for the cron event; invoked synchronously for all hooks before any OnCronTriggered runs, so the identity flows down into the execution phase. + /// + /// void OnAuthenticate(CrontabItem item) { } Task OnCronTriggered(CrontabItem item) diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/Gpt4xModelConstants.cs b/src/Infrastructure/BotSharp.Abstraction/Models/Gpt4xModelConstants.cs index a812ee02d..8919b743c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Models/Gpt4xModelConstants.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Models/Gpt4xModelConstants.cs @@ -4,7 +4,6 @@ public static class Gpt4xModelConstants { public const string GPT_4o = "gpt-4o"; public const string GPT_4o_Mini = "gpt-4o-mini"; - public const string GPT_4o_Mini_Realtime_Preview = "gpt-4o-mini-realtime-preview"; public const string GPT_4o_Mini_Search_Preview = "gpt-4o-mini-search-preview"; public const string GPT_4o_Mini_Transcribe = "gpt-4o-mini-transcribe"; public const string GPT_4o_Transcribe = "gpt-4o-transcribe"; diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index 30119236b..40d9a0633 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -41,8 +41,6 @@ public RealTimeCompletionProvider( _logger = logger; _services = services; _botsharpOptions = botsharpOptions; - - var settingService = _services.GetRequiredService(); } public async Task Connect( diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs index 40ba7d80d..5006a26f0 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs @@ -53,6 +53,9 @@ public async Task InitiateStreamConversation(ConversationalVoiceReq instruction.SpeechPaths.Add(request.InitAudioFile); } + // Authenticate (synchronous, so identity writes survive into the async session hooks) + HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId); + // Before creating session await HookEmitter.Emit(_services, async hook => { @@ -76,7 +79,9 @@ await HookEmitter.Emit(_services, async hook => if (twilio.MachineDetected(request)) { response = new VoiceResponse(); - await HookEmitter.Emit(_services, + // Authenticate (synchronous, so identity writes survive into the async status hook) + HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId); + await HookEmitter.Emit(_services, async hook => await hook.OnVoicemailStarting(request), request.AgentId); var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3"); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs index 5543af38a..f355d77ce 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs @@ -33,7 +33,9 @@ public async Task InitiateOutboundCall(ConversationalVoiceRequest r if (twilio.MachineDetected(request)) { response = new VoiceResponse(); - await HookEmitter.Emit(_services, + // Authenticate (synchronous, so identity writes survive into the async status hook) + HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId); + await HookEmitter.Emit(_services, async hook => await hook.OnVoicemailStarting(request), request.AgentId); var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3"); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs index c6690d84c..bb96d9d8e 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs @@ -36,6 +36,7 @@ public async Task PhoneRecordingStatus(ConversationalVoiceRequest }); await convService.SaveStates(); + HookEmitter.Emit(_services, x => x.OnAuthenticate(request), request.AgentId); // recording completed await HookEmitter.Emit(_services, x => x.OnRecordingCompleted(request), request.AgentId); } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index 786f116cd..5dc53b375 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -62,6 +62,9 @@ public async Task InitiateConversation(ConversationalVoiceRequest r ActionOnEmptyResult = true }; + // Authenticate (synchronous, so identity writes survive into the async session hooks) + HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId); + await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreating(request, instruction); @@ -112,6 +115,9 @@ await HookEmitter.Emit(_services, async hook => [HttpPost("twilio/voice/receive/{seqNum}")] public async Task ReceiveCallerMessage(ConversationalVoiceRequest request) { + // Authenticate (synchronous, so identity writes survive into the async session hooks) + HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId); + var twilio = _services.GetRequiredService(); var messageQueue = _services.GetRequiredService(); var sessionManager = _services.GetRequiredService(); @@ -203,6 +209,9 @@ await HookEmitter.Emit(_services, async hook => [HttpPost("twilio/voice/reply/{seqNum}")] public async Task ReplyCallerMessage(ConversationalVoiceRequest request) { + // Authenticate (synchronous, so identity writes survive into the async session hooks) + HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId); + var nextSeqNum = request.SeqNum + 1; var sessionManager = _services.GetRequiredService(); var twilio = _services.GetRequiredService(); @@ -343,6 +352,9 @@ public async Task PhoneCallStatus(ConversationalVoiceRequest reque { var twilio = _services.GetRequiredService(); + // Authenticate (synchronous, so identity writes survive into the async status hooks) + HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId); + switch (request.CallStatus) { case "completed": diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs index 3ccc4f4d6..8762c5649 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs @@ -41,6 +41,7 @@ public override async Task OnFunctionExecuted(RoleDialogModel message, InvokeFun CallSid = sid, }; + // No OnAuthenticate before these hooks — identity is established only at the HTTP entry points. foreach (var hook in hooks) { if (await hook.ShouldReconnect(request, message)) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs index e7ce09325..3c73b5c95 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs @@ -6,6 +6,11 @@ namespace BotSharp.Plugin.Twilio.Interfaces; public interface ITwilioCallStatusHook : IHookBase { + /// + /// Establishes the operator identity synchronously at the call-status webhook entry, before any status hook method runs. + /// + /// + void OnAuthenticate(ConversationalVoiceRequest request); Task OnVoicemailLeft(ConversationalVoiceRequest request) => Task.CompletedTask; Task OnUserDisconnected(ConversationalVoiceRequest request) => Task.CompletedTask; Task OnRecordingCompleted(ConversationalVoiceRequest request) => Task.CompletedTask; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs index 00837b6a4..8632e72e4 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs @@ -7,6 +7,12 @@ namespace BotSharp.Plugin.Twilio.Interfaces; public interface ITwilioSessionHook : IHookBase { + /// + /// Establishes the operator identity synchronously at the HTTP entry points only; the WebSocket and background flows manage identity separately. + /// + /// + void OnAuthenticate(ConversationalVoiceRequest request); + /// /// Before session creating /// diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index e0618aaf6..1720d95d5 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -324,7 +324,8 @@ public async Task WaitingForAiResponse(ConversationalVoiceRequest ActionOnEmptyResult = true }; - await HookEmitter.Emit(_services, async hook => await hook.OnWaitingAgentResponse(request, instruction), + // No OnAuthenticate before these hooks — identity is established only at the HTTP entry points. + await HookEmitter.Emit(_services, async hook => await hook.OnWaitingAgentResponse(request, instruction), request.AgentId); response = ReturnInstructions(instruction); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs b/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs index d653a3417..0f49ea07b 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs @@ -79,6 +79,7 @@ private async Task HandleWebSocket(IServiceProvider services, string agentId, st var convService = services.GetRequiredService(); await convService.SetConversationId(conversationId, []); + // No OnAuthenticate before these hooks — identity is established only at the HTTP entry points. var hooks = services.GetHooks(agentId); foreach (var hook in hooks) {