diff --git a/CHANGELOG.md b/CHANGELOG.md index f13b3cecee..c405b4bc76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Runner: `body` property moved to `RunnerArguments` base class as all transports + should support arbitrary body data as part of a request. + - `CartesiaSTTService` now inherits from `WebsocketSTTService`. - Package upgrades: @@ -106,6 +109,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed an issue where the `SmallWebRTCRequest` dataclass in runner would scrub + arbitrary request data from client due to camelCase typing. This fixes data + passthrough for JS clients where `APIRequest` is used. + - Fixed an issue in `RivaSegmentedSTTService` where a runtime error occurred due to a mismatch in the _handle_transcription method's signature. diff --git a/src/pipecat/runner/run.py b/src/pipecat/runner/run.py index a90c96ac5b..27e2456bbb 100644 --- a/src/pipecat/runner/run.py +++ b/src/pipecat/runner/run.py @@ -260,7 +260,9 @@ async def offer(request: SmallWebRTCRequest, background_tasks: BackgroundTasks): # Prepare runner arguments with the callback to run your bot async def webrtc_connection_callback(connection): bot_module = _get_bot_module() - runner_args = SmallWebRTCRunnerArguments(webrtc_connection=connection) + runner_args = SmallWebRTCRunnerArguments( + webrtc_connection=connection, body=request.request_data + ) background_tasks.add_task(bot_module.bot, runner_args) # Delegate handling to SmallWebRTCRequestHandler diff --git a/src/pipecat/runner/types.py b/src/pipecat/runner/types.py index 89aecd84c9..87357d5822 100644 --- a/src/pipecat/runner/types.py +++ b/src/pipecat/runner/types.py @@ -24,10 +24,13 @@ class RunnerArguments: handle_sigterm: bool = field(init=False) pipeline_idle_timeout_secs: int = field(init=False) + body: Optional[Any] = field(default_factory=dict) + def __post_init__(self): self.handle_sigint = False self.handle_sigterm = False self.pipeline_idle_timeout_secs = 300 + self.body = self.body or {} @dataclass @@ -42,7 +45,6 @@ class DailyRunnerArguments(RunnerArguments): room_url: str token: Optional[str] = None - body: Optional[Any] = field(default_factory=dict) @dataclass @@ -55,7 +57,6 @@ class WebSocketRunnerArguments(RunnerArguments): """ websocket: WebSocket - body: Optional[Any] = field(default_factory=dict) @dataclass diff --git a/src/pipecat/services/aws/nova_sonic/llm.py b/src/pipecat/services/aws/nova_sonic/llm.py index 5df3bbd21f..e5e54100a5 100644 --- a/src/pipecat/services/aws/nova_sonic/llm.py +++ b/src/pipecat/services/aws/nova_sonic/llm.py @@ -627,7 +627,7 @@ async def _send_prompt_start_event(self, tools: List[Any]): else "" ) - prompt_start = f''' + prompt_start = f""" {{ "event": {{ "promptStart": {{ @@ -647,14 +647,14 @@ async def _send_prompt_start_event(self, tools: List[Any]): }} }} }} - ''' + """ await self._send_client_event(prompt_start) async def _send_audio_input_start_event(self): if not self._prompt_name: return - audio_content_start = f''' + audio_content_start = f""" {{ "event": {{ "contentStart": {{ @@ -674,7 +674,7 @@ async def _send_audio_input_start_event(self): }} }} }} - ''' + """ await self._send_client_event(audio_content_start) async def _send_text_event(self, text: str, role: Role): @@ -683,7 +683,7 @@ async def _send_text_event(self, text: str, role: Role): content_name = str(uuid.uuid4()) - text_content_start = f''' + text_content_start = f""" {{ "event": {{ "contentStart": {{ @@ -698,11 +698,11 @@ async def _send_text_event(self, text: str, role: Role): }} }} }} - ''' + """ await self._send_client_event(text_content_start) escaped_text = json.dumps(text) # includes quotes - text_input = f''' + text_input = f""" {{ "event": {{ "textInput": {{ @@ -712,10 +712,10 @@ async def _send_text_event(self, text: str, role: Role): }} }} }} - ''' + """ await self._send_client_event(text_input) - text_content_end = f''' + text_content_end = f""" {{ "event": {{ "contentEnd": {{ @@ -724,7 +724,7 @@ async def _send_text_event(self, text: str, role: Role): }} }} }} - ''' + """ await self._send_client_event(text_content_end) async def _send_user_audio_event(self, audio: bytes): @@ -732,7 +732,7 @@ async def _send_user_audio_event(self, audio: bytes): return blob = base64.b64encode(audio) - audio_event = f''' + audio_event = f""" {{ "event": {{ "audioInput": {{ @@ -742,14 +742,14 @@ async def _send_user_audio_event(self, audio: bytes): }} }} }} - ''' + """ await self._send_client_event(audio_event) async def _send_session_end_events(self): if not self._stream or not self._prompt_name: return - prompt_end = f''' + prompt_end = f""" {{ "event": {{ "promptEnd": {{ @@ -757,7 +757,7 @@ async def _send_session_end_events(self): }} }} }} - ''' + """ await self._send_client_event(prompt_end) session_end = """ @@ -775,7 +775,7 @@ async def _send_tool_result(self, tool_call_id, result): content_name = str(uuid.uuid4()) - result_content_start = f''' + result_content_start = f""" {{ "event": {{ "contentStart": {{ @@ -794,7 +794,7 @@ async def _send_tool_result(self, tool_call_id, result): }} }} }} - ''' + """ await self._send_client_event(result_content_start) result_content = json.dumps( diff --git a/src/pipecat/transports/smallwebrtc/request_handler.py b/src/pipecat/transports/smallwebrtc/request_handler.py index b2c02a03e6..d6ab3841df 100644 --- a/src/pipecat/transports/smallwebrtc/request_handler.py +++ b/src/pipecat/transports/smallwebrtc/request_handler.py @@ -39,6 +39,13 @@ class SmallWebRTCRequest: restart_pc: Optional[bool] = None request_data: Optional[Any] = None + @classmethod + def from_dict(cls, data: dict): + """Accept both snake_case and camelCase for the request_data field.""" + if "requestData" in data and "request_data" not in data: + data["request_data"] = data.pop("requestData") + return cls(**data) + @dataclass class IceCandidate: