Skip to main content

mimo_api/types/
response.rs

1//! Response types for the MiMo API.
2
3use serde::{Deserialize, Serialize};
4// Import audio types from the audio module
5use super::{ResponseAudio, DeltaAudio};
6
7/// Chat completion response.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ChatResponse {
10    /// Unique identifier for the response
11    pub id: String,
12    /// List of completion choices
13    pub choices: Vec<Choice>,
14    /// Unix timestamp of creation
15    pub created: i64,
16    /// Model used for generation
17    pub model: String,
18    /// Object type (always "chat.completion")
19    pub object: String,
20    /// Token usage information
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub usage: Option<Usage>,
23}
24
25/// A completion choice.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Choice {
28    /// The reason the model stopped generating
29    pub finish_reason: FinishReason,
30    /// The index of this choice
31    pub index: u32,
32    /// The generated message
33    pub message: ResponseMessage,
34}
35
36/// Reason for finishing generation.
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38#[serde(rename_all = "snake_case")]
39pub enum FinishReason {
40    /// Natural stop or stop sequence
41    Stop,
42    /// Max tokens reached
43    Length,
44    /// Tool was called
45    ToolCalls,
46    /// Content filtered
47    ContentFilter,
48}
49
50/// Response message.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ResponseMessage {
53    /// Message content
54    pub content: String,
55    /// Reasoning content (for thinking mode)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub reasoning_content: Option<String>,
58    /// Message role
59    pub role: Role,
60    /// Tool calls
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub tool_calls: Option<Vec<ToolCall>>,
63    /// Annotations (from web search)
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub annotations: Option<Vec<Annotation>>,
66    /// Error message (from web search)
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub error_message: Option<String>,
69    /// Audio data (for TTS)
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub audio: Option<ResponseAudio>,
72}
73
74/// Web search annotation.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct Annotation {
77    /// Logo URL
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub logo_url: Option<String>,
80    /// Publish time
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub publish_time: Option<String>,
83    /// Site name
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub site_name: Option<String>,
86    /// Summary
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub summary: Option<String>,
89    /// Title
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub title: Option<String>,
92    /// Annotation type
93    #[serde(skip_serializing_if = "Option::is_none")]
94    #[serde(rename = "type")]
95    pub annotation_type: Option<String>,
96    /// URL
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub url: Option<String>,
99}
100
101/// Token usage information.
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct Usage {
104    /// Tokens used in the completion
105    pub completion_tokens: u32,
106    /// Tokens used in the prompt
107    pub prompt_tokens: u32,
108    /// Total tokens used
109    pub total_tokens: u32,
110    /// Completion token details
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub completion_tokens_details: Option<CompletionTokensDetails>,
113    /// Prompt token details
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub prompt_tokens_details: Option<PromptTokensDetails>,
116    /// Web search usage
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub web_search_usage: Option<WebSearchUsage>,
119}
120
121/// Completion tokens details.
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct CompletionTokensDetails {
124    /// Tokens used for reasoning
125    pub reasoning_tokens: u32,
126}
127
128/// Prompt tokens details.
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct PromptTokensDetails {
131    /// Cached tokens
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub cached_tokens: Option<u32>,
134    /// Audio tokens
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub audio_tokens: Option<u32>,
137    /// Image tokens
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub image_tokens: Option<u32>,
140    /// Video tokens
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub video_tokens: Option<u32>,
143}
144
145/// Web search usage.
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct WebSearchUsage {
148    /// Number of API calls
149    pub tool_usage: u32,
150    /// Number of pages returned
151    pub page_usage: u32,
152}
153
154/// Streaming response chunk.
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct StreamChunk {
157    /// Unique identifier
158    pub id: String,
159    /// List of choices
160    pub choices: Vec<StreamChoice>,
161    /// Unix timestamp
162    pub created: i64,
163    /// Model used
164    pub model: String,
165    /// Object type (always "chat.completion.chunk")
166    pub object: String,
167    /// Token usage (only in final chunk)
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub usage: Option<Usage>,
170}
171
172/// A streaming choice.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct StreamChoice {
175    /// Delta content
176    pub delta: DeltaMessage,
177    /// Reason for finishing (only in final chunk)
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub finish_reason: Option<FinishReason>,
180    /// Choice index
181    pub index: u32,
182}
183
184/// Delta message in a streaming response.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct DeltaMessage {
187    /// Message content
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub content: Option<String>,
190    /// Reasoning content
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub reasoning_content: Option<String>,
193    /// Message role
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub role: Option<Role>,
196    /// Tool calls
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub tool_calls: Option<Vec<DeltaToolCall>>,
199    /// Annotations
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub annotations: Option<Vec<Annotation>>,
202    /// Error message
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub error_message: Option<String>,
205    /// Audio data
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub audio: Option<DeltaAudio>,
208}
209
210/// Delta tool call in a streaming response.
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct DeltaToolCall {
213    /// Tool call index
214    pub index: u32,
215    /// Tool call ID
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub id: Option<String>,
218    /// Tool type
219    #[serde(skip_serializing_if = "Option::is_none")]
220    #[serde(rename = "type")]
221    pub tool_type: Option<ToolCallType>,
222    /// Function call
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub function: Option<DeltaFunctionCall>,
225}
226
227/// Delta function call in a streaming response.
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct DeltaFunctionCall {
230    /// Function name
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub name: Option<String>,
233    /// Function arguments (incremental)
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub arguments: Option<String>,
236}
237
238// Import types from other modules
239use 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}