import logging from typing import Optional from twilio.rest import Client from vocode.streaming.models.agent import AgentConfig from vocode.streaming.models.synthesizer import ( AzureSynthesizerConfig, SynthesizerConfig, ) from vocode.streaming.models.telephony import CallConfig, TwilioConfig from vocode.streaming.models.transcriber import ( DeepgramTranscriberConfig, PunctuationEndpointingConfig, TranscriberConfig, ) from vocode.streaming.telephony.config_manager.base_config_manager import ( BaseConfigManager, ) from vocode.streaming.telephony.constants import ( DEFAULT_AUDIO_ENCODING, DEFAULT_CHUNK_SIZE, DEFAULT_SAMPLING_RATE, ) from vocode.streaming.telephony.twilio import create_twilio_client from vocode.streaming.utils import create_conversation_id class OutboundCall: def __init__( self, base_url: str, to_phone: str, from_phone: str, config_manager: BaseConfigManager, agent_config: AgentConfig, twilio_config: TwilioConfig, transcriber_config: Optional[TranscriberConfig] = None, synthesizer_config: Optional[SynthesizerConfig] = None, conversation_id: Optional[str] = None, logger: Optional[logging.Logger] = None, ): self.base_url = base_url self.to_phone = to_phone self.from_phone = from_phone self.config_manager = config_manager self.agent_config = agent_config self.transcriber_config = transcriber_config or DeepgramTranscriberConfig( sampling_rate=DEFAULT_SAMPLING_RATE, audio_encoding=DEFAULT_AUDIO_ENCODING, chunk_size=DEFAULT_CHUNK_SIZE, model="voicemail", endpointing_config=PunctuationEndpointingConfig(), ) self.synthesizer_config = synthesizer_config or AzureSynthesizerConfig( sampling_rate=DEFAULT_SAMPLING_RATE, audio_encoding=DEFAULT_AUDIO_ENCODING ) self.conversation_id = conversation_id or create_conversation_id() self.logger = logger self.twilio_config = twilio_config self.twilio_client = create_twilio_client(twilio_config) self.twilio_sid = None def create_twilio_call( self, to_phone: str, from_phone: str, digits: str = "" ) -> str: twilio_call = self.twilio_client.calls.create( url=f"https://{self.base_url}/twiml/initiate_call/{self.conversation_id}", to=to_phone, from_=from_phone, send_digits=digits, ) return twilio_call.sid def validate_outbound_call( self, to_phone: str, from_phone: str, mobile_only: bool = True, ): if len(to_phone) < 8: raise ValueError("Invalid 'to' phone") if not mobile_only: return line_type_intelligence = ( self.twilio_client.lookups.v2.phone_numbers(to_phone) .fetch(fields="line_type_intelligence") .line_type_intelligence ) if not line_type_intelligence or ( line_type_intelligence and line_type_intelligence["type"] != "mobile" ): raise ValueError("Can only call mobile phones") def start(self): self.logger.debug("Starting outbound call") self.validate_outbound_call(self.to_phone, self.from_phone) self.twilio_sid = self.create_twilio_call(self.to_phone, self.from_phone) call_config = CallConfig( transcriber_config=self.transcriber_config, agent_config=self.agent_config, synthesizer_config=self.synthesizer_config, twilio_config=self.twilio_config, twilio_sid=self.twilio_sid, ) self.config_manager.save_config(self.conversation_id, call_config) def end(self): response = self.twilio_client.calls(self.twilio_sid).update(status="completed") return response.status == "completed"