Skip to content

Commit fd0e89b

Browse files
authored
Merge pull request #948 from hchen2020/master
util-twilio-transfer_phone_call
2 parents cc6f4d6 + 9cd09a8 commit fd0e89b

File tree

13 files changed

+188
-18
lines changed

13 files changed

+188
-18
lines changed

src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ private async Task ConnectToModel(WebSocket userWebSocket)
9292
}
9393

9494
routing.Context.SetDialogs(dialogs);
95+
routing.Context.SetMessageId(_conn.ConversationId, dialogs.Last().MessageId);
9596

9697
var states = _services.GetRequiredService<IConversationStateService>();
9798

@@ -188,6 +189,7 @@ await _completer.Connect(_conn,
188189
// append input audio transcript to conversation
189190
dialogs.Add(message);
190191
storage.Append(_conn.ConversationId, message);
192+
routing.Context.SetMessageId(_conn.ConversationId, message.MessageId);
191193

192194
foreach (var hook in hookProvider.HooksOrderByPriority)
193195
{

src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13+
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-transfer_phone_call.json">
14+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
15+
</Content>
1316
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-hangup_phone_call.json">
1417
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
1518
</Content>

src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioStreamController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@ public async Task<TwiMLResult> InitiateStreamConversation(ConversationalVoiceReq
3535
throw new ArgumentNullException(nameof(VoiceRequest.CallSid));
3636
}
3737

38+
var twilio = _services.GetRequiredService<TwilioService>();
3839
VoiceResponse response = default!;
3940

4041
if (request.AnsweredBy == "machine_start" &&
4142
request.Direction == "outbound-api" &&
4243
request.InitAudioFile != null)
4344
{
4445
response = new VoiceResponse();
45-
response.Play(new Uri(request.InitAudioFile));
46+
var url = twilio.GetSpeechPath(request.ConversationId, request.InitAudioFile);
47+
response.Play(new Uri(url));
4648
return TwiML(response);
4749
}
4850

@@ -68,9 +70,7 @@ await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
6870

6971
request.ConversationId = await InitConversation(request);
7072

71-
var twilio = _services.GetRequiredService<TwilioService>();
72-
73-
response = twilio.ReturnBidirectionalMediaStreamsInstructions(request.ConversationId, instruction);
73+
response = twilio.ReturnBidirectionalMediaStreamsInstructions(instruction);
7474

7575
await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
7676
{

src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,26 @@ public async Task<TwiMLResult> Hangup(ConversationalVoiceRequest request)
438438
return TwiML(response);
439439
}
440440

441+
[ValidateRequest]
442+
[HttpPost("twilio/voice/transfer-call")]
443+
public async Task<TwiMLResult> TransferCall(ConversationalVoiceRequest request)
444+
{
445+
var instruction = new ConversationalVoiceResponse
446+
{
447+
ConversationId = request.ConversationId,
448+
TransferTo = request.TransferTo
449+
};
450+
451+
if (request.InitAudioFile != null)
452+
{
453+
instruction.SpeechPaths.Add(request.InitAudioFile);
454+
}
455+
456+
var twilio = _services.GetRequiredService<TwilioService>();
457+
var response = twilio.TransferCall(instruction);
458+
return TwiML(response);
459+
}
460+
441461
[ValidateRequest]
442462
[HttpPost("twilio/voice/status")]
443463
public async Task<ActionResult> PhoneCallStatus(ConversationalVoiceRequest request)

src/Plugins/BotSharp.Plugin.Twilio/Models/ConversationalVoiceRequest.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public class ConversationalVoiceRequest : VoiceRequest
2727
[FromForm]
2828
public string? CallbackSource { get; set; }
2929

30+
[FromQuery(Name = "transfer-to")]
31+
public string? TransferTo { get; set; }
32+
3033
/// <summary>
3134
/// machine_start
3235
/// </summary>

src/Plugins/BotSharp.Plugin.Twilio/Models/ConversationalVoiceResponse.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ public class ConversationalVoiceResponse
1313
public int Timeout { get; set; } = 3;
1414

1515
public string Hints { get; set; }
16+
17+
/// <summary>
18+
/// The Phone Number to transfer to
19+
/// </summary>
20+
public string? TransferTo { get; set; }
1621
}

src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ public async Task<bool> Execute(RoleDialogModel message)
108108
statusCallback: new Uri(statusUrl),
109109
// https://www.twilio.com/docs/voice/answering-machine-detection
110110
machineDetection: _twilioSetting.MachineDetection,
111+
machineDetectionSilenceTimeout: _twilioSetting.MachineDetectionSilenceTimeout,
111112
record: _twilioSetting.RecordingEnabled,
112113
recordingStatusCallback: $"{_twilioSetting.CallbackHost}/twilio/record/status?conversation-id={newConversationId}");
113114

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using BotSharp.Abstraction.Files;
2+
using BotSharp.Abstraction.Options;
3+
using BotSharp.Abstraction.Routing;
4+
using BotSharp.Core.Infrastructures;
5+
using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
6+
using Twilio.Rest.Api.V2010.Account;
7+
8+
namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions;
9+
10+
public class TransferPhoneCallFn : IFunctionCallback
11+
{
12+
private readonly IServiceProvider _services;
13+
private readonly ILogger _logger;
14+
private readonly BotSharpOptions _options;
15+
private readonly TwilioSetting _twilioSetting;
16+
17+
public string Name => "util-twilio-transfer_phone_call";
18+
public string Indication => "Transferring the active line";
19+
20+
public TransferPhoneCallFn(
21+
IServiceProvider services,
22+
ILogger<TransferPhoneCallFn> logger,
23+
BotSharpOptions options,
24+
TwilioSetting twilioSetting)
25+
{
26+
_services = services;
27+
_logger = logger;
28+
_options = options;
29+
_twilioSetting = twilioSetting;
30+
}
31+
32+
public async Task<bool> Execute(RoleDialogModel message)
33+
{
34+
var args = JsonSerializer.Deserialize<ForwardPhoneCallArgs>(message.FunctionArgs, _options.JsonSerializerOptions);
35+
36+
var fileStorage = _services.GetRequiredService<IFileStorageService>();
37+
var states = _services.GetRequiredService<IConversationStateService>();
38+
var routing = _services.GetRequiredService<IRoutingService>();
39+
var conversationId = routing.Context.ConversationId;
40+
var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/transfer-call?conversation-id={conversationId}&transfer-to={args.PhoneNumber}";
41+
42+
// Generate initial assistant audio
43+
string initAudioFile = null;
44+
if (!string.IsNullOrEmpty(args.TransitionMessage))
45+
{
46+
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
47+
var data = await completion.GenerateAudioFromTextAsync(args.TransitionMessage);
48+
initAudioFile = "transfer.mp3";
49+
fileStorage.SaveSpeechFile(conversationId, initAudioFile, data);
50+
51+
processUrl += $"&init-audio-file={initAudioFile}";
52+
}
53+
54+
if (!string.IsNullOrEmpty(initAudioFile))
55+
{
56+
processUrl += $"&init-audio-file={initAudioFile}";
57+
}
58+
59+
// Forward call
60+
var sid = states.GetState("twilio_call_sid");
61+
62+
if (string.IsNullOrEmpty(sid))
63+
{
64+
_logger.LogError("Twilio call sid is empty.");
65+
message.Content = "There is an error when transferring the phone call.";
66+
return false;
67+
}
68+
else
69+
{
70+
var call = CallResource.Update(
71+
pathSid: sid,
72+
url: new Uri(processUrl));
73+
74+
message.Content = args.TransitionMessage;
75+
return true;
76+
}
77+
}
78+
}

src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Hooks/OutboundPhoneCallHandlerUtilityHook.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ public class OutboundPhoneCallHandlerUtilityHook : IAgentUtilityHook
77
{
88
private static string PREFIX = "util-twilio-";
99
private static string OUTBOUND_PHONE_CALL_FN = $"{PREFIX}outbound_phone_call";
10+
private static string TRANSFER_PHONE_CALL_FN = $"{PREFIX}transfer_phone_call";
1011
private static string HANGUP_PHONE_CALL_FN = $"{PREFIX}hangup_phone_call";
11-
public static string TEXT_MESSAGE_FN = $"{PREFIX}text_message";
12+
private static string TEXT_MESSAGE_FN = $"{PREFIX}text_message";
1213

1314
public void AddUtilities(List<AgentUtility> utilities)
1415
{
@@ -18,6 +19,7 @@ public void AddUtilities(List<AgentUtility> utilities)
1819
Functions =
1920
[
2021
new($"{OUTBOUND_PHONE_CALL_FN}"),
22+
new($"{TRANSFER_PHONE_CALL_FN}"),
2123
new($"{HANGUP_PHONE_CALL_FN}"),
2224
new($"{TEXT_MESSAGE_FN}")
2325
],
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
4+
5+
public class ForwardPhoneCallArgs
6+
{
7+
[JsonPropertyName("phone_number")]
8+
public string PhoneNumber { get; set; } = null!;
9+
10+
[JsonPropertyName("transition_message")]
11+
public string TransitionMessage { get; set; } = null!;
12+
}

0 commit comments

Comments
 (0)