"""Run a media worker and persist its actual terminal outcome.""" import asyncio from pipecat.frames.frames import CancelFrame, EndFrame, ErrorFrame from pipecat.pipeline.worker import PipelineWorker from pipecat.workers.runner import WorkerRunner from services.conversation_history import ConversationRecorder async def run_media_worker( worker: PipelineWorker, recorder: ConversationRecorder | None, ) -> None: # A runner can return normally after a fatal ErrorFrame or a failed worker. # Only an observed, expected terminal frame counts as normal completion. status = "failed" fatal_error = False @worker.event_handler("on_pipeline_error") async def on_pipeline_error(_worker, frame: ErrorFrame): nonlocal status, fatal_error if frame.fatal: fatal_error = True status = "failed" @worker.event_handler("on_pipeline_finished") async def on_pipeline_finished(_worker, frame): nonlocal status normal_end = isinstance(frame, EndFrame) or ( isinstance(frame, CancelFrame) and frame.reason in {"client_disconnected", "idle timeout"} ) if normal_end and not fatal_error: status = "completed" runner = WorkerRunner(handle_sigint=False) can_finalize = True try: await runner.add_workers(worker) await runner.run() # Pipecat's runner consumes cancellation while draining its workers. # Preserve the owning server task's cancellation after that cleanup. owner = asyncio.current_task() if owner is not None and owner.cancelling(): raise asyncio.CancelledError except GeneratorExit: can_finalize = False # Synchronously closed coroutines cannot await. raise except BaseException: status = "failed" raise finally: if recorder and can_finalize: await recorder.finish(status=status)