Skip to content

refactor(voice): move ConversationRelay logic onto ConversationRelayProvider - #116

Open
xinghaohuang91 wants to merge 2 commits into
feat/conversation-relay-provider-configfrom
feat/conversation-relay-provider-impl
Open

refactor(voice): move ConversationRelay logic onto ConversationRelayProvider#116
xinghaohuang91 wants to merge 2 commits into
feat/conversation-relay-provider-configfrom
feat/conversation-relay-provider-impl

Conversation

@xinghaohuang91

Copy link
Copy Markdown
Contributor

Summary

  • Stacked on refactor(voice): add VoiceProviderConfig base for pluggable providers #114 — merge that one first.
  • VoiceChannel owned every piece of ConversationRelay-specific behavior directly: the WebSocket setup/prompt/interrupt protocol loop, outbound call initiation, and the ConversationRelay callback webhook. Moves all of it onto ConversationRelayProvider; VoiceChannel now only builds the provider (config.create_provider(self, tac.config)) and delegates through thin wrapper methods.
  • Moved as-is — method bodies are unchanged apart from the necessary self.Xself.channel.X renames for functionality VoiceChannel still owns (TAC, conversation bookkeeping, Calls API client). Verified via a line-by-line diff against the pre-move code.
  • _merge_call_options/_build_call_kwargs move in full — they're only used by outbound, and outbound support itself is provider-specific, so there's no reason to keep them on VoiceChannel for a hypothetical second provider that may not support outbound at all.
  • Renames handle_conversation_relay_callbackhandle_twilio_provider_callback: this webhook is Twilio's ConversationRelay-specific mechanism, but Media Streams has its own independent statusCallback equivalent, so the channel-facing name shouldn't bake in "ConversationRelay". VoiceProvider declares the neutral no-op default; ConversationRelayProvider implements it.
  • VoiceProvider.__init__(channel) now stores self.channel and gets its own self.logger (named after the concrete provider's module, same convention as BaseChannel) — no more threading channel through every method call.
  • Left on VoiceChannel unchanged: Calls-API webhook handling (on_call_status/on_amd/on_recording + their event handlers), _get_twilio_client, end_call, get_conversation_session_by_call_sid, process_webhook — all genuinely provider-agnostic.
  • No public API changes. Test changes are mechanical only: private provider methods are now reached via channel._provider, no more channel argument to thread through.

Test plan

  • make lint / make type-check / make check all pass
  • 876 existing tests pass (mechanical updates only, no assertions changed)
  • Manually tested end-to-end against a real call via ngrok + voice_streaming.py example — full flow (WS setup → CO conversation lookup → memory retrieval → hangup → WS cleanup → ConversationRelay callback → conversation ended) works, logs correctly attributed to tac.channels.voice.conversation_relay vs tac.channels.voice.channel

…ConversationRelayProvider

VoiceChannel owned every piece of ConversationRelay-specific behavior
directly: the WebSocket setup/prompt/interrupt protocol loop, outbound
call initiation, and the ConversationRelay callback webhook (TwiML
building was already extracted to twiml.py in an earlier PR). Move all
of it onto ConversationRelayProvider — VoiceChannel now only builds the
provider and delegates through thin wrapper methods.

Moved as-is — method bodies are unchanged apart from the necessary
self.X -> self.channel.X renames for functionality VoiceChannel still
owns (TAC, conversation bookkeeping, Calls API client).
_merge_call_options/_build_call_kwargs move in full since they're only
used by outbound, and outbound support itself is provider-specific — no
reason to keep them on VoiceChannel for a hypothetical second provider.

Also renames handle_conversation_relay_callback -> handle_twilio_provider_callback:
this webhook is Twilio's ConversationRelay-specific mechanism, but Media
Streams has its own independent statusCallback equivalent, so the
channel-facing name shouldn't bake in "ConversationRelay" — VoiceProvider
declares the neutral no-op default, ConversationRelayProvider implements it.

Left on VoiceChannel unchanged: Calls-API webhook handling (on_call_status/
on_amd/on_recording and their event handlers), _get_twilio_client, end_call,
get_conversation_session_by_call_sid, process_webhook — all genuinely
provider-agnostic.

No public API changes. Tests updated only at the mechanical level: private
provider methods are now reached via channel._provider, and the channel
argument no longer needs threading through since VoiceProvider now stores
it at construction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

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

Refactors voice transport behavior into a pluggable provider architecture while retaining generic lifecycle management in VoiceChannel.

Changes:

  • Moves ConversationRelay WebSocket, TwiML, callback, and outbound-call logic into ConversationRelayProvider.
  • Adds provider delegation methods to VoiceChannel.
  • Updates server integration and tests for the provider boundary.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/tac/channels/voice/channel.py Delegates transport behavior to the provider.
src/tac/channels/voice/conversation_relay.py Implements ConversationRelay provider behavior.
src/tac/channels/voice/provider.py Defines the provider interface and defaults.
src/tac/channels/voice/twiml.py Avoids a runtime configuration import cycle.
src/tac/server/fastapi_server.py Uses the provider-neutral callback method.
tests/test_voice_channel.py Updates voice tests for relocated internals.
tests/test_relay_only_mode.py Updates relay-only callback and interrupt tests.
Suppressed comments (1)

src/tac/channels/voice/channel.py:205

  • Renaming this method removes the existing public VoiceChannel.handle_conversation_relay_callback entry point. Custom server integrations that post the <Connect action> payload through that documented, unfiltered method will now fail with AttributeError, contrary to the stated no-public-API-change goal. Keep the old name as a compatibility alias (optionally deprecated) while the neutral name becomes preferred.
    async def handle_twilio_provider_callback(

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/tac/channels/voice/channel.py Outdated
…erConfig

Nothing read the VoiceProvider.memory_mode property except VoiceChannel's
own constructor plumbing it through. Moving the field onto the config lets
VoiceChannel.__init__ call super().__init__() before create_provider(),
removing the two-step ordering workaround.
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