"""Client-visible Workflow output and fixed speech in one place.""" from __future__ import annotations from typing import Any from uuid import uuid4 from pipecat.frames.frames import OutputTransportMessageUrgentFrame from pipecat.utils.time import time_now_iso8601 from services.fixed_speech import FixedSpeechOutput class WorkflowOutput(FixedSpeechOutput): """Publish debug events and fixed speech without duplicating persistence.""" async def emit_node_active(self, node_id: str | None) -> None: if node_id: await self.emit({"type": "node-active", "nodeId": node_id}) async def emit_trace( self, event: str, *, revision: str, transition_id: int, **details: Any, ) -> None: """Publish one ordered, machine-readable Workflow runtime event.""" await self.emit( { "type": "workflow-event", "eventId": f"wfe_{uuid4().hex[:20]}", "event": event, "timestamp": time_now_iso8601(), "sessionId": self._runtime.session_id, "workflowRevision": revision, "transitionId": transition_id, **details, } ) async def emit_variables( self, *, reason: str, node_id: str | None, changed: list[str] | None = None, ) -> None: message: dict[str, Any] = { "type": "workflow-variables", "reason": reason, "variables": self.public_variables(), } if node_id: message["nodeId"] = node_id if changed: message["changed"] = [ name for name in changed if not name.startswith(("system__", "secret__")) ] await self.emit(message) async def emit_error( self, message: str, *, node_id: str | None, code: str = "workflow_runtime_error", ) -> None: payload: dict[str, Any] = { "type": "workflow-error", "code": code, "message": message, } if node_id: payload["nodeId"] = node_id await self.emit(payload) def public_variables(self) -> dict[str, str | int | float | bool]: return { name: value for name, value in self._store.values.items() if not name.startswith(("system__", "secret__")) and isinstance(value, (str, int, float, bool)) } async def emit(self, message: dict[str, Any]) -> None: await self._runtime.queue_frame( OutputTransportMessageUrgentFrame(message=message) )