dv-pipecat-ai 0.0.82.dev857__py3-none-any.whl → 0.0.85.dev837__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dv-pipecat-ai might be problematic. Click here for more details.
- {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/METADATA +98 -130
- {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/RECORD +192 -140
- pipecat/adapters/base_llm_adapter.py +38 -1
- pipecat/adapters/services/anthropic_adapter.py +9 -14
- pipecat/adapters/services/aws_nova_sonic_adapter.py +120 -5
- pipecat/adapters/services/bedrock_adapter.py +236 -13
- pipecat/adapters/services/gemini_adapter.py +12 -8
- pipecat/adapters/services/open_ai_adapter.py +19 -7
- pipecat/adapters/services/open_ai_realtime_adapter.py +5 -0
- pipecat/audio/dtmf/dtmf-0.wav +0 -0
- pipecat/audio/dtmf/dtmf-1.wav +0 -0
- pipecat/audio/dtmf/dtmf-2.wav +0 -0
- pipecat/audio/dtmf/dtmf-3.wav +0 -0
- pipecat/audio/dtmf/dtmf-4.wav +0 -0
- pipecat/audio/dtmf/dtmf-5.wav +0 -0
- pipecat/audio/dtmf/dtmf-6.wav +0 -0
- pipecat/audio/dtmf/dtmf-7.wav +0 -0
- pipecat/audio/dtmf/dtmf-8.wav +0 -0
- pipecat/audio/dtmf/dtmf-9.wav +0 -0
- pipecat/audio/dtmf/dtmf-pound.wav +0 -0
- pipecat/audio/dtmf/dtmf-star.wav +0 -0
- pipecat/audio/filters/krisp_viva_filter.py +193 -0
- pipecat/audio/filters/noisereduce_filter.py +15 -0
- pipecat/audio/turn/base_turn_analyzer.py +9 -1
- pipecat/audio/turn/smart_turn/base_smart_turn.py +14 -8
- pipecat/audio/turn/smart_turn/data/__init__.py +0 -0
- pipecat/audio/turn/smart_turn/data/smart-turn-v3.0.onnx +0 -0
- pipecat/audio/turn/smart_turn/http_smart_turn.py +6 -2
- pipecat/audio/turn/smart_turn/local_smart_turn.py +1 -1
- pipecat/audio/turn/smart_turn/local_smart_turn_v2.py +1 -1
- pipecat/audio/turn/smart_turn/local_smart_turn_v3.py +124 -0
- pipecat/audio/vad/data/README.md +10 -0
- pipecat/audio/vad/data/silero_vad_v2.onnx +0 -0
- pipecat/audio/vad/silero.py +9 -3
- pipecat/audio/vad/vad_analyzer.py +13 -1
- pipecat/extensions/voicemail/voicemail_detector.py +5 -5
- pipecat/frames/frames.py +277 -86
- pipecat/observers/loggers/debug_log_observer.py +3 -3
- pipecat/observers/loggers/llm_log_observer.py +7 -3
- pipecat/observers/loggers/user_bot_latency_log_observer.py +22 -10
- pipecat/pipeline/runner.py +18 -6
- pipecat/pipeline/service_switcher.py +64 -36
- pipecat/pipeline/task.py +125 -79
- pipecat/pipeline/tts_switcher.py +30 -0
- pipecat/processors/aggregators/dtmf_aggregator.py +2 -3
- pipecat/processors/aggregators/{gated_openai_llm_context.py → gated_llm_context.py} +9 -9
- pipecat/processors/aggregators/gated_open_ai_llm_context.py +12 -0
- pipecat/processors/aggregators/llm_context.py +40 -2
- pipecat/processors/aggregators/llm_response.py +32 -15
- pipecat/processors/aggregators/llm_response_universal.py +19 -15
- pipecat/processors/aggregators/user_response.py +6 -6
- pipecat/processors/aggregators/vision_image_frame.py +24 -2
- pipecat/processors/audio/audio_buffer_processor.py +43 -8
- pipecat/processors/dtmf_aggregator.py +174 -77
- pipecat/processors/filters/stt_mute_filter.py +17 -0
- pipecat/processors/frame_processor.py +110 -24
- pipecat/processors/frameworks/langchain.py +8 -2
- pipecat/processors/frameworks/rtvi.py +210 -68
- pipecat/processors/frameworks/strands_agents.py +170 -0
- pipecat/processors/logger.py +2 -2
- pipecat/processors/transcript_processor.py +26 -5
- pipecat/processors/user_idle_processor.py +35 -11
- pipecat/runner/daily.py +59 -20
- pipecat/runner/run.py +395 -93
- pipecat/runner/types.py +6 -4
- pipecat/runner/utils.py +51 -10
- pipecat/serializers/__init__.py +5 -1
- pipecat/serializers/asterisk.py +16 -2
- pipecat/serializers/convox.py +41 -4
- pipecat/serializers/custom.py +257 -0
- pipecat/serializers/exotel.py +5 -5
- pipecat/serializers/livekit.py +20 -0
- pipecat/serializers/plivo.py +5 -5
- pipecat/serializers/protobuf.py +6 -5
- pipecat/serializers/telnyx.py +2 -2
- pipecat/serializers/twilio.py +43 -23
- pipecat/serializers/vi.py +324 -0
- pipecat/services/ai_service.py +2 -6
- pipecat/services/anthropic/llm.py +2 -25
- pipecat/services/assemblyai/models.py +6 -0
- pipecat/services/assemblyai/stt.py +13 -5
- pipecat/services/asyncai/tts.py +5 -3
- pipecat/services/aws/__init__.py +1 -0
- pipecat/services/aws/llm.py +147 -105
- pipecat/services/aws/nova_sonic/__init__.py +0 -0
- pipecat/services/aws/nova_sonic/context.py +436 -0
- pipecat/services/aws/nova_sonic/frames.py +25 -0
- pipecat/services/aws/nova_sonic/llm.py +1265 -0
- pipecat/services/aws/stt.py +3 -3
- pipecat/services/aws_nova_sonic/__init__.py +19 -1
- pipecat/services/aws_nova_sonic/aws.py +11 -1151
- pipecat/services/aws_nova_sonic/context.py +8 -354
- pipecat/services/aws_nova_sonic/frames.py +13 -17
- pipecat/services/azure/llm.py +51 -1
- pipecat/services/azure/realtime/__init__.py +0 -0
- pipecat/services/azure/realtime/llm.py +65 -0
- pipecat/services/azure/stt.py +15 -0
- pipecat/services/cartesia/stt.py +77 -70
- pipecat/services/cartesia/tts.py +80 -13
- pipecat/services/deepgram/__init__.py +1 -0
- pipecat/services/deepgram/flux/__init__.py +0 -0
- pipecat/services/deepgram/flux/stt.py +640 -0
- pipecat/services/elevenlabs/__init__.py +4 -1
- pipecat/services/elevenlabs/stt.py +339 -0
- pipecat/services/elevenlabs/tts.py +87 -46
- pipecat/services/fish/tts.py +5 -2
- pipecat/services/gemini_multimodal_live/events.py +38 -524
- pipecat/services/gemini_multimodal_live/file_api.py +23 -173
- pipecat/services/gemini_multimodal_live/gemini.py +41 -1403
- pipecat/services/gladia/stt.py +56 -72
- pipecat/services/google/__init__.py +1 -0
- pipecat/services/google/gemini_live/__init__.py +3 -0
- pipecat/services/google/gemini_live/file_api.py +189 -0
- pipecat/services/google/gemini_live/llm.py +1582 -0
- pipecat/services/google/gemini_live/llm_vertex.py +184 -0
- pipecat/services/google/llm.py +15 -11
- pipecat/services/google/llm_openai.py +3 -3
- pipecat/services/google/llm_vertex.py +86 -16
- pipecat/services/google/stt.py +4 -0
- pipecat/services/google/tts.py +7 -3
- pipecat/services/heygen/api.py +2 -0
- pipecat/services/heygen/client.py +8 -4
- pipecat/services/heygen/video.py +2 -0
- pipecat/services/hume/__init__.py +5 -0
- pipecat/services/hume/tts.py +220 -0
- pipecat/services/inworld/tts.py +6 -6
- pipecat/services/llm_service.py +15 -5
- pipecat/services/lmnt/tts.py +4 -2
- pipecat/services/mcp_service.py +4 -2
- pipecat/services/mem0/memory.py +6 -5
- pipecat/services/mistral/llm.py +29 -8
- pipecat/services/moondream/vision.py +42 -16
- pipecat/services/neuphonic/tts.py +5 -2
- pipecat/services/openai/__init__.py +1 -0
- pipecat/services/openai/base_llm.py +27 -20
- pipecat/services/openai/realtime/__init__.py +0 -0
- pipecat/services/openai/realtime/context.py +272 -0
- pipecat/services/openai/realtime/events.py +1106 -0
- pipecat/services/openai/realtime/frames.py +37 -0
- pipecat/services/openai/realtime/llm.py +829 -0
- pipecat/services/openai/tts.py +49 -10
- pipecat/services/openai_realtime/__init__.py +27 -0
- pipecat/services/openai_realtime/azure.py +21 -0
- pipecat/services/openai_realtime/context.py +21 -0
- pipecat/services/openai_realtime/events.py +21 -0
- pipecat/services/openai_realtime/frames.py +21 -0
- pipecat/services/openai_realtime_beta/azure.py +16 -0
- pipecat/services/openai_realtime_beta/openai.py +17 -5
- pipecat/services/piper/tts.py +7 -9
- pipecat/services/playht/tts.py +34 -4
- pipecat/services/rime/tts.py +12 -12
- pipecat/services/riva/stt.py +3 -1
- pipecat/services/salesforce/__init__.py +9 -0
- pipecat/services/salesforce/llm.py +700 -0
- pipecat/services/sarvam/__init__.py +7 -0
- pipecat/services/sarvam/stt.py +540 -0
- pipecat/services/sarvam/tts.py +97 -13
- pipecat/services/simli/video.py +2 -2
- pipecat/services/speechmatics/stt.py +22 -10
- pipecat/services/stt_service.py +47 -0
- pipecat/services/tavus/video.py +2 -2
- pipecat/services/tts_service.py +75 -22
- pipecat/services/vision_service.py +7 -6
- pipecat/services/vistaar/llm.py +51 -9
- pipecat/tests/utils.py +4 -4
- pipecat/transcriptions/language.py +41 -1
- pipecat/transports/base_input.py +13 -34
- pipecat/transports/base_output.py +140 -104
- pipecat/transports/daily/transport.py +199 -26
- pipecat/transports/heygen/__init__.py +0 -0
- pipecat/transports/heygen/transport.py +381 -0
- pipecat/transports/livekit/transport.py +228 -63
- pipecat/transports/local/audio.py +6 -1
- pipecat/transports/local/tk.py +11 -2
- pipecat/transports/network/fastapi_websocket.py +1 -1
- pipecat/transports/smallwebrtc/connection.py +103 -19
- pipecat/transports/smallwebrtc/request_handler.py +246 -0
- pipecat/transports/smallwebrtc/transport.py +65 -23
- pipecat/transports/tavus/transport.py +23 -12
- pipecat/transports/websocket/client.py +41 -5
- pipecat/transports/websocket/fastapi.py +21 -11
- pipecat/transports/websocket/server.py +14 -7
- pipecat/transports/whatsapp/api.py +8 -0
- pipecat/transports/whatsapp/client.py +47 -0
- pipecat/utils/base_object.py +54 -22
- pipecat/utils/redis.py +58 -0
- pipecat/utils/string.py +13 -1
- pipecat/utils/tracing/service_decorators.py +21 -21
- pipecat/serializers/genesys.py +0 -95
- pipecat/services/google/test-google-chirp.py +0 -45
- pipecat/services/openai.py +0 -698
- {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/WHEEL +0 -0
- {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/licenses/LICENSE +0 -0
- {dv_pipecat_ai-0.0.82.dev857.dist-info → dv_pipecat_ai-0.0.85.dev837.dist-info}/top_level.txt +0 -0
- /pipecat/services/{aws_nova_sonic → aws/nova_sonic}/ready.wav +0 -0
pipecat/runner/run.py
CHANGED
|
@@ -67,15 +67,22 @@ To run locally:
|
|
|
67
67
|
|
|
68
68
|
import argparse
|
|
69
69
|
import asyncio
|
|
70
|
+
import mimetypes
|
|
70
71
|
import os
|
|
71
72
|
import sys
|
|
73
|
+
import uuid
|
|
72
74
|
from contextlib import asynccontextmanager
|
|
73
|
-
from
|
|
75
|
+
from http import HTTPMethod
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
from typing import Any, Dict, List, Optional, TypedDict
|
|
74
78
|
|
|
79
|
+
import aiohttp
|
|
80
|
+
from fastapi.responses import FileResponse, Response
|
|
75
81
|
from loguru import logger
|
|
76
82
|
|
|
77
83
|
from pipecat.runner.types import (
|
|
78
84
|
DailyRunnerArguments,
|
|
85
|
+
RunnerArguments,
|
|
79
86
|
SmallWebRTCRunnerArguments,
|
|
80
87
|
WebSocketRunnerArguments,
|
|
81
88
|
)
|
|
@@ -83,7 +90,7 @@ from pipecat.runner.types import (
|
|
|
83
90
|
try:
|
|
84
91
|
import uvicorn
|
|
85
92
|
from dotenv import load_dotenv
|
|
86
|
-
from fastapi import BackgroundTasks, FastAPI, Request, WebSocket
|
|
93
|
+
from fastapi import BackgroundTasks, FastAPI, Header, HTTPException, Request, WebSocket
|
|
87
94
|
from fastapi.middleware.cors import CORSMiddleware
|
|
88
95
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
89
96
|
except ImportError as e:
|
|
@@ -97,6 +104,12 @@ except ImportError as e:
|
|
|
97
104
|
load_dotenv(override=True)
|
|
98
105
|
os.environ["ENV"] = "local"
|
|
99
106
|
|
|
107
|
+
TELEPHONY_TRANSPORTS = ["twilio", "telnyx", "plivo", "exotel"]
|
|
108
|
+
|
|
109
|
+
RUNNER_DOWNLOADS_FOLDER: Optional[str] = None
|
|
110
|
+
RUNNER_HOST: str = "localhost"
|
|
111
|
+
RUNNER_PORT: int = 7860
|
|
112
|
+
|
|
100
113
|
|
|
101
114
|
def _get_bot_module():
|
|
102
115
|
"""Get the bot module from the calling script."""
|
|
@@ -151,7 +164,13 @@ async def _run_telephony_bot(websocket: WebSocket):
|
|
|
151
164
|
|
|
152
165
|
|
|
153
166
|
def _create_server_app(
|
|
154
|
-
|
|
167
|
+
*,
|
|
168
|
+
transport_type: str,
|
|
169
|
+
host: str = "localhost",
|
|
170
|
+
proxy: str,
|
|
171
|
+
esp32_mode: bool = False,
|
|
172
|
+
whatsapp_enabled: bool = False,
|
|
173
|
+
folder: Optional[str] = None,
|
|
155
174
|
):
|
|
156
175
|
"""Create FastAPI app with transport-specific routes."""
|
|
157
176
|
app = FastAPI()
|
|
@@ -166,29 +185,46 @@ def _create_server_app(
|
|
|
166
185
|
|
|
167
186
|
# Set up transport-specific routes
|
|
168
187
|
if transport_type == "webrtc":
|
|
169
|
-
_setup_webrtc_routes(app, esp32_mode=esp32_mode, host=host)
|
|
188
|
+
_setup_webrtc_routes(app, esp32_mode=esp32_mode, host=host, folder=folder)
|
|
189
|
+
if whatsapp_enabled:
|
|
190
|
+
_setup_whatsapp_routes(app)
|
|
170
191
|
elif transport_type == "daily":
|
|
171
192
|
_setup_daily_routes(app)
|
|
172
|
-
elif transport_type in
|
|
173
|
-
_setup_telephony_routes(app, transport_type, proxy)
|
|
193
|
+
elif transport_type in TELEPHONY_TRANSPORTS:
|
|
194
|
+
_setup_telephony_routes(app, transport_type=transport_type, proxy=proxy)
|
|
174
195
|
else:
|
|
175
196
|
logger.warning(f"Unknown transport type: {transport_type}")
|
|
176
197
|
|
|
177
198
|
return app
|
|
178
199
|
|
|
179
200
|
|
|
180
|
-
def _setup_webrtc_routes(
|
|
201
|
+
def _setup_webrtc_routes(
|
|
202
|
+
app: FastAPI, *, esp32_mode: bool = False, host: str = "localhost", folder: Optional[str] = None
|
|
203
|
+
):
|
|
181
204
|
"""Set up WebRTC-specific routes."""
|
|
182
205
|
try:
|
|
183
206
|
from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI
|
|
184
207
|
|
|
185
|
-
from pipecat.transports.smallwebrtc.connection import SmallWebRTCConnection
|
|
208
|
+
from pipecat.transports.smallwebrtc.connection import IceServer, SmallWebRTCConnection
|
|
209
|
+
from pipecat.transports.smallwebrtc.request_handler import (
|
|
210
|
+
IceCandidate,
|
|
211
|
+
SmallWebRTCPatchRequest,
|
|
212
|
+
SmallWebRTCRequest,
|
|
213
|
+
SmallWebRTCRequestHandler,
|
|
214
|
+
)
|
|
186
215
|
except ImportError as e:
|
|
187
216
|
logger.error(f"WebRTC transport dependencies not installed: {e}")
|
|
188
217
|
return
|
|
189
218
|
|
|
190
|
-
|
|
191
|
-
|
|
219
|
+
class IceConfig(TypedDict):
|
|
220
|
+
iceServers: List[IceServer]
|
|
221
|
+
|
|
222
|
+
class StartBotResult(TypedDict, total=False):
|
|
223
|
+
sessionId: str
|
|
224
|
+
iceConfig: Optional[IceConfig]
|
|
225
|
+
|
|
226
|
+
# In-memory store of active sessions: session_id -> session info
|
|
227
|
+
active_sessions: Dict[str, Dict[str, Any]] = {}
|
|
192
228
|
|
|
193
229
|
# Mount the frontend
|
|
194
230
|
app.mount("/client", SmallWebRTCPrebuiltUI)
|
|
@@ -198,62 +234,305 @@ def _setup_webrtc_routes(app: FastAPI, esp32_mode: bool = False, host: str = "lo
|
|
|
198
234
|
"""Redirect root requests to client interface."""
|
|
199
235
|
return RedirectResponse(url="/client/")
|
|
200
236
|
|
|
201
|
-
@app.
|
|
202
|
-
async def
|
|
203
|
-
"""Handle
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
)
|
|
214
|
-
else:
|
|
215
|
-
pipecat_connection = SmallWebRTCConnection()
|
|
216
|
-
await pipecat_connection.initialize(sdp=request["sdp"], type=request["type"])
|
|
237
|
+
@app.get("/files/{filename:path}")
|
|
238
|
+
async def download_file(filename: str):
|
|
239
|
+
"""Handle file downloads."""
|
|
240
|
+
if not folder:
|
|
241
|
+
logger.warning(f"Attempting to dowload {filename}, but downloads folder not setup.")
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
file_path = Path(folder) / filename
|
|
245
|
+
if not os.path.exists(file_path):
|
|
246
|
+
raise HTTPException(404)
|
|
247
|
+
|
|
248
|
+
media_type, _ = mimetypes.guess_type(file_path)
|
|
217
249
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
250
|
+
return FileResponse(path=file_path, media_type=media_type, filename=filename)
|
|
251
|
+
|
|
252
|
+
# Initialize the SmallWebRTC request handler
|
|
253
|
+
small_webrtc_handler: SmallWebRTCRequestHandler = SmallWebRTCRequestHandler(
|
|
254
|
+
esp32_mode=esp32_mode, host=host
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@app.post("/api/offer")
|
|
258
|
+
async def offer(request: SmallWebRTCRequest, background_tasks: BackgroundTasks):
|
|
259
|
+
"""Handle WebRTC offer requests via SmallWebRTCRequestHandler."""
|
|
223
260
|
|
|
261
|
+
# Prepare runner arguments with the callback to run your bot
|
|
262
|
+
async def webrtc_connection_callback(connection):
|
|
224
263
|
bot_module = _get_bot_module()
|
|
225
|
-
runner_args = SmallWebRTCRunnerArguments(webrtc_connection=
|
|
264
|
+
runner_args = SmallWebRTCRunnerArguments(webrtc_connection=connection)
|
|
226
265
|
background_tasks.add_task(bot_module.bot, runner_args)
|
|
227
266
|
|
|
228
|
-
|
|
267
|
+
# Delegate handling to SmallWebRTCRequestHandler
|
|
268
|
+
answer = await small_webrtc_handler.handle_web_request(
|
|
269
|
+
request=request,
|
|
270
|
+
webrtc_connection_callback=webrtc_connection_callback,
|
|
271
|
+
)
|
|
272
|
+
return answer
|
|
229
273
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
274
|
+
@app.patch("/api/offer")
|
|
275
|
+
async def ice_candidate(request: SmallWebRTCPatchRequest):
|
|
276
|
+
"""Handle WebRTC new ice candidate requests."""
|
|
277
|
+
logger.debug(f"Received patch request: {request}")
|
|
278
|
+
await small_webrtc_handler.handle_patch_request(request)
|
|
279
|
+
return {"status": "success"}
|
|
233
280
|
|
|
234
|
-
|
|
281
|
+
@app.post("/start")
|
|
282
|
+
async def rtvi_start(request: Request):
|
|
283
|
+
"""Mimic Pipecat Cloud's /start endpoint."""
|
|
284
|
+
# Parse the request body
|
|
285
|
+
try:
|
|
286
|
+
request_data = await request.json()
|
|
287
|
+
logger.debug(f"Received request: {request_data}")
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.error(f"Failed to parse request body: {e}")
|
|
290
|
+
request_data = {}
|
|
235
291
|
|
|
236
|
-
|
|
237
|
-
|
|
292
|
+
# Store session info immediately in memory, replicate the behavior expected on Pipecat Cloud
|
|
293
|
+
session_id = str(uuid.uuid4())
|
|
294
|
+
active_sessions[session_id] = request_data
|
|
295
|
+
|
|
296
|
+
result: StartBotResult = {"sessionId": session_id}
|
|
297
|
+
if request_data.get("enableDefaultIceServers"):
|
|
298
|
+
result["iceConfig"] = IceConfig(
|
|
299
|
+
iceServers=[IceServer(urls="stun:stun.l.google.com:19302")]
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
@app.api_route(
|
|
305
|
+
"/sessions/{session_id}/{path:path}",
|
|
306
|
+
methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
307
|
+
)
|
|
308
|
+
async def proxy_request(
|
|
309
|
+
session_id: str, path: str, request: Request, background_tasks: BackgroundTasks
|
|
310
|
+
):
|
|
311
|
+
"""Mimic Pipecat Cloud's proxy."""
|
|
312
|
+
active_session = active_sessions.get(session_id)
|
|
313
|
+
if active_session is None:
|
|
314
|
+
return Response(content="Invalid or not-yet-ready session_id", status_code=404)
|
|
315
|
+
|
|
316
|
+
if path.endswith("api/offer"):
|
|
317
|
+
# Parse the request body and convert to SmallWebRTCRequest
|
|
318
|
+
try:
|
|
319
|
+
request_data = await request.json()
|
|
320
|
+
if request.method == HTTPMethod.POST.value:
|
|
321
|
+
webrtc_request = SmallWebRTCRequest(
|
|
322
|
+
sdp=request_data["sdp"],
|
|
323
|
+
type=request_data["type"],
|
|
324
|
+
pc_id=request_data.get("pc_id"),
|
|
325
|
+
restart_pc=request_data.get("restart_pc"),
|
|
326
|
+
request_data=request_data,
|
|
327
|
+
)
|
|
328
|
+
return await offer(webrtc_request, background_tasks)
|
|
329
|
+
elif request.method == HTTPMethod.PATCH.value:
|
|
330
|
+
patch_request = SmallWebRTCPatchRequest(
|
|
331
|
+
pc_id=request_data["pc_id"],
|
|
332
|
+
candidates=[IceCandidate(**c) for c in request_data.get("candidates", [])],
|
|
333
|
+
)
|
|
334
|
+
return await ice_candidate(patch_request)
|
|
335
|
+
except Exception as e:
|
|
336
|
+
logger.error(f"Failed to parse WebRTC request: {e}")
|
|
337
|
+
return Response(content="Invalid WebRTC request", status_code=400)
|
|
338
|
+
|
|
339
|
+
logger.info(f"Received request for path: {path}")
|
|
340
|
+
return Response(status_code=200)
|
|
238
341
|
|
|
239
342
|
@asynccontextmanager
|
|
240
|
-
async def
|
|
343
|
+
async def smallwebrtc_lifespan(app: FastAPI):
|
|
241
344
|
"""Manage FastAPI application lifecycle and cleanup connections."""
|
|
242
345
|
yield
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
346
|
+
await small_webrtc_handler.close()
|
|
347
|
+
|
|
348
|
+
# Add the SmallWebRTC lifespan to the app
|
|
349
|
+
_add_lifespan_to_app(app, smallwebrtc_lifespan)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _add_lifespan_to_app(app: FastAPI, new_lifespan):
|
|
353
|
+
"""Add a new lifespan context manager to the app, combining with existing if present.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
app: The FastAPI application instance
|
|
357
|
+
new_lifespan: The new lifespan context manager to add
|
|
358
|
+
"""
|
|
359
|
+
if hasattr(app.router, "lifespan_context") and app.router.lifespan_context is not None:
|
|
360
|
+
# If there's already a lifespan context, combine them
|
|
361
|
+
existing_lifespan = app.router.lifespan_context
|
|
362
|
+
|
|
363
|
+
@asynccontextmanager
|
|
364
|
+
async def combined_lifespan(app: FastAPI):
|
|
365
|
+
async with existing_lifespan(app):
|
|
366
|
+
async with new_lifespan(app):
|
|
367
|
+
yield
|
|
368
|
+
|
|
369
|
+
app.router.lifespan_context = combined_lifespan
|
|
370
|
+
else:
|
|
371
|
+
# No existing lifespan, use the new one
|
|
372
|
+
app.router.lifespan_context = new_lifespan
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _setup_whatsapp_routes(app: FastAPI):
|
|
376
|
+
"""Set up WebRTC-specific routes."""
|
|
377
|
+
WHATSAPP_APP_SECRET = os.getenv("WHATSAPP_APP_SECRET")
|
|
378
|
+
WHATSAPP_PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID")
|
|
379
|
+
WHATSAPP_TOKEN = os.getenv("WHATSAPP_TOKEN")
|
|
380
|
+
WHATSAPP_WEBHOOK_VERIFICATION_TOKEN = os.getenv("WHATSAPP_WEBHOOK_VERIFICATION_TOKEN")
|
|
381
|
+
|
|
382
|
+
if not all(
|
|
383
|
+
[
|
|
384
|
+
WHATSAPP_APP_SECRET,
|
|
385
|
+
WHATSAPP_PHONE_NUMBER_ID,
|
|
386
|
+
WHATSAPP_TOKEN,
|
|
387
|
+
WHATSAPP_WEBHOOK_VERIFICATION_TOKEN,
|
|
388
|
+
]
|
|
389
|
+
):
|
|
390
|
+
logger.error(
|
|
391
|
+
"""Missing required environment variables for WhatsApp transport:
|
|
392
|
+
WHATSAPP_APP_SECRET
|
|
393
|
+
WHATSAPP_PHONE_NUMBER_ID
|
|
394
|
+
WHATSAPP_TOKEN
|
|
395
|
+
WHATSAPP_WEBHOOK_VERIFICATION_TOKEN
|
|
396
|
+
"""
|
|
397
|
+
)
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI
|
|
402
|
+
|
|
403
|
+
from pipecat.transports.smallwebrtc.connection import SmallWebRTCConnection
|
|
404
|
+
from pipecat.transports.smallwebrtc.request_handler import (
|
|
405
|
+
SmallWebRTCRequest,
|
|
406
|
+
SmallWebRTCRequestHandler,
|
|
407
|
+
)
|
|
408
|
+
from pipecat.transports.whatsapp.api import WhatsAppWebhookRequest
|
|
409
|
+
from pipecat.transports.whatsapp.client import WhatsAppClient
|
|
410
|
+
except ImportError as e:
|
|
411
|
+
logger.error(f"WhatsApp transport dependencies not installed: {e}")
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
# Global WhatsApp client instance
|
|
415
|
+
whatsapp_client: Optional[WhatsAppClient] = None
|
|
416
|
+
|
|
417
|
+
@app.get(
|
|
418
|
+
"/whatsapp",
|
|
419
|
+
summary="Verify WhatsApp webhook",
|
|
420
|
+
description="Handles WhatsApp webhook verification requests from Meta",
|
|
421
|
+
)
|
|
422
|
+
async def verify_webhook(request: Request):
|
|
423
|
+
"""Verify WhatsApp webhook endpoint.
|
|
424
|
+
|
|
425
|
+
This endpoint is called by Meta's WhatsApp Business API to verify
|
|
426
|
+
the webhook URL during setup. It validates the verification token
|
|
427
|
+
and returns the challenge parameter if successful.
|
|
428
|
+
"""
|
|
429
|
+
if whatsapp_client is None:
|
|
430
|
+
logger.error("WhatsApp client is not initialized")
|
|
431
|
+
raise HTTPException(status_code=503, detail="Service unavailable")
|
|
246
432
|
|
|
247
|
-
|
|
433
|
+
params = dict(request.query_params)
|
|
434
|
+
logger.debug(f"Webhook verification request received with params: {list(params.keys())}")
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
result = await whatsapp_client.handle_verify_webhook_request(
|
|
438
|
+
params=params, expected_verification_token=WHATSAPP_WEBHOOK_VERIFICATION_TOKEN
|
|
439
|
+
)
|
|
440
|
+
logger.info("Webhook verification successful")
|
|
441
|
+
return result
|
|
442
|
+
except ValueError as e:
|
|
443
|
+
logger.warning(f"Webhook verification failed: {e}")
|
|
444
|
+
raise HTTPException(status_code=403, detail="Verification failed")
|
|
445
|
+
|
|
446
|
+
@app.post(
|
|
447
|
+
"/whatsapp",
|
|
448
|
+
summary="Handle WhatsApp webhook events",
|
|
449
|
+
description="Processes incoming WhatsApp messages and call events",
|
|
450
|
+
)
|
|
451
|
+
async def whatsapp_webhook(
|
|
452
|
+
body: WhatsAppWebhookRequest,
|
|
453
|
+
background_tasks: BackgroundTasks,
|
|
454
|
+
request: Request,
|
|
455
|
+
x_hub_signature_256: str = Header(None),
|
|
456
|
+
):
|
|
457
|
+
"""Handle incoming WhatsApp webhook events.
|
|
458
|
+
|
|
459
|
+
For call events, establishes WebRTC connections and spawns bot instances
|
|
460
|
+
in the background to handle real-time communication.
|
|
461
|
+
"""
|
|
462
|
+
if whatsapp_client is None:
|
|
463
|
+
logger.error("WhatsApp client is not initialized")
|
|
464
|
+
raise HTTPException(status_code=503, detail="Service unavailable")
|
|
465
|
+
|
|
466
|
+
# Validate webhook object type
|
|
467
|
+
if body.object != "whatsapp_business_account":
|
|
468
|
+
logger.warning(f"Invalid webhook object type: {body.object}")
|
|
469
|
+
raise HTTPException(status_code=400, detail="Invalid object type")
|
|
470
|
+
|
|
471
|
+
logger.debug(f"Processing WhatsApp webhook: {body.model_dump()}")
|
|
472
|
+
|
|
473
|
+
async def connection_callback(connection: SmallWebRTCConnection):
|
|
474
|
+
"""Handle new WebRTC connections from WhatsApp calls.
|
|
475
|
+
|
|
476
|
+
Called when a WebRTC connection is established for a WhatsApp call.
|
|
477
|
+
Spawns a bot instance to handle the conversation.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
connection: The established WebRTC connection
|
|
481
|
+
"""
|
|
482
|
+
bot_module = _get_bot_module()
|
|
483
|
+
runner_args = SmallWebRTCRunnerArguments(webrtc_connection=connection)
|
|
484
|
+
background_tasks.add_task(bot_module.bot, runner_args)
|
|
485
|
+
|
|
486
|
+
try:
|
|
487
|
+
# Process the webhook request
|
|
488
|
+
raw_body = await request.body()
|
|
489
|
+
result = await whatsapp_client.handle_webhook_request(
|
|
490
|
+
body, connection_callback, sha256_signature=x_hub_signature_256, raw_body=raw_body
|
|
491
|
+
)
|
|
492
|
+
logger.debug(f"Webhook processed successfully: {result}")
|
|
493
|
+
return {"status": "success", "message": "Webhook processed successfully"}
|
|
494
|
+
except ValueError as ve:
|
|
495
|
+
logger.warning(f"Invalid webhook request format: {ve}")
|
|
496
|
+
raise HTTPException(status_code=400, detail=f"Invalid request: {str(ve)}")
|
|
497
|
+
except Exception as e:
|
|
498
|
+
logger.error(f"Internal error processing webhook: {e}")
|
|
499
|
+
raise HTTPException(status_code=500, detail="Internal server error processing webhook")
|
|
500
|
+
|
|
501
|
+
@asynccontextmanager
|
|
502
|
+
async def whatsapp_lifespan(app: FastAPI):
|
|
503
|
+
"""Manage WhatsApp client lifecycle and cleanup connections."""
|
|
504
|
+
nonlocal whatsapp_client
|
|
505
|
+
|
|
506
|
+
# Initialize WhatsApp client with persistent HTTP session
|
|
507
|
+
async with aiohttp.ClientSession() as session:
|
|
508
|
+
whatsapp_client = WhatsAppClient(
|
|
509
|
+
whatsapp_token=WHATSAPP_TOKEN,
|
|
510
|
+
whatsapp_secret=WHATSAPP_APP_SECRET,
|
|
511
|
+
phone_number_id=WHATSAPP_PHONE_NUMBER_ID,
|
|
512
|
+
session=session,
|
|
513
|
+
)
|
|
514
|
+
logger.info("WhatsApp client initialized successfully")
|
|
515
|
+
|
|
516
|
+
try:
|
|
517
|
+
yield # Run the application
|
|
518
|
+
finally:
|
|
519
|
+
# Cleanup all active calls on shutdown
|
|
520
|
+
logger.info("Cleaning up WhatsApp client resources...")
|
|
521
|
+
if whatsapp_client:
|
|
522
|
+
await whatsapp_client.terminate_all_calls()
|
|
523
|
+
logger.info("WhatsApp cleanup completed")
|
|
524
|
+
|
|
525
|
+
# Add the WhatsApp lifespan to the app
|
|
526
|
+
_add_lifespan_to_app(app, whatsapp_lifespan)
|
|
248
527
|
|
|
249
528
|
|
|
250
529
|
def _setup_daily_routes(app: FastAPI):
|
|
251
530
|
"""Set up Daily-specific routes."""
|
|
252
531
|
|
|
253
532
|
@app.get("/")
|
|
254
|
-
async def
|
|
533
|
+
async def create_room_and_start_agent():
|
|
255
534
|
"""Launch a Daily bot and redirect to room."""
|
|
256
|
-
print("Starting bot with Daily transport")
|
|
535
|
+
print("Starting bot with Daily transport and redirecting to Daily room")
|
|
257
536
|
|
|
258
537
|
import aiohttp
|
|
259
538
|
|
|
@@ -268,11 +547,11 @@ def _setup_daily_routes(app: FastAPI):
|
|
|
268
547
|
asyncio.create_task(bot_module.bot(runner_args))
|
|
269
548
|
return RedirectResponse(room_url)
|
|
270
549
|
|
|
271
|
-
|
|
272
|
-
|
|
550
|
+
@app.post("/start")
|
|
551
|
+
async def start_agent(request: Request):
|
|
552
|
+
"""Handler for /start endpoints.
|
|
273
553
|
|
|
274
554
|
Expects POST body like::
|
|
275
|
-
|
|
276
555
|
{
|
|
277
556
|
"createDailyRoom": true,
|
|
278
557
|
"dailyRoomProperties": { "start_video_off": true },
|
|
@@ -289,50 +568,35 @@ def _setup_daily_routes(app: FastAPI):
|
|
|
289
568
|
logger.error(f"Failed to parse request body: {e}")
|
|
290
569
|
request_data = {}
|
|
291
570
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
bot_body = request_data.get("body", {})
|
|
571
|
+
create_daily_room = request_data.get("createDailyRoom", False)
|
|
572
|
+
body = request_data.get("body", {})
|
|
295
573
|
|
|
296
|
-
|
|
297
|
-
if bot_body:
|
|
298
|
-
logger.info(f"Extracted body data for bot: {bot_body}")
|
|
299
|
-
else:
|
|
300
|
-
logger.debug("No body data provided in request")
|
|
301
|
-
|
|
302
|
-
import aiohttp
|
|
303
|
-
|
|
304
|
-
from pipecat.runner.daily import configure
|
|
574
|
+
bot_module = _get_bot_module()
|
|
305
575
|
|
|
306
|
-
|
|
307
|
-
|
|
576
|
+
result = None
|
|
577
|
+
if create_daily_room:
|
|
578
|
+
import aiohttp
|
|
308
579
|
|
|
309
|
-
|
|
310
|
-
bot_module = _get_bot_module()
|
|
311
|
-
runner_args = DailyRunnerArguments(room_url=room_url, token=token, body=bot_body)
|
|
312
|
-
asyncio.create_task(bot_module.bot(runner_args))
|
|
313
|
-
# Match PCC /start endpoint response format:
|
|
314
|
-
return {"dailyRoom": room_url, "dailyToken": token}
|
|
580
|
+
from pipecat.runner.daily import configure
|
|
315
581
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
582
|
+
async with aiohttp.ClientSession() as session:
|
|
583
|
+
room_url, token = await configure(session)
|
|
584
|
+
runner_args = DailyRunnerArguments(room_url=room_url, token=token, body=body)
|
|
585
|
+
result = {
|
|
586
|
+
"dailyRoom": room_url,
|
|
587
|
+
"dailyToken": token,
|
|
588
|
+
"sessionId": str(uuid.uuid4()),
|
|
589
|
+
}
|
|
590
|
+
else:
|
|
591
|
+
runner_args = RunnerArguments(body=body)
|
|
320
592
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
"""Launch a Daily bot and return connection info for RTVI clients.
|
|
593
|
+
# Start the bot in the background
|
|
594
|
+
asyncio.create_task(bot_module.bot(runner_args))
|
|
324
595
|
|
|
325
|
-
|
|
326
|
-
Use /start instead. This endpoint will be removed in a future version.
|
|
327
|
-
"""
|
|
328
|
-
logger.warning(
|
|
329
|
-
"DEPRECATED: /connect endpoint is deprecated. Please use /start instead. "
|
|
330
|
-
"This endpoint will be removed in a future version."
|
|
331
|
-
)
|
|
332
|
-
return await _handle_rtvi_request(request)
|
|
596
|
+
return result
|
|
333
597
|
|
|
334
598
|
|
|
335
|
-
def _setup_telephony_routes(app: FastAPI, transport_type: str, proxy: str):
|
|
599
|
+
def _setup_telephony_routes(app: FastAPI, *, transport_type: str, proxy: str):
|
|
336
600
|
"""Set up telephony-specific routes."""
|
|
337
601
|
# XML response templates (Exotel doesn't use XML webhooks)
|
|
338
602
|
XML_TEMPLATES = {
|
|
@@ -388,8 +652,6 @@ def _setup_telephony_routes(app: FastAPI, transport_type: str, proxy: str):
|
|
|
388
652
|
async def _run_daily_direct():
|
|
389
653
|
"""Run Daily bot with direct connection (no FastAPI server)."""
|
|
390
654
|
try:
|
|
391
|
-
import aiohttp
|
|
392
|
-
|
|
393
655
|
from pipecat.runner.daily import configure
|
|
394
656
|
except ImportError as e:
|
|
395
657
|
logger.error("Daily transport dependencies not installed.")
|
|
@@ -435,6 +697,21 @@ def _validate_and_clean_proxy(proxy: str) -> str:
|
|
|
435
697
|
return proxy
|
|
436
698
|
|
|
437
699
|
|
|
700
|
+
def runner_downloads_folder() -> Optional[str]:
|
|
701
|
+
"""Returns the folder where files are stored for later download."""
|
|
702
|
+
return RUNNER_DOWNLOADS_FOLDER
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def runner_host() -> str:
|
|
706
|
+
"""Returns the host name of this runner."""
|
|
707
|
+
return RUNNER_HOST
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def runner_port() -> int:
|
|
711
|
+
"""Returns the port of this runner."""
|
|
712
|
+
return RUNNER_PORT
|
|
713
|
+
|
|
714
|
+
|
|
438
715
|
def main():
|
|
439
716
|
"""Start the Pipecat development runner.
|
|
440
717
|
|
|
@@ -455,14 +732,16 @@ def main():
|
|
|
455
732
|
|
|
456
733
|
The bot file must contain a `bot(runner_args)` function as the entry point.
|
|
457
734
|
"""
|
|
735
|
+
global RUNNER_DOWNLOADS_FOLDER, RUNNER_HOST, RUNNER_PORT
|
|
736
|
+
|
|
458
737
|
parser = argparse.ArgumentParser(description="Pipecat Development Runner")
|
|
459
|
-
parser.add_argument("--host", type=str, default=
|
|
460
|
-
parser.add_argument("--port", type=int, default=
|
|
738
|
+
parser.add_argument("--host", type=str, default=RUNNER_HOST, help="Host address")
|
|
739
|
+
parser.add_argument("--port", type=int, default=RUNNER_PORT, help="Port number")
|
|
461
740
|
parser.add_argument(
|
|
462
741
|
"-t",
|
|
463
742
|
"--transport",
|
|
464
743
|
type=str,
|
|
465
|
-
choices=["daily", "webrtc",
|
|
744
|
+
choices=["daily", "webrtc", *TELEPHONY_TRANSPORTS],
|
|
466
745
|
default="webrtc",
|
|
467
746
|
help="Transport type",
|
|
468
747
|
)
|
|
@@ -480,9 +759,16 @@ def main():
|
|
|
480
759
|
default=False,
|
|
481
760
|
help="Connect directly to Daily room (automatically sets transport to daily)",
|
|
482
761
|
)
|
|
762
|
+
parser.add_argument("-f", "--folder", type=str, help="Path to downloads folder")
|
|
483
763
|
parser.add_argument(
|
|
484
764
|
"--verbose", "-v", action="count", default=0, help="Increase logging verbosity"
|
|
485
765
|
)
|
|
766
|
+
parser.add_argument(
|
|
767
|
+
"--whatsapp",
|
|
768
|
+
action="store_true",
|
|
769
|
+
default=False,
|
|
770
|
+
help="Ensure requried WhatsApp environment variables are present",
|
|
771
|
+
)
|
|
486
772
|
|
|
487
773
|
args = parser.parse_args()
|
|
488
774
|
|
|
@@ -502,6 +788,10 @@ def main():
|
|
|
502
788
|
logger.error("For ESP32, you need to specify `--host IP` so we can do SDP munging.")
|
|
503
789
|
return
|
|
504
790
|
|
|
791
|
+
if args.transport in TELEPHONY_TRANSPORTS and not args.proxy:
|
|
792
|
+
logger.error(f"For telephony transports, you need to specify `--proxy PROXY`.")
|
|
793
|
+
return
|
|
794
|
+
|
|
505
795
|
# Log level
|
|
506
796
|
logger.remove()
|
|
507
797
|
logger.add(sys.stderr, level="TRACE" if args.verbose else "DEBUG")
|
|
@@ -521,10 +811,11 @@ def main():
|
|
|
521
811
|
print()
|
|
522
812
|
if args.esp32:
|
|
523
813
|
print(f"🚀 Bot ready! (ESP32 mode)")
|
|
524
|
-
|
|
814
|
+
elif args.whatsapp:
|
|
815
|
+
print(f"🚀 Bot ready! (WhatsApp)")
|
|
525
816
|
else:
|
|
526
817
|
print(f"🚀 Bot ready!")
|
|
527
|
-
|
|
818
|
+
print(f" → Open http://{args.host}:{args.port}/client in your browser")
|
|
528
819
|
print()
|
|
529
820
|
elif args.transport == "daily":
|
|
530
821
|
print()
|
|
@@ -532,8 +823,19 @@ def main():
|
|
|
532
823
|
print(f" → Open http://{args.host}:{args.port} in your browser to start a session")
|
|
533
824
|
print()
|
|
534
825
|
|
|
826
|
+
RUNNER_DOWNLOADS_FOLDER = args.folder
|
|
827
|
+
RUNNER_HOST = args.host
|
|
828
|
+
RUNNER_PORT = args.port
|
|
829
|
+
|
|
535
830
|
# Create the app with transport-specific setup
|
|
536
|
-
app = _create_server_app(
|
|
831
|
+
app = _create_server_app(
|
|
832
|
+
transport_type=args.transport,
|
|
833
|
+
host=args.host,
|
|
834
|
+
proxy=args.proxy,
|
|
835
|
+
esp32_mode=args.esp32,
|
|
836
|
+
whatsapp_enabled=args.whatsapp,
|
|
837
|
+
folder=args.folder,
|
|
838
|
+
)
|
|
537
839
|
|
|
538
840
|
# Run the server
|
|
539
841
|
uvicorn.run(app, host=args.host, port=args.port)
|
pipecat/runner/types.py
CHANGED
|
@@ -20,9 +20,11 @@ from fastapi import WebSocket
|
|
|
20
20
|
class RunnerArguments:
|
|
21
21
|
"""Base class for runner session arguments."""
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
# Use kw_only so subclasses don't need to worry about ordering.
|
|
24
|
+
handle_sigint: bool = field(init=False, kw_only=True)
|
|
25
|
+
handle_sigterm: bool = field(init=False, kw_only=True)
|
|
26
|
+
pipeline_idle_timeout_secs: int = field(init=False, kw_only=True)
|
|
27
|
+
body: Optional[Any] = field(default_factory=dict, kw_only=True)
|
|
26
28
|
|
|
27
29
|
def __post_init__(self):
|
|
28
30
|
self.handle_sigint = False
|
|
@@ -42,7 +44,6 @@ class DailyRunnerArguments(RunnerArguments):
|
|
|
42
44
|
|
|
43
45
|
room_url: str
|
|
44
46
|
token: Optional[str] = None
|
|
45
|
-
body: Optional[Any] = field(default_factory=dict)
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
@dataclass
|
|
@@ -51,6 +52,7 @@ class WebSocketRunnerArguments(RunnerArguments):
|
|
|
51
52
|
|
|
52
53
|
Parameters:
|
|
53
54
|
websocket: WebSocket connection for audio streaming
|
|
55
|
+
body: Additional request data
|
|
54
56
|
"""
|
|
55
57
|
|
|
56
58
|
websocket: WebSocket
|