diff --git a/pyproject.toml b/pyproject.toml index 5a5e101..0d2b96c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "vocode" -version = "0.1.39" +version = "0.1.40" description = "The all-in-one voice SDK" authors = ["Ajay Raj "] license = "MIT License" diff --git a/vocode/models/telephony.py b/vocode/models/telephony.py index 989bb97..47afa98 100644 --- a/vocode/models/telephony.py +++ b/vocode/models/telephony.py @@ -5,6 +5,18 @@ from vocode.models.synthesizer import SynthesizerConfig from vocode.models.transcriber import TranscriberConfig +class TwilioConfig(BaseModel): + account_sid: str + auth_token: str + + +class InternalTwilioConfig(BaseModel): + account_sid: str + api_key: str + api_secret: str + outgoing_application_sid: str + + class CallEntity(BaseModel): phone_number: str @@ -14,6 +26,7 @@ class CreateInboundCall(BaseModel): agent_config: AgentConfig synthesizer_config: Optional[SynthesizerConfig] = None twilio_sid: str + twilio_config: Optional[TwilioConfig] = None class EndOutboundCall(BaseModel): @@ -27,6 +40,7 @@ class CreateOutboundCall(BaseModel): agent_config: AgentConfig synthesizer_config: Optional[SynthesizerConfig] = None conversation_id: Optional[str] = None + twilio_config: Optional[TwilioConfig] = None # TODO add IVR/etc. @@ -39,3 +53,4 @@ class DialIntoZoomCall(BaseModel): agent_config: AgentConfig synthesizer_config: Optional[SynthesizerConfig] = None conversation_id: Optional[str] = None + twilio_config: Optional[TwilioConfig] = None diff --git a/vocode/telephony/inbound_call_server.py b/vocode/telephony/inbound_call_server.py index 6a1e085..35fc5f4 100644 --- a/vocode/telephony/inbound_call_server.py +++ b/vocode/telephony/inbound_call_server.py @@ -3,12 +3,14 @@ from typing import Optional import requests import uvicorn from vocode.models.synthesizer import SynthesizerConfig +from twilio.jwt.access_token.grants import VoiceGrant from vocode.models.transcriber import TranscriberConfig +from vocode.telephony.utils import create_access_token from .. import api_key, BASE_URL from ..models.agent import AgentConfig -from ..models.telephony import CreateInboundCall +from ..models.telephony import CreateInboundCall, InternalTwilioConfig, TwilioConfig VOCODE_INBOUND_CALL_URL = f"https://{BASE_URL}/create_inbound_call" @@ -20,6 +22,7 @@ class InboundCallServer: transcriber_config: Optional[TranscriberConfig] = None, synthesizer_config: Optional[SynthesizerConfig] = None, response_on_rate_limit: Optional[str] = None, + internal_twilio_config: Optional[InternalTwilioConfig] = None, ): self.agent_config = agent_config self.transcriber_config = transcriber_config @@ -30,6 +33,20 @@ class InboundCallServer: response_on_rate_limit or "The line is really busy right now, check back later!" ) + self.internal_twilio_config = internal_twilio_config + + def create_twilio_config(self) -> TwilioConfig: + access_token = create_access_token(self.internal_twilio_config) + access_token.add_grant( + VoiceGrant( + outgoing_application_sid=self.internal_twilio_config.outgoing_application_sid, + incoming_allow=True, + ) + ) + return TwilioConfig( + account_sid=self.internal_twilio_config.account_sid, + access_token=access_token.to_jwt(), + ) async def handle_call(self, twilio_sid: str = Form(alias="CallSid")): response = requests.post( diff --git a/vocode/telephony/outbound_call.py b/vocode/telephony/outbound_call.py index d70e540..a715c46 100644 --- a/vocode/telephony/outbound_call.py +++ b/vocode/telephony/outbound_call.py @@ -2,10 +2,20 @@ from typing import Optional from vocode.models.agent import AgentConfig from vocode.models.synthesizer import SynthesizerConfig from vocode.models.transcriber import TranscriberConfig -from ..models.telephony import CallEntity, CreateOutboundCall, EndOutboundCall +from vocode.telephony.utils import create_access_token +from ..models.telephony import ( + CallEntity, + CreateOutboundCall, + EndOutboundCall, + InternalTwilioConfig, + TwilioConfig, +) import requests from .. import api_key, BASE_URL +from twilio.jwt.access_token.grants import VoiceGrant + + VOCODE_CREATE_OUTBOUND_CALL_URL = f"https://{BASE_URL}/create_outbound_call" VOCODE_END_OUTBOUND_CALL_URL = f"https://{BASE_URL}/end_outbound_call" @@ -19,6 +29,7 @@ class OutboundCall: transcriber_config: Optional[TranscriberConfig] = None, synthesizer_config: Optional[SynthesizerConfig] = None, conversation_id: Optional[str] = None, + internal_twilio_config: Optional[InternalTwilioConfig] = None, ): self.recipient = recipient self.caller = caller @@ -26,6 +37,19 @@ class OutboundCall: self.transcriber_config = transcriber_config self.synthesizer_config = synthesizer_config self.conversation_id = conversation_id + self.internal_twilio_config = internal_twilio_config + + def create_twilio_config(self) -> TwilioConfig: + access_token = create_access_token(self.internal_twilio_config) + access_token.add_grant( + VoiceGrant( + outgoing_application_sid=self.internal_twilio_config.outgoing_application_sid + ) + ) + return TwilioConfig( + account_sid=self.internal_twilio_config.account_sid, + access_token=access_token.to_jwt(), + ) def start(self) -> str: response = requests.post( @@ -38,6 +62,9 @@ class OutboundCall: transcriber_config=self.transcriber_config, synthesizer_config=self.synthesizer_config, conversation_id=self.conversation_id, + twilio_config=self.create_twilio_config() + if self.internal_twilio_config + else None, ).dict(), ) assert response.ok, response.text diff --git a/vocode/telephony/utils.py b/vocode/telephony/utils.py new file mode 100644 index 0000000..da9f410 --- /dev/null +++ b/vocode/telephony/utils.py @@ -0,0 +1,12 @@ +from twilio.jwt.access_token import AccessToken + +from vocode.models.telephony import InternalTwilioConfig + + +def create_access_token(twilio_config: InternalTwilioConfig): + return AccessToken( + twilio_config.account_sid, + twilio_config.api_key, + twilio_config.api_secret, + identity="user", + ) diff --git a/vocode/telephony/zoom_dial_in.py b/vocode/telephony/zoom_dial_in.py index bf46f80..5433494 100644 --- a/vocode/telephony/zoom_dial_in.py +++ b/vocode/telephony/zoom_dial_in.py @@ -3,7 +3,12 @@ from vocode.models.agent import AgentConfig from vocode.models.synthesizer import SynthesizerConfig from vocode.models.transcriber import TranscriberConfig from vocode.telephony.outbound_call import OutboundCall -from ..models.telephony import CallEntity, DialIntoZoomCall +from ..models.telephony import ( + CallEntity, + DialIntoZoomCall, + InternalTwilioConfig, + TwilioConfig, +) import requests from .. import api_key, BASE_URL @@ -21,6 +26,7 @@ class ZoomDialIn(OutboundCall): transcriber_config: Optional[TranscriberConfig] = None, synthesizer_config: Optional[SynthesizerConfig] = None, conversation_id: Optional[str] = None, + internal_twilio_config: Optional[InternalTwilioConfig] = None, ): super().__init__( recipient=recipient, @@ -29,6 +35,7 @@ class ZoomDialIn(OutboundCall): transcriber_config=transcriber_config, synthesizer_config=synthesizer_config, conversation_id=conversation_id, + internal_twilio_config=internal_twilio_config, ) self.zoom_meeting_id = zoom_meeting_id self.zoom_meeting_password = zoom_meeting_password @@ -46,6 +53,9 @@ class ZoomDialIn(OutboundCall): transcriber_config=self.transcriber_config, synthesizer_config=self.synthesizer_config, conversation_id=self.conversation_id, + twilio_config=self.create_twilio_config() + if self.internal_twilio_config + else None, ).dict(), ) assert response.ok, response.text