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
This commit is contained in:
Abhishek 2026-05-02 15:53:58 +05:30 committed by GitHub
parent 5cfdbeff02
commit 7fd3b96470
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 1529 additions and 545 deletions

View file

@ -15,11 +15,6 @@ from api.db.base_client import BaseDBClient
from api.db.models import CampaignModel, TelephonyConfigurationModel
class TelephonyConfigurationDuplicateAccountError(Exception):
"""Raised when saving a config whose account_id collides with an existing
config of the same provider in the same organization."""
class TelephonyConfigurationInUseError(Exception):
"""Raised when deleting a config that is still referenced by a campaign."""
@ -67,29 +62,6 @@ class TelephonyConfigurationClient(BaseDBClient):
)
return result.scalars().first()
async def find_telephony_config_by_account(
self, provider: str, account_id_field: str, account_id: str
) -> Optional[TelephonyConfigurationModel]:
"""Global lookup used by the workflow-agnostic inbound dispatcher.
Returns the single config whose stored credentials contain
``credentials[account_id_field] == account_id``. Filters in Python
over the per-provider candidate set since credentials is JSON.
"""
if not account_id_field or not account_id:
return None
async with self.async_session() as session:
result = await session.execute(
select(TelephonyConfigurationModel).where(
TelephonyConfigurationModel.provider == provider,
)
)
for cand in result.scalars().all():
stored = (cand.credentials or {}).get(account_id_field)
if stored and stored == account_id:
return cand
return None
async def list_telephony_configurations_by_provider(
self, organization_id: int, provider: str
) -> List[TelephonyConfigurationModel]:
@ -126,19 +98,9 @@ class TelephonyConfigurationClient(BaseDBClient):
provider: str,
credentials: Dict[str, Any],
is_default_outbound: bool = False,
account_id_credential_field: Optional[str] = None,
) -> TelephonyConfigurationModel:
"""Create a new config. Raises ``TelephonyConfigurationDuplicateAccountError``
if the same provider+account_id is already configured for the org."""
if account_id_credential_field:
await self._guard_duplicate_account(
organization_id,
provider,
credentials.get(account_id_credential_field),
account_id_credential_field,
exclude_id=None,
)
"""Create a new config row. Duplicate-account guarding is the caller's
responsibility; this method does not enforce it."""
async with self.async_session() as session:
existing_count = await session.scalar(
select(func.count(TelephonyConfigurationModel.id)).where(
@ -172,22 +134,12 @@ class TelephonyConfigurationClient(BaseDBClient):
organization_id: int,
name: Optional[str] = None,
credentials: Optional[Dict[str, Any]] = None,
account_id_credential_field: Optional[str] = None,
) -> Optional[TelephonyConfigurationModel]:
async with self.async_session() as session:
row = await session.get(TelephonyConfigurationModel, config_id)
if not row or row.organization_id != organization_id:
return None
if credentials is not None and account_id_credential_field:
await self._guard_duplicate_account(
organization_id,
row.provider,
credentials.get(account_id_credential_field),
account_id_credential_field,
exclude_id=config_id,
)
if name is not None:
row.name = name
if credentials is not None:
@ -238,33 +190,6 @@ class TelephonyConfigurationClient(BaseDBClient):
await session.commit()
return True
async def _guard_duplicate_account(
self,
organization_id: int,
provider: str,
account_id: Optional[str],
credential_field: str,
exclude_id: Optional[int],
) -> None:
if not account_id:
return
async with self.async_session() as session:
result = await session.execute(
select(TelephonyConfigurationModel).where(
TelephonyConfigurationModel.organization_id == organization_id,
TelephonyConfigurationModel.provider == provider,
)
)
for row in result.scalars().all():
if exclude_id is not None and row.id == exclude_id:
continue
stored = (row.credentials or {}).get(credential_field)
if stored and stored == account_id:
raise TelephonyConfigurationDuplicateAccountError(
f"A {provider} configuration with this account is already "
f"registered (config id {row.id})."
)
@staticmethod
async def _clear_default_outbound(session, organization_id: int) -> None:
await session.execute(