1use crate::content::ContentBlock;
7use crate::types::{CacheUsage, StopReason, Usage};
8use chrono::Utc;
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct Message {
15 pub id: String,
17
18 #[serde(rename = "type")]
20 pub message_type: String,
21
22 pub role: MessageRole,
24
25 pub content: Vec<ContentBlock>,
27
28 pub model: String,
30
31 pub stop_reason: StopReason,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub stop_sequence: Option<String>,
37
38 pub created_at: String,
40
41 pub usage: Usage,
43
44 #[serde(default, skip_serializing_if = "is_zero_cache")]
46 pub cache_usage: CacheUsage,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
51pub struct UserMessage {
52 pub id: Option<String>,
54
55 #[serde(rename = "type", default = "default_user_type")]
57 pub message_type: String,
58
59 pub role: MessageRole,
61
62 pub content: Vec<ContentBlock>,
64
65 #[serde(default = "default_timestamp")]
67 pub created_at: String,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72pub struct AssistantMessage {
73 pub id: String,
75
76 #[serde(rename = "type", default = "default_assistant_type")]
78 pub message_type: String,
79
80 pub role: MessageRole,
82
83 pub content: Vec<ContentBlock>,
85
86 pub model: String,
88
89 pub stop_reason: StopReason,
91
92 #[serde(default = "default_timestamp")]
94 pub created_at: String,
95
96 pub usage: Usage,
98
99 #[serde(default, skip_serializing_if = "is_zero_cache")]
101 pub cache_usage: CacheUsage,
102
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub parent_tool_use_id: Option<String>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
111 pub error: Option<AssistantMessageError>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
116pub struct SystemMessage {
117 pub subtype: String,
119
120 pub data: serde_json::Value,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
126pub struct ResultMessage {
127 pub subtype: String,
129
130 pub duration_ms: u64,
132
133 pub duration_api_ms: u64,
135
136 pub is_error: bool,
138
139 pub num_turns: u32,
141
142 pub session_id: String,
144
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub total_cost_usd: Option<f64>,
148
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub usage: Option<serde_json::Value>,
152
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub result: Option<String>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
160pub struct StreamEvent {
161 pub uuid: String,
163
164 pub session_id: String,
166
167 pub event: serde_json::Value,
169
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub parent_tool_use_id: Option<String>,
173}
174
175#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
177#[serde(rename_all = "lowercase")]
178pub enum MessageRole {
179 User,
181
182 Assistant,
184}
185
186#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
191#[serde(rename_all = "snake_case")]
192pub enum AssistantMessageError {
193 AuthenticationFailed,
195
196 BillingError,
198
199 RateLimit,
201
202 InvalidRequest,
204
205 ServerError,
207
208 Unknown,
210}
211
212impl AssistantMessageError {
213 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 pub fn is_retryable(&self) -> bool {
227 matches!(self, Self::RateLimit | Self::ServerError)
228 }
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct MessageRequest {
234 pub model: String,
236
237 pub max_tokens: u32,
239
240 #[serde(skip_serializing_if = "Option::is_none")]
242 pub system: Option<String>,
243
244 pub messages: Vec<MessageParameter>,
246
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub tools: Option<Vec<serde_json::Value>>,
250
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub tool_choice: Option<serde_json::Value>,
254
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub stop_sequences: Option<Vec<String>>,
258
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub temperature: Option<f32>,
262
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub top_p: Option<f32>,
266
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub top_k: Option<u32>,
270
271 #[serde(skip_serializing_if = "Option::is_none")]
273 pub thinking: Option<serde_json::Value>,
274
275 #[serde(default, skip_serializing_if = "is_empty_metadata")]
277 pub metadata: serde_json::Map<String, serde_json::Value>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct MessageParameter {
283 pub role: MessageRole,
285
286 pub content: Vec<ContentBlock>,
288}
289
290impl Message {
291 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 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 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 pub fn used_tools(&self) -> bool {
326 self.stop_reason == StopReason::ToolUse
327 }
328
329 pub fn is_complete(&self) -> bool {
331 self.stop_reason == StopReason::EndTurn
332 }
333}
334
335impl UserMessage {
336 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 pub fn text(text: impl Into<String>) -> Self {
349 Self::new(vec![ContentBlock::text(text)])
350 }
351}
352
353impl AssistantMessage {
354 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 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 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 pub fn has_error(&self) -> bool {
391 self.error.is_some()
392 }
393
394 pub fn is_retryable(&self) -> bool {
396 self.error.is_some_and(|e| e.is_retryable())
397 }
398}
399
400impl SystemMessage {
401 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 #[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 pub fn with_cost(mut self, cost: f64) -> Self {
436 self.total_cost_usd = Some(cost);
437 self
438 }
439
440 pub fn with_usage(mut self, usage: serde_json::Value) -> Self {
442 self.usage = Some(usage);
443 self
444 }
445
446 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 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 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
475fn 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 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}