dograh/api/services/telephony/providers/plivo/__init__.py
Abhishek 7fd3b96470
feat: agent stream for cloudonix OPBX (#261)
* feat: agent stream for cloudonix OPBX

* feat: make cloudonix app name optional

* feat: create application while configuring telephony config

* fix: get telephony configuration from stamped workflow run

* fix: fix vobiz hangup URL
2026-05-02 15:53:58 +05:30

154 lines
4.9 KiB
Python

"""Plivo telephony provider package."""
import uuid
from typing import Any, Dict
import aiohttp
from fastapi import HTTPException
from loguru import logger
from api.services.telephony.registry import (
ProviderSpec,
ProviderUIField,
ProviderUIMetadata,
register,
)
from api.utils.common import get_backend_endpoints
from .config import PlivoConfigurationRequest, PlivoConfigurationResponse
from .provider import PlivoProvider
from .transport import create_transport
PLIVO_API_BASE_URL = "https://api.plivo.com/v1"
def _config_loader(value: Dict[str, Any]) -> Dict[str, Any]:
return {
"provider": "plivo",
"auth_id": value.get("auth_id"),
"auth_token": value.get("auth_token"),
"application_id": value.get("application_id"),
"from_numbers": value.get("from_numbers", []),
}
async def _ensure_application_id(credentials: Dict[str, Any]) -> Dict[str, Any]:
"""Auto-create a Plivo Application if one wasn't supplied.
The application is created with our inbound dispatcher URL pre-set — the
same URL ``configure_inbound`` would POST later — so inbound calls work
immediately for any number bound to this application.
"""
if credentials.get("application_id"):
return credentials
auth_id = credentials.get("auth_id")
auth_token = credentials.get("auth_token")
if not auth_id or not auth_token:
return credentials
backend_endpoint, _ = await get_backend_endpoints()
inbound_url = f"{backend_endpoint}/api/v1/telephony/inbound/run"
app_name = f"dograh-{uuid.uuid4().hex[:12]}"
endpoint = f"{PLIVO_API_BASE_URL}/Account/{auth_id}/Application/"
body = {
"app_name": app_name,
"answer_url": inbound_url,
"answer_method": "POST",
"hangup_url": "",
}
auth = aiohttp.BasicAuth(auth_id, auth_token)
try:
async with aiohttp.ClientSession() as session:
async with session.post(endpoint, json=body, auth=auth) as response:
response_text = await response.text()
if response.status not in (200, 201, 202):
logger.error(
f"[Plivo] applicationCreate failed: "
f"HTTP {response.status} body={response_text}"
)
raise HTTPException(
status_code=response.status,
detail=(
f"Failed to auto-create Plivo Application: "
f"HTTP {response.status} {response_text}"
),
)
data = await response.json()
except aiohttp.ClientError as e:
logger.error(f"[Plivo] applicationCreate transport error: {e}")
raise HTTPException(
status_code=502,
detail=f"Failed to reach Plivo to auto-create application: {e}",
)
created_id = data.get("app_id")
if not created_id:
logger.error(f"[Plivo] applicationCreate response missing app_id: {data}")
raise HTTPException(
status_code=502,
detail=f"Plivo applicationCreate response missing app_id: {data}",
)
logger.info(
f"[Plivo] auto-created Application '{app_name}' (id={created_id}) on "
f"account {auth_id}"
)
return {**credentials, "application_id": str(created_id)}
_UI_METADATA = ProviderUIMetadata(
display_name="Plivo",
docs_url="https://docs.dograh.com/integrations/telephony/plivo",
fields=[
ProviderUIField(name="auth_id", label="Auth ID", type="text", sensitive=True),
ProviderUIField(
name="auth_token", label="Auth Token", type="password", sensitive=True
),
ProviderUIField(
name="application_id",
label="Application ID",
type="text",
required=False,
description=(
"Plivo Application ID whose answer_url is updated when inbound "
"workflows are attached to numbers on this account. Leave blank "
"and we will auto-create one for you on save."
),
),
ProviderUIField(
name="from_numbers",
label="Phone Numbers",
type="string-array",
description="E.164-formatted Plivo phone numbers used for outbound calls",
),
],
)
SPEC = ProviderSpec(
name="plivo",
provider_cls=PlivoProvider,
config_loader=_config_loader,
transport_factory=create_transport,
transport_sample_rate=8000,
config_request_cls=PlivoConfigurationRequest,
ui_metadata=_UI_METADATA,
config_response_cls=PlivoConfigurationResponse,
account_id_credential_field="auth_id",
preprocess_credentials_on_save=_ensure_application_id,
)
register(SPEC)
__all__ = [
"SPEC",
"PlivoConfigurationRequest",
"PlivoConfigurationResponse",
"PlivoProvider",
"create_transport",
]