async_claude/messages/
response.rs

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