diff --git a/changelog/4442.added.2.md b/changelog/4442.added.2.md new file mode 100644 index 000000000..1c2452667 --- /dev/null +++ b/changelog/4442.added.2.md @@ -0,0 +1 @@ +- Added `GET /status` endpoint to the development runner that reports which transports the running instance accepts (all by default, or the single transport passed via `-t`). diff --git a/changelog/4442.added.md b/changelog/4442.added.md new file mode 100644 index 000000000..2360e4e78 --- /dev/null +++ b/changelog/4442.added.md @@ -0,0 +1 @@ +- Added plain WebSocket transport support to the development runner. Bots can now accept connections from non-telephony WebSocket clients (e.g., browser apps using protobuf framing) via the `/ws-client` endpoint alongside other transports. diff --git a/changelog/4442.changed.md b/changelog/4442.changed.md new file mode 100644 index 000000000..0299bc831 --- /dev/null +++ b/changelog/4442.changed.md @@ -0,0 +1 @@ +- ⚠️ The development runner now supports all transports (WebRTC, Daily, telephony, plain WebSocket) simultaneously from a single server. The `/start` endpoint accepts a `"transport"` field to select the transport per-request; omitting `-t` at startup enables all transports instead of defaulting to WebRTC. The Daily browser-redirect route moved from `GET /` to `GET /daily`. diff --git a/pyproject.toml b/pyproject.toml index 2f3de7ea9..7206dae00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,7 +103,7 @@ piper = [ "piper-tts>=1.3.0,<2", "requests>=2.32.5,<3" ] qwen = [] resembleai = [ "pipecat-ai[websockets-base]" ] rime = [ "pipecat-ai[websockets-base]" ] -runner = [ "python-dotenv>=1.0.0,<2.0.0", "uvicorn>=0.32.0,<1.0.0", "fastapi>=0.115.6,<1", "pipecat-ai-small-webrtc-prebuilt>=2.5.0"] +runner = [ "python-dotenv>=1.0.0,<2.0.0", "uvicorn>=0.32.0,<1.0.0", "fastapi>=0.115.6,<1", "pipecat-ai-prebuilt>=1.0.0"] sagemaker = ["aws_sdk_sagemaker_runtime_http2; python_version>='3.12'"] sambanova = [] sarvam = [ "sarvamai==0.1.28", "pipecat-ai[websockets-base]" ] diff --git a/src/pipecat/runner/run.py b/src/pipecat/runner/run.py index f3965b79c..62f3b2d51 100644 --- a/src/pipecat/runner/run.py +++ b/src/pipecat/runner/run.py @@ -19,6 +19,10 @@ All bots must implement a `bot(runner_args)` async function as the entry point. The server automatically discovers and executes this function when connections are established. +By default the runner starts a single FastAPI server that supports WebRTC, Daily, +and telephony transports simultaneously. Clients declare which transport they want +via the ``transport`` field in the ``/start`` request body (default: ``"webrtc"``). + Single transport example:: async def bot(runner_args: RunnerArguments): @@ -55,14 +59,33 @@ Supported transports: - WebRTC - Provides local WebRTC interface with prebuilt UI - Telephony - Handles webhook and WebSocket connections for Twilio, Telnyx, Plivo, Exotel +The ``/start`` endpoint accepts:: + + { + "transport": "webrtc", // "webrtc" | "daily" | "twilio" | "telnyx" | + // "plivo" | "exotel" — default: "webrtc" + + // WebRTC-specific + "enableDefaultIceServers": false, + "body": {...}, + + // Daily-specific + "createDailyRoom": true, + "dailyRoomProperties": {...}, + "dailyMeetingTokenProperties": {...}, + "body": {...} + } + To run locally: -- WebRTC: `python bot.py -t webrtc` -- ESP32: `python bot.py -t webrtc --esp32 --host 192.168.1.100` -- Daily (server): `python bot.py -t daily` -- Daily (direct, testing only): `python bot.py -d` -- Telephony: `python bot.py -t twilio -x your_username.ngrok.io` -- Exotel: `python bot.py -t exotel` (no proxy needed, but ngrok connection to HTTP 7860 is required) +- All transports (default): ``python bot.py`` +- WebRTC only: ``python bot.py -t webrtc`` +- ESP32: ``python bot.py -t webrtc --esp32 --host 192.168.1.100`` +- Daily only: ``python bot.py -t daily`` +- Daily (direct, testing only): ``python bot.py -d`` +- Telephony: ``python bot.py -t twilio -x your_username.ngrok.io`` +- Exotel: ``python bot.py -t exotel`` (no proxy needed, but ngrok connection to HTTP 7860 is required) +- WhatsApp: ``python bot.py --whatsapp`` """ import argparse @@ -186,8 +209,33 @@ async def _run_telephony_bot(websocket: WebSocket, args: argparse.Namespace): await bot_module.bot(runner_args) +async def _run_websocket_bot(websocket: WebSocket, args: argparse.Namespace): + """Run a bot for plain WebSocket transport.""" + bot_module = _get_bot_module() + + runner_args = WebSocketRunnerArguments( + websocket=websocket, + transport_type="websocket", + session_id=str(uuid.uuid4()), + ) + runner_args.cli_args = args + + await bot_module.bot(runner_args) + + +def _setup_websocket_routes(app: FastAPI, args: argparse.Namespace): + """Set up the plain WebSocket route at ``/ws-client``.""" + + @app.websocket("/ws-client") + async def websocket_client_endpoint(websocket: WebSocket): + """Handle plain WebSocket connections (non-telephony).""" + await websocket.accept() + logger.debug("Plain WebSocket connection accepted") + await _run_websocket_bot(websocket, args) + + def _configure_server_app(args: argparse.Namespace): - """Configure the module-level FastAPI app with transport-specific routes.""" + """Configure the module-level FastAPI app with routes for all transports.""" app.add_middleware( CORSMiddleware, allow_origins=["*"], @@ -196,17 +244,198 @@ def _configure_server_app(args: argparse.Namespace): allow_headers=["*"], ) - # Set up transport-specific routes - if args.transport == "webrtc": - _setup_webrtc_routes(app, args) - if args.whatsapp: - _setup_whatsapp_routes(app, args) - elif args.transport == "daily": - _setup_daily_routes(app, args) - elif args.transport in TELEPHONY_TRANSPORTS: - _setup_telephony_routes(app, args) - else: - logger.warning(f"Unknown transport type: {args.transport}") + # Shared session store: session_id -> body data. Used by the WebRTC /start + # flow and the /sessions/{session_id}/... proxy routes. + active_sessions: dict[str, dict[str, Any]] = {} + + _setup_frontend_routes(app) + _setup_webrtc_routes(app, args, active_sessions) + _setup_daily_routes(app, args) + _setup_telephony_routes(app, args) + _setup_websocket_routes(app, args) + _setup_unified_start_route(app, args, active_sessions) + + if args.whatsapp: + _setup_whatsapp_routes(app, args) + + +def _setup_unified_start_route( + app: FastAPI, args: argparse.Namespace, active_sessions: dict[str, dict[str, Any]] +): + """Register the unified POST /start and GET /status endpoints. + + Handles WebRTC, Daily, and telephony transport start flows. Clients specify + which transport they want via the ``transport`` field in the request body. + When ``-t`` was passed on the command line, requests for any other transport + are rejected with HTTP 400. + """ + ALL_TRANSPORTS = ["webrtc", "daily", *TELEPHONY_TRANSPORTS, "websocket"] + + @app.get("/status") + async def status(): + """Return the transports supported by this runner instance.""" + transports = [args.transport] if args.transport is not None else ALL_TRANSPORTS + return {"status": "ready", "transports": transports} + + class IceServer(TypedDict, total=False): + urls: str | list[str] + + class IceConfig(TypedDict): + iceServers: list[IceServer] + + class StartBotResult(TypedDict, total=False): + sessionId: str + iceConfig: IceConfig | None + dailyRoom: str | None + dailyToken: str | None + wsUrl: str | None + token: str | None + + @app.post("/start") + async def start_agent(request: Request): + """Start a bot session. + + Accepts:: + + { + "transport": "webrtc", // "webrtc" | "daily" | "twilio" | "telnyx" | + // "plivo" | "exotel" — default: "webrtc" + + // WebRTC-specific + "enableDefaultIceServers": false, + "body": {...}, + + // Daily-specific + "createDailyRoom": true, + "dailyRoomProperties": {...}, + "dailyMeetingTokenProperties": {...}, + "body": {...} + } + """ + try: + request_data = await request.json() + logger.debug(f"Received request: {request_data}") + except Exception as e: + logger.error(f"Failed to parse request body: {e}") + request_data = {} + + # Determine transport: explicit field → legacy Daily hint → CLI default → webrtc + transport = request_data.get("transport") + if transport is None and request_data.get("createDailyRoom", False): + transport = "daily" + if transport is None: + transport = args.transport or "webrtc" + + # Enforce restriction when -t was explicitly set on the command line + if args.transport is not None and transport != args.transport: + raise HTTPException( + status_code=400, + detail=( + f"Transport '{transport}' is not allowed. " + f"Server is configured for '{args.transport}' only (-t {args.transport})." + ), + ) + + if transport == "webrtc": + # WebRTC: register the session; the bot starts when the WebRTC offer arrives. + session_id = str(uuid.uuid4()) + active_sessions[session_id] = request_data.get("body", {}) + + result = StartBotResult( + sessionId=session_id, + ) + if request_data.get("enableDefaultIceServers"): + result["iceConfig"] = IceConfig( + iceServers=[IceServer(urls=["stun:stun.l.google.com:19302"])] + ) + return result + + elif transport == "daily": + create_daily_room = request_data.get("createDailyRoom", False) + body = request_data.get("body", {}) + daily_room_properties_dict = request_data.get("dailyRoomProperties", None) + daily_token_properties_dict = request_data.get("dailyMeetingTokenProperties", None) + + bot_module = _get_bot_module() + + existing_room_url = os.getenv("DAILY_ROOM_URL") + session_id = str(uuid.uuid4()) + result: StartBotResult | None = None + + if create_daily_room or existing_room_url: + from pipecat.runner.daily import configure + from pipecat.transports.daily.utils import ( + DailyMeetingTokenProperties, + DailyRoomProperties, + ) + + async with aiohttp.ClientSession() as session: + room_properties = None + if daily_room_properties_dict: + daily_room_properties_dict.setdefault( + "exp", time.time() + PIPECAT_ROOM_EXP_HOURS * 3600 + ) + daily_room_properties_dict.setdefault("eject_at_room_exp", True) + try: + room_properties = DailyRoomProperties(**daily_room_properties_dict) + logger.debug(f"Using custom room properties: {room_properties}") + except Exception as e: + logger.error(f"Failed to parse dailyRoomProperties: {e}") + + token_properties = None + if daily_token_properties_dict: + try: + token_properties = DailyMeetingTokenProperties( + **daily_token_properties_dict + ) + logger.debug(f"Using custom token properties: {token_properties}") + except Exception as e: + logger.error(f"Failed to parse dailyMeetingTokenProperties: {e}") + + room_url, token = await configure( + session, + room_exp_duration=PIPECAT_ROOM_EXP_HOURS, + room_properties=room_properties, + token_properties=token_properties, + ) + runner_args = DailyRunnerArguments( + room_url=room_url, token=token, body=body, session_id=session_id + ) + result = StartBotResult( + dailyRoom=room_url, + dailyToken=token, + sessionId=session_id, + ) + else: + runner_args = RunnerArguments(body=body, session_id=session_id) + + runner_args.cli_args = args + asyncio.create_task(bot_module.bot(runner_args)) + return result + + elif transport in TELEPHONY_TRANSPORTS: + # Telephony: the bot starts when the provider connects to /ws. + # Return the WebSocket URL so the caller knows where to point their provider. + scheme = "wss" if args.host != "localhost" else "ws" + return StartBotResult( + wsUrl=f"{scheme}://{args.host}:{args.port}/ws", + ) + + elif transport == "websocket": + # Plain WebSocket: the bot starts when the client connects to /ws-client. + scheme = "wss" if args.host != "localhost" else "ws" + session_id = str(uuid.uuid4()) + return StartBotResult( + wsUrl=f"{scheme}://{args.host}:{args.port}/ws-client", + sessionId=session_id, + token="mock_token", + ) + + else: + raise HTTPException( + status_code=400, + detail=f"Unknown transport '{transport}'.", + ) def _resolve_download_path(folder: str, filename: str) -> Path: @@ -220,11 +449,27 @@ def _resolve_download_path(folder: str, filename: str) -> Path: return file_path -def _setup_webrtc_routes(app: FastAPI, args: argparse.Namespace): +def _setup_frontend_routes(app: FastAPI): + """Mount the prebuilt frontend UI and root redirect for all transports.""" + try: + from pipecat_ai_prebuilt.frontend import PipecatPrebuiltUI + except ImportError as e: + logger.error(f"Prebuilt frontend not available: {e}") + return + + app.mount("/client", PipecatPrebuiltUI) + + @app.get("/", include_in_schema=False) + async def root_redirect(): + """Redirect root requests to client interface.""" + return RedirectResponse(url="/client/") + + +def _setup_webrtc_routes( + app: FastAPI, args: argparse.Namespace, active_sessions: dict[str, dict[str, Any]] +): """Set up WebRTC-specific routes.""" try: - from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI - from pipecat.transports.smallwebrtc.connection import SmallWebRTCConnection from pipecat.transports.smallwebrtc.request_handler import ( IceCandidate, @@ -236,27 +481,6 @@ def _setup_webrtc_routes(app: FastAPI, args: argparse.Namespace): logger.error(f"WebRTC transport dependencies not installed: {e}") return - class IceServer(TypedDict, total=False): - urls: str | list[str] - - class IceConfig(TypedDict): - iceServers: list[IceServer] - - class StartBotResult(TypedDict, total=False): - sessionId: str - iceConfig: IceConfig | None - - # In-memory store of active sessions: session_id -> session info - active_sessions: dict[str, dict[str, Any]] = {} - - # Mount the frontend - 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.get("/files/{filename:path}") async def download_file(filename: str): """Handle file downloads.""" @@ -315,29 +539,6 @@ def _setup_webrtc_routes(app: FastAPI, args: argparse.Namespace): await small_webrtc_handler.handle_patch_request(request) return {"status": "success"} - @app.post("/start") - async def rtvi_start(request: Request): - """Mimic Pipecat Cloud's /start endpoint.""" - # Parse the request body - try: - request_data = await request.json() - logger.debug(f"Received request: {request_data}") - except Exception as e: - logger.error(f"Failed to parse request body: {e}") - request_data = {} - - # Store session info immediately in memory, replicate the behavior expected on Pipecat Cloud - session_id = str(uuid.uuid4()) - active_sessions[session_id] = request_data.get("body", {}) - - result: StartBotResult = {"sessionId": session_id} - if request_data.get("enableDefaultIceServers"): - result["iceConfig"] = IceConfig( - iceServers=[IceServer(urls=["stun:stun.l.google.com:19302"])] - ) - - return result - @app.api_route( "/sessions/{session_id}/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"], @@ -563,12 +764,10 @@ def _setup_whatsapp_routes(app: FastAPI, args: argparse.Namespace): def _setup_daily_routes(app: FastAPI, args: argparse.Namespace): """Set up Daily-specific routes.""" - @app.get("/") + @app.get("/daily") async def create_room_and_start_agent(): """Launch a Daily bot and redirect to room.""" - print("Starting bot with Daily transport and redirecting to Daily room") - - import aiohttp + logger.debug("Starting bot with Daily transport and redirecting to Daily room") from pipecat.runner.daily import configure @@ -584,105 +783,6 @@ def _setup_daily_routes(app: FastAPI, args: argparse.Namespace): asyncio.create_task(bot_module.bot(runner_args)) return RedirectResponse(room_url) - @app.post("/start") - async def start_agent(request: Request): - """Handler for /start endpoints. - - Expects POST body like:: - { - "createDailyRoom": true, - "dailyRoomProperties": { "start_video_off": true }, - "dailyMeetingTokenProperties": { "is_owner": true, "user_name": "Bot" }, - "body": { "custom_data": "value" } - } - """ - print("Starting bot with Daily transport") - - # Parse the request body - try: - request_data = await request.json() - logger.debug(f"Received request: {request_data}") - except Exception as e: - logger.error(f"Failed to parse request body: {e}") - request_data = {} - - create_daily_room = request_data.get("createDailyRoom", False) - body = request_data.get("body", {}) - daily_room_properties_dict = request_data.get("dailyRoomProperties", None) - daily_token_properties_dict = request_data.get("dailyMeetingTokenProperties", None) - - bot_module = _get_bot_module() - - existing_room_url = os.getenv("DAILY_ROOM_URL") - - session_id = str(uuid.uuid4()) - result = None - - # Configure room if: - # 1. Explicitly requested via createDailyRoom in payload - # 2. Using pre-configured room from DAILY_ROOM_URL env var - if create_daily_room or existing_room_url: - import aiohttp - - from pipecat.runner.daily import configure - from pipecat.transports.daily.utils import ( - DailyMeetingTokenProperties, - DailyRoomProperties, - ) - - async with aiohttp.ClientSession() as session: - # Parse dailyRoomProperties if provided - room_properties = None - if daily_room_properties_dict: - # Apply Pipecat Cloud's session policy if caller didn't override. - daily_room_properties_dict.setdefault( - "exp", time.time() + PIPECAT_ROOM_EXP_HOURS * 3600 - ) - daily_room_properties_dict.setdefault("eject_at_room_exp", True) - try: - room_properties = DailyRoomProperties(**daily_room_properties_dict) - logger.debug(f"Using custom room properties: {room_properties}") - except Exception as e: - logger.error(f"Failed to parse dailyRoomProperties: {e}") - # Continue without custom properties - - # Parse dailyMeetingTokenProperties if provided - token_properties = None - if daily_token_properties_dict: - try: - token_properties = DailyMeetingTokenProperties( - **daily_token_properties_dict - ) - logger.debug(f"Using custom token properties: {token_properties}") - except Exception as e: - logger.error(f"Failed to parse dailyMeetingTokenProperties: {e}") - # Continue without custom properties - - room_url, token = await configure( - session, - room_exp_duration=PIPECAT_ROOM_EXP_HOURS, - room_properties=room_properties, - token_properties=token_properties, - ) - runner_args = DailyRunnerArguments( - room_url=room_url, token=token, body=body, session_id=session_id - ) - result = { - "dailyRoom": room_url, - "dailyToken": token, - "sessionId": session_id, - } - else: - runner_args = RunnerArguments(body=body, session_id=session_id) - - # Update CLI args. - runner_args.cli_args = args - - # Start the bot in the background - asyncio.create_task(bot_module.bot(runner_args)) - - return result - if args.dialin: @app.post("/daily-dialin-webhook") @@ -731,8 +831,6 @@ def _setup_daily_routes(app: FastAPI, args: argparse.Namespace): detail="Missing required fields: From, To, callId, callDomain", ) - import aiohttp - from pipecat.runner.daily import configure from pipecat.runner.types import DailyDialinRequest, DialinSettings @@ -801,44 +899,51 @@ def _setup_daily_routes(app: FastAPI, args: argparse.Namespace): def _setup_telephony_routes(app: FastAPI, args: argparse.Namespace): - """Set up telephony-specific routes.""" - # XML response templates (Exotel doesn't use XML webhooks) - XML_TEMPLATES = { - "twilio": f""" + """Set up telephony-specific routes. + + The WebSocket endpoint (``/ws``) is always registered so providers can + connect directly. The XML webhook (``POST /``) is only registered when a + specific telephony transport is chosen via ``-t`` because the XML template + is provider-specific and requires a proxy hostname (``--proxy``). + """ + if args.transport in TELEPHONY_TRANSPORTS: + # XML response templates (Exotel doesn't use XML webhooks) + XML_TEMPLATES = { + "twilio": f""" """, - "telnyx": f""" + "telnyx": f""" """, - "plivo": f""" + "plivo": f""" wss://{args.proxy}/ws """, - } + } - @app.post("/") - async def start_call(): - """Handle telephony webhook and return XML response.""" - if args.transport == "exotel": - # Exotel doesn't use POST webhooks - redirect to proper documentation - logger.debug("POST Exotel endpoint - not used") - return { - "error": "Exotel doesn't use POST webhooks", - "websocket_url": f"wss://{args.proxy}/ws", - "note": "Configure the WebSocket URL above in your Exotel App Bazaar Voicebot Applet", - } - else: - logger.debug(f"POST {args.transport.upper()} XML") - xml_content = XML_TEMPLATES.get(args.transport, "") - return HTMLResponse(content=xml_content, media_type="application/xml") + @app.post("/") + async def start_call(): + """Handle telephony webhook and return XML response.""" + if args.transport == "exotel": + # Exotel doesn't use POST webhooks - redirect to proper documentation + logger.debug("POST Exotel endpoint - not used") + return { + "error": "Exotel doesn't use POST webhooks", + "websocket_url": f"wss://{args.proxy}/ws", + "note": "Configure the WebSocket URL above in your Exotel App Bazaar Voicebot Applet", + } + else: + logger.debug(f"POST {args.transport.upper()} XML") + xml_content = XML_TEMPLATES.get(args.transport, "") + return HTMLResponse(content=xml_content, media_type="application/xml") @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): @@ -847,11 +952,6 @@ def _setup_telephony_routes(app: FastAPI, args: argparse.Namespace): logger.debug("WebSocket connection accepted") await _run_telephony_bot(websocket, args) - @app.get("/") - async def start_agent(): - """Simple status endpoint for telephony transports.""" - return {"status": f"Bot started with {args.transport}"} - async def _run_daily_direct(args: argparse.Namespace): """Run Daily bot with direct connection (no FastAPI server).""" @@ -922,22 +1022,27 @@ def runner_port() -> int: def main(parser: argparse.ArgumentParser | None = None): """Start the Pipecat development runner. - Parses command-line arguments and starts a FastAPI server configured - for the specified transport type. + Parses command-line arguments and starts a FastAPI server that supports + WebRTC, Daily, and telephony transports simultaneously. Clients declare + which transport to use via the ``transport`` field in the ``/start`` body. + + When ``-t`` is provided, the server restricts ``/start`` to that transport + only and displays transport-specific startup information. The runner discovers and runs any ``bot(runner_args)`` function found in the calling module. Command-line arguments: - - --host: Server host address (default: localhost) 879 + - --host: Server host address (default: localhost) - --port: Server port (default: 7860) - - -t/--transport: Transport type (daily, webrtc, twilio, telnyx, plivo, exotel) + - -t/--transport: Restrict to a single transport and set as default for /start + (daily, webrtc, twilio, telnyx, plivo, exotel). Omit to support all transports. - -x/--proxy: Public proxy hostname for telephony webhooks - -d/--direct: Connect directly to Daily room (automatically sets transport to daily) - -f/--folder: Path to downloads folder - - --dialin: Enable Daily PSTN dial-in webhook handling (requires Daily transport) + - --dialin: Enable Daily PSTN dial-in webhook handling - --esp32: Enable SDP munging for ESP32 compatibility (requires --host with IP address) - - --whatsapp: Ensure requried WhatsApp environment variables are present + - --whatsapp: Ensure required WhatsApp environment variables are present - -v/--verbose: Increase logging verbosity Args: @@ -958,8 +1063,11 @@ def main(parser: argparse.ArgumentParser | None = None): "--transport", type=str, choices=["daily", "webrtc", *TELEPHONY_TRANSPORTS], - default="webrtc", - help="Transport type", + default=None, + help=( + "Restrict the server to a single transport and set it as the default for /start. " + "Omit to support all transports simultaneously (default behaviour)." + ), ) parser.add_argument("-x", "--proxy", help="Public proxy host name") parser.add_argument( @@ -977,7 +1085,7 @@ def main(parser: argparse.ArgumentParser | None = None): "--dialin", action="store_true", default=False, - help="Enable Daily PSTN dial-in webhook handling (requires Daily transport)", + help="Enable Daily PSTN dial-in webhook handling", ) parser.add_argument( "--esp32", @@ -989,7 +1097,7 @@ def main(parser: argparse.ArgumentParser | None = None): "--whatsapp", action="store_true", default=False, - help="Ensure requried WhatsApp environment variables are present", + help="Ensure required WhatsApp environment variables are present", ) args = parser.parse_args() @@ -998,12 +1106,13 @@ def main(parser: argparse.ArgumentParser | None = None): if args.proxy: args.proxy = _validate_and_clean_proxy(args.proxy) - # Auto-set transport to daily if --direct is used without explicit transport - if args.direct and args.transport == "webrtc": # webrtc is the default - args.transport = "daily" - elif args.direct and args.transport != "daily": - logger.error("--direct flag only works with Daily transport (-t daily)") - return + # --direct implies Daily transport + if args.direct: + if args.transport is None or args.transport == "daily": + args.transport = "daily" + else: + logger.error("--direct flag only works with Daily transport (-t daily)") + return # Validate ESP32 requirements if args.esp32 and args.host == "localhost": @@ -1011,7 +1120,7 @@ def main(parser: argparse.ArgumentParser | None = None): return # Validate dial-in requirements - if args.dialin and args.transport != "daily": + if args.dialin and args.transport is not None and args.transport != "daily": logger.error("--dialin flag only works with Daily transport (-t daily)") return @@ -1029,28 +1138,38 @@ def main(parser: argparse.ArgumentParser | None = None): asyncio.run(_run_daily_direct(args)) return - # Print startup message for server-based transports - if args.transport == "webrtc": - print() + # Print startup message + print() + if args.transport is None: + print("🚀 Bot ready!") + print(f" → WebRTC: http://{args.host}:{args.port}/client") + print(f" → Daily: http://{args.host}:{args.port}/daily") + print(f" → Telephony: ws://{args.host}:{args.port}/ws") + elif args.transport == "webrtc": if args.esp32: - print(f"🚀 Bot ready! (ESP32 mode)") + print("🚀 Bot ready! (ESP32 mode)") elif args.whatsapp: - print(f"🚀 Bot ready! (WhatsApp)") + print("🚀 Bot ready! (WhatsApp)") else: - print(f"🚀 Bot ready!") + print("🚀 Bot ready! (WebRTC)") print(f" → Open http://{args.host}:{args.port}/client in your browser") - print() elif args.transport == "daily": - print() - print(f"🚀 Bot ready!") + print("🚀 Bot ready! (Daily)") if args.dialin: print( f" → Daily dial-in webhook: http://{args.host}:{args.port}/daily-dialin-webhook" ) print(f" → Configure this URL in your Daily phone number settings") else: - print(f" → Open http://{args.host}:{args.port} in your browser to start a session") - print() + print( + f" → Open http://{args.host}:{args.port}/daily in your browser to start a session" + ) + elif args.transport in TELEPHONY_TRANSPORTS: + print(f"🚀 Bot ready! ({args.transport.capitalize()})") + if args.proxy: + print(f" → XML webhook: http://{args.host}:{args.port}/") + print(f" → WebSocket: ws://{args.host}:{args.port}/ws") + print() RUNNER_DOWNLOADS_FOLDER = args.folder RUNNER_HOST = args.host diff --git a/src/pipecat/runner/types.py b/src/pipecat/runner/types.py index 70ce03f29..b6bdd0014 100644 --- a/src/pipecat/runner/types.py +++ b/src/pipecat/runner/types.py @@ -105,10 +105,14 @@ class WebSocketRunnerArguments(RunnerArguments): Parameters: websocket: WebSocket connection for audio streaming + transport_type: Transport type identifier. Set to ``"websocket"`` for plain + WebSocket connections; ``None`` triggers auto-detection from the first + telephony provider message. body: Additional request data """ websocket: WebSocket + transport_type: str | None = None @dataclass diff --git a/src/pipecat/runner/utils.py b/src/pipecat/runner/utils.py index 4715be6f3..19f2e6a50 100644 --- a/src/pipecat/runner/utils.py +++ b/src/pipecat/runner/utils.py @@ -562,6 +562,12 @@ async def create_transport( ) elif isinstance(runner_args, WebSocketRunnerArguments): + if runner_args.transport_type == "websocket": + params = _get_transport_params("websocket", transport_params) + from pipecat.transports.websocket.fastapi import FastAPIWebsocketTransport + + return FastAPIWebsocketTransport(websocket=runner_args.websocket, params=params) + # Parse once to determine the provider and get data transport_type, call_data = await parse_telephony_websocket(runner_args.websocket) params = _get_transport_params(transport_type, transport_params) diff --git a/uv.lock b/uv.lock index 81c1949a9..11ca03d4a 100644 --- a/uv.lock +++ b/uv.lock @@ -1898,7 +1898,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/0f/a91f143f356523ff682309732b175765a9bc2836fd7c081c2c67fedc1ad4/greenlet-3.5.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082", size = 284726, upload-time = "2026-04-27T12:20:51.402Z" }, { url = "https://files.pythonhosted.org/packages/95/82/800646c7ffc5dbabd75ddd2f6b519bb898c0c9c969e5d0473bfe5d20bcce/greenlet-3.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3", size = 604264, upload-time = "2026-04-27T12:52:39.494Z" }, { url = "https://files.pythonhosted.org/packages/ca/ac/354867c0bba812fc33b15bc55aedafedd0aee3c7dd91dfca22444157dc0c/greenlet-3.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c", size = 616099, upload-time = "2026-04-27T12:59:39.623Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ab/192090c4a5b30df148c22bf4b8895457d739a7c7c5a7b9c41e5dd7f537f2/greenlet-3.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa94cb2288681e3a11645958f1871d48ee9211bd2f66628fdace505927d6e564", size = 623976, upload-time = "2026-04-27T13:02:37.363Z" }, { url = "https://files.pythonhosted.org/packages/ff/b0/815bece7399e01cadb69014219eebd0042339875c59a59b0820a46ece356/greenlet-3.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662", size = 615198, upload-time = "2026-04-27T12:25:25.928Z" }, + { url = "https://files.pythonhosted.org/packages/24/11/05eb2b9b188c6df7d68a89c99134d644a7af616a40b9808e8e6ced315d5d/greenlet-3.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:64d6ac45f7271f48e45f67c95b54ef73534c52ec041fcda8edf520c6d811f4bc", size = 418379, upload-time = "2026-04-27T13:05:12.755Z" }, { url = "https://files.pythonhosted.org/packages/10/80/3b2c0a895d6698f6ddb31b07942ebfa982f3e30888bc5546a5b5990de8b2/greenlet-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b", size = 1574927, upload-time = "2026-04-27T12:53:25.81Z" }, { url = "https://files.pythonhosted.org/packages/44/0e/f354af514a4c61454dbc68e44d47544a5a4d6317e30b77ddfa3a09f4c5f3/greenlet-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4", size = 1642683, upload-time = "2026-04-27T12:25:23.9Z" }, { url = "https://files.pythonhosted.org/packages/fa/6a/87f38255201e993a1915265ebb80cd7c2c78b04a45744995abbf6b259fd8/greenlet-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8", size = 238115, upload-time = "2026-04-27T12:21:48.845Z" }, @@ -1906,7 +1908,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/32/f2ce6d4cac3e55bc6173f92dbe627e782e1850f89d986c3606feb63aafa7/greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f", size = 286228, upload-time = "2026-04-27T12:20:34.421Z" }, { url = "https://files.pythonhosted.org/packages/b7/aa/caed9e5adf742315fc7be2a84196373aab4816e540e38ba0d76cb7584d68/greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628", size = 601775, upload-time = "2026-04-27T12:52:41.045Z" }, { url = "https://files.pythonhosted.org/packages/c7/af/90ae08497400a941595d12774447f752d3dfe0fbb012e35b76bc5c0ff37e/greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b", size = 614436, upload-time = "2026-04-27T12:59:41.595Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e9/4eeadf8cb3403ac274245ba75f07844abc7fa5f6787583fc9156ba741e0f/greenlet-3.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:41353ec2ecedf7aa8f682753a41919f8718031a6edac46b8d3dc7ed9e1ceb136", size = 620610, upload-time = "2026-04-27T13:02:39.194Z" }, { url = "https://files.pythonhosted.org/packages/2b/e0/2e13df68f367e2f9960616927d60857dd7e56aaadd59a47c644216b2f920/greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c", size = 611388, upload-time = "2026-04-27T12:25:28.008Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ef/f913b3c0eb7d26d86a2401c5e1546c9d46b657efee724b06f6f4ac5d8824/greenlet-3.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:58c1c374fe2b3d852f9b6b11a7dff4c85404e51b9a596fd9e89cf904eb09866d", size = 422775, upload-time = "2026-04-27T13:05:14.261Z" }, { url = "https://files.pythonhosted.org/packages/82/f7/393c64055132ac0d488ef6be549253b7e6274194863967ddc0bc8f5b87b8/greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588", size = 1570768, upload-time = "2026-04-27T12:53:28.099Z" }, { url = "https://files.pythonhosted.org/packages/b8/4b/eaf7735253522cf56d1b74d672a58f54fc114702ceaf05def59aae72f6e1/greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e", size = 1635983, upload-time = "2026-04-27T12:25:26.903Z" }, { url = "https://files.pythonhosted.org/packages/4c/fe/4fb3a0805bd5165da5ebf858da7cc01cce8061674106d2cf5bdab32cbfde/greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8", size = 238840, upload-time = "2026-04-27T12:23:54.806Z" }, @@ -1914,7 +1918,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, + { url = "https://files.pythonhosted.org/packages/6a/15/a643b4ecd09969e30b8a150d5919960caae0abe4f5af75ab040b1ab85e78/greenlet-3.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d", size = 623234, upload-time = "2026-04-27T13:02:40.611Z" }, { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, + { url = "https://files.pythonhosted.org/packages/77/18/3b13d5ef1275b0ffaf933b05efa21408ac4ca95823c7411d79682e4fdcff/greenlet-3.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae", size = 425243, upload-time = "2026-04-27T13:05:15.689Z" }, { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, @@ -1922,7 +1928,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, + { url = "https://files.pythonhosted.org/packages/fb/89/2dadb89793c37ee8b4c237857188293e9060dc085f19845c292e00f8e091/greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37", size = 668086, upload-time = "2026-04-27T13:02:42.314Z" }, { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/75722be7e26a2af4cbd2dc35b0ed382dacf9394b7e75551f76ed1abe87f2/greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2", size = 470799, upload-time = "2026-04-27T13:05:17.094Z" }, { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, @@ -1930,7 +1938,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/0602239503b124b70e39355cbdb39361ecfe65b87a5f2f63752c32f5286f/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce", size = 657015, upload-time = "2026-04-27T13:02:43.973Z" }, { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/38/51/8699f865f125dc952384cb432b0f7138aa4d8f2969a7d12d0df5b94d054d/greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2", size = 488275, upload-time = "2026-04-27T13:05:18.28Z" }, { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, @@ -4351,7 +4361,7 @@ rnnoise = [ ] runner = [ { name = "fastapi" }, - { name = "pipecat-ai-small-webrtc-prebuilt" }, + { name = "pipecat-ai-prebuilt" }, { name = "python-dotenv" }, { name = "uvicorn" }, ] @@ -4516,7 +4526,7 @@ requires-dist = [ { name = "pipecat-ai", extras = ["websockets-base"], marker = "extra == 'ultravox'" }, { name = "pipecat-ai", extras = ["websockets-base"], marker = "extra == 'websocket'" }, { name = "pipecat-ai", extras = ["websockets-base"], marker = "extra == 'xai'" }, - { name = "pipecat-ai-small-webrtc-prebuilt", marker = "extra == 'runner'", specifier = ">=2.5.0" }, + { name = "pipecat-ai-prebuilt", marker = "extra == 'runner'", specifier = ">=1.0.0" }, { name = "piper-tts", marker = "extra == 'piper'", specifier = ">=1.3.0,<2" }, { name = "protobuf", specifier = ">=5.29.6,<7" }, { name = "protobuf", marker = "extra == 'nvidia'", specifier = ">=6.31.1,<7" }, @@ -4578,15 +4588,15 @@ docs = [ ] [[package]] -name = "pipecat-ai-small-webrtc-prebuilt" -version = "2.5.0" +name = "pipecat-ai-prebuilt" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastapi", extra = ["all"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/4f/40bfc9fc1a13f9b1f2657e292c51ff3e3516c530ca722effdcf342d465d9/pipecat_ai_small_webrtc_prebuilt-2.5.0.tar.gz", hash = "sha256:51481506b7b5dff10eff0357ff929cba504a5198c3393697178d2be9895ad9e6", size = 474299, upload-time = "2026-04-22T18:05:16.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/86/7527474a324e3da787468133a1dba877e06576edc502e1bc7dd84ba7c9f7/pipecat_ai_prebuilt-1.0.0.tar.gz", hash = "sha256:dc66df541f17620eef5dedb2fd44737eb97232899779afb66dcca5aaa9317512", size = 601709, upload-time = "2026-05-14T21:15:26.575Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/58/1a2e10c1fb7b44e47558cb6c0954e24a60f98afe912fe55c74fdee66f080/pipecat_ai_small_webrtc_prebuilt-2.5.0-py3-none-any.whl", hash = "sha256:23b1eee95662a0072d9ee5128b8567108eda10d5a54ad71f279730afbb678bfe", size = 474308, upload-time = "2026-04-22T18:05:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/648122d5e418d3e0c8f797028bc53a22229ffc07a2406712b13b76735f38/pipecat_ai_prebuilt-1.0.0-py3-none-any.whl", hash = "sha256:6b7057920d3d00e5687adb26e032634ba1f6d924eb9079b1804d031620a1e854", size = 601949, upload-time = "2026-05-14T21:15:24.666Z" }, ] [[package]]