From ec8964425a39166b3372fca206fdfff84b4031f3 Mon Sep 17 00:00:00 2001 From: fbarril Date: Fri, 21 Nov 2025 00:27:57 +0000 Subject: [PATCH] add livekit helper --- pyproject.toml | 1 + src/pipecat/transports/livekit/utils.py | 96 +++++++++++++++++++++++++ uv.lock | 6 +- 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/pipecat/transports/livekit/utils.py diff --git a/pyproject.toml b/pyproject.toml index 73da9083a..149bf18a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,6 +112,7 @@ webrtc = [ "aiortc>=1.13.0,<2", "opencv-python>=4.11.0.86,<5" ] websocket = [ "pipecat-ai[websockets-base]", "fastapi>=0.115.6,<0.122.0" ] websockets-base = [ "websockets>=13.1,<16.0" ] whisper = [ "faster-whisper~=1.1.1" ] +auth = [ "pyjwt>=2.10.1" ] [dependency-groups] dev = [ diff --git a/src/pipecat/transports/livekit/utils.py b/src/pipecat/transports/livekit/utils.py new file mode 100644 index 000000000..741b820b2 --- /dev/null +++ b/src/pipecat/transports/livekit/utils.py @@ -0,0 +1,96 @@ +# +# Copyright (c) 2024–2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + +"""LiveKit REST Helpers. + +Methods that wrap the LiveKit API for room management. +""" + +import aiohttp + + +class LiveKitRESTHelper: + """Helper class for interacting with LiveKit's REST API. + + Provides methods for managing LiveKit rooms. + """ + + def __init__( + self, + *, + api_key: str, + api_secret: str, + api_url: str = "https://your-livekit-host.com", + aiohttp_session: aiohttp.ClientSession, + ): + """Initialize the LiveKit REST helper. + + Args: + api_key: Your LiveKit API key. + api_secret: Your LiveKit API secret. + api_url: LiveKit server URL (e.g. "https://your-livekit-host.com"). + aiohttp_session: Async HTTP session for making requests. + """ + self.api_key = api_key + self.api_secret = api_secret + self.api_url = api_url.rstrip("/") + self.aiohttp_session = aiohttp_session + + def _create_access_token(self, room_create: bool = True) -> str: + """Create a signed access token for LiveKit API authentication. + + Args: + room_create: Whether to grant roomCreate permission. + + Returns: + Signed JWT access token. + """ + import time + + import jwt + + claims = { + "iss": self.api_key, + "sub": self.api_key, + "nbf": int(time.time()), + "exp": int(time.time()) + 60, # Token valid for 60 seconds + "video": { + "roomCreate": room_create, + }, + } + + return jwt.encode(claims, self.api_secret, algorithm="HS256") + + async def delete_room_by_name(self, room_name: str) -> bool: + """Delete a LiveKit room by name. + + This will forcibly disconnect all participants currently in the room. + + Args: + room_name: Name of the room to delete. + + Returns: + True if deletion was successful. + + Raises: + Exception: If deletion fails. + """ + token = self._create_access_token(room_create=True) + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + async with self.aiohttp_session.post( + f"{self.api_url}/twirp/livekit.RoomService/DeleteRoom", + headers=headers, + json={"room": room_name}, + ) as r: + if r.status != 200: + text = await r.text() + raise Exception(f"Failed to delete room [{room_name}] (status: {r.status}): {text}") + + return True diff --git a/uv.lock b/uv.lock index 662150b5b..8f7585828 100644 --- a/uv.lock +++ b/uv.lock @@ -4442,6 +4442,9 @@ assemblyai = [ asyncai = [ { name = "websockets" }, ] +auth = [ + { name = "pyjwt" }, +] aws = [ { name = "aioboto3" }, { name = "websockets" }, @@ -4721,6 +4724,7 @@ requires-dist = [ { name = "pyaudio", marker = "extra == 'local'", specifier = "~=0.2.14" }, { name = "pydantic", specifier = ">=2.10.6,<3" }, { name = "pygobject", marker = "extra == 'gstreamer'", specifier = "~=3.50.0" }, + { name = "pyjwt", marker = "extra == 'auth'", specifier = ">=2.10.1" }, { name = "pyloudnorm", specifier = "~=0.1.1" }, { name = "python-dotenv", marker = "extra == 'runner'", specifier = ">=1.0.0,<2.0.0" }, { name = "pyvips", extras = ["binary"], marker = "extra == 'moondream'", specifier = "~=3.0.0" }, @@ -4745,7 +4749,7 @@ requires-dist = [ { name = "wait-for2", marker = "python_full_version < '3.12'", specifier = ">=0.4.1" }, { name = "websockets", marker = "extra == 'websockets-base'", specifier = ">=13.1,<16.0" }, ] -provides-extras = ["aic", "anthropic", "assemblyai", "asyncai", "aws", "aws-nova-sonic", "azure", "cartesia", "cerebras", "deepseek", "daily", "deepgram", "elevenlabs", "fal", "fireworks", "fish", "gladia", "google", "grok", "groq", "gstreamer", "heygen", "hume", "inworld", "krisp", "koala", "langchain", "livekit", "lmnt", "local", "mcp", "mem0", "mistral", "mlx-whisper", "moondream", "nim", "neuphonic", "noisereduce", "openai", "openpipe", "openrouter", "perplexity", "playht", "qwen", "rime", "riva", "runner", "sambanova", "sarvam", "sentry", "local-smart-turn", "local-smart-turn-v3", "remote-smart-turn", "silero", "simli", "soniox", "soundfile", "speechmatics", "strands", "tavus", "together", "tracing", "ultravox", "webrtc", "websocket", "websockets-base", "whisper"] +provides-extras = ["aic", "anthropic", "assemblyai", "asyncai", "aws", "aws-nova-sonic", "azure", "cartesia", "cerebras", "deepseek", "daily", "deepgram", "elevenlabs", "fal", "fireworks", "fish", "gladia", "google", "grok", "groq", "gstreamer", "heygen", "hume", "inworld", "krisp", "koala", "langchain", "livekit", "lmnt", "local", "mcp", "mem0", "mistral", "mlx-whisper", "moondream", "nim", "neuphonic", "noisereduce", "openai", "openpipe", "openrouter", "perplexity", "playht", "qwen", "rime", "riva", "runner", "sambanova", "sarvam", "sentry", "local-smart-turn", "local-smart-turn-v3", "remote-smart-turn", "silero", "simli", "soniox", "soundfile", "speechmatics", "strands", "tavus", "together", "tracing", "ultravox", "webrtc", "websocket", "websockets-base", "whisper", "auth"] [package.metadata.requires-dev] dev = [