"""Small, explicit validation helpers for the supported Realtime event subset.""" from __future__ import annotations from dataclasses import dataclass from typing import Any from uuid import uuid4 SUPPORTED_CLIENT_EVENTS = { "session.update", "conversation.item.create", "conversation.item.truncate", "input_audio_buffer.append", "input_audio_buffer.commit", "input_audio_buffer.clear", "output_audio_buffer.clear", "response.create", "response.cancel", "x.interactive_media.capabilities.update", "x.interactive_media.session.variables.update", } SUPPORTED_CAPABILITIES = { "dynamic_variables", "workflow_events", "handoff", "video_track", } @dataclass class RealtimeEventError(ValueError): message: str code: str = "invalid_request_error" param: str | None = None event_id: str | None = None def __str__(self) -> str: return self.message def require_event(message: object) -> dict[str, Any]: if not isinstance(message, dict): raise RealtimeEventError("Event must be a JSON object") event_type = str(message.get("type") or "") if event_type not in SUPPORTED_CLIENT_EVENTS: raise RealtimeEventError( f"Unsupported client event: {event_type or ''}", code="invalid_event", param="type", event_id=str(message.get("event_id") or "") or None, ) return message def assistant_id_from_model(model: object) -> str: value = str(model or "").strip() if not value.startswith("assistant:") or not value.removeprefix("assistant:"): raise RealtimeEventError( "model must use assistant:asst_xxx", code="invalid_model", param="session.model", ) return value.removeprefix("assistant:") def normalize_turn_detection(value: object) -> dict[str, Any] | None: if value is None: return None if not isinstance(value, dict) or value.get("type") != "server_vad": raise RealtimeEventError( "turn_detection must be server_vad or null", param="session.audio.input.turn_detection", ) try: threshold = float(value.get("threshold", 0.7)) prefix_ms = int(value.get("prefix_padding_ms", 200)) silence_ms = int(value.get("silence_duration_ms", 600)) except (TypeError, ValueError) as exc: raise RealtimeEventError( "Invalid server_vad threshold, padding, or silence duration", param="session.audio.input.turn_detection", ) from exc if ( not 0 <= threshold <= 1 or not 0 <= prefix_ms <= 5000 or not 100 <= silence_ms <= 10000 ): raise RealtimeEventError( "Invalid server_vad threshold, padding, or silence duration", param="session.audio.input.turn_detection", ) return { "type": "server_vad", "threshold": threshold, "prefix_padding_ms": prefix_ms, "silence_duration_ms": silence_ms, "create_response": bool(value.get("create_response", True)), "interrupt_response": bool(value.get("interrupt_response", True)), } def error_event(error: RealtimeEventError) -> dict[str, Any]: return { "type": "error", "event_id": f"event_{uuid4().hex}", "error": { "type": "invalid_request_error", "code": error.code, "message": error.message, "param": error.param, "event_id": error.event_id, }, }