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 let actual: Response = serde_json::from_str(json).unwrap();
177 assert_eq!(actual, expected, "deserialize test failed: {}", name);
178 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}