feat: add initial Hold Slayer AI telephony gateway implementation
Complete project scaffolding and core implementation of an AI-powered telephony system that calls companies, navigates IVR menus, waits on hold, and transfers to the user when a human answers. Key components: - FastAPI server with REST API, WebSocket, and MCP (SSE) interfaces - SIP/VoIP call management via PJSUA2 with RTP audio streaming - LLM-powered IVR navigation using OpenAI/Anthropic with tool calling - Hold detection service combining audio analysis and silence detection - Real-time STT (Whisper/Deepgram) and TTS (OpenAI/Piper) pipelines - Call recording with per-channel and mixed audio capture - Event bus (asyncio pub/sub) for real-time client updates - Web dashboard with live call monitoring - SQLite persistence via SQLAlchemy with call history and analytics - Notification support (email, SMS, webhook, desktop) - Docker Compose deployment with Opal VoIP and Opal Media containers - Comprehensive test suite with unit, integration, and E2E tests - Simplified .gitignore and full project documentation in README
This commit is contained in:
81
models/device.py
Normal file
81
models/device.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Device models — SIP phones, softphones, cell phones.
|
||||
|
||||
Devices register with the gateway and can receive transferred calls.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class DeviceType(str, Enum):
|
||||
"""Types of devices that can connect to the gateway."""
|
||||
|
||||
SIP_PHONE = "sip_phone" # Hardware SIP phone
|
||||
SOFTPHONE = "softphone" # Software SIP client
|
||||
CELL = "cell" # Cell phone (reached via PSTN trunk)
|
||||
TABLET = "tablet" # Tablet with SIP client
|
||||
WEBRTC = "webrtc" # Browser-based WebRTC client
|
||||
|
||||
|
||||
class DeviceBase(BaseModel):
|
||||
"""Shared device fields."""
|
||||
|
||||
name: str # "Office SIP Phone"
|
||||
type: DeviceType
|
||||
extension: Optional[int] = None # 221-299, auto-assigned if omitted
|
||||
sip_uri: Optional[str] = None # sip:robert@gateway.helu.ca
|
||||
phone_number: Optional[str] = None # For PSTN devices (E.164)
|
||||
priority: int = 10 # Routing priority (lower = higher priority)
|
||||
capabilities: list[str] = Field(default_factory=lambda: ["voice"])
|
||||
|
||||
|
||||
class Device(DeviceBase):
|
||||
"""Full device model."""
|
||||
|
||||
id: str
|
||||
is_online: bool = False
|
||||
last_seen: Optional[datetime] = None
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
@property
|
||||
def can_receive_call(self) -> bool:
|
||||
"""Can this device receive a call right now?"""
|
||||
if self.type in (DeviceType.SIP_PHONE, DeviceType.SOFTPHONE, DeviceType.WEBRTC):
|
||||
return self.is_online and self.sip_uri is not None
|
||||
if self.type == DeviceType.CELL:
|
||||
return self.phone_number is not None
|
||||
return False
|
||||
|
||||
|
||||
class DeviceCreate(DeviceBase):
|
||||
"""Request model for registering a new device."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DeviceUpdate(BaseModel):
|
||||
"""Request model for updating a device."""
|
||||
|
||||
name: Optional[str] = None
|
||||
type: Optional[DeviceType] = None
|
||||
extension: Optional[int] = None
|
||||
sip_uri: Optional[str] = None
|
||||
phone_number: Optional[str] = None
|
||||
priority: Optional[int] = None
|
||||
capabilities: Optional[list[str]] = None
|
||||
|
||||
|
||||
class DeviceStatus(BaseModel):
|
||||
"""Lightweight device status for list views."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
type: DeviceType
|
||||
is_online: bool
|
||||
last_seen: Optional[datetime] = None
|
||||
can_receive_call: bool
|
||||
Reference in New Issue
Block a user