dograh/api/services/telephony/providers/ari/__init__.py
Abhishek d8cd34e8d2
feat: add vici dial controls from UI (#563)
* ViciDial Working

* feat(telephony): add FreeSWITCH provider to upstream-PBX seam

Generalize the upstream-PBX capture and control paths to dispatch by
provider. FreeSWITCH bridges in with X-PBX-* headers and is driven over
the Event Socket Library (uuid_kill / uuid_transfer) by the channel UUID,
alongside the existing VICIdial ra_call_control path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* add vici specific configs

* feat(telephony): env-based VICIdial config + address3 post-call routing

Move VICIdial agent/non-agent API and FreeSWITCH ESL connection settings
out of hardcoded POC values into environment variables so the same image
works against a PBX on another server.

Add VICIdial update_lead forwarding: X-VICI-UPDATE-LEAD_* extracted
variables are mapped to lead columns and pushed via the non-agent API
before a transfer, with reserved API-control params dropped.

Add hardcoded address3 disposition routing (Y/N -> in-group transfer,
else hang up) and a synchronous final variable extraction so the
transfer path sees the freshest conversation state. Skip upstream_pbx
capture on non-PJSIP channels to avoid Asterisk 500s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: run formatter

* feat: make vici configurable from UI

* chore: clean up PR

* fix: incorporate review comments

* chore: incorporate review comments

* chore: generate client

* chore: incporporate review comments

* chore: incorporate review comments

---------

Co-authored-by: Dograh POC <payment@dograh.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:57:34 +05:30

175 lines
5.5 KiB
Python

"""ARI (Asterisk REST Interface) telephony provider package."""
from typing import Any, Dict
from api.services.telephony.registry import (
ProviderSpec,
ProviderUICondition,
ProviderUIField,
ProviderUIMetadata,
ProviderUIOption,
register,
)
from .config import ARIConfigurationRequest, ARIConfigurationResponse
from .provider import ARIProvider
from .transport import create_transport
def _config_loader(value: Dict[str, Any]) -> Dict[str, Any]:
return {
"provider": "ari",
"ari_endpoint": value.get("ari_endpoint"),
"app_name": value.get("app_name"),
"app_password": value.get("app_password"),
"external_pbx": value.get("external_pbx"),
"from_numbers": value.get("from_numbers", []),
}
_UI_METADATA = ProviderUIMetadata(
display_name="Asterisk ARI",
docs_url="https://docs.dograh.com/integrations/telephony/asterisk-ari",
fields=[
ProviderUIField(
name="ari_endpoint",
label="ARI Endpoint",
type="text",
description="ARI base URL (e.g., http://asterisk.example.com:8088)",
),
ProviderUIField(
name="app_name",
label="Stasis App Name",
type="text",
description="Stasis application name registered in Asterisk",
),
ProviderUIField(
name="app_password",
label="ARI Password",
type="password",
sensitive=True,
),
ProviderUIField(
name="ws_client_name",
label="websocket_client.conf Name",
type="text",
description="websocket_client.conf connection name for externalMedia",
),
ProviderUIField(
name="from_numbers",
label="From Extensions",
type="string-array",
description="SIP extensions/numbers for outbound calls",
),
ProviderUIField(
name="external_pbx.type",
label="External PBX Type",
type="select",
required=False,
description=(
"Enable PBX-specific call control for calls patched into Dograh "
"through this Asterisk configuration."
),
options=[ProviderUIOption(value="vicidial", label="VICIdial")],
section="External PBX",
feature_gate="external_pbx_integrations",
),
ProviderUIField(
name="external_pbx.agent_api.url",
label="Agent API URL",
type="text",
description="Full VICIdial remote-agent API URL, ending in agc/api.php",
placeholder="https://vici.example.com/agc/api.php",
visible_when=ProviderUICondition(
field="external_pbx.type", equals="vicidial"
),
section="External PBX",
feature_gate="external_pbx_integrations",
),
ProviderUIField(
name="external_pbx.agent_api.username",
label="Agent API User",
type="text",
sensitive=True,
visible_when=ProviderUICondition(
field="external_pbx.type", equals="vicidial"
),
section="External PBX",
feature_gate="external_pbx_integrations",
),
ProviderUIField(
name="external_pbx.agent_api.password",
label="Agent API Password",
type="password",
sensitive=True,
visible_when=ProviderUICondition(
field="external_pbx.type", equals="vicidial"
),
section="External PBX",
feature_gate="external_pbx_integrations",
),
ProviderUIField(
name="external_pbx.non_agent_api.url",
label="Non-Agent API URL",
type="text",
required=False,
description=(
"Optional. Required only when a workflow updates VICIdial lead fields."
),
placeholder="https://vici.example.com/vicidial/non_agent_api.php",
visible_when=ProviderUICondition(
field="external_pbx.type", equals="vicidial"
),
section="External PBX",
feature_gate="external_pbx_integrations",
),
ProviderUIField(
name="external_pbx.non_agent_api.username",
label="Non-Agent API User",
type="text",
required=False,
sensitive=True,
visible_when=ProviderUICondition(
field="external_pbx.type", equals="vicidial"
),
section="External PBX",
feature_gate="external_pbx_integrations",
),
ProviderUIField(
name="external_pbx.non_agent_api.password",
label="Non-Agent API Password",
type="password",
required=False,
sensitive=True,
visible_when=ProviderUICondition(
field="external_pbx.type", equals="vicidial"
),
section="External PBX",
feature_gate="external_pbx_integrations",
),
],
)
SPEC = ProviderSpec(
name="ari",
provider_cls=ARIProvider,
config_loader=_config_loader,
transport_factory=create_transport,
transport_sample_rate=8000,
config_request_cls=ARIConfigurationRequest,
ui_metadata=_UI_METADATA,
config_response_cls=ARIConfigurationResponse,
)
register(SPEC)
__all__ = [
"SPEC",
"ARIConfigurationRequest",
"ARIConfigurationResponse",
"ARIProvider",
"create_transport",
]