feat(telephony/telnyx): add call transfer via conference bridge

Conference-based transfer for Telnyx with a two-step flow:
- transfer_call dials the destination with a per-transfer webhook URL.
- On call.answered, the webhook seeds a conference with the destination's
  live call_control_id and publishes DESTINATION_ANSWERED.
- TelnyxConferenceStrategy joins the caller into the conference on
  pipeline teardown (EndTaskReason.TRANSFER_CALL).
- On post-answer destination hangup, the webhook hangs up the caller —
  Telnyx's create_conference doesn't accept end_conference_on_exit on
  the seed leg, so we tear down the bridge ourselves.

TransferContext gains optional workflow_run_id (for webhook→provider
resolution in multi-config orgs) and conference_id (set on answer,
rd by the strategy).

Also fixes the transfer tool's provider lookup to go through
get_telephony_provider_for_run instead of the deprecated org-default
shim, which was returning the wrong provider in multi-config orgs.
This commit is contained in:
Sabiha Khan 2026-05-12 12:04:37 +05:30
parent 45a81c88e0
commit 700b7f1145
7 changed files with 596 additions and 7 deletions

View file

@ -25,7 +25,7 @@ from api.db import db_client
from api.enums import ToolCategory, WorkflowRunMode
from api.services.pipecat.audio_playback import play_audio, play_audio_loop
from api.services.telephony.call_transfer_manager import get_call_transfer_manager
from api.services.telephony.factory import get_telephony_provider
from api.services.telephony.factory import get_telephony_provider_for_run
from api.services.telephony.transfer_event_protocol import TransferContext
from api.services.workflow.tools.calculator import get_calculator_tools, safe_calculator
from api.services.workflow.tools.custom_tool import (
@ -511,7 +511,9 @@ class CustomToolManager:
)
return
provider = await get_telephony_provider(organization_id)
provider = await get_telephony_provider_for_run(
workflow_run, organization_id
)
if not provider.supports_transfers() or not provider.validate_config():
validation_error_result = {
"status": "failed",
@ -542,6 +544,7 @@ class CustomToolManager:
original_call_sid=original_call_sid,
conference_name=conference_name,
initiated_at=time.time(),
workflow_run_id=self._engine._workflow_run_id,
)
await call_transfer_manager.store_transfer_context(transfer_context)