feat: add ThunderPhone realtime (speech-to-speech) plugin - #7202
Open
kolchinski wants to merge 2 commits into
Open
feat: add ThunderPhone realtime (speech-to-speech) plugin#7202kolchinski wants to merge 2 commits into
kolchinski wants to merge 2 commits into
Conversation
This was referenced Sep 10, 2026
Author
|
Pushed fixes for the review findings and CI:
|
ThunderPhone's Realtime WebSocket API speaks the OpenAI Realtime protocol, so the plugin subclasses livekit-plugins-openai's RealtimeModel/RealtimeSession and adds the ThunderPhone-specific parts: the endpoint and its query parameters (saved agent, engine, language, phone numbers, wire audio), sk_live_ keys, saved-agent sessions whose prompt/voice/tools live on ThunderPhone, one fully-configured session.update for inline sessions (the first update starts the call and freezes instructions and tools), ThunderPhone's call.* events, and a clean session end when the call hangs up.
…on, register sessions, lint/type fixes - max_session_duration is forced to None: the framework's 20-minute socket recycle would hang up and start a second billed ThunderPhone call. - capabilities declare turn_detection=True / can_disable_turn_detection=False so AgentSession never runs its own VAD or turn detector against ThunderPhone's. - session() registers in _sessions so update_options() reaches live sessions. - call.ended is logged with the lk.pii.event marker; call_id remap guards non-str. - ruff import order, unused import; mypy-clean event names.
kolchinski
force-pushed
the
add-thunderphone-plugin
branch
from
September 10, 2026 18:37
cc0da1c to
dca147c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
livekit-plugins-thunderphone: ThunderPhone voice agents as a realtime (speech-to-speech) model.ThunderPhone's Realtime WebSocket API speaks the OpenAI Realtime protocol, so the plugin is a thin subclass of
livekit-plugins-openai'sRealtimeModel/RealtimeSessionthat adds the ThunderPhone-specific parts:sk_live_keys,provider = "ThunderPhone";RealtimeModel(agent_id=12)): prompt, voice, engine, languages, tools and greeting live on ThunderPhone, so the Agent's instructions/tools are not sent and the capabilities declare instructions and tools immutable;RealtimeModel(product="bolt", voice="olivia")): instructions and tools come from the LiveKitAgent; the firstsession.updatestarts the call on ThunderPhone and freezes them, so the plugin sends one fully-configured update instead of the framework's three incremental ones;call.*events (hang-up reason, transfer, keypad, ignored speech) asthunderphone_call_event/thunderphone_call_ended, and a clean session end when the call hangs up (no reconnect: a reconnect would start a new, separately billed call).Docs: https://thunderphone.com/docs/guides/use-with-livekit
Installation
Validation
tests/test_plugin_thunderphone_realtime.py: 15 passed (URL/config shaping, held first update, call events, call-id mapping).call.ended→ session closes cleanly, bad key →APIStatusError.uv lockregenerated for the new workspace member.