Outbound Voice with TAC with Flex handoff#67
Conversation
| fixed.append(updated) | ||
| else: | ||
| fixed.append(p) | ||
| return fixed |
There was a problem hiding this comment.
We shouldn't need this after voice team fixes this fix on the conversation relay side. Let's wait and see till then
| f"<Task>{task_attrs}</Task></Enqueue></Response>" | ||
| ) | ||
| return Response(content=twiml, media_type="application/xml") | ||
|
|
There was a problem hiding this comment.
What is this trying to accomplish?
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds support for outbound voice calls via ConversationRelay + TAC by correcting inverted participant roles and enabling optional Flex handoff on call completion via a callback route.
Changes:
- Add outbound participant-role correction logic for
OUTBOUND-APIcalls inVoiceChannel. - Update FastAPI
/conversation-relay-callbackroute to optionally return TaskRouter<Enqueue>TwiML whenHandoffDatais present. - Add unit/integration tests and expand getting-started docs for outbound calling.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_voice_channel.py | Adds tests for outbound participant role correction and websocket integration behavior. |
| tests/test_server.py | Adds tests for /conversation-relay-callback behavior with/without HandoffData and workflow SID. |
| src/tac/server/fastapi_server.py | Updates callback route to return <Enqueue> TwiML for Flex handoff scenarios. |
| src/tac/channels/voice/channel.py | Implements _fix_outbound_participant_roles and invokes it for OUTBOUND-API setup messages. |
| getting_started/README.md | Documents how to initiate an outbound call with callback/action URL. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @app.post(config.conversation_relay_callback_path) | ||
| async def conversation_relay_callback(request: Request) -> Response: |
| form_data = await request.form() | ||
| payload_dict = {k: str(v) for k, v in form_data.items()} | ||
| await vc.handle_conversation_relay_callback(payload_dict) | ||
|
|
||
| workflow_sid = os.environ.get("TWILIO_TASKROUTER_WORKFLOW_SID", "") | ||
| if workflow_sid and payload_dict.get("HandoffData"): | ||
| task_attrs = json.dumps({"handoffData": payload_dict["HandoffData"]}) | ||
| twiml = ( | ||
| f'<Response><Enqueue workflowSid="{workflow_sid}">' | ||
| f"<Task>{task_attrs}</Task></Enqueue></Response>" | ||
| ) | ||
| return Response(content=twiml, media_type="application/xml") | ||
|
|
||
| return Response(content="", media_type="text/plain", status_code=200) |
| task_attrs = json.dumps({"handoffData": payload_dict["HandoffData"]}) | ||
| twiml = ( | ||
| f'<Response><Enqueue workflowSid="{workflow_sid}">' | ||
| f"<Task>{task_attrs}</Task></Enqueue></Response>" | ||
| ) |
| if TYPE_CHECKING: | ||
| from typing import Literal |
| expected_type: Literal["AI_AGENT", "CUSTOMER"] = ( | ||
| "AI_AGENT" if voice_addr == agent_phone else "CUSTOMER" | ||
| ) |
| @@ -132,6 +132,24 @@ See `examples/.env.example` for all available configuration options. Key variabl | |||
| - `TWILIO_WHATSAPP_NUMBER`: WhatsApp-enabled phone number in format `whatsapp:+1234567890` (required for `features/whatsapp.py`) | |||
| - `TWILIO_CONVERSATIONS_SERVICE_SID`: Conversations Service SID (required for Chat channel examples) | |||
|
|
|||
Summary
Type of Change
Checklist
SDK Parity
This is the Python SDK. If this change affects shared functionality, ensure the TypeScript SDK is updated as well.