1use serde::{Deserialize, Serialize};
4use super::{ResponseAudio, DeltaAudio};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ChatResponse {
10 pub id: String,
12 pub choices: Vec<Choice>,
14 pub created: i64,
16 pub model: String,
18 pub object: String,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub usage: Option<Usage>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Choice {
28 pub finish_reason: FinishReason,
30 pub index: u32,
32 pub message: ResponseMessage,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38#[serde(rename_all = "snake_case")]
39pub enum FinishReason {
40 Stop,
42 Length,
44 ToolCalls,
46 ContentFilter,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ResponseMessage {
53 pub content: String,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub reasoning_content: Option<String>,
58 pub role: Role,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub tool_calls: Option<Vec<ToolCall>>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub annotations: Option<Vec<Annotation>>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub error_message: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub audio: Option<ResponseAudio>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct Annotation {
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub logo_url: Option<String>,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub publish_time: Option<String>,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub site_name: Option<String>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub summary: Option<String>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub title: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 #[serde(rename = "type")]
95 pub annotation_type: Option<String>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub url: Option<String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct Usage {
104 pub completion_tokens: u32,
106 pub prompt_tokens: u32,
108 pub total_tokens: u32,
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub completion_tokens_details: Option<CompletionTokensDetails>,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub prompt_tokens_details: Option<PromptTokensDetails>,
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub web_search_usage: Option<WebSearchUsage>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct CompletionTokensDetails {
124 pub reasoning_tokens: u32,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct PromptTokensDetails {
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub cached_tokens: Option<u32>,
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub audio_tokens: Option<u32>,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub image_tokens: Option<u32>,
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub video_tokens: Option<u32>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct WebSearchUsage {
148 pub tool_usage: u32,
150 pub page_usage: u32,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct StreamChunk {
157 pub id: String,
159 pub choices: Vec<StreamChoice>,
161 pub created: i64,
163 pub model: String,
165 pub object: String,
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub usage: Option<Usage>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct StreamChoice {
175 pub delta: DeltaMessage,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub finish_reason: Option<FinishReason>,
180 pub index: u32,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct DeltaMessage {
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub content: Option<String>,
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub reasoning_content: Option<String>,
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub role: Option<Role>,
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub tool_calls: Option<Vec<DeltaToolCall>>,
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub annotations: Option<Vec<Annotation>>,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub error_message: Option<String>,
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub audio: Option<DeltaAudio>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct DeltaToolCall {
213 pub index: u32,
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub id: Option<String>,
218 #[serde(skip_serializing_if = "Option::is_none")]
220 #[serde(rename = "type")]
221 pub tool_type: Option<ToolCallType>,
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub function: Option<DeltaFunctionCall>,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct DeltaFunctionCall {
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub name: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub arguments: Option<String>,
236}
237
238use super::message::{ToolCall, ToolCallType};
240use super::Role;
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_chat_response_deserialization() {
248 let json = r#"{
249 "id": "test-id",
250 "choices": [{
251 "finish_reason": "stop",
252 "index": 0,
253 "message": {
254 "content": "Hello!",
255 "role": "assistant"
256 }
257 }],
258 "created": 1234567890,
259 "model": "mimo-v2-flash",
260 "object": "chat.completion"
261 }"#;
262
263 let response: ChatResponse = serde_json::from_str(json).unwrap();
264 assert_eq!(response.id, "test-id");
265 assert_eq!(response.choices.len(), 1);
266 assert_eq!(response.choices[0].message.content, "Hello!");
267 }
268
269 #[test]
270 fn test_finish_reason_deserialization() {
271 assert_eq!(
272 serde_json::from_str::<FinishReason>(r#""stop""#).unwrap(),
273 FinishReason::Stop
274 );
275 assert_eq!(
276 serde_json::from_str::<FinishReason>(r#""tool_calls""#).unwrap(),
277 FinishReason::ToolCalls
278 );
279 }
280
281 #[test]
282 fn test_usage() {
283 let json = r#"{
284 "completion_tokens": 100,
285 "prompt_tokens": 50,
286 "total_tokens": 150
287 }"#;
288
289 let usage: Usage = serde_json::from_str(json).unwrap();
290 assert_eq!(usage.completion_tokens, 100);
291 assert_eq!(usage.prompt_tokens, 50);
292 assert_eq!(usage.total_tokens, 150);
293 }
294
295 #[test]
296 fn test_stream_chunk_deserialization() {
297 let json = r#"{
298 "id": "chunk-id",
299 "choices": [{
300 "delta": {
301 "content": "Hello"
302 },
303 "index": 0
304 }],
305 "created": 1234567890,
306 "model": "mimo-v2-flash",
307 "object": "chat.completion.chunk"
308 }"#;
309
310 let chunk: StreamChunk = serde_json::from_str(json).unwrap();
311 assert_eq!(chunk.id, "chunk-id");
312 assert_eq!(chunk.choices[0].delta.content, Some("Hello".to_string()));
313 }
314
315 #[test]
316 fn test_response_with_thinking() {
317 let json = r#"{
318 "id": "test-id",
319 "choices": [{
320 "finish_reason": "stop",
321 "index": 0,
322 "message": {
323 "content": "The answer is 42.",
324 "reasoning_content": "Let me think about this...",
325 "role": "assistant"
326 }
327 }],
328 "created": 1234567890,
329 "model": "mimo-v2-pro",
330 "object": "chat.completion"
331 }"#;
332
333 let response: ChatResponse = serde_json::from_str(json).unwrap();
334 assert_eq!(
335 response.choices[0].message.reasoning_content,
336 Some("Let me think about this...".to_string())
337 );
338 }
339}