mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-22 11:51:04 +02:00
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:
parent
5cfdbeff02
commit
7fd3b96470
48 changed files with 1529 additions and 545 deletions
|
|
@ -24,27 +24,35 @@ from typing import Any, Dict, List, Optional, Tuple, Type
|
|||
from loguru import logger
|
||||
|
||||
from api.db import db_client
|
||||
from api.db.models import TelephonyConfigurationModel
|
||||
from api.db.models import TelephonyConfigurationModel, WorkflowRunModel
|
||||
from api.services.telephony import registry
|
||||
from api.services.telephony.base import TelephonyProvider
|
||||
|
||||
|
||||
async def load_telephony_config_by_id(
|
||||
telephony_configuration_id: int,
|
||||
organization_id: int,
|
||||
) -> Dict[str, Any]:
|
||||
"""Load and normalize the config row by primary key.
|
||||
"""Load and normalize the config row by primary key, scoped to the org.
|
||||
|
||||
Returns a dict in the shape each provider class expects in its constructor
|
||||
(provider name + provider-specific credentials + ``from_numbers`` list of
|
||||
raw address strings).
|
||||
raw address strings). Raises ``ValueError`` if the config doesn't exist
|
||||
or doesn't belong to ``organization_id`` — the org scope is what makes
|
||||
this safe to expose to user-driven request flows.
|
||||
"""
|
||||
if not telephony_configuration_id:
|
||||
raise ValueError("telephony_configuration_id is required")
|
||||
if not organization_id:
|
||||
raise ValueError("organization_id is required")
|
||||
|
||||
row = await db_client.get_telephony_configuration(telephony_configuration_id)
|
||||
row = await db_client.get_telephony_configuration_for_org(
|
||||
telephony_configuration_id, organization_id
|
||||
)
|
||||
if not row:
|
||||
raise ValueError(
|
||||
f"Telephony configuration {telephony_configuration_id} not found"
|
||||
f"Telephony configuration {telephony_configuration_id} not found "
|
||||
f"for organization {organization_id}"
|
||||
)
|
||||
return await _normalize_with_phone_numbers(row)
|
||||
|
||||
|
|
@ -68,6 +76,9 @@ async def find_telephony_config_for_inbound(
|
|||
) -> Optional[Tuple[int, Dict[str, Any]]]:
|
||||
"""Match an inbound webhook to one of the org's configs of the detected
|
||||
provider. Returns ``(config_id, normalized_config)`` or None.
|
||||
|
||||
Always scoped to ``organization_id`` — never matches across orgs even if
|
||||
two orgs happen to have credentials with the same account_id.
|
||||
"""
|
||||
spec = registry.get_optional(provider_name)
|
||||
if not spec:
|
||||
|
|
@ -96,10 +107,10 @@ async def find_telephony_config_for_inbound(
|
|||
matched = next(
|
||||
(c for c in candidates if c.is_default_outbound), candidates[0]
|
||||
)
|
||||
else:
|
||||
elif account_id:
|
||||
for cand in candidates:
|
||||
stored = (cand.credentials or {}).get(field)
|
||||
if stored and account_id and stored == account_id:
|
||||
if stored and stored == account_id:
|
||||
matched = cand
|
||||
break
|
||||
|
||||
|
|
@ -112,11 +123,32 @@ async def find_telephony_config_for_inbound(
|
|||
|
||||
async def get_telephony_provider_by_id(
|
||||
telephony_configuration_id: int,
|
||||
organization_id: int,
|
||||
) -> TelephonyProvider:
|
||||
config = await load_telephony_config_by_id(telephony_configuration_id)
|
||||
config = await load_telephony_config_by_id(
|
||||
telephony_configuration_id, organization_id
|
||||
)
|
||||
return _instantiate(config)
|
||||
|
||||
|
||||
async def get_telephony_provider_for_run(
|
||||
workflow_run: WorkflowRunModel,
|
||||
organization_id: int,
|
||||
) -> TelephonyProvider:
|
||||
"""Resolve the provider for a given workflow run.
|
||||
|
||||
Prefers ``initial_context.telephony_configuration_id`` — stamped at run
|
||||
creation by ``/initiate-call``, ``_create_inbound_workflow_run``, the
|
||||
campaign dispatcher, and ``public_agent``. Falls back to the org's
|
||||
default config so legacy runs created before the multi-config migration
|
||||
still resolve.
|
||||
"""
|
||||
cfg_id = (workflow_run.initial_context or {}).get("telephony_configuration_id")
|
||||
if cfg_id:
|
||||
return await get_telephony_provider_by_id(cfg_id, organization_id)
|
||||
return await get_default_telephony_provider(organization_id)
|
||||
|
||||
|
||||
async def get_default_telephony_provider(organization_id: int) -> TelephonyProvider:
|
||||
config = await load_default_telephony_config(organization_id)
|
||||
return _instantiate(config)
|
||||
|
|
@ -149,7 +181,9 @@ async def load_credentials_for_transport(
|
|||
Raises ValueError when the resolved config is for a different provider.
|
||||
"""
|
||||
if telephony_configuration_id:
|
||||
config = await load_telephony_config_by_id(telephony_configuration_id)
|
||||
config = await load_telephony_config_by_id(
|
||||
telephony_configuration_id, organization_id
|
||||
)
|
||||
else:
|
||||
config = await load_default_telephony_config(organization_id)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue