use serde::{Deserialize, Serialize};
use super::{DeltaAudio, ResponseAudio};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatResponse {
pub id: String,
pub choices: Vec<Choice>,
pub created: i64,
pub model: String,
pub object: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Choice {
pub finish_reason: FinishReason,
pub index: u32,
pub message: ResponseMessage,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
Stop,
Length,
ToolCalls,
ContentFilter,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseMessage {
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_content: Option<String>,
pub role: Role,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Vec<Annotation>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<ResponseAudio>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Annotation {
#[serde(skip_serializing_if = "Option::is_none")]
pub logo_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub publish_time: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub site_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "type")]
pub annotation_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Usage {
pub completion_tokens: u32,
pub prompt_tokens: u32,
pub total_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub completion_tokens_details: Option<CompletionTokensDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens_details: Option<PromptTokensDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub web_search_usage: Option<WebSearchUsage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionTokensDetails {
pub reasoning_tokens: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptTokensDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_tokens: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSearchUsage {
pub tool_usage: u32,
pub page_usage: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamChunk {
pub id: String,
pub choices: Vec<StreamChoice>,
pub created: i64,
pub model: String,
pub object: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamChoice {
pub delta: DeltaMessage,
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<FinishReason>,
pub index: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeltaMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<Role>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<DeltaToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Vec<Annotation>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<DeltaAudio>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeltaToolCall {
pub index: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "type")]
pub tool_type: Option<ToolCallType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub function: Option<DeltaFunctionCall>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeltaFunctionCall {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
}
use super::Role;
use super::message::{ToolCall, ToolCallType};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chat_response_deserialization() {
let json = r#"{
"id": "test-id",
"choices": [{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "Hello!",
"role": "assistant"
}
}],
"created": 1234567890,
"model": "mimo-v2-flash",
"object": "chat.completion"
}"#;
let response: ChatResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.id, "test-id");
assert_eq!(response.choices.len(), 1);
assert_eq!(response.choices[0].message.content, "Hello!");
}
#[test]
fn test_finish_reason_deserialization() {
assert_eq!(
serde_json::from_str::<FinishReason>(r#""stop""#).unwrap(),
FinishReason::Stop
);
assert_eq!(
serde_json::from_str::<FinishReason>(r#""tool_calls""#).unwrap(),
FinishReason::ToolCalls
);
}
#[test]
fn test_usage() {
let json = r#"{
"completion_tokens": 100,
"prompt_tokens": 50,
"total_tokens": 150
}"#;
let usage: Usage = serde_json::from_str(json).unwrap();
assert_eq!(usage.completion_tokens, 100);
assert_eq!(usage.prompt_tokens, 50);
assert_eq!(usage.total_tokens, 150);
}
#[test]
fn test_stream_chunk_deserialization() {
let json = r#"{
"id": "chunk-id",
"choices": [{
"delta": {
"content": "Hello"
},
"index": 0
}],
"created": 1234567890,
"model": "mimo-v2-flash",
"object": "chat.completion.chunk"
}"#;
let chunk: StreamChunk = serde_json::from_str(json).unwrap();
assert_eq!(chunk.id, "chunk-id");
assert_eq!(chunk.choices[0].delta.content, Some("Hello".to_string()));
}
#[test]
fn test_response_with_thinking() {
let json = r#"{
"id": "test-id",
"choices": [{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "The answer is 42.",
"reasoning_content": "Let me think about this...",
"role": "assistant"
}
}],
"created": 1234567890,
"model": "mimo-v2-pro",
"object": "chat.completion"
}"#;
let response: ChatResponse = serde_json::from_str(json).unwrap();
assert_eq!(
response.choices[0].message.reasoning_content,
Some("Let me think about this...".to_string())
);
}
}