diff --git a/src/pipecat/runner/__init__.py b/src/pipecat/runner/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/pipecat/runner/daily_runner.py b/src/pipecat/runner/daily_runner.py
new file mode 100644
index 000000000..d8d6acd81
--- /dev/null
+++ b/src/pipecat/runner/daily_runner.py
@@ -0,0 +1,89 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+"""Daily.co room configuration utilities for Pipecat examples."""
+
+import argparse
+import os
+from typing import Optional
+
+import aiohttp
+
+from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper
+
+
+async def configure(aiohttp_session: aiohttp.ClientSession):
+ """Configure Daily.co room URL and token from arguments or environment.
+
+ Args:
+ aiohttp_session: HTTP session for making API requests.
+
+ Returns:
+ Tuple containing the room URL and authentication token.
+
+ Raises:
+ Exception: If room URL or API key are not provided.
+ """
+ (url, token, _) = await configure_with_args(aiohttp_session)
+ return (url, token)
+
+
+async def configure_with_args(
+ aiohttp_session: aiohttp.ClientSession, parser: Optional[argparse.ArgumentParser] = None
+):
+ """Configure Daily.co room with command-line argument parsing.
+
+ Args:
+ aiohttp_session: HTTP session for making API requests.
+ parser: Optional argument parser. If None, creates a default one.
+
+ Returns:
+ Tuple containing room URL, authentication token, and parsed arguments.
+
+ Raises:
+ Exception: If room URL or API key are not provided via arguments or environment.
+ """
+ if not parser:
+ parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
+ parser.add_argument(
+ "-u", "--url", type=str, required=False, help="URL of the Daily room to join"
+ )
+ parser.add_argument(
+ "-k",
+ "--apikey",
+ type=str,
+ required=False,
+ help="Daily API Key (needed to create an owner token for the room)",
+ )
+
+ args, unknown = parser.parse_known_args()
+
+ url = args.url or os.getenv("DAILY_SAMPLE_ROOM_URL")
+ key = args.apikey or os.getenv("DAILY_API_KEY")
+
+ if not url:
+ raise Exception(
+ "No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL."
+ )
+
+ if not key:
+ raise Exception(
+ "No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers."
+ )
+
+ daily_rest_helper = DailyRESTHelper(
+ daily_api_key=key,
+ daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
+ aiohttp_session=aiohttp_session,
+ )
+
+ # Create a meeting token for the given room with an expiration 1 hour in
+ # the future.
+ expiry_time: float = 60 * 60
+
+ token = await daily_rest_helper.get_token(url, expiry_time)
+
+ return (url, token, args)
diff --git a/src/pipecat/runner/livekit_runner.py b/src/pipecat/runner/livekit_runner.py
new file mode 100644
index 000000000..344f65d52
--- /dev/null
+++ b/src/pipecat/runner/livekit_runner.py
@@ -0,0 +1,126 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+"""LiveKit room configuration utilities for Pipecat applications."""
+
+import argparse
+import os
+from typing import Optional
+
+from livekit import api
+from loguru import logger
+
+
+def generate_token(room_name: str, participant_name: str, api_key: str, api_secret: str) -> str:
+ """Generate a LiveKit access token for a participant.
+
+ Args:
+ room_name: Name of the LiveKit room.
+ participant_name: Name of the participant.
+ api_key: LiveKit API key.
+ api_secret: LiveKit API secret.
+
+ Returns:
+ JWT token string for room access.
+ """
+ token = api.AccessToken(api_key, api_secret)
+ token.with_identity(participant_name).with_name(participant_name).with_grants(
+ api.VideoGrants(
+ room_join=True,
+ room=room_name,
+ )
+ )
+
+ return token.to_jwt()
+
+
+def generate_token_with_agent(
+ room_name: str, participant_name: str, api_key: str, api_secret: str
+) -> str:
+ """Generate a LiveKit access token for an agent participant.
+
+ Args:
+ room_name: Name of the LiveKit room.
+ participant_name: Name of the participant.
+ api_key: LiveKit API key.
+ api_secret: LiveKit API secret.
+
+ Returns:
+ JWT token string for agent room access.
+ """
+ token = api.AccessToken(api_key, api_secret)
+ token.with_identity(participant_name).with_name(participant_name).with_grants(
+ api.VideoGrants(
+ room_join=True,
+ room=room_name,
+ agent=True, # This makes LiveKit client know agent has joined
+ )
+ )
+
+ return token.to_jwt()
+
+
+async def configure():
+ """Configure LiveKit room URL and token from arguments or environment.
+
+ Returns:
+ Tuple containing the server URL, authentication token, and room name.
+
+ Raises:
+ Exception: If required LiveKit configuration is not provided.
+ """
+ (url, token, room_name, _) = await configure_with_args()
+ return (url, token, room_name)
+
+
+async def configure_with_args(parser: Optional[argparse.ArgumentParser] = None):
+ """Configure LiveKit room with command-line argument parsing.
+
+ Args:
+ parser: Optional argument parser. If None, creates a default one.
+
+ Returns:
+ Tuple containing server URL, authentication token, room name, and parsed arguments.
+
+ Raises:
+ Exception: If required LiveKit configuration is not provided via arguments or environment.
+ """
+ if not parser:
+ parser = argparse.ArgumentParser(description="LiveKit AI SDK Bot Sample")
+ parser.add_argument(
+ "-r", "--room", type=str, required=False, help="Name of the LiveKit room to join"
+ )
+ parser.add_argument("-u", "--url", type=str, required=False, help="URL of the LiveKit server")
+
+ args, unknown = parser.parse_known_args()
+
+ room_name = args.room or os.getenv("LIVEKIT_ROOM_NAME")
+ url = args.url or os.getenv("LIVEKIT_URL")
+ api_key = os.getenv("LIVEKIT_API_KEY")
+ api_secret = os.getenv("LIVEKIT_API_SECRET")
+
+ if not room_name:
+ raise Exception(
+ "No LiveKit room specified. Use the -r/--room option from the command line, or set LIVEKIT_ROOM_NAME in your environment."
+ )
+
+ if not url:
+ raise Exception(
+ "No LiveKit server URL specified. Use the -u/--url option from the command line, or set LIVEKIT_URL in your environment."
+ )
+
+ if not api_key or not api_secret:
+ raise Exception(
+ "LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set in environment variables."
+ )
+
+ token = generate_token_with_agent(room_name, "Pipecat Agent", api_key, api_secret)
+
+ # Generate user token for testing/debugging
+ user_token = generate_token(room_name, "User", api_key, api_secret)
+ logger.info(f"User token: {user_token}")
+
+ return (url, token, room_name, args)
diff --git a/src/pipecat/runner/run.py b/src/pipecat/runner/run.py
new file mode 100644
index 000000000..c82211d57
--- /dev/null
+++ b/src/pipecat/runner/run.py
@@ -0,0 +1,649 @@
+#
+# Copyright (c) 2024–2025, Daily
+#
+# SPDX-License-Identifier: BSD 2-Clause License
+#
+
+"""Pipecat runner with support for multiple transport types.
+
+This module provides a unified interface for running Pipecat applications
+across different transport types including Daily.co, WebRTC, and Twilio. It
+handles setup, configuration, and lifecycle management for each transport type.
+
+Example usage:
+ DailyTransport::
+
+ python bot.py --transport daily
+
+ LiveKitTransport::
+
+ python bot.py --transport livekit
+
+ Plivo::
+
+ python bot.py --transport plivo --proxy username.ngrok.io
+ # Note: Concurrently, run an ngrok tunnel to your local server:
+ # ngrok http 7860
+
+ SmallWebRTCTransport::
+
+ python bot.py --transport webrtc
+
+ Telnyx::
+
+ python bot.py --transport telnyx --proxy username.ngrok.io
+ # Note: Concurrently, run an ngrok tunnel to your local server:
+ # ngrok http 7860
+
+ Twilio::
+
+ python bot.py --transport twilio --proxy username.ngrok.io
+ # Note: Concurrently, run an ngrok tunnel to your local server:
+ # ngrok http 7860
+"""
+
+import argparse
+import asyncio
+import json
+import os
+import re
+import sys
+from contextlib import asynccontextmanager
+from typing import Any, Callable, Dict, Mapping, Optional
+
+import aiohttp
+import uvicorn
+from dotenv import load_dotenv
+from fastapi import BackgroundTasks, FastAPI, WebSocket
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import HTMLResponse, RedirectResponse
+from loguru import logger
+
+from pipecat.serializers.plivo import PlivoFrameSerializer
+from pipecat.serializers.telnyx import TelnyxFrameSerializer
+from pipecat.serializers.twilio import TwilioFrameSerializer
+from pipecat.transports.base_transport import BaseTransport, TransportParams
+from pipecat.transports.network.fastapi_websocket import (
+ FastAPIWebsocketParams,
+ FastAPIWebsocketTransport,
+)
+from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
+from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
+from pipecat.transports.services.daily import DailyParams, DailyTransport
+from pipecat.transports.services.livekit import LiveKitParams, LiveKitTransport
+
+# Load environment variables
+load_dotenv(override=True)
+
+
+def get_transport_client_id(transport: BaseTransport, client: Any) -> str:
+ """Get client identifier from transport-specific client object.
+
+ Args:
+ transport: The transport instance.
+ client: Transport-specific client object.
+
+ Returns:
+ Client identifier string, empty if transport not supported.
+ """
+ if isinstance(transport, SmallWebRTCTransport):
+ return client.pc_id
+ elif isinstance(transport, DailyTransport):
+ return client["id"]
+ logger.warning(f"Unable to get client id from unsupported transport {type(transport)}")
+ return ""
+
+
+async def maybe_capture_participant_camera(
+ transport: BaseTransport, client: Any, framerate: int = 0
+):
+ """Capture participant camera video if transport supports it.
+
+ Args:
+ transport: The transport instance.
+ client: Transport-specific client object.
+ framerate: Video capture framerate. Defaults to 0 (auto).
+ """
+ if isinstance(transport, DailyTransport):
+ await transport.capture_participant_video(
+ client["id"], framerate=framerate, video_source="camera"
+ )
+
+
+async def maybe_capture_participant_screen(
+ transport: BaseTransport, client: Any, framerate: int = 0
+):
+ """Capture participant screen video if transport supports it.
+
+ Args:
+ transport: The transport instance.
+ client: Transport-specific client object.
+ framerate: Video capture framerate. Defaults to 0 (auto).
+ """
+ if isinstance(transport, DailyTransport):
+ await transport.capture_participant_video(
+ client["id"], framerate=framerate, video_source="screenVideo"
+ )
+
+
+def smallwebrtc_sdp_cleanup_ice_candidates(text: str, pattern: str) -> str:
+ """Clean up ICE candidates in SDP text for SmallWebRTC.
+
+ Args:
+ text: SDP text to clean up.
+ pattern: Pattern to match for candidate filtering.
+
+ Returns:
+ Cleaned SDP text with filtered ICE candidates.
+ """
+ result = []
+ lines = text.splitlines()
+ for line in lines:
+ if re.search("a=candidate", line):
+ if re.search(pattern, line) and not re.search("raddr", line):
+ result.append(line)
+ else:
+ result.append(line)
+ return "\r\n".join(result)
+
+
+def smallwebrtc_sdp_cleanup_fingerprints(text: str) -> str:
+ """Remove unsupported fingerprint algorithms from SDP text.
+
+ Args:
+ text: SDP text to clean up.
+
+ Returns:
+ SDP text with sha-384 and sha-512 fingerprints removed.
+ """
+ result = []
+ lines = text.splitlines()
+ for line in lines:
+ if not re.search("sha-384", line) and not re.search("sha-512", line):
+ result.append(line)
+ return "\r\n".join(result)
+
+
+def smallwebrtc_sdp_munging(sdp: str, host: str) -> str:
+ """Apply SDP modifications for SmallWebRTC compatibility.
+
+ Args:
+ sdp: Original SDP string.
+ host: Host address for ICE candidate filtering.
+
+ Returns:
+ Modified SDP string with fingerprint and ICE candidate cleanup.
+ """
+ sdp = smallwebrtc_sdp_cleanup_fingerprints(sdp)
+ sdp = smallwebrtc_sdp_cleanup_ice_candidates(sdp, host)
+ return sdp
+
+
+def run_daily(
+ run: Callable,
+ args: argparse.Namespace,
+ transport_params: Mapping[str, Callable] = {},
+):
+ """Run using Daily.co transport.
+
+ Args:
+ run: The function to run.
+ args: Parsed command-line arguments.
+ transport_params: Mapping of transport names to parameter factory functions.
+ """
+ logger.info("Running with DailyTransport...")
+
+ from pipecat.runner.daily_runner import configure
+
+ async def run_daily_impl():
+ async with aiohttp.ClientSession() as session:
+ (room_url, token) = await configure(session)
+
+ # Run function with DailyTransport transport arguments.
+ params: DailyParams = transport_params[args.transport]()
+ transport = DailyTransport(room_url, token, "Pipecat", params=params)
+ await run(transport, args, True)
+
+ asyncio.run(run_daily_impl())
+
+
+def run_livekit(
+ run: Callable,
+ args: argparse.Namespace,
+ transport_params: Mapping[str, Callable] = {},
+):
+ """Run using LiveKit transport.
+
+ Args:
+ run: The function to run.
+ args: Parsed command-line arguments.
+ transport_params: Mapping of transport names to parameter factory functions.
+ """
+ logger.info("Running with LiveKitTransport...")
+
+ from pipecat.runner.livekit_runner import configure
+
+ async def run_livekit_impl():
+ (url, token, room_name) = await configure()
+
+ # Run function with LiveKit transport arguments.
+ params: LiveKitParams = transport_params[args.transport]()
+ transport = LiveKitTransport(url=url, token=token, room_name=room_name, params=params)
+ await run(transport, args, True)
+
+ asyncio.run(run_livekit_impl())
+
+
+def run_webrtc(
+ run: Callable,
+ args: argparse.Namespace,
+ transport_params: Mapping[str, Callable] = {},
+):
+ """Run using WebRTC transport with FastAPI server.
+
+ Args:
+ run: The function to run.
+ args: Parsed command-line arguments.
+ transport_params: Mapping of transport names to parameter factory functions.
+ """
+ logger.info("Running with SmallWebRTCTransport...")
+
+ from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI
+
+ app = FastAPI()
+
+ # Store connections by pc_id
+ pcs_map: Dict[str, SmallWebRTCConnection] = {}
+
+ # Mount the frontend at /
+ app.mount("/client", SmallWebRTCPrebuiltUI)
+
+ @app.get("/", include_in_schema=False)
+ async def root_redirect():
+ """Redirect root requests to client interface."""
+ return RedirectResponse(url="/client/")
+
+ @app.post("/api/offer")
+ async def offer(request: dict, background_tasks: BackgroundTasks):
+ """Handle WebRTC offer requests and manage peer connections.
+
+ Args:
+ request: WebRTC offer request containing SDP and connection details.
+ background_tasks: FastAPI background tasks for running applications.
+
+ Returns:
+ WebRTC answer with connection details.
+ """
+ pc_id = request.get("pc_id")
+
+ if pc_id and pc_id in pcs_map:
+ pipecat_connection = pcs_map[pc_id]
+ logger.info(f"Reusing existing connection for pc_id: {pc_id}")
+ await pipecat_connection.renegotiate(
+ sdp=request["sdp"],
+ type=request["type"],
+ restart_pc=request.get("restart_pc", False),
+ )
+ else:
+ pipecat_connection = SmallWebRTCConnection()
+ await pipecat_connection.initialize(sdp=request["sdp"], type=request["type"])
+
+ @pipecat_connection.event_handler("closed")
+ async def handle_disconnected(webrtc_connection: SmallWebRTCConnection):
+ """Handle WebRTC connection closure and cleanup.
+
+ Args:
+ webrtc_connection: The closed WebRTC connection.
+ """
+ logger.info(f"Discarding peer connection for pc_id: {webrtc_connection.pc_id}")
+ pcs_map.pop(webrtc_connection.pc_id, None)
+
+ # Run function with SmallWebRTC transport arguments.
+ params: TransportParams = transport_params[args.transport]()
+ transport = SmallWebRTCTransport(params=params, webrtc_connection=pipecat_connection)
+ background_tasks.add_task(run, transport, args, False)
+
+ answer = pipecat_connection.get_answer()
+
+ if args.esp32 and args.host:
+ answer["sdp"] = smallwebrtc_sdp_munging(answer["sdp"], args.host)
+
+ # Updating the peer connection inside the map
+ pcs_map[answer["pc_id"]] = pipecat_connection
+
+ return answer
+
+ @asynccontextmanager
+ async def lifespan(app: FastAPI):
+ """Manage FastAPI application lifecycle and cleanup connections.
+
+ Args:
+ app: The FastAPI application instance.
+
+ Yields:
+ Control to the FastAPI application runtime.
+ """
+ yield # Run app
+ coros = [pc.disconnect() for pc in pcs_map.values()]
+ await asyncio.gather(*coros)
+ pcs_map.clear()
+
+ uvicorn.run(app, host=args.host, port=args.port)
+
+
+def run_twilio(
+ run: Callable,
+ args: argparse.Namespace,
+ transport_params: Mapping[str, Callable] = {},
+):
+ """Run using Twilio transport with FastAPI WebSocket server.
+
+ Args:
+ run: The function to run.
+ args: Parsed command-line arguments.
+ transport_params: Mapping of transport names to parameter factory functions.
+ """
+ logger.info("Running with FastAPIWebsocketTransport (Twilio)...")
+
+ app = FastAPI()
+
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # Allow all origins for testing
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ )
+
+ @app.post("/")
+ async def start_call():
+ """Handle Twilio webhook and return TwiML response.
+
+ Returns:
+ TwiML XML response directing call to WebSocket stream.
+ """
+ logger.debug("POST TwiML")
+
+ xml_content = f"""
+
+
+
+
+
+
+ """
+ return HTMLResponse(content=xml_content, media_type="application/xml")
+
+ @app.websocket("/ws")
+ async def websocket_endpoint(websocket: WebSocket):
+ """Handle Twilio WebSocket connections for voice streaming.
+
+ Args:
+ websocket: The WebSocket connection from Twilio.
+ """
+ await websocket.accept()
+
+ logger.debug("WebSocket connection accepted")
+
+ # Reading Twilio data.
+ start_data = websocket.iter_text()
+ await start_data.__anext__()
+ call_data = json.loads(await start_data.__anext__())
+ print(call_data, flush=True)
+ stream_sid = call_data["start"]["streamSid"]
+ call_sid = call_data["start"]["callSid"]
+
+ # Create websocket transport and update params.
+ params: FastAPIWebsocketParams = transport_params[args.transport]()
+ params.add_wav_header = False
+ params.serializer = TwilioFrameSerializer(
+ stream_sid=stream_sid,
+ call_sid=call_sid,
+ account_sid=os.getenv("TWILIO_ACCOUNT_SID", ""),
+ auth_token=os.getenv("TWILIO_AUTH_TOKEN", ""),
+ )
+ transport = FastAPIWebsocketTransport(websocket=websocket, params=params)
+ await run(transport, args, False)
+
+ uvicorn.run(app, host=args.host, port=args.port)
+
+
+def run_telnyx(
+ run: Callable,
+ args: argparse.Namespace,
+ transport_params: Mapping[str, Callable] = {},
+):
+ """Run using Telnyx transport with FastAPI WebSocket server.
+
+ Args:
+ run: The function to run.
+ args: Parsed command-line arguments.
+ transport_params: Mapping of transport names to parameter factory functions.
+ """
+ logger.info("Running with FastAPIWebsocketTransport (Telnyx)...")
+
+ app = FastAPI()
+
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # Allow all origins for testing
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ )
+
+ @app.post("/")
+ async def start_call():
+ """Handle Telnyx webhook and return TeXML response.
+
+ Returns:
+ TeXML XML response directing call to WebSocket stream.
+ """
+ logger.debug("POST TeXML")
+
+ xml_content = f"""
+
+
+
+
+
+
+ """
+ return HTMLResponse(content=xml_content, media_type="application/xml")
+
+ @app.websocket("/ws")
+ async def websocket_endpoint(websocket: WebSocket):
+ """Handle Telnyx WebSocket connections for voice streaming.
+
+ Args:
+ websocket: The WebSocket connection from Telnyx.
+ """
+ await websocket.accept()
+
+ logger.debug("WebSocket connection accepted")
+
+ # Reading Telnyx data.
+ start_data = websocket.iter_text()
+ await start_data.__anext__()
+ call_data = json.loads(await start_data.__anext__())
+ print(call_data, flush=True)
+ stream_id = call_data["stream_id"]
+ call_control_id = call_data["start"]["call_control_id"]
+ outbound_encoding = call_data["start"]["media_format"]["encoding"]
+
+ # Create websocket transport and update params.
+ params: FastAPIWebsocketParams = transport_params[args.transport]()
+ params.add_wav_header = False
+ params.serializer = TelnyxFrameSerializer(
+ stream_id=stream_id,
+ call_control_id=call_control_id,
+ outbound_encoding=outbound_encoding,
+ inbound_encoding="PCMU", # You might want to make this configurable
+ )
+ transport = FastAPIWebsocketTransport(websocket=websocket, params=params)
+ await run(transport, args, False)
+
+ uvicorn.run(app, host=args.host, port=args.port)
+
+
+def run_plivo(
+ run: Callable,
+ args: argparse.Namespace,
+ transport_params: Mapping[str, Callable] = {},
+):
+ """Run using Plivo transport with FastAPI WebSocket server.
+
+ Args:
+ run: The function to run.
+ args: Parsed command-line arguments.
+ transport_params: Mapping of transport names to parameter factory functions.
+ """
+ logger.info("Running with FastAPIWebsocketTransport (Plivo)...")
+
+ app = FastAPI()
+
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # Allow all origins for testing
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ )
+
+ @app.post("/")
+ async def start_call():
+ """Handle Plivo webhook and return Plivo XML response.
+
+ Returns:
+ Plivo XML response directing call to WebSocket stream.
+ """
+ logger.debug("POST Plivo XML")
+
+ xml_content = f"""
+
+ wss://{args.proxy}/ws
+
+ """
+ return HTMLResponse(content=xml_content, media_type="application/xml")
+
+ @app.websocket("/ws")
+ async def websocket_endpoint(websocket: WebSocket):
+ """Handle Plivo WebSocket connections for voice streaming.
+
+ Args:
+ websocket: The WebSocket connection from Plivo.
+ """
+ await websocket.accept()
+
+ logger.debug("WebSocket connection accepted")
+
+ # Reading Plivo data.
+ start_data = websocket.iter_text()
+ start_message = json.loads(await start_data.__anext__())
+
+ logger.debug(f"Received start message: {start_message}")
+
+ # Extract stream_id and call_id from the start event
+ start_info = start_message.get("start", {})
+ stream_id = start_info.get("streamId")
+ call_id = start_info.get("callId")
+
+ if not stream_id:
+ logger.error("No streamId found in start message")
+ await websocket.close()
+ return
+
+ logger.info(f"WebSocket connection accepted for stream: {stream_id}, call: {call_id}")
+
+ # Create websocket transport and update params.
+ params: FastAPIWebsocketParams = transport_params[args.transport]()
+ params.add_wav_header = False
+ params.serializer = PlivoFrameSerializer(
+ stream_id=stream_id,
+ call_id=call_id,
+ )
+ transport = FastAPIWebsocketTransport(websocket=websocket, params=params)
+ await run(transport, args, False)
+
+ uvicorn.run(app, host=args.host, port=args.port)
+
+
+def run_main(
+ run: Callable,
+ args: argparse.Namespace,
+ transport_params: Mapping[str, Callable] = {},
+):
+ """Run the application with the specified transport type.
+
+ Args:
+ run: The function to run.
+ args: Parsed command-line arguments.
+ transport_params: Mapping of transport names to parameter factory functions.
+ """
+ if args.transport not in transport_params:
+ logger.error(f"Transport '{args.transport}' not supported by this application.")
+ return
+
+ match args.transport:
+ case "daily":
+ run_daily(run, args, transport_params)
+ case "livekit":
+ run_livekit(run, args, transport_params)
+ case "plivo":
+ run_plivo(run, args, transport_params)
+ case "telnyx":
+ run_telnyx(run, args, transport_params)
+ case "twilio":
+ run_twilio(run, args, transport_params)
+ case "webrtc":
+ run_webrtc(run, args, transport_params)
+
+
+def main(
+ run: Callable,
+ *,
+ parser: Optional[argparse.ArgumentParser] = None,
+ transport_params: Mapping[str, Callable] = {},
+):
+ """Main entry point for running Pipecat applications with transport selection.
+
+ Args:
+ run: The function to run.
+ parser: Optional argument parser. If None, creates a default one.
+ transport_params: Mapping of transport names to parameter factory functions.
+ """
+ if not parser:
+ parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
+ parser.add_argument(
+ "--host", default="localhost", help="Host for HTTP server (default: localhost)"
+ )
+ parser.add_argument(
+ "--port", type=int, default=7860, help="Port for HTTP server (default: 7860)"
+ )
+ parser.add_argument(
+ "--transport",
+ "-t",
+ type=str,
+ choices=["daily", "livekit", "plivo", "telnyx", "twilio", "webrtc"],
+ default="webrtc",
+ help="The transport this application should use",
+ )
+ parser.add_argument(
+ "--proxy", "-x", help="A public proxy host name (no protocol, e.g. proxy.example.com)"
+ )
+ parser.add_argument(
+ "--esp32", action="store_true", default=False, help="Perform SDP munging for the ESP32"
+ )
+ parser.add_argument("--verbose", "-v", action="count", default=0)
+ args = parser.parse_args()
+
+ if args.esp32 and args.host == "localhost":
+ logger.error("For ESP32, you need to specify `--host IP` so we can do SDP munging.")
+ return
+
+ # Log level
+ logger.remove(0)
+ logger.add(sys.stderr, level="TRACE" if args.verbose else "DEBUG")
+
+ # Import the bot file
+ run_main(run, args, transport_params)