theater 0.3.5

A WebAssembly actor system for AI agents
Documentation
package theater:simple;

/// # WebSocket Server Interface
///
/// The `websocket-server` interface provides capabilities for handling WebSocket connections,
/// receiving messages, and sending responses. It allows actors to implement real-time
/// bidirectional communication with web clients.
///
/// ## Purpose
///
/// This interface enables actors to build WebSocket-based applications such as chat servers,
/// real-time dashboards, collaborative tools, and streaming data services. It handles the
/// WebSocket protocol details and exposes a simple event-driven API for actors to process
/// messages and maintain connections.
///
/// ## Example
///
/// ```wit
/// // Using the websocket-server interface in a WIT definition
/// use theater:simple/websocket-server;
///
/// // Implementing the interface in Rust
/// impl websocket_server::Guest for MyActor {
///   fn handle_message(state: State, params: (websocket_server::WebsocketMessage,)) 
///     -> Result<(State, (websocket_server::WebsocketResponse,)), String> {
///     // Process the message and update state
///     // Return updated state and response
///   }
/// }
/// ```
///
/// ## Security
///
/// WebSocket connections can be long-lived and consume server resources, so implementations
/// should consider rate limiting, connection timeouts, and payload size limits. Validate
/// all incoming data and be cautious about trusting client-provided information.
///
/// ## Implementation Notes
///
/// The actor must implement the `handle_message` function, which is called for every
/// WebSocket event (connection, message, disconnection). The actor maintains its state
/// across these invocations, allowing it to track connected clients and conversation history.

/// Interface for handling WebSocket connections and messages
interface websocket-server {
    /// The type of WebSocket message/event
    ///
    /// ## Purpose
    ///
    /// This enum represents the different types of WebSocket events that can occur,
    /// including text and binary messages, connection events, and protocol control messages.
    /// It allows actors to differentiate between different kinds of events and handle them
    /// appropriately.
    ///
    /// ## Example
    ///
    /// ```rust
    /// // In Rust actor code
    /// match message.ty {
    ///     MessageType::Text => { /* Handle text message */ },
    ///     MessageType::Binary => { /* Handle binary message */ },
    ///     MessageType::Connect => { /* Handle new connection */ },
    ///     MessageType::Close => { /* Handle connection closed */ },
    ///     // Handle other message types
    /// }
    /// ```
    variant message-type {
        /// A text message
        text,
        /// A binary message
        binary,
        /// A new connection was established
        connect,
        /// The connection was closed
        close,
        /// A ping message (for keep-alive)
        ping,
        /// A pong message (response to ping)
        pong,
        /// Any other message type
        other(string),
    }

    /// Represents a message sent or received over a WebSocket connection
    ///
    /// ## Purpose
    ///
    /// This record encapsulates the data for a WebSocket message or event, including its
    /// type and payload (which can be either text or binary). It's the primary data structure
    /// used for communication in the WebSocket system.
    ///
    /// ## Example
    ///
    /// ```rust
    /// // In Rust actor code
    /// if message.ty == MessageType::Text {
    ///     if let Some(text) = &message.text {
    ///         println!("Received text message: {}", text);
    ///     }
    /// } else if message.ty == MessageType::Binary {
    ///     if let Some(data) = &message.data {
    ///         println!("Received binary message of {} bytes", data.len());
    ///     }
    /// }
    /// ```
    ///
    /// ## Implementation Notes
    ///
    /// For text messages, the `text` field will be populated and the `data` field will be None.
    /// For binary messages, the `data` field will be populated and the `text` field will be None.
    /// For control messages (connect, close, ping, pong), both fields may be None.
    record websocket-message {
        /// The type of the message
        ty: message-type,
        /// Binary data payload (used for binary messages)
        data: option<list<u8>>,
        /// Text payload (used for text messages)
        text: option<string>,
    }

    /// Response containing messages to send back over the WebSocket
    ///
    /// ## Purpose
    ///
    /// This record contains the messages that the actor wants to send back to connected
    /// WebSocket clients. It allows actors to respond to incoming messages or send
    /// unsolicited messages to connected clients.
    ///
    /// ## Example
    ///
    /// ```rust
    /// // In Rust actor code
    /// let response = WebsocketResponse {
    ///     messages: vec![
    ///         WebsocketMessage {
    ///             ty: MessageType::Text,
    ///             data: None,
    ///             text: Some("Hello, client!".to_string()),
    ///         },
    ///     ],
    /// };
    /// ```
    ///
    /// ## Implementation Notes
    ///
    /// The response can contain multiple messages, which will be sent to the client in order.
    /// An empty list means no messages will be sent back to the client.
    record websocket-response {
        /// List of messages to send back to the client
        messages: list<websocket-message>,
    }

    /// Called for each event on the WebSocket (connections, messages, disconnections)
    ///
    /// ## Purpose
    ///
    /// This function is the core of the WebSocket interface. It's called by the Theater runtime
    /// whenever a WebSocket event occurs (new connection, incoming message, disconnection). The
    /// actor implements this function to handle the events, update its state, and respond to clients.
    ///
    /// ## Parameters
    ///
    /// * `state` - The current state of the actor
    /// * `params` - A tuple containing the WebSocket message that triggered this call
    ///
    /// ## Returns
    ///
    /// * `Ok(tuple<state, tuple<websocket-response>>)` - The updated state and response to send
    /// * `Err(string)` - An error message if processing fails
    ///
    /// ## Example
    ///
    /// ```rust
    /// // In Rust actor code
    /// fn handle_message(
    ///     state: State,
    ///     params: (WebsocketMessage,)
    /// ) -> Result<(State, (WebsocketResponse,)), String> {
    ///     let message = &params.0;
    ///     
    ///     // Handle a text message
    ///     if message.ty == MessageType::Text {
    ///         if let Some(text) = &message.text {
    ///             // Echo the message back to the client
    ///             let response = WebsocketResponse {
    ///                 messages: vec![
    ///                     WebsocketMessage {
    ///                         ty: MessageType::Text,
    ///                         data: None,
    ///                         text: Some(format!("You said: {}", text)),
    ///                     },
    ///                 ],
    ///             };
    ///             
    ///             return Ok((state, (response,)));
    ///         }
    ///     }
    ///     
    ///     // Handle a new connection
    ///     if message.ty == MessageType::Connect {
    ///         // Send a welcome message
    ///         let response = WebsocketResponse {
    ///             messages: vec![
    ///                 WebsocketMessage {
    ///                     ty: MessageType::Text,
    ///                     data: None,
    ///                     text: Some("Welcome to the WebSocket server!".to_string()),
    ///                 },
    ///             ],
    ///         };
    ///         
    ///         return Ok((state, (response,)));
    ///     }
    ///     
    ///     // Return empty response for other message types
    ///     Ok((state, (WebsocketResponse { messages: vec![] },)))
    /// }
    /// ```
    ///
    /// ## Security
    ///
    /// Validate all incoming messages and don't trust client-provided data. Consider size limits
    /// for message payloads and rate limiting to prevent abuse.
    ///
    /// ## Implementation Notes
    ///
    /// The function is called once for each WebSocket event. For efficient handling of multiple
    /// connections, the actor should store connection IDs or other identifying information in its
    /// state to track individual clients.
    handle-message: func(state: option<list<u8>>, params: tuple<websocket-message>) -> result<tuple<option<list<u8>>, tuple<websocket-response>>, string>;
}