async_claude/messages/
response.rs

1use serde::{Deserialize, Serialize};
2
3use super::{ResponseContentBlock, Role, StopReason, Usage};
4
5#[derive(Debug, Deserialize, Default, Clone, PartialEq, Serialize)]
6pub struct Response {
7    pub id: String,
8    pub r#type: String,
9    pub role: Role,
10    pub content: Vec<ResponseContentBlock>,
11    pub model: String,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub stop_reason: Option<StopReason>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub stop_sequence: Option<String>,
16    pub usage: Usage,
17}
18
19#[cfg(test)]
20mod tests {
21    use crate::messages::{
22        BaseContentBlock, RedactedThinkingContentBlock, Role, ToolUseContentBlock,
23    };
24
25    use super::*;
26    #[test]
27    fn serde() {
28        let tests = vec![
29            (
30                "simple",
31                r#"{
32                "content": [
33                  {
34                    "text": "Hi! My name is Claude.",
35                    "type": "text"
36                  }
37                ],
38                "id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
39                "model": "claude-3-opus-20240229",
40                "role": "assistant",
41                "stop_reason": "end_turn",
42                "stop_sequence": null,
43                "type": "message",
44                "usage": {
45                  "input_tokens": 10,
46                  "output_tokens": 25
47                }
48              }"#,
49                Response {
50                    id: "msg_013Zva2CMHLNnXjNJJKqJ2EF".to_string(),
51                    model: "claude-3-opus-20240229".to_string(),
52                    content: vec![
53                        ResponseContentBlock::Base(BaseContentBlock::Text {
54                            text: "Hi! My name is Claude.".to_string(),
55                        }),
56                    ],
57                    role: Role::Assistant,
58                    stop_reason: Some(StopReason::EndTurn),
59                    stop_sequence: None,
60                    r#type: "message".to_string(),
61                    usage: Usage {
62                        input_tokens: Some(10),
63                        output_tokens: 25,
64                    },
65                },
66            ),
67            (
68                "tool_use",
69                r#"{
70                    "id": "msg_01Aq9w938a90dw8q",
71                    "type": "message",
72                    "model": "claude-3-opus-20240229",
73                    "stop_reason": "tool_use",
74                    "role": "assistant",
75                    "stop_sequence": null,
76                    "content": [
77                      {
78                        "type": "text",
79                        "text": "<thinking>I need to call the get_weather function, and the user wants SF, which is likely San Francisco, CA.</thinking>"
80                      },
81                      {
82                        "type": "tool_use",
83                        "id": "toolu_01A09q90qw90lq917835lq9",
84                        "name": "get_weather",
85                        "input": {"location": "San Francisco, CA", "unit": "celsius"}
86                      }
87                    ],
88                    "usage": {
89                        "input_tokens": 527,
90                        "output_tokens": 137
91                    }
92                  }"#,
93                Response {
94                    id: "msg_01Aq9w938a90dw8q".to_string(),
95                    model: "claude-3-opus-20240229".to_string(),
96                    content: vec![
97                        ResponseContentBlock::Base(BaseContentBlock::Text {
98                            text: "<thinking>I need to call the get_weather function, and the user wants SF, which is likely San Francisco, CA.</thinking>".to_string(),
99                        }),
100                        ResponseContentBlock::Base(BaseContentBlock::ToolUse(ToolUseContentBlock {
101                            id: "toolu_01A09q90qw90lq917835lq9".to_string(),
102                            name: "get_weather".to_string(),
103                            input: serde_json::json!({
104                                "location": "San Francisco, CA",
105                                "unit": "celsius"
106                            }),
107                        })),
108                    ],
109                    role: Role::Assistant,
110                    stop_reason: Some(StopReason::ToolUse),
111                    stop_sequence: None,
112                    r#type: "message".to_string(),
113                    usage: Usage {
114                        input_tokens: Some(527),
115                        output_tokens: 137,
116                    },
117                },
118            ),
119            (
120                "thinking_block",
121                r#"{
122                  "id": "msg_01Bq9w938a90dw8r",
123                  "type": "message",
124                  "model": "claude-3-opus-20240229",
125                  "stop_reason": "end_turn",
126                  "role": "assistant",
127                  "stop_sequence": null,
128                  "content": [
129                    {
130                      "type": "thinking",
131                      "thinking": "Let me analyze this step by step...",
132                      "signature": "WaUjzkypQ2mUEVM36O2TxuC06KN8xyfbJwyem2dw3URve/op91XWHOEBLLqIOMfFG/UvLEczmEsUjavL...."
133                    },
134                    {
135                      "type": "redacted_thinking",
136                      "data": "EmwKAhgBEgy3va3pzix/LafPsn4aDFIT2Xlxh0L5L8rLVyIwxtE3rAFBa8cr3qpP..."
137                    },
138                    {
139                      "type": "text",
140                      "text": "Based on my analysis..."
141                    }
142                  ],
143                  "usage": {
144                    "input_tokens": 320,
145                    "output_tokens": 150
146                  }
147                }"#,
148                Response {
149                    id: "msg_01Bq9w938a90dw8r".to_string(),
150                    model: "claude-3-opus-20240229".to_string(),
151                    content: vec![
152                        ResponseContentBlock::Base(BaseContentBlock::Thinking {
153                            thinking: "Let me analyze this step by step...".to_string(),
154                            signature: Some("WaUjzkypQ2mUEVM36O2TxuC06KN8xyfbJwyem2dw3URve/op91XWHOEBLLqIOMfFG/UvLEczmEsUjavL....".to_string()),
155                        }),
156                        ResponseContentBlock::RedactedThinking(RedactedThinkingContentBlock::RedactedThinking {
157                            data: "EmwKAhgBEgy3va3pzix/LafPsn4aDFIT2Xlxh0L5L8rLVyIwxtE3rAFBa8cr3qpP...".to_string(),
158                        }),
159                        ResponseContentBlock::Base(BaseContentBlock::Text {
160                            text: "Based on my analysis...".to_string(),
161                        }),
162                    ],
163                    role: Role::Assistant,
164                    stop_reason: Some(StopReason::EndTurn),
165                    stop_sequence: None,
166                    r#type: "message".to_string(),
167                    usage: Usage {
168                        input_tokens: Some(320),
169                        output_tokens: 150,
170                    },
171                },
172            ),
173        ];
174        for (name, json, expected) in tests {
175            //test deserialize
176            let actual: Response = serde_json::from_str(json).unwrap();
177            assert_eq!(actual, expected, "deserialize test failed: {}", name);
178            //test serialize
179            let serialized = serde_json::to_string(&expected).unwrap();
180            let actual: Response = serde_json::from_str(&serialized).unwrap();
181            assert_eq!(actual, expected, "serialize test failed: {}", name);
182        }
183    }
184}