94 lines
2.7 KiB
Python
94 lines
2.7 KiB
Python
"""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)
|
|
)
|