mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-13 11:22:14 +02:00
fix: scope workflow run to org rather than user
This commit is contained in:
parent
4989bab1e9
commit
43737c67dc
29 changed files with 218 additions and 158 deletions
|
|
@ -354,7 +354,6 @@ class CampaignCallDispatcher:
|
|||
webhook_url = (
|
||||
f"{backend_endpoint}/api/v1/telephony/{webhook_endpoint}"
|
||||
f"?workflow_id={campaign.workflow_id}"
|
||||
f"&user_id={campaign.created_by}"
|
||||
f"&workflow_run_id={workflow_run.id}"
|
||||
f"&organization_id={campaign.organization_id}"
|
||||
)
|
||||
|
|
@ -365,7 +364,7 @@ class CampaignCallDispatcher:
|
|||
workflow_run_id=workflow_run.id,
|
||||
from_number=from_number,
|
||||
workflow_id=campaign.workflow_id,
|
||||
user_id=campaign.created_by,
|
||||
organization_id=campaign.organization_id,
|
||||
)
|
||||
|
||||
# Store provider type and metadata in gathered_context
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ async def run_pipeline_telephony(
|
|||
provider_name: str,
|
||||
workflow_id: int,
|
||||
workflow_run_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
call_id: str,
|
||||
transport_kwargs: dict,
|
||||
) -> None:
|
||||
|
|
@ -268,7 +268,7 @@ async def run_pipeline_telephony(
|
|||
provider_name=provider_name,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=call_id,
|
||||
transport_kwargs=transport_kwargs,
|
||||
)
|
||||
|
|
@ -285,7 +285,7 @@ async def _run_pipeline_telephony_impl(
|
|||
provider_name: str,
|
||||
workflow_id: int,
|
||||
workflow_run_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
call_id: str,
|
||||
transport_kwargs: dict,
|
||||
) -> None:
|
||||
|
|
@ -300,7 +300,9 @@ async def _run_pipeline_telephony_impl(
|
|||
provider_name: Stable identifier of the provider (registry key).
|
||||
workflow_id: Workflow being executed.
|
||||
workflow_run_id: Workflow run row.
|
||||
user_id: Owner of the workflow.
|
||||
organization_id: Tenant owning the workflow and the run. Every lookup
|
||||
below is scoped by it; the workflow owner is read off the workflow
|
||||
row and used only for attribution.
|
||||
call_id: Provider call identifier.
|
||||
transport_kwargs: Provider-specific kwargs forwarded to the transport
|
||||
factory (e.g. stream_sid + call_sid for Twilio).
|
||||
|
|
@ -308,12 +310,15 @@ async def _run_pipeline_telephony_impl(
|
|||
logger.debug(f"Running {provider_name} pipeline for workflow_run {workflow_run_id}")
|
||||
set_current_run_id(workflow_run_id)
|
||||
|
||||
workflow = await db_client.get_workflow(workflow_id, user_id)
|
||||
if workflow:
|
||||
set_current_org_id(workflow.organization_id)
|
||||
workflow = await db_client.get_workflow(
|
||||
workflow_id, organization_id=organization_id
|
||||
)
|
||||
if not workflow:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
set_current_org_id(workflow.organization_id)
|
||||
|
||||
ambient_noise_config = None
|
||||
if workflow and workflow.workflow_configurations:
|
||||
if workflow.workflow_configurations:
|
||||
ambient_noise_config = workflow.workflow_configurations.get(
|
||||
"ambient_noise_configuration"
|
||||
)
|
||||
|
|
@ -322,9 +327,16 @@ async def _run_pipeline_telephony_impl(
|
|||
# (test call, campaign dispatch, inbound). Transports use it to load creds
|
||||
# from the right config row. Falls back to None for legacy runs (transports
|
||||
# then resolve the org's default config).
|
||||
workflow_run = await db_client.get_workflow_run(workflow_run_id)
|
||||
workflow_run = await db_client.get_workflow_run(
|
||||
workflow_run_id, organization_id=organization_id
|
||||
)
|
||||
if not workflow_run:
|
||||
raise HTTPException(status_code=404, detail="Workflow run not found")
|
||||
if workflow_run.workflow_id != workflow_id:
|
||||
raise HTTPException(status_code=400, detail="workflow_run_workflow_mismatch")
|
||||
|
||||
telephony_configuration_id = None
|
||||
if workflow_run and workflow_run.initial_context:
|
||||
if workflow_run.initial_context:
|
||||
telephony_configuration_id = workflow_run.initial_context.get(
|
||||
"telephony_configuration_id"
|
||||
)
|
||||
|
|
@ -336,11 +348,9 @@ async def _run_pipeline_telephony_impl(
|
|||
get_effective_ai_model_configuration_for_workflow,
|
||||
)
|
||||
|
||||
run_configs = (
|
||||
(workflow_run.definition.workflow_configurations or {}) if workflow_run else {}
|
||||
)
|
||||
run_configs = workflow_run.definition.workflow_configurations or {}
|
||||
user_config = await get_effective_ai_model_configuration_for_workflow(
|
||||
organization_id=workflow.organization_id if workflow else None,
|
||||
organization_id=workflow.organization_id,
|
||||
workflow_configurations=run_configs,
|
||||
)
|
||||
is_realtime = bool(user_config.is_realtime and user_config.realtime is not None)
|
||||
|
|
@ -364,10 +374,12 @@ async def _run_pipeline_telephony_impl(
|
|||
transport,
|
||||
workflow_id,
|
||||
workflow_run_id,
|
||||
user_id,
|
||||
# Attribution only — scoping is driven by organization_id below.
|
||||
workflow.user_id,
|
||||
audio_config=audio_config,
|
||||
workflow_run=workflow_run,
|
||||
resolved_user_config=user_config,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class TelephonyProvider(ABC):
|
|||
async def verify_webhook_signature(self, url: str, params: Dict[str, Any], signature: str) -> bool
|
||||
|
||||
@abstractmethod
|
||||
async def get_webhook_response(self, workflow_id: int, user_id: int, workflow_run_id: int) -> str
|
||||
async def get_webhook_response(self, workflow_id: int, organization_id: int, workflow_run_id: int) -> str
|
||||
```
|
||||
|
||||
## Configuration Loading
|
||||
|
|
|
|||
|
|
@ -353,19 +353,18 @@ class ARIConnection:
|
|||
|
||||
workflow_run_id = args_dict.get("workflow_run_id")
|
||||
workflow_id = args_dict.get("workflow_id")
|
||||
user_id = args_dict.get("user_id")
|
||||
|
||||
if not workflow_run_id or not workflow_id or not user_id:
|
||||
if not workflow_run_id or not workflow_id:
|
||||
logger.warning(
|
||||
f"[ARI org={self.organization_id}] StasisStart missing required args: "
|
||||
f"workflow_run_id={workflow_run_id}, workflow_id={workflow_id}, user_id={user_id}"
|
||||
f"workflow_run_id={workflow_run_id}, workflow_id={workflow_id}"
|
||||
)
|
||||
return
|
||||
|
||||
# Start pipeline connection in background task
|
||||
asyncio.create_task(
|
||||
self._handle_stasis_start(
|
||||
channel_id, channel_state, workflow_run_id, workflow_id, user_id
|
||||
channel_id, channel_state, workflow_run_id, workflow_id
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -448,7 +447,6 @@ class ARIConnection:
|
|||
async def _create_external_media(
|
||||
self,
|
||||
workflow_id: str,
|
||||
user_id: str,
|
||||
workflow_run_id: str,
|
||||
channel_id: Optional[str] = None,
|
||||
) -> str:
|
||||
|
|
@ -464,10 +462,10 @@ class ARIConnection:
|
|||
the POST and avoid racing against the StasisStart event.
|
||||
"""
|
||||
# v() appends URI query params to the websocket_client.conf URL
|
||||
# e.g. wss://api.dograh.com/ws/ari?workflow_id=1&user_id=2&workflow_run_id=3
|
||||
# e.g. wss://api.dograh.com/ws/ari?workflow_id=1&organization_id=2&workflow_run_id=3
|
||||
transport_data = (
|
||||
f"v(workflow_id={workflow_id},"
|
||||
f"user_id={user_id},"
|
||||
f"organization_id={self.organization_id},"
|
||||
f"workflow_run_id={workflow_run_id})"
|
||||
)
|
||||
|
||||
|
|
@ -646,7 +644,6 @@ class ARIConnection:
|
|||
channel_state,
|
||||
str(workflow_run.id),
|
||||
str(inbound_workflow_id),
|
||||
str(user_id),
|
||||
)
|
||||
except Exception as e:
|
||||
if workflow_run:
|
||||
|
|
@ -668,7 +665,6 @@ class ARIConnection:
|
|||
channel_state: str,
|
||||
workflow_run_id: str,
|
||||
workflow_id: str,
|
||||
user_id: str,
|
||||
):
|
||||
"""Set up external media for a caller channel that has entered Stasis.
|
||||
|
||||
|
|
@ -712,7 +708,6 @@ class ARIConnection:
|
|||
# 3. Create the ext media channel with the id we just registered.
|
||||
created_id = await self._create_external_media(
|
||||
workflow_id,
|
||||
user_id,
|
||||
workflow_run_id,
|
||||
channel_id=ext_channel_id,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -150,14 +150,15 @@ class TelephonyProvider(ABC):
|
|||
|
||||
@abstractmethod
|
||||
async def get_webhook_response(
|
||||
self, workflow_id: int, user_id: int, workflow_run_id: int
|
||||
self, workflow_id: int, organization_id: int, workflow_run_id: int
|
||||
) -> str:
|
||||
"""
|
||||
Generate the initial webhook response for starting a call session.
|
||||
|
||||
Args:
|
||||
workflow_id: The workflow ID
|
||||
user_id: The user ID
|
||||
organization_id: The organization owning the workflow; providers
|
||||
embed it in the media websocket URL they hand back
|
||||
workflow_run_id: The workflow run ID
|
||||
|
||||
Returns:
|
||||
|
|
@ -223,7 +224,7 @@ class TelephonyProvider(ABC):
|
|||
self,
|
||||
websocket: "WebSocket",
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -232,10 +233,14 @@ class TelephonyProvider(ABC):
|
|||
This method encapsulates all provider-specific WebSocket handshake and
|
||||
message routing logic, keeping the main websocket endpoint clean.
|
||||
|
||||
``organization_id`` is the tenant that every workflow/run lookup must be
|
||||
scoped by. The workflow owner is deliberately not passed in — derive it
|
||||
from the workflow row where it's needed for attribution.
|
||||
|
||||
Args:
|
||||
websocket: The WebSocket connection
|
||||
workflow_id: The workflow ID
|
||||
user_id: The user ID
|
||||
organization_id: The organization owning the workflow and run
|
||||
workflow_run_id: The workflow run ID
|
||||
"""
|
||||
pass
|
||||
|
|
@ -355,7 +360,6 @@ class TelephonyProvider(ABC):
|
|||
*,
|
||||
organization_id: int,
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
workflow_run_id: int,
|
||||
params: Dict[str, str],
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ class ARIProvider(TelephonyProvider):
|
|||
[
|
||||
f"workflow_run_id={workflow_run_id}",
|
||||
f"workflow_id={kwargs.get('workflow_id', '')}",
|
||||
f"user_id={kwargs.get('user_id', '')}",
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -179,7 +178,7 @@ class ARIProvider(TelephonyProvider):
|
|||
return True
|
||||
|
||||
async def get_webhook_response(
|
||||
self, workflow_id: int, user_id: int, workflow_run_id: int
|
||||
self, workflow_id: int, organization_id: int, workflow_run_id: int
|
||||
) -> str:
|
||||
"""ARI does not use webhook responses - call control is via REST API."""
|
||||
logger.warning(
|
||||
|
|
@ -241,7 +240,7 @@ class ARIProvider(TelephonyProvider):
|
|||
self,
|
||||
websocket: "WebSocket",
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -253,7 +252,9 @@ class ARIProvider(TelephonyProvider):
|
|||
from api.services.pipecat.run_pipeline import run_pipeline_telephony
|
||||
|
||||
# Get channel_id from workflow run context
|
||||
workflow_run = await db_client.get_workflow_run(workflow_run_id, user_id)
|
||||
workflow_run = await db_client.get_workflow_run(
|
||||
workflow_run_id, organization_id=organization_id
|
||||
)
|
||||
channel_id = ""
|
||||
if workflow_run and workflow_run.gathered_context:
|
||||
channel_id = workflow_run.gathered_context.get("call_id", "")
|
||||
|
|
@ -267,7 +268,7 @@ class ARIProvider(TelephonyProvider):
|
|||
provider_name=self.PROVIDER_NAME,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=channel_id,
|
||||
transport_kwargs={"channel_id": channel_id},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -120,7 +120,10 @@ class CloudonixProvider(TelephonyProvider):
|
|||
logger.info(
|
||||
f"Selected phone number {from_number} for outbound call to {to_number}"
|
||||
)
|
||||
workflow_id, user_id = kwargs["workflow_id"], kwargs["user_id"]
|
||||
workflow_id, organization_id = (
|
||||
kwargs["workflow_id"],
|
||||
kwargs["organization_id"],
|
||||
)
|
||||
|
||||
# Prepare call data using Cloudonix callObject schema
|
||||
# Note: 'caller-id' is REQUIRED by Cloudonix API
|
||||
|
|
@ -130,7 +133,7 @@ class CloudonixProvider(TelephonyProvider):
|
|||
"cxml": f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Response>
|
||||
<Connect>
|
||||
<Stream url="{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{user_id}/{workflow_run_id}"></Stream>
|
||||
<Stream url="{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}"></Stream>
|
||||
</Connect>
|
||||
<Pause length="40"/>
|
||||
</Response>""",
|
||||
|
|
@ -408,7 +411,7 @@ class CloudonixProvider(TelephonyProvider):
|
|||
}
|
||||
|
||||
async def get_webhook_response(
|
||||
self, workflow_id: int, user_id: int, workflow_run_id: int
|
||||
self, workflow_id: int, organization_id: int, workflow_run_id: int
|
||||
) -> str:
|
||||
"""
|
||||
Dummy implementation - Cloudonix doesn't use webhook responses.
|
||||
|
|
@ -430,7 +433,7 @@ class CloudonixProvider(TelephonyProvider):
|
|||
self,
|
||||
websocket: "WebSocket",
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -502,7 +505,7 @@ class CloudonixProvider(TelephonyProvider):
|
|||
provider_name=self.PROVIDER_NAME,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=call_id,
|
||||
transport_kwargs={"call_id": call_id, "stream_sid": stream_sid},
|
||||
)
|
||||
|
|
@ -517,7 +520,6 @@ class CloudonixProvider(TelephonyProvider):
|
|||
*,
|
||||
organization_id: int,
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
workflow_run_id: int,
|
||||
params: Dict[str, str],
|
||||
) -> None:
|
||||
|
|
@ -671,7 +673,7 @@ class CloudonixProvider(TelephonyProvider):
|
|||
provider_name=self.PROVIDER_NAME,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=call_session,
|
||||
transport_kwargs={
|
||||
"call_id": call_session,
|
||||
|
|
|
|||
|
|
@ -238,13 +238,13 @@ class PlivoProvider(TelephonyProvider):
|
|||
return any(hmac.compare_digest(computed, candidate) for candidate in candidates)
|
||||
|
||||
async def get_webhook_response(
|
||||
self, workflow_id: int, user_id: int, workflow_run_id: int
|
||||
self, workflow_id: int, organization_id: int, workflow_run_id: int
|
||||
) -> str:
|
||||
_, wss_backend_endpoint = await get_backend_endpoints()
|
||||
|
||||
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Response>
|
||||
<Stream bidirectional="true" keepCallAlive="true" contentType="audio/x-mulaw;rate=8000">{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{user_id}/{workflow_run_id}</Stream>
|
||||
<Stream bidirectional="true" keepCallAlive="true" contentType="audio/x-mulaw;rate=8000">{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}</Stream>
|
||||
</Response>"""
|
||||
|
||||
async def get_call_cost(self, call_id: str) -> Dict[str, Any]:
|
||||
|
|
@ -309,7 +309,7 @@ class PlivoProvider(TelephonyProvider):
|
|||
self,
|
||||
websocket: "WebSocket",
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int,
|
||||
) -> None:
|
||||
from api.services.pipecat.run_pipeline import run_pipeline_telephony
|
||||
|
|
@ -348,7 +348,7 @@ class PlivoProvider(TelephonyProvider):
|
|||
provider_name=self.PROVIDER_NAME,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=call_id,
|
||||
transport_kwargs={"stream_id": stream_id, "call_id": call_id},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ async def _handle_plivo_status_callback(
|
|||
@router.post("/plivo-xml", include_in_schema=False)
|
||||
async def handle_plivo_xml_webhook(
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
workflow_run_id: int,
|
||||
organization_id: int,
|
||||
request: Request,
|
||||
|
|
@ -110,7 +109,7 @@ async def handle_plivo_xml_webhook(
|
|||
)
|
||||
|
||||
response_content = await provider.get_webhook_response(
|
||||
workflow_id, user_id, workflow_run_id
|
||||
workflow_id, organization_id, workflow_run_id
|
||||
)
|
||||
return HTMLResponse(content=response_content, media_type="application/xml")
|
||||
|
||||
|
|
|
|||
|
|
@ -101,10 +101,10 @@ class TelnyxProvider(TelephonyProvider):
|
|||
|
||||
# Build the WebSocket stream URL for inline audio streaming
|
||||
workflow_id = kwargs.get("workflow_id")
|
||||
user_id = kwargs.get("user_id")
|
||||
organization_id = kwargs.get("organization_id")
|
||||
stream_url = (
|
||||
f"{wss_backend_endpoint}/api/v1/telephony/ws"
|
||||
f"/{workflow_id}/{user_id}/{workflow_run_id}"
|
||||
f"/{workflow_id}/{organization_id}/{workflow_run_id}"
|
||||
)
|
||||
|
||||
# Build the webhook URL for status callbacks
|
||||
|
|
@ -267,7 +267,7 @@ class TelnyxProvider(TelephonyProvider):
|
|||
return False
|
||||
|
||||
async def get_webhook_response(
|
||||
self, workflow_id: int, user_id: int, workflow_run_id: int
|
||||
self, workflow_id: int, organization_id: int, workflow_run_id: int
|
||||
) -> str:
|
||||
"""Not used for Telnyx — streaming is inline with the dial request."""
|
||||
return ""
|
||||
|
|
@ -338,7 +338,7 @@ class TelnyxProvider(TelephonyProvider):
|
|||
self,
|
||||
websocket: "WebSocket",
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int,
|
||||
) -> None:
|
||||
"""Handle Telnyx WebSocket connection for real-time audio.
|
||||
|
|
@ -406,7 +406,7 @@ class TelnyxProvider(TelephonyProvider):
|
|||
provider_name=self.PROVIDER_NAME,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=call_control_id,
|
||||
transport_kwargs={
|
||||
"stream_id": stream_id,
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ class TwilioProvider(TelephonyProvider):
|
|||
return validator.validate(url, params, signature)
|
||||
|
||||
async def get_webhook_response(
|
||||
self, workflow_id: int, user_id: int, workflow_run_id: int
|
||||
self, workflow_id: int, organization_id: int, workflow_run_id: int
|
||||
) -> str:
|
||||
"""
|
||||
Generate TwiML response for starting a call session.
|
||||
|
|
@ -176,7 +176,7 @@ class TwilioProvider(TelephonyProvider):
|
|||
twiml_content = f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Response>
|
||||
<Connect>
|
||||
<Stream url="{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{user_id}/{workflow_run_id}"></Stream>
|
||||
<Stream url="{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}"></Stream>
|
||||
</Connect>
|
||||
<Pause length="40"/>
|
||||
</Response>"""
|
||||
|
|
@ -274,7 +274,7 @@ class TwilioProvider(TelephonyProvider):
|
|||
self,
|
||||
websocket: "WebSocket",
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -325,7 +325,7 @@ class TwilioProvider(TelephonyProvider):
|
|||
provider_name=self.PROVIDER_NAME,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=call_sid,
|
||||
transport_kwargs={"stream_sid": stream_sid, "call_sid": call_sid},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ async def _persist_amd_result_if_present(
|
|||
@router.post("/twiml", include_in_schema=False)
|
||||
async def handle_twiml_webhook(
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
workflow_run_id: int,
|
||||
organization_id: int,
|
||||
request: Request,
|
||||
|
|
@ -79,7 +78,7 @@ async def handle_twiml_webhook(
|
|||
)
|
||||
|
||||
response_content = await provider.get_webhook_response(
|
||||
workflow_id, user_id, workflow_run_id
|
||||
workflow_id, organization_id, workflow_run_id
|
||||
)
|
||||
|
||||
return HTMLResponse(content=response_content, media_type="application/xml")
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ class VobizProvider(TelephonyProvider):
|
|||
return is_valid
|
||||
|
||||
async def get_webhook_response(
|
||||
self, workflow_id: int, user_id: int, workflow_run_id: int
|
||||
self, workflow_id: int, organization_id: int, workflow_run_id: int
|
||||
) -> str:
|
||||
"""
|
||||
Generate Vobiz XML response for starting a call session.
|
||||
|
|
@ -269,7 +269,7 @@ class VobizProvider(TelephonyProvider):
|
|||
|
||||
vobiz_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Response>
|
||||
<Stream bidirectional="true" keepCallAlive="true" contentType="audio/x-mulaw;rate=8000">{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{user_id}/{workflow_run_id}</Stream>
|
||||
<Stream bidirectional="true" keepCallAlive="true" contentType="audio/x-mulaw;rate=8000">{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}</Stream>
|
||||
</Response>"""
|
||||
return vobiz_xml
|
||||
|
||||
|
|
@ -350,7 +350,7 @@ class VobizProvider(TelephonyProvider):
|
|||
self,
|
||||
websocket: "WebSocket",
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -394,7 +394,7 @@ class VobizProvider(TelephonyProvider):
|
|||
provider_name=self.PROVIDER_NAME,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=call_id,
|
||||
transport_kwargs={"stream_id": stream_id, "call_id": call_id},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ async def _verify_vobiz_callback(
|
|||
|
||||
@router.post("/vobiz-xml", include_in_schema=False)
|
||||
async def handle_vobiz_xml_webhook(
|
||||
workflow_id: int, user_id: int, workflow_run_id: int, organization_id: int
|
||||
workflow_id: int, workflow_run_id: int, organization_id: int
|
||||
):
|
||||
"""
|
||||
Handle initial webhook from Vobiz when call is answered.
|
||||
|
|
@ -65,7 +65,7 @@ async def handle_vobiz_xml_webhook(
|
|||
set_current_run_id(workflow_run_id)
|
||||
logger.info(
|
||||
f"[run {workflow_run_id}] Vobiz XML webhook called - "
|
||||
f"workflow_id={workflow_id}, user_id={user_id}, org_id={organization_id}"
|
||||
f"workflow_id={workflow_id}, org_id={organization_id}"
|
||||
)
|
||||
|
||||
workflow_run = await db_client.get_workflow_run_by_id(workflow_run_id)
|
||||
|
|
@ -74,7 +74,7 @@ async def handle_vobiz_xml_webhook(
|
|||
logger.debug(f"[run {workflow_run_id}] Using provider: {provider.PROVIDER_NAME}")
|
||||
|
||||
response_content = await provider.get_webhook_response(
|
||||
workflow_id, user_id, workflow_run_id
|
||||
workflow_id, organization_id, workflow_run_id
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ class VonageProvider(TelephonyProvider):
|
|||
return False
|
||||
|
||||
async def get_webhook_response(
|
||||
self, workflow_id: int, user_id: int, workflow_run_id: int
|
||||
self, workflow_id: int, organization_id: int, workflow_run_id: int
|
||||
) -> str:
|
||||
"""
|
||||
Generate NCCO response for starting a call session.
|
||||
|
|
@ -222,7 +222,7 @@ class VonageProvider(TelephonyProvider):
|
|||
"endpoint": [
|
||||
{
|
||||
"type": "websocket",
|
||||
"uri": f"{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{user_id}/{workflow_run_id}",
|
||||
"uri": f"{wss_backend_endpoint}/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}",
|
||||
"content-type": "audio/l16;rate=16000", # 16kHz Linear PCM
|
||||
"headers": {},
|
||||
}
|
||||
|
|
@ -323,7 +323,7 @@ class VonageProvider(TelephonyProvider):
|
|||
self,
|
||||
websocket: "WebSocket",
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -338,14 +338,17 @@ class VonageProvider(TelephonyProvider):
|
|||
|
||||
try:
|
||||
# Get workflow run to extract call UUID
|
||||
workflow_run = await db_client.get_workflow_run(workflow_run_id)
|
||||
workflow_run = await db_client.get_workflow_run(
|
||||
workflow_run_id, organization_id=organization_id
|
||||
)
|
||||
if not workflow_run:
|
||||
logger.error(f"Workflow run {workflow_run_id} not found")
|
||||
await websocket.close(code=4404, reason="Workflow run not found")
|
||||
return
|
||||
|
||||
# Get workflow for organization info
|
||||
workflow = await db_client.get_workflow(workflow_id, user_id)
|
||||
workflow = await db_client.get_workflow(
|
||||
workflow_id, organization_id=organization_id
|
||||
)
|
||||
if not workflow:
|
||||
logger.error(f"Workflow {workflow_id} not found")
|
||||
await websocket.close(code=4404, reason="Workflow not found")
|
||||
|
|
@ -392,7 +395,7 @@ class VonageProvider(TelephonyProvider):
|
|||
provider_name=self.PROVIDER_NAME,
|
||||
workflow_id=workflow_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
call_id=call_uuid,
|
||||
transport_kwargs={"call_uuid": call_uuid},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ provider registry — see ProviderSpec.router.
|
|||
"""
|
||||
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from loguru import logger
|
||||
|
|
@ -20,9 +19,8 @@ router = APIRouter()
|
|||
@router.get("/ncco", include_in_schema=False)
|
||||
async def handle_ncco_webhook(
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
workflow_run_id: int,
|
||||
organization_id: Optional[int] = None,
|
||||
organization_id: int,
|
||||
):
|
||||
"""Handle NCCO (Nexmo Call Control Objects) webhook for Vonage.
|
||||
|
||||
|
|
@ -30,12 +28,10 @@ async def handle_ncco_webhook(
|
|||
"""
|
||||
|
||||
workflow_run = await db_client.get_workflow_run_by_id(workflow_run_id)
|
||||
provider = await get_telephony_provider_for_run(
|
||||
workflow_run, organization_id or user_id
|
||||
)
|
||||
provider = await get_telephony_provider_for_run(workflow_run, organization_id)
|
||||
|
||||
response_content = await provider.get_webhook_response(
|
||||
workflow_id, user_id, workflow_run_id
|
||||
workflow_id, organization_id, workflow_run_id
|
||||
)
|
||||
|
||||
return json.loads(response_content)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue