1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use fmt::Display;
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::client::ChatSession;
use crate::error::ApiError;

/// Represents a message in the conversation.
///
/// A `Message` struct contains the role of the sender (either "user" or "assistant")
/// and the content of the message.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Message {
    /// The role of the sender, either "user" or "assistant".
    pub role: String,
    /// The content of the message.
    pub content: String,
}

/// Represents the request body sent to the Anthropic API.
///
/// The `RequestBody` struct contains the model name, the list of messages,
/// the maximum number of tokens to generate, and the temperature value.
#[derive(Serialize, Deserialize, Debug)]
pub struct RequestBody {
    /// The name of the model to use for generating the response.
    pub model: String,
    /// The list of messages in the conversation.
    pub messages: Vec<Message>,
    /// The maximum number of tokens to generate in the response.
    pub max_tokens: u32,
    /// The temperature value to control the randomness of the generated response.
    pub temperature: f32,
    /// A system prompt is a way of providing context and instructions to Claude, such as 
    /// specifying a particular goal or role. 
    /// https://docs.anthropic.com/claude/docs/system-prompts
    pub system: String,
}

/// Represents a block of content in the API response.
///
/// A `ContentBlock` struct contains the text content and the type of the block.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ContentBlock {
    /// The text content of the block.
    pub text: String,
    /// The type of the content block.
    ///
    /// The `#[serde(rename = "type")]` attribute is used to map the `type` field
    /// in the JSON response to the `block_type` field in the struct.
    #[serde(rename = "type")]
    pub block_type: String,
}

/// Represents the response message received from the Anthropic API.
///
/// The `ResponseMessage` struct contains the ID of the response, the role of the sender,
/// and the list of content blocks in the response.
#[derive(Serialize, Deserialize, Debug)]
pub struct ResponseMessage {
    /// The ID of the response message.
    pub id: String,
    /// The role of the sender, either "user" or "assistant".
    pub role: String,
    /// The list of content blocks in the response.
    pub content: Vec<ContentBlock>,
}

impl ResponseMessage {
    /// Returns the text content of the first content block in the response message.
    ///
    /// This method retrieves the first content block from the `content` vector of the `ResponseMessage`
    /// and returns its text content as a `String`. If the `content` vector is empty, an empty string
    /// is returned.
    ///
    /// # Examples
    ///
    /// ```
    /// use llm_api_adapter::models::ResponseMessage;
    ///
    /// let response_message = ResponseMessage {
    ///     id: "123".to_string(),
    ///     role: "assistant".to_string(),
    ///     content: vec![
    ///         ContentBlock {
    ///             text: "Hello, how can I assist you today?".to_string(),
    ///             block_type: "text".to_string(),
    ///         },
    ///         ContentBlock {
    ///             text: "Let me know if you have any questions!".to_string(),
    ///             block_type: "text".to_string(),
    ///         },
    ///     ],
    /// };
    ///
    /// let first_message = response_message.first_message();
    /// assert_eq!(first_message, "Hello, how can I assist you today?");
    /// ```
    ///
    /// # Returns
    ///
    /// A `String` containing the text content of the first content block in the response message.
    /// If the `content` vector is empty, an empty string is returned.
    pub fn first_message(&self) -> String {
        let content = self.content.first();
        match content {
            None => {
                "".to_string()
            }
            Some(content) => {
                content.text.to_string()
            }
        }
    }
}

/// Implement Display trait for ResponseMessage
impl Display for ResponseMessage {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "ResponseMessage {{ id: {}, role: {}, content: {:?} }}", self.id, self.role, self.content)
    }
}

/// Represents the response from a chat session.
///
/// The lifetime parameter `'a` indicates that the `ChatResponse` borrows data from a `ChatSession`
/// instance and can only live as long as the `ChatSession` instance.
pub struct ChatResponse<'a> {
    pub(crate) session: ChatSession<'a>,
    pub(crate) last_response: String,
}

impl<'a> ChatResponse<'a> {
    /// Returns the last response from the chat session.
    pub fn last_response(&self) -> &str {
        &self.last_response
    }

    /// Returns the entire conversation history of the chat session.
    pub fn dialog(&self) -> &[Message] {
        &self.session.messages
    }

    /// Sends a new user message to continue the conversation and returns the updated `ChatResponse`.
    ///
    /// # Arguments
    ///
    /// * `message` - The user message to send.
    ///
    /// # Returns
    ///
    /// An updated `ChatResponse` instance containing the last response and the updated chat session.
    pub async fn add(self, message: &str) -> Result<ChatResponse<'a>, ApiError> {
        self.session.send(message).await
    }
}