Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ public interface ICrontabHook : IHookBase
string[]? Triggers
=> null;

/// <summary>
/// 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.
/// </summary>
/// <param name="item"></param>
void OnAuthenticate(CrontabItem item) { }

Task OnCronTriggered(CrontabItem item)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ public RealTimeCompletionProvider(
_logger = logger;
_services = services;
_botsharpOptions = botsharpOptions;

var settingService = _services.GetRequiredService<ISettingService>();
}

public async Task Connect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public async Task<TwiMLResult> InitiateStreamConversation(ConversationalVoiceReq
instruction.SpeechPaths.Add(request.InitAudioFile);
}

// Authenticate (synchronous, so identity writes survive into the async session hooks)
HookEmitter.Emit<ITwilioSessionHook>(_services, hook => hook.OnAuthenticate(request), request.AgentId);

Comment thread
qodo-code-review[bot] marked this conversation as resolved.
// Before creating session
await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
{
Expand All @@ -76,7 +79,9 @@ await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
if (twilio.MachineDetected(request))
{
response = new VoiceResponse();
await HookEmitter.Emit<ITwilioCallStatusHook>(_services,
// Authenticate (synchronous, so identity writes survive into the async status hook)
HookEmitter.Emit<ITwilioCallStatusHook>(_services, hook => hook.OnAuthenticate(request), request.AgentId);
await HookEmitter.Emit<ITwilioCallStatusHook>(_services,
async hook => await hook.OnVoicemailStarting(request), request.AgentId);

var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ public async Task<TwiMLResult> InitiateOutboundCall(ConversationalVoiceRequest r
if (twilio.MachineDetected(request))
{
response = new VoiceResponse();
await HookEmitter.Emit<ITwilioCallStatusHook>(_services,
// Authenticate (synchronous, so identity writes survive into the async status hook)
HookEmitter.Emit<ITwilioCallStatusHook>(_services, hook => hook.OnAuthenticate(request), request.AgentId);
await HookEmitter.Emit<ITwilioCallStatusHook>(_services,
async hook => await hook.OnVoicemailStarting(request), request.AgentId);

var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public async Task<ActionResult> PhoneRecordingStatus(ConversationalVoiceRequest
});
await convService.SaveStates();

HookEmitter.Emit<ITwilioCallStatusHook>(_services, x => x.OnAuthenticate(request), request.AgentId);
// recording completed
await HookEmitter.Emit<ITwilioCallStatusHook>(_services, x => x.OnRecordingCompleted(request), request.AgentId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public async Task<TwiMLResult> InitiateConversation(ConversationalVoiceRequest r
ActionOnEmptyResult = true
};

// Authenticate (synchronous, so identity writes survive into the async session hooks)
HookEmitter.Emit<ITwilioSessionHook>(_services, hook => hook.OnAuthenticate(request), request.AgentId);

Comment thread
yileicn marked this conversation as resolved.
await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
{
await hook.OnSessionCreating(request, instruction);
Expand Down Expand Up @@ -112,6 +115,9 @@ await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
[HttpPost("twilio/voice/receive/{seqNum}")]
public async Task<TwiMLResult> ReceiveCallerMessage(ConversationalVoiceRequest request)
{
// Authenticate (synchronous, so identity writes survive into the async session hooks)
HookEmitter.Emit<ITwilioSessionHook>(_services, hook => hook.OnAuthenticate(request), request.AgentId);

var twilio = _services.GetRequiredService<TwilioService>();
var messageQueue = _services.GetRequiredService<TwilioMessageQueue>();
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
Expand Down Expand Up @@ -203,6 +209,9 @@ await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
[HttpPost("twilio/voice/reply/{seqNum}")]
public async Task<TwiMLResult> ReplyCallerMessage(ConversationalVoiceRequest request)
{
// Authenticate (synchronous, so identity writes survive into the async session hooks)
HookEmitter.Emit<ITwilioSessionHook>(_services, hook => hook.OnAuthenticate(request), request.AgentId);

var nextSeqNum = request.SeqNum + 1;
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
var twilio = _services.GetRequiredService<TwilioService>();
Expand Down Expand Up @@ -343,6 +352,9 @@ public async Task<ActionResult> PhoneCallStatus(ConversationalVoiceRequest reque
{
var twilio = _services.GetRequiredService<TwilioService>();

// Authenticate (synchronous, so identity writes survive into the async status hooks)
HookEmitter.Emit<ITwilioCallStatusHook>(_services, hook => hook.OnAuthenticate(request), request.AgentId);

switch (request.CallStatus)
{
case "completed":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ namespace BotSharp.Plugin.Twilio.Interfaces;

public interface ITwilioCallStatusHook : IHookBase
{
/// <summary>
/// Establishes the operator identity synchronously at the call-status webhook entry, before any status hook method runs.
/// </summary>
/// <param name="request"></param>
void OnAuthenticate(ConversationalVoiceRequest request);
Task OnVoicemailLeft(ConversationalVoiceRequest request) => Task.CompletedTask;
Task OnUserDisconnected(ConversationalVoiceRequest request) => Task.CompletedTask;
Task OnRecordingCompleted(ConversationalVoiceRequest request) => Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ namespace BotSharp.Plugin.Twilio.Interfaces;

public interface ITwilioSessionHook : IHookBase
{
/// <summary>
/// Establishes the operator identity synchronously at the HTTP entry points only; the WebSocket and background flows manage identity separately.
/// </summary>
/// <param name="request"></param>
void OnAuthenticate(ConversationalVoiceRequest request);

Comment on lines +10 to +15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Breaking hook interface change 🐞 Bug ⚙ Maintainability

ITwilioSessionHook/ITwilioCallStatusHook add a new non-default OnAuthenticate method, which is a
source-breaking API change for any external hook implementations. Because the new method is
synchronous and mandatory, any auth work needing async I/O will be forced into blocking patterns.
Agent Prompt
### Issue description
Adding `void OnAuthenticate(...)` as a required interface member breaks compilation for all existing implementations of `ITwilioSessionHook` / `ITwilioCallStatusHook`.

### Issue Context
These are extension-point interfaces; even if this repo has no implementers, downstream projects will.

### Fix Focus Areas
- src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs[10-16]
- src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs[9-10]

### Suggested fix
- Prefer backward-compatible default interface implementations:
  - `void OnAuthenticate(ConversationalVoiceRequest request) { }`
- Or introduce a new optional interface (e.g., `ITwilioSessionAuthHook`) and have emitters call it when present.
- If you intend a hard break, bump major version and document the migration.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

/// <summary>
/// Before session creating
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,8 @@ public async Task<VoiceResponse> WaitingForAiResponse(ConversationalVoiceRequest
ActionOnEmptyResult = true
};

await HookEmitter.Emit<ITwilioSessionHook>(_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<ITwilioSessionHook>(_services, async hook => await hook.OnWaitingAgentResponse(request, instruction),
request.AgentId);

response = ReturnInstructions(instruction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ private async Task HandleWebSocket(IServiceProvider services, string agentId, st
var convService = services.GetRequiredService<IConversationService>();
await convService.SetConversationId(conversationId, []);

// No OnAuthenticate before these hooks — identity is established only at the HTTP entry points.
var hooks = services.GetHooks<ITwilioSessionHook>(agentId);
foreach (var hook in hooks)
{
Expand Down
Loading