Skip to main content

nylas_types/
smart_compose.rs

1//! Smart Compose (AI-powered message generation) types for the Nylas API v3.
2//!
3//! Requires Nylas Plus package.
4
5use serde::{Deserialize, Serialize};
6
7use crate::MessageId;
8
9/// Request for Smart Compose AI message generation.
10///
11/// # Example
12///
13/// ```
14/// # use nylas_types::{SmartComposeRequest, MessageId};
15/// // New message
16/// let request = SmartComposeRequest::new(
17///     "Write a professional email asking for a meeting next week"
18/// );
19///
20/// // Reply to existing message
21/// let reply = SmartComposeRequest::reply_to(
22///     "Write a polite decline",
23///     MessageId::new("msg_123")
24/// ).with_temperature(0.7);
25/// ```
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub struct SmartComposeRequest {
28    /// Prompt for message generation (max 1000 tokens).
29    pub prompt: String,
30
31    /// Message ID to reply to (optional).
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub message_id: Option<MessageId>,
34
35    /// Temperature for generation (0.0 - 1.0).
36    /// Lower values are more deterministic, higher values are more creative.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub temperature: Option<f32>,
39}
40
41impl SmartComposeRequest {
42    /// Create a new Smart Compose request for generating a new message.
43    pub fn new(prompt: impl Into<String>) -> Self {
44        Self {
45            prompt: prompt.into(),
46            message_id: None,
47            temperature: None,
48        }
49    }
50
51    /// Create a Smart Compose request for replying to an existing message.
52    pub fn reply_to(prompt: impl Into<String>, message_id: MessageId) -> Self {
53        Self {
54            prompt: prompt.into(),
55            message_id: Some(message_id),
56            temperature: None,
57        }
58    }
59
60    /// Set the temperature for generation (0.0 - 1.0).
61    pub fn with_temperature(mut self, temperature: f32) -> Self {
62        self.temperature = Some(temperature);
63        self
64    }
65}
66
67/// Response from Smart Compose API (REST mode).
68///
69/// # Example
70///
71/// ```
72/// # use nylas_types::SmartComposeResponse;
73/// # use serde_json::json;
74/// let json = json!({
75///     "suggestion": "Dear John,\n\nI hope this email finds you well..."
76/// });
77///
78/// let response: SmartComposeResponse = serde_json::from_value(json).unwrap();
79/// assert!(response.suggestion.contains("Dear John"));
80/// ```
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82pub struct SmartComposeResponse {
83    /// AI-generated message content.
84    pub suggestion: String,
85}
86
87/// Smart Compose streaming event (SSE mode).
88///
89/// When using Server-Sent Events streaming, the API sends multiple events:
90/// - `content` events: contain partial message generation
91/// - `done` events: signal completion
92/// - `error` events: contain error information
93///
94/// # Example
95///
96/// ```
97/// # use nylas_types::SmartComposeStreamEvent;
98/// # use serde_json::json;
99/// let event_json = json!({
100///     "type": "content",
101///     "content": "Dear "
102/// });
103///
104/// let event: SmartComposeStreamEvent = serde_json::from_value(event_json).unwrap();
105/// assert_eq!(event.event_type, "content");
106/// assert_eq!(event.content, Some("Dear ".to_string()));
107/// ```
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109pub struct SmartComposeStreamEvent {
110    /// Event type: "content", "done", or "error".
111    #[serde(rename = "type")]
112    pub event_type: String,
113
114    /// Content delta (for "content" events).
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub content: Option<String>,
117
118    /// Error message (for "error" events).
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub error: Option<String>,
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_smart_compose_request_new() {
129        let request = SmartComposeRequest::new("Write a professional email");
130
131        assert_eq!(request.prompt, "Write a professional email");
132        assert_eq!(request.message_id, None);
133        assert_eq!(request.temperature, None);
134    }
135
136    #[test]
137    fn test_smart_compose_request_reply_to() {
138        let message_id = MessageId::new("msg_123");
139        let request = SmartComposeRequest::reply_to("Write a polite decline", message_id.clone());
140
141        assert_eq!(request.prompt, "Write a polite decline");
142        assert_eq!(request.message_id, Some(message_id));
143        assert_eq!(request.temperature, None);
144    }
145
146    #[test]
147    fn test_smart_compose_request_with_temperature() {
148        let request = SmartComposeRequest::new("Generate email").with_temperature(0.7);
149
150        assert_eq!(request.temperature, Some(0.7));
151    }
152
153    #[test]
154    fn test_smart_compose_request_serialization() {
155        let request = SmartComposeRequest::new("Test prompt").with_temperature(0.5);
156
157        let json = serde_json::to_string(&request).unwrap();
158        assert!(json.contains("Test prompt"));
159        assert!(json.contains("0.5"));
160
161        let deserialized: SmartComposeRequest = serde_json::from_str(&json).unwrap();
162        assert_eq!(deserialized.prompt, "Test prompt");
163        assert_eq!(deserialized.temperature, Some(0.5));
164    }
165
166    #[test]
167    fn test_smart_compose_response_deserialization() {
168        let json = r#"{"suggestion":"Dear John,\n\nI hope this finds you well."}"#;
169        let response: SmartComposeResponse = serde_json::from_str(json).unwrap();
170
171        assert!(response.suggestion.contains("Dear John"));
172    }
173
174    #[test]
175    fn test_smart_compose_stream_event_content() {
176        let json = r#"{"type":"content","content":"Hello "}"#;
177        let event: SmartComposeStreamEvent = serde_json::from_str(json).unwrap();
178
179        assert_eq!(event.event_type, "content");
180        assert_eq!(event.content, Some("Hello ".to_string()));
181        assert_eq!(event.error, None);
182    }
183
184    #[test]
185    fn test_smart_compose_stream_event_done() {
186        let json = r#"{"type":"done"}"#;
187        let event: SmartComposeStreamEvent = serde_json::from_str(json).unwrap();
188
189        assert_eq!(event.event_type, "done");
190        assert_eq!(event.content, None);
191        assert_eq!(event.error, None);
192    }
193
194    #[test]
195    fn test_smart_compose_stream_event_error() {
196        let json = r#"{"type":"error","error":"Rate limit exceeded"}"#;
197        let event: SmartComposeStreamEvent = serde_json::from_str(json).unwrap();
198
199        assert_eq!(event.event_type, "error");
200        assert_eq!(event.error, Some("Rate limit exceeded".to_string()));
201        assert_eq!(event.content, None);
202    }
203}