diff --git a/CHANGELOG.md b/CHANGELOG.md index d31a76c75..8d3ed8a88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added the ability to pass `body` properties to the development runner using + the `--body`/`-b` command. The `body` accepts an object provided using valid + JSON format. Currently, the `body` is only accepted when using + `DailyTransport`. + ### Fixed - Fixed an issue in `LiveKitTransport` where empty `AudioRawFrame`s were pushed diff --git a/src/pipecat/runner/run.py b/src/pipecat/runner/run.py index 3d75b563e..ab9b55768 100644 --- a/src/pipecat/runner/run.py +++ b/src/pipecat/runner/run.py @@ -61,6 +61,7 @@ To run locally: - 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` +- Daily with body data: `python bot.py -d --body '{"foo": "bar"}'` - Telephony: `python bot.py -t twilio -x your_username.ngrok.io` """ @@ -150,7 +151,11 @@ async def _run_telephony_bot(websocket: WebSocket): def _create_server_app( - transport_type: str, host: str = "localhost", proxy: str = None, esp32_mode: bool = False + transport_type: str, + host: str = "localhost", + proxy: str = None, + esp32_mode: bool = False, + body: dict = None, ): """Create FastAPI app with transport-specific routes.""" app = FastAPI() @@ -167,7 +172,7 @@ def _create_server_app( if transport_type == "webrtc": _setup_webrtc_routes(app, esp32_mode=esp32_mode, host=host) elif transport_type == "daily": - _setup_daily_routes(app) + _setup_daily_routes(app, body) elif transport_type in ["twilio", "telnyx", "plivo"]: _setup_telephony_routes(app, transport_type, proxy) else: @@ -246,7 +251,7 @@ def _setup_webrtc_routes(app: FastAPI, esp32_mode: bool = False, host: str = "lo app.router.lifespan_context = lifespan -def _setup_daily_routes(app: FastAPI): +def _setup_daily_routes(app: FastAPI, body: dict = None): """Set up Daily-specific routes.""" @app.get("/") @@ -261,9 +266,9 @@ def _setup_daily_routes(app: FastAPI): async with aiohttp.ClientSession() as session: room_url, token = await configure(session) - # Start the bot in the background + # Start the bot in the background with provided body bot_module = _get_bot_module() - runner_args = DailyRunnerArguments(room_url=room_url, token=token, body={}) + runner_args = DailyRunnerArguments(room_url=room_url, token=token, body=body or {}) asyncio.create_task(bot_module.bot(runner_args)) return RedirectResponse(room_url) @@ -279,9 +284,9 @@ def _setup_daily_routes(app: FastAPI): async with aiohttp.ClientSession() as session: room_url, token = await configure(session) - # Start the bot in the background + # Start the bot in the background with provided body bot_module = _get_bot_module() - runner_args = DailyRunnerArguments(room_url=room_url, token=token, body={}) + runner_args = DailyRunnerArguments(room_url=room_url, token=token, body=body or {}) asyncio.create_task(bot_module.bot(runner_args)) return {"room_url": room_url, "token": token} @@ -330,7 +335,7 @@ def _setup_telephony_routes(app: FastAPI, transport_type: str, proxy: str): return {"status": f"Bot started with {transport_type}"} -async def _run_daily_direct(): +async def _run_daily_direct(body: dict = None): """Run Daily bot with direct connection (no FastAPI server).""" try: import aiohttp @@ -345,13 +350,16 @@ async def _run_daily_direct(): async with aiohttp.ClientSession() as session: room_url, token = await configure(session) - runner_args = DailyRunnerArguments(room_url=room_url, token=token, body={}) + # Use provided body or default to empty dict + runner_args = DailyRunnerArguments(room_url=room_url, token=token, body=body or {}) # Get the bot module and run it directly bot_module = _get_bot_module() print(f"📞 Joining Daily room: {room_url}") print(" (Direct connection - no web server needed)") + if body: + print(f" Body data: {body}") print() await bot_module.bot(runner_args) @@ -373,6 +381,7 @@ def main(): -x/--proxy: Public proxy hostname for telephony webhooks --esp32: Enable SDP munging for ESP32 compatibility (requires --host with IP address) -d/--direct: Connect directly to Daily room (automatically sets transport to daily) + -b/--body: JSON body data to pass to Daily bot (only valid with Daily transport) -v/--verbose: Increase logging verbosity The bot file must contain a `bot(runner_args)` function as the entry point. @@ -402,6 +411,12 @@ def main(): default=False, help="Connect directly to Daily room (automatically sets transport to daily)", ) + parser.add_argument( + "-b", + "--body", + type=str, + help="JSON body data to pass to Daily bot (only valid with Daily transport)", + ) parser.add_argument( "--verbose", "-v", action="count", default=0, help="Increase logging verbosity" ) @@ -415,6 +430,22 @@ def main(): logger.error("--direct flag only works with Daily transport (-t daily)") return + # Validate body argument is only used with Daily transport + if args.body and args.transport != "daily": + logger.error("--body/-b flag only works with Daily transport (-t daily)") + return + + # Parse and validate JSON body if provided + parsed_body = {} + if args.body: + try: + import json + + parsed_body = json.loads(args.body) + except json.JSONDecodeError as e: + logger.error(f"Invalid JSON in --body argument: {e}") + return + # Validate ESP32 requirements if args.esp32 and args.host == "localhost": logger.error("For ESP32, you need to specify `--host IP` so we can do SDP munging.") @@ -430,8 +461,8 @@ def main(): print("🚀 Connecting directly to Daily room...") print() - # Run direct Daily connection - asyncio.run(_run_daily_direct()) + # Run direct Daily connection with parsed body + asyncio.run(_run_daily_direct(parsed_body)) return # Print startup message for server-based transports @@ -452,7 +483,7 @@ def main(): print() # Create the app with transport-specific setup - app = _create_server_app(args.transport, args.host, args.proxy, args.esp32) + app = _create_server_app(args.transport, args.host, args.proxy, args.esp32, parsed_body) # Run the server uvicorn.run(app, host=args.host, port=args.port)