nylas-types 0.1.1

Type definitions for Nylas API v3
Documentation
//! Smart Compose (AI-powered message generation) types for the Nylas API v3.
//!
//! Requires Nylas Plus package.

use serde::{Deserialize, Serialize};

use crate::MessageId;

/// Request for Smart Compose AI message generation.
///
/// # Example
///
/// ```
/// # use nylas_types::{SmartComposeRequest, MessageId};
/// // New message
/// let request = SmartComposeRequest::new(
///     "Write a professional email asking for a meeting next week"
/// );
///
/// // Reply to existing message
/// let reply = SmartComposeRequest::reply_to(
///     "Write a polite decline",
///     MessageId::new("msg_123")
/// ).with_temperature(0.7);
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmartComposeRequest {
    /// Prompt for message generation (max 1000 tokens).
    pub prompt: String,

    /// Message ID to reply to (optional).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message_id: Option<MessageId>,

    /// Temperature for generation (0.0 - 1.0).
    /// Lower values are more deterministic, higher values are more creative.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub temperature: Option<f32>,
}

impl SmartComposeRequest {
    /// Create a new Smart Compose request for generating a new message.
    pub fn new(prompt: impl Into<String>) -> Self {
        Self {
            prompt: prompt.into(),
            message_id: None,
            temperature: None,
        }
    }

    /// Create a Smart Compose request for replying to an existing message.
    pub fn reply_to(prompt: impl Into<String>, message_id: MessageId) -> Self {
        Self {
            prompt: prompt.into(),
            message_id: Some(message_id),
            temperature: None,
        }
    }

    /// Set the temperature for generation (0.0 - 1.0).
    pub fn with_temperature(mut self, temperature: f32) -> Self {
        self.temperature = Some(temperature);
        self
    }
}

/// Response from Smart Compose API (REST mode).
///
/// # Example
///
/// ```
/// # use nylas_types::SmartComposeResponse;
/// # use serde_json::json;
/// let json = json!({
///     "suggestion": "Dear John,\n\nI hope this email finds you well..."
/// });
///
/// let response: SmartComposeResponse = serde_json::from_value(json).unwrap();
/// assert!(response.suggestion.contains("Dear John"));
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmartComposeResponse {
    /// AI-generated message content.
    pub suggestion: String,
}

/// Smart Compose streaming event (SSE mode).
///
/// When using Server-Sent Events streaming, the API sends multiple events:
/// - `content` events: contain partial message generation
/// - `done` events: signal completion
/// - `error` events: contain error information
///
/// # Example
///
/// ```
/// # use nylas_types::SmartComposeStreamEvent;
/// # use serde_json::json;
/// let event_json = json!({
///     "type": "content",
///     "content": "Dear "
/// });
///
/// let event: SmartComposeStreamEvent = serde_json::from_value(event_json).unwrap();
/// assert_eq!(event.event_type, "content");
/// assert_eq!(event.content, Some("Dear ".to_string()));
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmartComposeStreamEvent {
    /// Event type: "content", "done", or "error".
    #[serde(rename = "type")]
    pub event_type: String,

    /// Content delta (for "content" events).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,

    /// Error message (for "error" events).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_smart_compose_request_new() {
        let request = SmartComposeRequest::new("Write a professional email");

        assert_eq!(request.prompt, "Write a professional email");
        assert_eq!(request.message_id, None);
        assert_eq!(request.temperature, None);
    }

    #[test]
    fn test_smart_compose_request_reply_to() {
        let message_id = MessageId::new("msg_123");
        let request = SmartComposeRequest::reply_to("Write a polite decline", message_id.clone());

        assert_eq!(request.prompt, "Write a polite decline");
        assert_eq!(request.message_id, Some(message_id));
        assert_eq!(request.temperature, None);
    }

    #[test]
    fn test_smart_compose_request_with_temperature() {
        let request = SmartComposeRequest::new("Generate email").with_temperature(0.7);

        assert_eq!(request.temperature, Some(0.7));
    }

    #[test]
    fn test_smart_compose_request_serialization() {
        let request = SmartComposeRequest::new("Test prompt").with_temperature(0.5);

        let json = serde_json::to_string(&request).unwrap();
        assert!(json.contains("Test prompt"));
        assert!(json.contains("0.5"));

        let deserialized: SmartComposeRequest = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.prompt, "Test prompt");
        assert_eq!(deserialized.temperature, Some(0.5));
    }

    #[test]
    fn test_smart_compose_response_deserialization() {
        let json = r#"{"suggestion":"Dear John,\n\nI hope this finds you well."}"#;
        let response: SmartComposeResponse = serde_json::from_str(json).unwrap();

        assert!(response.suggestion.contains("Dear John"));
    }

    #[test]
    fn test_smart_compose_stream_event_content() {
        let json = r#"{"type":"content","content":"Hello "}"#;
        let event: SmartComposeStreamEvent = serde_json::from_str(json).unwrap();

        assert_eq!(event.event_type, "content");
        assert_eq!(event.content, Some("Hello ".to_string()));
        assert_eq!(event.error, None);
    }

    #[test]
    fn test_smart_compose_stream_event_done() {
        let json = r#"{"type":"done"}"#;
        let event: SmartComposeStreamEvent = serde_json::from_str(json).unwrap();

        assert_eq!(event.event_type, "done");
        assert_eq!(event.content, None);
        assert_eq!(event.error, None);
    }

    #[test]
    fn test_smart_compose_stream_event_error() {
        let json = r#"{"type":"error","error":"Rate limit exceeded"}"#;
        let event: SmartComposeStreamEvent = serde_json::from_str(json).unwrap();

        assert_eq!(event.event_type, "error");
        assert_eq!(event.error, Some("Rate limit exceeded".to_string()));
        assert_eq!(event.content, None);
    }
}