turboclaude_protocol/
message.rs

1//! Message types for the protocol
2//!
3//! Defines the message structures used in both REST API and Agent protocol.
4//! Matches the Anthropic Messages API format.
5
6use crate::content::ContentBlock;
7use crate::types::{CacheUsage, StopReason, Usage};
8use chrono::Utc;
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12/// A message in a conversation
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct Message {
15    /// Unique identifier for the message
16    pub id: String,
17
18    /// Type of message (always "message")
19    #[serde(rename = "type")]
20    pub message_type: String,
21
22    /// The role that produced the message
23    pub role: MessageRole,
24
25    /// The content blocks in the message
26    pub content: Vec<ContentBlock>,
27
28    /// The model used to generate this message
29    pub model: String,
30
31    /// Why the model stopped generating
32    pub stop_reason: StopReason,
33
34    /// Additional stop sequences if any
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub stop_sequence: Option<String>,
37
38    /// When the message was created
39    pub created_at: String,
40
41    /// Token usage information
42    pub usage: Usage,
43
44    /// Cache usage information
45    #[serde(default, skip_serializing_if = "is_zero_cache")]
46    pub cache_usage: CacheUsage,
47}
48
49/// A user message in a conversation
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
51pub struct UserMessage {
52    /// Unique identifier for the message
53    pub id: Option<String>,
54
55    /// Type of message (always "message")
56    #[serde(rename = "type", default = "default_user_type")]
57    pub message_type: String,
58
59    /// The role (always "user")
60    pub role: MessageRole,
61
62    /// The content blocks in the message
63    pub content: Vec<ContentBlock>,
64
65    /// When the message was created
66    #[serde(default = "default_timestamp")]
67    pub created_at: String,
68}
69
70/// An assistant message in a conversation
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72pub struct AssistantMessage {
73    /// Unique identifier for the message
74    pub id: String,
75
76    /// Type of message (always "message")
77    #[serde(rename = "type", default = "default_assistant_type")]
78    pub message_type: String,
79
80    /// The role (always "assistant")
81    pub role: MessageRole,
82
83    /// The content blocks in the message
84    pub content: Vec<ContentBlock>,
85
86    /// The model used to generate this message
87    pub model: String,
88
89    /// Why the model stopped generating
90    pub stop_reason: StopReason,
91
92    /// When the message was created
93    #[serde(default = "default_timestamp")]
94    pub created_at: String,
95
96    /// Token usage information
97    pub usage: Usage,
98
99    /// Cache usage information
100    #[serde(default, skip_serializing_if = "is_zero_cache")]
101    pub cache_usage: CacheUsage,
102
103    /// Parent tool use ID for messages in tool execution context
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub parent_tool_use_id: Option<String>,
106
107    /// Error type if the message generation failed.
108    ///
109    /// When this is set, the message may be incomplete or contain error information.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub error: Option<AssistantMessageError>,
112}
113
114/// A system message from the Claude CLI
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
116pub struct SystemMessage {
117    /// Subtype of the system message
118    pub subtype: String,
119
120    /// Raw data from the system message
121    pub data: serde_json::Value,
122}
123
124/// A result message indicating query completion
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
126pub struct ResultMessage {
127    /// Subtype of the result message
128    pub subtype: String,
129
130    /// Duration in milliseconds
131    pub duration_ms: u64,
132
133    /// API duration in milliseconds
134    pub duration_api_ms: u64,
135
136    /// Whether the result is an error
137    pub is_error: bool,
138
139    /// Number of turns in the conversation
140    pub num_turns: u32,
141
142    /// Session identifier
143    pub session_id: String,
144
145    /// Total cost in USD (if available)
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub total_cost_usd: Option<f64>,
148
149    /// Token usage information
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub usage: Option<serde_json::Value>,
152
153    /// Result data
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub result: Option<String>,
156}
157
158/// A stream event for partial message updates during streaming
159#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
160pub struct StreamEvent {
161    /// Unique identifier for the event
162    pub uuid: String,
163
164    /// Session identifier
165    pub session_id: String,
166
167    /// The raw Anthropic API stream event
168    pub event: serde_json::Value,
169
170    /// Parent tool use ID (if applicable)
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub parent_tool_use_id: Option<String>,
173}
174
175/// Message role
176#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
177#[serde(rename_all = "lowercase")]
178pub enum MessageRole {
179    /// User message
180    User,
181
182    /// Assistant message
183    Assistant,
184}
185
186/// Error type for assistant messages when an error occurred during generation.
187///
188/// Indicates the category of error that prevented successful message completion.
189/// This enum matches the error types from the Claude Agent SDK.
190#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
191#[serde(rename_all = "snake_case")]
192pub enum AssistantMessageError {
193    /// Authentication failed (invalid API key, etc.)
194    AuthenticationFailed,
195
196    /// Billing error (exceeded quota, payment issue, etc.)
197    BillingError,
198
199    /// Rate limit exceeded
200    RateLimit,
201
202    /// Invalid request parameters
203    InvalidRequest,
204
205    /// Server-side error from the API
206    ServerError,
207
208    /// Unknown or unclassified error
209    Unknown,
210}
211
212impl AssistantMessageError {
213    /// Returns a human-readable description of the error type.
214    pub fn description(&self) -> &'static str {
215        match self {
216            Self::AuthenticationFailed => "Authentication failed - check your API key",
217            Self::BillingError => "Billing error - check your account quota",
218            Self::RateLimit => "Rate limit exceeded - slow down requests",
219            Self::InvalidRequest => "Invalid request parameters",
220            Self::ServerError => "Server error from the API",
221            Self::Unknown => "Unknown error occurred",
222        }
223    }
224
225    /// Returns whether this error is retryable.
226    pub fn is_retryable(&self) -> bool {
227        matches!(self, Self::RateLimit | Self::ServerError)
228    }
229}
230
231/// Request to create a message
232#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct MessageRequest {
234    /// The model to use
235    pub model: String,
236
237    /// Maximum tokens to generate
238    pub max_tokens: u32,
239
240    /// System prompt (optional)
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub system: Option<String>,
243
244    /// Messages in the conversation
245    pub messages: Vec<MessageParameter>,
246
247    /// Tools available to the model (optional)
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub tools: Option<Vec<serde_json::Value>>,
250
251    /// Tool choice (optional)
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub tool_choice: Option<serde_json::Value>,
254
255    /// Stop sequences (optional)
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub stop_sequences: Option<Vec<String>>,
258
259    /// Temperature for sampling (optional)
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub temperature: Option<f32>,
262
263    /// Top P for nucleus sampling (optional)
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub top_p: Option<f32>,
266
267    /// Top K for sampling (optional)
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub top_k: Option<u32>,
270
271    /// Whether to use extended thinking
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub thinking: Option<serde_json::Value>,
274
275    /// Metadata for the request
276    #[serde(default, skip_serializing_if = "is_empty_metadata")]
277    pub metadata: serde_json::Map<String, serde_json::Value>,
278}
279
280/// A message parameter (user or assistant message)
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct MessageParameter {
283    /// Message role
284    pub role: MessageRole,
285
286    /// Message content
287    pub content: Vec<ContentBlock>,
288}
289
290impl Message {
291    /// Create a new message
292    pub fn new(model: impl Into<String>, role: MessageRole, content: Vec<ContentBlock>) -> Self {
293        Self {
294            id: format!("msg_{}", Uuid::new_v4()),
295            message_type: "message".to_string(),
296            role,
297            content,
298            model: model.into(),
299            stop_reason: StopReason::EndTurn,
300            stop_sequence: None,
301            created_at: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
302            usage: Usage::new(0, 0),
303            cache_usage: CacheUsage::default(),
304        }
305    }
306
307    /// Get all text content from the message
308    pub fn get_text_content(&self) -> String {
309        self.content
310            .iter()
311            .filter_map(|block| block.as_text())
312            .collect::<Vec<_>>()
313            .join("\n")
314    }
315
316    /// Get all tool uses from the message
317    pub fn get_tool_uses(&self) -> Vec<(&str, &str, &serde_json::Value)> {
318        self.content
319            .iter()
320            .filter_map(|block| block.as_tool_use())
321            .collect()
322    }
323
324    /// Check if message ended due to tool use
325    pub fn used_tools(&self) -> bool {
326        self.stop_reason == StopReason::ToolUse
327    }
328
329    /// Check if message is complete
330    pub fn is_complete(&self) -> bool {
331        self.stop_reason == StopReason::EndTurn
332    }
333}
334
335impl UserMessage {
336    /// Create a new user message
337    pub fn new(content: Vec<ContentBlock>) -> Self {
338        Self {
339            id: Some(format!("msg_{}", Uuid::new_v4())),
340            message_type: "message".to_string(),
341            role: MessageRole::User,
342            content,
343            created_at: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
344        }
345    }
346
347    /// Create a user message from text
348    pub fn text(text: impl Into<String>) -> Self {
349        Self::new(vec![ContentBlock::text(text)])
350    }
351}
352
353impl AssistantMessage {
354    /// Create a new assistant message
355    pub fn new(model: impl Into<String>, content: Vec<ContentBlock>, usage: Usage) -> Self {
356        Self {
357            id: format!("msg_{}", Uuid::new_v4()),
358            message_type: "message".to_string(),
359            role: MessageRole::Assistant,
360            content,
361            model: model.into(),
362            stop_reason: StopReason::EndTurn,
363            created_at: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
364            usage,
365            cache_usage: CacheUsage::default(),
366            parent_tool_use_id: None,
367            error: None,
368        }
369    }
370
371    /// Create an error message with the specified error type.
372    pub fn with_error(
373        model: impl Into<String>,
374        content: Vec<ContentBlock>,
375        usage: Usage,
376        error: AssistantMessageError,
377    ) -> Self {
378        let mut msg = Self::new(model, content, usage);
379        msg.error = Some(error);
380        msg
381    }
382
383    /// Set the parent tool use ID.
384    pub fn set_parent_tool_use_id(mut self, id: impl Into<String>) -> Self {
385        self.parent_tool_use_id = Some(id.into());
386        self
387    }
388
389    /// Check if this message has an error.
390    pub fn has_error(&self) -> bool {
391        self.error.is_some()
392    }
393
394    /// Check if this message's error is retryable.
395    pub fn is_retryable(&self) -> bool {
396        self.error.is_some_and(|e| e.is_retryable())
397    }
398}
399
400impl SystemMessage {
401    /// Create a new system message
402    pub fn new(subtype: impl Into<String>, data: serde_json::Value) -> Self {
403        Self {
404            subtype: subtype.into(),
405            data,
406        }
407    }
408}
409
410impl ResultMessage {
411    /// Create a new result message
412    #[allow(clippy::too_many_arguments)]
413    pub fn new(
414        subtype: impl Into<String>,
415        duration_ms: u64,
416        duration_api_ms: u64,
417        is_error: bool,
418        num_turns: u32,
419        session_id: impl Into<String>,
420    ) -> Self {
421        Self {
422            subtype: subtype.into(),
423            duration_ms,
424            duration_api_ms,
425            is_error,
426            num_turns,
427            session_id: session_id.into(),
428            total_cost_usd: None,
429            usage: None,
430            result: None,
431        }
432    }
433
434    /// Set the total cost
435    pub fn with_cost(mut self, cost: f64) -> Self {
436        self.total_cost_usd = Some(cost);
437        self
438    }
439
440    /// Set the usage information
441    pub fn with_usage(mut self, usage: serde_json::Value) -> Self {
442        self.usage = Some(usage);
443        self
444    }
445
446    /// Set the result data
447    pub fn with_result(mut self, result: impl Into<String>) -> Self {
448        self.result = Some(result.into());
449        self
450    }
451}
452
453impl StreamEvent {
454    /// Create a new stream event
455    pub fn new(
456        uuid: impl Into<String>,
457        session_id: impl Into<String>,
458        event: serde_json::Value,
459    ) -> Self {
460        Self {
461            uuid: uuid.into(),
462            session_id: session_id.into(),
463            event,
464            parent_tool_use_id: None,
465        }
466    }
467
468    /// Set the parent tool use ID
469    pub fn with_parent_tool_use_id(mut self, parent_tool_use_id: impl Into<String>) -> Self {
470        self.parent_tool_use_id = Some(parent_tool_use_id.into());
471        self
472    }
473}
474
475// Helper functions for serde defaults
476fn default_user_type() -> String {
477    "message".to_string()
478}
479
480fn default_assistant_type() -> String {
481    "message".to_string()
482}
483
484fn default_timestamp() -> String {
485    Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
486}
487
488fn is_zero_cache(cache: &CacheUsage) -> bool {
489    cache.cache_creation_input_tokens == 0 && cache.cache_read_input_tokens == 0
490}
491
492fn is_empty_metadata(meta: &serde_json::Map<String, serde_json::Value>) -> bool {
493    meta.is_empty()
494}
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499
500    #[test]
501    fn test_user_message_creation() {
502        let msg = UserMessage::text("Hello");
503        assert_eq!(msg.role, MessageRole::User);
504        assert_eq!(msg.content.len(), 1);
505    }
506
507    #[test]
508    fn test_message_get_text() {
509        let msg = Message::new(
510            "claude-3-5-sonnet",
511            MessageRole::Assistant,
512            vec![ContentBlock::text("Hello world")],
513        );
514        assert_eq!(msg.get_text_content(), "Hello world");
515    }
516
517    #[test]
518    fn test_message_serialization() {
519        let msg = Message::new(
520            "claude-3-5-sonnet",
521            MessageRole::Assistant,
522            vec![ContentBlock::text("Test")],
523        );
524        let json = serde_json::to_string(&msg).unwrap();
525        let deserialized: Message = serde_json::from_str(&json).unwrap();
526        assert_eq!(msg, deserialized);
527    }
528
529    #[test]
530    fn test_assistant_message_error_serialization() {
531        // Test all variants serialize correctly
532        let errors = vec![
533            (AssistantMessageError::AuthenticationFailed, "\"authentication_failed\""),
534            (AssistantMessageError::BillingError, "\"billing_error\""),
535            (AssistantMessageError::RateLimit, "\"rate_limit\""),
536            (AssistantMessageError::InvalidRequest, "\"invalid_request\""),
537            (AssistantMessageError::ServerError, "\"server_error\""),
538            (AssistantMessageError::Unknown, "\"unknown\""),
539        ];
540
541        for (error, expected_json) in errors {
542            let json = serde_json::to_string(&error).unwrap();
543            assert_eq!(json, expected_json);
544
545            let deserialized: AssistantMessageError = serde_json::from_str(&json).unwrap();
546            assert_eq!(deserialized, error);
547        }
548    }
549
550    #[test]
551    fn test_assistant_message_error_retryable() {
552        assert!(!AssistantMessageError::AuthenticationFailed.is_retryable());
553        assert!(!AssistantMessageError::BillingError.is_retryable());
554        assert!(AssistantMessageError::RateLimit.is_retryable());
555        assert!(!AssistantMessageError::InvalidRequest.is_retryable());
556        assert!(AssistantMessageError::ServerError.is_retryable());
557        assert!(!AssistantMessageError::Unknown.is_retryable());
558    }
559
560    #[test]
561    fn test_assistant_message_with_error() {
562        let msg = AssistantMessage::with_error(
563            "claude-sonnet-4-5",
564            vec![ContentBlock::text("Error occurred")],
565            Usage::new(10, 5),
566            AssistantMessageError::RateLimit,
567        );
568
569        assert!(msg.has_error());
570        assert!(msg.is_retryable());
571        assert_eq!(msg.error, Some(AssistantMessageError::RateLimit));
572    }
573
574    #[test]
575    fn test_assistant_message_no_error() {
576        let msg = AssistantMessage::new(
577            "claude-sonnet-4-5",
578            vec![ContentBlock::text("Hello")],
579            Usage::new(10, 20),
580        );
581
582        assert!(!msg.has_error());
583        assert!(!msg.is_retryable());
584        assert_eq!(msg.error, None);
585    }
586
587    #[test]
588    fn test_assistant_message_error_description() {
589        assert!(!AssistantMessageError::AuthenticationFailed.description().is_empty());
590        assert!(!AssistantMessageError::RateLimit.description().is_empty());
591    }
592
593    #[test]
594    fn test_assistant_message_with_parent_tool_use() {
595        let msg = AssistantMessage::new(
596            "claude-sonnet-4-5",
597            vec![ContentBlock::text("Tool result")],
598            Usage::new(10, 20),
599        ).set_parent_tool_use_id("toolu_123");
600
601        assert_eq!(msg.parent_tool_use_id, Some("toolu_123".to_string()));
602    }
603}