""" OFP Client Handles sending and receiving Open Floor Protocol envelopes via HTTPS """ import requests import logging import json from typing import Dict, Optional from .models import Envelope, DialogEvent logger = logging.getLogger(__name__) class OFPClient: """Client for sending OFP envelopes to conveners and other assistants""" def __init__(self, speaker_uri: str, service_url: str, manifest: Dict): self.speaker_uri = speaker_uri self.service_url = service_url self.manifest = manifest logger.info(f"OFP Client initialized for {speaker_uri}") def send_envelope(self, recipient_url: str, envelope: Envelope, timeout: int = 10) -> bool: """Send OFP envelope to recipient via HTTPS POST""" try: payload = envelope.to_payload() logger.debug(f"Sending envelope to {recipient_url}: {json.dumps(payload, indent=2)}") response = requests.post( recipient_url, json=payload, headers={ 'Content-Type': 'application/json', 'User-Agent': 'OFP-BadWord-Sentinel/1.0' }, timeout=timeout ) response.raise_for_status() logger.info(f"✓ Envelope sent successfully to {recipient_url}") return True except requests.exceptions.Timeout: logger.error(f"✗ Timeout sending envelope to {recipient_url}") return False except requests.exceptions.RequestException as e: logger.error(f"✗ Failed to send envelope to {recipient_url}: {e}") return False except Exception as e: logger.error(f"✗ Unexpected error sending envelope: {e}") return False def send_private_alert( self, convener_uri: str, convener_url: str, conversation_id: str, alert_data: Dict ) -> bool: """Send private alert to convener about profanity detection""" try: # Create alert text as JSON alert_text = json.dumps(alert_data, indent=2) # Create dialog event for the alert alert_event = DialogEvent.create_text_event( speaker_uri=self.speaker_uri, text=alert_text ) # Create envelope with private utterance event envelope = Envelope( schema={"version": "1.0.0"}, conversation={"id": conversation_id}, sender={"speakerUri": self.speaker_uri}, events=[{ "eventType": "utterance", "to": { "speakerUri": convener_uri, "private": True # CRITICAL: Only convener sees this }, "parameters": { "dialogEvent": alert_event.to_dict() } }] ) logger.info(f"Sending private alert to convener: {convener_uri}") return self.send_envelope(convener_url, envelope) except Exception as e: logger.error(f"Error creating private alert: {e}") return False def send_public_message( self, conversation_id: str, recipient_url: str, text: str ) -> bool: """Send public message to the floor (visible to all participants)""" try: dialog_event = DialogEvent.create_text_event( speaker_uri=self.speaker_uri, text=text ) envelope = Envelope( schema={"version": "1.0.0"}, conversation={"id": conversation_id}, sender={"speakerUri": self.speaker_uri}, events=[{ "eventType": "utterance", "parameters": { "dialogEvent": dialog_event.to_dict() } }] ) return self.send_envelope(recipient_url, envelope) except Exception as e: logger.error(f"Error sending public message: {e}") return False def request_floor( self, conversation_id: str, convener_url: str, convener_uri: str ) -> bool: """Request speaking floor from convener""" envelope = Envelope( schema={"version": "1.0.0"}, conversation={"id": conversation_id}, sender={"speakerUri": self.speaker_uri}, events=[{ "eventType": "floorRequest", "to": { "speakerUri": convener_uri } }] ) return self.send_envelope(convener_url, envelope) def get_manifest(self) -> Dict: """Return assistant manifest""" return self.manifest