diff --git a/arts/BotSharp.gif b/arts/BotSharp.gif
new file mode 100644
index 00000000..84cb0140
Binary files /dev/null and b/arts/BotSharp.gif differ
diff --git a/arts/Icon.png b/arts/Icon.png
index 335e43ad..cf98da0b 100644
Binary files a/arts/Icon.png and b/arts/Icon.png differ
diff --git a/arts/Logo.png b/arts/Logo.png
index 2a1eb907..cf98da0b 100644
Binary files a/arts/Logo.png and b/arts/Logo.png differ
diff --git a/docs/architecture/assets/architecture.drawio b/docs/architecture/assets/architecture.drawio
new file mode 100644
index 00000000..2d602264
--- /dev/null
+++ b/docs/architecture/assets/architecture.drawio
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/Token.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/Token.cs
index 8f3d7f0d..98ca34c1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/Token.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/Token.cs
@@ -1,5 +1,3 @@
-using System.Text.Json.Serialization;
-
namespace BotSharp.Abstraction.Users.Models;
public class Token
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
index ec2cd1a4..acea5752 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
@@ -1,21 +1,7 @@
-using BotSharp.Abstraction.Conversations;
-using BotSharp.Plugin.Twilio.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using System.Collections.Generic;
-using System;
-using Twilio.AspNet.Common;
-using Twilio.AspNet.Core;
-using Twilio.TwiML;
-using Twilio.TwiML.Voice;
-using System.Threading.Tasks;
-using Microsoft.Extensions.DependencyInjection;
-using BotSharp.Abstraction.Routing.Settings;
-using Twilio.Http;
-using Twilio.TwiML.Messaging;
-using BotSharp.Abstraction.Agents.Enums;
-using BotSharp.Abstraction.Conversations.Models;
-using BotSharp.Abstraction.Conversations.Enums;
+using System.IdentityModel.Tokens.Jwt;
+using BotSharp.Plugin.Twilio.Services;
namespace BotSharp.Plugin.Twilio.Controllers;
@@ -30,12 +16,29 @@ public class TwilioVoiceController : TwilioController
_settings = settings;
_services = services;
}
-
+
+ [Authorize]
+ [HttpGet("/twilio/token")]
+ public Token GetAccessToken()
+ {
+ var twilio = _services.GetRequiredService();
+ var accessToken = twilio.GetAccessToken();
+ var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
+ return new Token
+ {
+ AccessToken = accessToken,
+ ExpireTime = jwt.Payload.Exp.Value,
+ TokenType = "Bearer",
+ Scope = "api"
+ };
+ }
+
[HttpPost("/twilio/voice/welcome")]
public async Task StartConversation(VoiceRequest request)
{
string sessionId = $"TwilioVoice_{request.CallSid}";
- var response = ReturnInstructions("Hello, how may I help you?");
+ var twilio = _services.GetRequiredService();
+ var response = twilio.ReturnInstructions("Hello, how may I help you?");
return TwiML(response);
}
@@ -51,13 +54,18 @@ public class TwilioVoiceController : TwilioController
$"calling_phone={input.DialCallSid}"
});
+ var twilio = _services.GetRequiredService();
VoiceResponse response = default;
var result = await conv.SendMessage(agentId,
new RoleDialogModel(AgentRole.User, input.SpeechResult),
async msg =>
{
- response = HangUp(msg.Content);
+ response = twilio.ReturnInstructions(msg.Content);
+ if (msg.FunctionName == "conversation_end")
+ {
+ response = twilio.HangUp(msg.Content);
+ }
}, async functionExecuting =>
{
}, async functionExecuted =>
@@ -66,53 +74,4 @@ public class TwilioVoiceController : TwilioController
return TwiML(response);
}
-
- private VoiceResponse ReturnInstructions(string message)
- {
- var routingSetting = _services.GetRequiredService();
-
- var response = new VoiceResponse();
- var gather = new Gather()
- {
- Input = new List()
- {
- Gather.InputEnum.Speech
- },
- Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.RouterId}")
- };
- gather.Say(message);
- response.Append(gather);
- return response;
- }
-
- private VoiceResponse HangUp(string message)
- {
- var response = new VoiceResponse();
- if (!string.IsNullOrEmpty(message))
- {
- response.Say(message);
- }
- response.Hangup();
- return response;
- }
-
- private VoiceResponse HoldOn(int interval, string message = null)
- {
- var routingSetting = _services.GetRequiredService();
-
- var response = new VoiceResponse();
- var gather = new Gather()
- {
- Input = new List() { Gather.InputEnum.Speech },
- Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.RouterId}"),
- ActionOnEmptyResult = true
- };
- if (!string.IsNullOrEmpty(message))
- {
- gather.Say(message);
- }
- gather.Pause(interval);
- response.Append(gather);
- return response;
- }
}
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs
new file mode 100644
index 00000000..dcbcf30c
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs
@@ -0,0 +1,96 @@
+using Twilio.Jwt.AccessToken;
+using Token = Twilio.Jwt.AccessToken.Token;
+
+namespace BotSharp.Plugin.Twilio.Services;
+
+///
+/// https://github.com/TwilioDevEd/voice-javascript-sdk-quickstart-csharp
+///
+public class TwilioService
+{
+ private readonly TwilioSetting _settings;
+ private readonly IServiceProvider _services;
+
+ public TwilioService(TwilioSetting settings, IServiceProvider services)
+ {
+ _settings = settings;
+ _services = services;
+ }
+
+ public string GetAccessToken()
+ {
+ // These are specific to Voice
+ var user = _services.GetRequiredService();
+
+ // Create a Voice grant for this token
+ var grant = new VoiceGrant();
+ grant.OutgoingApplicationSid = _settings.AppSID;
+
+ // Optional: add to allow incoming calls
+ grant.IncomingAllow = true;
+
+ var grants = new HashSet
+ {
+ { grant }
+ };
+
+ // Create an Access Token generator
+ var token = new Token(
+ _settings.AccountSID,
+ _settings.ApiKeySID,
+ _settings.ApiSecret,
+ user.Id,
+ grants: grants);
+
+ return token.ToJwt();
+ }
+
+ public VoiceResponse ReturnInstructions(string message)
+ {
+ var routingSetting = _services.GetRequiredService();
+
+ var response = new VoiceResponse();
+ var gather = new Gather()
+ {
+ Input = new List()
+ {
+ Gather.InputEnum.Speech
+ },
+ Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.RouterId}")
+ };
+ gather.Say(message);
+ response.Append(gather);
+ return response;
+ }
+
+ public VoiceResponse HangUp(string message)
+ {
+ var response = new VoiceResponse();
+ if (!string.IsNullOrEmpty(message))
+ {
+ response.Say(message);
+ }
+ response.Hangup();
+ return response;
+ }
+
+ public VoiceResponse HoldOn(int interval, string message = null)
+ {
+ var routingSetting = _services.GetRequiredService();
+
+ var response = new VoiceResponse();
+ var gather = new Gather()
+ {
+ Input = new List() { Gather.InputEnum.Speech },
+ Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.RouterId}"),
+ ActionOnEmptyResult = true
+ };
+ if (!string.IsNullOrEmpty(message))
+ {
+ gather.Say(message);
+ }
+ gather.Pause(interval);
+ response.Append(gather);
+ return response;
+ }
+}
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs
index a1048b8c..3da775cf 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs
@@ -5,5 +5,8 @@ public class TwilioSetting
public string PhoneNumber { get; set; }
public string AccountSID { get; set; }
public string AuthToken { get; set; }
+ public string AppSID { get; set; }
+ public string ApiKeySID { get; set; }
+ public string ApiSecret { get; set; }
public string CallbackHost { get; set; }
}
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs b/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs
index 80b2b5df..cba9ba30 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs
@@ -1,4 +1,4 @@
-using BotSharp.Plugin.Twilio.Settings;
+using BotSharp.Plugin.Twilio.Services;
namespace BotSharp.Plugin.Twilio;
@@ -12,5 +12,7 @@ public class TwilioPlugin : IBotSharpPlugin
var setting = new TwilioSetting();
config.Bind("Twilio", setting);
services.AddSingleton(setting);
+
+ services.AddScoped();
}
}
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Using.cs b/src/Plugins/BotSharp.Plugin.Twilio/Using.cs
index 5026d491..66698a49 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Using.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Using.cs
@@ -14,4 +14,13 @@ global using BotSharp.Abstraction.Models;
global using BotSharp.Abstraction.Agents;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Plugins;
-global using BotSharp.Abstraction.Conversations.Models;
\ No newline at end of file
+global using BotSharp.Abstraction.Conversations.Models;
+global using BotSharp.Abstraction.Routing.Settings;
+global using BotSharp.Abstraction.Conversations.Enums;
+global using BotSharp.Abstraction.Users.Models;
+global using BotSharp.Abstraction.Users;
+global using Twilio.AspNet.Common;
+global using Twilio.AspNet.Core;
+global using Twilio.TwiML;
+global using Twilio.TwiML.Voice;
+global using BotSharp.Plugin.Twilio.Settings;
\ No newline at end of file