Skip to content

Outbound Voice with TAC with Flex handoff#67

Open
dee-at-twilio wants to merge 7 commits into
twilio:mainfrom
dee-at-twilio:main
Open

Outbound Voice with TAC with Flex handoff#67
dee-at-twilio wants to merge 7 commits into
twilio:mainfrom
dee-at-twilio:main

Conversation

@dee-at-twilio

Copy link
Copy Markdown

Summary

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring

Checklist

  • Tests added/updated
  • Documentation updated
  • Tested E2E

SDK Parity

This is the Python SDK. If this change affects shared functionality, ensure the TypeScript SDK is updated as well.

Tip: Use the /sync-to-ts-sdk skill in Claude Code to automatically generate and create a TypeScript SDK PR from your changes.

  • Change is Python-specific (no TypeScript update needed)
  • TypeScript SDK PR created:

fixed.append(updated)
else:
fixed.append(p)
return fixed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this trying to accomplish?

@ryanrishi ryanrishi requested a review from Copilot June 29, 2026 20:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-API calls in VoiceChannel.
  • Update FastAPI /conversation-relay-callback route to optionally return TaskRouter <Enqueue> TwiML when HandoffData is 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.

Comment on lines 223 to 224
@app.post(config.conversation_relay_callback_path)
async def conversation_relay_callback(request: Request) -> Response:
Comment on lines +225 to 238
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)
Comment on lines +231 to +235
task_attrs = json.dumps({"handoffData": payload_dict["HandoffData"]})
twiml = (
f'<Response><Enqueue workflowSid="{workflow_sid}">'
f"<Task>{task_attrs}</Task></Enqueue></Response>"
)
Comment on lines 8 to +9
if TYPE_CHECKING:
from typing import Literal
Comment on lines +113 to +115
expected_type: Literal["AI_AGENT", "CUSTOMER"] = (
"AI_AGENT" if voice_addr == agent_phone else "CUSTOMER"
)
Comment thread getting_started/README.md
@@ -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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants